diff --git a/.ci.yaml b/.ci.yaml index f264e0e792a4c..64a4de522fd43 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -78,7 +78,10 @@ platform_properties: ] os: Mac-12 device_type: none - xcode: 14e222b + $flutter/osx_sdk : >- + { + "sdk_version": "14e300c" + } mac_arm64: properties: dependencies: >- @@ -88,7 +91,10 @@ platform_properties: os: Mac-12 device_type: none cpu: arm64 - xcode: 14e222b + $flutter/osx_sdk : >- + { + "sdk_version": "14e300c" + } mac_benchmark: properties: dependencies: >- @@ -100,7 +106,10 @@ platform_properties: os: Mac-12 tags: > ["devicelab", "hostonly", "mac"] - xcode: 14e222b + $flutter/osx_sdk : >- + { + "sdk_version": "14e300c" + } mac_x64: properties: dependencies: >- @@ -110,25 +119,30 @@ platform_properties: os: Mac-12 device_type: none cpu: x86 - xcode: 14e222b + $flutter/osx_sdk : >- + { + "sdk_version": "14e300c" + } mac_build_test: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"}, {"dependency": "apple_signing", "version": "version:2022_to_2023"} ] os: Mac-12 device_type: none cpu: x86 - xcode: 14e222b + $flutter/osx_sdk : >- + { + "sdk_version": "14e300c" + } mac_android: properties: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:110.0"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] os: Mac-12 @@ -148,26 +162,30 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"}, {"dependency": "apple_signing", "version": "version:2022_to_2023"} ] os: Mac-12 cpu: x86 device_os: iOS-16 - xcode: 14e222b + $flutter/osx_sdk : >- + { + "sdk_version": "14e300c" + } mac_arm64_ios: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"}, {"dependency": "apple_signing", "version": "none"} ] os: Mac-12 cpu: arm64 device_os: iOS-16 - xcode: 14e222b + $flutter/osx_sdk : >- + { + "sdk_version": "14e300c" + } windows: properties: dependencies: >- @@ -182,7 +200,7 @@ platform_properties: [ {"dependency": "android_sdk", "version": "version:33v6"}, {"dependency": "certs", "version": "version:9563bb"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] os: Windows-10 @@ -225,6 +243,8 @@ targets: - name: Linux packages_autoroller presubmit: false + # TODO(fujino): https://github.com/flutter/flutter/issues/129744 + bringup: true recipe: pub_autoroller/pub_autoroller timeout: 30 enabled_branches: @@ -244,7 +264,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "android_virtual_device", "version": "31"} + {"dependency": "android_virtual_device", "version": "33"}, + {"dependency": "open_jdk", "version": "version:11"} ] tags: > ["framework","hostonly","linux"] @@ -257,7 +278,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:17"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, @@ -276,8 +297,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, - {"dependency": "open_jdk", "version": "version:11"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, + {"dependency": "open_jdk", "version": "version:17"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -295,8 +316,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, - {"dependency": "open_jdk", "version": "version:11"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, + {"dependency": "open_jdk", "version": "version:17"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -381,7 +402,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -392,6 +413,24 @@ targets: - bin/** - .ci.yaml + - name: Linux firebase_oriol33_abstract_method_smoke_test + bringup: true + recipe: firebaselab/firebaselab + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:33v6"}, + {"dependency": "open_jdk", "version": "version:11"} + ] + tags: > + ["firebaselab"] + task_name: abstract_method_smoke_test + physical_devices: >- + ["--device", "model=oriole,version=33"] + virtual_devices: >- + [] + - name: Linux firebase_abstract_method_smoke_test bringup: true # Flaky https://github.com/flutter/flutter/issues/124691 recipe: firebaselab/firebaselab @@ -399,11 +438,28 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:33v6"} + {"dependency": "android_sdk", "version": "version:33v6"}, + {"dependency": "open_jdk", "version": "version:11"} ] tags: > ["firebaselab"] task_name: abstract_method_smoke_test + physical_devices: >- + [ + "--device", "model=redfin,version=30", + "--device", "model=griffin,version=24" + ] + # TODO(flutter/flutter#123331): This device is flaking. + # "--device", "model=Nexus6P,version=25", + virtual_devices: >- + [ + "--device", "model=Nexus5,version=21", + "--device", "model=Nexus5,version=22", + "--device", "model=Nexus5,version=23", + "--device", "model=Nexus6P,version=26", + "--device", "model=Nexus6P,version=27", + "--device", "model=NexusLowRes,version=29" + ] - name: Linux firebase_android_embedding_v2_smoke_test recipe: firebaselab/firebaselab @@ -411,11 +467,28 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:33v6"} + {"dependency": "android_sdk", "version": "version:33v6"}, + {"dependency": "open_jdk", "version": "version:11"} ] tags: > ["firebaselab"] task_name: android_embedding_v2_smoke_test + physical_devices: >- + [ + "--device", "model=redfin,version=30", + "--device", "model=griffin,version=24" + ] + # TODO(flutter/flutter#123331): This device is flaking. + # "--device", "model=Nexus6P,version=25", + virtual_devices: >- + [ + "--device", "model=Nexus5,version=21", + "--device", "model=Nexus5,version=22", + "--device", "model=Nexus5,version=23", + "--device", "model=Nexus6P,version=26", + "--device", "model=Nexus6P,version=27", + "--device", "model=NexusLowRes,version=29" + ] - name: Linux firebase_release_smoke_test recipe: firebaselab/firebaselab @@ -423,11 +496,28 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:33v6"} + {"dependency": "android_sdk", "version": "version:33v6"}, + {"dependency": "open_jdk", "version": "version:11"} ] tags: > ["firebaselab"] task_name: release_smoke_test + physical_devices: >- + [ + "--device", "model=redfin,version=30", + "--device", "model=griffin,version=24" + ] + # TODO(flutter/flutter#123331): This device is flaking. + # "--device", "model=Nexus6P,version=25", + virtual_devices: >- + [ + "--device", "model=Nexus5,version=21", + "--device", "model=Nexus5,version=22", + "--device", "model=Nexus5,version=23", + "--device", "model=Nexus6P,version=26", + "--device", "model=Nexus6P,version=27", + "--device", "model=NexusLowRes,version=29" + ] - name: Linux flutter_packaging_test recipe: packaging/packaging @@ -521,6 +611,7 @@ targets: ["framework", "hostonly", "shard", "linux"] runIf: - dev/** + - examples/api/** - packages/flutter/** - packages/flutter_driver/** - packages/integration_test/** @@ -573,7 +664,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -591,7 +682,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -609,7 +700,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -627,7 +718,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -645,7 +736,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -663,7 +754,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -682,7 +773,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"} + {"dependency": "chrome_and_driver", "version": "version:114.0"} ] tags: > ["devicelab", "hostonly", "linux"] @@ -700,7 +791,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -719,7 +810,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -738,7 +829,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -836,7 +927,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"} + {"dependency": "chrome_and_driver", "version": "version:114.0"} ] tags: > ["devicelab", "hostonly", "linux"] @@ -862,7 +953,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} @@ -886,7 +977,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} @@ -910,7 +1001,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} @@ -934,7 +1025,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} @@ -998,7 +1089,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"} + {"dependency": "chrome_and_driver", "version": "version:114.0"} ] tags: > ["devicelab","hostonly", "linux"] @@ -1006,12 +1097,13 @@ targets: - name: Linux web_benchmarks_html recipe: devicelab/devicelab_drone + presubmit: false timeout: 60 properties: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"} + {"dependency": "chrome_and_driver", "version": "version:114.0"} ] tags: > ["devicelab"] @@ -1021,6 +1113,25 @@ targets: - bin/** - .ci.yaml + - name: Linux web_benchmarks_skwasm + bringup: true + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + dependencies: >- + [ + {"dependency": "android_sdk", "version": "version:33v6"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"} + ] + tags: > + ["devicelab"] + task_name: web_benchmarks_skwasm + runIf: + - dev/** + - bin/** + - .ci.yaml + - name: Linux web_long_running_tests_1_5 recipe: flutter/flutter_drone timeout: 60 @@ -1028,7 +1139,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_long_running_tests @@ -1048,7 +1159,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_long_running_tests @@ -1068,7 +1179,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_long_running_tests @@ -1088,7 +1199,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_long_running_tests @@ -1108,7 +1219,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_long_running_tests @@ -1128,7 +1239,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_tests @@ -1148,7 +1259,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_tests @@ -1168,7 +1279,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_tests @@ -1188,7 +1299,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_tests @@ -1208,7 +1319,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_tests @@ -1228,7 +1339,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_tests @@ -1248,7 +1359,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_tests @@ -1268,7 +1379,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_tests @@ -1288,7 +1399,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_canvaskit_tests @@ -1308,7 +1419,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_canvaskit_tests @@ -1328,7 +1439,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_canvaskit_tests @@ -1348,7 +1459,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_canvaskit_tests @@ -1368,7 +1479,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_canvaskit_tests @@ -1388,7 +1499,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_canvaskit_tests @@ -1408,7 +1519,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_canvaskit_tests @@ -1428,7 +1539,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] shard: web_canvaskit_tests @@ -1448,7 +1559,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] @@ -1477,7 +1588,7 @@ targets: task_name: android_defines_test dependencies: >- [ - {"dependency": "android_virtual_device", "version": "31"} + {"dependency": "android_virtual_device", "version": "33"} ] use_emulator: "true" @@ -2295,6 +2406,26 @@ targets: ["devicelab", "android", "linux", "samsung", "s10"] task_name: platform_views_scroll_perf__timeline_summary + - name: Linux_android platform_views_scroll_perf_impeller__timeline_summary + bringup: true + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux"] + task_name: platform_views_scroll_perf_impeller__timeline_summary + + - name: Linux_samsung_s10 platform_views_scroll_perf_impeller__timeline_summary + bringup: true + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "samsung", "s10"] + task_name: platform_views_scroll_perf_impeller__timeline_summary + - name: Linux_android platform_view__start_up recipe: devicelab/devicelab_drone presubmit: false @@ -2504,6 +2635,7 @@ targets: ["framework", "hostonly", "shard", "linux"] runIf: - dev/** + - examples/api/** - packages/flutter/** - packages/flutter_driver/** - packages/integration_test/** @@ -2522,7 +2654,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] task_name: animated_complex_opacity_perf_macos__e2e_summary @@ -2532,19 +2663,15 @@ targets: recipe: devicelab/devicelab_drone timeout: 60 properties: - dependencies: >- - [ - {"dependency": "xcode", "version": "14e222b"} - ] task_name: basic_material_app_macos__compile - name: Mac build_ios_framework_module_test recipe: devicelab/devicelab_drone timeout: 60 properties: + add_recipes_cq: "true" dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] tags: > @@ -2578,9 +2705,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:110.0"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:17"}, - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] @@ -2597,9 +2723,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:110.0"}, - {"dependency": "open_jdk", "version": "version:11"}, - {"dependency": "xcode", "version": "14e222b"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, + {"dependency": "open_jdk", "version": "version:17"}, {"dependency": "gems", "version": "v3.3.14"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] @@ -2616,9 +2741,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:110.0"}, - {"dependency": "open_jdk", "version": "version:11"}, - {"dependency": "xcode", "version": "14e222b"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, + {"dependency": "open_jdk", "version": "version:17"}, {"dependency": "gems", "version": "v3.3.14"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] @@ -2635,9 +2759,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:110.0"}, - {"dependency": "open_jdk", "version": "version:11"}, - {"dependency": "xcode", "version": "14e222b"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, + {"dependency": "open_jdk", "version": "version:17"}, {"dependency": "gems", "version": "v3.3.14"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] @@ -2653,7 +2776,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] task_name: complex_layout_macos__start_up @@ -2665,7 +2787,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] task_name: complex_layout_scroll_perf_macos__timeline_summary @@ -2687,7 +2808,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] tags: > @@ -2706,7 +2826,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] task_name: flutter_gallery_macos__compile @@ -2718,7 +2837,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] task_name: flutter_gallery_macos__start_up @@ -2752,10 +2870,6 @@ targets: recipe: devicelab/devicelab_drone timeout: 60 properties: - dependencies: >- - [ - {"dependency": "xcode", "version": "14e222b"} - ] task_name: flutter_view_macos__start_up - name: Mac framework_tests_libraries @@ -2791,7 +2905,6 @@ targets: dependencies: >- [ {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "android_sdk", "version": "version:33v6"} @@ -2802,6 +2915,7 @@ targets: ["framework", "hostonly", "shard", "mac"] runIf: - dev/** + - examples/api/** - packages/flutter/** - packages/flutter_driver/** - packages/integration_test/** @@ -2861,10 +2975,6 @@ targets: recipe: devicelab/devicelab_drone timeout: 60 properties: - dependencies: >- - [ - {"dependency": "xcode", "version": "14e222b"} - ] task_name: hello_world_macos__compile - name: Mac integration_ui_test_test_macos @@ -2874,7 +2984,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] tags: > @@ -2942,7 +3051,6 @@ targets: cpu: x86 # Codesigning fails on ARM https://github.com/flutter/flutter/issues/112033 dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] tags: > @@ -2973,10 +3081,6 @@ targets: recipe: devicelab/devicelab_drone timeout: 60 properties: - dependencies: >- - [ - {"dependency": "xcode", "version": "14e222b"} - ] task_name: platform_view_macos__start_up - name: Mac platform_channel_sample_test_macos @@ -2984,10 +3088,6 @@ targets: presubmit: false timeout: 60 properties: - dependencies: >- - [ - {"dependency": "xcode", "version": "14e222b"} - ] tags: > ["devicelab", "hostonly", "mac"] task_name: platform_channel_sample_test_macos @@ -3000,7 +3100,6 @@ targets: [ {"dependency": "android_sdk", "version": "version:33v6"}, {"dependency": "open_jdk", "version": "version:11"}, - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] tags: > @@ -3018,7 +3117,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] tags: > @@ -3070,7 +3168,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] tags: > @@ -3088,7 +3185,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] tags: > @@ -3106,7 +3202,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] shard: tool_host_cross_arch_tests @@ -3125,7 +3220,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] shard: tool_host_cross_arch_tests @@ -3146,9 +3240,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:110.0"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] @@ -3171,9 +3264,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:110.0"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] @@ -3196,9 +3288,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:110.0"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] @@ -3221,9 +3312,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:110.0"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] @@ -3280,10 +3370,6 @@ targets: presubmit: false timeout: 60 properties: - dependencies: >- - [ - {"dependency": "xcode", "version": "14e222b"} - ] tags: > ["framework", "hostonly", "shard", "mac"] validation: verify_binaries_codesigned @@ -3296,10 +3382,6 @@ targets: presubmit: false timeout: 60 properties: - dependencies: >- - [ - {"dependency": "xcode", "version": "14e222b"} - ] tags: > ["framework", "hostonly", "shard", "mac"] validation: verify_binaries_codesigned @@ -3312,7 +3394,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:110.0"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] @@ -3582,6 +3664,16 @@ targets: ["devicelab", "ios", "mac"] task_name: flutter_gallery_ios__start_up + - name: Mac_ios flutter_gallery_ios__start_up_xcode_debug + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: flutter_gallery_ios__start_up_xcode_debug + bringup: true + - name: Mac_ios flutter_view_ios__start_up recipe: devicelab/devicelab_drone presubmit: false @@ -3649,6 +3741,16 @@ targets: ["devicelab", "ios", "mac"] task_name: integration_ui_ios_driver + - name: Mac_ios integration_ui_ios_driver_xcode_debug + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: integration_ui_ios_driver_xcode_debug + bringup: true + - name: Mac_ios integration_ui_ios_frame_number recipe: devicelab/devicelab_drone presubmit: false @@ -3766,6 +3868,17 @@ targets: ["devicelab", "ios", "mac"] task_name: microbenchmarks_ios + # TODO(vashworth): Remove after Xcode 15 and iOS 17 are in CI (https://github.com/flutter/flutter/issues/132128) + - name: Mac_ios microbenchmarks_ios_xcode_debug + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "ios", "mac"] + task_name: microbenchmarks_ios_xcode_debug + bringup: true + - name: Mac_ios native_platform_view_ui_tests_ios recipe: devicelab/devicelab_drone presubmit: false @@ -3964,7 +4077,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] tags: > @@ -3982,7 +4094,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] tags: > @@ -4000,7 +4111,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] tags: > @@ -4033,7 +4143,6 @@ targets: properties: dependencies: >- [ - {"dependency": "xcode", "version": "14e222b"}, {"dependency": "gems", "version": "v3.3.14"} ] tags: > @@ -4067,7 +4176,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:17"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -4085,8 +4194,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, - {"dependency": "open_jdk", "version": "version:11"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, + {"dependency": "open_jdk", "version": "version:17"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, {"dependency": "vs_build", "version": "version:vs2019"} ] @@ -4103,8 +4212,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, - {"dependency": "open_jdk", "version": "version:11"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, + {"dependency": "open_jdk", "version": "version:17"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, {"dependency": "vs_build", "version": "version:vs2019"} ] @@ -4121,8 +4230,8 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, - {"dependency": "open_jdk", "version": "version:11"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, + {"dependency": "open_jdk", "version": "version:17"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, {"dependency": "vs_build", "version": "version:vs2019"} ] @@ -4184,6 +4293,7 @@ targets: ["framework", "hostonly", "shard", "windows"] runIf: - dev/** + - examples/api/** - packages/flutter/** - packages/flutter_driver/** - packages/integration_test/** @@ -4227,7 +4337,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -4258,7 +4368,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -4277,7 +4387,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -4296,7 +4406,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -4328,7 +4438,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -4347,7 +4457,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"} ] tags: > @@ -4420,7 +4530,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -4444,7 +4554,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -4468,7 +4578,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -4492,7 +4602,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -4516,7 +4626,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -4540,7 +4650,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"}, {"dependency": "vs_build", "version": "version:vs2019"} @@ -4603,7 +4713,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] @@ -4624,7 +4734,7 @@ targets: dependencies: >- [ {"dependency": "android_sdk", "version": "version:33v6"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, + {"dependency": "chrome_and_driver", "version": "version:114.0"}, {"dependency": "open_jdk", "version": "version:11"}, {"dependency": "goldctl", "version": "git_revision:f808dcff91b221ae313e540c09d79696cd08b8de"} ] diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index cdcba477e7631..0000000000000 --- a/.cirrus.yml +++ /dev/null @@ -1,158 +0,0 @@ -# CIRRUS CONFIGURATION FILE -# https://cirrus-ci.org/guide/writing-tasks/ - -environment: - # For details about environment variables used in Cirrus, including how encrypted variables work, - # see https://cirrus-ci.org/guide/writing-tasks/#environment-variables - # We change Flutter's directory to include a space in its name (see $CIRRUS_WORKING_DIR) so that - # we constantly test path names with spaces in them. The FLUTTER_SDK_PATH_WITH_SPACE variable must - # therefore have a space in it. - FLUTTER_SDK_PATH_WITH_SPACE: "flutter sdk" - # We force BOT to true so that all our tools know we're in a CI environment. This avoids any - # dependency on precisely how Cirrus is detected by our tools. - BOT: "true" - -gcp_credentials: ENCRYPTED[!9c8e92e8da192ce2a51b7d4ed9948c4250d0bae3660193d9b901196c9692abbebe784d4a32e9f38b328571d65f6e7aed!] - -# LINUX SHARDS -task: - gke_container: - dockerfile: "dev/ci/docker_linux/Dockerfile" - builder_image_name: docker-builder-linux # gce vm image - builder_image_project: flutter-cirrus - cluster_name: test-cluster - zone: us-central1-a - namespace: default - cpu: $CPU - memory: $MEMORY - use_in_memory_disk: $USE_IN_MEMORY_DISK - environment: - # We shrink our default resource requirement as much as possible because that way we are more - # likely to get scheduled. We require 4G of RAM because most of the shards (all but one as of - # October 2019) just get OOM-killed with less. Some shards may need more. When increasing the - # requirements for select shards, please leave a comment on those shards saying when you - # increased the requirements, what numbers you tried, and what the results were. - CPU: 1 # 0.1-8 without compute credits, 0.1-30 with (yes, you can go fractional) - MEMORY: 4G # 256M-24G without compute credits, 256M-90G with - CIRRUS_WORKING_DIR: "/tmp/$FLUTTER_SDK_PATH_WITH_SPACE" - CIRRUS_DOCKER_CONTEXT: "dev/" - PATH: "$CIRRUS_WORKING_DIR/bin:$CIRRUS_WORKING_DIR/bin/cache/dart-sdk/bin:$PATH" - ANDROID_SDK_ROOT: "/opt/android_sdk" - SHOULD_UPDATE_PACKAGES: 'TRUE' # can be overridden at the task level - USE_IN_MEMORY_DISK: false - pub_cache: - folder: $HOME/.pub-cache - fingerprint_script: echo $OS; grep -r --include=pubspec.yaml 'PUBSPEC CHECKSUM' "$CIRRUS_WORKING_DIR" - reupload_on_changes: false - flutter_pkg_cache: - folder: bin/cache/pkg - fingerprint_script: echo $OS; cat bin/internal/*.version - reupload_on_changes: false - artifacts_cache: - folder: bin/cache/artifacts - fingerprint_script: echo $OS; cat bin/internal/*.version - reupload_on_changes: false - setup_script: - - date - - git clean -xffd --exclude=bin/cache/ - - git fetch origin - - git fetch origin master # To set FETCH_HEAD, so that "git merge-base" works. - - flutter config --no-analytics - - if [ "$SHOULD_UPDATE_PACKAGES" == TRUE ]; then flutter update-packages; fi - - flutter doctor -v - - ./dev/bots/accept_android_sdk_licenses.sh - - date - on_failure: - failure_script: - - date - - which flutter - matrix: - - name: analyze-linux # linux-only - only_if: "$CIRRUS_PR != '' && $CIRRUS_BASE_BRANCH == 'master'" - environment: - # Empirically, the analyze-linux shard runs surprisingly fast (under 15 minutes) with just 1 - # CPU. We noticed OOM failures with 6GB 4/2020, so we increased the memory. - CPU: 1 - MEMORY: 8G - script: - - dart --enable-asserts ./dev/bots/analyze.dart - - - name: framework_tests-widgets-linux - only_if: "changesInclude('.cirrus.yml', 'dev/**', 'packages/flutter/**', 'packages/flutter_test/**', 'packages/flutter_tools/lib/src/test/**', 'bin/**') && $CIRRUS_PR != '' && $CIRRUS_BASE_BRANCH == 'master'" - environment: - # We use 3 CPUs because that's the minimum required to get framework_tests-widgets-linux - # running fast enough that it is not the long pole, as of October 2019. - CPU: 3 - script: - - dart --enable-asserts ./dev/bots/test.dart - - - name: framework_tests-libraries-linux - only_if: "changesInclude('.cirrus.yml', 'dev/**', 'packages/flutter/**', 'packages/flutter_test/**', 'packages/flutter_tools/lib/src/test/**', 'bin/**') && $CIRRUS_PR != '' && $CIRRUS_BASE_BRANCH == 'master'" - environment: - # We use 3 CPUs because that's the minimum required to get the - # framework_tests-libraries-linux shard running fast enough that it is not the long pole, as - # of October 2019. - CPU: 3 - script: - - dart --enable-asserts ./dev/bots/test.dart - - - name: framework_tests-misc-linux - # this includes the tests for directories in dev/ - only_if: "changesInclude('.cirrus.yml', 'dev/**', 'packages/flutter/**', 'packages/flutter_goldens/**', 'packages/flutter_test/**', 'packages/flutter_tools/lib/src/test/**', 'bin/**') && $CIRRUS_PR != '' && $CIRRUS_BASE_BRANCH == 'master'" - environment: - # We use 3 CPUs because that's the minimum required to get framework_tests-misc-linux - # running fast enough that it is not the long pole, as of October 2019. - CPU: 3 - script: - - dart --enable-asserts ./dev/bots/test.dart - - - name: tool_tests-general-linux - only_if: "changesInclude('.cirrus.yml', 'dev/**', 'packages/flutter_tools/**', 'bin/**') && $CIRRUS_PR != '' && $CIRRUS_BASE_BRANCH == 'master'" - environment: - # As of November 2019, the tool_tests-general-linux shard got faster with more CPUs up to 4 - # CPUs, and needed at least 10G of RAM to not run out of memory. - CPU: 4 - MEMORY: 10G - SHOULD_UPDATE_PACKAGES: "FALSE" - script: - - (cd packages/flutter_tools; dart pub get) - - (cd packages/flutter_tools/test/data/asset_test/main; dart pub get) - - (cd packages/flutter_tools/test/data/asset_test/font; dart pub get) - - (cd dev/bots; dart pub get) - - dart --enable-asserts ./dev/bots/test.dart - - - name: tool_tests-commands-linux - only_if: "changesInclude('.cirrus.yml', 'dev/**', 'packages/flutter_tools/**', 'bin/**') && $CIRRUS_PR != '' && $CIRRUS_BASE_BRANCH == 'master'" - environment: - # As of October 2019, the tool_tests-commands-linux shard got faster with more CPUs up to 6 - # CPUs, and needed at least 8G of RAM to not run out of memory. - # Increased to 10GB on 19th Nov 2019 due to significant number of OOMKilled failures on PR builds. - CPU: 6 - MEMORY: 10G - SHOULD_UPDATE_PACKAGES: "FALSE" - script: - - (cd packages/flutter_tools; dart pub get) - - (cd dev/bots; dart pub get) - - dart --enable-asserts ./dev/bots/test.dart - - - name: docs-linux # linux-only - environment: - CPU: 4 - MEMORY: 12G - only_if: "$CIRRUS_PR != '' && $CIRRUS_BASE_BRANCH == 'master'" - script: - - ./dev/bots/docs.sh - - - name: customer_testing-linux - only_if: "$CIRRUS_PR != '' && $CIRRUS_BASE_BRANCH == 'master'" - environment: - # Empirically, this shard runs fine at 1 CPU and 4G RAM as of October 2019. We will probably - # want to grow this container when we invite people to add their tests in large numbers. - SHOULD_UPDATE_PACKAGES: "FALSE" - script: - # Cirrus doesn't give us the master branch, so we have to fetch it for ourselves, - # otherwise we won't be able to figure out how old or new our current branch is. - - git config user.email "cirrus-bot@invalid" - - git fetch origin master:master - # The actual logic is in a shell script so that it can be shared between CIs. - - (cd dev/customer_testing/; ./ci.sh) diff --git a/.github/ISSUE_TEMPLATE/4_performance_others.md b/.github/ISSUE_TEMPLATE/4_performance_others.md index 748786a2959c1..6777d05ce28f6 100644 --- a/.github/ISSUE_TEMPLATE/4_performance_others.md +++ b/.github/ISSUE_TEMPLATE/4_performance_others.md @@ -1,8 +1,8 @@ --- -name: My app has some non-speed performance issues. +name: My app has some non-speed performance issues about: You are writing an application but have discovered that it uses too much memory, too much energy (e.g., CPU/GPU usage is high), or its app size is too large. title: '' -labels: 'created via performance template' +labels: 'from: performance template' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/5_performance_speed.md b/.github/ISSUE_TEMPLATE/5_performance_speed.md index ba7f3178adf14..0c7054c535829 100644 --- a/.github/ISSUE_TEMPLATE/5_performance_speed.md +++ b/.github/ISSUE_TEMPLATE/5_performance_speed.md @@ -1,9 +1,9 @@ --- -name: My app is slow or missing frames. +name: My app is slow or missing frames about: You are writing an application but have discovered that it is slow, you are not hitting 60Hz, or you are getting jank (missed frames). title: '' -labels: 'created via performance template' +labels: 'from: performance template' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/6_infrastructure.md b/.github/ISSUE_TEMPLATE/6_infrastructure.md index 802371e4d7d70..d12e41ae11aac 100644 --- a/.github/ISSUE_TEMPLATE/6_infrastructure.md +++ b/.github/ISSUE_TEMPLATE/6_infrastructure.md @@ -15,8 +15,8 @@ assignees: '' If you are filing a feature request, please describe the use case and a proposal. - If you are requesting a small infra task with P0 or P1 priority, please add it to the - "Infra Ticket Queue" project with "New" column, explain why the task is needed and what - actions need to perform (if you happen to know). No need to set an assignee; the infra oncall + If you are requesting a small infra task with P0 priority, please add it to the + "Infra Ticket Queue" project with "New" column, explain why the task is urgent and what + actions need to be performed (if you happen to know). No need to set an assignee; the infra oncall will triage and process the infra ticket queue. --> diff --git a/.github/ISSUE_TEMPLATE/7_cherry_pick.yml b/.github/ISSUE_TEMPLATE/7_cherry_pick.yml index 1d43ea734ee86..8fd172ab4a914 100644 --- a/.github/ISSUE_TEMPLATE/7_cherry_pick.yml +++ b/.github/ISSUE_TEMPLATE/7_cherry_pick.yml @@ -1,4 +1,4 @@ -name: Request a cherry-pick. +name: Request a cherry-pick description: As a contributor, you would like to request that a feature be cherry-picked into a release. title: '[CP] ' labels: ['cp: review'] @@ -6,21 +6,10 @@ assignees: - itsjustkevin - caseyhillers body: -- type: markdown - attributes: - value: | -<!--- -Flutter releases generally follow the given timeline: -1. On Tuesday at 10 AM PT the CP list will be finalized for the week's release - A. If there's any stable CPs, a stable release will be shipped. - B. Based on discretion of the release team, a beta hotfix may be shipped. -2. Tuesday the release engineer for the week will get the release prepped -3. Wednesday morning at 10 AM PT the release will be shipped ---> - type: input id: issue_link attributes: - label: issue_link + label: Issue Link description: What is the link to the issue this cherry-pick is addressing? validations: required: true @@ -44,7 +33,7 @@ Flutter releases generally follow the given timeline: - type: input id: pr_link attributes: - label: pr_link + label: PR Link description: >- Link to an open PR that cherrypick's this into the target release branch. The current branches can be found under release-caniddate-branch.version for [beta](https://github.com/flutter/flutter/blob/beta/bin/internal/release-candidate-branch.version) and [stable](https://github.com/flutter/flutter/blob/stable/bin/internal/release-candidate-branch.version) diff --git a/.github/ISSUE_TEMPLATE/8_design_doc.yml b/.github/ISSUE_TEMPLATE/8_design_doc.yml new file mode 100644 index 0000000000000..76e435697d7d2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/8_design_doc.yml @@ -0,0 +1,32 @@ +name: Share a Flutter design document +description: As a contributor, I would like to share a design document. +labels: ['design doc'] +body: + - type: markdown + attributes: + value: | + Thank you for your interest in contributing to Flutter! + + Please ensure that you are following + https://github.com/flutter/flutter/wiki/Design-Documents + when sharing a design document. + + Please fill out the sections below to the best of your ability. + - type: input + id: document_link + attributes: + label: Document Link + description: | + Insert the "https://flutter.dev/go" link for your document here. + For details on creating a link, see the instructions in + https://flutter.dev/go/template + validations: + required: true + - type: textarea + id: proposal_description + attributes: + label: What problem are you solving? + description: | + Please provide a brief description of the problem you are solving. + validations: + required: true diff --git a/.github/labeler.yml b/.github/labeler.yml index f2091cdd95e40..b134dfdbc907f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -4,111 +4,100 @@ # See https://github.com/actions/labeler/blob/main/README.md for docs. 'a: accessibility': - - any: - - accessibility - - semantics + - '**/accessibility/*' + - '**/*accessibility*' + - '**/semantics/*' + - '**/*semantics*' 'a: animation': - - any: - - animation + - '**/animation/*' + - '**/*animation*' 'a: internationalization': - - any: - - packages/flutter_localizations/** + - packages/flutter_localizations/**/* -'a: test': - - any: - - packages/flutter_driver/** - - packages/flutter_goldens/** - - packages/flutter_goldens_client/** - - packages/flutter_test/** +'a: tests': + - packages/flutter_driver/**/* + - packages/flutter_goldens/**/* + - packages/flutter_goldens_client/**/* + - packages/flutter_test/**/* 'a: text input': - - any: - - text + - '**/text/*' + - '**/*text*' 'd: api docs': - - any: - - examples/api/** + - examples/api/**/* 'd: examples': - - any: - - examples/** - -documentation: - - any: - - examples/api/** + - examples/**/* engine: - - any: - - bin/internal/engine.version + - bin/internal/engine.version 'f: cupertino': - - any: - - cupertino + - '**/cupertino/*' + - '**/*cupertino*' 'f: focus': - - any: - - focus + - '**/focus/*' + - '**/*focus*' 'f: gestures': - - any: - - gestures + - '**/gestures/*' + - '**/*gestures*' -'f: material': - - any: - - material +'f: material design': + - '**/material/*' + - '**/*material*' 'f: routes': - - any: - - navigator - - route + - '**/navigator/*' + - '**/*navigator*' + - '**/route/*' + - '**/*route*' 'f: scrolling': - - any: - - scroll - - sliver - - viewport + - '**/*scroll*' + - '**/scroll/*' + - '**/*sliver*' + - '**/sliver/*' + - '**/*viewport*' + - '**/viewport/*' framework: - - any: - - packages/flutter/** - - packages/flutter_driver/** - - packages/flutter_goldens/** - - packages/flutter_goldens_client/** - - packages/flutter_test/** - - packages/integration_test/** - -integration_test: - - any: - - packages/integration_test/** + - packages/flutter/**/* + - packages/flutter_driver/**/* + - packages/flutter_goldens/**/* + - packages/flutter_goldens_client/**/* + - packages/flutter_test/**/* + - packages/integration_test/**/* + +'f: integration_test': + - packages/integration_test/**/* platform-ios: - - any: - - packages/flutter_tools/lib/src/ios/** + - packages/flutter_tools/lib/src/ios/**/* team: - - any: - - '**/pubspec.yaml' - - '**/fix_data.yaml' - - '**/*.expect' - - '**/*test_fixes*' - - dev/** - - examples/** - - packages/flutter_goldens/** - - packages/flutter_goldens_client/** - -'team: gallery': - - any: - - examples/flutter_gallery/** + - '**/pubspec.yaml' + - '**/fix_data.yaml' + - '**/*.expect' + - '**/*test_fixes*' + - .github/**/* + - dev/**/* + - examples/**/* + - packages/flutter_goldens/**/* + - packages/flutter_goldens_client/**/* + +'customer: gallery': + - examples/flutter_gallery/**/* tech-debt: - - any: - - '**/fix_data.yaml' - - '**/*.expect' - - '**/*test_fixes*' + - '**/fix_data.yaml' + - '**/*.expect' + - '**/*test_fixes*' tool: - - any: - - packages/flutter_tools/** - - packages/fuchsia_remote_debug_protocol/** + - packages/flutter_tools/**/* + - packages/fuchsia_remote_debug_protocol/**/* diff --git a/.github/move.yml b/.github/move.yml deleted file mode 100644 index ba4009ec3fa80..0000000000000 --- a/.github/move.yml +++ /dev/null @@ -1,20 +0,0 @@ -# Configuration for move-issues - https://github.com/dessant/move-issues - -# Delete the command comment when it contains no other content. -deleteCommand: true - -# Close the source issue after moving. -closeSourceIssue: true - -# Lock the source issue after moving. -lockSourceIssue: false - -# Mention issue and comment authors. -mentionAuthors: true - -# Preserve mentions in the issue content. -keepContentMentions: true - -# Set custom aliases for targets -aliases: - ide: flutter-intellij diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000000..b204fadf0463f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: ci + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + pull_request: + push: + branches: + - main + +jobs: + test: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + runs-on: ${{ matrix.os }} + + name: 🧪 Test + + env: + FLUTTER_STORAGE_BASE_URL: https://download.shorebird.dev + + steps: + - name: 📚 Git Checkout + uses: actions/checkout@v4 + + - name: 🎯 Setup Dart + uses: dart-lang/setup-dart@v1 + + - name: 📦 Install Dependencies + run: | + dart pub get -C ./dev/bots + dart pub get -C ./dev/tools + + - name: 🧪 Run Tests + run: dart ./dev/bots/test.dart diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index ace9b15820418..3d5816c9aef03 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest if: ${{ github.repository == 'flutter/flutter' }} steps: - - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 - name: ./bin/flutter test --coverage run: pushd packages/flutter;../../bin/flutter test --coverage -j 1;popd - name: upload coverage diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 3a5577155a088..336d04f809a15 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -17,4 +17,6 @@ jobs: runs-on: ubuntu-latest steps: # Source available at https://github.com/actions/labeler/blob/main/README.md - - uses: actions/labeler@0776a679364a9a16110aac8d0f40f5e11009e327 + - uses: actions/labeler@0967ca812e7fdc8f5f71402a1b486d5bd061fe20 + with: + sync-labels: false diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index ad0e8bfc68c81..31e4e434b4244 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -23,12 +23,12 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@80e868c13c90f172d68d1f4501dee99e2479f7af + uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 with: results_file: results.sarif results_format: sarif diff --git a/.gitignore b/.gitignore index 9a78642e8ce47..c21169b36a8b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Do not remove or rename entries in this file, only add new ones +# See https://github.com/flutter/flutter/issues/128635 for more context. + # Miscellaneous *.class *.lock @@ -32,6 +35,7 @@ /dev/bots/android_tools/ /dev/devicelab/ABresults*.json /dev/docs/doc/ +/dev/docs/api_docs.zip /dev/docs/flutter.docs.zip /dev/docs/lib/ /dev/docs/pubspec.yaml @@ -52,6 +56,7 @@ analysis_benchmark.json **/generated_plugin_registrant.dart .packages .pub-preload-cache/ +.pub-cache/ .pub/ build/ flutter_*.png diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cf1c28ed39fe5..1d73e488ea62e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -121,6 +121,7 @@ presented. 5. [Flutter design doc template](https://flutter.dev/go/template), which should be used when proposing a new technical design. This is a good practice to do before coding more intricate changes. + See also our [guidance for writing design docs](https://github.com/flutter/flutter/wiki/Design-Documents). [![How to contribute to Flutter](https://img.youtube.com/vi/4yBgOBAOx_A/0.jpg)](https://www.youtube.com/watch?v=4yBgOBAOx_A) diff --git a/TESTOWNERS b/TESTOWNERS index 8456415d84dff..18b3626990c39 100644 --- a/TESTOWNERS +++ b/TESTOWNERS @@ -61,7 +61,7 @@ /dev/devicelab/bin/tasks/gradient_dynamic_perf__e2e_summary.dart @flar @flutter/engine /dev/devicelab/bin/tasks/gradient_static_perf__e2e_summary.dart @flar @flutter/engine /dev/devicelab/bin/tasks/gradle_java8_compile_test.dart @reidbaker @flutter/tool -/dev/devicelab/bin/tasks/hot_mode_dev_cycle_linux__benchmark.dart @zanderso @flutter/tool +/dev/devicelab/bin/tasks/hot_mode_dev_cycle_linux__benchmark.dart @christopherfujino @flutter/tool /dev/devicelab/bin/tasks/image_list_jit_reported_duration.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/image_list_reported_duration.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/large_image_changer_perf_android.dart @zanderso @flutter/engine @@ -81,10 +81,12 @@ /dev/devicelab/bin/tasks/picture_cache_perf__e2e_summary.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/platform_channels_benchmarks.dart @gaaclarke @flutter/engine /dev/devicelab/bin/tasks/platform_views_scroll_perf__timeline_summary.dart @zanderso @flutter/engine +/dev/devicelab/bin/tasks/platform_views_scroll_perf_impeller__timeline_summary.dart @bdero @flutter/engine /dev/devicelab/bin/tasks/plugin_dependencies_test.dart @stuartmorgan @flutter/tool /dev/devicelab/bin/tasks/raster_cache_use_memory_perf__e2e_summary.dart @flar @flutter/engine /dev/devicelab/bin/tasks/routing_test.dart @zanderso @flutter/tool /dev/devicelab/bin/tasks/shader_mask_cache_perf__e2e_summary.dart @flar @flutter/engine +/dev/devicelab/bin/tasks/spell_check_test_ios.dart @camsim99 @flutter/android /dev/devicelab/bin/tasks/spell_check_test.dart @camsim99 @flutter/android /dev/devicelab/bin/tasks/textfield_perf__e2e_summary.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/web_size__compile_test.dart @yjbanov @flutter/web @@ -98,7 +100,7 @@ /dev/devicelab/bin/tasks/complex_layout_win__compile.dart @zanderso @flutter/tool /dev/devicelab/bin/tasks/flavors_test_win.dart @zanderso @flutter/tool /dev/devicelab/bin/tasks/flutter_gallery_win__compile.dart @zanderso @flutter/tool -/dev/devicelab/bin/tasks/hot_mode_dev_cycle_win__benchmark.dart @zanderso @flutter/tool +/dev/devicelab/bin/tasks/hot_mode_dev_cycle_win__benchmark.dart @andrewkolos @flutter/tool /dev/devicelab/bin/tasks/windows_chrome_dev_mode.dart @yjbanov @flutter/web ## Mac Android DeviceLab tests @@ -123,7 +125,7 @@ /dev/devicelab/bin/tasks/hello_world__memory.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/hello_world_android__compile.dart @zanderso @flutter/tool /dev/devicelab/bin/tasks/home_scroll_perf__timeline_summary.dart @zanderso @flutter/engine -/dev/devicelab/bin/tasks/hot_mode_dev_cycle__benchmark.dart @zanderso @flutter/tool +/dev/devicelab/bin/tasks/hot_mode_dev_cycle__benchmark.dart @eliasyishak @flutter/tool /dev/devicelab/bin/tasks/hybrid_android_views_integration_test.dart @stuartmorgan @flutter/plugin /dev/devicelab/bin/tasks/imagefiltered_transform_animation_perf__timeline_summary.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/integration_test_test.dart @zanderso @flutter/tool @@ -145,59 +147,62 @@ /dev/devicelab/bin/tasks/tiles_scroll_perf__timeline_summary.dart @zanderso @flutter/engine ## Mac iOS DeviceLab tests -/dev/devicelab/bin/tasks/animated_complex_opacity_perf_ios__e2e_summary.dart @vashworth @flutter/engine +/dev/devicelab/bin/tasks/animated_complex_opacity_perf_ios__e2e_summary.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/animation_with_microtasks_perf_ios__timeline_summary.dart @iskakaushik @flutter/engine -/dev/devicelab/bin/tasks/backdrop_filter_perf_ios__timeline_summary.dart @vashworth @flutter/engine +/dev/devicelab/bin/tasks/backdrop_filter_perf_ios__timeline_summary.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/basic_material_app_ios__compile.dart @vashworth @flutter/tool -/dev/devicelab/bin/tasks/channels_integration_test_ios.dart @vashworth @flutter/engine +/dev/devicelab/bin/tasks/channels_integration_test_ios.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/codegen_integration_mac.dart @zanderso @flutter/tool -/dev/devicelab/bin/tasks/color_filter_and_fade_perf_ios__e2e_summary.dart @vashworth @flutter/engine -/dev/devicelab/bin/tasks/complex_layout_ios__compile.dart @vashworth @flutter/engine -/dev/devicelab/bin/tasks/complex_layout_ios__start_up.dart @vashworth @flutter/engine +/dev/devicelab/bin/tasks/color_filter_and_fade_perf_ios__e2e_summary.dart @cyanglaz @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_ios__compile.dart @cyanglaz @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_ios__start_up.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/complex_layout_scroll_perf_bad_ios__timeline_summary.dart @jonahwilliams @flutter/engine -/dev/devicelab/bin/tasks/complex_layout_scroll_perf_ios__timeline_summary.dart @vashworth @flutter/engine +/dev/devicelab/bin/tasks/complex_layout_scroll_perf_ios__timeline_summary.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/cubic_bezier_perf_ios_sksl_warmup__timeline_summary.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/external_ui_integration_test_ios.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/flavors_test_ios.dart @vashworth @flutter/tool /dev/devicelab/bin/tasks/flutter_gallery__transition_perf_e2e_ios.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/flutter_gallery_ios__compile.dart @vashworth @flutter/engine /dev/devicelab/bin/tasks/flutter_gallery_ios__start_up.dart @vashworth @flutter/engine +/dev/devicelab/bin/tasks/flutter_gallery_ios__start_up_xcode_debug.dart @vashworth @flutter/engine /dev/devicelab/bin/tasks/flutter_gallery_ios_sksl_warmup__transition_perf.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/flutter_view_ios__start_up.dart @zanderso @flutter/engine -/dev/devicelab/bin/tasks/fullscreen_textfield_perf_ios__e2e_summary.dart @vashworth @flutter/engine +/dev/devicelab/bin/tasks/fullscreen_textfield_perf_ios__e2e_summary.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/hello_world_ios__compile.dart @vashworth @flutter/engine -/dev/devicelab/bin/tasks/hot_mode_dev_cycle_ios__benchmark.dart @zanderso @flutter/tool -/dev/devicelab/bin/tasks/hot_mode_dev_cycle_ios_simulator.dart @vashworth @flutter/tool +/dev/devicelab/bin/tasks/hot_mode_dev_cycle_ios__benchmark.dart @cyanglaz @flutter/tool +/dev/devicelab/bin/tasks/hot_mode_dev_cycle_ios_simulator.dart @cyanglaz @flutter/tool /dev/devicelab/bin/tasks/hot_mode_dev_cycle_macos_target__benchmark.dart @cbracken @flutter/tool -/dev/devicelab/bin/tasks/imagefiltered_transform_animation_perf_ios__timeline_summary.dart @vashworth @flutter/engine -/dev/devicelab/bin/tasks/integration_test_test_ios.dart @vashworth @flutter/engine -/dev/devicelab/bin/tasks/integration_ui_ios_driver.dart @vashworth @flutter/tool +/dev/devicelab/bin/tasks/imagefiltered_transform_animation_perf_ios__timeline_summary.dart @cyanglaz @flutter/engine +/dev/devicelab/bin/tasks/integration_test_test_ios.dart @cyanglaz @flutter/engine +/dev/devicelab/bin/tasks/integration_ui_ios_driver.dart @cyanglaz @flutter/tool +/dev/devicelab/bin/tasks/integration_ui_ios_driver_xcode_debug.dart @vashworth @flutter/tool /dev/devicelab/bin/tasks/integration_ui_ios_frame_number.dart @iskakaushik @flutter/engine -/dev/devicelab/bin/tasks/integration_ui_ios_keyboard_resize.dart @vashworth @flutter/engine -/dev/devicelab/bin/tasks/integration_ui_ios_screenshot.dart @vashworth @flutter/tool -/dev/devicelab/bin/tasks/integration_ui_ios_textfield.dart @vashworth @flutter/tool -/dev/devicelab/bin/tasks/ios_app_with_extensions_test.dart @vashworth @flutter/tool +/dev/devicelab/bin/tasks/integration_ui_ios_keyboard_resize.dart @cyanglaz @flutter/engine +/dev/devicelab/bin/tasks/integration_ui_ios_screenshot.dart @cyanglaz @flutter/tool +/dev/devicelab/bin/tasks/integration_ui_ios_textfield.dart @cyanglaz @flutter/tool +/dev/devicelab/bin/tasks/ios_app_with_extensions_test.dart @cyanglaz @flutter/tool /dev/devicelab/bin/tasks/ios_content_validation_test.dart @christopherfujino @flutter/tool /dev/devicelab/bin/tasks/ios_defines_test.dart @vashworth @flutter/tool /dev/devicelab/bin/tasks/ios_picture_cache_complexity_scoring_perf__timeline_summary.dart @flar @flutter/engine /dev/devicelab/bin/tasks/ios_platform_view_tests.dart @stuartmorgan @flutter/plugin /dev/devicelab/bin/tasks/large_image_changer_perf_ios.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/macos_chrome_dev_mode.dart @zanderso @flutter/tool -/dev/devicelab/bin/tasks/microbenchmarks_ios.dart @vashworth @flutter/engine +/dev/devicelab/bin/tasks/microbenchmarks_ios.dart @cyanglaz @flutter/engine +/dev/devicelab/bin/tasks/microbenchmarks_ios_xcode_debug.dart @vashworth @flutter/engine /dev/devicelab/bin/tasks/native_platform_view_ui_tests_ios.dart @hellohuanlin @flutter/ios /dev/devicelab/bin/tasks/new_gallery_ios__transition_perf.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/new_gallery_skia_ios__transition_perf.dart @zanderso @flutter/engine -/dev/devicelab/bin/tasks/platform_channel_sample_test_ios.dart @vashworth @flutter/engine -/dev/devicelab/bin/tasks/platform_channel_sample_test_swift.dart @vashworth @flutter/engine +/dev/devicelab/bin/tasks/platform_channel_sample_test_ios.dart @cyanglaz @flutter/engine +/dev/devicelab/bin/tasks/platform_channel_sample_test_swift.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/platform_channels_benchmarks_ios.dart @gaaclarke @flutter/engine -/dev/devicelab/bin/tasks/platform_interaction_test_ios.dart @vashworth @flutter/engine +/dev/devicelab/bin/tasks/platform_interaction_test_ios.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/platform_view_ios__start_up.dart @stuartmorgan @flutter/plugin -/dev/devicelab/bin/tasks/platform_views_scroll_perf_ios__timeline_summary.dart @vashworth @flutter/engine +/dev/devicelab/bin/tasks/platform_views_scroll_perf_ios__timeline_summary.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/platform_views_scroll_perf_non_intersecting_impeller_ios__timeline_summary.dart @jonahwilliams @flutter/engine -/dev/devicelab/bin/tasks/post_backdrop_filter_perf_ios__timeline_summary.dart @vashworth @flutter/engine +/dev/devicelab/bin/tasks/post_backdrop_filter_perf_ios__timeline_summary.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/route_test_ios.dart @vashworth @flutter/tool -/dev/devicelab/bin/tasks/simple_animation_perf_ios.dart @vashworth @flutter/engine -/dev/devicelab/bin/tasks/tiles_scroll_perf_ios__timeline_summary.dart @vashworth @flutter/engine +/dev/devicelab/bin/tasks/simple_animation_perf_ios.dart @cyanglaz @flutter/engine +/dev/devicelab/bin/tasks/tiles_scroll_perf_ios__timeline_summary.dart @cyanglaz @flutter/engine /dev/devicelab/bin/tasks/animated_blur_backdrop_filter_perf_ios__timeline_summary.dart @jonahwilliams @flutter/engine /dev/devicelab/bin/tasks/draw_points_perf_ios__timeline_summary.dart @jonahwilliams @flutter/engine @@ -219,6 +224,7 @@ /dev/devicelab/bin/tasks/flutter_gallery_macos__start_up.dart @cbracken @flutter/desktop /dev/devicelab/bin/tasks/flutter_gallery_win_desktop__compile.dart @yaakovschectman @flutter/desktop /dev/devicelab/bin/tasks/flutter_gallery_win_desktop__start_up.dart @yaakovschectman @flutter/desktop +/dev/devicelab/bin/tasks/flutter_tool_startup.dart @jensjoha @flutter/tool /dev/devicelab/bin/tasks/flutter_tool_startup__linux.dart @jensjoha @flutter/tool /dev/devicelab/bin/tasks/flutter_tool_startup__macos.dart @jensjoha @flutter/tool /dev/devicelab/bin/tasks/flutter_tool_startup__windows.dart @jensjoha @flutter/tool @@ -263,6 +269,7 @@ /dev/devicelab/bin/tasks/technical_debt__cost.dart @HansMuller @flutter/framework /dev/devicelab/bin/tasks/web_benchmarks_canvaskit.dart @yjbanov @flutter/web /dev/devicelab/bin/tasks/web_benchmarks_html.dart @yjbanov @flutter/web +/dev/devicelab/bin/tasks/web_benchmarks_skwasm.dart @jacksongardner @flutter/web /dev/devicelab/bin/tasks/windows_home_scroll_perf__timeline_summary.dart @jonahwilliams @flutter/engine /dev/devicelab/bin/tasks/windows_startup_test.dart @loic-sharma @flutter/desktop @@ -295,21 +302,21 @@ # TODO(keyonghan): add files/paths for below framework host only testss. # https://github.com/flutter/flutter/issues/82068 # -# build_tests @zanderso @flutter/tool +# build_tests @eliasyishak @flutter/tool # ci_yaml flutter roller @caseyhillers @flutter/infra # coverage @godofredoc @flutter/infra # flutter_packaging @godofredoc @flutter/infra # flutter_plugins @stuartmorgan @flutter/plugin # framework_tests @HansMuller @flutter/framework -# fuchsia_precache @zanderso @flutter/tool +# fuchsia_precache @christopherfujino @flutter/tool # skp_generator @Hixie # test_ownership @keyonghan -# tool_host_cross_arch_tests @zanderso @flutter/tool -# tool_integration_tests @zanderso @flutter/tool -# tool_tests @zanderso @flutter/tool -# verify_binaries_codesigned @christopherfujino @flutter/releases +# tool_host_cross_arch_tests @andrewkolos @flutter/tool +# tool_integration_tests @christopherfujino @flutter/tool +# tool_tests @andrewkolos @flutter/tool +# verify_binaries_codesigned @xilaizhang @flutter/releases # web_canvaskit_tests @yjbanov @flutter/web # web_integration_tests @yjbanov @flutter/web # web_long_running_tests @yjbanov @flutter/web # web_tests @yjbanov @flutter/web -# web_tool_tests @zanderso @flutter/tool +# web_tool_tests @eliasyishak @flutter/tool diff --git a/analysis_options.yaml b/analysis_options.yaml index 1eab21a2243e5..747d8e24b4750 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,16 +1,15 @@ # Specify analysis options. # -# For a list of lints, see: http://dart-lang.github.io/linter/lints/ -# See the configuration guide for more -# https://github.com/dart-lang/sdk/tree/main/pkg/analyzer#configuring-the-analyzer +# For a list of lints, see: https://dart.dev/lints +# For guidelines on configuring static analysis, see: +# https://dart.dev/guides/language/analysis-options # # There are other similar analysis options files in the flutter repos, # which should be kept in sync with this file: # # - analysis_options.yaml (this file) -# - https://github.com/flutter/plugins/blob/master/analysis_options.yaml -# - https://github.com/flutter/engine/blob/master/analysis_options.yaml -# - https://github.com/flutter/packages/blob/master/analysis_options.yaml +# - https://github.com/flutter/engine/blob/main/analysis_options.yaml +# - https://github.com/flutter/packages/blob/main/analysis_options.yaml # # This file contains the analysis options used for code in the flutter/flutter # repository. @@ -31,7 +30,7 @@ analyzer: linter: rules: # This list is derived from the list of all available lints located at - # https://github.com/dart-lang/linter/blob/master/example/all.yaml + # https://github.com/dart-lang/linter/blob/main/example/all.yaml - always_declare_return_types - always_put_control_body_on_new_line # - always_put_required_named_parameters_first # we prefer having parameters in the same order as fields https://github.com/flutter/flutter/issues/10219 @@ -110,7 +109,6 @@ linter: - implicit_call_tearoffs - implicit_reopen - invalid_case_patterns - - iterable_contains_unrelated_type # - join_return_with_assignment # not required by flutter style - leading_newlines_in_multiline_strings - library_annotations @@ -118,7 +116,6 @@ linter: - library_prefixes - library_private_types_in_public_api # - lines_longer_than_80_chars # not required by flutter style - - list_remove_unrelated_type # - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/linter/issues/453 # - matching_super_parameters # blocked on https://github.com/dart-lang/language/issues/2509 - missing_whitespace_between_adjacent_strings @@ -130,6 +127,8 @@ linter: - no_literal_bool_comparisons - no_logic_in_create_state # - no_runtimeType_toString # ok in tests; we enable this only in packages/ + - no_self_assignments + - no_wildcard_variable_uses - non_constant_identifier_names - noop_primitive_operations - null_check_on_nullable_type_parameter @@ -224,7 +223,7 @@ linter: - unnecessary_string_interpolations - unnecessary_this - unnecessary_to_list_in_spreads - # - unreachable_from_main # Do not enable this rule until it is un-marked as "experimental" and carefully re-evaluated. + - unreachable_from_main - unrelated_type_equality_checks - unsafe_html - use_build_context_synchronously diff --git a/bin/internal/engine.version b/bin/internal/engine.version index 4311d66dfc2f1..354e60cd50630 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -6ff02c17268ff8d0dc0cf33c7df0ef76c66cecff +7cf74ca79c3a332497b31afe28cbec935e897a85 diff --git a/bin/internal/flutter_packages.version b/bin/internal/flutter_packages.version index 6e55c855ebb93..3999e9c238279 100644 --- a/bin/internal/flutter_packages.version +++ b/bin/internal/flutter_packages.version @@ -1 +1 @@ -995bfc5f98572224cbe9776ae037697ff44c45cf +771ec9b42a382b2282d2a18f99e96b6276d7063c diff --git a/bin/internal/fuchsia-linux.version b/bin/internal/fuchsia-linux.version index 72a39fbdc4a11..11f77a582348c 100644 --- a/bin/internal/fuchsia-linux.version +++ b/bin/internal/fuchsia-linux.version @@ -1 +1 @@ -nLnQzTesaABpgroOlhBeASv01MRL0Jc8PYKa6rU42hkC +iwgWLB4KaXslnaGwKuAD5S9wamgkF0Mj9a411131XdkC diff --git a/bin/internal/fuchsia-mac.version b/bin/internal/fuchsia-mac.version index 3c348f7b60365..40252d6d5a90b 100644 --- a/bin/internal/fuchsia-mac.version +++ b/bin/internal/fuchsia-mac.version @@ -1 +1 @@ -X4uS4T6_J-VUh9M4AOEc6qP0HLS0HpuLm1nDElf---0C +C3Q7MJBYkiin8zw-fLJ9QmM-8anKHqabR7B2KFuBYUgC diff --git a/bin/internal/release-candidate-branch.version b/bin/internal/release-candidate-branch.version new file mode 100644 index 0000000000000..e9546287aea5d --- /dev/null +++ b/bin/internal/release-candidate-branch.version @@ -0,0 +1 @@ +flutter-3.13-candidate.0 diff --git a/bin/internal/shared.bat b/bin/internal/shared.bat index d2f41602e2c6b..421868a9ee580 100644 --- a/bin/internal/shared.bat +++ b/bin/internal/shared.bat @@ -128,6 +128,7 @@ GOTO :after_subroutine :do_snapshot IF EXIST "%FLUTTER_ROOT%\version" DEL "%FLUTTER_ROOT%\version" + IF EXIST "%FLUTTER_ROOT%\bin\cache\flutter.version.json" DEL "%FLUTTER_ROOT%\bin\cache\flutter.version.json" ECHO: > "%cache_dir%\.dartignore" ECHO Building flutter tool... 1>&2 PUSHD "%flutter_tools_dir%" diff --git a/bin/internal/shared.sh b/bin/internal/shared.sh index 93dc7d7a7567a..3532c23114a57 100644 --- a/bin/internal/shared.sh +++ b/bin/internal/shared.sh @@ -123,7 +123,10 @@ function upgrade_flutter () ( # * STAMP_PATH is an empty file, or # * Contents of STAMP_PATH is not what we are going to compile, or # * pubspec.yaml last modified after pubspec.lock - if [[ ! -f "$SNAPSHOT_PATH" || ! -s "$STAMP_PATH" || "$(cat "$STAMP_PATH")" != "$compilekey" || "$FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" ]]; then + if [[ ! -f "$SNAPSHOT_PATH" || \ + ! -s "$STAMP_PATH" || \ + "$(cat "$STAMP_PATH")" != "$compilekey" || \ + "$FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" ]]; then # Waits for the update lock to be acquired. Placing this check inside the # conditional allows the majority of flutter/dart installations to bypass # the lock entirely, but as a result this required a second verification that @@ -137,6 +140,7 @@ function upgrade_flutter () ( # Fetch Dart... rm -f "$FLUTTER_ROOT/version" + rm -f "$FLUTTER_ROOT/bin/cache/flutter.version.json" touch "$FLUTTER_ROOT/bin/cache/.dartignore" "$FLUTTER_ROOT/bin/internal/update_dart_sdk.sh" diff --git a/dev/automated_tests/flutter_test/print_correct_local_widget_test.dart b/dev/automated_tests/flutter_test/print_correct_local_widget_test.dart index c24a2555f3593..f00ceb52b5d25 100644 --- a/dev/automated_tests/flutter_test/print_correct_local_widget_test.dart +++ b/dev/automated_tests/flutter_test/print_correct_local_widget_test.dart @@ -10,6 +10,7 @@ void main() { // This should fail with user created widget = Row. await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( appBar: AppBar( title: const Text('RenderFlex OverFlow'), diff --git a/dev/automated_tests/pubspec.yaml b/dev/automated_tests/pubspec.yaml index 984c014abadaa..e930a775edacd 100644 --- a/dev/automated_tests/pubspec.yaml +++ b/dev/automated_tests/pubspec.yaml @@ -13,11 +13,11 @@ dependencies: integration_test: sdk: flutter platform: 3.1.0 - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -29,13 +29,13 @@ dependencies: fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -56,12 +56,13 @@ dependencies: stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -71,4 +72,4 @@ flutter: assets: - icon/test.png -# PUBSPEC CHECKSUM: bff1 +# PUBSPEC CHECKSUM: 8050 diff --git a/dev/benchmarks/complex_layout/ios/Runner/Info.plist b/dev/benchmarks/complex_layout/ios/Runner/Info.plist index acf7d7bff061a..dd132e4c2a7c1 100644 --- a/dev/benchmarks/complex_layout/ios/Runner/Info.plist +++ b/dev/benchmarks/complex_layout/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/benchmarks/complex_layout/pubspec.yaml b/dev/benchmarks/complex_layout/pubspec.yaml index 2add711796def..fe6ed3df54dc5 100644 --- a/dev/benchmarks/complex_layout/pubspec.yaml +++ b/dev/benchmarks/complex_layout/pubspec.yaml @@ -21,8 +21,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -32,32 +31,34 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter - test: 1.24.2 + test: 1.24.3 integration_test: sdk: flutter - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -69,7 +70,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -82,4 +83,4 @@ flutter: - packages/flutter_gallery_assets/people/square/ali.png - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png -# PUBSPEC CHECKSUM: 09ca +# PUBSPEC CHECKSUM: 1c29 diff --git a/dev/benchmarks/complex_layout/windows/flutter/generated_plugins.cmake b/dev/benchmarks/complex_layout/windows/flutter/generated_plugins.cmake deleted file mode 100644 index b93c4c30c1670..0000000000000 --- a/dev/benchmarks/complex_layout/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/dev/benchmarks/macrobenchmarks/ios/Runner/Info.plist b/dev/benchmarks/macrobenchmarks/ios/Runner/Info.plist index 6a54a91263e39..3e5abf1294736 100644 --- a/dev/benchmarks/macrobenchmarks/ios/Runner/Info.plist +++ b/dev/benchmarks/macrobenchmarks/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/benchmarks/macrobenchmarks/lib/src/opacity_peephole.dart b/dev/benchmarks/macrobenchmarks/lib/src/opacity_peephole.dart index a596050a03ee0..9e792c05c227f 100644 --- a/dev/benchmarks/macrobenchmarks/lib/src/opacity_peephole.dart +++ b/dev/benchmarks/macrobenchmarks/lib/src/opacity_peephole.dart @@ -21,7 +21,7 @@ class OpacityPeepholePage extends StatelessWidget { body: ListView( key: const Key(kOpacityScrollableName), children: <Widget>[ - for (OpacityPeepholeCase variant in allOpacityPeepholeCases) + for (final OpacityPeepholeCase variant in allOpacityPeepholeCases) ElevatedButton( key: Key(variant.route), child: Text(variant.name), diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_image_decoding.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_image_decoding.dart index fc478993ce450..b68ecc9bcf0ab 100644 --- a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_image_decoding.dart +++ b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_image_decoding.dart @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:html' as html; +import 'dart:js_interop'; import 'dart:typed_data'; import 'dart:ui' as ui; +import 'package:web/web.dart' as web; + import 'recorder.dart'; // Measures the performance of image decoding. @@ -43,8 +45,11 @@ class BenchImageDecoding extends RawRecorder { return; } for (final String imageUrl in _imageUrls) { - final html.Body image = await html.window.fetch(imageUrl) as html.Body; - _imageData.add((await image.arrayBuffer() as ByteBuffer).asUint8List()); + final Future<JSAny?> fetchFuture = web.window.fetch(imageUrl.toJS).toDart; + final web.Body image = (await fetchFuture)! as web.Body; + final Future<JSAny?> imageFuture = image.arrayBuffer().toDart; + final JSArrayBuffer imageBuffer = (await imageFuture)! as JSArrayBuffer; + _imageData.add(imageBuffer.toDart.asUint8List()); } } diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_material_3.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_material_3.dart index a531281c6d468..882ae9266ff5f 100644 --- a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_material_3.dart +++ b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_material_3.dart @@ -3,8 +3,8 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import 'material3.dart'; import 'recorder.dart'; /// Measures how expensive it is to construct the material 3 components screen. @@ -15,2330 +15,6 @@ class BenchMaterial3Components extends WidgetBuildRecorder { @override Widget createWidget() { - return const Material3Components(); - } -} - -const SizedBox rowDivider = SizedBox(width: 20); -const SizedBox colDivider = SizedBox(height: 10); -const double smallSpacing = 10.0; -const double cardWidth = 115; -const double widthConstraint = 450; - -class Material3Components extends StatefulWidget { - const Material3Components({super.key}); - - @override - State<Material3Components> createState() => _Material3ComponentsState(); -} - -class _Material3ComponentsState extends State<Material3Components> { - final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); - - @override - Widget build(BuildContext context) { - return MaterialApp( - home: Scaffold( - key: scaffoldKey, - body: Row( - children: <Widget>[ - Expanded( - child: FirstComponentList( - showNavBottomBar: true, - scaffoldKey: scaffoldKey, - showSecondList: true, - ), - ), - Expanded( - child: SecondComponentList(scaffoldKey: scaffoldKey), - ), - ], - ), - ), - ); - } -} - -class FirstComponentList extends StatelessWidget { - const FirstComponentList({ - super.key, - required this.showNavBottomBar, - required this.scaffoldKey, - required this.showSecondList, - }); - - final bool showNavBottomBar; - final GlobalKey<ScaffoldState> scaffoldKey; - final bool showSecondList; - - @override - Widget build(BuildContext context) { - // Fully traverse this list before moving on. - return FocusTraversalGroup( - child: ListView( - padding: showSecondList - ? const EdgeInsetsDirectional.only(end: smallSpacing) - : EdgeInsets.zero, - children: <Widget>[ - const Actions(), - colDivider, - const Communication(), - colDivider, - const Containment(), - if (!showSecondList) ...<Widget>[ - colDivider, - Navigation(scaffoldKey: scaffoldKey), - colDivider, - const Selection(), - colDivider, - const TextInputs() - ], - ], - ), - ); - } -} - -class SecondComponentList extends StatelessWidget { - const SecondComponentList({ - super.key, - required this.scaffoldKey, - }); - - final GlobalKey<ScaffoldState> scaffoldKey; - - @override - Widget build(BuildContext context) { - // Fully traverse this list before moving on. - return FocusTraversalGroup( - child: ListView( - padding: const EdgeInsetsDirectional.only(end: smallSpacing), - children: <Widget>[ - Navigation(scaffoldKey: scaffoldKey), - colDivider, - const Selection(), - colDivider, - const TextInputs(), - ], - ), - ); - } -} - -class Actions extends StatelessWidget { - const Actions({super.key}); - - @override - Widget build(BuildContext context) { - return const ComponentGroupDecoration(label: 'Actions', children: <Widget>[ - Buttons(), - FloatingActionButtons(), - IconToggleButtons(), - SegmentedButtons(), - ]); - } -} - -class Communication extends StatelessWidget { - const Communication({super.key}); - - @override - Widget build(BuildContext context) { - return const ComponentGroupDecoration(label: 'Communication', children: <Widget>[ - NavigationBars( - selectedIndex: 1, - isExampleBar: true, - isBadgeExample: true, - ), - ProgressIndicators(), - SnackBarSection(), - ]); - } -} - -class Containment extends StatelessWidget { - const Containment({super.key}); - - @override - Widget build(BuildContext context) { - return const ComponentGroupDecoration(label: 'Containment', children: <Widget>[ - BottomSheetSection(), - Cards(), - Dialogs(), - Dividers(), - ]); - } -} - -class Navigation extends StatelessWidget { - const Navigation({super.key, required this.scaffoldKey}); - - final GlobalKey<ScaffoldState> scaffoldKey; - - @override - Widget build(BuildContext context) { - return ComponentGroupDecoration(label: 'Navigation', children: <Widget>[ - const BottomAppBars(), - const NavigationBars( - selectedIndex: 0, - isExampleBar: true, - ), - NavigationDrawers(scaffoldKey: scaffoldKey), - const NavigationRails(), - const Tabs(), - const TopAppBars(), - ]); - } -} - -class Selection extends StatelessWidget { - const Selection({super.key}); - - @override - Widget build(BuildContext context) { - return const ComponentGroupDecoration(label: 'Selection', children: <Widget>[ - Checkboxes(), - Chips(), - Menus(), - Radios(), - Sliders(), - Switches(), - ]); - } -} - -class TextInputs extends StatelessWidget { - const TextInputs({super.key}); - - @override - Widget build(BuildContext context) { - return const ComponentGroupDecoration( - label: 'Text inputs', - children: <Widget>[TextFields()], - ); - } -} - -class Buttons extends StatefulWidget { - const Buttons({super.key}); - - @override - State<Buttons> createState() => _ButtonsState(); -} - -class _ButtonsState extends State<Buttons> { - @override - Widget build(BuildContext context) { - return const ComponentDecoration( - label: 'Common buttons', - tooltipMessage: - 'Use ElevatedButton, FilledButton, FilledButton.tonal, OutlinedButton, or TextButton', - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: <Widget>[ - ButtonsWithoutIcon(isDisabled: false), - ButtonsWithIcon(), - ButtonsWithoutIcon(isDisabled: true), - ], - ), - ), - ); - } -} - -class ButtonsWithoutIcon extends StatelessWidget { - const ButtonsWithoutIcon({super.key, required this.isDisabled}); - - final bool isDisabled; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 5.0), - child: IntrinsicWidth( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: <Widget>[ - ElevatedButton( - onPressed: isDisabled ? null : () {}, - child: const Text('Elevated'), - ), - colDivider, - FilledButton( - onPressed: isDisabled ? null : () {}, - child: const Text('Filled'), - ), - colDivider, - FilledButton.tonal( - onPressed: isDisabled ? null : () {}, - child: const Text('Filled tonal'), - ), - colDivider, - OutlinedButton( - onPressed: isDisabled ? null : () {}, - child: const Text('Outlined'), - ), - colDivider, - TextButton( - onPressed: isDisabled ? null : () {}, - child: const Text('Text'), - ), - ], - ), - ), - ); - } -} - -class ButtonsWithIcon extends StatelessWidget { - const ButtonsWithIcon({super.key}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 10.0), - child: IntrinsicWidth( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: <Widget>[ - ElevatedButton.icon( - onPressed: () {}, - icon: const Icon(Icons.add), - label: const Text('Icon'), - ), - colDivider, - FilledButton.icon( - onPressed: () {}, - label: const Text('Icon'), - icon: const Icon(Icons.add), - ), - colDivider, - FilledButton.tonalIcon( - onPressed: () {}, - label: const Text('Icon'), - icon: const Icon(Icons.add), - ), - colDivider, - OutlinedButton.icon( - onPressed: () {}, - icon: const Icon(Icons.add), - label: const Text('Icon'), - ), - colDivider, - TextButton.icon( - onPressed: () {}, - icon: const Icon(Icons.add), - label: const Text('Icon'), - ) - ], - ), - ), - ); - } -} - -class FloatingActionButtons extends StatelessWidget { - const FloatingActionButtons({super.key}); - - @override - Widget build(BuildContext context) { - return ComponentDecoration( - label: 'Floating action buttons', - tooltipMessage: - 'Use FloatingActionButton or FloatingActionButton.extended', - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - runSpacing: smallSpacing, - spacing: smallSpacing, - children: <Widget>[ - FloatingActionButton.small( - onPressed: () {}, - tooltip: 'Small', - child: const Icon(Icons.add), - ), - FloatingActionButton.extended( - onPressed: () {}, - tooltip: 'Extended', - icon: const Icon(Icons.add), - label: const Text('Create'), - ), - FloatingActionButton( - onPressed: () {}, - tooltip: 'Standard', - child: const Icon(Icons.add), - ), - FloatingActionButton.large( - onPressed: () {}, - tooltip: 'Large', - child: const Icon(Icons.add), - ), - ], - ), - ); - } -} - -class Cards extends StatelessWidget { - const Cards({super.key}); - - @override - Widget build(BuildContext context) { - return ComponentDecoration( - label: 'Cards', - tooltipMessage: 'Use Card', - child: Wrap( - alignment: WrapAlignment.spaceEvenly, - children: <Widget>[ - SizedBox( - width: cardWidth, - child: Card( - child: Container( - padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), - child: Column( - children: <Widget>[ - Align( - alignment: Alignment.topRight, - child: IconButton( - icon: const Icon(Icons.more_vert), - onPressed: () {}, - ), - ), - const SizedBox(height: 20), - const Align( - alignment: Alignment.bottomLeft, - child: Text('Elevated'), - ) - ], - ), - ), - ), - ), - SizedBox( - width: cardWidth, - child: Card( - color: Theme.of(context).colorScheme.surfaceVariant, - elevation: 0, - child: Container( - padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), - child: Column( - children: <Widget>[ - Align( - alignment: Alignment.topRight, - child: IconButton( - icon: const Icon(Icons.more_vert), - onPressed: () {}, - ), - ), - const SizedBox(height: 20), - const Align( - alignment: Alignment.bottomLeft, - child: Text('Filled'), - ) - ], - ), - ), - ), - ), - SizedBox( - width: cardWidth, - child: Card( - elevation: 0, - shape: RoundedRectangleBorder( - side: BorderSide( - color: Theme.of(context).colorScheme.outline, - ), - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - child: Container( - padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), - child: Column( - children: <Widget>[ - Align( - alignment: Alignment.topRight, - child: IconButton( - icon: const Icon(Icons.more_vert), - onPressed: () {}, - ), - ), - const SizedBox(height: 20), - const Align( - alignment: Alignment.bottomLeft, - child: Text('Outlined'), - ) - ], - ), - ), - ), - ), - ], - ), - ); - } -} - -class _ClearButton extends StatelessWidget { - const _ClearButton({required this.controller}); - - final TextEditingController controller; - - @override - Widget build(BuildContext context) => IconButton( - icon: const Icon(Icons.clear), - onPressed: () => controller.clear(), - ); -} - -class TextFields extends StatefulWidget { - const TextFields({super.key}); - - @override - State<TextFields> createState() => _TextFieldsState(); -} - -class _TextFieldsState extends State<TextFields> { - final TextEditingController _controllerFilled = TextEditingController(); - final TextEditingController _controllerOutlined = TextEditingController(); - - @override - Widget build(BuildContext context) { - return ComponentDecoration( - label: 'Text fields', - tooltipMessage: 'Use TextField with different InputDecoration', - child: Column( - children: <Widget>[ - Padding( - padding: const EdgeInsets.all(smallSpacing), - child: TextField( - controller: _controllerFilled, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.search), - suffixIcon: _ClearButton(controller: _controllerFilled), - labelText: 'Filled', - hintText: 'hint text', - helperText: 'supporting text', - filled: true, - ), - ), - ), - Padding( - padding: const EdgeInsets.all(smallSpacing), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: <Widget>[ - Flexible( - child: SizedBox( - width: 200, - child: TextField( - maxLength: 10, - maxLengthEnforcement: MaxLengthEnforcement.none, - controller: _controllerFilled, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.search), - suffixIcon: _ClearButton(controller: _controllerFilled), - labelText: 'Filled', - hintText: 'hint text', - helperText: 'supporting text', - filled: true, - errorText: 'error text', - ), - ), - ), - ), - const SizedBox(width: smallSpacing), - Flexible( - child: SizedBox( - width: 200, - child: TextField( - controller: _controllerFilled, - enabled: false, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.search), - suffixIcon: _ClearButton(controller: _controllerFilled), - labelText: 'Disabled', - hintText: 'hint text', - helperText: 'supporting text', - filled: true, - ), - ), - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.all(smallSpacing), - child: TextField( - controller: _controllerOutlined, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.search), - suffixIcon: _ClearButton(controller: _controllerOutlined), - labelText: 'Outlined', - hintText: 'hint text', - helperText: 'supporting text', - border: const OutlineInputBorder(), - ), - ), - ), - Padding( - padding: const EdgeInsets.all(smallSpacing), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: <Widget>[ - Flexible( - child: SizedBox( - width: 200, - child: TextField( - controller: _controllerOutlined, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.search), - suffixIcon: - _ClearButton(controller: _controllerOutlined), - labelText: 'Outlined', - hintText: 'hint text', - helperText: 'supporting text', - errorText: 'error text', - border: const OutlineInputBorder(), - filled: true, - ), - ), - ), - ), - const SizedBox(width: smallSpacing), - Flexible( - child: SizedBox( - width: 200, - child: TextField( - controller: _controllerOutlined, - enabled: false, - decoration: InputDecoration( - prefixIcon: const Icon(Icons.search), - suffixIcon: - _ClearButton(controller: _controllerOutlined), - labelText: 'Disabled', - hintText: 'hint text', - helperText: 'supporting text', - border: const OutlineInputBorder(), - filled: true, - ), - ), - ), - ), - ])), - ], - ), - ); - } -} - -class Dialogs extends StatefulWidget { - const Dialogs({super.key}); - - @override - State<Dialogs> createState() => _DialogsState(); -} - -class _DialogsState extends State<Dialogs> { - void openDialog(BuildContext context) { - showDialog<void>( - context: context, - builder: (BuildContext context) => AlertDialog( - title: const Text('What is a dialog?'), - content: const Text( - 'A dialog is a type of modal window that appears in front of app content to provide critical information, or prompt for a decision to be made.'), - actions: <Widget>[ - TextButton( - child: const Text('Okay'), - onPressed: () => Navigator.of(context).pop(), - ), - FilledButton( - child: const Text('Dismiss'), - onPressed: () => Navigator.of(context).pop(), - ), - ], - ), - ); - } - - void openFullscreenDialog(BuildContext context) { - showDialog<void>( - context: context, - builder: (BuildContext context) => Dialog.fullscreen( - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Scaffold( - appBar: AppBar( - title: const Text('Full-screen dialog'), - centerTitle: false, - leading: IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.of(context).pop(), - ), - actions: <Widget>[ - TextButton( - child: const Text('Close'), - onPressed: () => Navigator.of(context).pop(), - ), - ], - ), - ), - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - return ComponentDecoration( - label: 'Dialog', - tooltipMessage: - 'Use showDialog with Dialog.fullscreen, AlertDialog, or SimpleDialog', - child: Wrap( - alignment: WrapAlignment.spaceBetween, - children: <Widget>[ - TextButton( - child: const Text( - 'Show dialog', - style: TextStyle(fontWeight: FontWeight.bold), - ), - onPressed: () => openDialog(context), - ), - TextButton( - child: const Text( - 'Show full-screen dialog', - style: TextStyle(fontWeight: FontWeight.bold), - ), - onPressed: () => openFullscreenDialog(context), - ), - ], - ), - ); - } -} - -class Dividers extends StatelessWidget { - const Dividers({super.key}); - - @override - Widget build(BuildContext context) { - return const ComponentDecoration( - label: 'Dividers', - tooltipMessage: 'Use Divider or VerticalDivider', - child: Column( - children: <Widget>[ - Divider(key: Key('divider')), - ], - ), - ); - } -} - -class Switches extends StatelessWidget { - const Switches({super.key}); - - @override - Widget build(BuildContext context) { - return const ComponentDecoration( - label: 'Switches', - tooltipMessage: 'Use SwitchListTile or Switch', - child: Column( - children: <Widget>[ - SwitchRow(isEnabled: true), - SwitchRow(isEnabled: false), - ], - ), - ); - } -} - -class SwitchRow extends StatefulWidget { - const SwitchRow({super.key, required this.isEnabled}); - - final bool isEnabled; - - @override - State<SwitchRow> createState() => _SwitchRowState(); -} - -class _SwitchRowState extends State<SwitchRow> { - bool value0 = false; - bool value1 = true; - - final MaterialStateProperty<Icon?> thumbIcon = - MaterialStateProperty.resolveWith<Icon?>((Set<MaterialState> states) { - if (states.contains(MaterialState.selected)) { - return const Icon(Icons.check); - } - return const Icon(Icons.close); - }); - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: <Widget>[ - Switch( - value: value0, - onChanged: widget.isEnabled - ? (bool value) { - setState(() { - value0 = value; - }); - } - : null, - ), - Switch( - thumbIcon: thumbIcon, - value: value1, - onChanged: widget.isEnabled - ? (bool value) { - setState(() { - value1 = value; - }); - } - : null, - ), - ], - ); - } -} - -class Checkboxes extends StatefulWidget { - const Checkboxes({super.key}); - - @override - State<Checkboxes> createState() => _CheckboxesState(); -} - -class _CheckboxesState extends State<Checkboxes> { - bool? isChecked0 = true; - bool? isChecked1; - bool? isChecked2 = false; - - @override - Widget build(BuildContext context) { - return ComponentDecoration( - label: 'Checkboxes', - tooltipMessage: 'Use CheckboxListTile or Checkbox', - child: Column( - children: <Widget>[ - CheckboxListTile( - tristate: true, - value: isChecked0, - title: const Text('Option 1'), - onChanged: (bool? value) { - setState(() { - isChecked0 = value; - }); - }, - ), - CheckboxListTile( - tristate: true, - value: isChecked1, - title: const Text('Option 2'), - onChanged: (bool? value) { - setState(() { - isChecked1 = value; - }); - }, - ), - CheckboxListTile( - tristate: true, - value: isChecked2, - title: const Text('Option 3'), - onChanged: (bool? value) { - setState(() { - isChecked2 = value; - }); - }, - ), - const CheckboxListTile( - tristate: true, - title: Text('Option 4'), - value: true, - onChanged: null, - ), - ], - ), - ); - } -} - -enum Value { first, second } - -class Radios extends StatefulWidget { - const Radios({super.key}); - - @override - State<Radios> createState() => _RadiosState(); -} - -enum Options { option1, option2, option3 } - -class _RadiosState extends State<Radios> { - Options? _selectedOption = Options.option1; - - @override - Widget build(BuildContext context) { - return ComponentDecoration( - label: 'Radio buttons', - tooltipMessage: 'Use RadioListTile<T> or Radio<T>', - child: Column( - children: <Widget>[ - RadioListTile<Options>( - title: const Text('Option 1'), - value: Options.option1, - groupValue: _selectedOption, - onChanged: (Options? value) { - setState(() { - _selectedOption = value; - }); - }, - ), - RadioListTile<Options>( - title: const Text('Option 2'), - value: Options.option2, - groupValue: _selectedOption, - onChanged: (Options? value) { - setState(() { - _selectedOption = value; - }); - }, - ), - RadioListTile<Options>( - title: const Text('Option 3'), - value: Options.option3, - groupValue: _selectedOption, - onChanged: null, - ), - ], - ), - ); - } -} - -class ProgressIndicators extends StatefulWidget { - const ProgressIndicators({super.key}); - - @override - State<ProgressIndicators> createState() => _ProgressIndicatorsState(); -} - -class _ProgressIndicatorsState extends State<ProgressIndicators> { - bool playProgressIndicator = false; - - @override - Widget build(BuildContext context) { - final double? progressValue = playProgressIndicator ? null : 0.7; - - return ComponentDecoration( - label: 'Progress indicators', - tooltipMessage: - 'Use CircularProgressIndicator or LinearProgressIndicator', - child: Column( - children: <Widget>[ - Row( - children: <Widget>[ - IconButton( - isSelected: playProgressIndicator, - selectedIcon: const Icon(Icons.pause), - icon: const Icon(Icons.play_arrow), - onPressed: () { - setState(() { - playProgressIndicator = !playProgressIndicator; - }); - }, - ), - Expanded( - child: Row( - children: <Widget>[ - rowDivider, - CircularProgressIndicator( - value: progressValue, - ), - rowDivider, - Expanded( - child: LinearProgressIndicator( - value: progressValue, - ), - ), - rowDivider, - ], - ), - ), - ], - ), - ], - ), - ); - } -} - -const List<NavigationDestination> appBarDestinations = <NavigationDestination>[ - NavigationDestination( - tooltip: '', - icon: Icon(Icons.widgets_outlined), - label: 'Components', - selectedIcon: Icon(Icons.widgets), - ), - NavigationDestination( - tooltip: '', - icon: Icon(Icons.format_paint_outlined), - label: 'Color', - selectedIcon: Icon(Icons.format_paint), - ), - NavigationDestination( - tooltip: '', - icon: Icon(Icons.text_snippet_outlined), - label: 'Typography', - selectedIcon: Icon(Icons.text_snippet), - ), - NavigationDestination( - tooltip: '', - icon: Icon(Icons.invert_colors_on_outlined), - label: 'Elevation', - selectedIcon: Icon(Icons.opacity), - ) -]; - -const List<Widget> exampleBarDestinations = <Widget>[ - NavigationDestination( - tooltip: '', - icon: Icon(Icons.explore_outlined), - label: 'Explore', - selectedIcon: Icon(Icons.explore), - ), - NavigationDestination( - tooltip: '', - icon: Icon(Icons.pets_outlined), - label: 'Pets', - selectedIcon: Icon(Icons.pets), - ), - NavigationDestination( - tooltip: '', - icon: Icon(Icons.account_box_outlined), - label: 'Account', - selectedIcon: Icon(Icons.account_box), - ) -]; - -List<Widget> barWithBadgeDestinations = <Widget>[ - NavigationDestination( - tooltip: '', - icon: Badge.count(count: 1000, child: const Icon(Icons.mail_outlined)), - label: 'Mail', - selectedIcon: Badge.count(count: 1000, child: const Icon(Icons.mail)), - ), - const NavigationDestination( - tooltip: '', - icon: Badge(label: Text('10'), child: Icon(Icons.chat_bubble_outline)), - label: 'Chat', - selectedIcon: Badge(label: Text('10'), child: Icon(Icons.chat_bubble)), - ), - const NavigationDestination( - tooltip: '', - icon: Badge(child: Icon(Icons.group_outlined)), - label: 'Rooms', - selectedIcon: Badge(child: Icon(Icons.group_rounded)), - ), - NavigationDestination( - tooltip: '', - icon: Badge.count(count: 3, child: const Icon(Icons.videocam_outlined)), - label: 'Meet', - selectedIcon: Badge.count(count: 3, child: const Icon(Icons.videocam)), - ) -]; - -class NavigationBars extends StatefulWidget { - const NavigationBars({ - super.key, - this.onSelectItem, - required this.selectedIndex, - required this.isExampleBar, - this.isBadgeExample = false, - }); - - final void Function(int)? onSelectItem; - final int selectedIndex; - final bool isExampleBar; - final bool isBadgeExample; - - @override - State<NavigationBars> createState() => _NavigationBarsState(); -} - -class _NavigationBarsState extends State<NavigationBars> { - late int selectedIndex; - - @override - void initState() { - super.initState(); - selectedIndex = widget.selectedIndex; - } - - @override - void didUpdateWidget(covariant NavigationBars oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.selectedIndex != oldWidget.selectedIndex) { - selectedIndex = widget.selectedIndex; - } - } - - @override - Widget build(BuildContext context) { - // App NavigationBar should get first focus. - Widget navigationBar = Focus( - autofocus: !(widget.isExampleBar || widget.isBadgeExample), - child: NavigationBar( - selectedIndex: selectedIndex, - onDestinationSelected: (int index) { - setState(() { - selectedIndex = index; - }); - if (!widget.isExampleBar) { - widget.onSelectItem!(index); - } - }, - destinations: widget.isExampleBar && widget.isBadgeExample - ? barWithBadgeDestinations - : widget.isExampleBar - ? exampleBarDestinations - : appBarDestinations, - ), - ); - - if (widget.isExampleBar && widget.isBadgeExample) { - navigationBar = ComponentDecoration( - label: 'Badges', - tooltipMessage: 'Use Badge or Badge.count', - child: navigationBar); - } else if (widget.isExampleBar) { - navigationBar = ComponentDecoration( - label: 'Navigation bar', - tooltipMessage: 'Use NavigationBar', - child: navigationBar); - } - - return navigationBar; - } -} - -class IconToggleButtons extends StatefulWidget { - const IconToggleButtons({super.key}); - - @override - State<IconToggleButtons> createState() => _IconToggleButtonsState(); -} - -class _IconToggleButtonsState extends State<IconToggleButtons> { - @override - Widget build(BuildContext context) { - return const ComponentDecoration( - label: 'Icon buttons', - tooltipMessage: 'Use IconButton', - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: <Widget>[ - Column( - // Standard IconButton - children: <Widget>[ - IconToggleButton( - isEnabled: true, - tooltip: 'Standard', - ), - colDivider, - IconToggleButton( - isEnabled: false, - tooltip: 'Standard (disabled)', - ), - ], - ), - Column( - children: <Widget>[ - // Filled IconButton - IconToggleButton( - isEnabled: true, - tooltip: 'Filled', - getDefaultStyle: enabledFilledButtonStyle, - ), - colDivider, - IconToggleButton( - isEnabled: false, - tooltip: 'Filled (disabled)', - getDefaultStyle: disabledFilledButtonStyle, - ), - ], - ), - Column( - children: <Widget>[ - // Filled Tonal IconButton - IconToggleButton( - isEnabled: true, - tooltip: 'Filled tonal', - getDefaultStyle: enabledFilledTonalButtonStyle, - ), - colDivider, - IconToggleButton( - isEnabled: false, - tooltip: 'Filled tonal (disabled)', - getDefaultStyle: disabledFilledTonalButtonStyle, - ), - ], - ), - Column( - children: <Widget>[ - // Outlined IconButton - IconToggleButton( - isEnabled: true, - tooltip: 'Outlined', - getDefaultStyle: enabledOutlinedButtonStyle, - ), - colDivider, - IconToggleButton( - isEnabled: false, - tooltip: 'Outlined (disabled)', - getDefaultStyle: disabledOutlinedButtonStyle, - ), - ], - ), - ], - ), - ); - } -} - -class IconToggleButton extends StatefulWidget { - const IconToggleButton({ - required this.isEnabled, - required this.tooltip, - this.getDefaultStyle, - super.key, - }); - - final bool isEnabled; - final String tooltip; - final ButtonStyle? Function(bool, ColorScheme)? getDefaultStyle; - - @override - State<IconToggleButton> createState() => _IconToggleButtonState(); -} - -class _IconToggleButtonState extends State<IconToggleButton> { - bool selected = false; - - @override - Widget build(BuildContext context) { - final ColorScheme colors = Theme.of(context).colorScheme; - final VoidCallback? onPressed = widget.isEnabled - ? () { - setState(() { - selected = !selected; - }); - } - : null; - final ButtonStyle? style = widget.getDefaultStyle?.call(selected, colors); - - return IconButton( - visualDensity: VisualDensity.standard, - isSelected: selected, - tooltip: widget.tooltip, - icon: const Icon(Icons.settings_outlined), - selectedIcon: const Icon(Icons.settings), - onPressed: onPressed, - style: style, - ); - } -} - -ButtonStyle enabledFilledButtonStyle(bool selected, ColorScheme colors) { - return IconButton.styleFrom( - foregroundColor: selected ? colors.onPrimary : colors.primary, - backgroundColor: selected ? colors.primary : colors.surfaceVariant, - disabledForegroundColor: colors.onSurface.withOpacity(0.38), - disabledBackgroundColor: colors.onSurface.withOpacity(0.12), - hoverColor: selected - ? colors.onPrimary.withOpacity(0.08) - : colors.primary.withOpacity(0.08), - focusColor: selected - ? colors.onPrimary.withOpacity(0.12) - : colors.primary.withOpacity(0.12), - highlightColor: selected - ? colors.onPrimary.withOpacity(0.12) - : colors.primary.withOpacity(0.12), - ); -} - -ButtonStyle disabledFilledButtonStyle(bool selected, ColorScheme colors) { - return IconButton.styleFrom( - disabledForegroundColor: colors.onSurface.withOpacity(0.38), - disabledBackgroundColor: colors.onSurface.withOpacity(0.12), - ); -} - -ButtonStyle enabledFilledTonalButtonStyle(bool selected, ColorScheme colors) { - return IconButton.styleFrom( - foregroundColor: - selected ? colors.onSecondaryContainer : colors.onSurfaceVariant, - backgroundColor: - selected ? colors.secondaryContainer : colors.surfaceVariant, - hoverColor: selected - ? colors.onSecondaryContainer.withOpacity(0.08) - : colors.onSurfaceVariant.withOpacity(0.08), - focusColor: selected - ? colors.onSecondaryContainer.withOpacity(0.12) - : colors.onSurfaceVariant.withOpacity(0.12), - highlightColor: selected - ? colors.onSecondaryContainer.withOpacity(0.12) - : colors.onSurfaceVariant.withOpacity(0.12), - ); -} - -ButtonStyle disabledFilledTonalButtonStyle(bool selected, ColorScheme colors) { - return IconButton.styleFrom( - disabledForegroundColor: colors.onSurface.withOpacity(0.38), - disabledBackgroundColor: colors.onSurface.withOpacity(0.12), - ); -} - -ButtonStyle enabledOutlinedButtonStyle(bool selected, ColorScheme colors) { - return IconButton.styleFrom( - backgroundColor: selected ? colors.inverseSurface : null, - hoverColor: selected - ? colors.onInverseSurface.withOpacity(0.08) - : colors.onSurfaceVariant.withOpacity(0.08), - focusColor: selected - ? colors.onInverseSurface.withOpacity(0.12) - : colors.onSurfaceVariant.withOpacity(0.12), - highlightColor: selected - ? colors.onInverseSurface.withOpacity(0.12) - : colors.onSurface.withOpacity(0.12), - side: BorderSide(color: colors.outline), - ).copyWith( - foregroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { - if (states.contains(MaterialState.selected)) { - return colors.onInverseSurface; - } - if (states.contains(MaterialState.pressed)) { - return colors.onSurface; - } - return null; - }), - ); -} - -ButtonStyle disabledOutlinedButtonStyle(bool selected, ColorScheme colors) { - return IconButton.styleFrom( - disabledForegroundColor: colors.onSurface.withOpacity(0.38), - disabledBackgroundColor: - selected ? colors.onSurface.withOpacity(0.12) : null, - side: selected ? null : BorderSide(color: colors.outline.withOpacity(0.12)), - ); -} - -class Chips extends StatefulWidget { - const Chips({super.key}); - - @override - State<Chips> createState() => _ChipsState(); -} - -class _ChipsState extends State<Chips> { - bool isFiltered = true; - - @override - Widget build(BuildContext context) { - return ComponentDecoration( - label: 'Chips', - tooltipMessage: - 'Use ActionChip, FilterChip, or InputChip. \nActionChip can also be used for suggestion chip', - child: Column( - children: <Widget>[ - Wrap( - spacing: smallSpacing, - runSpacing: smallSpacing, - children: <Widget>[ - ActionChip( - label: const Text('Assist'), - avatar: const Icon(Icons.event), - onPressed: () {}, - ), - FilterChip( - label: const Text('Filter'), - selected: isFiltered, - onSelected: (bool selected) { - setState(() => isFiltered = selected); - }, - ), - InputChip( - label: const Text('Input'), - onPressed: () {}, - onDeleted: () {}, - ), - ActionChip( - label: const Text('Suggestion'), - onPressed: () {}, - ), - ], - ), - colDivider, - Wrap( - spacing: smallSpacing, - runSpacing: smallSpacing, - children: <Widget>[ - const ActionChip( - label: Text('Assist'), - avatar: Icon(Icons.event), - ), - FilterChip( - label: const Text('Filter'), - selected: isFiltered, - onSelected: null, - ), - InputChip( - label: const Text('Input'), - onDeleted: () {}, - isEnabled: false, - ), - const ActionChip( - label: Text('Suggestion'), - ), - ], - ), - ], - ), - ); - } -} - -class SegmentedButtons extends StatelessWidget { - const SegmentedButtons({super.key}); - - @override - Widget build(BuildContext context) { - return const ComponentDecoration( - label: 'Segmented buttons', - tooltipMessage: 'Use SegmentedButton<T>', - child: Column( - children: <Widget>[ - SingleChoice(), - colDivider, - MultipleChoice(), - ], - ), - ); - } -} - -enum Calendar { day, week, month, year } - -class SingleChoice extends StatefulWidget { - const SingleChoice({super.key}); - - @override - State<SingleChoice> createState() => _SingleChoiceState(); -} - -class _SingleChoiceState extends State<SingleChoice> { - Calendar calendarView = Calendar.day; - - @override - Widget build(BuildContext context) { - return SegmentedButton<Calendar>( - segments: const <ButtonSegment<Calendar>>[ - ButtonSegment<Calendar>( - value: Calendar.day, - label: Text('Day'), - icon: Icon(Icons.calendar_view_day)), - ButtonSegment<Calendar>( - value: Calendar.week, - label: Text('Week'), - icon: Icon(Icons.calendar_view_week)), - ButtonSegment<Calendar>( - value: Calendar.month, - label: Text('Month'), - icon: Icon(Icons.calendar_view_month)), - ButtonSegment<Calendar>( - value: Calendar.year, - label: Text('Year'), - icon: Icon(Icons.calendar_today)), - ], - selected: <Calendar>{calendarView}, - onSelectionChanged: (Set<Calendar> newSelection) { - setState(() { - // By default there is only a single segment that can be - // selected at one time, so its value is always the first - // item in the selected set. - calendarView = newSelection.first; - }); - }, - ); - } -} - -enum Sizes { extraSmall, small, medium, large, extraLarge } - -class MultipleChoice extends StatefulWidget { - const MultipleChoice({super.key}); - - @override - State<MultipleChoice> createState() => _MultipleChoiceState(); -} - -class _MultipleChoiceState extends State<MultipleChoice> { - Set<Sizes> selection = <Sizes>{Sizes.large, Sizes.extraLarge}; - - @override - Widget build(BuildContext context) { - return SegmentedButton<Sizes>( - segments: const <ButtonSegment<Sizes>>[ - ButtonSegment<Sizes>(value: Sizes.extraSmall, label: Text('XS')), - ButtonSegment<Sizes>(value: Sizes.small, label: Text('S')), - ButtonSegment<Sizes>(value: Sizes.medium, label: Text('M')), - ButtonSegment<Sizes>( - value: Sizes.large, - label: Text('L'), - ), - ButtonSegment<Sizes>(value: Sizes.extraLarge, label: Text('XL')), - ], - selected: selection, - onSelectionChanged: (Set<Sizes> newSelection) { - setState(() { - selection = newSelection; - }); - }, - multiSelectionEnabled: true, - ); - } -} - -class SnackBarSection extends StatelessWidget { - const SnackBarSection({super.key}); - - @override - Widget build(BuildContext context) { - return ComponentDecoration( - label: 'Snackbar', - tooltipMessage: - 'Use ScaffoldMessenger.of(context).showSnackBar with SnackBar', - child: TextButton( - onPressed: () { - final SnackBar snackBar = SnackBar( - behavior: SnackBarBehavior.floating, - width: 400.0, - content: const Text('This is a snackbar'), - action: SnackBarAction( - label: 'Close', - onPressed: () {}, - ), - ); - - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar(snackBar); - }, - child: const Text( - 'Show snackbar', - style: TextStyle(fontWeight: FontWeight.bold), - ), - ), - ); - } -} - -class BottomSheetSection extends StatefulWidget { - const BottomSheetSection({super.key}); - - @override - State<BottomSheetSection> createState() => _BottomSheetSectionState(); -} - -class _BottomSheetSectionState extends State<BottomSheetSection> { - bool isNonModalBottomSheetOpen = false; - PersistentBottomSheetController<void>? _nonModalBottomSheetController; - - @override - Widget build(BuildContext context) { - List<Widget> buttonList = <Widget>[ - IconButton(onPressed: () {}, icon: const Icon(Icons.share_outlined)), - IconButton(onPressed: () {}, icon: const Icon(Icons.add)), - IconButton(onPressed: () {}, icon: const Icon(Icons.delete_outline)), - IconButton(onPressed: () {}, icon: const Icon(Icons.archive_outlined)), - IconButton(onPressed: () {}, icon: const Icon(Icons.settings_outlined)), - IconButton(onPressed: () {}, icon: const Icon(Icons.favorite_border)), - ]; - const List<Text> labelList = <Text>[ - Text('Share'), - Text('Add to'), - Text('Trash'), - Text('Archive'), - Text('Settings'), - Text('Favorite') - ]; - - buttonList = List<Widget>.generate( - buttonList.length, - (int index) => Padding( - padding: const EdgeInsets.fromLTRB(20.0, 30.0, 20.0, 20.0), - child: Column( - children: <Widget>[ - buttonList[index], - labelList[index], - ], - ), - )); - - return ComponentDecoration( - label: 'Bottom sheet', - tooltipMessage: 'Use showModalBottomSheet<T> or showBottomSheet<T>', - child: Wrap( - alignment: WrapAlignment.spaceEvenly, - children: <Widget>[ - TextButton( - child: const Text( - 'Show modal bottom sheet', - style: TextStyle(fontWeight: FontWeight.bold), - ), - onPressed: () { - showModalBottomSheet<void>( - context: context, - constraints: const BoxConstraints(maxWidth: 640), - builder: (BuildContext context) { - return SizedBox( - height: 150, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32.0), - child: ListView( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - children: buttonList, - ), - ), - ); - }, - ); - }, - ), - TextButton( - child: Text( - isNonModalBottomSheetOpen - ? 'Hide bottom sheet' - : 'Show bottom sheet', - style: const TextStyle(fontWeight: FontWeight.bold), - ), - onPressed: () { - if (isNonModalBottomSheetOpen) { - _nonModalBottomSheetController?.close(); - setState(() { - isNonModalBottomSheetOpen = false; - }); - return; - } else { - setState(() { - isNonModalBottomSheetOpen = true; - }); - } - - _nonModalBottomSheetController = showBottomSheet<void>( - elevation: 8.0, - context: context, - constraints: const BoxConstraints(maxWidth: 640), - builder: (BuildContext context) { - return SizedBox( - height: 150, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32.0), - child: ListView( - shrinkWrap: true, - scrollDirection: Axis.horizontal, - children: buttonList, - ), - ), - ); - }, - ); - }, - ), - ], - ), - ); - } -} - -class BottomAppBars extends StatelessWidget { - const BottomAppBars({super.key}); - - @override - Widget build(BuildContext context) { - return ComponentDecoration( - label: 'Bottom app bar', - tooltipMessage: 'Use BottomAppBar', - child: Column( - children: <Widget>[ - SizedBox( - height: 80, - child: Scaffold( - floatingActionButton: FloatingActionButton( - onPressed: () {}, - elevation: 0.0, - child: const Icon(Icons.add), - ), - floatingActionButtonLocation: - FloatingActionButtonLocation.endContained, - bottomNavigationBar: BottomAppBar( - child: Row( - children: <Widget>[ - const IconButtonAnchorExample(), - IconButton( - tooltip: 'Search', - icon: const Icon(Icons.search), - onPressed: () {}, - ), - IconButton( - tooltip: 'Favorite', - icon: const Icon(Icons.favorite), - onPressed: () {}, - ), - ], - ), - ), - ), - ), - ], - ), - ); - } -} - -class IconButtonAnchorExample extends StatelessWidget { - const IconButtonAnchorExample({super.key}); - - @override - Widget build(BuildContext context) { - return MenuAnchor( - builder: (BuildContext context, MenuController controller, Widget? child) { - return IconButton( - onPressed: () { - if (controller.isOpen) { - controller.close(); - } else { - controller.open(); - } - }, - icon: const Icon(Icons.more_vert), - ); - }, - menuChildren: <Widget>[ - MenuItemButton( - child: const Text('Menu 1'), - onPressed: () {}, - ), - MenuItemButton( - child: const Text('Menu 2'), - onPressed: () {}, - ), - SubmenuButton( - menuChildren: <Widget>[ - MenuItemButton( - onPressed: () {}, - child: const Text('Menu 3.1'), - ), - MenuItemButton( - onPressed: () {}, - child: const Text('Menu 3.2'), - ), - MenuItemButton( - onPressed: () {}, - child: const Text('Menu 3.3'), - ), - ], - child: const Text('Menu 3'), - ), - ], - ); - } -} - -class ButtonAnchorExample extends StatelessWidget { - const ButtonAnchorExample({super.key}); - - @override - Widget build(BuildContext context) { - return MenuAnchor( - builder: (BuildContext context, MenuController controller, Widget? child) { - return FilledButton.tonal( - onPressed: () { - if (controller.isOpen) { - controller.close(); - } else { - controller.open(); - } - }, - child: const Text('Show menu'), - ); - }, - menuChildren: <Widget>[ - MenuItemButton( - leadingIcon: const Icon(Icons.people_alt_outlined), - child: const Text('Item 1'), - onPressed: () {}, - ), - MenuItemButton( - leadingIcon: const Icon(Icons.remove_red_eye_outlined), - child: const Text('Item 2'), - onPressed: () {}, - ), - MenuItemButton( - leadingIcon: const Icon(Icons.refresh), - onPressed: () {}, - child: const Text('Item 3'), - ), - ], - ); - } -} - -class NavigationDrawers extends StatelessWidget { - const NavigationDrawers({super.key, required this.scaffoldKey}); - final GlobalKey<ScaffoldState> scaffoldKey; - - @override - Widget build(BuildContext context) { - return ComponentDecoration( - label: 'Navigation drawer', - tooltipMessage: - 'Use NavigationDrawer. For modal navigation drawers, see Scaffold.endDrawer', - child: Column( - children: <Widget>[ - const SizedBox(height: 520, child: NavigationDrawerSection()), - colDivider, - colDivider, - TextButton( - child: const Text('Show modal navigation drawer', - style: TextStyle(fontWeight: FontWeight.bold)), - onPressed: () { - scaffoldKey.currentState!.openEndDrawer(); - }, - ), - ], - ), - ); - } -} - -class NavigationDrawerSection extends StatefulWidget { - const NavigationDrawerSection({super.key}); - - @override - State<NavigationDrawerSection> createState() => - _NavigationDrawerSectionState(); -} - -class _NavigationDrawerSectionState extends State<NavigationDrawerSection> { - int navDrawerIndex = 0; - - @override - Widget build(BuildContext context) { - return NavigationDrawer( - onDestinationSelected: (int selectedIndex) { - setState(() { - navDrawerIndex = selectedIndex; - }); - }, - selectedIndex: navDrawerIndex, - children: <Widget>[ - Padding( - padding: const EdgeInsets.fromLTRB(28, 16, 16, 10), - child: Text( - 'Mail', - style: Theme.of(context).textTheme.titleSmall, - ), - ), - ...destinations.map((ExampleDestination destination) { - return NavigationDrawerDestination( - label: Text(destination.label), - icon: destination.icon, - selectedIcon: destination.selectedIcon, - ); - }), - const Divider(indent: 28, endIndent: 28), - Padding( - padding: const EdgeInsets.fromLTRB(28, 16, 16, 10), - child: Text( - 'Labels', - style: Theme.of(context).textTheme.titleSmall, - ), - ), - ...labelDestinations.map((ExampleDestination destination) { - return NavigationDrawerDestination( - label: Text(destination.label), - icon: destination.icon, - selectedIcon: destination.selectedIcon, - ); - }), - ], - ); - } -} - -class ExampleDestination { - const ExampleDestination(this.label, this.icon, this.selectedIcon); - - final String label; - final Widget icon; - final Widget selectedIcon; -} - -const List<ExampleDestination> destinations = <ExampleDestination>[ - ExampleDestination('Inbox', Icon(Icons.inbox_outlined), Icon(Icons.inbox)), - ExampleDestination('Outbox', Icon(Icons.send_outlined), Icon(Icons.send)), - ExampleDestination( - 'Favorites', Icon(Icons.favorite_outline), Icon(Icons.favorite)), - ExampleDestination('Trash', Icon(Icons.delete_outline), Icon(Icons.delete)), -]; - -const List<ExampleDestination> labelDestinations = <ExampleDestination>[ - ExampleDestination( - 'Family', Icon(Icons.bookmark_border), Icon(Icons.bookmark)), - ExampleDestination( - 'School', Icon(Icons.bookmark_border), Icon(Icons.bookmark)), - ExampleDestination('Work', Icon(Icons.bookmark_border), Icon(Icons.bookmark)), -]; - -class NavigationRails extends StatelessWidget { - const NavigationRails({super.key}); - - @override - Widget build(BuildContext context) { - return const ComponentDecoration( - label: 'Navigation rail', - tooltipMessage: 'Use NavigationRail', - child: IntrinsicWidth( - child: SizedBox(height: 420, child: NavigationRailSection())), - ); - } -} - -class NavigationRailSection extends StatefulWidget { - const NavigationRailSection({super.key}); - - @override - State<NavigationRailSection> createState() => _NavigationRailSectionState(); -} - -class _NavigationRailSectionState extends State<NavigationRailSection> { - int navRailIndex = 0; - - @override - Widget build(BuildContext context) { - return NavigationRail( - onDestinationSelected: (int selectedIndex) { - setState(() { - navRailIndex = selectedIndex; - }); - }, - elevation: 4, - leading: FloatingActionButton( - child: const Icon(Icons.create), onPressed: () {}), - groupAlignment: 0.0, - selectedIndex: navRailIndex, - labelType: NavigationRailLabelType.selected, - destinations: <NavigationRailDestination>[ - ...destinations.map((ExampleDestination destination) { - return NavigationRailDestination( - label: Text(destination.label), - icon: destination.icon, - selectedIcon: destination.selectedIcon, - ); - }), - ], - ); - } -} - -class Tabs extends StatefulWidget { - const Tabs({super.key}); - - @override - State<Tabs> createState() => _TabsState(); -} - -class _TabsState extends State<Tabs> with TickerProviderStateMixin { - late TabController _tabController; - - @override - void initState() { - super.initState(); - _tabController = TabController(length: 3, vsync: this); - } - - @override - Widget build(BuildContext context) { - return ComponentDecoration( - label: 'Tabs', - tooltipMessage: 'Use TabBar', - child: SizedBox( - height: 80, - child: Scaffold( - appBar: AppBar( - bottom: TabBar( - controller: _tabController, - tabs: const <Widget>[ - Tab( - icon: Icon(Icons.videocam_outlined), - text: 'Video', - iconMargin: EdgeInsets.zero, - ), - Tab( - icon: Icon(Icons.photo_outlined), - text: 'Photos', - iconMargin: EdgeInsets.zero, - ), - Tab( - icon: Icon(Icons.audiotrack_sharp), - text: 'Audio', - iconMargin: EdgeInsets.zero, - ), - ], - ), - ), - ), - ), - ); - } -} - -class TopAppBars extends StatelessWidget { - const TopAppBars({super.key}); - - static final List<IconButton> actions = <IconButton>[ - IconButton(icon: const Icon(Icons.attach_file), onPressed: () {}), - IconButton(icon: const Icon(Icons.event), onPressed: () {}), - IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}), - ]; - - @override - Widget build(BuildContext context) { - return ComponentDecoration( - label: 'Top app bars', - tooltipMessage: - 'Use AppBar, SliverAppBar, SliverAppBar.medium, or SliverAppBar.large', - child: Column( - children: <Widget>[ - AppBar( - title: const Text('Center-aligned'), - leading: const BackButton(), - actions: <Widget>[ - IconButton( - iconSize: 32, - icon: const Icon(Icons.account_circle_outlined), - onPressed: () {}, - ), - ], - centerTitle: true, - ), - colDivider, - AppBar( - title: const Text('Small'), - leading: const BackButton(), - actions: actions, - centerTitle: false, - ), - colDivider, - SizedBox( - height: 100, - child: CustomScrollView( - slivers: <Widget>[ - SliverAppBar.medium( - title: const Text('Medium'), - leading: const BackButton(), - actions: actions, - ), - const SliverFillRemaining(), - ], - ), - ), - colDivider, - SizedBox( - height: 130, - child: CustomScrollView( - slivers: <Widget>[ - SliverAppBar.large( - title: const Text('Large'), - leading: const BackButton(), - actions: actions, - ), - const SliverFillRemaining(), - ], - ), - ), - ], - ), - ); - } -} - -class Menus extends StatefulWidget { - const Menus({super.key}); - - @override - State<Menus> createState() => _MenusState(); -} - -class _MenusState extends State<Menus> { - final TextEditingController colorController = TextEditingController(); - final TextEditingController iconController = TextEditingController(); - IconLabel? selectedIcon = IconLabel.smile; - ColorLabel? selectedColor; - - @override - Widget build(BuildContext context) { - final List<DropdownMenuEntry<ColorLabel>> colorEntries = - <DropdownMenuEntry<ColorLabel>>[]; - for (final ColorLabel color in ColorLabel.values) { - colorEntries.add(DropdownMenuEntry<ColorLabel>( - value: color, label: color.label, enabled: color.label != 'Grey')); - } - - final List<DropdownMenuEntry<IconLabel>> iconEntries = - <DropdownMenuEntry<IconLabel>>[]; - for (final IconLabel icon in IconLabel.values) { - iconEntries - .add(DropdownMenuEntry<IconLabel>(value: icon, label: icon.label)); - } - - return ComponentDecoration( - label: 'Menus', - tooltipMessage: 'Use MenuAnchor or DropdownMenu<T>', - child: Column( - children: <Widget>[ - const Row( - mainAxisAlignment: MainAxisAlignment.center, - children: <Widget>[ - ButtonAnchorExample(), - rowDivider, - IconButtonAnchorExample(), - ], - ), - colDivider, - Wrap( - alignment: WrapAlignment.spaceAround, - runAlignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - spacing: smallSpacing, - runSpacing: smallSpacing, - children: <Widget>[ - DropdownMenu<ColorLabel>( - controller: colorController, - label: const Text('Color'), - enableFilter: true, - dropdownMenuEntries: colorEntries, - inputDecorationTheme: const InputDecorationTheme(filled: true), - onSelected: (ColorLabel? color) { - setState(() { - selectedColor = color; - }); - }, - ), - DropdownMenu<IconLabel>( - initialSelection: IconLabel.smile, - controller: iconController, - leadingIcon: const Icon(Icons.search), - label: const Text('Icon'), - dropdownMenuEntries: iconEntries, - onSelected: (IconLabel? icon) { - setState(() { - selectedIcon = icon; - }); - }, - ), - Icon( - selectedIcon?.icon, - color: selectedColor?.color ?? Colors.grey.withOpacity(0.5), - ) - ], - ), - ], - ), - ); - } -} - -enum ColorLabel { - blue('Blue', Colors.blue), - pink('Pink', Colors.pink), - green('Green', Colors.green), - yellow('Yellow', Colors.yellow), - grey('Grey', Colors.grey); - - const ColorLabel(this.label, this.color); - final String label; - final Color color; -} - -enum IconLabel { - smile('Smile', Icons.sentiment_satisfied_outlined), - cloud( - 'Cloud', - Icons.cloud_outlined, - ), - brush('Brush', Icons.brush_outlined), - heart('Heart', Icons.favorite); - - const IconLabel(this.label, this.icon); - final String label; - final IconData icon; -} - -class Sliders extends StatefulWidget { - const Sliders({super.key}); - - @override - State<Sliders> createState() => _SlidersState(); -} - -class _SlidersState extends State<Sliders> { - double sliderValue0 = 30.0; - double sliderValue1 = 20.0; - - @override - Widget build(BuildContext context) { - return ComponentDecoration( - label: 'Sliders', - tooltipMessage: 'Use Slider or RangeSlider', - child: Column( - children: <Widget>[ - Slider( - max: 100, - value: sliderValue0, - onChanged: (double value) { - setState(() { - sliderValue0 = value; - }); - }, - ), - const SizedBox(height: 20), - Slider( - max: 100, - divisions: 5, - value: sliderValue1, - label: sliderValue1.round().toString(), - onChanged: (double value) { - setState(() { - sliderValue1 = value; - }); - }, - ), - ], - )); - } -} - -class ComponentDecoration extends StatefulWidget { - const ComponentDecoration({ - super.key, - required this.label, - required this.child, - this.tooltipMessage = '', - }); - - final String label; - final Widget child; - final String? tooltipMessage; - - @override - State<ComponentDecoration> createState() => _ComponentDecorationState(); -} - -class _ComponentDecorationState extends State<ComponentDecoration> { - final FocusNode focusNode = FocusNode(); - - @override - Widget build(BuildContext context) { - return RepaintBoundary( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: smallSpacing), - child: Column( - children: <Widget>[ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: <Widget>[ - Text(widget.label, - style: Theme.of(context).textTheme.titleSmall), - Tooltip( - message: widget.tooltipMessage, - child: const Padding( - padding: EdgeInsets.symmetric(horizontal: 5.0), - child: Icon(Icons.info_outline, size: 16)), - ), - ], - ), - ConstrainedBox( - constraints: - const BoxConstraints.tightFor(width: widthConstraint), - // Tapping within the a component card should request focus - // for that component's children. - child: Focus( - focusNode: focusNode, - canRequestFocus: true, - child: GestureDetector( - onTapDown: (_) { - focusNode.requestFocus(); - }, - behavior: HitTestBehavior.opaque, - child: Card( - elevation: 0, - shape: RoundedRectangleBorder( - side: BorderSide( - color: Theme.of(context).colorScheme.outlineVariant, - ), - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 5.0, vertical: 20.0), - child: Center( - child: widget.child, - ), - ), - ), - ), - ), - ), - ], - ), - ), - ); - } -} - -class ComponentGroupDecoration extends StatelessWidget { - const ComponentGroupDecoration( - {super.key, required this.label, required this.children}); - - final String label; - final List<Widget> children; - - @override - Widget build(BuildContext context) { - // Fully traverse this component group before moving on - return FocusTraversalGroup( - child: Card( - margin: EdgeInsets.zero, - elevation: 0, - color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 20.0), - child: Center( - child: Column( - children: <Widget>[ - Text(label, style: Theme.of(context).textTheme.titleLarge), - colDivider, - ...children - ], - ), - ), - ), - ), - ); + return const TwoColumnMaterial3Components(); } } diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_material_3_semantics.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_material_3_semantics.dart new file mode 100644 index 0000000000000..8b230803c83d7 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_material_3_semantics.dart @@ -0,0 +1,147 @@ +// 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/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/semantics.dart'; + +import 'material3.dart'; +import 'recorder.dart'; + +/// Measures the cost of semantics when constructing screens containing +/// Material 3 widgets. +class BenchMaterial3Semantics extends WidgetBuildRecorder { + BenchMaterial3Semantics() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_material3_semantics'; + + @override + Future<void> setUpAll() async { + FlutterTimeline.debugCollectionEnabled = true; + super.setUpAll(); + SemanticsBinding.instance.ensureSemantics(); + } + + @override + Future<void> tearDownAll() async { + FlutterTimeline.debugReset(); + } + + @override + void frameDidDraw() { + // Only record frames that show the widget. Frames that remove the widget + // are not interesting. + if (showWidget) { + final AggregatedTimings timings = FlutterTimeline.debugCollect(); + final AggregatedTimedBlock semanticsBlock = timings.getAggregated('SEMANTICS'); + final AggregatedTimedBlock getFragmentBlock = timings.getAggregated('Semantics.GetFragment'); + final AggregatedTimedBlock compileChildrenBlock = timings.getAggregated('Semantics.compileChildren'); + profile!.addTimedBlock(semanticsBlock, reported: true); + profile!.addTimedBlock(getFragmentBlock, reported: true); + profile!.addTimedBlock(compileChildrenBlock, reported: true); + } + + super.frameDidDraw(); + FlutterTimeline.debugReset(); + } + + @override + Widget createWidget() { + return const SingleColumnMaterial3Components(); + } +} + +/// Measures the cost of semantics when scrolling screens containing Material 3 +/// widgets. +/// +/// The implementation uses a ListView that jumps the scroll position between +/// 0 and 1 every frame. Such a small delta is not enough for lazy rendering to +/// add/remove widgets, but its enough to trigger the framework to recompute +/// some of the semantics. +/// +/// The expected output numbers of this benchmarks should be very small as +/// scrolling a list view should be a matter of shifting some widgets and +/// updating the projected clip imposed by the viewport. As of June 2023, the +/// numbers are not great. Semantics consumes >50% of frame time. +class BenchMaterial3ScrollSemantics extends WidgetRecorder { + BenchMaterial3ScrollSemantics() : super(name: benchmarkName); + + static const String benchmarkName = 'bench_material3_scroll_semantics'; + + @override + Future<void> setUpAll() async { + FlutterTimeline.debugCollectionEnabled = true; + super.setUpAll(); + SemanticsBinding.instance.ensureSemantics(); + } + + @override + Future<void> tearDownAll() async { + FlutterTimeline.debugReset(); + } + + @override + void frameDidDraw() { + final AggregatedTimings timings = FlutterTimeline.debugCollect(); + final AggregatedTimedBlock semanticsBlock = timings.getAggregated('SEMANTICS'); + final AggregatedTimedBlock getFragmentBlock = timings.getAggregated('Semantics.GetFragment'); + final AggregatedTimedBlock compileChildrenBlock = timings.getAggregated('Semantics.compileChildren'); + profile!.addTimedBlock(semanticsBlock, reported: true); + profile!.addTimedBlock(getFragmentBlock, reported: true); + profile!.addTimedBlock(compileChildrenBlock, reported: true); + + super.frameDidDraw(); + FlutterTimeline.debugReset(); + } + + @override + Widget createWidget() => _ScrollTest(); +} + +class _ScrollTest extends StatefulWidget { + @override + State<_ScrollTest> createState() => _ScrollTestState(); +} + +class _ScrollTestState extends State<_ScrollTest> with SingleTickerProviderStateMixin { + late final Ticker ticker; + late final ScrollController scrollController; + + @override + void initState() { + super.initState(); + + scrollController = ScrollController(); + + bool forward = true; + + // A one-off timer is necessary to allow the framework to measure the + // available scroll extents before the scroll controller can be exercised + // to change the scroll position. + Timer.run(() { + ticker = createTicker((_) { + scrollController.jumpTo(forward ? 1 : 0); + forward = !forward; + }); + ticker.start(); + }); + } + + @override + void dispose() { + ticker.dispose(); + scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SingleColumnMaterial3Components( + scrollController: scrollController, + ); + } +} diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_scroll.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_scroll.dart index 6eabda48ed6bc..7fbb80e1992ca 100644 --- a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_scroll.dart +++ b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_mouse_region_grid_scroll.dart @@ -130,10 +130,10 @@ class _Tester { final int frameDurationMs = fullFrameDuration.inMilliseconds; final int fullFrames = duration.inMilliseconds ~/ frameDurationMs; - final Offset fullFrameOffset = offset * ((frameDurationMs as double) / durationMs); + final Offset fullFrameOffset = offset * (frameDurationMs.toDouble() / durationMs); final Duration finalFrameDuration = duration - fullFrameDuration * fullFrames; - final Offset finalFrameOffset = offset - fullFrameOffset * (fullFrames as double); + final Offset finalFrameOffset = offset - fullFrameOffset * fullFrames.toDouble(); await gesture.down(start, timeStamp: currentTime); diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_platform_view_infinite_scroll.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_platform_view_infinite_scroll.dart index 01bcecd746c27..378127476f071 100644 --- a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_platform_view_infinite_scroll.dart +++ b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_platform_view_infinite_scroll.dart @@ -3,10 +3,10 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; import 'dart:ui_web' as ui_web; import 'package:flutter/material.dart'; +import 'package:web/web.dart' as web; import 'recorder.dart'; @@ -14,16 +14,17 @@ const String benchmarkViewType = 'benchmark_element'; void _registerFactory() { ui_web.platformViewRegistry.registerViewFactory(benchmarkViewType, (int viewId) { - final html.Element htmlElement = html.DivElement(); + final web.HTMLElement htmlElement = + web.document.createElement('div') as web.HTMLDivElement; htmlElement.id = '${benchmarkViewType}_$viewId'; htmlElement.innerText = 'Google'; htmlElement.style - ..width = '100%' - ..height = '100%' - ..color = 'black' - ..backgroundColor = 'rgba(0, 255, 0, .5)' - ..textAlign = 'center' - ..border = '1px solid black'; + ..setProperty('width', '100%') + ..setProperty('height', '100%') + ..setProperty('color', 'black') + ..setProperty('backgroundColor', 'rgba(0, 255, 0, .5)') + ..setProperty('textAlign', 'center') + ..setProperty('border', '1px solid black'); return htmlElement; }); } diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_layout.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_layout.dart index e1156dd383044..6788ec8b71232 100644 --- a/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_layout.dart +++ b/dev/benchmarks/macrobenchmarks/lib/src/web/bench_text_layout.dart @@ -203,6 +203,7 @@ class BenchBuildColorsGrid extends WidgetBuildRecorder { @override Future<void> setUpAll() async { + super.setUpAll(); registerEngineBenchmarkValueListener('text_layout', (num value) { _textLayoutMicros += value; }); diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/material3.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/material3.dart new file mode 100644 index 0000000000000..1ec8ae15bf062 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/lib/src/web/material3.dart @@ -0,0 +1,2369 @@ +// 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/material.dart'; +import 'package:flutter/services.dart'; + +const SizedBox rowDivider = SizedBox(width: 20); +const SizedBox colDivider = SizedBox(height: 10); +const double smallSpacing = 10.0; +const double cardWidth = 115; +const double widthConstraint = 450; +final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); + +class SingleColumnMaterial3Components extends StatelessWidget { + const SingleColumnMaterial3Components({ + super.key, + this.scrollController, + }); + + final ScrollController? scrollController; + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + key: scaffoldKey, + body: ListView( + controller: scrollController, + children: <Widget>[ + const Actions(), + colDivider, + const Communication(), + colDivider, + const Containment(), + colDivider, + Navigation(scaffoldKey: scaffoldKey), + colDivider, + const Selection(), + colDivider, + const TextInputs(), + colDivider, + Navigation(scaffoldKey: scaffoldKey), + colDivider, + const Selection(), + colDivider, + const TextInputs(), + ], + ), + ), + ); + } +} + +class TwoColumnMaterial3Components extends StatefulWidget { + const TwoColumnMaterial3Components({super.key}); + + @override + State<TwoColumnMaterial3Components> createState() => _TwoColumnMaterial3ComponentsState(); +} + +class _TwoColumnMaterial3ComponentsState extends State<TwoColumnMaterial3Components> { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + key: scaffoldKey, + body: Row( + children: <Widget>[ + Expanded( + child: FirstComponentList( + showNavBottomBar: true, + scaffoldKey: scaffoldKey, + showSecondList: true, + ), + ), + Expanded( + child: SecondComponentList(scaffoldKey: scaffoldKey), + ), + ], + ), + ), + ); + } +} + +class FirstComponentList extends StatelessWidget { + const FirstComponentList({ + super.key, + required this.showNavBottomBar, + required this.scaffoldKey, + required this.showSecondList, + }); + + final bool showNavBottomBar; + final GlobalKey<ScaffoldState> scaffoldKey; + final bool showSecondList; + + @override + Widget build(BuildContext context) { + // Fully traverse this list before moving on. + return FocusTraversalGroup( + child: ListView( + padding: showSecondList + ? const EdgeInsetsDirectional.only(end: smallSpacing) + : EdgeInsets.zero, + children: <Widget>[ + const Actions(), + colDivider, + const Communication(), + colDivider, + const Containment(), + if (!showSecondList) ...<Widget>[ + colDivider, + Navigation(scaffoldKey: scaffoldKey), + colDivider, + const Selection(), + colDivider, + const TextInputs() + ], + ], + ), + ); + } +} + +class SecondComponentList extends StatelessWidget { + const SecondComponentList({ + super.key, + required this.scaffoldKey, + }); + + final GlobalKey<ScaffoldState> scaffoldKey; + + @override + Widget build(BuildContext context) { + // Fully traverse this list before moving on. + return FocusTraversalGroup( + child: ListView( + padding: const EdgeInsetsDirectional.only(end: smallSpacing), + children: <Widget>[ + Navigation(scaffoldKey: scaffoldKey), + colDivider, + const Selection(), + colDivider, + const TextInputs(), + ], + ), + ); + } +} + +class Actions extends StatelessWidget { + const Actions({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration(label: 'Actions', children: <Widget>[ + Buttons(), + FloatingActionButtons(), + IconToggleButtons(), + SegmentedButtons(), + ]); + } +} + +class Communication extends StatelessWidget { + const Communication({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration(label: 'Communication', children: <Widget>[ + NavigationBars( + selectedIndex: 1, + isExampleBar: true, + isBadgeExample: true, + ), + ProgressIndicators(), + SnackBarSection(), + ]); + } +} + +class Containment extends StatelessWidget { + const Containment({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration(label: 'Containment', children: <Widget>[ + BottomSheetSection(), + Cards(), + Dialogs(), + Dividers(), + ]); + } +} + +class Navigation extends StatelessWidget { + const Navigation({super.key, required this.scaffoldKey}); + + final GlobalKey<ScaffoldState> scaffoldKey; + + @override + Widget build(BuildContext context) { + return ComponentGroupDecoration(label: 'Navigation', children: <Widget>[ + const BottomAppBars(), + const NavigationBars( + selectedIndex: 0, + isExampleBar: true, + ), + NavigationDrawers(scaffoldKey: scaffoldKey), + const NavigationRails(), + const Tabs(), + const TopAppBars(), + ]); + } +} + +class Selection extends StatelessWidget { + const Selection({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration(label: 'Selection', children: <Widget>[ + Checkboxes(), + Chips(), + Menus(), + Radios(), + Sliders(), + Switches(), + ]); + } +} + +class TextInputs extends StatelessWidget { + const TextInputs({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentGroupDecoration( + label: 'Text inputs', + children: <Widget>[TextFields()], + ); + } +} + +class Buttons extends StatefulWidget { + const Buttons({super.key}); + + @override + State<Buttons> createState() => _ButtonsState(); +} + +class _ButtonsState extends State<Buttons> { + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Common buttons', + tooltipMessage: + 'Use ElevatedButton, FilledButton, FilledButton.tonal, OutlinedButton, or TextButton', + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: <Widget>[ + ButtonsWithoutIcon(isDisabled: false), + ButtonsWithIcon(), + ButtonsWithoutIcon(isDisabled: true), + ], + ), + ), + ); + } +} + +class ButtonsWithoutIcon extends StatelessWidget { + const ButtonsWithoutIcon({super.key, required this.isDisabled}); + + final bool isDisabled; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: <Widget>[ + ElevatedButton( + onPressed: isDisabled ? null : () {}, + child: const Text('Elevated'), + ), + colDivider, + FilledButton( + onPressed: isDisabled ? null : () {}, + child: const Text('Filled'), + ), + colDivider, + FilledButton.tonal( + onPressed: isDisabled ? null : () {}, + child: const Text('Filled tonal'), + ), + colDivider, + OutlinedButton( + onPressed: isDisabled ? null : () {}, + child: const Text('Outlined'), + ), + colDivider, + TextButton( + onPressed: isDisabled ? null : () {}, + child: const Text('Text'), + ), + ], + ), + ), + ); + } +} + +class ButtonsWithIcon extends StatelessWidget { + const ButtonsWithIcon({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: <Widget>[ + ElevatedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Icon'), + ), + colDivider, + FilledButton.icon( + onPressed: () {}, + label: const Text('Icon'), + icon: const Icon(Icons.add), + ), + colDivider, + FilledButton.tonalIcon( + onPressed: () {}, + label: const Text('Icon'), + icon: const Icon(Icons.add), + ), + colDivider, + OutlinedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Icon'), + ), + colDivider, + TextButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Icon'), + ) + ], + ), + ), + ); + } +} + +class FloatingActionButtons extends StatelessWidget { + const FloatingActionButtons({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Floating action buttons', + tooltipMessage: + 'Use FloatingActionButton or FloatingActionButton.extended', + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + runSpacing: smallSpacing, + spacing: smallSpacing, + children: <Widget>[ + FloatingActionButton.small( + onPressed: () {}, + tooltip: 'Small', + child: const Icon(Icons.add), + ), + FloatingActionButton.extended( + onPressed: () {}, + tooltip: 'Extended', + icon: const Icon(Icons.add), + label: const Text('Create'), + ), + FloatingActionButton( + onPressed: () {}, + tooltip: 'Standard', + child: const Icon(Icons.add), + ), + FloatingActionButton.large( + onPressed: () {}, + tooltip: 'Large', + child: const Icon(Icons.add), + ), + ], + ), + ); + } +} + +class Cards extends StatelessWidget { + const Cards({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Cards', + tooltipMessage: 'Use Card', + child: Wrap( + alignment: WrapAlignment.spaceEvenly, + children: <Widget>[ + SizedBox( + width: cardWidth, + child: Card( + child: Container( + padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), + child: Column( + children: <Widget>[ + Align( + alignment: Alignment.topRight, + child: IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () {}, + ), + ), + const SizedBox(height: 20), + const Align( + alignment: Alignment.bottomLeft, + child: Text('Elevated'), + ) + ], + ), + ), + ), + ), + SizedBox( + width: cardWidth, + child: Card( + color: Theme.of(context).colorScheme.surfaceVariant, + elevation: 0, + child: Container( + padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), + child: Column( + children: <Widget>[ + Align( + alignment: Alignment.topRight, + child: IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () {}, + ), + ), + const SizedBox(height: 20), + const Align( + alignment: Alignment.bottomLeft, + child: Text('Filled'), + ) + ], + ), + ), + ), + ), + SizedBox( + width: cardWidth, + child: Card( + elevation: 0, + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).colorScheme.outline, + ), + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + child: Container( + padding: const EdgeInsets.fromLTRB(10, 5, 5, 10), + child: Column( + children: <Widget>[ + Align( + alignment: Alignment.topRight, + child: IconButton( + icon: const Icon(Icons.more_vert), + onPressed: () {}, + ), + ), + const SizedBox(height: 20), + const Align( + alignment: Alignment.bottomLeft, + child: Text('Outlined'), + ) + ], + ), + ), + ), + ), + ], + ), + ); + } +} + +class _ClearButton extends StatelessWidget { + const _ClearButton({required this.controller}); + + final TextEditingController controller; + + @override + Widget build(BuildContext context) => IconButton( + icon: const Icon(Icons.clear), + onPressed: () => controller.clear(), + ); +} + +class TextFields extends StatefulWidget { + const TextFields({super.key}); + + @override + State<TextFields> createState() => _TextFieldsState(); +} + +class _TextFieldsState extends State<TextFields> { + final TextEditingController _controllerFilled = TextEditingController(); + final TextEditingController _controllerOutlined = TextEditingController(); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Text fields', + tooltipMessage: 'Use TextField with different InputDecoration', + child: Column( + children: <Widget>[ + Padding( + padding: const EdgeInsets.all(smallSpacing), + child: TextField( + controller: _controllerFilled, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton(controller: _controllerFilled), + labelText: 'Filled', + hintText: 'hint text', + helperText: 'supporting text', + filled: true, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(smallSpacing), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: <Widget>[ + Flexible( + child: SizedBox( + width: 200, + child: TextField( + maxLength: 10, + maxLengthEnforcement: MaxLengthEnforcement.none, + controller: _controllerFilled, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton(controller: _controllerFilled), + labelText: 'Filled', + hintText: 'hint text', + helperText: 'supporting text', + filled: true, + errorText: 'error text', + ), + ), + ), + ), + const SizedBox(width: smallSpacing), + Flexible( + child: SizedBox( + width: 200, + child: TextField( + controller: _controllerFilled, + enabled: false, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton(controller: _controllerFilled), + labelText: 'Disabled', + hintText: 'hint text', + helperText: 'supporting text', + filled: true, + ), + ), + ), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(smallSpacing), + child: TextField( + controller: _controllerOutlined, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: _ClearButton(controller: _controllerOutlined), + labelText: 'Outlined', + hintText: 'hint text', + helperText: 'supporting text', + border: const OutlineInputBorder(), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(smallSpacing), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: <Widget>[ + Flexible( + child: SizedBox( + width: 200, + child: TextField( + controller: _controllerOutlined, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: + _ClearButton(controller: _controllerOutlined), + labelText: 'Outlined', + hintText: 'hint text', + helperText: 'supporting text', + errorText: 'error text', + border: const OutlineInputBorder(), + filled: true, + ), + ), + ), + ), + const SizedBox(width: smallSpacing), + Flexible( + child: SizedBox( + width: 200, + child: TextField( + controller: _controllerOutlined, + enabled: false, + decoration: InputDecoration( + prefixIcon: const Icon(Icons.search), + suffixIcon: + _ClearButton(controller: _controllerOutlined), + labelText: 'Disabled', + hintText: 'hint text', + helperText: 'supporting text', + border: const OutlineInputBorder(), + filled: true, + ), + ), + ), + ), + ])), + ], + ), + ); + } +} + +class Dialogs extends StatefulWidget { + const Dialogs({super.key}); + + @override + State<Dialogs> createState() => _DialogsState(); +} + +class _DialogsState extends State<Dialogs> { + void openDialog(BuildContext context) { + showDialog<void>( + context: context, + builder: (BuildContext context) => AlertDialog( + title: const Text('What is a dialog?'), + content: const Text( + 'A dialog is a type of modal window that appears in front of app content to provide critical information, or prompt for a decision to be made.'), + actions: <Widget>[ + TextButton( + child: const Text('Okay'), + onPressed: () => Navigator.of(context).pop(), + ), + FilledButton( + child: const Text('Dismiss'), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ); + } + + void openFullscreenDialog(BuildContext context) { + showDialog<void>( + context: context, + builder: (BuildContext context) => Dialog.fullscreen( + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Scaffold( + appBar: AppBar( + title: const Text('Full-screen dialog'), + centerTitle: false, + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), + ), + actions: <Widget>[ + TextButton( + child: const Text('Close'), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Dialog', + tooltipMessage: + 'Use showDialog with Dialog.fullscreen, AlertDialog, or SimpleDialog', + child: Wrap( + alignment: WrapAlignment.spaceBetween, + children: <Widget>[ + TextButton( + child: const Text( + 'Show dialog', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () => openDialog(context), + ), + TextButton( + child: const Text( + 'Show full-screen dialog', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () => openFullscreenDialog(context), + ), + ], + ), + ); + } +} + +class Dividers extends StatelessWidget { + const Dividers({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Dividers', + tooltipMessage: 'Use Divider or VerticalDivider', + child: Column( + children: <Widget>[ + Divider(key: Key('divider')), + ], + ), + ); + } +} + +class Switches extends StatelessWidget { + const Switches({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Switches', + tooltipMessage: 'Use SwitchListTile or Switch', + child: Column( + children: <Widget>[ + SwitchRow(isEnabled: true), + SwitchRow(isEnabled: false), + ], + ), + ); + } +} + +class SwitchRow extends StatefulWidget { + const SwitchRow({super.key, required this.isEnabled}); + + final bool isEnabled; + + @override + State<SwitchRow> createState() => _SwitchRowState(); +} + +class _SwitchRowState extends State<SwitchRow> { + bool value0 = false; + bool value1 = true; + + final MaterialStateProperty<Icon?> thumbIcon = + MaterialStateProperty.resolveWith<Icon?>((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return const Icon(Icons.check); + } + return const Icon(Icons.close); + }); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: <Widget>[ + Switch( + value: value0, + onChanged: widget.isEnabled + ? (bool value) { + setState(() { + value0 = value; + }); + } + : null, + ), + Switch( + thumbIcon: thumbIcon, + value: value1, + onChanged: widget.isEnabled + ? (bool value) { + setState(() { + value1 = value; + }); + } + : null, + ), + ], + ); + } +} + +class Checkboxes extends StatefulWidget { + const Checkboxes({super.key}); + + @override + State<Checkboxes> createState() => _CheckboxesState(); +} + +class _CheckboxesState extends State<Checkboxes> { + bool? isChecked0 = true; + bool? isChecked1; + bool? isChecked2 = false; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Checkboxes', + tooltipMessage: 'Use CheckboxListTile or Checkbox', + child: Column( + children: <Widget>[ + CheckboxListTile( + tristate: true, + value: isChecked0, + title: const Text('Option 1'), + onChanged: (bool? value) { + setState(() { + isChecked0 = value; + }); + }, + ), + CheckboxListTile( + tristate: true, + value: isChecked1, + title: const Text('Option 2'), + onChanged: (bool? value) { + setState(() { + isChecked1 = value; + }); + }, + ), + CheckboxListTile( + tristate: true, + value: isChecked2, + title: const Text('Option 3'), + onChanged: (bool? value) { + setState(() { + isChecked2 = value; + }); + }, + ), + const CheckboxListTile( + tristate: true, + title: Text('Option 4'), + value: true, + onChanged: null, + ), + ], + ), + ); + } +} + +enum Value { first, second } + +class Radios extends StatefulWidget { + const Radios({super.key}); + + @override + State<Radios> createState() => _RadiosState(); +} + +enum Options { option1, option2, option3 } + +class _RadiosState extends State<Radios> { + Options? _selectedOption = Options.option1; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Radio buttons', + tooltipMessage: 'Use RadioListTile<T> or Radio<T>', + child: Column( + children: <Widget>[ + RadioListTile<Options>( + title: const Text('Option 1'), + value: Options.option1, + groupValue: _selectedOption, + onChanged: (Options? value) { + setState(() { + _selectedOption = value; + }); + }, + ), + RadioListTile<Options>( + title: const Text('Option 2'), + value: Options.option2, + groupValue: _selectedOption, + onChanged: (Options? value) { + setState(() { + _selectedOption = value; + }); + }, + ), + RadioListTile<Options>( + title: const Text('Option 3'), + value: Options.option3, + groupValue: _selectedOption, + onChanged: null, + ), + ], + ), + ); + } +} + +class ProgressIndicators extends StatefulWidget { + const ProgressIndicators({super.key}); + + @override + State<ProgressIndicators> createState() => _ProgressIndicatorsState(); +} + +class _ProgressIndicatorsState extends State<ProgressIndicators> { + bool playProgressIndicator = false; + + @override + Widget build(BuildContext context) { + final double? progressValue = playProgressIndicator ? null : 0.7; + + return ComponentDecoration( + label: 'Progress indicators', + tooltipMessage: + 'Use CircularProgressIndicator or LinearProgressIndicator', + child: Column( + children: <Widget>[ + Row( + children: <Widget>[ + IconButton( + isSelected: playProgressIndicator, + selectedIcon: const Icon(Icons.pause), + icon: const Icon(Icons.play_arrow), + onPressed: () { + setState(() { + playProgressIndicator = !playProgressIndicator; + }); + }, + ), + Expanded( + child: Row( + children: <Widget>[ + rowDivider, + CircularProgressIndicator( + value: progressValue, + ), + rowDivider, + Expanded( + child: LinearProgressIndicator( + value: progressValue, + ), + ), + rowDivider, + ], + ), + ), + ], + ), + ], + ), + ); + } +} + +const List<NavigationDestination> appBarDestinations = <NavigationDestination>[ + NavigationDestination( + tooltip: '', + icon: Icon(Icons.widgets_outlined), + label: 'Components', + selectedIcon: Icon(Icons.widgets), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.format_paint_outlined), + label: 'Color', + selectedIcon: Icon(Icons.format_paint), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.text_snippet_outlined), + label: 'Typography', + selectedIcon: Icon(Icons.text_snippet), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.invert_colors_on_outlined), + label: 'Elevation', + selectedIcon: Icon(Icons.opacity), + ) +]; + +const List<Widget> exampleBarDestinations = <Widget>[ + NavigationDestination( + tooltip: '', + icon: Icon(Icons.explore_outlined), + label: 'Explore', + selectedIcon: Icon(Icons.explore), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.pets_outlined), + label: 'Pets', + selectedIcon: Icon(Icons.pets), + ), + NavigationDestination( + tooltip: '', + icon: Icon(Icons.account_box_outlined), + label: 'Account', + selectedIcon: Icon(Icons.account_box), + ) +]; + +List<Widget> barWithBadgeDestinations = <Widget>[ + NavigationDestination( + tooltip: '', + icon: Badge.count(count: 1000, child: const Icon(Icons.mail_outlined)), + label: 'Mail', + selectedIcon: Badge.count(count: 1000, child: const Icon(Icons.mail)), + ), + const NavigationDestination( + tooltip: '', + icon: Badge(label: Text('10'), child: Icon(Icons.chat_bubble_outline)), + label: 'Chat', + selectedIcon: Badge(label: Text('10'), child: Icon(Icons.chat_bubble)), + ), + const NavigationDestination( + tooltip: '', + icon: Badge(child: Icon(Icons.group_outlined)), + label: 'Rooms', + selectedIcon: Badge(child: Icon(Icons.group_rounded)), + ), + NavigationDestination( + tooltip: '', + icon: Badge.count(count: 3, child: const Icon(Icons.videocam_outlined)), + label: 'Meet', + selectedIcon: Badge.count(count: 3, child: const Icon(Icons.videocam)), + ) +]; + +class NavigationBars extends StatefulWidget { + const NavigationBars({ + super.key, + this.onSelectItem, + required this.selectedIndex, + required this.isExampleBar, + this.isBadgeExample = false, + }); + + final void Function(int)? onSelectItem; + final int selectedIndex; + final bool isExampleBar; + final bool isBadgeExample; + + @override + State<NavigationBars> createState() => _NavigationBarsState(); +} + +class _NavigationBarsState extends State<NavigationBars> { + late int selectedIndex; + + @override + void initState() { + super.initState(); + selectedIndex = widget.selectedIndex; + } + + @override + void didUpdateWidget(covariant NavigationBars oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.selectedIndex != oldWidget.selectedIndex) { + selectedIndex = widget.selectedIndex; + } + } + + @override + Widget build(BuildContext context) { + // App NavigationBar should get first focus. + Widget navigationBar = Focus( + autofocus: !(widget.isExampleBar || widget.isBadgeExample), + child: NavigationBar( + selectedIndex: selectedIndex, + onDestinationSelected: (int index) { + setState(() { + selectedIndex = index; + }); + if (!widget.isExampleBar) { + widget.onSelectItem!(index); + } + }, + destinations: widget.isExampleBar && widget.isBadgeExample + ? barWithBadgeDestinations + : widget.isExampleBar + ? exampleBarDestinations + : appBarDestinations, + ), + ); + + if (widget.isExampleBar && widget.isBadgeExample) { + navigationBar = ComponentDecoration( + label: 'Badges', + tooltipMessage: 'Use Badge or Badge.count', + child: navigationBar); + } else if (widget.isExampleBar) { + navigationBar = ComponentDecoration( + label: 'Navigation bar', + tooltipMessage: 'Use NavigationBar', + child: navigationBar); + } + + return navigationBar; + } +} + +class IconToggleButtons extends StatefulWidget { + const IconToggleButtons({super.key}); + + @override + State<IconToggleButtons> createState() => _IconToggleButtonsState(); +} + +class _IconToggleButtonsState extends State<IconToggleButtons> { + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Icon buttons', + tooltipMessage: 'Use IconButton', + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: <Widget>[ + Column( + // Standard IconButton + children: <Widget>[ + IconToggleButton( + isEnabled: true, + tooltip: 'Standard', + ), + colDivider, + IconToggleButton( + isEnabled: false, + tooltip: 'Standard (disabled)', + ), + ], + ), + Column( + children: <Widget>[ + // Filled IconButton + IconToggleButton( + isEnabled: true, + tooltip: 'Filled', + getDefaultStyle: enabledFilledButtonStyle, + ), + colDivider, + IconToggleButton( + isEnabled: false, + tooltip: 'Filled (disabled)', + getDefaultStyle: disabledFilledButtonStyle, + ), + ], + ), + Column( + children: <Widget>[ + // Filled Tonal IconButton + IconToggleButton( + isEnabled: true, + tooltip: 'Filled tonal', + getDefaultStyle: enabledFilledTonalButtonStyle, + ), + colDivider, + IconToggleButton( + isEnabled: false, + tooltip: 'Filled tonal (disabled)', + getDefaultStyle: disabledFilledTonalButtonStyle, + ), + ], + ), + Column( + children: <Widget>[ + // Outlined IconButton + IconToggleButton( + isEnabled: true, + tooltip: 'Outlined', + getDefaultStyle: enabledOutlinedButtonStyle, + ), + colDivider, + IconToggleButton( + isEnabled: false, + tooltip: 'Outlined (disabled)', + getDefaultStyle: disabledOutlinedButtonStyle, + ), + ], + ), + ], + ), + ); + } +} + +class IconToggleButton extends StatefulWidget { + const IconToggleButton({ + required this.isEnabled, + required this.tooltip, + this.getDefaultStyle, + super.key, + }); + + final bool isEnabled; + final String tooltip; + final ButtonStyle? Function(bool, ColorScheme)? getDefaultStyle; + + @override + State<IconToggleButton> createState() => _IconToggleButtonState(); +} + +class _IconToggleButtonState extends State<IconToggleButton> { + bool selected = false; + + @override + Widget build(BuildContext context) { + final ColorScheme colors = Theme.of(context).colorScheme; + final VoidCallback? onPressed = widget.isEnabled + ? () { + setState(() { + selected = !selected; + }); + } + : null; + final ButtonStyle? style = widget.getDefaultStyle?.call(selected, colors); + + return IconButton( + visualDensity: VisualDensity.standard, + isSelected: selected, + tooltip: widget.tooltip, + icon: const Icon(Icons.settings_outlined), + selectedIcon: const Icon(Icons.settings), + onPressed: onPressed, + style: style, + ); + } +} + +ButtonStyle enabledFilledButtonStyle(bool selected, ColorScheme colors) { + return IconButton.styleFrom( + foregroundColor: selected ? colors.onPrimary : colors.primary, + backgroundColor: selected ? colors.primary : colors.surfaceVariant, + disabledForegroundColor: colors.onSurface.withOpacity(0.38), + disabledBackgroundColor: colors.onSurface.withOpacity(0.12), + hoverColor: selected + ? colors.onPrimary.withOpacity(0.08) + : colors.primary.withOpacity(0.08), + focusColor: selected + ? colors.onPrimary.withOpacity(0.12) + : colors.primary.withOpacity(0.12), + highlightColor: selected + ? colors.onPrimary.withOpacity(0.12) + : colors.primary.withOpacity(0.12), + ); +} + +ButtonStyle disabledFilledButtonStyle(bool selected, ColorScheme colors) { + return IconButton.styleFrom( + disabledForegroundColor: colors.onSurface.withOpacity(0.38), + disabledBackgroundColor: colors.onSurface.withOpacity(0.12), + ); +} + +ButtonStyle enabledFilledTonalButtonStyle(bool selected, ColorScheme colors) { + return IconButton.styleFrom( + foregroundColor: + selected ? colors.onSecondaryContainer : colors.onSurfaceVariant, + backgroundColor: + selected ? colors.secondaryContainer : colors.surfaceVariant, + hoverColor: selected + ? colors.onSecondaryContainer.withOpacity(0.08) + : colors.onSurfaceVariant.withOpacity(0.08), + focusColor: selected + ? colors.onSecondaryContainer.withOpacity(0.12) + : colors.onSurfaceVariant.withOpacity(0.12), + highlightColor: selected + ? colors.onSecondaryContainer.withOpacity(0.12) + : colors.onSurfaceVariant.withOpacity(0.12), + ); +} + +ButtonStyle disabledFilledTonalButtonStyle(bool selected, ColorScheme colors) { + return IconButton.styleFrom( + disabledForegroundColor: colors.onSurface.withOpacity(0.38), + disabledBackgroundColor: colors.onSurface.withOpacity(0.12), + ); +} + +ButtonStyle enabledOutlinedButtonStyle(bool selected, ColorScheme colors) { + return IconButton.styleFrom( + backgroundColor: selected ? colors.inverseSurface : null, + hoverColor: selected + ? colors.onInverseSurface.withOpacity(0.08) + : colors.onSurfaceVariant.withOpacity(0.08), + focusColor: selected + ? colors.onInverseSurface.withOpacity(0.12) + : colors.onSurfaceVariant.withOpacity(0.12), + highlightColor: selected + ? colors.onInverseSurface.withOpacity(0.12) + : colors.onSurface.withOpacity(0.12), + side: BorderSide(color: colors.outline), + ).copyWith( + foregroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return colors.onInverseSurface; + } + if (states.contains(MaterialState.pressed)) { + return colors.onSurface; + } + return null; + }), + ); +} + +ButtonStyle disabledOutlinedButtonStyle(bool selected, ColorScheme colors) { + return IconButton.styleFrom( + disabledForegroundColor: colors.onSurface.withOpacity(0.38), + disabledBackgroundColor: + selected ? colors.onSurface.withOpacity(0.12) : null, + side: selected ? null : BorderSide(color: colors.outline.withOpacity(0.12)), + ); +} + +class Chips extends StatefulWidget { + const Chips({super.key}); + + @override + State<Chips> createState() => _ChipsState(); +} + +class _ChipsState extends State<Chips> { + bool isFiltered = true; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Chips', + tooltipMessage: + 'Use ActionChip, FilterChip, or InputChip. \nActionChip can also be used for suggestion chip', + child: Column( + children: <Widget>[ + Wrap( + spacing: smallSpacing, + runSpacing: smallSpacing, + children: <Widget>[ + ActionChip( + label: const Text('Assist'), + avatar: const Icon(Icons.event), + onPressed: () {}, + ), + FilterChip( + label: const Text('Filter'), + selected: isFiltered, + onSelected: (bool selected) { + setState(() => isFiltered = selected); + }, + ), + InputChip( + label: const Text('Input'), + onPressed: () {}, + onDeleted: () {}, + ), + ActionChip( + label: const Text('Suggestion'), + onPressed: () {}, + ), + ], + ), + colDivider, + Wrap( + spacing: smallSpacing, + runSpacing: smallSpacing, + children: <Widget>[ + const ActionChip( + label: Text('Assist'), + avatar: Icon(Icons.event), + ), + FilterChip( + label: const Text('Filter'), + selected: isFiltered, + onSelected: null, + ), + InputChip( + label: const Text('Input'), + onDeleted: () {}, + isEnabled: false, + ), + const ActionChip( + label: Text('Suggestion'), + ), + ], + ), + ], + ), + ); + } +} + +class SegmentedButtons extends StatelessWidget { + const SegmentedButtons({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Segmented buttons', + tooltipMessage: 'Use SegmentedButton<T>', + child: Column( + children: <Widget>[ + SingleChoice(), + colDivider, + MultipleChoice(), + ], + ), + ); + } +} + +enum Calendar { day, week, month, year } + +class SingleChoice extends StatefulWidget { + const SingleChoice({super.key}); + + @override + State<SingleChoice> createState() => _SingleChoiceState(); +} + +class _SingleChoiceState extends State<SingleChoice> { + Calendar calendarView = Calendar.day; + + @override + Widget build(BuildContext context) { + return SegmentedButton<Calendar>( + segments: const <ButtonSegment<Calendar>>[ + ButtonSegment<Calendar>( + value: Calendar.day, + label: Text('Day'), + icon: Icon(Icons.calendar_view_day)), + ButtonSegment<Calendar>( + value: Calendar.week, + label: Text('Week'), + icon: Icon(Icons.calendar_view_week)), + ButtonSegment<Calendar>( + value: Calendar.month, + label: Text('Month'), + icon: Icon(Icons.calendar_view_month)), + ButtonSegment<Calendar>( + value: Calendar.year, + label: Text('Year'), + icon: Icon(Icons.calendar_today)), + ], + selected: <Calendar>{calendarView}, + onSelectionChanged: (Set<Calendar> newSelection) { + setState(() { + // By default there is only a single segment that can be + // selected at one time, so its value is always the first + // item in the selected set. + calendarView = newSelection.first; + }); + }, + ); + } +} + +enum Sizes { extraSmall, small, medium, large, extraLarge } + +class MultipleChoice extends StatefulWidget { + const MultipleChoice({super.key}); + + @override + State<MultipleChoice> createState() => _MultipleChoiceState(); +} + +class _MultipleChoiceState extends State<MultipleChoice> { + Set<Sizes> selection = <Sizes>{Sizes.large, Sizes.extraLarge}; + + @override + Widget build(BuildContext context) { + return SegmentedButton<Sizes>( + segments: const <ButtonSegment<Sizes>>[ + ButtonSegment<Sizes>(value: Sizes.extraSmall, label: Text('XS')), + ButtonSegment<Sizes>(value: Sizes.small, label: Text('S')), + ButtonSegment<Sizes>(value: Sizes.medium, label: Text('M')), + ButtonSegment<Sizes>( + value: Sizes.large, + label: Text('L'), + ), + ButtonSegment<Sizes>(value: Sizes.extraLarge, label: Text('XL')), + ], + selected: selection, + onSelectionChanged: (Set<Sizes> newSelection) { + setState(() { + selection = newSelection; + }); + }, + multiSelectionEnabled: true, + ); + } +} + +class SnackBarSection extends StatelessWidget { + const SnackBarSection({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Snackbar', + tooltipMessage: + 'Use ScaffoldMessenger.of(context).showSnackBar with SnackBar', + child: TextButton( + onPressed: () { + final SnackBar snackBar = SnackBar( + behavior: SnackBarBehavior.floating, + width: 400.0, + content: const Text('This is a snackbar'), + action: SnackBarAction( + label: 'Close', + onPressed: () {}, + ), + ); + + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + }, + child: const Text( + 'Show snackbar', + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + ); + } +} + +class BottomSheetSection extends StatefulWidget { + const BottomSheetSection({super.key}); + + @override + State<BottomSheetSection> createState() => _BottomSheetSectionState(); +} + +class _BottomSheetSectionState extends State<BottomSheetSection> { + bool isNonModalBottomSheetOpen = false; + PersistentBottomSheetController<void>? _nonModalBottomSheetController; + + @override + Widget build(BuildContext context) { + List<Widget> buttonList = <Widget>[ + IconButton(onPressed: () {}, icon: const Icon(Icons.share_outlined)), + IconButton(onPressed: () {}, icon: const Icon(Icons.add)), + IconButton(onPressed: () {}, icon: const Icon(Icons.delete_outline)), + IconButton(onPressed: () {}, icon: const Icon(Icons.archive_outlined)), + IconButton(onPressed: () {}, icon: const Icon(Icons.settings_outlined)), + IconButton(onPressed: () {}, icon: const Icon(Icons.favorite_border)), + ]; + const List<Text> labelList = <Text>[ + Text('Share'), + Text('Add to'), + Text('Trash'), + Text('Archive'), + Text('Settings'), + Text('Favorite') + ]; + + buttonList = List<Widget>.generate( + buttonList.length, + (int index) => Padding( + padding: const EdgeInsets.fromLTRB(20.0, 30.0, 20.0, 20.0), + child: Column( + children: <Widget>[ + buttonList[index], + labelList[index], + ], + ), + )); + + return ComponentDecoration( + label: 'Bottom sheet', + tooltipMessage: 'Use showModalBottomSheet<T> or showBottomSheet<T>', + child: Wrap( + alignment: WrapAlignment.spaceEvenly, + children: <Widget>[ + TextButton( + child: const Text( + 'Show modal bottom sheet', + style: TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () { + showModalBottomSheet<void>( + context: context, + constraints: const BoxConstraints(maxWidth: 640), + builder: (BuildContext context) { + return SizedBox( + height: 150, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: buttonList, + ), + ), + ); + }, + ); + }, + ), + TextButton( + child: Text( + isNonModalBottomSheetOpen + ? 'Hide bottom sheet' + : 'Show bottom sheet', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + onPressed: () { + if (isNonModalBottomSheetOpen) { + _nonModalBottomSheetController?.close(); + setState(() { + isNonModalBottomSheetOpen = false; + }); + return; + } else { + setState(() { + isNonModalBottomSheetOpen = true; + }); + } + + _nonModalBottomSheetController = showBottomSheet<void>( + elevation: 8.0, + context: context, + constraints: const BoxConstraints(maxWidth: 640), + builder: (BuildContext context) { + return SizedBox( + height: 150, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32.0), + child: ListView( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + children: buttonList, + ), + ), + ); + }, + ); + }, + ), + ], + ), + ); + } +} + +class BottomAppBars extends StatelessWidget { + const BottomAppBars({super.key}); + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Bottom app bar', + tooltipMessage: 'Use BottomAppBar', + child: Column( + children: <Widget>[ + SizedBox( + height: 80, + child: Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () {}, + elevation: 0.0, + child: const Icon(Icons.add), + ), + floatingActionButtonLocation: + FloatingActionButtonLocation.endContained, + bottomNavigationBar: BottomAppBar( + child: Row( + children: <Widget>[ + const IconButtonAnchorExample(), + IconButton( + tooltip: 'Search', + icon: const Icon(Icons.search), + onPressed: () {}, + ), + IconButton( + tooltip: 'Favorite', + icon: const Icon(Icons.favorite), + onPressed: () {}, + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} + +class IconButtonAnchorExample extends StatelessWidget { + const IconButtonAnchorExample({super.key}); + + @override + Widget build(BuildContext context) { + return MenuAnchor( + builder: (BuildContext context, MenuController controller, Widget? child) { + return IconButton( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + icon: const Icon(Icons.more_vert), + ); + }, + menuChildren: <Widget>[ + MenuItemButton( + child: const Text('Menu 1'), + onPressed: () {}, + ), + MenuItemButton( + child: const Text('Menu 2'), + onPressed: () {}, + ), + SubmenuButton( + menuChildren: <Widget>[ + MenuItemButton( + onPressed: () {}, + child: const Text('Menu 3.1'), + ), + MenuItemButton( + onPressed: () {}, + child: const Text('Menu 3.2'), + ), + MenuItemButton( + onPressed: () {}, + child: const Text('Menu 3.3'), + ), + ], + child: const Text('Menu 3'), + ), + ], + ); + } +} + +class ButtonAnchorExample extends StatelessWidget { + const ButtonAnchorExample({super.key}); + + @override + Widget build(BuildContext context) { + return MenuAnchor( + builder: (BuildContext context, MenuController controller, Widget? child) { + return FilledButton.tonal( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + child: const Text('Show menu'), + ); + }, + menuChildren: <Widget>[ + MenuItemButton( + leadingIcon: const Icon(Icons.people_alt_outlined), + child: const Text('Item 1'), + onPressed: () {}, + ), + MenuItemButton( + leadingIcon: const Icon(Icons.remove_red_eye_outlined), + child: const Text('Item 2'), + onPressed: () {}, + ), + MenuItemButton( + leadingIcon: const Icon(Icons.refresh), + onPressed: () {}, + child: const Text('Item 3'), + ), + ], + ); + } +} + +class NavigationDrawers extends StatelessWidget { + const NavigationDrawers({super.key, required this.scaffoldKey}); + final GlobalKey<ScaffoldState> scaffoldKey; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Navigation drawer', + tooltipMessage: + 'Use NavigationDrawer. For modal navigation drawers, see Scaffold.endDrawer', + child: Column( + children: <Widget>[ + const SizedBox(height: 520, child: NavigationDrawerSection()), + colDivider, + colDivider, + TextButton( + child: const Text('Show modal navigation drawer', + style: TextStyle(fontWeight: FontWeight.bold)), + onPressed: () { + scaffoldKey.currentState!.openEndDrawer(); + }, + ), + ], + ), + ); + } +} + +class NavigationDrawerSection extends StatefulWidget { + const NavigationDrawerSection({super.key}); + + @override + State<NavigationDrawerSection> createState() => + _NavigationDrawerSectionState(); +} + +class _NavigationDrawerSectionState extends State<NavigationDrawerSection> { + int navDrawerIndex = 0; + + @override + Widget build(BuildContext context) { + return NavigationDrawer( + onDestinationSelected: (int selectedIndex) { + setState(() { + navDrawerIndex = selectedIndex; + }); + }, + selectedIndex: navDrawerIndex, + children: <Widget>[ + Padding( + padding: const EdgeInsets.fromLTRB(28, 16, 16, 10), + child: Text( + 'Mail', + style: Theme.of(context).textTheme.titleSmall, + ), + ), + ...destinations.map((ExampleDestination destination) { + return NavigationDrawerDestination( + label: Text(destination.label), + icon: destination.icon, + selectedIcon: destination.selectedIcon, + ); + }), + const Divider(indent: 28, endIndent: 28), + Padding( + padding: const EdgeInsets.fromLTRB(28, 16, 16, 10), + child: Text( + 'Labels', + style: Theme.of(context).textTheme.titleSmall, + ), + ), + ...labelDestinations.map((ExampleDestination destination) { + return NavigationDrawerDestination( + label: Text(destination.label), + icon: destination.icon, + selectedIcon: destination.selectedIcon, + ); + }), + ], + ); + } +} + +class ExampleDestination { + const ExampleDestination(this.label, this.icon, this.selectedIcon); + + final String label; + final Widget icon; + final Widget selectedIcon; +} + +const List<ExampleDestination> destinations = <ExampleDestination>[ + ExampleDestination('Inbox', Icon(Icons.inbox_outlined), Icon(Icons.inbox)), + ExampleDestination('Outbox', Icon(Icons.send_outlined), Icon(Icons.send)), + ExampleDestination( + 'Favorites', Icon(Icons.favorite_outline), Icon(Icons.favorite)), + ExampleDestination('Trash', Icon(Icons.delete_outline), Icon(Icons.delete)), +]; + +const List<ExampleDestination> labelDestinations = <ExampleDestination>[ + ExampleDestination( + 'Family', Icon(Icons.bookmark_border), Icon(Icons.bookmark)), + ExampleDestination( + 'School', Icon(Icons.bookmark_border), Icon(Icons.bookmark)), + ExampleDestination('Work', Icon(Icons.bookmark_border), Icon(Icons.bookmark)), +]; + +class NavigationRails extends StatelessWidget { + const NavigationRails({super.key}); + + @override + Widget build(BuildContext context) { + return const ComponentDecoration( + label: 'Navigation rail', + tooltipMessage: 'Use NavigationRail', + child: IntrinsicWidth( + child: SizedBox(height: 420, child: NavigationRailSection())), + ); + } +} + +class NavigationRailSection extends StatefulWidget { + const NavigationRailSection({super.key}); + + @override + State<NavigationRailSection> createState() => _NavigationRailSectionState(); +} + +class _NavigationRailSectionState extends State<NavigationRailSection> { + int navRailIndex = 0; + + @override + Widget build(BuildContext context) { + return NavigationRail( + onDestinationSelected: (int selectedIndex) { + setState(() { + navRailIndex = selectedIndex; + }); + }, + elevation: 4, + leading: FloatingActionButton( + child: const Icon(Icons.create), onPressed: () {}), + groupAlignment: 0.0, + selectedIndex: navRailIndex, + labelType: NavigationRailLabelType.selected, + destinations: <NavigationRailDestination>[ + ...destinations.map((ExampleDestination destination) { + return NavigationRailDestination( + label: Text(destination.label), + icon: destination.icon, + selectedIcon: destination.selectedIcon, + ); + }), + ], + ); + } +} + +class Tabs extends StatefulWidget { + const Tabs({super.key}); + + @override + State<Tabs> createState() => _TabsState(); +} + +class _TabsState extends State<Tabs> with TickerProviderStateMixin { + late TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 3, vsync: this); + } + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Tabs', + tooltipMessage: 'Use TabBar', + child: SizedBox( + height: 80, + child: Scaffold( + appBar: AppBar( + bottom: TabBar( + controller: _tabController, + tabs: const <Widget>[ + Tab( + icon: Icon(Icons.videocam_outlined), + text: 'Video', + iconMargin: EdgeInsets.zero, + ), + Tab( + icon: Icon(Icons.photo_outlined), + text: 'Photos', + iconMargin: EdgeInsets.zero, + ), + Tab( + icon: Icon(Icons.audiotrack_sharp), + text: 'Audio', + iconMargin: EdgeInsets.zero, + ), + ], + ), + ), + ), + ), + ); + } +} + +class TopAppBars extends StatelessWidget { + const TopAppBars({super.key}); + + static final List<IconButton> actions = <IconButton>[ + IconButton(icon: const Icon(Icons.attach_file), onPressed: () {}), + IconButton(icon: const Icon(Icons.event), onPressed: () {}), + IconButton(icon: const Icon(Icons.more_vert), onPressed: () {}), + ]; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Top app bars', + tooltipMessage: + 'Use AppBar, SliverAppBar, SliverAppBar.medium, or SliverAppBar.large', + child: Column( + children: <Widget>[ + AppBar( + title: const Text('Center-aligned'), + leading: const BackButton(), + actions: <Widget>[ + IconButton( + iconSize: 32, + icon: const Icon(Icons.account_circle_outlined), + onPressed: () {}, + ), + ], + centerTitle: true, + ), + colDivider, + AppBar( + title: const Text('Small'), + leading: const BackButton(), + actions: actions, + centerTitle: false, + ), + colDivider, + SizedBox( + height: 100, + child: CustomScrollView( + slivers: <Widget>[ + SliverAppBar.medium( + title: const Text('Medium'), + leading: const BackButton(), + actions: actions, + ), + const SliverFillRemaining(), + ], + ), + ), + colDivider, + SizedBox( + height: 130, + child: CustomScrollView( + slivers: <Widget>[ + SliverAppBar.large( + title: const Text('Large'), + leading: const BackButton(), + actions: actions, + ), + const SliverFillRemaining(), + ], + ), + ), + ], + ), + ); + } +} + +class Menus extends StatefulWidget { + const Menus({super.key}); + + @override + State<Menus> createState() => _MenusState(); +} + +class _MenusState extends State<Menus> { + final TextEditingController colorController = TextEditingController(); + final TextEditingController iconController = TextEditingController(); + IconLabel? selectedIcon = IconLabel.smile; + ColorLabel? selectedColor; + + @override + Widget build(BuildContext context) { + final List<DropdownMenuEntry<ColorLabel>> colorEntries = + <DropdownMenuEntry<ColorLabel>>[]; + for (final ColorLabel color in ColorLabel.values) { + colorEntries.add(DropdownMenuEntry<ColorLabel>( + value: color, label: color.label, enabled: color.label != 'Grey')); + } + + final List<DropdownMenuEntry<IconLabel>> iconEntries = + <DropdownMenuEntry<IconLabel>>[]; + for (final IconLabel icon in IconLabel.values) { + iconEntries + .add(DropdownMenuEntry<IconLabel>(value: icon, label: icon.label)); + } + + return ComponentDecoration( + label: 'Menus', + tooltipMessage: 'Use MenuAnchor or DropdownMenu<T>', + child: Column( + children: <Widget>[ + const Row( + mainAxisAlignment: MainAxisAlignment.center, + children: <Widget>[ + ButtonAnchorExample(), + rowDivider, + IconButtonAnchorExample(), + ], + ), + colDivider, + Wrap( + alignment: WrapAlignment.spaceAround, + runAlignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + spacing: smallSpacing, + runSpacing: smallSpacing, + children: <Widget>[ + DropdownMenu<ColorLabel>( + controller: colorController, + label: const Text('Color'), + enableFilter: true, + dropdownMenuEntries: colorEntries, + inputDecorationTheme: const InputDecorationTheme(filled: true), + onSelected: (ColorLabel? color) { + setState(() { + selectedColor = color; + }); + }, + ), + DropdownMenu<IconLabel>( + initialSelection: IconLabel.smile, + controller: iconController, + leadingIcon: const Icon(Icons.search), + label: const Text('Icon'), + dropdownMenuEntries: iconEntries, + onSelected: (IconLabel? icon) { + setState(() { + selectedIcon = icon; + }); + }, + ), + Icon( + selectedIcon?.icon, + color: selectedColor?.color ?? Colors.grey.withOpacity(0.5), + ) + ], + ), + ], + ), + ); + } +} + +enum ColorLabel { + blue('Blue', Colors.blue), + pink('Pink', Colors.pink), + green('Green', Colors.green), + yellow('Yellow', Colors.yellow), + grey('Grey', Colors.grey); + + const ColorLabel(this.label, this.color); + final String label; + final Color color; +} + +enum IconLabel { + smile('Smile', Icons.sentiment_satisfied_outlined), + cloud( + 'Cloud', + Icons.cloud_outlined, + ), + brush('Brush', Icons.brush_outlined), + heart('Heart', Icons.favorite); + + const IconLabel(this.label, this.icon); + final String label; + final IconData icon; +} + +class Sliders extends StatefulWidget { + const Sliders({super.key}); + + @override + State<Sliders> createState() => _SlidersState(); +} + +class _SlidersState extends State<Sliders> { + double sliderValue0 = 30.0; + double sliderValue1 = 20.0; + + @override + Widget build(BuildContext context) { + return ComponentDecoration( + label: 'Sliders', + tooltipMessage: 'Use Slider or RangeSlider', + child: Column( + children: <Widget>[ + Slider( + max: 100, + value: sliderValue0, + onChanged: (double value) { + setState(() { + sliderValue0 = value; + }); + }, + ), + const SizedBox(height: 20), + Slider( + max: 100, + divisions: 5, + value: sliderValue1, + label: sliderValue1.round().toString(), + onChanged: (double value) { + setState(() { + sliderValue1 = value; + }); + }, + ), + ], + )); + } +} + +class ComponentDecoration extends StatefulWidget { + const ComponentDecoration({ + super.key, + required this.label, + required this.child, + this.tooltipMessage = '', + }); + + final String label; + final Widget child; + final String? tooltipMessage; + + @override + State<ComponentDecoration> createState() => _ComponentDecorationState(); +} + +class _ComponentDecorationState extends State<ComponentDecoration> { + final FocusNode focusNode = FocusNode(); + + @override + Widget build(BuildContext context) { + return RepaintBoundary( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: smallSpacing), + child: Column( + children: <Widget>[ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: <Widget>[ + Text(widget.label, + style: Theme.of(context).textTheme.titleSmall), + Tooltip( + message: widget.tooltipMessage, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 5.0), + child: Icon(Icons.info_outline, size: 16)), + ), + ], + ), + ConstrainedBox( + constraints: + const BoxConstraints.tightFor(width: widthConstraint), + // Tapping within the a component card should request focus + // for that component's children. + child: Focus( + focusNode: focusNode, + canRequestFocus: true, + child: GestureDetector( + onTapDown: (_) { + focusNode.requestFocus(); + }, + behavior: HitTestBehavior.opaque, + child: Card( + elevation: 0, + shape: RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context).colorScheme.outlineVariant, + ), + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 5.0, vertical: 20.0), + child: Center( + child: widget.child, + ), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} + +class ComponentGroupDecoration extends StatelessWidget { + const ComponentGroupDecoration( + {super.key, required this.label, required this.children}); + + final String label; + final List<Widget> children; + + @override + Widget build(BuildContext context) { + // Fully traverse this component group before moving on + return FocusTraversalGroup( + child: Card( + margin: EdgeInsets.zero, + elevation: 0, + color: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.3), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0), + child: Center( + child: Column( + children: <Widget>[ + Text(label, style: Theme.of(context).textTheme.titleLarge), + colDivider, + ...children + ], + ), + ), + ), + ), + ); + } +} diff --git a/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart b/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart index 178698c536446..ae583dc8ba80e 100644 --- a/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart +++ b/dev/benchmarks/macrobenchmarks/lib/src/web/recorder.dart @@ -3,8 +3,11 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:html' as html; -import 'dart:js_util' as js_util; +import 'dart:js_interop'; +// The analyzer currently thinks `js_interop_unsafe` is unused, but it is used +// for `JSObject.[]=`. +// ignore: unused_import +import 'dart:js_interop_unsafe'; import 'dart:math' as math; import 'dart:ui'; @@ -15,6 +18,7 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; +import 'package:web/web.dart' as web; /// The default number of samples from warm-up iterations. /// @@ -422,12 +426,18 @@ abstract class WidgetRecorder extends Recorder implements FrameRecorder { _runCompleter!.completeError(error, stackTrace); } + late final _RecordingWidgetsBinding _binding; + + @override + @mustCallSuper + Future<void> setUpAll() async { + _binding = _RecordingWidgetsBinding.ensureInitialized(); + } + @override Future<Profile> run() async { _runCompleter = Completer<void>(); final Profile localProfile = profile = Profile(name: name, useCustomWarmUp: useCustomWarmUp); - final _RecordingWidgetsBinding binding = - _RecordingWidgetsBinding.ensureInitialized(); final Widget widget = createWidget(); registerEngineBenchmarkValueListener(kProfilePrerollFrame, (num value) { @@ -445,7 +455,7 @@ abstract class WidgetRecorder extends Recorder implements FrameRecorder { ); }); - binding._beginRecording(this, widget); + _binding._beginRecording(this, widget); try { await _runCompleter!.future; @@ -504,6 +514,14 @@ abstract class WidgetBuildRecorder extends Recorder implements FrameRecorder { } } + late final _RecordingWidgetsBinding _binding; + + @override + @mustCallSuper + Future<void> setUpAll() async { + _binding = _RecordingWidgetsBinding.ensureInitialized(); + } + @override @mustCallSuper void frameWillDraw() { @@ -542,9 +560,7 @@ abstract class WidgetBuildRecorder extends Recorder implements FrameRecorder { Future<Profile> run() async { _runCompleter = Completer<void>(); final Profile localProfile = profile = Profile(name: name); - final _RecordingWidgetsBinding binding = - _RecordingWidgetsBinding.ensureInitialized(); - binding._beginRecording(this, _WidgetBuildRecorderHost(this)); + _binding._beginRecording(this, _WidgetBuildRecorderHost(this)); try { await _runCompleter!.future; @@ -655,7 +671,8 @@ class Timeseries { final double dirtyStandardDeviation = _computeStandardDeviationForPopulation(name, candidateValues); // Any value that's higher than this is considered an outlier. - final double outlierCutOff = dirtyAverage + dirtyStandardDeviation; + // Two standard deviations captures 95% of a normal distribution. + final double outlierCutOff = dirtyAverage + dirtyStandardDeviation * 2; // Candidates with outliers removed. final Iterable<double> cleanValues = candidateValues.where((double value) => value <= outlierCutOff); @@ -943,6 +960,15 @@ class Profile { } } + /// A convenience wrapper over [addDataPoint] for adding [AggregatedTimedBlock] + /// to the profile. + /// + /// Uses [AggregatedTimedBlock.name] as the name of the data point, and + /// [AggregatedTimedBlock.duration] as the duration. + void addTimedBlock(AggregatedTimedBlock timedBlock, { required bool reported }) { + addDataPoint(timedBlock.name, Duration(microseconds: timedBlock.duration.toInt()), reported: reported); + } + /// Checks the samples collected so far and sets the appropriate benchmark phase. /// /// If enough warm-up samples have been collected, stops the warm-up phase and @@ -1253,8 +1279,7 @@ void startMeasureFrame(Profile profile) { if (!profile.isWarmingUp) { // Tell the browser to mark the beginning of the frame. - html.window.performance.mark('measured_frame_start#$_currentFrameNumber'); - + web.window.performance.mark('measured_frame_start#$_currentFrameNumber'); _isMeasuringFrame = true; } } @@ -1276,10 +1301,10 @@ void endMeasureFrame() { if (_isMeasuringFrame) { // Tell the browser to mark the end of the frame, and measure the duration. - html.window.performance.mark('measured_frame_end#$_currentFrameNumber'); - html.window.performance.measure( + web.window.performance.mark('measured_frame_end#$_currentFrameNumber'); + web.window.performance.measure( 'measured_frame', - 'measured_frame_start#$_currentFrameNumber', + 'measured_frame_start#$_currentFrameNumber'.toJS, 'measured_frame_end#$_currentFrameNumber', ); @@ -1310,9 +1335,11 @@ void registerEngineBenchmarkValueListener(String name, EngineBenchmarkValueListe if (_engineBenchmarkListeners.isEmpty) { // The first listener is being registered. Register the global listener. - js_util.setProperty(html.window, '_flutter_internal_on_benchmark', _dispatchEngineBenchmarkValue); + web.window['_flutter_internal_on_benchmark'.toJS] = + // Upcast to [Object] to export. + // ignore: unnecessary_cast + (_dispatchEngineBenchmarkValue as Object).toJS; } - _engineBenchmarkListeners[name] = listener; } @@ -1320,8 +1347,9 @@ void registerEngineBenchmarkValueListener(String name, EngineBenchmarkValueListe void stopListeningToEngineBenchmarkValues(String name) { _engineBenchmarkListeners.remove(name); if (_engineBenchmarkListeners.isEmpty) { + // The last listener unregistered. Remove the global listener. - js_util.setProperty(html.window, '_flutter_internal_on_benchmark', null); + web.window['_flutter_internal_on_benchmark'.toJS] = null; } } diff --git a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart index bb9d7fa7c467b..e4e18d598b908 100644 --- a/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart +++ b/dev/benchmarks/macrobenchmarks/lib/web_benchmarks.dart @@ -4,9 +4,11 @@ import 'dart:async'; import 'dart:convert' show json; -import 'dart:html' as html; +import 'dart:js_interop'; import 'dart:math' as math; +import 'package:web/web.dart' as web; + import 'src/web/bench_build_image.dart'; import 'src/web/bench_build_material_checkbox.dart'; import 'src/web/bench_card_infinite_scroll.dart'; @@ -17,6 +19,7 @@ import 'src/web/bench_draw_rect.dart'; import 'src/web/bench_dynamic_clip_on_static_picture.dart'; import 'src/web/bench_image_decoding.dart'; import 'src/web/bench_material_3.dart'; +import 'src/web/bench_material_3_semantics.dart'; import 'src/web/bench_mouse_region_grid_hover.dart'; import 'src/web/bench_mouse_region_grid_scroll.dart'; import 'src/web/bench_mouse_region_mixed_grid_hover.dart'; @@ -33,6 +36,7 @@ import 'src/web/recorder.dart'; typedef RecorderFactory = Recorder Function(); const bool isCanvasKit = bool.fromEnvironment('FLUTTER_WEB_USE_SKIA'); +const bool isSkwasm = bool.fromEnvironment('FLUTTER_WEB_USE_SKWASM'); /// List of all benchmarks that run in the devicelab. /// @@ -59,12 +63,18 @@ final Map<String, RecorderFactory> benchmarks = <String, RecorderFactory>{ BenchMouseRegionGridHover.benchmarkName: () => BenchMouseRegionGridHover(), BenchMouseRegionMixedGridHover.benchmarkName: () => BenchMouseRegionMixedGridHover(), BenchWrapBoxScroll.benchmarkName: () => BenchWrapBoxScroll(), - BenchPlatformViewInfiniteScroll.benchmarkName: () => BenchPlatformViewInfiniteScroll.forward(), - BenchPlatformViewInfiniteScroll.benchmarkNameBackward: () => BenchPlatformViewInfiniteScroll.backward(), + if (!isSkwasm) ...<String, RecorderFactory>{ + // Platform views are not yet supported with Skwasm. + // https://github.com/flutter/flutter/issues/126346 + BenchPlatformViewInfiniteScroll.benchmarkName: () => BenchPlatformViewInfiniteScroll.forward(), + BenchPlatformViewInfiniteScroll.benchmarkNameBackward: () => BenchPlatformViewInfiniteScroll.backward(), + }, BenchMaterial3Components.benchmarkName: () => BenchMaterial3Components(), + BenchMaterial3Semantics.benchmarkName: () => BenchMaterial3Semantics(), + BenchMaterial3ScrollSemantics.benchmarkName: () => BenchMaterial3ScrollSemantics(), - // CanvasKit-only benchmarks - if (isCanvasKit) ...<String, RecorderFactory>{ + // Skia-only benchmarks + if (isCanvasKit || isSkwasm) ...<String, RecorderFactory>{ BenchTextLayout.canvasKitBenchmarkName: () => BenchTextLayout.canvasKit(), BenchBuildColorsGrid.canvasKitBenchmarkName: () => BenchBuildColorsGrid.canvasKit(), BenchTextCachedLayout.canvasKitBenchmarkName: () => BenchTextCachedLayout.canvasKit(), @@ -76,7 +86,7 @@ final Map<String, RecorderFactory> benchmarks = <String, RecorderFactory>{ }, // HTML-only benchmarks - if (!isCanvasKit) ...<String, RecorderFactory>{ + if (!isCanvasKit && !isSkwasm) ...<String, RecorderFactory>{ BenchTextLayout.canvasBenchmarkName: () => BenchTextLayout.canvas(), BenchTextCachedLayout.canvasBenchmarkName: () => BenchTextCachedLayout.canvas(), BenchBuildColorsGrid.canvasBenchmarkName: () => BenchBuildColorsGrid.canvas(), @@ -95,7 +105,7 @@ Future<void> main() async { } await _runBenchmark(nextBenchmark); - html.window.location.reload(); + web.window.location.reload(); } Future<void> _runBenchmark(String benchmarkName) async { @@ -150,8 +160,20 @@ Future<void> _runBenchmark(String benchmarkName) async { ); } +extension WebHTMLElementExtension on web.HTMLElement { + void appendHtml(String html) { + final web.HTMLDivElement div = web.document.createElement('div') as + web.HTMLDivElement; + div.innerHTML = html; + final web.DocumentFragment fragment = web.document.createDocumentFragment(); + fragment.append(div); + web.document.adoptNode(fragment); + append(fragment); + } +} + void _fallbackToManual(String error) { - html.document.body!.appendHtml(''' + web.document.body!.appendHtml(''' <div id="manual-panel"> <h3>$error</h3> @@ -166,28 +188,29 @@ void _fallbackToManual(String error) { } </ul> </div> - ''', validator: html.NodeValidatorBuilder()..allowHtml5()..allowInlineStyles()); + '''); for (final String benchmarkName in benchmarks.keys) { - final html.Element button = html.document.querySelector('#$benchmarkName')!; - button.addEventListener('click', (_) { - final html.Element? manualPanel = html.document.querySelector('#manual-panel'); + final web.Element button = web.document.querySelector('#$benchmarkName')!; + button.addEventListener('click', (JSObject _) { + final web.Element? manualPanel = + web.document.querySelector('#manual-panel'); manualPanel?.remove(); _runBenchmark(benchmarkName); - }); + }.toJS); } } /// Visualizes results on the Web page for manual inspection. void _printResultsToScreen(Profile profile) { - html.document.body!.remove(); - html.document.body = html.BodyElement(); - html.document.body!.appendHtml('<h2>${profile.name}</h2>'); + web.document.body!.remove(); + web.document.body = web.document.createElement('body') as web.HTMLBodyElement; + web.document.body!.appendHtml('<h2>${profile.name}</h2>'); profile.scoreData.forEach((String scoreKey, Timeseries timeseries) { - html.document.body!.appendHtml('<h2>$scoreKey</h2>'); - html.document.body!.appendHtml('<pre>${timeseries.computeStats()}</pre>'); - html.document.body!.append(TimeseriesVisualization(timeseries).render()); + web.document.body!.appendHtml('<h2>$scoreKey</h2>'); + web.document.body!.appendHtml('<pre>${timeseries.computeStats()}</pre>'); + web.document.body!.append(TimeseriesVisualization(timeseries).render()); }); } @@ -195,15 +218,15 @@ void _printResultsToScreen(Profile profile) { class TimeseriesVisualization { TimeseriesVisualization(this._timeseries) { _stats = _timeseries.computeStats(); - _canvas = html.CanvasElement(); - _screenWidth = html.window.screen!.width!; + _canvas = web.document.createElement('canvas') as web.HTMLCanvasElement; + _screenWidth = web.window.screen.width; _canvas.width = _screenWidth; - _canvas.height = (_kCanvasHeight * html.window.devicePixelRatio).round(); + _canvas.height = (_kCanvasHeight * web.window.devicePixelRatio).round(); _canvas.style - ..width = '100%' - ..height = '${_kCanvasHeight}px' - ..outline = '1px solid green'; - _ctx = _canvas.context2D; + ..setProperty('width', '100%') + ..setProperty('height', '${_kCanvasHeight}px') + ..setProperty('outline', '1px solid green'); + _ctx = _canvas.getContext('2d')! as web.CanvasRenderingContext2D; // The amount of vertical space available on the chart. Because some // outliers can be huge they can dwarf all the useful values. So we @@ -218,8 +241,8 @@ class TimeseriesVisualization { final Timeseries _timeseries; late TimeseriesStats _stats; - late html.CanvasElement _canvas; - late html.CanvasRenderingContext2D _ctx; + late web.HTMLCanvasElement _canvas; + late web.CanvasRenderingContext2D _ctx; late int _screenWidth; // Used to normalize benchmark values to chart height. @@ -235,15 +258,15 @@ class TimeseriesVisualization { /// A utility for drawing lines. void drawLine(num x1, num y1, num x2, num y2) { _ctx.beginPath(); - _ctx.moveTo(x1, y1); - _ctx.lineTo(x2, y2); + _ctx.moveTo(x1.toDouble(), y1.toDouble()); + _ctx.lineTo(x2.toDouble(), y2.toDouble()); _ctx.stroke(); } /// Renders the timeseries into a `<canvas>` and returns the canvas element. - html.CanvasElement render() { - _ctx.translate(0, _kCanvasHeight * html.window.devicePixelRatio); - _ctx.scale(1, -html.window.devicePixelRatio); + web.HTMLCanvasElement render() { + _ctx.translate(0, _kCanvasHeight * web.window.devicePixelRatio); + _ctx.scale(1, -web.window.devicePixelRatio); final double barWidth = _screenWidth / _stats.samples.length; double xOffset = 0; @@ -252,19 +275,19 @@ class TimeseriesVisualization { if (sample.isWarmUpValue) { // Put gray background behind warm-up samples. - _ctx.fillStyle = 'rgba(200,200,200,1)'; + _ctx.fillStyle = 'rgba(200,200,200,1)'.toJS; _ctx.fillRect(xOffset, 0, barWidth, _normalized(_maxValueChartRange)); } if (sample.magnitude > _maxValueChartRange) { // The sample value is so big it doesn't fit on the chart. Paint it purple. - _ctx.fillStyle = 'rgba(100,50,100,0.8)'; + _ctx.fillStyle = 'rgba(100,50,100,0.8)'.toJS; } else if (sample.isOutlier) { // The sample is an outlier, color it light red. - _ctx.fillStyle = 'rgba(255,50,50,0.6)'; + _ctx.fillStyle = 'rgba(255,50,50,0.6)'.toJS; } else { // A non-outlier sample, color it light blue. - _ctx.fillStyle = 'rgba(50,50,255,0.6)'; + _ctx.fillStyle = 'rgba(50,50,255,0.6)'.toJS; } _ctx.fillRect(xOffset, 0, barWidth - 1, _normalized(sample.magnitude)); @@ -276,15 +299,15 @@ class TimeseriesVisualization { drawLine(0, _normalized(_stats.average), _screenWidth, _normalized(_stats.average)); // Draw a horizontal dashed line corresponding to the outlier cut off. - _ctx.setLineDash(<num>[5, 5]); + _ctx.setLineDash(<JSAny?>[5.toJS, 5.toJS].toJS); drawLine(0, _normalized(_stats.outlierCutOff), _screenWidth, _normalized(_stats.outlierCutOff)); // Draw a light red band that shows the noise (1 stddev in each direction). - _ctx.fillStyle = 'rgba(255,50,50,0.3)'; + _ctx.fillStyle = 'rgba(255,50,50,0.3)'.toJS; _ctx.fillRect( 0, _normalized(_stats.average * (1 - _stats.noise)), - _screenWidth, + _screenWidth.toDouble(), _normalized(2 * _stats.average * _stats.noise), ); @@ -313,7 +336,7 @@ class LocalBenchmarkServerClient { /// Returns [kManualFallback] if local server is not available (uses 404 as a /// signal). Future<String> requestNextBenchmark() async { - final html.HttpRequest request = await _requestXhr( + final web.XMLHttpRequest request = await _requestXhr( '/next-benchmark', method: 'POST', mimeType: 'application/json', @@ -323,13 +346,13 @@ class LocalBenchmarkServerClient { // 404 is expected in the following cases: // - The benchmark is ran using plain `flutter run`, which does not provide "next-benchmark" handler. // - We ran all benchmarks and the benchmark is telling us there are no more benchmarks to run. - if (request.status == 404) { + if (request.status != 200) { isInManualMode = true; return kManualFallback; } isInManualMode = false; - return request.responseText!; + return request.responseText; } void _checkNotManualMode() { @@ -345,7 +368,7 @@ class LocalBenchmarkServerClient { /// DevTools Protocol. Future<void> startPerformanceTracing(String benchmarkName) async { _checkNotManualMode(); - await html.HttpRequest.request( + await _requestXhr( '/start-performance-tracing?label=$benchmarkName', method: 'POST', mimeType: 'application/json', @@ -355,7 +378,7 @@ class LocalBenchmarkServerClient { /// Stops the performance tracing session started by [startPerformanceTracing]. Future<void> stopPerformanceTracing() async { _checkNotManualMode(); - await html.HttpRequest.request( + await _requestXhr( '/stop-performance-tracing', method: 'POST', mimeType: 'application/json', @@ -366,7 +389,7 @@ class LocalBenchmarkServerClient { /// server. Future<void> sendProfileData(Profile profile) async { _checkNotManualMode(); - final html.HttpRequest request = await html.HttpRequest.request( + final web.XMLHttpRequest request = await _requestXhr( '/profile-data', method: 'POST', mimeType: 'application/json', @@ -385,7 +408,7 @@ class LocalBenchmarkServerClient { /// The server will halt the devicelab task and log the error. Future<void> reportError(dynamic error, StackTrace stackTrace) async { _checkNotManualMode(); - await html.HttpRequest.request( + await _requestXhr( '/on-error', method: 'POST', mimeType: 'application/json', @@ -399,7 +422,7 @@ class LocalBenchmarkServerClient { /// Reports a message about the demo to the benchmark server. Future<void> printToConsole(String report) async { _checkNotManualMode(); - await html.HttpRequest.request( + await _requestXhr( '/print-to-console', method: 'POST', mimeType: 'text/plain', @@ -409,7 +432,7 @@ class LocalBenchmarkServerClient { /// This is the same as calling [html.HttpRequest.request] but it doesn't /// crash on 404, which we use to detect `flutter run`. - Future<html.HttpRequest> _requestXhr( + Future<web.XMLHttpRequest> _requestXhr( String url, { String? method, bool? withCredentials, @@ -418,11 +441,11 @@ class LocalBenchmarkServerClient { Map<String, String>? requestHeaders, dynamic sendData, }) { - final Completer<html.HttpRequest> completer = Completer<html.HttpRequest>(); - final html.HttpRequest xhr = html.HttpRequest(); + final Completer<web.XMLHttpRequest> completer = Completer<web.XMLHttpRequest>(); + final web.XMLHttpRequest xhr = web.XMLHttpRequest(); method ??= 'GET'; - xhr.open(method, url, async: true); + xhr.open(method, url, true); if (withCredentials != null) { xhr.withCredentials = withCredentials; @@ -442,14 +465,16 @@ class LocalBenchmarkServerClient { }); } - xhr.onLoad.listen((html.ProgressEvent e) { + xhr.addEventListener('load', (web.ProgressEvent e) { completer.complete(xhr); - }); + }.toJS); - xhr.onError.listen(completer.completeError); + xhr.addEventListener('error', (JSObject error) { + return completer.completeError(error); + }.toJS); if (sendData != null) { - xhr.send(sendData); + xhr.send((sendData as Object?).jsify()); } else { xhr.send(); } diff --git a/dev/benchmarks/macrobenchmarks/pubspec.yaml b/dev/benchmarks/macrobenchmarks/pubspec.yaml index c2ceb18e33eab..7617d7829eb31 100644 --- a/dev/benchmarks/macrobenchmarks/pubspec.yaml +++ b/dev/benchmarks/macrobenchmarks/pubspec.yaml @@ -18,6 +18,8 @@ dependencies: # flutter update-packages --force-upgrade flutter_gallery_assets: 1.0.2 + web: 0.1.4-beta + async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -25,40 +27,39 @@ dependencies: collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - plugin_platform_interface: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.24.2 + test: 1.24.3 integration_test: sdk: flutter - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -70,7 +71,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -209,4 +210,4 @@ flutter: fonts: - asset: packages/flutter_gallery_assets/fonts/GalleryIcons.ttf -# PUBSPEC CHECKSUM: 1586 +# PUBSPEC CHECKSUM: 1c29 diff --git a/dev/benchmarks/microbenchmarks/ios/Runner/Info.plist b/dev/benchmarks/microbenchmarks/ios/Runner/Info.plist index 57ca345c81e45..82452718ef5fe 100644 --- a/dev/benchmarks/microbenchmarks/ios/Runner/Info.plist +++ b/dev/benchmarks/microbenchmarks/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/benchmarks/microbenchmarks/lib/foundation/timeline_bench.dart b/dev/benchmarks/microbenchmarks/lib/foundation/timeline_bench.dart index 339a46ce15b6f..7c9eb4a3968ca 100644 --- a/dev/benchmarks/microbenchmarks/lib/foundation/timeline_bench.dart +++ b/dev/benchmarks/microbenchmarks/lib/foundation/timeline_bench.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:developer'; +import 'package:flutter/foundation.dart'; import '../common.dart'; @@ -16,8 +16,8 @@ void main() { final Stopwatch watch = Stopwatch(); watch.start(); for (int i = 0; i < _kNumIterations; i += 1) { - Timeline.startSync('foo'); - Timeline.finishSync(); + FlutterTimeline.startSync('foo'); + FlutterTimeline.finishSync(); } watch.stop(); @@ -31,14 +31,14 @@ void main() { watch.reset(); watch.start(); for (int i = 0; i < _kNumIterations; i += 1) { - Timeline.startSync('foo', arguments: <String, dynamic>{ + FlutterTimeline.startSync('foo', arguments: <String, dynamic>{ 'int': 1234, 'double': 0.3, 'list': <int>[1, 2, 3, 4], 'map': <String, dynamic>{'map': true}, 'bool': false, }); - Timeline.finishSync(); + FlutterTimeline.finishSync(); } watch.stop(); diff --git a/dev/benchmarks/microbenchmarks/lib/language/compute_bench.dart b/dev/benchmarks/microbenchmarks/lib/language/compute_bench.dart index aa9c43129a6e8..36e8ef75276d2 100644 --- a/dev/benchmarks/microbenchmarks/lib/language/compute_bench.dart +++ b/dev/benchmarks/microbenchmarks/lib/language/compute_bench.dart @@ -12,9 +12,10 @@ const int _kNumWarmUp = 100; class Data { Data(this.value); - Map<String, dynamic> toJson() => <String, dynamic>{ 'value': value }; - final int value; + + @override + String toString() => 'Data($value)'; } List<Data> test(int length) { diff --git a/dev/benchmarks/microbenchmarks/lib/ui/image_bench.dart b/dev/benchmarks/microbenchmarks/lib/ui/image_bench.dart index ac3ba0bc54028..2b807acf3c915 100644 --- a/dev/benchmarks/microbenchmarks/lib/ui/image_bench.dart +++ b/dev/benchmarks/microbenchmarks/lib/ui/image_bench.dart @@ -86,7 +86,7 @@ Future<void> main() async { watch.start(); for (int i = 0; i < 10; i += 1) { await Future.wait(<Future<ui.ImmutableBuffer>>[ - for (String asset in assets) + for (final String asset in assets) rootBundle.loadBuffer(asset) ]); } diff --git a/dev/benchmarks/microbenchmarks/pubspec.yaml b/dev/benchmarks/microbenchmarks/pubspec.yaml index 39ede57b9cedb..c54fbc3f85e6f 100644 --- a/dev/benchmarks/microbenchmarks/pubspec.yaml +++ b/dev/benchmarks/microbenchmarks/pubspec.yaml @@ -12,12 +12,12 @@ dependencies: sdk: flutter stocks: path: ../test_apps/stocks - test: 1.24.2 + test: 1.24.3 flutter_gallery_assets: 1.0.2 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -29,7 +29,7 @@ dependencies: fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -37,8 +37,8 @@ dependencies: io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" isolate: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -57,12 +57,13 @@ dependencies: stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -136,4 +137,4 @@ flutter: - packages/flutter_gallery_assets/people/square/stella.png - packages/flutter_gallery_assets/people/square/trevor.png -# PUBSPEC CHECKSUM: 1f42 +# PUBSPEC CHECKSUM: 06a0 diff --git a/dev/benchmarks/multiple_flutters/module/pubspec.yaml b/dev/benchmarks/multiple_flutters/module/pubspec.yaml index 20ac49fbd1344..fe87a6f038a95 100644 --- a/dev/benchmarks/multiple_flutters/module/pubspec.yaml +++ b/dev/benchmarks/multiple_flutters/module/pubspec.yaml @@ -21,16 +21,15 @@ dependencies: file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider: 2.0.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - path_provider_android: 2.0.21 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path_provider_android: 2.0.27 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider_foundation: 2.2.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - path_provider_linux: 2.1.10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path_provider_linux: 2.1.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider_platform_interface: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - path_provider_windows: 2.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path_provider_windows: 2.1.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" platform: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" plugin_platform_interface: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" process: 4.2.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -39,7 +38,8 @@ dependencies: term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - win32: 4.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + win32: 5.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" xdg_directories: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: @@ -50,4 +50,4 @@ flutter: androidPackage: com.example.multiple_flutters_module iosBundleIdentifier: com.example.multipleFluttersModule -# PUBSPEC CHECKSUM: d98d +# PUBSPEC CHECKSUM: 4eb9 diff --git a/dev/benchmarks/platform_channels_benchmarks/pubspec.yaml b/dev/benchmarks/platform_channels_benchmarks/pubspec.yaml index 1d82bc8efbf63..ebb1feccb255e 100644 --- a/dev/benchmarks/platform_channels_benchmarks/pubspec.yaml +++ b/dev/benchmarks/platform_channels_benchmarks/pubspec.yaml @@ -16,9 +16,9 @@ dependencies: path: ../microbenchmarks cupertino_icons: 1.0.5 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -31,13 +31,13 @@ dependencies: file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter_gallery_assets: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -57,13 +57,14 @@ dependencies: stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test: 1.24.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test: 1.24.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -73,4 +74,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: cf2d +# PUBSPEC CHECKSUM: 918b diff --git a/dev/benchmarks/platform_views_layout/ios/Runner/Info.plist b/dev/benchmarks/platform_views_layout/ios/Runner/Info.plist index c2c46f54def6e..08da00d98485a 100644 --- a/dev/benchmarks/platform_views_layout/ios/Runner/Info.plist +++ b/dev/benchmarks/platform_views_layout/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>io.flutter.embedded_views_preview</key> <true/> <key>io.flutter.metal_preview</key> diff --git a/dev/benchmarks/platform_views_layout/lib/main.dart b/dev/benchmarks/platform_views_layout/lib/main.dart index 9fd59bfb1a12f..4eb4f81c43998 100644 --- a/dev/benchmarks/platform_views_layout/lib/main.dart +++ b/dev/benchmarks/platform_views_layout/lib/main.dart @@ -5,7 +5,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart' show timeDilation; void main() { runApp( @@ -31,12 +30,6 @@ class PlatformViewAppState extends State<PlatformViewApp> { home: const PlatformViewLayout(), ); } - - void toggleAnimationSpeed() { - setState(() { - timeDilation = (timeDilation != 1.0) ? 1.0 : 5.0; - }); - } } class PlatformViewLayout extends StatelessWidget { diff --git a/dev/benchmarks/platform_views_layout/lib/main_non_intersecting.dart b/dev/benchmarks/platform_views_layout/lib/main_non_intersecting.dart index 63156e8f9924f..9107e9a6b9f76 100644 --- a/dev/benchmarks/platform_views_layout/lib/main_non_intersecting.dart +++ b/dev/benchmarks/platform_views_layout/lib/main_non_intersecting.dart @@ -5,7 +5,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart' show timeDilation; void main() { runApp( @@ -31,12 +30,6 @@ class PlatformViewAppState extends State<PlatformViewApp> { home: const PlatformViewLayout(), ); } - - void toggleAnimationSpeed() { - setState(() { - timeDilation = (timeDilation != 1.0) ? 1.0 : 5.0; - }); - } } class PlatformViewLayout extends StatelessWidget { diff --git a/dev/benchmarks/platform_views_layout/pubspec.yaml b/dev/benchmarks/platform_views_layout/pubspec.yaml index 02b0030d30c0c..396fbd7838694 100644 --- a/dev/benchmarks/platform_views_layout/pubspec.yaml +++ b/dev/benchmarks/platform_views_layout/pubspec.yaml @@ -21,8 +21,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -32,30 +31,32 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -67,7 +68,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -80,4 +81,4 @@ flutter: - packages/flutter_gallery_assets/people/square/ali.png - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png -# PUBSPEC CHECKSUM: 09ca +# PUBSPEC CHECKSUM: 1c29 diff --git a/dev/benchmarks/platform_views_layout_hybrid_composition/ios/Runner/Info.plist b/dev/benchmarks/platform_views_layout_hybrid_composition/ios/Runner/Info.plist index 028706db29782..f2d51edfee303 100644 --- a/dev/benchmarks/platform_views_layout_hybrid_composition/ios/Runner/Info.plist +++ b/dev/benchmarks/platform_views_layout_hybrid_composition/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>io.flutter.embedded_views_preview</key> <true/> <key>io.flutter.metal_preview</key> diff --git a/dev/benchmarks/platform_views_layout_hybrid_composition/lib/main.dart b/dev/benchmarks/platform_views_layout_hybrid_composition/lib/main.dart index 98cdb0c65306c..f56bc63cc1f1d 100644 --- a/dev/benchmarks/platform_views_layout_hybrid_composition/lib/main.dart +++ b/dev/benchmarks/platform_views_layout_hybrid_composition/lib/main.dart @@ -5,7 +5,6 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart' show timeDilation; import 'package:flutter_driver/driver_extension.dart'; import 'android_platform_view.dart'; @@ -35,12 +34,6 @@ class PlatformViewAppState extends State<PlatformViewApp> { home: const PlatformViewLayout(), ); } - - void toggleAnimationSpeed() { - setState(() { - timeDilation = (timeDilation != 1.0) ? 1.0 : 5.0; - }); - } } class PlatformViewLayout extends StatelessWidget { diff --git a/dev/benchmarks/platform_views_layout_hybrid_composition/pubspec.yaml b/dev/benchmarks/platform_views_layout_hybrid_composition/pubspec.yaml index 22ee070b7295a..df764b6158940 100644 --- a/dev/benchmarks/platform_views_layout_hybrid_composition/pubspec.yaml +++ b/dev/benchmarks/platform_views_layout_hybrid_composition/pubspec.yaml @@ -21,8 +21,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -32,30 +31,32 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -67,7 +68,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -80,4 +81,4 @@ flutter: - packages/flutter_gallery_assets/people/square/ali.png - packages/flutter_gallery_assets/places/india_chettinad_silk_maker.png -# PUBSPEC CHECKSUM: 09ca +# PUBSPEC CHECKSUM: 1c29 diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Info.plist b/dev/benchmarks/test_apps/stocks/ios/Runner/Info.plist index ea08bb1772f02..a110ad2a97151 100644 --- a/dev/benchmarks/test_apps/stocks/ios/Runner/Info.plist +++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/benchmarks/test_apps/stocks/lib/main.dart b/dev/benchmarks/test_apps/stocks/lib/main.dart index 159de8c8eb6de..536e906c3c2e1 100644 --- a/dev/benchmarks/test_apps/stocks/lib/main.dart +++ b/dev/benchmarks/test_apps/stocks/lib/main.dart @@ -50,11 +50,13 @@ class StocksAppState extends State<StocksApp> { switch (_configuration.stockMode) { case StockMode.optimistic: return ThemeData( + useMaterial3: false, brightness: Brightness.light, primarySwatch: Colors.purple, ); case StockMode.pessimistic: return ThemeData( + useMaterial3: false, brightness: Brightness.dark, primarySwatch: Colors.purple, ); diff --git a/dev/benchmarks/test_apps/stocks/pubspec.yaml b/dev/benchmarks/test_apps/stocks/pubspec.yaml index c69ae95c19ab4..5d08473095290 100644 --- a/dev/benchmarks/test_apps/stocks/pubspec.yaml +++ b/dev/benchmarks/test_apps/stocks/pubspec.yaml @@ -17,7 +17,6 @@ dependencies: clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -26,17 +25,18 @@ dependencies: term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter flutter_driver: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -44,11 +44,12 @@ dev_dependencies: fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -63,9 +64,9 @@ dev_dependencies: stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -75,4 +76,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 3cec +# PUBSPEC CHECKSUM: d14b diff --git a/dev/bots/allowlist.dart b/dev/bots/allowlist.dart index cd011c38beec3..ddd2b66545ca0 100644 --- a/dev/bots/allowlist.dart +++ b/dev/bots/allowlist.dart @@ -31,7 +31,6 @@ const Set<String> kCorePackageAllowList = <String>{ 'fuchsia_remote_debug_protocol', 'integration_test', 'intl', - 'js', 'matcher', 'material_color_utilities', 'meta', @@ -48,5 +47,6 @@ const Set<String> kCorePackageAllowList = <String>{ 'test_api', 'vector_math', 'vm_service', + 'web', 'webdriver', }; diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart index 9f9bc8b316e53..19056c54f36b1 100644 --- a/dev/bots/analyze.dart +++ b/dev/bots/analyze.dart @@ -239,7 +239,15 @@ class _DoubleClampVisitor extends RecursiveAstVisitor<CompilationUnit> { @override CompilationUnit? visitMethodInvocation(MethodInvocation node) { - if (node.methodName.name == 'clamp') { + final NodeList<Expression> arguments = node.argumentList.arguments; + // This may produce false positives when `node.target` is not a subtype of + // num. The static type of `node.target` isn't guaranteed to be resolved at + // this time. Check whether the argument list consists of 2 positional args + // to reduce false positives. + final bool isNumClampInvocation = node.methodName.name == 'clamp' + && arguments.length == 2 + && !arguments.any((Expression exp) => exp is NamedExpression); + if (isNumClampInvocation) { final _Line line = _getLine(parseResult, node.function.offset); if (!line.content.contains('// ignore_clamp_double_lint')) { clamps.add(line); diff --git a/dev/bots/docs.sh b/dev/bots/docs.sh index 978d607429a34..6a0cec83c97d8 100755 --- a/dev/bots/docs.sh +++ b/dev/bots/docs.sh @@ -20,12 +20,12 @@ function generate_docs() { # Install and activate dartdoc. # When updating to a new dartdoc version, please also update # `dartdoc_options.yaml` to include newly introduced error and warning types. - "$DART" pub global activate dartdoc 6.2.2 + "$DART" pub global activate dartdoc 6.3.0 # Install and activate the snippets tool, which resides in the # assets-for-api-docs repo: # https://github.com/flutter/assets-for-api-docs/tree/master/packages/snippets - "$DART" pub global activate snippets 0.3.0 + "$DART" pub global activate snippets 0.3.1 # This script generates a unified doc set, and creates # a custom index.html, placing everything into dev/docs/doc. diff --git a/dev/bots/pubspec.yaml b/dev/bots/pubspec.yaml index 0d0c3d7440c3f..d1787a231ad73 100644 --- a/dev/bots/pubspec.yaml +++ b/dev/bots/pubspec.yaml @@ -5,7 +5,7 @@ environment: sdk: '>=3.0.0-0 <4.0.0' dependencies: - args: 2.4.1 + args: 2.4.2 crypto: 3.0.3 intl: 0.18.1 flutter_devicelab: @@ -15,11 +15,11 @@ dependencies: path: 1.8.3 platform: 3.1.0 process: 4.2.4 - test: 1.24.2 + test: 1.24.3 - _discoveryapis_commons: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _discoveryapis_commons: 1.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" archive: 3.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -31,18 +31,18 @@ dependencies: equatable: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - gcloud: 0.8.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + gcloud: 0.8.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" googleapis: 3.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - googleapis_auth: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + googleapis_auth: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http: 0.13.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" json_annotation: 4.8.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - metrics_center: 1.0.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + metrics_center: 1.0.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -60,15 +60,16 @@ dependencies: stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test_api: 0.5.2 + test_api: 0.6.0 -# PUBSPEC CHECKSUM: 1ebf +# PUBSPEC CHECKSUM: f223 diff --git a/dev/bots/test.dart b/dev/bots/test.dart index a125356ca1db3..1f3e0a0590147 100644 --- a/dev/bots/test.dart +++ b/dev/bots/test.dart @@ -185,7 +185,7 @@ const Map<String, List<String>> kWebTestFileKnownFailures = <String, List<String }; const String kTestHarnessShardName = 'test_harness_tests'; -const List<String> _kAllBuildModes = <String>['debug', 'profile', 'release']; +const List<String> _kAllBuildModes = <String>['release']; // The seed used to shuffle tests. If not passed with // --test-randomize-ordering-seed=<seed> on the command line, it will be set the @@ -241,24 +241,24 @@ Future<void> main(List<String> args) async { printProgress('Running task: ${Platform.environment[CIRRUS_TASK_NAME]}'); } await selectShard(<String, ShardRunner>{ - 'add_to_app_life_cycle_tests': _runAddToAppLifeCycleTests, - 'build_tests': _runBuildTests, - 'framework_coverage': _runFrameworkCoverage, + // 'add_to_app_life_cycle_tests': _runAddToAppLifeCycleTests, + // 'build_tests': _runBuildTests, + // 'framework_coverage': _runFrameworkCoverage, 'framework_tests': _runFrameworkTests, - 'tool_tests': _runToolTests, - // web_tool_tests is also used by HHH: https://dart.googlesource.com/recipes/+/refs/heads/master/recipes/dart/flutter_engine.py - 'web_tool_tests': _runWebToolTests, - 'tool_integration_tests': _runIntegrationToolTests, - 'tool_host_cross_arch_tests': _runToolHostCrossArchTests, - // All the unit/widget tests run using `flutter test --platform=chrome --web-renderer=html` - 'web_tests': _runWebHtmlUnitTests, - // All the unit/widget tests run using `flutter test --platform=chrome --web-renderer=canvaskit` - 'web_canvaskit_tests': _runWebCanvasKitUnitTests, - // All web integration tests - 'web_long_running_tests': _runWebLongRunningTests, - 'flutter_plugins': _runFlutterPackagesTests, - 'skp_generator': _runSkpGeneratorTests, - kTestHarnessShardName: _runTestHarnessTests, // Used for testing this script; also run as part of SHARD=framework_tests, SUBSHARD=misc. + // 'tool_tests': _runToolTests, + // // web_tool_tests is also used by HHH: https://dart.googlesource.com/recipes/+/refs/heads/master/recipes/dart/flutter_engine.py + // 'web_tool_tests': _runWebToolTests, + // 'tool_integration_tests': _runIntegrationToolTests, + // 'tool_host_cross_arch_tests': _runToolHostCrossArchTests, + // // All the unit/widget tests run using `flutter test --platform=chrome --web-renderer=html` + // 'web_tests': _runWebHtmlUnitTests, + // // All the unit/widget tests run using `flutter test --platform=chrome --web-renderer=canvaskit` + // 'web_canvaskit_tests': _runWebCanvasKitUnitTests, + // // All web integration tests + // 'web_long_running_tests': _runWebLongRunningTests, + // 'flutter_plugins': _runFlutterPackagesTests, + // 'skp_generator': _runSkpGeneratorTests, + // kTestHarnessShardName: _runTestHarnessTests, // Used for testing this script; also run as part of SHARD=framework_tests, SUBSHARD=misc. }); } catch (error, stackTrace) { foundError(<String>[ @@ -277,7 +277,8 @@ Future<void> main(List<String> args) async { } final String _luciBotId = Platform.environment['SWARMING_BOT_ID'] ?? ''; -final bool _runningInDartHHHBot = _luciBotId.startsWith('luci-dart-'); +final bool _runningInDartHHHBot = + _luciBotId.startsWith('luci-dart-') || _luciBotId.startsWith('dart-tests-'); /// Verify the Flutter Engine is the revision in /// bin/cache/internal/engine.version. @@ -318,7 +319,8 @@ Future<void> _validateEngineHash() async { Future<void> _runTestHarnessTests() async { printProgress('${green}Running test harness tests...$reset'); - await _validateEngineHash(); + // TODO(felangel): flutter_test executable does not point to the shorebird engine revision. + // await _validateEngineHash(); // Verify that the tests actually return failure on failure and success on // success. @@ -398,10 +400,11 @@ Future<void> _runTestHarnessTests() async { } // Verify that we correctly generated the version file. - final String? versionError = await verifyVersion(File(path.join(flutterRoot, 'version'))); - if (versionError != null) { - foundError(<String>[versionError]); - } + // TODO(felangel): teach shorebird to generate the correct version file + // final String? versionError = await verifyVersion(File(path.join(flutterRoot, 'version'))); + // if (versionError != null) { + // foundError(<String>[versionError]); + // } } final String _toolsPath = path.join(flutterRoot, 'packages', 'flutter_tools'); @@ -970,16 +973,17 @@ Future<void> _runFrameworkTests() async { await runTracingTests(); await runFixTests('flutter'); await runFixTests('flutter_test'); + await runFixTests('integration_test'); await runPrivateTests(); } Future<void> runMisc() async { printProgress('${green}Running package tests$reset for directories other than packages/flutter'); await _runTestHarnessTests(); - await runExampleTests(); - await _runDartTest(path.join(flutterRoot, 'dev', 'bots')); - await _runDartTest(path.join(flutterRoot, 'dev', 'devicelab'), ensurePrecompiledTool: false); // See https://github.com/flutter/flutter/issues/86209 - await _runDartTest(path.join(flutterRoot, 'dev', 'conductor', 'core'), forceSingleCore: true); + // await runExampleTests(); + // await _runDartTest(path.join(flutterRoot, 'dev', 'bots')); + // await _runDartTest(path.join(flutterRoot, 'dev', 'devicelab'), ensurePrecompiledTool: false); // See https://github.com/flutter/flutter/issues/86209 + // await _runDartTest(path.join(flutterRoot, 'dev', 'conductor', 'core'), forceSingleCore: true); // TODO(gspencergoog): Remove the exception for fatalWarnings once https://github.com/flutter/flutter/issues/113782 has landed. await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'android_semantics_testing'), fatalWarnings: false); await _runFlutterTest(path.join(flutterRoot, 'dev', 'integration_tests', 'ui')); @@ -989,12 +993,12 @@ Future<void> _runFrameworkTests() async { await _runFlutterTest(path.join(flutterRoot, 'dev', 'tools', 'gen_keycodes')); await _runFlutterTest(path.join(flutterRoot, 'dev', 'benchmarks', 'test_apps', 'stocks')); await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tests: <String>[path.join('test', 'src', 'real_tests')]); - await _runFlutterTest(path.join(flutterRoot, 'packages', 'integration_test'), options: <String>[ - '--enable-vmservice', - // Web-specific tests depend on Chromium, so they run as part of the web_long_running_tests shard. - '--exclude-tags=web', - ]); - await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_goldens')); + // await _runFlutterTest(path.join(flutterRoot, 'packages', 'integration_test'), options: <String>[ + // '--enable-vmservice', + // // Web-specific tests depend on Chromium, so they run as part of the web_long_running_tests shard. + // '--exclude-tags=web', + // ]); + // await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_goldens')); await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations')); await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test')); await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol')); @@ -1026,9 +1030,9 @@ Future<void> _runFrameworkTests() async { } await selectSubshard(<String, ShardRunner>{ - 'widgets': runWidgets, - 'libraries': runLibraries, - 'slow': runSlow, + // 'widgets': runWidgets, + // 'libraries': runLibraries, + // 'slow': runSlow, 'misc': runMisc, }); } @@ -1136,7 +1140,7 @@ Future<void> _runWebUnitTests(String webRenderer) async { Future<void> _runWebLongRunningTests() async { final String engineVersion = File(engineVersionFile).readAsStringSync().trim(); final List<ShardRunner> tests = <ShardRunner>[ - for (String buildMode in _kAllBuildModes) ...<ShardRunner>[ + for (final String buildMode in _kAllBuildModes) ...<ShardRunner>[ () => _runFlutterDriverWebTest( testAppDirectory: path.join('packages', 'integration_test', 'example'), target: path.join('test_driver', 'failure.dart'), @@ -1374,6 +1378,14 @@ Future<void> _runWebTreeshakeTest() async { pos = javaScript.indexOf(word, pos); } + // The following are classes from `timeline.dart` that should be treeshaken + // off unless the app (typically a benchmark) uses methods that need them. + expect(javaScript.contains('AggregatedTimedBlock'), false); + expect(javaScript.contains('AggregatedTimings'), false); + expect(javaScript.contains('_BlockBuffer'), false); + expect(javaScript.contains('_StringListChain'), false); + expect(javaScript.contains('_Float64ListChain'), false); + const int kMaxExpectedDebugFillProperties = 11; if (count > kMaxExpectedDebugFillProperties) { throw Exception( diff --git a/dev/ci/docker_linux/Dockerfile b/dev/ci/docker_linux/Dockerfile index 11c70631a6a5c..db58a1a3a6261 100644 --- a/dev/ci/docker_linux/Dockerfile +++ b/dev/ci/docker_linux/Dockerfile @@ -12,9 +12,12 @@ # Last manual update 2021-09-24 (changing this comment will re-build image) -FROM ubuntu:bionic@sha256:eb1392bbdde63147bc2b4ff1a4053dcfe6d15e4dfd3cce29e9b9f52a4f88bc74 +FROM ubuntu:focal@sha256:f8f658407c35733471596f25fdb4ed748b80e545ab57e84efbdb1dbbb01bd70e MAINTAINER Flutter Developers <flutter-dev@googlegroups.com> +ENV TZ=America/Los_Angeles +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + RUN apt-get update -y && \ apt-get upgrade -y diff --git a/dev/conductor/core/bin/packages_autoroller.dart b/dev/conductor/core/bin/packages_autoroller.dart index db9635e73a308..6cb35b2cf8712 100644 --- a/dev/conductor/core/bin/packages_autoroller.dart +++ b/dev/conductor/core/bin/packages_autoroller.dart @@ -15,8 +15,8 @@ import 'package:process/process.dart'; const String kTokenOption = 'token'; const String kGithubClient = 'github-client'; -const String kMirrorRemote = 'mirror-remote'; const String kUpstreamRemote = 'upstream-remote'; +const String kGithubAccountName = 'flutter-pub-roller-bot'; Future<void> main(List<String> args) { return run(args); @@ -39,11 +39,10 @@ Future<void> run( help: 'Path to GitHub CLI client. If not provided, it is assumed `gh` is ' 'present on the PATH.', ); + // TODO(fujino): delete after recipe has been migrated to stop passing this parser.addOption( - kMirrorRemote, - help: 'The mirror git remote that the feature branch will be pushed to. ' - 'Required', - mandatory: true, + 'mirror-remote', + help: '(Deprecated) this is now a no-op. To change the account, edit this tool.', ); parser.addOption( kUpstreamRemote, @@ -64,7 +63,7 @@ ${parser.usage} rethrow; } - final String mirrorUrl = results[kMirrorRemote]! as String; + const String mirrorUrl = 'https://github.com/flutter-pub-roller-bot/flutter.git'; final String upstreamUrl = results[kUpstreamRemote]! as String; final String tokenPath = results[kTokenOption]! as String; final File tokenFile = fs.file(tokenPath); @@ -92,6 +91,7 @@ ${parser.usage} orgName: _parseOrgName(mirrorUrl), token: token, processManager: processManager, + githubUsername: kGithubAccountName, ).roll(); } diff --git a/dev/conductor/core/lib/src/packages_autoroller.dart b/dev/conductor/core/lib/src/packages_autoroller.dart index b27a2bb65e398..b39a15936b7ea 100644 --- a/dev/conductor/core/lib/src/packages_autoroller.dart +++ b/dev/conductor/core/lib/src/packages_autoroller.dart @@ -20,7 +20,7 @@ class PackageAutoroller { required this.framework, required this.orgName, required this.processManager, - this.githubUsername = 'fluttergithubbot', + required this.githubUsername, Stdio? stdio, }) { this.stdio = stdio ?? VerboseStdio.local(); @@ -50,9 +50,11 @@ class PackageAutoroller { static const String hostname = 'github.com'; - static const String prBody = ''' -This PR was generated by `flutter update-packages --force-upgrade`. -'''; + String get prBody { + return ''' +This PR was generated by the automated +[Pub packages autoroller](https://github.com/flutter/flutter/blob/main/dev/conductor/core/bin/packages_autoroller.dart).'''; + } /// Name of the feature branch to be opened on against the mirror repo. /// @@ -109,7 +111,7 @@ This PR was generated by `flutter update-packages --force-upgrade`. Future<bool> updatePackages({ bool verbose = true, }) async { - final String author = '$githubUsername <$githubUsername@gmail.com>'; + final String author = '$githubUsername <$githubUsername@google.com>'; await framework.newBranch(await featureBranchName); final io.Process flutterProcess = await framework.streamFlutter(<String>[ @@ -166,12 +168,13 @@ This PR was generated by `flutter update-packages --force-upgrade`. ); } + static const String _prTitle = 'Roll pub packages'; + /// Create a pull request on GitHub. /// /// Depends on the gh cli tool. Future<void> createPr({ required io.Directory repository, - String title = 'Roll pub packages', String body = 'This PR was generated by `flutter update-packages --force-upgrade`.', String base = FrameworkRepository.defaultBranch, bool draft = false, @@ -185,7 +188,7 @@ This PR was generated by `flutter update-packages --force-upgrade`. 'pr', 'create', '--title', - title.trim(), + _prTitle, '--body', body.trim(), '--head', @@ -257,15 +260,19 @@ This PR was generated by `flutter update-packages --force-upgrade`. final String openPrString = await cli(<String>[ 'pr', 'list', + '--author', githubUsername, + '--repo', 'flutter/flutter', + '--state', 'open', - // We are only interested in pub rolls, not devicelab flaky PRs - '--label', - 'tool', + + '--search', + _prTitle, + // Return structured JSON with the PR numbers of open PRs '--json', 'number', @@ -274,6 +281,7 @@ This PR was generated by `flutter update-packages --force-upgrade`. // This will be an array of objects, one for each open PR. final List<Object?> openPrs = json.decode(openPrString) as List<Object?>; + // We are only interested in pub rolls, not devicelab flaky PRs if (openPrs.isNotEmpty) { log('$githubUsername already has open tool PRs:\n$openPrs'); return true; diff --git a/dev/conductor/core/pubspec.yaml b/dev/conductor/core/pubspec.yaml index f7a8a8842b1bc..e650875668300 100644 --- a/dev/conductor/core/pubspec.yaml +++ b/dev/conductor/core/pubspec.yaml @@ -8,13 +8,13 @@ environment: dependencies: archive: 3.3.2 - args: 2.4.1 + args: 2.4.2 http: 0.13.6 intl: 0.18.1 meta: 1.9.1 path: 1.8.3 process: 4.2.4 - protobuf: 2.1.0 + protobuf: 3.0.0 async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -30,21 +30,21 @@ dependencies: typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.24.2 - test_api: 0.5.2 + test: 1.24.3 + test_api: 0.6.0 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -58,11 +58,11 @@ dev_dependencies: source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 8224 +# PUBSPEC CHECKSUM: 282c diff --git a/dev/conductor/core/test/codesign_test.dart b/dev/conductor/core/test/codesign_test.dart index 9c246f9ae9a95..423d24522820f 100644 --- a/dev/conductor/core/test/codesign_test.dart +++ b/dev/conductor/core/test/codesign_test.dart @@ -155,7 +155,7 @@ void main() { ], stdout: allBinaries.join('\n'), ), - for (String bin in allBinaries) + for (final String bin in allBinaries) FakeCommand( command: <String>['file', '--mime-type', '-b', bin], stdout: 'application/x-mach-binary', @@ -239,7 +239,7 @@ void main() { ], stdout: allBinaries.join('\n'), ), - for (String bin in allBinaries) + for (final String bin in allBinaries) FakeCommand( command: <String>['file', '--mime-type', '-b', bin], stdout: 'application/x-mach-binary', @@ -329,7 +329,7 @@ void main() { ], stdout: allBinaries.join('\n'), ), - for (String bin in allBinaries) + for (final String bin in allBinaries) FakeCommand( command: <String>['file', '--mime-type', '-b', bin], stdout: 'application/x-mach-binary', @@ -422,7 +422,7 @@ void main() { ], stdout: allBinaries.join('\n'), ), - for (String bin in allBinaries) + for (final String bin in allBinaries) FakeCommand( command: <String>['file', '--mime-type', '-b', bin], stdout: 'application/x-mach-binary', @@ -514,7 +514,7 @@ void main() { ], stdout: allBinaries.join('\n'), ), - for (String bin in allBinaries) + for (final String bin in allBinaries) FakeCommand( command: <String>['file', '--mime-type', '-b', bin], stdout: 'application/x-mach-binary', @@ -578,7 +578,7 @@ void main() { ], stdout: allBinaries.join('\n'), ), - for (String bin in allBinaries) + for (final String bin in allBinaries) FakeCommand( command: <String>['file', '--mime-type', '-b', bin], stdout: 'application/x-mach-binary', diff --git a/dev/conductor/core/test/packages_autoroller_test.dart b/dev/conductor/core/test/packages_autoroller_test.dart index 9ffc6cd8d19ce..50e21a1aee56f 100644 --- a/dev/conductor/core/test/packages_autoroller_test.dart +++ b/dev/conductor/core/test/packages_autoroller_test.dart @@ -63,6 +63,7 @@ void main() { orgName: orgName, processManager: processManager, stdio: stdio, + githubUsername: 'flutter-pub-roller-bot', ); }); @@ -199,13 +200,13 @@ void main() { 'pr', 'list', '--author', - 'fluttergithubbot', + 'flutter-pub-roller-bot', '--repo', 'flutter/flutter', '--state', 'open', - '--label', - 'tool', + '--search', + 'Roll pub packages', '--json', 'number', // Non empty array means there are open PRs by the bot with the tool label @@ -216,7 +217,7 @@ void main() { await controller.stream.drain(); await rollFuture; expect(processManager, hasNoRemainingExpectations); - expect(stdio.stdout, contains('fluttergithubbot already has open tool PRs')); + expect(stdio.stdout, contains('flutter-pub-roller-bot already has open tool PRs')); expect(stdio.stdout, contains(r'[{number: 123}]')); }); @@ -239,13 +240,13 @@ void main() { 'pr', 'list', '--author', - 'fluttergithubbot', + 'flutter-pub-roller-bot', '--repo', 'flutter/flutter', '--state', 'open', - '--label', - 'tool', + '--search', + 'Roll pub packages', '--json', 'number', // Returns empty array, as there are no other open roll PRs from the bot @@ -335,13 +336,13 @@ void main() { 'pr', 'list', '--author', - 'fluttergithubbot', + 'flutter-pub-roller-bot', '--repo', 'flutter/flutter', '--state', 'open', - '--label', - 'tool', + '--search', + 'Roll pub packages', '--json', 'number', // Returns empty array, as there are no other open roll PRs from the bot @@ -427,7 +428,7 @@ void main() { 'commit', '--message', 'roll packages', - '--author="fluttergithubbot <fluttergithubbot@gmail.com>"', + '--author="flutter-pub-roller-bot <flutter-pub-roller-bot@google.com>"', ]), const FakeCommand(command: <String>[ 'git', @@ -475,12 +476,11 @@ void main() { group('command argument validations', () { const String tokenPath = '/path/to/token'; - const String mirrorRemote = 'https://githost.com/org/project'; test('validates that file exists at --token option', () async { await expectLater( () => run( - <String>['--token', tokenPath, '--mirror-remote', mirrorRemote], + <String>['--token', tokenPath], fs: fileSystem, processManager: processManager, ), @@ -499,7 +499,7 @@ void main() { ..writeAsStringSync(''); await expectLater( () => run( - <String>['--token', tokenPath, '--mirror-remote', mirrorRemote], + <String>['--token', tokenPath], fs: fileSystem, processManager: processManager, ), diff --git a/dev/customer_testing/pubspec.yaml b/dev/customer_testing/pubspec.yaml index e4825d2ff4aff..9f5cb6daa43af 100644 --- a/dev/customer_testing/pubspec.yaml +++ b/dev/customer_testing/pubspec.yaml @@ -5,9 +5,9 @@ environment: sdk: '>=3.0.0-0 <4.0.0' dependencies: - args: 2.4.1 + args: 2.4.2 path: 1.8.3 - glob: 2.1.1 + glob: 2.1.2 meta: 1.9.1 async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -18,10 +18,10 @@ dependencies: term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -31,8 +31,8 @@ dev_dependencies: http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -46,13 +46,13 @@ dev_dependencies: source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 8771 +# PUBSPEC CHECKSUM: df79 diff --git a/dev/devicelab/bin/tasks/flutter_engine_group_performance.dart b/dev/devicelab/bin/tasks/flutter_engine_group_performance.dart index 727228233af46..995b38918e66c 100644 --- a/dev/devicelab/bin/tasks/flutter_engine_group_performance.dart +++ b/dev/devicelab/bin/tasks/flutter_engine_group_performance.dart @@ -20,6 +20,13 @@ Future<void> _withApkInstall( final DeviceDiscovery devices = DeviceDiscovery(); final AndroidDevice device = await devices.workingDevice as AndroidDevice; await device.unlock(); + try { + // Force proper cleanup before trying to install app. If uninstall fails, + // we log exception and proceed with running the test. + await device.adb(<String>['uninstall', bundleName]); + } on Exception catch (error) { + print('adb uninstall failed with exception: $error. Will proceed with test run.'); + } await device.adb(<String>['install', '-r', apkPath]); try { await body(device); diff --git a/dev/devicelab/bin/tasks/flutter_gallery_ios__start_up_xcode_debug.dart b/dev/devicelab/bin/tasks/flutter_gallery_ios__start_up_xcode_debug.dart new file mode 100644 index 0000000000000..a17c45d2d8678 --- /dev/null +++ b/dev/devicelab/bin/tasks/flutter_gallery_ios__start_up_xcode_debug.dart @@ -0,0 +1,21 @@ +// 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_devicelab/framework/devices.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/perf_tests.dart'; + +Future<void> main() async { + // TODO(vashworth): Remove after Xcode 15 and iOS 17 are in CI (https://github.com/flutter/flutter/issues/132128) + // XcodeDebug workflow is used for CoreDevices (iOS 17+ and Xcode 15+). Use + // FORCE_XCODE_DEBUG environment variable to force the use of XcodeDebug + // workflow in CI to test from older versions since devicelab has not yet been + // updated to iOS 17 and Xcode 15. + deviceOperatingSystem = DeviceOperatingSystem.ios; + await task(createFlutterGalleryStartupTest( + runEnvironment: <String, String>{ + 'FORCE_XCODE_DEBUG': 'true', + }, + )); +} diff --git a/dev/devicelab/bin/tasks/integration_ui_ios_driver_xcode_debug.dart b/dev/devicelab/bin/tasks/integration_ui_ios_driver_xcode_debug.dart new file mode 100644 index 0000000000000..f51b231d2953f --- /dev/null +++ b/dev/devicelab/bin/tasks/integration_ui_ios_driver_xcode_debug.dart @@ -0,0 +1,21 @@ +// 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_devicelab/framework/devices.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/integration_tests.dart'; + +Future<void> main() async { + // TODO(vashworth): Remove after Xcode 15 and iOS 17 are in CI (https://github.com/flutter/flutter/issues/132128) + // XcodeDebug workflow is used for CoreDevices (iOS 17+ and Xcode 15+). Use + // FORCE_XCODE_DEBUG environment variable to force the use of XcodeDebug + // workflow in CI to test from older versions since devicelab has not yet been + // updated to iOS 17 and Xcode 15. + deviceOperatingSystem = DeviceOperatingSystem.ios; + await task(createEndToEndDriverTest( + environment: <String, String>{ + 'FORCE_XCODE_DEBUG': 'true', + }, + )); +} diff --git a/dev/devicelab/bin/tasks/microbenchmarks_ios_xcode_debug.dart b/dev/devicelab/bin/tasks/microbenchmarks_ios_xcode_debug.dart new file mode 100644 index 0000000000000..3373a683672e8 --- /dev/null +++ b/dev/devicelab/bin/tasks/microbenchmarks_ios_xcode_debug.dart @@ -0,0 +1,21 @@ +// 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_devicelab/framework/devices.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/microbenchmarks.dart'; + +/// Runs microbenchmarks on iOS. +Future<void> main() async { + // XcodeDebug workflow is used for CoreDevices (iOS 17+ and Xcode 15+). Use + // FORCE_XCODE_DEBUG environment variable to force the use of XcodeDebug + // workflow in CI to test from older versions since devicelab has not yet been + // updated to iOS 17 and Xcode 15. + deviceOperatingSystem = DeviceOperatingSystem.ios; + await task(createMicrobenchmarkTask( + environment: <String, String>{ + 'FORCE_XCODE_DEBUG': 'true', + }, + )); +} diff --git a/dev/devicelab/bin/tasks/platform_views_scroll_perf_impeller__timeline_summary.dart b/dev/devicelab/bin/tasks/platform_views_scroll_perf_impeller__timeline_summary.dart new file mode 100644 index 0000000000000..95bd8adff34f1 --- /dev/null +++ b/dev/devicelab/bin/tasks/platform_views_scroll_perf_impeller__timeline_summary.dart @@ -0,0 +1,12 @@ +// 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_devicelab/framework/devices.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/perf_tests.dart'; + +Future<void> main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(createAndroidTextureScrollPerfTest(enableImpeller: true)); +} diff --git a/dev/devicelab/bin/tasks/web_benchmarks_canvaskit.dart b/dev/devicelab/bin/tasks/web_benchmarks_canvaskit.dart index 313f7e27afd1a..1aabf51464d9f 100644 --- a/dev/devicelab/bin/tasks/web_benchmarks_canvaskit.dart +++ b/dev/devicelab/bin/tasks/web_benchmarks_canvaskit.dart @@ -8,6 +8,9 @@ import 'package:flutter_devicelab/tasks/web_benchmarks.dart'; /// Runs all Web benchmarks using the CanvasKit rendering backend. Future<void> main() async { await task(() async { - return runWebBenchmark(useCanvasKit: true); + return runWebBenchmark(( + webRenderer: 'canvaskit', + useWasm: false + )); }); } diff --git a/dev/devicelab/bin/tasks/web_benchmarks_html.dart b/dev/devicelab/bin/tasks/web_benchmarks_html.dart index 8cca0d6092906..b7719cc7ba90f 100644 --- a/dev/devicelab/bin/tasks/web_benchmarks_html.dart +++ b/dev/devicelab/bin/tasks/web_benchmarks_html.dart @@ -8,6 +8,9 @@ import 'package:flutter_devicelab/tasks/web_benchmarks.dart'; /// Runs all Web benchmarks using the HTML rendering backend. Future<void> main() async { await task(() async { - return runWebBenchmark(useCanvasKit: false); + return runWebBenchmark(( + webRenderer: 'html', + useWasm: false + )); }); } diff --git a/dev/devicelab/bin/tasks/web_benchmarks_skwasm.dart b/dev/devicelab/bin/tasks/web_benchmarks_skwasm.dart new file mode 100644 index 0000000000000..256634a50b960 --- /dev/null +++ b/dev/devicelab/bin/tasks/web_benchmarks_skwasm.dart @@ -0,0 +1,16 @@ +// 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_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/web_benchmarks.dart'; + +/// Runs all Web benchmarks using the Skwasm rendering backend. +Future<void> main() async { + await task(() async { + return runWebBenchmark(( + webRenderer: 'skwasm', + useWasm: true + )); + }); +} diff --git a/dev/devicelab/lib/framework/ab.dart b/dev/devicelab/lib/framework/ab.dart index 7988b019c5f37..e7da55855006e 100644 --- a/dev/devicelab/lib/framework/ab.dart +++ b/dev/devicelab/lib/framework/ab.dart @@ -49,7 +49,7 @@ class ABTest { static Map<String, List<double>> _convertFrom(dynamic results) { final Map<String, dynamic> resultMap = results as Map<String, dynamic>; return <String, List<double>> { - for (String key in resultMap.keys) + for (final String key in resultMap.keys) key: (resultMap[key] as List<dynamic>).cast<double>(), }; } diff --git a/dev/devicelab/lib/framework/browser.dart b/dev/devicelab/lib/framework/browser.dart index 3180bb758a35d..9d4581faec009 100644 --- a/dev/devicelab/lib/framework/browser.dart +++ b/dev/devicelab/lib/framework/browser.dart @@ -25,6 +25,7 @@ class ChromeOptions { this.windowHeight = 1024, this.headless, this.debugPort, + this.enableWasmGC = false, }); /// If not null passed as `--user-data-dir`. @@ -53,6 +54,9 @@ class ChromeOptions { /// mode without a debug port, Chrome quits immediately. For most tests it is /// typical to set [headless] to true and set a non-null debug port. final int? debugPort; + + /// Whether to enable experimental WasmGC flags + final bool enableWasmGC; } /// A function called when the Chrome process encounters an error. @@ -104,6 +108,8 @@ class Chrome { '--no-default-browser-check', '--disable-default-apps', '--disable-translate', + if (options.enableWasmGC) + '--js-flags=--experimental-wasm-gc', ]; final io.Process chromeProcess = await _spawnChromiumProcess( diff --git a/dev/devicelab/lib/framework/utils.dart b/dev/devicelab/lib/framework/utils.dart index ad057e3fd0e73..20301e51eebd0 100644 --- a/dev/devicelab/lib/framework/utils.dart +++ b/dev/devicelab/lib/framework/utils.dart @@ -471,6 +471,10 @@ List<String> _flutterCommandArgs(String command, List<String> options) { if (localEngineSrcPath != null) ...<String>['--local-engine-src-path', localEngineSrcPath], if (localWebSdk != null) ...<String>['--local-web-sdk', localWebSdk], ...options, + // Use CI flag when running devicelab tests, except for `packages`/`pub` commands. + // `packages`/`pub` commands effectively runs the `pub` tool, which does not have + // the same allowed args. + if (!command.startsWith('packages') && !command.startsWith('pub')) '--ci', ]; } diff --git a/dev/devicelab/lib/microbenchmarks.dart b/dev/devicelab/lib/microbenchmarks.dart index 0cf3d8192466e..451be27b1a550 100644 --- a/dev/devicelab/lib/microbenchmarks.dart +++ b/dev/devicelab/lib/microbenchmarks.dart @@ -64,6 +64,12 @@ Future<Map<String, double>> readJsonResults(Process process) { // See https://github.com/flutter/flutter/issues/19208 process.stdin.write('q'); await process.stdin.flush(); + + // Give the process a couple of seconds to exit and run shutdown hooks + // before sending kill signal. + // TODO(fujino): https://github.com/flutter/flutter/issues/134566 + await Future<void>.delayed(const Duration(seconds: 2)); + // Also send a kill signal in case the `q` above didn't work. process.kill(ProcessSignal.sigint); try { diff --git a/dev/devicelab/lib/tasks/integration_tests.dart b/dev/devicelab/lib/tasks/integration_tests.dart index 0172d576fe417..c06e717874d17 100644 --- a/dev/devicelab/lib/tasks/integration_tests.dart +++ b/dev/devicelab/lib/tasks/integration_tests.dart @@ -106,10 +106,11 @@ TaskFunction createEndToEndFrameNumberTest() { ).call; } -TaskFunction createEndToEndDriverTest() { +TaskFunction createEndToEndDriverTest({Map<String, String>? environment}) { return DriverTest( '${flutterDirectory.path}/dev/integration_tests/ui', 'lib/driver.dart', + environment: environment, ).call; } @@ -173,6 +174,7 @@ class DriverTest { this.testTarget, { this.extraOptions = const <String>[], this.deviceIdOverride, + this.environment, } ); @@ -180,6 +182,7 @@ class DriverTest { final String testTarget; final List<String> extraOptions; final String? deviceIdOverride; + final Map<String, String>? environment; Future<TaskResult> call() { return inDirectory<TaskResult>(testDirectory, () async { @@ -202,7 +205,7 @@ class DriverTest { deviceId, ...extraOptions, ]; - await flutter('drive', options: options); + await flutter('drive', options: options, environment: environment); return TaskResult.success(null); }); diff --git a/dev/devicelab/lib/tasks/microbenchmarks.dart b/dev/devicelab/lib/tasks/microbenchmarks.dart index 967bb58dfe607..6bd01aac1d2e8 100644 --- a/dev/devicelab/lib/tasks/microbenchmarks.dart +++ b/dev/devicelab/lib/tasks/microbenchmarks.dart @@ -15,7 +15,10 @@ import '../microbenchmarks.dart'; /// Creates a device lab task that runs benchmarks in /// `dev/benchmarks/microbenchmarks` reports results to the dashboard. -TaskFunction createMicrobenchmarkTask({bool? enableImpeller}) { +TaskFunction createMicrobenchmarkTask({ + bool? enableImpeller, + Map<String, String> environment = const <String, String>{}, +}) { return () async { final Device device = await devices.workingDevice; await device.unlock(); @@ -41,9 +44,9 @@ TaskFunction createMicrobenchmarkTask({bool? enableImpeller}) { return startFlutter( 'run', options: options, + environment: environment, ); }); - return readJsonResults(flutterProcess); } diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index 04c846673b30a..193e4e57fba3f 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -70,12 +70,14 @@ TaskFunction createUiKitViewScrollPerfNonIntersectingTest({bool? enableImpeller} ).run; } -TaskFunction createAndroidTextureScrollPerfTest() { +TaskFunction createAndroidTextureScrollPerfTest({bool? enableImpeller}) { return PerfTest( '${flutterDirectory.path}/dev/benchmarks/platform_views_layout', 'test_driver/android_view_scroll_perf.dart', 'platform_views_scroll_perf', testDriver: 'test_driver/scroll_perf_test.dart', + needsFullTimeline: false, + enableImpeller: enableImpeller, ).run; } @@ -230,10 +232,11 @@ TaskFunction createOpenPayScrollPerfTest({bool measureCpuGpu = true}) { ).run; } -TaskFunction createFlutterGalleryStartupTest({String target = 'lib/main.dart'}) { +TaskFunction createFlutterGalleryStartupTest({String target = 'lib/main.dart', Map<String, String>? runEnvironment}) { return StartupTest( '${flutterDirectory.path}/dev/integration_tests/flutter_gallery', target: target, + runEnvironment: runEnvironment, ).run; } @@ -691,11 +694,17 @@ Map<String, dynamic> _average(List<Map<String, dynamic>> results, int iterations /// Measure application startup performance. class StartupTest { - const StartupTest(this.testDirectory, { this.reportMetrics = true, this.target = 'lib/main.dart' }); + const StartupTest( + this.testDirectory, { + this.reportMetrics = true, + this.target = 'lib/main.dart', + this.runEnvironment, + }); final String testDirectory; final bool reportMetrics; final String target; + final Map<String, String>? runEnvironment; Future<TaskResult> run() async { return inDirectory<TaskResult>(testDirectory, () async { @@ -769,18 +778,23 @@ class StartupTest { const int maxFailures = 3; int currentFailures = 0; for (int i = 0; i < iterations; i += 1) { - final int result = await flutter('run', options: <String>[ - '--no-android-gradle-daemon', - '--no-publish-port', - '--verbose', - '--profile', - '--trace-startup', - '--target=$target', - '-d', - device.deviceId, - if (applicationBinaryPath != null) - '--use-application-binary=$applicationBinaryPath', - ], canFail: true); + final int result = await flutter( + 'run', + options: <String>[ + '--no-android-gradle-daemon', + '--no-publish-port', + '--verbose', + '--profile', + '--trace-startup', + '--target=$target', + '-d', + device.deviceId, + if (applicationBinaryPath != null) + '--use-application-binary=$applicationBinaryPath', + ], + environment: runEnvironment, + canFail: true, + ); if (result == 0) { final Map<String, dynamic> data = json.decode( file('${_testOutputDirectory(testDirectory)}/start_up_info.json').readAsStringSync(), diff --git a/dev/devicelab/lib/tasks/web_benchmarks.dart b/dev/devicelab/lib/tasks/web_benchmarks.dart index d3defb42fd755..051133c70db7b 100644 --- a/dev/devicelab/lib/tasks/web_benchmarks.dart +++ b/dev/devicelab/lib/tasks/web_benchmarks.dart @@ -20,7 +20,12 @@ import '../framework/utils.dart'; const int benchmarkServerPort = 9999; const int chromeDebugPort = 10000; -Future<TaskResult> runWebBenchmark({ required bool useCanvasKit }) async { +typedef WebBenchmarkOptions = ({ + String webRenderer, + bool useWasm, +}); + +Future<TaskResult> runWebBenchmark(WebBenchmarkOptions benchmarkOptions) async { // Reduce logging level. Otherwise, package:webkit_inspection_protocol is way too spammy. Logger.root.level = Level.INFO; final String macrobenchmarksDirectory = path.join(flutterDirectory.path, 'dev', 'benchmarks', 'macrobenchmarks'); @@ -28,8 +33,12 @@ Future<TaskResult> runWebBenchmark({ required bool useCanvasKit }) async { await flutter('clean'); await evalFlutter('build', options: <String>[ 'web', + if (benchmarkOptions.useWasm) ...<String>[ + '--wasm', + '--wasm-opt=debug', + ], '--dart-define=FLUTTER_WEB_ENABLE_PROFILING=true', - '--web-renderer=${useCanvasKit ? 'canvaskit' : 'html'}', + '--web-renderer=${benchmarkOptions.webRenderer}', '--profile', '--no-web-resources-cdn', '-t', @@ -114,8 +123,8 @@ Future<TaskResult> runWebBenchmark({ required bool useCanvasKit }) async { profileData.completeError(error, stackTrace); return Response.internalServerError(body: '$error'); } - }).add(createStaticHandler( - path.join(macrobenchmarksDirectory, 'build', 'web'), + }).add(createBuildDirectoryHandler( + path.join(macrobenchmarksDirectory, 'build', benchmarkOptions.useWasm ? 'web_wasm' : 'web'), )); server = await io.HttpServer.bind('localhost', benchmarkServerPort); @@ -137,6 +146,7 @@ Future<TaskResult> runWebBenchmark({ required bool useCanvasKit }) async { userDataDirectory: userDataDir, headless: isUncalibratedSmokeTest, debugPort: chromeDebugPort, + enableWasmGC: benchmarkOptions.useWasm, ); print('Launching Chrome.'); @@ -149,7 +159,6 @@ Future<TaskResult> runWebBenchmark({ required bool useCanvasKit }) async { ); print('Waiting for the benchmark to report benchmark profile.'); - final String backend = useCanvasKit ? 'canvaskit' : 'html'; final Map<String, dynamic> taskResult = <String, dynamic>{}; final List<String> benchmarkScoreKeys = <String>[]; final List<Map<String, dynamic>> profiles = await profileData.future; @@ -161,7 +170,7 @@ Future<TaskResult> runWebBenchmark({ required bool useCanvasKit }) async { throw 'Benchmark name is empty'; } - final String namespace = '$benchmarkName.$backend'; + final String namespace = '$benchmarkName.${benchmarkOptions.webRenderer}'; final List<String> scoreKeys = List<String>.from(profile['scoreKeys'] as List<dynamic>); if (scoreKeys.isEmpty) { throw 'No score keys in benchmark "$benchmarkName"'; @@ -188,3 +197,23 @@ Future<TaskResult> runWebBenchmark({ required bool useCanvasKit }) async { } }); } + +Handler createBuildDirectoryHandler(String buildDirectoryPath) { + final Handler childHandler = createStaticHandler(buildDirectoryPath); + return (Request request) async { + final Response response = await childHandler(request); + final String? mimeType = response.mimeType; + + // Provide COOP/COEP headers so that the browser loads the page as + // crossOriginIsolated. This will make sure that we get high-resolution + // timers for our benchmark measurements. + if (mimeType == 'text/html' || mimeType == 'text/javascript') { + return response.change(headers: <String, String>{ + 'Cross-Origin-Opener-Policy': 'same-origin', + 'Cross-Origin-Embedder-Policy': 'require-corp', + }); + } else { + return response; + } + }; +} diff --git a/dev/devicelab/pubspec.yaml b/dev/devicelab/pubspec.yaml index 43b6e70114791..a74f303ceb275 100644 --- a/dev/devicelab/pubspec.yaml +++ b/dev/devicelab/pubspec.yaml @@ -7,12 +7,12 @@ environment: dependencies: archive: 3.3.2 - args: 2.4.1 + args: 2.4.2 file: 6.1.4 http: 0.13.6 - logging: 1.1.1 + logging: 1.2.0 meta: 1.9.1 - metrics_center: 1.0.7 + metrics_center: 1.0.9 path: 1.8.3 platform: 3.1.0 process: 4.2.4 @@ -20,19 +20,20 @@ dependencies: shelf: 1.4.1 shelf_static: 1.1.2 stack_trace: 1.11.0 - vm_service: 11.6.0 + vm_service: 11.7.1 + web: 0.1.4-beta webkit_inspection_protocol: 1.2.0 - _discoveryapis_commons: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _discoveryapis_commons: 1.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" checked_yaml: 2.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" equatable: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - gcloud: 0.8.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + gcloud: 0.8.9 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" googleapis: 3.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - googleapis_auth: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + googleapis_auth: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" json_annotation: 4.8.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -46,17 +47,17 @@ dependencies: yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" pool: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -64,9 +65,9 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 022d +# PUBSPEC CHECKSUM: a090 diff --git a/dev/forbidden_from_release_tests/pubspec.yaml b/dev/forbidden_from_release_tests/pubspec.yaml index c8807ffba5973..38a767f90b6d1 100644 --- a/dev/forbidden_from_release_tests/pubspec.yaml +++ b/dev/forbidden_from_release_tests/pubspec.yaml @@ -5,15 +5,15 @@ environment: sdk: '>=2.19.0-0 <4.0.0' dependencies: - args: 2.4.1 + args: 2.4.2 file: 6.1.4 package_config: 2.1.0 path: 1.8.3 process: 4.2.4 - vm_snapshot_analysis: 0.7.2 + vm_snapshot_analysis: 0.7.6 collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" platform: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 5866 +# PUBSPEC CHECKSUM: 5e6b diff --git a/dev/integration_tests/abstract_method_smoke_test/android/app/src/main/AndroidManifest.xml b/dev/integration_tests/abstract_method_smoke_test/android/app/src/main/AndroidManifest.xml index a84f324464a1e..bd26b58491aaf 100644 --- a/dev/integration_tests/abstract_method_smoke_test/android/app/src/main/AndroidManifest.xml +++ b/dev/integration_tests/abstract_method_smoke_test/android/app/src/main/AndroidManifest.xml @@ -14,6 +14,7 @@ found in the LICENSE file. --> android:label="abstract_method_smoke_test"> <activity android:name=".MainActivity" + android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" diff --git a/dev/integration_tests/abstract_method_smoke_test/android/build.gradle b/dev/integration_tests/abstract_method_smoke_test/android/build.gradle index 57ace36b3c61b..a2a8866952986 100644 --- a/dev/integration_tests/abstract_method_smoke_test/android/build.gradle +++ b/dev/integration_tests/abstract_method_smoke_test/android/build.gradle @@ -7,14 +7,14 @@ // See dev/tools/bin/generate_gradle_lockfiles.dart. buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/dev/integration_tests/abstract_method_smoke_test/android/buildscript-gradle.lockfile b/dev/integration_tests/abstract_method_smoke_test/android/buildscript-gradle.lockfile index 2577f0aa37e26..eb605802d98f5 100644 --- a/dev/integration_tests/abstract_method_smoke_test/android/buildscript-gradle.lockfile +++ b/dev/integration_tests/abstract_method_smoke_test/android/buildscript-gradle.lockfile @@ -1,120 +1,152 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.databinding:databinding-common:4.1.3=classpath -androidx.databinding:databinding-compiler-common:4.1.3=classpath -com.android.databinding:baseLibrary:4.1.3=classpath -com.android.tools.analytics-library:crash:27.1.3=classpath -com.android.tools.analytics-library:protos:27.1.3=classpath -com.android.tools.analytics-library:shared:27.1.3=classpath -com.android.tools.analytics-library:tracker:27.1.3=classpath -com.android.tools.build.jetifier:jetifier-core:1.0.0-beta09=classpath -com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta09=classpath -com.android.tools.build:aapt2-proto:4.1.3-6503028=classpath -com.android.tools.build:aaptcompiler:4.1.3=classpath -com.android.tools.build:apksig:4.1.3=classpath -com.android.tools.build:apkzlib:4.1.3=classpath -com.android.tools.build:builder-model:4.1.3=classpath -com.android.tools.build:builder-test-api:4.1.3=classpath -com.android.tools.build:builder:4.1.3=classpath -com.android.tools.build:bundletool:0.14.0=classpath -com.android.tools.build:gradle-api:4.1.3=classpath -com.android.tools.build:gradle:4.1.3=classpath -com.android.tools.build:manifest-merger:27.1.3=classpath +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath -com.android.tools.ddms:ddmlib:27.1.3=classpath -com.android.tools.layoutlib:layoutlib-api:27.1.3=classpath -com.android.tools.lint:lint-gradle-api:27.1.3=classpath -com.android.tools.lint:lint-model:27.1.3=classpath -com.android.tools:annotations:27.1.3=classpath -com.android.tools:common:27.1.3=classpath -com.android.tools:dvlib:27.1.3=classpath -com.android.tools:repository:27.1.3=classpath -com.android.tools:sdk-common:27.1.3=classpath -com.android.tools:sdklib:27.1.3=classpath -com.android:signflinger:4.1.3=classpath -com.android:zipflinger:4.1.3=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath com.github.gundy:semver4j:0.16.4=classpath +com.google.android:annotations:4.1.1.4=classpath +com.google.api.grpc:proto-google-common-protos:2.0.1=classpath com.google.auto.value:auto-value-annotations:1.6.2=classpath com.google.code.findbugs:jsr305:3.0.2=classpath -com.google.code.gson:gson:2.8.6=classpath +com.google.code.gson:gson:2.8.9=classpath com.google.crypto.tink:tink:1.3.0-rc2=classpath -com.google.errorprone:error_prone_annotations:2.3.4=classpath +com.google.dagger:dagger:2.28.3=classpath +com.google.errorprone:error_prone_annotations:2.4.0=classpath com.google.flatbuffers:flatbuffers-java:1.12.0=classpath com.google.guava:failureaccess:1.0.1=classpath -com.google.guava:guava:29.0-jre=classpath +com.google.guava:guava:30.1-jre=classpath com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath com.google.j2objc:j2objc-annotations:1.3=classpath com.google.jimfs:jimfs:1.1=classpath -com.google.protobuf:protobuf-java-util:3.10.0=classpath -com.google.protobuf:protobuf-java:3.10.0=classpath -com.google.test.platform:core-proto:0.0.2-dev=classpath +com.google.protobuf:protobuf-java-util:3.17.2=classpath +com.google.protobuf:protobuf-java:3.17.2=classpath +com.google.testing.platform:core-proto:0.0.8-alpha07=classpath com.googlecode.json-simple:json-simple:1.1=classpath com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath com.squareup:javapoet:1.10.0=classpath com.squareup:javawriter:2.5.0=classpath com.sun.activation:javax.activation:1.2.0=classpath -com.sun.istack:istack-commons-runtime:3.0.7=classpath -com.sun.xml.fastinfoset:FastInfoset:1.2.15=classpath -commons-codec:commons-codec:1.10=classpath +com.sun.istack:istack-commons-runtime:3.0.8=classpath +com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath +commons-codec:commons-codec:1.11=classpath commons-io:commons-io:2.4=classpath commons-logging:commons-logging:1.2=classpath de.undercouch:gradle-download-task:4.1.1=classpath -it.unimi.dsi:fastutil:7.2.0=classpath -javax.activation:javax.activation-api:1.2.0=classpath +io.grpc:grpc-api:1.39.0=classpath +io.grpc:grpc-context:1.39.0=classpath +io.grpc:grpc-core:1.39.0=classpath +io.grpc:grpc-netty:1.39.0=classpath +io.grpc:grpc-protobuf-lite:1.39.0=classpath +io.grpc:grpc-protobuf:1.39.0=classpath +io.grpc:grpc-stub:1.39.0=classpath +io.netty:netty-buffer:4.1.52.Final=classpath +io.netty:netty-codec-http2:4.1.52.Final=classpath +io.netty:netty-codec-http:4.1.52.Final=classpath +io.netty:netty-codec-socks:4.1.52.Final=classpath +io.netty:netty-codec:4.1.52.Final=classpath +io.netty:netty-common:4.1.52.Final=classpath +io.netty:netty-handler-proxy:4.1.52.Final=classpath +io.netty:netty-handler:4.1.52.Final=classpath +io.netty:netty-resolver:4.1.52.Final=classpath +io.netty:netty-transport:4.1.52.Final=classpath +io.perfmark:perfmark-api:0.23.0=classpath +it.unimi.dsi:fastutil:8.4.0=classpath +jakarta.activation:jakarta.activation-api:1.2.1=classpath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath javax.inject:javax.inject:1=classpath -javax.xml.bind:jaxb-api:2.3.1=classpath +net.java.dev.jna:jna-platform:5.6.0=classpath +net.java.dev.jna:jna:5.6.0=classpath net.sf.jopt-simple:jopt-simple:4.9=classpath net.sf.kxml:kxml2:2.3.0=classpath -net.sf.proguard:proguard-base:6.0.3=classpath -net.sf.proguard:proguard-gradle:6.0.3=classpath -org.antlr:antlr4:4.5.3=classpath -org.apache.commons:commons-compress:1.12=classpath -org.apache.httpcomponents:httpclient:4.5.6=classpath -org.apache.httpcomponents:httpcore:4.4.10=classpath +org.apache.commons:commons-compress:1.20=classpath +org.apache.httpcomponents:httpclient:4.5.13=classpath +org.apache.httpcomponents:httpcore:4.4.13=classpath org.apache.httpcomponents:httpmime:4.5.6=classpath -org.bouncycastle:bcpkix-jdk15on:1.56=classpath -org.bouncycastle:bcprov-jdk15on:1.56=classpath -org.checkerframework:checker-qual:2.11.1=classpath -org.glassfish.jaxb:jaxb-runtime:2.3.1=classpath -org.glassfish.jaxb:txw2:2.3.1=classpath +org.bitbucket.b_c:jose4j:0.7.0=classpath +org.bouncycastle:bcpkix-jdk15on:1.67=classpath +org.bouncycastle:bcprov-jdk15on:1.67=classpath +org.checkerframework:checker-qual:3.5.0=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.19=classpath +org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath +org.glassfish.jaxb:txw2:2.3.2=classpath org.jdom:jdom2:2.0.6=classpath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=classpath -org.jetbrains.kotlin:kotlin-android-extensions:1.5.31=classpath -org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.5.31=classpath -org.jetbrains.kotlin:kotlin-build-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-runner:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-client:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31=classpath -org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-native-utils:1.5.31=classpath -org.jetbrains.kotlin:kotlin-project-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-reflect:1.3.72=classpath -org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=classpath -org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib:1.3.72=classpath -org.jetbrains.kotlin:kotlin-tooling-metadata:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-io:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-klib:1.5.31=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=classpath -org.jetbrains.trove4j:trove4j:20160824=classpath org.jetbrains:annotations:13.0=classpath org.json:json:20180813=classpath -org.jvnet.staxex:stax-ex:1.8=classpath -org.ow2.asm:asm-analysis:7.0=classpath -org.ow2.asm:asm-commons:7.0=classpath -org.ow2.asm:asm-tree:7.0=classpath -org.ow2.asm:asm-util:7.0=classpath -org.ow2.asm:asm:7.0=classpath -org.tensorflow:tensorflow-lite-metadata:0.1.0-rc1=classpath +org.jvnet.staxex:stax-ex:1.8.1=classpath +org.ow2.asm:asm-analysis:9.1=classpath +org.ow2.asm:asm-commons:9.1=classpath +org.ow2.asm:asm-tree:9.1=classpath +org.ow2.asm:asm-util:9.1=classpath +org.ow2.asm:asm:9.1=classpath +org.slf4j:slf4j-api:1.7.30=classpath +org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath +xerces:xercesImpl:2.12.0=classpath +xml-apis:xml-apis:1.4.01=classpath empty= diff --git a/dev/integration_tests/abstract_method_smoke_test/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/abstract_method_smoke_test/android/gradle/wrapper/gradle-wrapper.properties index 3c9d0852bfa52..cb24abda10ae7 100644 --- a/dev/integration_tests/abstract_method_smoke_test/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/abstract_method_smoke_test/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/dev/integration_tests/abstract_method_smoke_test/android/project-app.lockfile b/dev/integration_tests/abstract_method_smoke_test/android/project-app.lockfile index c573760e20686..5e0f607feb0aa 100644 --- a/dev/integration_tests/abstract_method_smoke_test/android/project-app.lockfile +++ b/dev/integration_tests/abstract_method_smoke_test/android/project-app.lockfile @@ -21,8 +21,8 @@ androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugApiDe androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window-java:1.0.0-beta03=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window:1.0.0-beta03=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.android.tools.analytics-library:protos:27.1.3=lintClassPath com.android.tools.analytics-library:shared:27.1.3=lintClassPath com.android.tools.analytics-library:tracker:27.1.3=lintClassPath @@ -74,6 +74,7 @@ it.unimi.dsi:fastutil:7.2.0=lintClassPath javax.activation:javax.activation-api:1.2.0=lintClassPath javax.inject:javax.inject:1=lintClassPath javax.xml.bind:jaxb-api:2.3.1=lintClassPath +net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath net.sf.jopt-simple:jopt-simple:4.9=lintClassPath net.sf.kxml:kxml2:2.3.0=lintClassPath org.apache.commons:commons-compress:1.12=lintClassPath @@ -87,22 +88,29 @@ org.codehaus.groovy:groovy-all:2.4.15=lintClassPath org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath org.glassfish.jaxb:txw2:2.3.1=lintClassPath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.5.31=kotlinKlibCommonizerClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.7.10=kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-reflect:1.5.31=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-script-runtime:1.5.31=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-reflect:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-script-runtime:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugApiDependenciesMetadata,profileApiDependenciesMetadata,releaseApiDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30=debugApiDependenciesMetadata,profileApiDependenciesMetadata,releaseApiDependenciesMetadata -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugApiDependenciesMetadata,profileApiDependenciesMetadata,releaseApiDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:atomicfu:0.16.3=debugApiDependenciesMetadata,debugImplementationDependenciesMetadata,profileApiDependenciesMetadata,profileImplementationDependenciesMetadata,releaseApiDependenciesMetadata,releaseImplementationDependenciesMetadata org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -110,8 +118,12 @@ org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,lintClassPath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -empty=androidApis,androidTestApiDependenciesMetadata,androidTestCompileOnlyDependenciesMetadata,androidTestDebugApiDependenciesMetadata,androidTestDebugCompileOnlyDependenciesMetadata,androidTestDebugImplementationDependenciesMetadata,androidTestDebugIntransitiveDependenciesMetadata,androidTestDebugRuntimeOnlyDependenciesMetadata,androidTestImplementationDependenciesMetadata,androidTestIntransitiveDependenciesMetadata,androidTestProfileApiDependenciesMetadata,androidTestProfileCompileOnlyDependenciesMetadata,androidTestProfileImplementationDependenciesMetadata,androidTestProfileIntransitiveDependenciesMetadata,androidTestProfileRuntimeOnlyDependenciesMetadata,androidTestReleaseApiDependenciesMetadata,androidTestReleaseCompileOnlyDependenciesMetadata,androidTestReleaseImplementationDependenciesMetadata,androidTestReleaseIntransitiveDependenciesMetadata,androidTestReleaseRuntimeOnlyDependenciesMetadata,androidTestRuntimeOnlyDependenciesMetadata,androidTestUtil,apiDependenciesMetadata,compile,compileOnlyDependenciesMetadata,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestApiDependenciesMetadata,debugAndroidTestCompileOnlyDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestIntransitiveDependenciesMetadata,debugAndroidTestRuntimeClasspath,debugAndroidTestRuntimeOnlyDependenciesMetadata,debugAnnotationProcessorClasspath,debugCompile,debugCompileOnly,debugCompileOnlyDependenciesMetadata,debugIntransitiveDependenciesMetadata,debugReverseMetadataValues,debugRuntimeOnlyDependenciesMetadata,debugUnitTestAnnotationProcessorClasspath,debugUnitTestApiDependenciesMetadata,debugUnitTestCompileOnlyDependenciesMetadata,debugUnitTestImplementationDependenciesMetadata,debugUnitTestIntransitiveDependenciesMetadata,debugUnitTestRuntimeOnlyDependenciesMetadata,debugWearBundling,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathProfile,kotlinCompilerPluginClasspathProfileUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseUnitTest,kotlinNativeCompilerPluginClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileCompile,profileCompileOnly,profileCompileOnlyDependenciesMetadata,profileIntransitiveDependenciesMetadata,profileReverseMetadataValues,profileRuntimeOnlyDependenciesMetadata,profileUnitTestAnnotationProcessorClasspath,profileUnitTestApiDependenciesMetadata,profileUnitTestCompileOnlyDependenciesMetadata,profileUnitTestImplementationDependenciesMetadata,profileUnitTestIntransitiveDependenciesMetadata,profileUnitTestRuntimeOnlyDependenciesMetadata,profileWearBundling,releaseAnnotationProcessorClasspath,releaseCompile,releaseCompileOnly,releaseCompileOnlyDependenciesMetadata,releaseIntransitiveDependenciesMetadata,releaseReverseMetadataValues,releaseRuntimeOnlyDependenciesMetadata,releaseUnitTestAnnotationProcessorClasspath,releaseUnitTestApiDependenciesMetadata,releaseUnitTestCompileOnlyDependenciesMetadata,releaseUnitTestImplementationDependenciesMetadata,releaseUnitTestIntransitiveDependenciesMetadata,releaseUnitTestRuntimeOnlyDependenciesMetadata,releaseWearBundling,runtimeOnlyDependenciesMetadata,testApiDependenciesMetadata,testCompile,testCompileOnlyDependenciesMetadata,testDebugApiDependenciesMetadata,testDebugCompileOnlyDependenciesMetadata,testDebugImplementationDependenciesMetadata,testDebugIntransitiveDependenciesMetadata,testDebugRuntimeOnlyDependenciesMetadata,testImplementationDependenciesMetadata,testIntransitiveDependenciesMetadata,testProfileApiDependenciesMetadata,testProfileCompileOnlyDependenciesMetadata,testProfileImplementationDependenciesMetadata,testProfileIntransitiveDependenciesMetadata,testProfileRuntimeOnlyDependenciesMetadata,testReleaseApiDependenciesMetadata,testReleaseCompileOnlyDependenciesMetadata,testReleaseImplementationDependenciesMetadata,testReleaseIntransitiveDependenciesMetadata,testReleaseRuntimeOnlyDependenciesMetadata,testRuntimeOnlyDependenciesMetadata +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestApiDependenciesMetadata,androidTestCompileOnlyDependenciesMetadata,androidTestDebugApiDependenciesMetadata,androidTestDebugCompileOnlyDependenciesMetadata,androidTestDebugImplementationDependenciesMetadata,androidTestDebugIntransitiveDependenciesMetadata,androidTestDebugRuntimeOnlyDependenciesMetadata,androidTestImplementationDependenciesMetadata,androidTestIntransitiveDependenciesMetadata,androidTestProfileApiDependenciesMetadata,androidTestProfileCompileOnlyDependenciesMetadata,androidTestProfileImplementationDependenciesMetadata,androidTestProfileIntransitiveDependenciesMetadata,androidTestProfileRuntimeOnlyDependenciesMetadata,androidTestReleaseApiDependenciesMetadata,androidTestReleaseCompileOnlyDependenciesMetadata,androidTestReleaseImplementationDependenciesMetadata,androidTestReleaseIntransitiveDependenciesMetadata,androidTestReleaseRuntimeOnlyDependenciesMetadata,androidTestRuntimeOnlyDependenciesMetadata,androidTestUtil,apiDependenciesMetadata,compile,compileOnlyDependenciesMetadata,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestApiDependenciesMetadata,debugAndroidTestCompileOnlyDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestIntransitiveDependenciesMetadata,debugAndroidTestRuntimeClasspath,debugAndroidTestRuntimeOnlyDependenciesMetadata,debugAnnotationProcessorClasspath,debugCompile,debugCompileOnly,debugCompileOnlyDependenciesMetadata,debugIntransitiveDependenciesMetadata,debugReverseMetadataValues,debugRuntimeOnlyDependenciesMetadata,debugUnitTestAnnotationProcessorClasspath,debugUnitTestApiDependenciesMetadata,debugUnitTestCompileOnlyDependenciesMetadata,debugUnitTestImplementationDependenciesMetadata,debugUnitTestIntransitiveDependenciesMetadata,debugUnitTestRuntimeOnlyDependenciesMetadata,debugWearBundling,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathProfile,kotlinCompilerPluginClasspathProfileUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseUnitTest,kotlinNativeCompilerPluginClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileCompile,profileCompileOnly,profileCompileOnlyDependenciesMetadata,profileIntransitiveDependenciesMetadata,profileReverseMetadataValues,profileRuntimeOnlyDependenciesMetadata,profileUnitTestAnnotationProcessorClasspath,profileUnitTestApiDependenciesMetadata,profileUnitTestCompileOnlyDependenciesMetadata,profileUnitTestImplementationDependenciesMetadata,profileUnitTestIntransitiveDependenciesMetadata,profileUnitTestRuntimeOnlyDependenciesMetadata,profileWearBundling,releaseAnnotationProcessorClasspath,releaseCompile,releaseCompileOnly,releaseCompileOnlyDependenciesMetadata,releaseIntransitiveDependenciesMetadata,releaseReverseMetadataValues,releaseRuntimeOnlyDependenciesMetadata,releaseUnitTestAnnotationProcessorClasspath,releaseUnitTestApiDependenciesMetadata,releaseUnitTestCompileOnlyDependenciesMetadata,releaseUnitTestImplementationDependenciesMetadata,releaseUnitTestIntransitiveDependenciesMetadata,releaseUnitTestRuntimeOnlyDependenciesMetadata,releaseWearBundling,runtimeOnlyDependenciesMetadata,testApiDependenciesMetadata,testCompile,testCompileOnlyDependenciesMetadata,testDebugApiDependenciesMetadata,testDebugCompileOnlyDependenciesMetadata,testDebugImplementationDependenciesMetadata,testDebugIntransitiveDependenciesMetadata,testDebugRuntimeOnlyDependenciesMetadata,testFixturesApiDependenciesMetadata,testFixturesCompileOnlyDependenciesMetadata,testFixturesDebugApiDependenciesMetadata,testFixturesDebugCompileOnlyDependenciesMetadata,testFixturesDebugImplementationDependenciesMetadata,testFixturesDebugIntransitiveDependenciesMetadata,testFixturesDebugRuntimeOnlyDependenciesMetadata,testFixturesImplementationDependenciesMetadata,testFixturesIntransitiveDependenciesMetadata,testFixturesProfileApiDependenciesMetadata,testFixturesProfileCompileOnlyDependenciesMetadata,testFixturesProfileImplementationDependenciesMetadata,testFixturesProfileIntransitiveDependenciesMetadata,testFixturesProfileRuntimeOnlyDependenciesMetadata,testFixturesReleaseApiDependenciesMetadata,testFixturesReleaseCompileOnlyDependenciesMetadata,testFixturesReleaseImplementationDependenciesMetadata,testFixturesReleaseIntransitiveDependenciesMetadata,testFixturesReleaseRuntimeOnlyDependenciesMetadata,testFixturesRuntimeOnlyDependenciesMetadata,testImplementationDependenciesMetadata,testIntransitiveDependenciesMetadata,testProfileApiDependenciesMetadata,testProfileCompileOnlyDependenciesMetadata,testProfileImplementationDependenciesMetadata,testProfileIntransitiveDependenciesMetadata,testProfileRuntimeOnlyDependenciesMetadata,testReleaseApiDependenciesMetadata,testReleaseCompileOnlyDependenciesMetadata,testReleaseImplementationDependenciesMetadata,testReleaseIntransitiveDependenciesMetadata,testReleaseRuntimeOnlyDependenciesMetadata,testRuntimeOnlyDependenciesMetadata diff --git a/dev/integration_tests/abstract_method_smoke_test/android/settings.gradle b/dev/integration_tests/abstract_method_smoke_test/android/settings.gradle index 03ae72d135c01..a020595962d63 100644 --- a/dev/integration_tests/abstract_method_smoke_test/android/settings.gradle +++ b/dev/integration_tests/abstract_method_smoke_test/android/settings.gradle @@ -8,8 +8,6 @@ include ':app' -enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') - def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/dev/integration_tests/abstract_method_smoke_test/pubspec.yaml b/dev/integration_tests/abstract_method_smoke_test/pubspec.yaml index 938786907a4cc..737b9e663c40c 100644 --- a/dev/integration_tests/abstract_method_smoke_test/pubspec.yaml +++ b/dev/integration_tests/abstract_method_smoke_test/pubspec.yaml @@ -12,12 +12,12 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 3e9c +# PUBSPEC CHECKSUM: a9c0 diff --git a/dev/integration_tests/android_custom_host_app/SampleApp/build.gradle b/dev/integration_tests/android_custom_host_app/SampleApp/build.gradle index 8e7852677f432..9d8b6ebcd18ca 100644 --- a/dev/integration_tests/android_custom_host_app/SampleApp/build.gradle +++ b/dev/integration_tests/android_custom_host_app/SampleApp/build.gradle @@ -5,7 +5,7 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 31 + compileSdkVersion 33 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/dev/integration_tests/android_embedding_v2_smoke_test/android/app/src/main/AndroidManifest.xml b/dev/integration_tests/android_embedding_v2_smoke_test/android/app/src/main/AndroidManifest.xml index db02bf97a7d1f..eec715be774ad 100644 --- a/dev/integration_tests/android_embedding_v2_smoke_test/android/app/src/main/AndroidManifest.xml +++ b/dev/integration_tests/android_embedding_v2_smoke_test/android/app/src/main/AndroidManifest.xml @@ -14,6 +14,7 @@ found in the LICENSE file. --> android:label="android_embedding_v2_smoke_test"> <activity android:name=".MainActivity" + android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" diff --git a/dev/integration_tests/android_embedding_v2_smoke_test/android/build.gradle b/dev/integration_tests/android_embedding_v2_smoke_test/android/build.gradle index 57ace36b3c61b..a2a8866952986 100644 --- a/dev/integration_tests/android_embedding_v2_smoke_test/android/build.gradle +++ b/dev/integration_tests/android_embedding_v2_smoke_test/android/build.gradle @@ -7,14 +7,14 @@ // See dev/tools/bin/generate_gradle_lockfiles.dart. buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/dev/integration_tests/android_embedding_v2_smoke_test/android/buildscript-gradle.lockfile b/dev/integration_tests/android_embedding_v2_smoke_test/android/buildscript-gradle.lockfile index 2577f0aa37e26..eb605802d98f5 100644 --- a/dev/integration_tests/android_embedding_v2_smoke_test/android/buildscript-gradle.lockfile +++ b/dev/integration_tests/android_embedding_v2_smoke_test/android/buildscript-gradle.lockfile @@ -1,120 +1,152 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.databinding:databinding-common:4.1.3=classpath -androidx.databinding:databinding-compiler-common:4.1.3=classpath -com.android.databinding:baseLibrary:4.1.3=classpath -com.android.tools.analytics-library:crash:27.1.3=classpath -com.android.tools.analytics-library:protos:27.1.3=classpath -com.android.tools.analytics-library:shared:27.1.3=classpath -com.android.tools.analytics-library:tracker:27.1.3=classpath -com.android.tools.build.jetifier:jetifier-core:1.0.0-beta09=classpath -com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta09=classpath -com.android.tools.build:aapt2-proto:4.1.3-6503028=classpath -com.android.tools.build:aaptcompiler:4.1.3=classpath -com.android.tools.build:apksig:4.1.3=classpath -com.android.tools.build:apkzlib:4.1.3=classpath -com.android.tools.build:builder-model:4.1.3=classpath -com.android.tools.build:builder-test-api:4.1.3=classpath -com.android.tools.build:builder:4.1.3=classpath -com.android.tools.build:bundletool:0.14.0=classpath -com.android.tools.build:gradle-api:4.1.3=classpath -com.android.tools.build:gradle:4.1.3=classpath -com.android.tools.build:manifest-merger:27.1.3=classpath +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath -com.android.tools.ddms:ddmlib:27.1.3=classpath -com.android.tools.layoutlib:layoutlib-api:27.1.3=classpath -com.android.tools.lint:lint-gradle-api:27.1.3=classpath -com.android.tools.lint:lint-model:27.1.3=classpath -com.android.tools:annotations:27.1.3=classpath -com.android.tools:common:27.1.3=classpath -com.android.tools:dvlib:27.1.3=classpath -com.android.tools:repository:27.1.3=classpath -com.android.tools:sdk-common:27.1.3=classpath -com.android.tools:sdklib:27.1.3=classpath -com.android:signflinger:4.1.3=classpath -com.android:zipflinger:4.1.3=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath com.github.gundy:semver4j:0.16.4=classpath +com.google.android:annotations:4.1.1.4=classpath +com.google.api.grpc:proto-google-common-protos:2.0.1=classpath com.google.auto.value:auto-value-annotations:1.6.2=classpath com.google.code.findbugs:jsr305:3.0.2=classpath -com.google.code.gson:gson:2.8.6=classpath +com.google.code.gson:gson:2.8.9=classpath com.google.crypto.tink:tink:1.3.0-rc2=classpath -com.google.errorprone:error_prone_annotations:2.3.4=classpath +com.google.dagger:dagger:2.28.3=classpath +com.google.errorprone:error_prone_annotations:2.4.0=classpath com.google.flatbuffers:flatbuffers-java:1.12.0=classpath com.google.guava:failureaccess:1.0.1=classpath -com.google.guava:guava:29.0-jre=classpath +com.google.guava:guava:30.1-jre=classpath com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath com.google.j2objc:j2objc-annotations:1.3=classpath com.google.jimfs:jimfs:1.1=classpath -com.google.protobuf:protobuf-java-util:3.10.0=classpath -com.google.protobuf:protobuf-java:3.10.0=classpath -com.google.test.platform:core-proto:0.0.2-dev=classpath +com.google.protobuf:protobuf-java-util:3.17.2=classpath +com.google.protobuf:protobuf-java:3.17.2=classpath +com.google.testing.platform:core-proto:0.0.8-alpha07=classpath com.googlecode.json-simple:json-simple:1.1=classpath com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath com.squareup:javapoet:1.10.0=classpath com.squareup:javawriter:2.5.0=classpath com.sun.activation:javax.activation:1.2.0=classpath -com.sun.istack:istack-commons-runtime:3.0.7=classpath -com.sun.xml.fastinfoset:FastInfoset:1.2.15=classpath -commons-codec:commons-codec:1.10=classpath +com.sun.istack:istack-commons-runtime:3.0.8=classpath +com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath +commons-codec:commons-codec:1.11=classpath commons-io:commons-io:2.4=classpath commons-logging:commons-logging:1.2=classpath de.undercouch:gradle-download-task:4.1.1=classpath -it.unimi.dsi:fastutil:7.2.0=classpath -javax.activation:javax.activation-api:1.2.0=classpath +io.grpc:grpc-api:1.39.0=classpath +io.grpc:grpc-context:1.39.0=classpath +io.grpc:grpc-core:1.39.0=classpath +io.grpc:grpc-netty:1.39.0=classpath +io.grpc:grpc-protobuf-lite:1.39.0=classpath +io.grpc:grpc-protobuf:1.39.0=classpath +io.grpc:grpc-stub:1.39.0=classpath +io.netty:netty-buffer:4.1.52.Final=classpath +io.netty:netty-codec-http2:4.1.52.Final=classpath +io.netty:netty-codec-http:4.1.52.Final=classpath +io.netty:netty-codec-socks:4.1.52.Final=classpath +io.netty:netty-codec:4.1.52.Final=classpath +io.netty:netty-common:4.1.52.Final=classpath +io.netty:netty-handler-proxy:4.1.52.Final=classpath +io.netty:netty-handler:4.1.52.Final=classpath +io.netty:netty-resolver:4.1.52.Final=classpath +io.netty:netty-transport:4.1.52.Final=classpath +io.perfmark:perfmark-api:0.23.0=classpath +it.unimi.dsi:fastutil:8.4.0=classpath +jakarta.activation:jakarta.activation-api:1.2.1=classpath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath javax.inject:javax.inject:1=classpath -javax.xml.bind:jaxb-api:2.3.1=classpath +net.java.dev.jna:jna-platform:5.6.0=classpath +net.java.dev.jna:jna:5.6.0=classpath net.sf.jopt-simple:jopt-simple:4.9=classpath net.sf.kxml:kxml2:2.3.0=classpath -net.sf.proguard:proguard-base:6.0.3=classpath -net.sf.proguard:proguard-gradle:6.0.3=classpath -org.antlr:antlr4:4.5.3=classpath -org.apache.commons:commons-compress:1.12=classpath -org.apache.httpcomponents:httpclient:4.5.6=classpath -org.apache.httpcomponents:httpcore:4.4.10=classpath +org.apache.commons:commons-compress:1.20=classpath +org.apache.httpcomponents:httpclient:4.5.13=classpath +org.apache.httpcomponents:httpcore:4.4.13=classpath org.apache.httpcomponents:httpmime:4.5.6=classpath -org.bouncycastle:bcpkix-jdk15on:1.56=classpath -org.bouncycastle:bcprov-jdk15on:1.56=classpath -org.checkerframework:checker-qual:2.11.1=classpath -org.glassfish.jaxb:jaxb-runtime:2.3.1=classpath -org.glassfish.jaxb:txw2:2.3.1=classpath +org.bitbucket.b_c:jose4j:0.7.0=classpath +org.bouncycastle:bcpkix-jdk15on:1.67=classpath +org.bouncycastle:bcprov-jdk15on:1.67=classpath +org.checkerframework:checker-qual:3.5.0=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.19=classpath +org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath +org.glassfish.jaxb:txw2:2.3.2=classpath org.jdom:jdom2:2.0.6=classpath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=classpath -org.jetbrains.kotlin:kotlin-android-extensions:1.5.31=classpath -org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.5.31=classpath -org.jetbrains.kotlin:kotlin-build-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-runner:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-client:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31=classpath -org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-native-utils:1.5.31=classpath -org.jetbrains.kotlin:kotlin-project-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-reflect:1.3.72=classpath -org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=classpath -org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib:1.3.72=classpath -org.jetbrains.kotlin:kotlin-tooling-metadata:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-io:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-klib:1.5.31=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=classpath -org.jetbrains.trove4j:trove4j:20160824=classpath org.jetbrains:annotations:13.0=classpath org.json:json:20180813=classpath -org.jvnet.staxex:stax-ex:1.8=classpath -org.ow2.asm:asm-analysis:7.0=classpath -org.ow2.asm:asm-commons:7.0=classpath -org.ow2.asm:asm-tree:7.0=classpath -org.ow2.asm:asm-util:7.0=classpath -org.ow2.asm:asm:7.0=classpath -org.tensorflow:tensorflow-lite-metadata:0.1.0-rc1=classpath +org.jvnet.staxex:stax-ex:1.8.1=classpath +org.ow2.asm:asm-analysis:9.1=classpath +org.ow2.asm:asm-commons:9.1=classpath +org.ow2.asm:asm-tree:9.1=classpath +org.ow2.asm:asm-util:9.1=classpath +org.ow2.asm:asm:9.1=classpath +org.slf4j:slf4j-api:1.7.30=classpath +org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath +xerces:xercesImpl:2.12.0=classpath +xml-apis:xml-apis:1.4.01=classpath empty= diff --git a/dev/integration_tests/android_embedding_v2_smoke_test/android/project-app.lockfile b/dev/integration_tests/android_embedding_v2_smoke_test/android/project-app.lockfile index 178876ecc76a3..e0e5b2b9b5951 100644 --- a/dev/integration_tests/android_embedding_v2_smoke_test/android/project-app.lockfile +++ b/dev/integration_tests/android_embedding_v2_smoke_test/android/project-app.lockfile @@ -21,8 +21,8 @@ androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugCompi androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window-java:1.0.0-beta03=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window:1.0.0-beta03=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.android.tools.analytics-library:protos:27.1.3=lintClassPath com.android.tools.analytics-library:shared:27.1.3=lintClassPath com.android.tools.analytics-library:tracker:27.1.3=lintClassPath @@ -74,6 +74,7 @@ it.unimi.dsi:fastutil:7.2.0=lintClassPath javax.activation:javax.activation-api:1.2.0=lintClassPath javax.inject:javax.inject:1=lintClassPath javax.xml.bind:jaxb-api:2.3.1=lintClassPath +net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath net.sf.jopt-simple:jopt-simple:4.9=lintClassPath net.sf.kxml:kxml2:2.3.0=lintClassPath org.apache.commons:commons-compress:1.12=lintClassPath @@ -87,21 +88,25 @@ org.codehaus.groovy:groovy-all:2.4.15=lintClassPath org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath org.glassfish.jaxb:txw2:2.3.1=lintClassPath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.5.31=kotlinKlibCommonizerClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.7.10=kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-reflect:1.5.31=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-script-runtime:1.5.31=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-reflect:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-script-runtime:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -109,8 +114,12 @@ org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -empty=androidApis,androidTestApiDependenciesMetadata,androidTestCompileOnlyDependenciesMetadata,androidTestDebugApiDependenciesMetadata,androidTestDebugCompileOnlyDependenciesMetadata,androidTestDebugImplementationDependenciesMetadata,androidTestDebugIntransitiveDependenciesMetadata,androidTestDebugRuntimeOnlyDependenciesMetadata,androidTestImplementationDependenciesMetadata,androidTestIntransitiveDependenciesMetadata,androidTestProfileApiDependenciesMetadata,androidTestProfileCompileOnlyDependenciesMetadata,androidTestProfileImplementationDependenciesMetadata,androidTestProfileIntransitiveDependenciesMetadata,androidTestProfileRuntimeOnlyDependenciesMetadata,androidTestReleaseApiDependenciesMetadata,androidTestReleaseCompileOnlyDependenciesMetadata,androidTestReleaseImplementationDependenciesMetadata,androidTestReleaseIntransitiveDependenciesMetadata,androidTestReleaseRuntimeOnlyDependenciesMetadata,androidTestRuntimeOnlyDependenciesMetadata,androidTestUtil,compile,compileOnlyDependenciesMetadata,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestApiDependenciesMetadata,debugAndroidTestCompileOnlyDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestIntransitiveDependenciesMetadata,debugAndroidTestRuntimeClasspath,debugAndroidTestRuntimeOnlyDependenciesMetadata,debugAnnotationProcessorClasspath,debugCompile,debugCompileOnly,debugCompileOnlyDependenciesMetadata,debugIntransitiveDependenciesMetadata,debugReverseMetadataValues,debugRuntimeOnlyDependenciesMetadata,debugUnitTestAnnotationProcessorClasspath,debugUnitTestApiDependenciesMetadata,debugUnitTestCompileOnlyDependenciesMetadata,debugUnitTestImplementationDependenciesMetadata,debugUnitTestIntransitiveDependenciesMetadata,debugUnitTestRuntimeOnlyDependenciesMetadata,debugWearBundling,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathProfile,kotlinCompilerPluginClasspathProfileUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseUnitTest,kotlinNativeCompilerPluginClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileCompile,profileCompileOnly,profileCompileOnlyDependenciesMetadata,profileIntransitiveDependenciesMetadata,profileReverseMetadataValues,profileRuntimeOnlyDependenciesMetadata,profileUnitTestAnnotationProcessorClasspath,profileUnitTestApiDependenciesMetadata,profileUnitTestCompileOnlyDependenciesMetadata,profileUnitTestImplementationDependenciesMetadata,profileUnitTestIntransitiveDependenciesMetadata,profileUnitTestRuntimeOnlyDependenciesMetadata,profileWearBundling,releaseAnnotationProcessorClasspath,releaseCompile,releaseCompileOnly,releaseCompileOnlyDependenciesMetadata,releaseIntransitiveDependenciesMetadata,releaseReverseMetadataValues,releaseRuntimeOnlyDependenciesMetadata,releaseUnitTestAnnotationProcessorClasspath,releaseUnitTestApiDependenciesMetadata,releaseUnitTestCompileOnlyDependenciesMetadata,releaseUnitTestImplementationDependenciesMetadata,releaseUnitTestIntransitiveDependenciesMetadata,releaseUnitTestRuntimeOnlyDependenciesMetadata,releaseWearBundling,runtimeOnlyDependenciesMetadata,testApiDependenciesMetadata,testCompile,testCompileOnlyDependenciesMetadata,testDebugApiDependenciesMetadata,testDebugCompileOnlyDependenciesMetadata,testDebugImplementationDependenciesMetadata,testDebugIntransitiveDependenciesMetadata,testDebugRuntimeOnlyDependenciesMetadata,testImplementationDependenciesMetadata,testIntransitiveDependenciesMetadata,testProfileApiDependenciesMetadata,testProfileCompileOnlyDependenciesMetadata,testProfileImplementationDependenciesMetadata,testProfileIntransitiveDependenciesMetadata,testProfileRuntimeOnlyDependenciesMetadata,testReleaseApiDependenciesMetadata,testReleaseCompileOnlyDependenciesMetadata,testReleaseImplementationDependenciesMetadata,testReleaseIntransitiveDependenciesMetadata,testReleaseRuntimeOnlyDependenciesMetadata,testRuntimeOnlyDependenciesMetadata +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestApiDependenciesMetadata,androidTestCompileOnlyDependenciesMetadata,androidTestDebugApiDependenciesMetadata,androidTestDebugCompileOnlyDependenciesMetadata,androidTestDebugImplementationDependenciesMetadata,androidTestDebugIntransitiveDependenciesMetadata,androidTestDebugRuntimeOnlyDependenciesMetadata,androidTestImplementationDependenciesMetadata,androidTestIntransitiveDependenciesMetadata,androidTestProfileApiDependenciesMetadata,androidTestProfileCompileOnlyDependenciesMetadata,androidTestProfileImplementationDependenciesMetadata,androidTestProfileIntransitiveDependenciesMetadata,androidTestProfileRuntimeOnlyDependenciesMetadata,androidTestReleaseApiDependenciesMetadata,androidTestReleaseCompileOnlyDependenciesMetadata,androidTestReleaseImplementationDependenciesMetadata,androidTestReleaseIntransitiveDependenciesMetadata,androidTestReleaseRuntimeOnlyDependenciesMetadata,androidTestRuntimeOnlyDependenciesMetadata,androidTestUtil,compile,compileOnlyDependenciesMetadata,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestApiDependenciesMetadata,debugAndroidTestCompileOnlyDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestIntransitiveDependenciesMetadata,debugAndroidTestRuntimeClasspath,debugAndroidTestRuntimeOnlyDependenciesMetadata,debugAnnotationProcessorClasspath,debugCompile,debugCompileOnly,debugCompileOnlyDependenciesMetadata,debugIntransitiveDependenciesMetadata,debugReverseMetadataValues,debugRuntimeOnlyDependenciesMetadata,debugUnitTestAnnotationProcessorClasspath,debugUnitTestApiDependenciesMetadata,debugUnitTestCompileOnlyDependenciesMetadata,debugUnitTestImplementationDependenciesMetadata,debugUnitTestIntransitiveDependenciesMetadata,debugUnitTestRuntimeOnlyDependenciesMetadata,debugWearBundling,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathProfile,kotlinCompilerPluginClasspathProfileUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseUnitTest,kotlinNativeCompilerPluginClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileCompile,profileCompileOnly,profileCompileOnlyDependenciesMetadata,profileIntransitiveDependenciesMetadata,profileReverseMetadataValues,profileRuntimeOnlyDependenciesMetadata,profileUnitTestAnnotationProcessorClasspath,profileUnitTestApiDependenciesMetadata,profileUnitTestCompileOnlyDependenciesMetadata,profileUnitTestImplementationDependenciesMetadata,profileUnitTestIntransitiveDependenciesMetadata,profileUnitTestRuntimeOnlyDependenciesMetadata,profileWearBundling,releaseAnnotationProcessorClasspath,releaseCompile,releaseCompileOnly,releaseCompileOnlyDependenciesMetadata,releaseIntransitiveDependenciesMetadata,releaseReverseMetadataValues,releaseRuntimeOnlyDependenciesMetadata,releaseUnitTestAnnotationProcessorClasspath,releaseUnitTestApiDependenciesMetadata,releaseUnitTestCompileOnlyDependenciesMetadata,releaseUnitTestImplementationDependenciesMetadata,releaseUnitTestIntransitiveDependenciesMetadata,releaseUnitTestRuntimeOnlyDependenciesMetadata,releaseWearBundling,runtimeOnlyDependenciesMetadata,testApiDependenciesMetadata,testCompile,testCompileOnlyDependenciesMetadata,testDebugApiDependenciesMetadata,testDebugCompileOnlyDependenciesMetadata,testDebugImplementationDependenciesMetadata,testDebugIntransitiveDependenciesMetadata,testDebugRuntimeOnlyDependenciesMetadata,testFixturesApiDependenciesMetadata,testFixturesCompileOnlyDependenciesMetadata,testFixturesDebugApiDependenciesMetadata,testFixturesDebugCompileOnlyDependenciesMetadata,testFixturesDebugImplementationDependenciesMetadata,testFixturesDebugIntransitiveDependenciesMetadata,testFixturesDebugRuntimeOnlyDependenciesMetadata,testFixturesImplementationDependenciesMetadata,testFixturesIntransitiveDependenciesMetadata,testFixturesProfileApiDependenciesMetadata,testFixturesProfileCompileOnlyDependenciesMetadata,testFixturesProfileImplementationDependenciesMetadata,testFixturesProfileIntransitiveDependenciesMetadata,testFixturesProfileRuntimeOnlyDependenciesMetadata,testFixturesReleaseApiDependenciesMetadata,testFixturesReleaseCompileOnlyDependenciesMetadata,testFixturesReleaseImplementationDependenciesMetadata,testFixturesReleaseIntransitiveDependenciesMetadata,testFixturesReleaseRuntimeOnlyDependenciesMetadata,testFixturesRuntimeOnlyDependenciesMetadata,testImplementationDependenciesMetadata,testIntransitiveDependenciesMetadata,testProfileApiDependenciesMetadata,testProfileCompileOnlyDependenciesMetadata,testProfileImplementationDependenciesMetadata,testProfileIntransitiveDependenciesMetadata,testProfileRuntimeOnlyDependenciesMetadata,testReleaseApiDependenciesMetadata,testReleaseCompileOnlyDependenciesMetadata,testReleaseImplementationDependenciesMetadata,testReleaseIntransitiveDependenciesMetadata,testReleaseRuntimeOnlyDependenciesMetadata,testRuntimeOnlyDependenciesMetadata diff --git a/dev/integration_tests/android_embedding_v2_smoke_test/android/project-battery.lockfile b/dev/integration_tests/android_embedding_v2_smoke_test/android/project-battery.lockfile index 813cc34512309..973309f6f1275 100644 --- a/dev/integration_tests/android_embedding_v2_smoke_test/android/project-battery.lockfile +++ b/dev/integration_tests/android_embedding_v2_smoke_test/android/project-battery.lockfile @@ -21,8 +21,8 @@ androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugAndro androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window-java:1.0.0-beta03=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window:1.0.0-beta03=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.android.tools.analytics-library:protos:27.1.3=lintClassPath com.android.tools.analytics-library:shared:27.1.3=lintClassPath com.android.tools.analytics-library:tracker:27.1.3=lintClassPath @@ -87,6 +87,10 @@ org.codehaus.groovy:groovy-all:2.4.15=lintClassPath org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath org.glassfish.jaxb:txw2:2.3.1=lintClassPath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -103,8 +107,12 @@ org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -empty=androidApis,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath,testCompile +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath,testCompile diff --git a/dev/integration_tests/android_embedding_v2_smoke_test/android/settings.gradle b/dev/integration_tests/android_embedding_v2_smoke_test/android/settings.gradle index 03ae72d135c01..a020595962d63 100644 --- a/dev/integration_tests/android_embedding_v2_smoke_test/android/settings.gradle +++ b/dev/integration_tests/android_embedding_v2_smoke_test/android/settings.gradle @@ -8,8 +8,6 @@ include ':app' -enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') - def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml b/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml index e227396249d54..5b52b760c400d 100644 --- a/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml +++ b/dev/integration_tests/android_embedding_v2_smoke_test/pubspec.yaml @@ -30,11 +30,11 @@ dependencies: battery_platform_interface: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" plugin_platform_interface: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -49,14 +49,14 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: @@ -96,4 +96,4 @@ flutter: # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages -# PUBSPEC CHECKSUM: ecef +# PUBSPEC CHECKSUM: d514 diff --git a/dev/integration_tests/android_host_app_v2_embedding/app/build.gradle b/dev/integration_tests/android_host_app_v2_embedding/app/build.gradle index cb7157d59a2c4..f653bc3792fca 100644 --- a/dev/integration_tests/android_host_app_v2_embedding/app/build.gradle +++ b/dev/integration_tests/android_host_app_v2_embedding/app/build.gradle @@ -5,7 +5,7 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 31 + compileSdkVersion 33 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/dev/integration_tests/android_semantics_testing/android/app/src/main/AndroidManifest.xml b/dev/integration_tests/android_semantics_testing/android/app/src/main/AndroidManifest.xml index 4f0cce7e65b47..b2da3f42cef28 100644 --- a/dev/integration_tests/android_semantics_testing/android/app/src/main/AndroidManifest.xml +++ b/dev/integration_tests/android_semantics_testing/android/app/src/main/AndroidManifest.xml @@ -18,6 +18,7 @@ found in the LICENSE file. --> Application and put your custom class here. --> <application android:name="${applicationName}" android:label="Platform Interaction" android:icon="@mipmap/ic_launcher"> <activity android:name="com.yourcompany.platforminteraction.MainActivity" + android:exported="true" android:launchMode="singleTop" android:theme="@android:style/Theme.Black.NoTitleBar" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density" diff --git a/dev/integration_tests/android_semantics_testing/android/build.gradle b/dev/integration_tests/android_semantics_testing/android/build.gradle index 57ace36b3c61b..a2a8866952986 100644 --- a/dev/integration_tests/android_semantics_testing/android/build.gradle +++ b/dev/integration_tests/android_semantics_testing/android/build.gradle @@ -7,14 +7,14 @@ // See dev/tools/bin/generate_gradle_lockfiles.dart. buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/dev/integration_tests/android_semantics_testing/android/buildscript-gradle.lockfile b/dev/integration_tests/android_semantics_testing/android/buildscript-gradle.lockfile index 2577f0aa37e26..eb605802d98f5 100644 --- a/dev/integration_tests/android_semantics_testing/android/buildscript-gradle.lockfile +++ b/dev/integration_tests/android_semantics_testing/android/buildscript-gradle.lockfile @@ -1,120 +1,152 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.databinding:databinding-common:4.1.3=classpath -androidx.databinding:databinding-compiler-common:4.1.3=classpath -com.android.databinding:baseLibrary:4.1.3=classpath -com.android.tools.analytics-library:crash:27.1.3=classpath -com.android.tools.analytics-library:protos:27.1.3=classpath -com.android.tools.analytics-library:shared:27.1.3=classpath -com.android.tools.analytics-library:tracker:27.1.3=classpath -com.android.tools.build.jetifier:jetifier-core:1.0.0-beta09=classpath -com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta09=classpath -com.android.tools.build:aapt2-proto:4.1.3-6503028=classpath -com.android.tools.build:aaptcompiler:4.1.3=classpath -com.android.tools.build:apksig:4.1.3=classpath -com.android.tools.build:apkzlib:4.1.3=classpath -com.android.tools.build:builder-model:4.1.3=classpath -com.android.tools.build:builder-test-api:4.1.3=classpath -com.android.tools.build:builder:4.1.3=classpath -com.android.tools.build:bundletool:0.14.0=classpath -com.android.tools.build:gradle-api:4.1.3=classpath -com.android.tools.build:gradle:4.1.3=classpath -com.android.tools.build:manifest-merger:27.1.3=classpath +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath -com.android.tools.ddms:ddmlib:27.1.3=classpath -com.android.tools.layoutlib:layoutlib-api:27.1.3=classpath -com.android.tools.lint:lint-gradle-api:27.1.3=classpath -com.android.tools.lint:lint-model:27.1.3=classpath -com.android.tools:annotations:27.1.3=classpath -com.android.tools:common:27.1.3=classpath -com.android.tools:dvlib:27.1.3=classpath -com.android.tools:repository:27.1.3=classpath -com.android.tools:sdk-common:27.1.3=classpath -com.android.tools:sdklib:27.1.3=classpath -com.android:signflinger:4.1.3=classpath -com.android:zipflinger:4.1.3=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath com.github.gundy:semver4j:0.16.4=classpath +com.google.android:annotations:4.1.1.4=classpath +com.google.api.grpc:proto-google-common-protos:2.0.1=classpath com.google.auto.value:auto-value-annotations:1.6.2=classpath com.google.code.findbugs:jsr305:3.0.2=classpath -com.google.code.gson:gson:2.8.6=classpath +com.google.code.gson:gson:2.8.9=classpath com.google.crypto.tink:tink:1.3.0-rc2=classpath -com.google.errorprone:error_prone_annotations:2.3.4=classpath +com.google.dagger:dagger:2.28.3=classpath +com.google.errorprone:error_prone_annotations:2.4.0=classpath com.google.flatbuffers:flatbuffers-java:1.12.0=classpath com.google.guava:failureaccess:1.0.1=classpath -com.google.guava:guava:29.0-jre=classpath +com.google.guava:guava:30.1-jre=classpath com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath com.google.j2objc:j2objc-annotations:1.3=classpath com.google.jimfs:jimfs:1.1=classpath -com.google.protobuf:protobuf-java-util:3.10.0=classpath -com.google.protobuf:protobuf-java:3.10.0=classpath -com.google.test.platform:core-proto:0.0.2-dev=classpath +com.google.protobuf:protobuf-java-util:3.17.2=classpath +com.google.protobuf:protobuf-java:3.17.2=classpath +com.google.testing.platform:core-proto:0.0.8-alpha07=classpath com.googlecode.json-simple:json-simple:1.1=classpath com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath com.squareup:javapoet:1.10.0=classpath com.squareup:javawriter:2.5.0=classpath com.sun.activation:javax.activation:1.2.0=classpath -com.sun.istack:istack-commons-runtime:3.0.7=classpath -com.sun.xml.fastinfoset:FastInfoset:1.2.15=classpath -commons-codec:commons-codec:1.10=classpath +com.sun.istack:istack-commons-runtime:3.0.8=classpath +com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath +commons-codec:commons-codec:1.11=classpath commons-io:commons-io:2.4=classpath commons-logging:commons-logging:1.2=classpath de.undercouch:gradle-download-task:4.1.1=classpath -it.unimi.dsi:fastutil:7.2.0=classpath -javax.activation:javax.activation-api:1.2.0=classpath +io.grpc:grpc-api:1.39.0=classpath +io.grpc:grpc-context:1.39.0=classpath +io.grpc:grpc-core:1.39.0=classpath +io.grpc:grpc-netty:1.39.0=classpath +io.grpc:grpc-protobuf-lite:1.39.0=classpath +io.grpc:grpc-protobuf:1.39.0=classpath +io.grpc:grpc-stub:1.39.0=classpath +io.netty:netty-buffer:4.1.52.Final=classpath +io.netty:netty-codec-http2:4.1.52.Final=classpath +io.netty:netty-codec-http:4.1.52.Final=classpath +io.netty:netty-codec-socks:4.1.52.Final=classpath +io.netty:netty-codec:4.1.52.Final=classpath +io.netty:netty-common:4.1.52.Final=classpath +io.netty:netty-handler-proxy:4.1.52.Final=classpath +io.netty:netty-handler:4.1.52.Final=classpath +io.netty:netty-resolver:4.1.52.Final=classpath +io.netty:netty-transport:4.1.52.Final=classpath +io.perfmark:perfmark-api:0.23.0=classpath +it.unimi.dsi:fastutil:8.4.0=classpath +jakarta.activation:jakarta.activation-api:1.2.1=classpath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath javax.inject:javax.inject:1=classpath -javax.xml.bind:jaxb-api:2.3.1=classpath +net.java.dev.jna:jna-platform:5.6.0=classpath +net.java.dev.jna:jna:5.6.0=classpath net.sf.jopt-simple:jopt-simple:4.9=classpath net.sf.kxml:kxml2:2.3.0=classpath -net.sf.proguard:proguard-base:6.0.3=classpath -net.sf.proguard:proguard-gradle:6.0.3=classpath -org.antlr:antlr4:4.5.3=classpath -org.apache.commons:commons-compress:1.12=classpath -org.apache.httpcomponents:httpclient:4.5.6=classpath -org.apache.httpcomponents:httpcore:4.4.10=classpath +org.apache.commons:commons-compress:1.20=classpath +org.apache.httpcomponents:httpclient:4.5.13=classpath +org.apache.httpcomponents:httpcore:4.4.13=classpath org.apache.httpcomponents:httpmime:4.5.6=classpath -org.bouncycastle:bcpkix-jdk15on:1.56=classpath -org.bouncycastle:bcprov-jdk15on:1.56=classpath -org.checkerframework:checker-qual:2.11.1=classpath -org.glassfish.jaxb:jaxb-runtime:2.3.1=classpath -org.glassfish.jaxb:txw2:2.3.1=classpath +org.bitbucket.b_c:jose4j:0.7.0=classpath +org.bouncycastle:bcpkix-jdk15on:1.67=classpath +org.bouncycastle:bcprov-jdk15on:1.67=classpath +org.checkerframework:checker-qual:3.5.0=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.19=classpath +org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath +org.glassfish.jaxb:txw2:2.3.2=classpath org.jdom:jdom2:2.0.6=classpath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=classpath -org.jetbrains.kotlin:kotlin-android-extensions:1.5.31=classpath -org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.5.31=classpath -org.jetbrains.kotlin:kotlin-build-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-runner:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-client:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31=classpath -org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-native-utils:1.5.31=classpath -org.jetbrains.kotlin:kotlin-project-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-reflect:1.3.72=classpath -org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=classpath -org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib:1.3.72=classpath -org.jetbrains.kotlin:kotlin-tooling-metadata:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-io:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-klib:1.5.31=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=classpath -org.jetbrains.trove4j:trove4j:20160824=classpath org.jetbrains:annotations:13.0=classpath org.json:json:20180813=classpath -org.jvnet.staxex:stax-ex:1.8=classpath -org.ow2.asm:asm-analysis:7.0=classpath -org.ow2.asm:asm-commons:7.0=classpath -org.ow2.asm:asm-tree:7.0=classpath -org.ow2.asm:asm-util:7.0=classpath -org.ow2.asm:asm:7.0=classpath -org.tensorflow:tensorflow-lite-metadata:0.1.0-rc1=classpath +org.jvnet.staxex:stax-ex:1.8.1=classpath +org.ow2.asm:asm-analysis:9.1=classpath +org.ow2.asm:asm-commons:9.1=classpath +org.ow2.asm:asm-tree:9.1=classpath +org.ow2.asm:asm-util:9.1=classpath +org.ow2.asm:asm:9.1=classpath +org.slf4j:slf4j-api:1.7.30=classpath +org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath +xerces:xercesImpl:2.12.0=classpath +xml-apis:xml-apis:1.4.01=classpath empty= diff --git a/dev/integration_tests/android_semantics_testing/android/project-app.lockfile b/dev/integration_tests/android_semantics_testing/android/project-app.lockfile index df07b3a3917bc..77a5232c1e42b 100644 --- a/dev/integration_tests/android_semantics_testing/android/project-app.lockfile +++ b/dev/integration_tests/android_semantics_testing/android/project-app.lockfile @@ -100,6 +100,10 @@ org.glassfish.jaxb:txw2:2.3.1=lintClassPath org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.hamcrest:hamcrest-integration:1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -116,8 +120,12 @@ org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -empty=androidApis,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestRuntimeClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestRuntimeClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile diff --git a/dev/integration_tests/android_semantics_testing/android/project-integration_test.lockfile b/dev/integration_tests/android_semantics_testing/android/project-integration_test.lockfile index 9d48dc0b092dd..79f963cac4aa2 100644 --- a/dev/integration_tests/android_semantics_testing/android/project-integration_test.lockfile +++ b/dev/integration_tests/android_semantics_testing/android/project-integration_test.lockfile @@ -99,6 +99,10 @@ org.glassfish.jaxb:txw2:2.3.1=lintClassPath org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.hamcrest:hamcrest-integration:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -115,8 +119,12 @@ org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -empty=androidApis,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath,testCompile +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath,testCompile diff --git a/dev/integration_tests/android_semantics_testing/android/settings.gradle b/dev/integration_tests/android_semantics_testing/android/settings.gradle index 03ae72d135c01..a020595962d63 100644 --- a/dev/integration_tests/android_semantics_testing/android/settings.gradle +++ b/dev/integration_tests/android_semantics_testing/android/settings.gradle @@ -8,8 +8,6 @@ include ':app' -enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') - def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/dev/integration_tests/android_semantics_testing/pubspec.yaml b/dev/integration_tests/android_semantics_testing/pubspec.yaml index 3957ea0cb34ec..f50c94031bd94 100644 --- a/dev/integration_tests/android_semantics_testing/pubspec.yaml +++ b/dev/integration_tests/android_semantics_testing/pubspec.yaml @@ -11,11 +11,11 @@ dependencies: flutter_test: sdk: flutter pub_semver: 2.1.4 - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -27,13 +27,13 @@ dependencies: fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -51,20 +51,19 @@ dependencies: stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 5030 +# PUBSPEC CHECKSUM: 3549 diff --git a/dev/integration_tests/android_views/android/build.gradle b/dev/integration_tests/android_views/android/build.gradle index 57ace36b3c61b..a2a8866952986 100644 --- a/dev/integration_tests/android_views/android/build.gradle +++ b/dev/integration_tests/android_views/android/build.gradle @@ -7,14 +7,14 @@ // See dev/tools/bin/generate_gradle_lockfiles.dart. buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/dev/integration_tests/android_views/android/buildscript-gradle.lockfile b/dev/integration_tests/android_views/android/buildscript-gradle.lockfile index 2577f0aa37e26..eb605802d98f5 100644 --- a/dev/integration_tests/android_views/android/buildscript-gradle.lockfile +++ b/dev/integration_tests/android_views/android/buildscript-gradle.lockfile @@ -1,120 +1,152 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.databinding:databinding-common:4.1.3=classpath -androidx.databinding:databinding-compiler-common:4.1.3=classpath -com.android.databinding:baseLibrary:4.1.3=classpath -com.android.tools.analytics-library:crash:27.1.3=classpath -com.android.tools.analytics-library:protos:27.1.3=classpath -com.android.tools.analytics-library:shared:27.1.3=classpath -com.android.tools.analytics-library:tracker:27.1.3=classpath -com.android.tools.build.jetifier:jetifier-core:1.0.0-beta09=classpath -com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta09=classpath -com.android.tools.build:aapt2-proto:4.1.3-6503028=classpath -com.android.tools.build:aaptcompiler:4.1.3=classpath -com.android.tools.build:apksig:4.1.3=classpath -com.android.tools.build:apkzlib:4.1.3=classpath -com.android.tools.build:builder-model:4.1.3=classpath -com.android.tools.build:builder-test-api:4.1.3=classpath -com.android.tools.build:builder:4.1.3=classpath -com.android.tools.build:bundletool:0.14.0=classpath -com.android.tools.build:gradle-api:4.1.3=classpath -com.android.tools.build:gradle:4.1.3=classpath -com.android.tools.build:manifest-merger:27.1.3=classpath +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath -com.android.tools.ddms:ddmlib:27.1.3=classpath -com.android.tools.layoutlib:layoutlib-api:27.1.3=classpath -com.android.tools.lint:lint-gradle-api:27.1.3=classpath -com.android.tools.lint:lint-model:27.1.3=classpath -com.android.tools:annotations:27.1.3=classpath -com.android.tools:common:27.1.3=classpath -com.android.tools:dvlib:27.1.3=classpath -com.android.tools:repository:27.1.3=classpath -com.android.tools:sdk-common:27.1.3=classpath -com.android.tools:sdklib:27.1.3=classpath -com.android:signflinger:4.1.3=classpath -com.android:zipflinger:4.1.3=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath com.github.gundy:semver4j:0.16.4=classpath +com.google.android:annotations:4.1.1.4=classpath +com.google.api.grpc:proto-google-common-protos:2.0.1=classpath com.google.auto.value:auto-value-annotations:1.6.2=classpath com.google.code.findbugs:jsr305:3.0.2=classpath -com.google.code.gson:gson:2.8.6=classpath +com.google.code.gson:gson:2.8.9=classpath com.google.crypto.tink:tink:1.3.0-rc2=classpath -com.google.errorprone:error_prone_annotations:2.3.4=classpath +com.google.dagger:dagger:2.28.3=classpath +com.google.errorprone:error_prone_annotations:2.4.0=classpath com.google.flatbuffers:flatbuffers-java:1.12.0=classpath com.google.guava:failureaccess:1.0.1=classpath -com.google.guava:guava:29.0-jre=classpath +com.google.guava:guava:30.1-jre=classpath com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath com.google.j2objc:j2objc-annotations:1.3=classpath com.google.jimfs:jimfs:1.1=classpath -com.google.protobuf:protobuf-java-util:3.10.0=classpath -com.google.protobuf:protobuf-java:3.10.0=classpath -com.google.test.platform:core-proto:0.0.2-dev=classpath +com.google.protobuf:protobuf-java-util:3.17.2=classpath +com.google.protobuf:protobuf-java:3.17.2=classpath +com.google.testing.platform:core-proto:0.0.8-alpha07=classpath com.googlecode.json-simple:json-simple:1.1=classpath com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath com.squareup:javapoet:1.10.0=classpath com.squareup:javawriter:2.5.0=classpath com.sun.activation:javax.activation:1.2.0=classpath -com.sun.istack:istack-commons-runtime:3.0.7=classpath -com.sun.xml.fastinfoset:FastInfoset:1.2.15=classpath -commons-codec:commons-codec:1.10=classpath +com.sun.istack:istack-commons-runtime:3.0.8=classpath +com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath +commons-codec:commons-codec:1.11=classpath commons-io:commons-io:2.4=classpath commons-logging:commons-logging:1.2=classpath de.undercouch:gradle-download-task:4.1.1=classpath -it.unimi.dsi:fastutil:7.2.0=classpath -javax.activation:javax.activation-api:1.2.0=classpath +io.grpc:grpc-api:1.39.0=classpath +io.grpc:grpc-context:1.39.0=classpath +io.grpc:grpc-core:1.39.0=classpath +io.grpc:grpc-netty:1.39.0=classpath +io.grpc:grpc-protobuf-lite:1.39.0=classpath +io.grpc:grpc-protobuf:1.39.0=classpath +io.grpc:grpc-stub:1.39.0=classpath +io.netty:netty-buffer:4.1.52.Final=classpath +io.netty:netty-codec-http2:4.1.52.Final=classpath +io.netty:netty-codec-http:4.1.52.Final=classpath +io.netty:netty-codec-socks:4.1.52.Final=classpath +io.netty:netty-codec:4.1.52.Final=classpath +io.netty:netty-common:4.1.52.Final=classpath +io.netty:netty-handler-proxy:4.1.52.Final=classpath +io.netty:netty-handler:4.1.52.Final=classpath +io.netty:netty-resolver:4.1.52.Final=classpath +io.netty:netty-transport:4.1.52.Final=classpath +io.perfmark:perfmark-api:0.23.0=classpath +it.unimi.dsi:fastutil:8.4.0=classpath +jakarta.activation:jakarta.activation-api:1.2.1=classpath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath javax.inject:javax.inject:1=classpath -javax.xml.bind:jaxb-api:2.3.1=classpath +net.java.dev.jna:jna-platform:5.6.0=classpath +net.java.dev.jna:jna:5.6.0=classpath net.sf.jopt-simple:jopt-simple:4.9=classpath net.sf.kxml:kxml2:2.3.0=classpath -net.sf.proguard:proguard-base:6.0.3=classpath -net.sf.proguard:proguard-gradle:6.0.3=classpath -org.antlr:antlr4:4.5.3=classpath -org.apache.commons:commons-compress:1.12=classpath -org.apache.httpcomponents:httpclient:4.5.6=classpath -org.apache.httpcomponents:httpcore:4.4.10=classpath +org.apache.commons:commons-compress:1.20=classpath +org.apache.httpcomponents:httpclient:4.5.13=classpath +org.apache.httpcomponents:httpcore:4.4.13=classpath org.apache.httpcomponents:httpmime:4.5.6=classpath -org.bouncycastle:bcpkix-jdk15on:1.56=classpath -org.bouncycastle:bcprov-jdk15on:1.56=classpath -org.checkerframework:checker-qual:2.11.1=classpath -org.glassfish.jaxb:jaxb-runtime:2.3.1=classpath -org.glassfish.jaxb:txw2:2.3.1=classpath +org.bitbucket.b_c:jose4j:0.7.0=classpath +org.bouncycastle:bcpkix-jdk15on:1.67=classpath +org.bouncycastle:bcprov-jdk15on:1.67=classpath +org.checkerframework:checker-qual:3.5.0=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.19=classpath +org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath +org.glassfish.jaxb:txw2:2.3.2=classpath org.jdom:jdom2:2.0.6=classpath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=classpath -org.jetbrains.kotlin:kotlin-android-extensions:1.5.31=classpath -org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.5.31=classpath -org.jetbrains.kotlin:kotlin-build-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-runner:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-client:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31=classpath -org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-native-utils:1.5.31=classpath -org.jetbrains.kotlin:kotlin-project-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-reflect:1.3.72=classpath -org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=classpath -org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib:1.3.72=classpath -org.jetbrains.kotlin:kotlin-tooling-metadata:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-io:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-klib:1.5.31=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=classpath -org.jetbrains.trove4j:trove4j:20160824=classpath org.jetbrains:annotations:13.0=classpath org.json:json:20180813=classpath -org.jvnet.staxex:stax-ex:1.8=classpath -org.ow2.asm:asm-analysis:7.0=classpath -org.ow2.asm:asm-commons:7.0=classpath -org.ow2.asm:asm-tree:7.0=classpath -org.ow2.asm:asm-util:7.0=classpath -org.ow2.asm:asm:7.0=classpath -org.tensorflow:tensorflow-lite-metadata:0.1.0-rc1=classpath +org.jvnet.staxex:stax-ex:1.8.1=classpath +org.ow2.asm:asm-analysis:9.1=classpath +org.ow2.asm:asm-commons:9.1=classpath +org.ow2.asm:asm-tree:9.1=classpath +org.ow2.asm:asm-util:9.1=classpath +org.ow2.asm:asm:9.1=classpath +org.slf4j:slf4j-api:1.7.30=classpath +org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath +xerces:xercesImpl:2.12.0=classpath +xml-apis:xml-apis:1.4.01=classpath empty= diff --git a/dev/integration_tests/android_views/android/project-app.lockfile b/dev/integration_tests/android_views/android/project-app.lockfile index ab7ba1ebb5ac0..04eea481fb875 100644 --- a/dev/integration_tests/android_views/android/project-app.lockfile +++ b/dev/integration_tests/android_views/android/project-app.lockfile @@ -3,7 +3,8 @@ # This file is expected to be part of source control. androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath +androidx.annotation:annotation:1.5.0=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.arch.core:core-common:2.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.arch.core:core-runtime:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -21,8 +22,8 @@ androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugCompi androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window-java:1.0.0-beta03=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window:1.0.0-beta03=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.android.tools.analytics-library:protos:27.1.3=lintClassPath com.android.tools.analytics-library:shared:27.1.3=lintClassPath com.android.tools.analytics-library:tracker:27.1.3=lintClassPath @@ -54,14 +55,13 @@ com.android.tools:sdk-common:27.1.3=lintClassPath com.android.tools:sdklib:27.1.3=lintClassPath com.android:signflinger:4.1.3=lintClassPath com.android:zipflinger:4.1.3=lintClassPath -com.google.code.findbugs:jsr305:3.0.2=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.code.findbugs:jsr305:3.0.2=lintClassPath com.google.code.gson:gson:2.8.5=lintClassPath -com.google.errorprone:error_prone_annotations:2.3.2=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath -com.google.guava:failureaccess:1.0.1=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath -com.google.guava:guava:28.1-android=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.3.2=lintClassPath +com.google.guava:failureaccess:1.0.1=lintClassPath com.google.guava:guava:28.1-jre=lintClassPath -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath -com.google.j2objc:j2objc-annotations:1.3=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=lintClassPath +com.google.j2objc:j2objc-annotations:1.3=lintClassPath com.google.jimfs:jimfs:1.1=lintClassPath com.google.protobuf:protobuf-java:3.10.0=lintClassPath com.googlecode.json-simple:json-simple:1.1=lintClassPath @@ -83,21 +83,26 @@ org.apache.httpcomponents:httpcore:4.4.10=lintClassPath org.apache.httpcomponents:httpmime:4.5.6=lintClassPath org.bouncycastle:bcpkix-jdk15on:1.56=lintClassPath org.bouncycastle:bcprov-jdk15on:1.56=lintClassPath -org.checkerframework:checker-compat-qual:2.5.5=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath org.checkerframework:checker-qual:2.8.1=lintClassPath org.codehaus.groovy:groovy-all:2.4.15=lintClassPath -org.codehaus.mojo:animal-sniffer-annotations:1.18=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath org.glassfish.jaxb:txw2:2.3.1=lintClassPath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugAndroidTestCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -105,8 +110,12 @@ org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -empty=androidApis,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestRuntimeClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestRuntimeClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile diff --git a/dev/integration_tests/android_views/android/project-path_provider_android.lockfile b/dev/integration_tests/android_views/android/project-path_provider_android.lockfile new file mode 100644 index 0000000000000..212a16c337105 --- /dev/null +++ b/dev/integration_tests/android_views/android/project-path_provider_android.lockfile @@ -0,0 +1,120 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core:1.6.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.fragment:fragment:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.android.tools.analytics-library:protos:27.1.3=lintClassPath +com.android.tools.analytics-library:shared:27.1.3=lintClassPath +com.android.tools.analytics-library:tracker:27.1.3=lintClassPath +com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524=lintClassPath +com.android.tools.build:aapt2:4.1.3-6503028=_internal_aapt2_binary +com.android.tools.build:apksig:4.1.3=lintClassPath +com.android.tools.build:apkzlib:4.1.3=lintClassPath +com.android.tools.build:builder-model:4.1.3=lintClassPath +com.android.tools.build:builder-test-api:4.1.3=lintClassPath +com.android.tools.build:builder:4.1.3=lintClassPath +com.android.tools.build:gradle-api:4.1.3=lintClassPath +com.android.tools.build:manifest-merger:27.1.3=lintClassPath +com.android.tools.ddms:ddmlib:27.1.3=lintClassPath +com.android.tools.external.com-intellij:intellij-core:27.1.3=lintClassPath +com.android.tools.external.com-intellij:kotlin-compiler:27.1.3=lintClassPath +com.android.tools.external.org-jetbrains:uast:27.1.3=lintClassPath +com.android.tools.layoutlib:layoutlib-api:27.1.3=lintClassPath +com.android.tools.lint:lint-api:27.1.3=lintClassPath +com.android.tools.lint:lint-checks:27.1.3=lintClassPath +com.android.tools.lint:lint-gradle-api:27.1.3=lintClassPath +com.android.tools.lint:lint-gradle:27.1.3=lintClassPath +com.android.tools.lint:lint-model:27.1.3=lintClassPath +com.android.tools.lint:lint:27.1.3=lintClassPath +com.android.tools:annotations:27.1.3=lintClassPath +com.android.tools:common:27.1.3=lintClassPath +com.android.tools:dvlib:27.1.3=lintClassPath +com.android.tools:repository:27.1.3=lintClassPath +com.android.tools:sdk-common:27.1.3=lintClassPath +com.android.tools:sdklib:27.1.3=lintClassPath +com.android:signflinger:4.1.3=lintClassPath +com.android:zipflinger:4.1.3=lintClassPath +com.google.code.findbugs:jsr305:3.0.2=lintClassPath +com.google.code.gson:gson:2.8.5=lintClassPath +com.google.errorprone:error_prone_annotations:2.3.2=lintClassPath +com.google.guava:failureaccess:1.0.1=lintClassPath +com.google.guava:guava:28.1-jre=lintClassPath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=lintClassPath +com.google.j2objc:j2objc-annotations:1.3=lintClassPath +com.google.jimfs:jimfs:1.1=lintClassPath +com.google.protobuf:protobuf-java:3.10.0=lintClassPath +com.googlecode.json-simple:json-simple:1.1=lintClassPath +com.squareup:javawriter:2.5.0=lintClassPath +com.sun.activation:javax.activation:1.2.0=lintClassPath +com.sun.istack:istack-commons-runtime:3.0.7=lintClassPath +com.sun.xml.fastinfoset:FastInfoset:1.2.15=lintClassPath +commons-codec:commons-codec:1.10=lintClassPath +commons-logging:commons-logging:1.2=lintClassPath +it.unimi.dsi:fastutil:7.2.0=lintClassPath +javax.activation:javax.activation-api:1.2.0=lintClassPath +javax.inject:javax.inject:1=lintClassPath +javax.xml.bind:jaxb-api:2.3.1=lintClassPath +junit:junit:4.13.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.sf.jopt-simple:jopt-simple:4.9=lintClassPath +net.sf.kxml:kxml2:2.3.0=lintClassPath +org.apache.commons:commons-compress:1.12=lintClassPath +org.apache.httpcomponents:httpclient:4.5.6=lintClassPath +org.apache.httpcomponents:httpcore:4.4.10=lintClassPath +org.apache.httpcomponents:httpmime:4.5.6=lintClassPath +org.bouncycastle:bcpkix-jdk15on:1.56=lintClassPath +org.bouncycastle:bcprov-jdk15on:1.56=lintClassPath +org.checkerframework:checker-qual:2.8.1=lintClassPath +org.codehaus.groovy:groovy-all:2.4.15=lintClassPath +org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath +org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath +org.glassfish.jaxb:txw2:2.3.1=lintClassPath +org.hamcrest:hamcrest-core:1.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.trove4j:trove4j:20160824=lintClassPath +org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jvnet.staxex:stax-ex:1.8=lintClassPath +org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt +org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt +org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt +org.ow2.asm:asm-util:7.0=lintClassPath +org.ow2.asm:asm:7.0=lintClassPath +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath,testCompile diff --git a/dev/integration_tests/android_views/android/settings.gradle b/dev/integration_tests/android_views/android/settings.gradle index 03ae72d135c01..a020595962d63 100644 --- a/dev/integration_tests/android_views/android/settings.gradle +++ b/dev/integration_tests/android_views/android/settings.gradle @@ -8,8 +8,6 @@ include ':app' -enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') - def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/dev/integration_tests/android_views/pubspec.yaml b/dev/integration_tests/android_views/pubspec.yaml index 67ff581b16a25..3d17c7d390563 100644 --- a/dev/integration_tests/android_views/pubspec.yaml +++ b/dev/integration_tests/android_views/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: path_provider: 2.0.15 # This made non-transitive to allow exact pinning # https://github.com/flutter/flutter/issues/116376 - path_provider_android: 2.0.21 + path_provider_android: 2.0.27 collection: 1.17.2 assets_for_android_views: git: @@ -27,15 +27,14 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" ffi: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider_foundation: 2.2.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - path_provider_linux: 2.1.10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path_provider_linux: 2.1.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider_platform_interface: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - path_provider_windows: 2.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path_provider_windows: 2.1.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" platform: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" plugin_platform_interface: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" process: 4.2.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -45,32 +44,34 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - win32: 4.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + win32: 5.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" xdg_directories: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -82,7 +83,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -92,4 +93,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: cb10 +# PUBSPEC CHECKSUM: 6876 diff --git a/dev/integration_tests/channels/android/build.gradle b/dev/integration_tests/channels/android/build.gradle index d93b7eb6e10e2..a2a8866952986 100644 --- a/dev/integration_tests/channels/android/build.gradle +++ b/dev/integration_tests/channels/android/build.gradle @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This file is auto generated. +// To update all the build.gradle files in the Flutter repo, +// See dev/tools/bin/generate_gradle_lockfiles.dart. + buildscript { ext.kotlin_version = '1.7.10' repositories { @@ -13,6 +17,10 @@ buildscript { classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } + + configurations.classpath { + resolutionStrategy.activateDependencyLocking() + } } allprojects { @@ -23,11 +31,19 @@ allprojects { } rootProject.buildDir = '../build' + subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') + dependencyLocking { + ignoredDependencies.add('io.flutter:*') + lockFile = file("${rootProject.projectDir}/project-${project.name}.lockfile") + if (!project.hasProperty('local-engine-repo')) { + lockAllConfigurations() + } + } } tasks.register("clean", Delete) { diff --git a/dev/integration_tests/channels/android/buildscript-gradle.lockfile b/dev/integration_tests/channels/android/buildscript-gradle.lockfile new file mode 100644 index 0000000000000..eb605802d98f5 --- /dev/null +++ b/dev/integration_tests/channels/android/buildscript-gradle.lockfile @@ -0,0 +1,152 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath +com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath +com.github.gundy:semver4j:0.16.4=classpath +com.google.android:annotations:4.1.1.4=classpath +com.google.api.grpc:proto-google-common-protos:2.0.1=classpath +com.google.auto.value:auto-value-annotations:1.6.2=classpath +com.google.code.findbugs:jsr305:3.0.2=classpath +com.google.code.gson:gson:2.8.9=classpath +com.google.crypto.tink:tink:1.3.0-rc2=classpath +com.google.dagger:dagger:2.28.3=classpath +com.google.errorprone:error_prone_annotations:2.4.0=classpath +com.google.flatbuffers:flatbuffers-java:1.12.0=classpath +com.google.guava:failureaccess:1.0.1=classpath +com.google.guava:guava:30.1-jre=classpath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath +com.google.j2objc:j2objc-annotations:1.3=classpath +com.google.jimfs:jimfs:1.1=classpath +com.google.protobuf:protobuf-java-util:3.17.2=classpath +com.google.protobuf:protobuf-java:3.17.2=classpath +com.google.testing.platform:core-proto:0.0.8-alpha07=classpath +com.googlecode.json-simple:json-simple:1.1=classpath +com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath +com.squareup:javapoet:1.10.0=classpath +com.squareup:javawriter:2.5.0=classpath +com.sun.activation:javax.activation:1.2.0=classpath +com.sun.istack:istack-commons-runtime:3.0.8=classpath +com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath +commons-codec:commons-codec:1.11=classpath +commons-io:commons-io:2.4=classpath +commons-logging:commons-logging:1.2=classpath +de.undercouch:gradle-download-task:4.1.1=classpath +io.grpc:grpc-api:1.39.0=classpath +io.grpc:grpc-context:1.39.0=classpath +io.grpc:grpc-core:1.39.0=classpath +io.grpc:grpc-netty:1.39.0=classpath +io.grpc:grpc-protobuf-lite:1.39.0=classpath +io.grpc:grpc-protobuf:1.39.0=classpath +io.grpc:grpc-stub:1.39.0=classpath +io.netty:netty-buffer:4.1.52.Final=classpath +io.netty:netty-codec-http2:4.1.52.Final=classpath +io.netty:netty-codec-http:4.1.52.Final=classpath +io.netty:netty-codec-socks:4.1.52.Final=classpath +io.netty:netty-codec:4.1.52.Final=classpath +io.netty:netty-common:4.1.52.Final=classpath +io.netty:netty-handler-proxy:4.1.52.Final=classpath +io.netty:netty-handler:4.1.52.Final=classpath +io.netty:netty-resolver:4.1.52.Final=classpath +io.netty:netty-transport:4.1.52.Final=classpath +io.perfmark:perfmark-api:0.23.0=classpath +it.unimi.dsi:fastutil:8.4.0=classpath +jakarta.activation:jakarta.activation-api:1.2.1=classpath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath +javax.inject:javax.inject:1=classpath +net.java.dev.jna:jna-platform:5.6.0=classpath +net.java.dev.jna:jna:5.6.0=classpath +net.sf.jopt-simple:jopt-simple:4.9=classpath +net.sf.kxml:kxml2:2.3.0=classpath +org.apache.commons:commons-compress:1.20=classpath +org.apache.httpcomponents:httpclient:4.5.13=classpath +org.apache.httpcomponents:httpcore:4.4.13=classpath +org.apache.httpcomponents:httpmime:4.5.6=classpath +org.bitbucket.b_c:jose4j:0.7.0=classpath +org.bouncycastle:bcpkix-jdk15on:1.67=classpath +org.bouncycastle:bcprov-jdk15on:1.67=classpath +org.checkerframework:checker-qual:3.5.0=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.19=classpath +org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath +org.glassfish.jaxb:txw2:2.3.2=classpath +org.jdom:jdom2:2.0.6=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath +org.jetbrains:annotations:13.0=classpath +org.json:json:20180813=classpath +org.jvnet.staxex:stax-ex:1.8.1=classpath +org.ow2.asm:asm-analysis:9.1=classpath +org.ow2.asm:asm-commons:9.1=classpath +org.ow2.asm:asm-tree:9.1=classpath +org.ow2.asm:asm-util:9.1=classpath +org.ow2.asm:asm:9.1=classpath +org.slf4j:slf4j-api:1.7.30=classpath +org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath +xerces:xercesImpl:2.12.0=classpath +xml-apis:xml-apis:1.4.01=classpath +empty= diff --git a/dev/integration_tests/channels/android/project-app.lockfile b/dev/integration_tests/channels/android/project-app.lockfile new file mode 100644 index 0000000000000..eedf71316a3d0 --- /dev/null +++ b/dev/integration_tests/channels/android/project-app.lockfile @@ -0,0 +1,70 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core:1.6.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.fragment:fragment:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-core:3.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-idling-resource:3.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:monitor:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:rules:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:runner:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.code.findbugs:jsr305:2.0.1=debugAndroidTestCompileClasspath +com.google.code.findbugs:jsr305:3.0.2=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.3.2=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:failureaccess:1.0.1=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:28.1-android=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.j2objc:j2objc-annotations:1.3=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.squareup:javawriter:2.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +javax.inject:javax.inject:1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +junit:junit:4.12=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +net.sf.kxml:kxml2:2.3.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.checkerframework:checker-compat-qual:2.5.5=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.18=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-integration:1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.7.10=kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-reflect:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-script-runtime:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt +org.ow2.asm:asm-commons:9.1=androidJacocoAnt +org.ow2.asm:asm-tree:9.1=androidJacocoAnt +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestApiDependenciesMetadata,androidTestCompileOnlyDependenciesMetadata,androidTestDebugApiDependenciesMetadata,androidTestDebugCompileOnlyDependenciesMetadata,androidTestDebugImplementationDependenciesMetadata,androidTestDebugIntransitiveDependenciesMetadata,androidTestDebugRuntimeOnlyDependenciesMetadata,androidTestImplementationDependenciesMetadata,androidTestIntransitiveDependenciesMetadata,androidTestProfileApiDependenciesMetadata,androidTestProfileCompileOnlyDependenciesMetadata,androidTestProfileImplementationDependenciesMetadata,androidTestProfileIntransitiveDependenciesMetadata,androidTestProfileRuntimeOnlyDependenciesMetadata,androidTestReleaseApiDependenciesMetadata,androidTestReleaseCompileOnlyDependenciesMetadata,androidTestReleaseImplementationDependenciesMetadata,androidTestReleaseIntransitiveDependenciesMetadata,androidTestReleaseRuntimeOnlyDependenciesMetadata,androidTestRuntimeOnlyDependenciesMetadata,androidTestUtil,compileOnlyDependenciesMetadata,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestApiDependenciesMetadata,debugAndroidTestCompileOnlyDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestIntransitiveDependenciesMetadata,debugAndroidTestRuntimeClasspath,debugAndroidTestRuntimeOnlyDependenciesMetadata,debugAnnotationProcessorClasspath,debugCompileOnlyDependenciesMetadata,debugIntransitiveDependenciesMetadata,debugReverseMetadataValues,debugRuntimeOnlyDependenciesMetadata,debugUnitTestAnnotationProcessorClasspath,debugUnitTestApiDependenciesMetadata,debugUnitTestCompileOnlyDependenciesMetadata,debugUnitTestImplementationDependenciesMetadata,debugUnitTestIntransitiveDependenciesMetadata,debugUnitTestRuntimeOnlyDependenciesMetadata,debugWearBundling,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathProfile,kotlinCompilerPluginClasspathProfileUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseUnitTest,kotlinNativeCompilerPluginClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileCompileOnlyDependenciesMetadata,profileIntransitiveDependenciesMetadata,profileReverseMetadataValues,profileRuntimeOnlyDependenciesMetadata,profileUnitTestAnnotationProcessorClasspath,profileUnitTestApiDependenciesMetadata,profileUnitTestCompileOnlyDependenciesMetadata,profileUnitTestImplementationDependenciesMetadata,profileUnitTestIntransitiveDependenciesMetadata,profileUnitTestRuntimeOnlyDependenciesMetadata,profileWearBundling,releaseAnnotationProcessorClasspath,releaseCompileOnlyDependenciesMetadata,releaseIntransitiveDependenciesMetadata,releaseReverseMetadataValues,releaseRuntimeOnlyDependenciesMetadata,releaseUnitTestAnnotationProcessorClasspath,releaseUnitTestApiDependenciesMetadata,releaseUnitTestCompileOnlyDependenciesMetadata,releaseUnitTestImplementationDependenciesMetadata,releaseUnitTestIntransitiveDependenciesMetadata,releaseUnitTestRuntimeOnlyDependenciesMetadata,releaseWearBundling,runtimeOnlyDependenciesMetadata,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testDebugApiDependenciesMetadata,testDebugCompileOnlyDependenciesMetadata,testDebugImplementationDependenciesMetadata,testDebugIntransitiveDependenciesMetadata,testDebugRuntimeOnlyDependenciesMetadata,testFixturesApiDependenciesMetadata,testFixturesCompileOnlyDependenciesMetadata,testFixturesDebugApiDependenciesMetadata,testFixturesDebugCompileOnlyDependenciesMetadata,testFixturesDebugImplementationDependenciesMetadata,testFixturesDebugIntransitiveDependenciesMetadata,testFixturesDebugRuntimeOnlyDependenciesMetadata,testFixturesImplementationDependenciesMetadata,testFixturesIntransitiveDependenciesMetadata,testFixturesProfileApiDependenciesMetadata,testFixturesProfileCompileOnlyDependenciesMetadata,testFixturesProfileImplementationDependenciesMetadata,testFixturesProfileIntransitiveDependenciesMetadata,testFixturesProfileRuntimeOnlyDependenciesMetadata,testFixturesReleaseApiDependenciesMetadata,testFixturesReleaseCompileOnlyDependenciesMetadata,testFixturesReleaseImplementationDependenciesMetadata,testFixturesReleaseIntransitiveDependenciesMetadata,testFixturesReleaseRuntimeOnlyDependenciesMetadata,testFixturesRuntimeOnlyDependenciesMetadata,testImplementationDependenciesMetadata,testIntransitiveDependenciesMetadata,testProfileApiDependenciesMetadata,testProfileCompileOnlyDependenciesMetadata,testProfileImplementationDependenciesMetadata,testProfileIntransitiveDependenciesMetadata,testProfileRuntimeOnlyDependenciesMetadata,testReleaseApiDependenciesMetadata,testReleaseCompileOnlyDependenciesMetadata,testReleaseImplementationDependenciesMetadata,testReleaseIntransitiveDependenciesMetadata,testReleaseRuntimeOnlyDependenciesMetadata,testRuntimeOnlyDependenciesMetadata diff --git a/dev/integration_tests/channels/android/project-integration_test.lockfile b/dev/integration_tests/channels/android/project-integration_test.lockfile new file mode 100644 index 0000000000000..f45da3d417cf0 --- /dev/null +++ b/dev/integration_tests/channels/android/project-integration_test.lockfile @@ -0,0 +1,62 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core:1.6.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.fragment:fragment:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-core:3.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-idling-resource:3.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:monitor:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:rules:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:runner:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.code.findbugs:jsr305:3.0.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.3.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:failureaccess:1.0.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:28.1-android=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.j2objc:j2objc-annotations:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.squareup:javawriter:2.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +javax.inject:javax.inject:1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +junit:junit:4.12=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.sf.kxml:kxml2:2.3.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.checkerframework:checker-compat-qual:2.5.5=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.18=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-integration:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt +org.ow2.asm:asm-commons:9.1=androidJacocoAnt +org.ow2.asm:asm-tree:9.1=androidJacocoAnt +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath diff --git a/dev/integration_tests/channels/android/settings.gradle b/dev/integration_tests/channels/android/settings.gradle index d3b6a4013d714..a020595962d63 100644 --- a/dev/integration_tests/channels/android/settings.gradle +++ b/dev/integration_tests/channels/android/settings.gradle @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This file is auto generated. +// To update all the settings.gradle files in the Flutter repo, +// See dev/tools/bin/generate_gradle_lockfiles.dart. + include ':app' def localPropertiesFile = new File(rootProject.projectDir, "local.properties") diff --git a/dev/integration_tests/channels/ios/Runner/Info.plist b/dev/integration_tests/channels/ios/Runner/Info.plist index eb05d07fcb1cd..8ed5e81003ed1 100644 --- a/dev/integration_tests/channels/ios/Runner/Info.plist +++ b/dev/integration_tests/channels/ios/Runner/Info.plist @@ -41,8 +41,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/integration_tests/channels/pubspec.yaml b/dev/integration_tests/channels/pubspec.yaml index cec04ed57cfbc..dbaf46734172a 100644 --- a/dev/integration_tests/channels/pubspec.yaml +++ b/dev/integration_tests/channels/pubspec.yaml @@ -10,10 +10,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: integration_test: @@ -28,7 +28,7 @@ dev_dependencies: clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -36,11 +36,11 @@ dev_dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 4c0e +# PUBSPEC CHECKSUM: 3234 diff --git a/dev/integration_tests/deferred_components_test/pubspec.yaml b/dev/integration_tests/deferred_components_test/pubspec.yaml index 8e1aad5faf471..fa45b4baee41e 100644 --- a/dev/integration_tests/deferred_components_test/pubspec.yaml +++ b/dev/integration_tests/deferred_components_test/pubspec.yaml @@ -16,8 +16,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -27,30 +26,32 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -62,7 +63,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -79,4 +80,4 @@ flutter: assets: - customassets/flutter_logo.png -# PUBSPEC CHECKSUM: 5030 +# PUBSPEC CHECKSUM: 968e diff --git a/dev/integration_tests/external_ui/android/build.gradle b/dev/integration_tests/external_ui/android/build.gradle index 57ace36b3c61b..a2a8866952986 100644 --- a/dev/integration_tests/external_ui/android/build.gradle +++ b/dev/integration_tests/external_ui/android/build.gradle @@ -7,14 +7,14 @@ // See dev/tools/bin/generate_gradle_lockfiles.dart. buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/dev/integration_tests/external_ui/android/buildscript-gradle.lockfile b/dev/integration_tests/external_ui/android/buildscript-gradle.lockfile index 2577f0aa37e26..eb605802d98f5 100644 --- a/dev/integration_tests/external_ui/android/buildscript-gradle.lockfile +++ b/dev/integration_tests/external_ui/android/buildscript-gradle.lockfile @@ -1,120 +1,152 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.databinding:databinding-common:4.1.3=classpath -androidx.databinding:databinding-compiler-common:4.1.3=classpath -com.android.databinding:baseLibrary:4.1.3=classpath -com.android.tools.analytics-library:crash:27.1.3=classpath -com.android.tools.analytics-library:protos:27.1.3=classpath -com.android.tools.analytics-library:shared:27.1.3=classpath -com.android.tools.analytics-library:tracker:27.1.3=classpath -com.android.tools.build.jetifier:jetifier-core:1.0.0-beta09=classpath -com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta09=classpath -com.android.tools.build:aapt2-proto:4.1.3-6503028=classpath -com.android.tools.build:aaptcompiler:4.1.3=classpath -com.android.tools.build:apksig:4.1.3=classpath -com.android.tools.build:apkzlib:4.1.3=classpath -com.android.tools.build:builder-model:4.1.3=classpath -com.android.tools.build:builder-test-api:4.1.3=classpath -com.android.tools.build:builder:4.1.3=classpath -com.android.tools.build:bundletool:0.14.0=classpath -com.android.tools.build:gradle-api:4.1.3=classpath -com.android.tools.build:gradle:4.1.3=classpath -com.android.tools.build:manifest-merger:27.1.3=classpath +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath -com.android.tools.ddms:ddmlib:27.1.3=classpath -com.android.tools.layoutlib:layoutlib-api:27.1.3=classpath -com.android.tools.lint:lint-gradle-api:27.1.3=classpath -com.android.tools.lint:lint-model:27.1.3=classpath -com.android.tools:annotations:27.1.3=classpath -com.android.tools:common:27.1.3=classpath -com.android.tools:dvlib:27.1.3=classpath -com.android.tools:repository:27.1.3=classpath -com.android.tools:sdk-common:27.1.3=classpath -com.android.tools:sdklib:27.1.3=classpath -com.android:signflinger:4.1.3=classpath -com.android:zipflinger:4.1.3=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath com.github.gundy:semver4j:0.16.4=classpath +com.google.android:annotations:4.1.1.4=classpath +com.google.api.grpc:proto-google-common-protos:2.0.1=classpath com.google.auto.value:auto-value-annotations:1.6.2=classpath com.google.code.findbugs:jsr305:3.0.2=classpath -com.google.code.gson:gson:2.8.6=classpath +com.google.code.gson:gson:2.8.9=classpath com.google.crypto.tink:tink:1.3.0-rc2=classpath -com.google.errorprone:error_prone_annotations:2.3.4=classpath +com.google.dagger:dagger:2.28.3=classpath +com.google.errorprone:error_prone_annotations:2.4.0=classpath com.google.flatbuffers:flatbuffers-java:1.12.0=classpath com.google.guava:failureaccess:1.0.1=classpath -com.google.guava:guava:29.0-jre=classpath +com.google.guava:guava:30.1-jre=classpath com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath com.google.j2objc:j2objc-annotations:1.3=classpath com.google.jimfs:jimfs:1.1=classpath -com.google.protobuf:protobuf-java-util:3.10.0=classpath -com.google.protobuf:protobuf-java:3.10.0=classpath -com.google.test.platform:core-proto:0.0.2-dev=classpath +com.google.protobuf:protobuf-java-util:3.17.2=classpath +com.google.protobuf:protobuf-java:3.17.2=classpath +com.google.testing.platform:core-proto:0.0.8-alpha07=classpath com.googlecode.json-simple:json-simple:1.1=classpath com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath com.squareup:javapoet:1.10.0=classpath com.squareup:javawriter:2.5.0=classpath com.sun.activation:javax.activation:1.2.0=classpath -com.sun.istack:istack-commons-runtime:3.0.7=classpath -com.sun.xml.fastinfoset:FastInfoset:1.2.15=classpath -commons-codec:commons-codec:1.10=classpath +com.sun.istack:istack-commons-runtime:3.0.8=classpath +com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath +commons-codec:commons-codec:1.11=classpath commons-io:commons-io:2.4=classpath commons-logging:commons-logging:1.2=classpath de.undercouch:gradle-download-task:4.1.1=classpath -it.unimi.dsi:fastutil:7.2.0=classpath -javax.activation:javax.activation-api:1.2.0=classpath +io.grpc:grpc-api:1.39.0=classpath +io.grpc:grpc-context:1.39.0=classpath +io.grpc:grpc-core:1.39.0=classpath +io.grpc:grpc-netty:1.39.0=classpath +io.grpc:grpc-protobuf-lite:1.39.0=classpath +io.grpc:grpc-protobuf:1.39.0=classpath +io.grpc:grpc-stub:1.39.0=classpath +io.netty:netty-buffer:4.1.52.Final=classpath +io.netty:netty-codec-http2:4.1.52.Final=classpath +io.netty:netty-codec-http:4.1.52.Final=classpath +io.netty:netty-codec-socks:4.1.52.Final=classpath +io.netty:netty-codec:4.1.52.Final=classpath +io.netty:netty-common:4.1.52.Final=classpath +io.netty:netty-handler-proxy:4.1.52.Final=classpath +io.netty:netty-handler:4.1.52.Final=classpath +io.netty:netty-resolver:4.1.52.Final=classpath +io.netty:netty-transport:4.1.52.Final=classpath +io.perfmark:perfmark-api:0.23.0=classpath +it.unimi.dsi:fastutil:8.4.0=classpath +jakarta.activation:jakarta.activation-api:1.2.1=classpath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath javax.inject:javax.inject:1=classpath -javax.xml.bind:jaxb-api:2.3.1=classpath +net.java.dev.jna:jna-platform:5.6.0=classpath +net.java.dev.jna:jna:5.6.0=classpath net.sf.jopt-simple:jopt-simple:4.9=classpath net.sf.kxml:kxml2:2.3.0=classpath -net.sf.proguard:proguard-base:6.0.3=classpath -net.sf.proguard:proguard-gradle:6.0.3=classpath -org.antlr:antlr4:4.5.3=classpath -org.apache.commons:commons-compress:1.12=classpath -org.apache.httpcomponents:httpclient:4.5.6=classpath -org.apache.httpcomponents:httpcore:4.4.10=classpath +org.apache.commons:commons-compress:1.20=classpath +org.apache.httpcomponents:httpclient:4.5.13=classpath +org.apache.httpcomponents:httpcore:4.4.13=classpath org.apache.httpcomponents:httpmime:4.5.6=classpath -org.bouncycastle:bcpkix-jdk15on:1.56=classpath -org.bouncycastle:bcprov-jdk15on:1.56=classpath -org.checkerframework:checker-qual:2.11.1=classpath -org.glassfish.jaxb:jaxb-runtime:2.3.1=classpath -org.glassfish.jaxb:txw2:2.3.1=classpath +org.bitbucket.b_c:jose4j:0.7.0=classpath +org.bouncycastle:bcpkix-jdk15on:1.67=classpath +org.bouncycastle:bcprov-jdk15on:1.67=classpath +org.checkerframework:checker-qual:3.5.0=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.19=classpath +org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath +org.glassfish.jaxb:txw2:2.3.2=classpath org.jdom:jdom2:2.0.6=classpath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=classpath -org.jetbrains.kotlin:kotlin-android-extensions:1.5.31=classpath -org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.5.31=classpath -org.jetbrains.kotlin:kotlin-build-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-runner:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-client:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31=classpath -org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-native-utils:1.5.31=classpath -org.jetbrains.kotlin:kotlin-project-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-reflect:1.3.72=classpath -org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=classpath -org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib:1.3.72=classpath -org.jetbrains.kotlin:kotlin-tooling-metadata:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-io:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-klib:1.5.31=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=classpath -org.jetbrains.trove4j:trove4j:20160824=classpath org.jetbrains:annotations:13.0=classpath org.json:json:20180813=classpath -org.jvnet.staxex:stax-ex:1.8=classpath -org.ow2.asm:asm-analysis:7.0=classpath -org.ow2.asm:asm-commons:7.0=classpath -org.ow2.asm:asm-tree:7.0=classpath -org.ow2.asm:asm-util:7.0=classpath -org.ow2.asm:asm:7.0=classpath -org.tensorflow:tensorflow-lite-metadata:0.1.0-rc1=classpath +org.jvnet.staxex:stax-ex:1.8.1=classpath +org.ow2.asm:asm-analysis:9.1=classpath +org.ow2.asm:asm-commons:9.1=classpath +org.ow2.asm:asm-tree:9.1=classpath +org.ow2.asm:asm-util:9.1=classpath +org.ow2.asm:asm:9.1=classpath +org.slf4j:slf4j-api:1.7.30=classpath +org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath +xerces:xercesImpl:2.12.0=classpath +xml-apis:xml-apis:1.4.01=classpath empty= diff --git a/dev/integration_tests/external_ui/android/project-app.lockfile b/dev/integration_tests/external_ui/android/project-app.lockfile index 7e81cc229c698..e160a81ffd42a 100644 --- a/dev/integration_tests/external_ui/android/project-app.lockfile +++ b/dev/integration_tests/external_ui/android/project-app.lockfile @@ -87,6 +87,10 @@ org.codehaus.groovy:groovy-all:2.4.15=lintClassPath org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath org.glassfish.jaxb:txw2:2.3.1=lintClassPath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -103,8 +107,12 @@ org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -empty=androidApis,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestRuntimeClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestRuntimeClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile diff --git a/dev/integration_tests/external_ui/android/settings.gradle b/dev/integration_tests/external_ui/android/settings.gradle index 03ae72d135c01..a020595962d63 100644 --- a/dev/integration_tests/external_ui/android/settings.gradle +++ b/dev/integration_tests/external_ui/android/settings.gradle @@ -8,8 +8,6 @@ include ':app' -enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') - def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/dev/integration_tests/external_ui/ios/Runner/Info.plist b/dev/integration_tests/external_ui/ios/Runner/Info.plist index 2af2a77f894d3..60d8199829bb2 100644 --- a/dev/integration_tests/external_ui/ios/Runner/Info.plist +++ b/dev/integration_tests/external_ui/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/integration_tests/external_ui/pubspec.yaml b/dev/integration_tests/external_ui/pubspec.yaml index e10e38cecafa6..63f9692033a55 100644 --- a/dev/integration_tests/external_ui/pubspec.yaml +++ b/dev/integration_tests/external_ui/pubspec.yaml @@ -9,11 +9,11 @@ dependencies: sdk: flutter flutter_driver: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -23,13 +23,13 @@ dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -50,12 +50,13 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -64,4 +65,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: c373 +# PUBSPEC CHECKSUM: 3dd1 diff --git a/dev/integration_tests/flavors/android/build.gradle b/dev/integration_tests/flavors/android/build.gradle index 2de20623576a8..a2a8866952986 100644 --- a/dev/integration_tests/flavors/android/build.gradle +++ b/dev/integration_tests/flavors/android/build.gradle @@ -7,14 +7,14 @@ // See dev/tools/bin/generate_gradle_lockfiles.dart. buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/dev/integration_tests/flavors/android/buildscript-gradle.lockfile b/dev/integration_tests/flavors/android/buildscript-gradle.lockfile index efe13277310ee..eb605802d98f5 100644 --- a/dev/integration_tests/flavors/android/buildscript-gradle.lockfile +++ b/dev/integration_tests/flavors/android/buildscript-gradle.lockfile @@ -1,69 +1,62 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.databinding:databinding-common:7.2.0=classpath -androidx.databinding:databinding-compiler-common:7.2.0=classpath -com.android.databinding:baseLibrary:7.2.0=classpath -com.android.tools.analytics-library:crash:30.2.0=classpath -com.android.tools.analytics-library:protos:30.2.0=classpath -com.android.tools.analytics-library:shared:30.2.0=classpath -com.android.tools.analytics-library:tracker:30.2.0=classpath -com.android.tools.build.jetifier:jetifier-core:1.0.0-beta09=classpath -com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta09=classpath -com.android.tools.build:aapt2-proto:7.2.0-7984345=classpath -com.android.tools.build:aaptcompiler:7.2.0=classpath -com.android.tools.build:apksig:7.2.0=classpath -com.android.tools.build:apkzlib:7.2.0=classpath -com.android.tools.build:builder-model:7.2.0=classpath -com.android.tools.build:builder-test-api:7.2.0=classpath -com.android.tools.build:builder:7.2.0=classpath -com.android.tools.build:bundletool:1.8.2=classpath -com.android.tools.build:gradle-api:7.2.0=classpath -com.android.tools.build:gradle:7.2.0=classpath -com.android.tools.build:manifest-merger:30.2.0=classpath +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath -com.android.tools.ddms:ddmlib:30.2.0=classpath -com.android.tools.layoutlib:layoutlib-api:30.2.0=classpath -com.android.tools.lint:lint-model:30.2.0=classpath -com.android.tools.lint:lint-typedef-remover:30.2.0=classpath -com.android.tools.utp:android-device-provider-ddmlib-proto:30.2.0=classpath -com.android.tools.utp:android-device-provider-gradle-proto:30.2.0=classpath -com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.2.0=classpath -com.android.tools.utp:android-test-plugin-host-coverage-proto:30.2.0=classpath -com.android.tools.utp:android-test-plugin-host-retention-proto:30.2.0=classpath -com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.2.0=classpath -com.android.tools:annotations:30.2.0=classpath -com.android.tools:common:30.2.0=classpath -com.android.tools:dvlib:30.2.0=classpath -com.android.tools:repository:30.2.0=classpath -com.android.tools:sdk-common:30.2.0=classpath -com.android.tools:sdklib:30.2.0=classpath -com.android:signflinger:7.2.0=classpath -com.android:zipflinger:7.2.0=classpath -com.fasterxml.jackson.core:jackson-annotations:2.11.1=classpath -com.fasterxml.jackson.core:jackson-core:2.11.1=classpath -com.fasterxml.jackson.core:jackson-databind:2.11.1=classpath -com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.11.1=classpath -com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.11.1=classpath -com.fasterxml.jackson.module:jackson-module-kotlin:2.11.1=classpath -com.fasterxml.woodstox:woodstox-core:6.2.1=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath com.github.gundy:semver4j:0.16.4=classpath com.google.android:annotations:4.1.1.4=classpath -com.google.api.grpc:proto-google-common-protos:1.12.0=classpath +com.google.api.grpc:proto-google-common-protos:2.0.1=classpath com.google.auto.value:auto-value-annotations:1.6.2=classpath com.google.code.findbugs:jsr305:3.0.2=classpath -com.google.code.gson:gson:2.8.6=classpath +com.google.code.gson:gson:2.8.9=classpath com.google.crypto.tink:tink:1.3.0-rc2=classpath com.google.dagger:dagger:2.28.3=classpath -com.google.errorprone:error_prone_annotations:2.3.4=classpath +com.google.errorprone:error_prone_annotations:2.4.0=classpath com.google.flatbuffers:flatbuffers-java:1.12.0=classpath com.google.guava:failureaccess:1.0.1=classpath com.google.guava:guava:30.1-jre=classpath com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath com.google.j2objc:j2objc-annotations:1.3=classpath com.google.jimfs:jimfs:1.1=classpath -com.google.protobuf:protobuf-java-util:3.10.0=classpath -com.google.protobuf:protobuf-java:3.10.0=classpath +com.google.protobuf:protobuf-java-util:3.17.2=classpath +com.google.protobuf:protobuf-java:3.17.2=classpath com.google.testing.platform:core-proto:0.0.8-alpha07=classpath com.googlecode.json-simple:json-simple:1.1=classpath com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath @@ -76,80 +69,76 @@ commons-codec:commons-codec:1.11=classpath commons-io:commons-io:2.4=classpath commons-logging:commons-logging:1.2=classpath de.undercouch:gradle-download-task:4.1.1=classpath -io.grpc:grpc-api:1.21.1=classpath -io.grpc:grpc-context:1.21.1=classpath -io.grpc:grpc-core:1.21.1=classpath -io.grpc:grpc-netty:1.21.1=classpath -io.grpc:grpc-protobuf-lite:1.21.1=classpath -io.grpc:grpc-protobuf:1.21.1=classpath -io.grpc:grpc-stub:1.21.1=classpath -io.netty:netty-buffer:4.1.34.Final=classpath -io.netty:netty-codec-http2:4.1.34.Final=classpath -io.netty:netty-codec-http:4.1.34.Final=classpath -io.netty:netty-codec-socks:4.1.34.Final=classpath -io.netty:netty-codec:4.1.34.Final=classpath -io.netty:netty-common:4.1.34.Final=classpath -io.netty:netty-handler-proxy:4.1.34.Final=classpath -io.netty:netty-handler:4.1.34.Final=classpath -io.netty:netty-resolver:4.1.34.Final=classpath -io.netty:netty-transport:4.1.34.Final=classpath -io.opencensus:opencensus-api:0.21.0=classpath -io.opencensus:opencensus-contrib-grpc-metrics:0.21.0=classpath +io.grpc:grpc-api:1.39.0=classpath +io.grpc:grpc-context:1.39.0=classpath +io.grpc:grpc-core:1.39.0=classpath +io.grpc:grpc-netty:1.39.0=classpath +io.grpc:grpc-protobuf-lite:1.39.0=classpath +io.grpc:grpc-protobuf:1.39.0=classpath +io.grpc:grpc-stub:1.39.0=classpath +io.netty:netty-buffer:4.1.52.Final=classpath +io.netty:netty-codec-http2:4.1.52.Final=classpath +io.netty:netty-codec-http:4.1.52.Final=classpath +io.netty:netty-codec-socks:4.1.52.Final=classpath +io.netty:netty-codec:4.1.52.Final=classpath +io.netty:netty-common:4.1.52.Final=classpath +io.netty:netty-handler-proxy:4.1.52.Final=classpath +io.netty:netty-handler:4.1.52.Final=classpath +io.netty:netty-resolver:4.1.52.Final=classpath +io.netty:netty-transport:4.1.52.Final=classpath +io.perfmark:perfmark-api:0.23.0=classpath it.unimi.dsi:fastutil:8.4.0=classpath jakarta.activation:jakarta.activation-api:1.2.1=classpath jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath javax.inject:javax.inject:1=classpath net.java.dev.jna:jna-platform:5.6.0=classpath net.java.dev.jna:jna:5.6.0=classpath net.sf.jopt-simple:jopt-simple:4.9=classpath net.sf.kxml:kxml2:2.3.0=classpath org.apache.commons:commons-compress:1.20=classpath -org.apache.httpcomponents:httpclient:4.5.9=classpath -org.apache.httpcomponents:httpcore:4.4.11=classpath +org.apache.httpcomponents:httpclient:4.5.13=classpath +org.apache.httpcomponents:httpcore:4.4.13=classpath org.apache.httpcomponents:httpmime:4.5.6=classpath org.bitbucket.b_c:jose4j:0.7.0=classpath -org.bouncycastle:bcpkix-jdk15on:1.56=classpath -org.bouncycastle:bcprov-jdk15on:1.56=classpath +org.bouncycastle:bcpkix-jdk15on:1.67=classpath +org.bouncycastle:bcprov-jdk15on:1.67=classpath org.checkerframework:checker-qual:3.5.0=classpath -org.codehaus.mojo:animal-sniffer-annotations:1.17=classpath -org.codehaus.woodstox:stax2-api:4.2.1=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.19=classpath org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath org.glassfish.jaxb:txw2:2.3.2=classpath org.jdom:jdom2:2.0.6=classpath -org.jetbrains.dokka:dokka-core:1.4.32=classpath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=classpath -org.jetbrains.kotlin:kotlin-android-extensions:1.5.31=classpath -org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.5.31=classpath -org.jetbrains.kotlin:kotlin-build-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-runner:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-client:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31=classpath -org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-native-utils:1.5.31=classpath -org.jetbrains.kotlin:kotlin-project-model:1.5.31=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath -org.jetbrains.kotlin:kotlin-tooling-metadata:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-io:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-klib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=classpath org.jetbrains:annotations:13.0=classpath -org.jetbrains:markdown-jvm:0.2.1=classpath -org.jetbrains:markdown:0.2.1=classpath org.json:json:20180813=classpath -org.jsoup:jsoup:1.13.1=classpath org.jvnet.staxex:stax-ex:1.8.1=classpath org.ow2.asm:asm-analysis:9.1=classpath org.ow2.asm:asm-commons:9.1=classpath diff --git a/dev/integration_tests/flavors/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/flavors/android/gradle/wrapper/gradle-wrapper.properties index f338a880848d1..cb24abda10ae7 100644 --- a/dev/integration_tests/flavors/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/flavors/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/dev/integration_tests/flavors/android/settings.gradle b/dev/integration_tests/flavors/android/settings.gradle index 03ae72d135c01..a020595962d63 100644 --- a/dev/integration_tests/flavors/android/settings.gradle +++ b/dev/integration_tests/flavors/android/settings.gradle @@ -8,8 +8,6 @@ include ':app' -enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') - def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/dev/integration_tests/flavors/ios/Runner/Info-Free.plist b/dev/integration_tests/flavors/ios/Runner/Info-Free.plist index ce8c72311f185..b6853d58c582f 100644 --- a/dev/integration_tests/flavors/ios/Runner/Info-Free.plist +++ b/dev/integration_tests/flavors/ios/Runner/Info-Free.plist @@ -41,8 +41,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> </dict> diff --git a/dev/integration_tests/flavors/ios/Runner/Info-Paid.plist b/dev/integration_tests/flavors/ios/Runner/Info-Paid.plist index ce8c72311f185..b6853d58c582f 100644 --- a/dev/integration_tests/flavors/ios/Runner/Info-Paid.plist +++ b/dev/integration_tests/flavors/ios/Runner/Info-Paid.plist @@ -41,8 +41,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> </dict> diff --git a/dev/integration_tests/flavors/pubspec.yaml b/dev/integration_tests/flavors/pubspec.yaml index b4774a6ce33c4..7e6e37466ed81 100644 --- a/dev/integration_tests/flavors/pubspec.yaml +++ b/dev/integration_tests/flavors/pubspec.yaml @@ -11,11 +11,11 @@ dependencies: sdk: flutter integration_test: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -25,13 +25,13 @@ dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -52,12 +52,13 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -73,4 +74,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 5030 +# PUBSPEC CHECKSUM: 968e diff --git a/dev/integration_tests/flutter_gallery/android/build.gradle b/dev/integration_tests/flutter_gallery/android/build.gradle index 2de20623576a8..a2a8866952986 100644 --- a/dev/integration_tests/flutter_gallery/android/build.gradle +++ b/dev/integration_tests/flutter_gallery/android/build.gradle @@ -7,14 +7,14 @@ // See dev/tools/bin/generate_gradle_lockfiles.dart. buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/dev/integration_tests/flutter_gallery/android/buildscript-gradle.lockfile b/dev/integration_tests/flutter_gallery/android/buildscript-gradle.lockfile index efe13277310ee..eb605802d98f5 100644 --- a/dev/integration_tests/flutter_gallery/android/buildscript-gradle.lockfile +++ b/dev/integration_tests/flutter_gallery/android/buildscript-gradle.lockfile @@ -1,69 +1,62 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.databinding:databinding-common:7.2.0=classpath -androidx.databinding:databinding-compiler-common:7.2.0=classpath -com.android.databinding:baseLibrary:7.2.0=classpath -com.android.tools.analytics-library:crash:30.2.0=classpath -com.android.tools.analytics-library:protos:30.2.0=classpath -com.android.tools.analytics-library:shared:30.2.0=classpath -com.android.tools.analytics-library:tracker:30.2.0=classpath -com.android.tools.build.jetifier:jetifier-core:1.0.0-beta09=classpath -com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta09=classpath -com.android.tools.build:aapt2-proto:7.2.0-7984345=classpath -com.android.tools.build:aaptcompiler:7.2.0=classpath -com.android.tools.build:apksig:7.2.0=classpath -com.android.tools.build:apkzlib:7.2.0=classpath -com.android.tools.build:builder-model:7.2.0=classpath -com.android.tools.build:builder-test-api:7.2.0=classpath -com.android.tools.build:builder:7.2.0=classpath -com.android.tools.build:bundletool:1.8.2=classpath -com.android.tools.build:gradle-api:7.2.0=classpath -com.android.tools.build:gradle:7.2.0=classpath -com.android.tools.build:manifest-merger:30.2.0=classpath +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath -com.android.tools.ddms:ddmlib:30.2.0=classpath -com.android.tools.layoutlib:layoutlib-api:30.2.0=classpath -com.android.tools.lint:lint-model:30.2.0=classpath -com.android.tools.lint:lint-typedef-remover:30.2.0=classpath -com.android.tools.utp:android-device-provider-ddmlib-proto:30.2.0=classpath -com.android.tools.utp:android-device-provider-gradle-proto:30.2.0=classpath -com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.2.0=classpath -com.android.tools.utp:android-test-plugin-host-coverage-proto:30.2.0=classpath -com.android.tools.utp:android-test-plugin-host-retention-proto:30.2.0=classpath -com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.2.0=classpath -com.android.tools:annotations:30.2.0=classpath -com.android.tools:common:30.2.0=classpath -com.android.tools:dvlib:30.2.0=classpath -com.android.tools:repository:30.2.0=classpath -com.android.tools:sdk-common:30.2.0=classpath -com.android.tools:sdklib:30.2.0=classpath -com.android:signflinger:7.2.0=classpath -com.android:zipflinger:7.2.0=classpath -com.fasterxml.jackson.core:jackson-annotations:2.11.1=classpath -com.fasterxml.jackson.core:jackson-core:2.11.1=classpath -com.fasterxml.jackson.core:jackson-databind:2.11.1=classpath -com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.11.1=classpath -com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.11.1=classpath -com.fasterxml.jackson.module:jackson-module-kotlin:2.11.1=classpath -com.fasterxml.woodstox:woodstox-core:6.2.1=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath com.github.gundy:semver4j:0.16.4=classpath com.google.android:annotations:4.1.1.4=classpath -com.google.api.grpc:proto-google-common-protos:1.12.0=classpath +com.google.api.grpc:proto-google-common-protos:2.0.1=classpath com.google.auto.value:auto-value-annotations:1.6.2=classpath com.google.code.findbugs:jsr305:3.0.2=classpath -com.google.code.gson:gson:2.8.6=classpath +com.google.code.gson:gson:2.8.9=classpath com.google.crypto.tink:tink:1.3.0-rc2=classpath com.google.dagger:dagger:2.28.3=classpath -com.google.errorprone:error_prone_annotations:2.3.4=classpath +com.google.errorprone:error_prone_annotations:2.4.0=classpath com.google.flatbuffers:flatbuffers-java:1.12.0=classpath com.google.guava:failureaccess:1.0.1=classpath com.google.guava:guava:30.1-jre=classpath com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath com.google.j2objc:j2objc-annotations:1.3=classpath com.google.jimfs:jimfs:1.1=classpath -com.google.protobuf:protobuf-java-util:3.10.0=classpath -com.google.protobuf:protobuf-java:3.10.0=classpath +com.google.protobuf:protobuf-java-util:3.17.2=classpath +com.google.protobuf:protobuf-java:3.17.2=classpath com.google.testing.platform:core-proto:0.0.8-alpha07=classpath com.googlecode.json-simple:json-simple:1.1=classpath com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath @@ -76,80 +69,76 @@ commons-codec:commons-codec:1.11=classpath commons-io:commons-io:2.4=classpath commons-logging:commons-logging:1.2=classpath de.undercouch:gradle-download-task:4.1.1=classpath -io.grpc:grpc-api:1.21.1=classpath -io.grpc:grpc-context:1.21.1=classpath -io.grpc:grpc-core:1.21.1=classpath -io.grpc:grpc-netty:1.21.1=classpath -io.grpc:grpc-protobuf-lite:1.21.1=classpath -io.grpc:grpc-protobuf:1.21.1=classpath -io.grpc:grpc-stub:1.21.1=classpath -io.netty:netty-buffer:4.1.34.Final=classpath -io.netty:netty-codec-http2:4.1.34.Final=classpath -io.netty:netty-codec-http:4.1.34.Final=classpath -io.netty:netty-codec-socks:4.1.34.Final=classpath -io.netty:netty-codec:4.1.34.Final=classpath -io.netty:netty-common:4.1.34.Final=classpath -io.netty:netty-handler-proxy:4.1.34.Final=classpath -io.netty:netty-handler:4.1.34.Final=classpath -io.netty:netty-resolver:4.1.34.Final=classpath -io.netty:netty-transport:4.1.34.Final=classpath -io.opencensus:opencensus-api:0.21.0=classpath -io.opencensus:opencensus-contrib-grpc-metrics:0.21.0=classpath +io.grpc:grpc-api:1.39.0=classpath +io.grpc:grpc-context:1.39.0=classpath +io.grpc:grpc-core:1.39.0=classpath +io.grpc:grpc-netty:1.39.0=classpath +io.grpc:grpc-protobuf-lite:1.39.0=classpath +io.grpc:grpc-protobuf:1.39.0=classpath +io.grpc:grpc-stub:1.39.0=classpath +io.netty:netty-buffer:4.1.52.Final=classpath +io.netty:netty-codec-http2:4.1.52.Final=classpath +io.netty:netty-codec-http:4.1.52.Final=classpath +io.netty:netty-codec-socks:4.1.52.Final=classpath +io.netty:netty-codec:4.1.52.Final=classpath +io.netty:netty-common:4.1.52.Final=classpath +io.netty:netty-handler-proxy:4.1.52.Final=classpath +io.netty:netty-handler:4.1.52.Final=classpath +io.netty:netty-resolver:4.1.52.Final=classpath +io.netty:netty-transport:4.1.52.Final=classpath +io.perfmark:perfmark-api:0.23.0=classpath it.unimi.dsi:fastutil:8.4.0=classpath jakarta.activation:jakarta.activation-api:1.2.1=classpath jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath javax.inject:javax.inject:1=classpath net.java.dev.jna:jna-platform:5.6.0=classpath net.java.dev.jna:jna:5.6.0=classpath net.sf.jopt-simple:jopt-simple:4.9=classpath net.sf.kxml:kxml2:2.3.0=classpath org.apache.commons:commons-compress:1.20=classpath -org.apache.httpcomponents:httpclient:4.5.9=classpath -org.apache.httpcomponents:httpcore:4.4.11=classpath +org.apache.httpcomponents:httpclient:4.5.13=classpath +org.apache.httpcomponents:httpcore:4.4.13=classpath org.apache.httpcomponents:httpmime:4.5.6=classpath org.bitbucket.b_c:jose4j:0.7.0=classpath -org.bouncycastle:bcpkix-jdk15on:1.56=classpath -org.bouncycastle:bcprov-jdk15on:1.56=classpath +org.bouncycastle:bcpkix-jdk15on:1.67=classpath +org.bouncycastle:bcprov-jdk15on:1.67=classpath org.checkerframework:checker-qual:3.5.0=classpath -org.codehaus.mojo:animal-sniffer-annotations:1.17=classpath -org.codehaus.woodstox:stax2-api:4.2.1=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.19=classpath org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath org.glassfish.jaxb:txw2:2.3.2=classpath org.jdom:jdom2:2.0.6=classpath -org.jetbrains.dokka:dokka-core:1.4.32=classpath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=classpath -org.jetbrains.kotlin:kotlin-android-extensions:1.5.31=classpath -org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.5.31=classpath -org.jetbrains.kotlin:kotlin-build-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-runner:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-client:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31=classpath -org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-native-utils:1.5.31=classpath -org.jetbrains.kotlin:kotlin-project-model:1.5.31=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath -org.jetbrains.kotlin:kotlin-tooling-metadata:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-io:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-klib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=classpath org.jetbrains:annotations:13.0=classpath -org.jetbrains:markdown-jvm:0.2.1=classpath -org.jetbrains:markdown:0.2.1=classpath org.json:json:20180813=classpath -org.jsoup:jsoup:1.13.1=classpath org.jvnet.staxex:stax-ex:1.8.1=classpath org.ow2.asm:asm-analysis:9.1=classpath org.ow2.asm:asm-commons:9.1=classpath diff --git a/dev/integration_tests/flutter_gallery/android/settings.gradle b/dev/integration_tests/flutter_gallery/android/settings.gradle index 03ae72d135c01..a020595962d63 100644 --- a/dev/integration_tests/flutter_gallery/android/settings.gradle +++ b/dev/integration_tests/flutter_gallery/android/settings.gradle @@ -8,8 +8,6 @@ include ':app' -enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') - def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/dev/integration_tests/flutter_gallery/ios/Runner/Info.plist b/dev/integration_tests/flutter_gallery/ios/Runner/Info.plist index 14453bec8272b..e9a4eea159f26 100644 --- a/dev/integration_tests/flutter_gallery/ios/Runner/Info.plist +++ b/dev/integration_tests/flutter_gallery/ios/Runner/Info.plist @@ -41,8 +41,6 @@ </array> <key>ITSAppUsesNonExemptEncryption</key> <false/> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/integration_tests/flutter_gallery/lib/demo/contacts_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/contacts_demo.dart index 589067655526d..70d1a97ee9dcb 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/contacts_demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/contacts_demo.dart @@ -104,6 +104,7 @@ class ContactsDemoState extends State<ContactsDemo> { Widget build(BuildContext context) { return Theme( data: ThemeData( + useMaterial3: false, brightness: Brightness.light, primarySwatch: Colors.indigo, platform: Theme.of(context).platform, diff --git a/dev/integration_tests/flutter_gallery/lib/demo/material/expansion_panels_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/material/expansion_panels_demo.dart index dcd2e687b0f07..86e0ca50e8086 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/material/expansion_panels_demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/material/expansion_panels_demo.dart @@ -345,7 +345,7 @@ class _ExpansionPanelsDemoState extends State<ExpansionPanelsDemo> { child: ExpansionPanelList( expansionCallback: (int index, bool isExpanded) { setState(() { - _demoItems[index].isExpanded = !isExpanded; + _demoItems[index].isExpanded = isExpanded; }); }, children: _demoItems.map<ExpansionPanel>((DemoItem<dynamic> item) { diff --git a/dev/integration_tests/flutter_gallery/lib/gallery/themes.dart b/dev/integration_tests/flutter_gallery/lib/gallery/themes.dart index 66b7d7a762abf..dbaefed98cbde 100644 --- a/dev/integration_tests/flutter_gallery/lib/gallery/themes.dart +++ b/dev/integration_tests/flutter_gallery/lib/gallery/themes.dart @@ -26,6 +26,7 @@ ThemeData _buildDarkTheme() { background: const Color(0xFF202124), ); final ThemeData base = ThemeData( + useMaterial3: false, brightness: Brightness.dark, colorScheme: colorScheme, primaryColor: primaryColor, @@ -50,6 +51,7 @@ ThemeData _buildLightTheme() { error: const Color(0xFFB00020), ); final ThemeData base = ThemeData( + useMaterial3: false, brightness: Brightness.light, colorScheme: colorScheme, primaryColor: primaryColor, diff --git a/dev/integration_tests/flutter_gallery/linux/flutter/generated_plugin_registrant.cc b/dev/integration_tests/flutter_gallery/linux/flutter/generated_plugin_registrant.cc deleted file mode 100644 index f6f23bfe970ff..0000000000000 --- a/dev/integration_tests/flutter_gallery/linux/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - -#include <url_launcher_linux/url_launcher_plugin.h> - -void fl_register_plugins(FlPluginRegistry* registry) { - g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); - url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); -} diff --git a/dev/integration_tests/flutter_gallery/linux/flutter/generated_plugin_registrant.h b/dev/integration_tests/flutter_gallery/linux/flutter/generated_plugin_registrant.h deleted file mode 100644 index e0f0a47bc08f3..0000000000000 --- a/dev/integration_tests/flutter_gallery/linux/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include <flutter_linux/flutter_linux.h> - -// Registers Flutter plugins. -void fl_register_plugins(FlPluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/dev/integration_tests/flutter_gallery/linux/flutter/generated_plugins.cmake b/dev/integration_tests/flutter_gallery/linux/flutter/generated_plugins.cmake deleted file mode 100644 index f16b4c34213ac..0000000000000 --- a/dev/integration_tests/flutter_gallery/linux/flutter/generated_plugins.cmake +++ /dev/null @@ -1,24 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST - url_launcher_linux -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/dev/integration_tests/flutter_gallery/macos/Podfile.lock b/dev/integration_tests/flutter_gallery/macos/Podfile.lock index 4aa9d3e025375..7fc925bb07325 100644 --- a/dev/integration_tests/flutter_gallery/macos/Podfile.lock +++ b/dev/integration_tests/flutter_gallery/macos/Podfile.lock @@ -28,8 +28,8 @@ SPEC CHECKSUMS: connectivity_macos: 5dae6ee11d320fac7c05f0d08bd08fc32b5514d9 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96 - url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3 + url_launcher_macos: 5335912b679c073563f29d89d33d10d459f95451 PODFILE CHECKSUM: 2e6060c123c393d6beb3ee5b7beaf789de4d2e47 -COCOAPODS: 1.11.3 +COCOAPODS: 1.12.0 diff --git a/dev/integration_tests/flutter_gallery/pubspec.yaml b/dev/integration_tests/flutter_gallery/pubspec.yaml index 5b607f252778c..7be18d52b30eb 100644 --- a/dev/integration_tests/flutter_gallery/pubspec.yaml +++ b/dev/integration_tests/flutter_gallery/pubspec.yaml @@ -31,10 +31,9 @@ dependencies: connectivity_for_web: 0.4.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" connectivity_macos: 0.2.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" connectivity_platform_interface: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - csslib: 0.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + csslib: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" device_info_platform_interface: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - html: 0.15.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + html: 0.15.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -44,12 +43,13 @@ dependencies: url_launcher_ios: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" url_launcher_linux: 3.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" url_launcher_macos: 3.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - url_launcher_platform_interface: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - url_launcher_web: 2.0.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + url_launcher_platform_interface: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + url_launcher_web: 2.0.17 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" url_launcher_windows: 3.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" video_player_platform_interface: 5.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" video_player_web: 2.0.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -58,13 +58,13 @@ dev_dependencies: sdk: flutter flutter_goldens: sdk: flutter - test: 1.24.2 + test: 1.24.3 integration_test: sdk: flutter - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -73,12 +73,13 @@ dev_dependencies: fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -95,10 +96,10 @@ dev_dependencies: stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -275,4 +276,4 @@ flutter: - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Regular.ttf - asset: packages/flutter_gallery_assets/fonts/merriweather/Merriweather-Light.ttf -# PUBSPEC CHECKSUM: a947 +# PUBSPEC CHECKSUM: bd6f diff --git a/dev/integration_tests/flutter_gallery/test/accessibility_test.dart b/dev/integration_tests/flutter_gallery/test/accessibility_test.dart index 4bbd3bd430e90..c1d229acb40c5 100644 --- a/dev/integration_tests/flutter_gallery/test/accessibility_test.dart +++ b/dev/integration_tests/flutter_gallery/test/accessibility_test.dart @@ -8,235 +8,249 @@ import 'package:flutter_gallery/gallery/app.dart'; import 'package:flutter_gallery/gallery/themes.dart'; import 'package:flutter_test/flutter_test.dart'; +class TestMaterialApp extends StatelessWidget { + const TestMaterialApp({ super.key, required this.home }); + + final Widget home; + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: home, + ); + } +} + void main() { group('All material demos meet recommended tap target sizes', () { testWidgets('backdrop_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: BackdropDemo())); + await tester.pumpWidget(const TestMaterialApp(home: BackdropDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('bottom_app_bar_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: BottomAppBarDemo())); + await tester.pumpWidget(const TestMaterialApp(home: BottomAppBarDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('bottom_navigation_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: BottomNavigationDemo())); + await tester.pumpWidget(const TestMaterialApp(home: BottomNavigationDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('buttons_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ButtonsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ButtonsDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('cards_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: CardsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: CardsDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('chip_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ChipDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ChipDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }, skip: true); // https://github.com/flutter/flutter/issues/42455 testWidgets('data_table_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: DataTableDemo())); + await tester.pumpWidget(const TestMaterialApp(home: DataTableDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('date_and_time_picker_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: DateAndTimePickerDemo())); + await tester.pumpWidget(const TestMaterialApp(home: DateAndTimePickerDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('dialog_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: DialogDemo())); + await tester.pumpWidget(const TestMaterialApp(home: DialogDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('drawer_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: DrawerDemo())); + await tester.pumpWidget(const TestMaterialApp(home: DrawerDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('elevation_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ElevationDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ElevationDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('expansion_panels_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ExpansionPanelsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ExpansionPanelsDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('grid_list_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: GridListDemo())); + await tester.pumpWidget(const TestMaterialApp(home: GridListDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('icons_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: IconsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: IconsDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('leave_behind_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: LeaveBehindDemo())); + await tester.pumpWidget(const TestMaterialApp(home: LeaveBehindDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('list_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ListDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ListDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('menu_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: MenuDemo())); + await tester.pumpWidget(const TestMaterialApp(home: MenuDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('modal_bottom_sheet_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ModalBottomSheetDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ModalBottomSheetDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('overscroll_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: OverscrollDemo())); + await tester.pumpWidget(const TestMaterialApp(home: OverscrollDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('page_selector_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: PageSelectorDemo())); + await tester.pumpWidget(const TestMaterialApp(home: PageSelectorDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('persistent_bottom_sheet_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: PersistentBottomSheetDemo())); + await tester.pumpWidget(const TestMaterialApp(home: PersistentBottomSheetDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('progress_indicator_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ProgressIndicatorDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ProgressIndicatorDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('reorderable_list_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ReorderableListDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ReorderableListDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('scrollable_tabs_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ScrollableTabsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ScrollableTabsDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('search_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: SearchDemo())); + await tester.pumpWidget(const TestMaterialApp(home: SearchDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('selection_controls_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: SelectionControlsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: SelectionControlsDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('slider_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: SliderDemo())); + await tester.pumpWidget(const TestMaterialApp(home: SliderDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('snack_bar_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: SnackBarDemo())); + await tester.pumpWidget(const TestMaterialApp(home: SnackBarDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('tabs_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: TabsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: TabsDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('tabs_fab_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: TabsFabDemo())); + await tester.pumpWidget(const TestMaterialApp(home: TabsFabDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('text_form_field_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: TextFormFieldDemo())); + await tester.pumpWidget(const TestMaterialApp(home: TextFormFieldDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('tooltip_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: TooltipDemo())); + await tester.pumpWidget(const TestMaterialApp(home: TooltipDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); testWidgets('expansion_tile_list_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ExpansionTileListDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ExpansionTileListDemo())); await expectLater(tester, meetsGuideline(androidTapTargetGuideline)); handle.dispose(); }); @@ -245,231 +259,231 @@ void main() { group('All material demos have labeled tap targets', () { testWidgets('backdrop_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: BackdropDemo())); + await tester.pumpWidget(const TestMaterialApp(home: BackdropDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('bottom_app_bar_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: BottomAppBarDemo())); + await tester.pumpWidget(const TestMaterialApp(home: BottomAppBarDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('bottom_navigation_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: BottomNavigationDemo())); + await tester.pumpWidget(const TestMaterialApp(home: BottomNavigationDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('buttons_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ButtonsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ButtonsDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('cards_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: CardsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: CardsDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('chip_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ChipDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ChipDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('data_table_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: DataTableDemo())); + await tester.pumpWidget(const TestMaterialApp(home: DataTableDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }, skip: true); // DataTables are not accessible, https://github.com/flutter/flutter/issues/10830 testWidgets('date_and_time_picker_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: DateAndTimePickerDemo())); + await tester.pumpWidget(const TestMaterialApp(home: DateAndTimePickerDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('dialog_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: DialogDemo())); + await tester.pumpWidget(const TestMaterialApp(home: DialogDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('drawer_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: DrawerDemo())); + await tester.pumpWidget(const TestMaterialApp(home: DrawerDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('elevation_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ElevationDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ElevationDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('expansion_panels_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ExpansionPanelsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ExpansionPanelsDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('grid_list_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: GridListDemo())); + await tester.pumpWidget(const TestMaterialApp(home: GridListDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('icons_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: IconsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: IconsDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('leave_behind_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: LeaveBehindDemo())); + await tester.pumpWidget(const TestMaterialApp(home: LeaveBehindDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('list_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ListDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ListDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('menu_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: MenuDemo())); + await tester.pumpWidget(const TestMaterialApp(home: MenuDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('modal_bottom_sheet_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ModalBottomSheetDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ModalBottomSheetDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('overscroll_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: OverscrollDemo())); + await tester.pumpWidget(const TestMaterialApp(home: OverscrollDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('page_selector_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: PageSelectorDemo())); + await tester.pumpWidget(const TestMaterialApp(home: PageSelectorDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('persistent_bottom_sheet_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: PersistentBottomSheetDemo())); + await tester.pumpWidget(const TestMaterialApp(home: PersistentBottomSheetDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('progress_indicator_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ProgressIndicatorDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ProgressIndicatorDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('reorderable_list_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ReorderableListDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ReorderableListDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('scrollable_tabs_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ScrollableTabsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ScrollableTabsDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('search_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: SearchDemo())); + await tester.pumpWidget(const TestMaterialApp(home: SearchDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('selection_controls_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: SelectionControlsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: SelectionControlsDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('slider_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: SliderDemo())); + await tester.pumpWidget(const TestMaterialApp(home: SliderDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('snack_bar_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: SnackBarDemo())); + await tester.pumpWidget(const TestMaterialApp(home: SnackBarDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('tabs_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: TabsDemo())); + await tester.pumpWidget(const TestMaterialApp(home: TabsDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('tabs_fab_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: TabsFabDemo())); + await tester.pumpWidget(const TestMaterialApp(home: TabsFabDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('text_form_field_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: TextFormFieldDemo())); + await tester.pumpWidget(const TestMaterialApp(home: TextFormFieldDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('tooltip_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: TooltipDemo())); + await tester.pumpWidget(const TestMaterialApp(home: TooltipDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); testWidgets('expansion_tile_list_demo', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); - await tester.pumpWidget(const MaterialApp(home: ExpansionTileListDemo())); + await tester.pumpWidget(const TestMaterialApp(home: ExpansionTileListDemo())); await expectLater(tester, meetsGuideline(labeledTapTargetGuideline)); handle.dispose(); }); diff --git a/dev/integration_tests/flutter_gallery/test/demo/material/buttons_demo_test.dart b/dev/integration_tests/flutter_gallery/test/demo/material/buttons_demo_test.dart index a219331609989..b4faf83c08f17 100644 --- a/dev/integration_tests/flutter_gallery/test/demo/material/buttons_demo_test.dart +++ b/dev/integration_tests/flutter_gallery/test/demo/material/buttons_demo_test.dart @@ -10,7 +10,7 @@ void main() { testWidgets('Button locations are OK', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/pull/85351 { - await tester.pumpWidget(const MaterialApp(home: ButtonsDemo())); + await tester.pumpWidget(MaterialApp(theme: ThemeData(useMaterial3: false), home: const ButtonsDemo())); expect(find.byType(ElevatedButton).evaluate().length, 2); final Offset topLeft1 = tester.getTopLeft(find.byType(ElevatedButton).first); final Offset topLeft2 = tester.getTopLeft(find.byType(ElevatedButton).last); diff --git a/dev/integration_tests/flutter_gallery/windows/flutter/generated_plugin_registrant.cc b/dev/integration_tests/flutter_gallery/windows/flutter/generated_plugin_registrant.cc deleted file mode 100644 index 4f7884874da74..0000000000000 --- a/dev/integration_tests/flutter_gallery/windows/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,14 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - -#include <url_launcher_windows/url_launcher_windows.h> - -void RegisterPlugins(flutter::PluginRegistry* registry) { - UrlLauncherWindowsRegisterWithRegistrar( - registry->GetRegistrarForPlugin("UrlLauncherWindows")); -} diff --git a/dev/integration_tests/flutter_gallery/windows/flutter/generated_plugin_registrant.h b/dev/integration_tests/flutter_gallery/windows/flutter/generated_plugin_registrant.h deleted file mode 100644 index dc139d85a9310..0000000000000 --- a/dev/integration_tests/flutter_gallery/windows/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include <flutter/plugin_registry.h> - -// Registers Flutter plugins. -void RegisterPlugins(flutter::PluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/dev/integration_tests/flutter_gallery/windows/flutter/generated_plugins.cmake b/dev/integration_tests/flutter_gallery/windows/flutter/generated_plugins.cmake deleted file mode 100644 index 88b22e5c775e5..0000000000000 --- a/dev/integration_tests/flutter_gallery/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,24 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST - url_launcher_windows -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/dev/integration_tests/flutter_gallery/windows/runner/flutter_window.cpp b/dev/integration_tests/flutter_gallery/windows/runner/flutter_window.cpp index 7f66913a0293c..252aa267868b6 100644 --- a/dev/integration_tests/flutter_gallery/windows/runner/flutter_window.cpp +++ b/dev/integration_tests/flutter_gallery/windows/runner/flutter_window.cpp @@ -36,8 +36,8 @@ bool FlutterWindow::OnCreate() { }); // Flutter can complete the first frame before the "show window" callback is - // registered. Ensure a frame is pending to ensure the window is shown. - // This no-ops if the first frame hasn't completed yet. + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; diff --git a/dev/integration_tests/gradle_deprecated_settings/android/app/build.gradle b/dev/integration_tests/gradle_deprecated_settings/android/app/build.gradle index 18cf6ccc8dcdc..52d4cb56f408a 100644 --- a/dev/integration_tests/gradle_deprecated_settings/android/app/build.gradle +++ b/dev/integration_tests/gradle_deprecated_settings/android/app/build.gradle @@ -19,7 +19,7 @@ apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 31 + compileSdkVersion 33 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/dev/integration_tests/gradle_deprecated_settings/android/app/src/main/AndroidManifest.xml b/dev/integration_tests/gradle_deprecated_settings/android/app/src/main/AndroidManifest.xml index 664706aee1380..0207e9357e0b1 100644 --- a/dev/integration_tests/gradle_deprecated_settings/android/app/src/main/AndroidManifest.xml +++ b/dev/integration_tests/gradle_deprecated_settings/android/app/src/main/AndroidManifest.xml @@ -12,6 +12,7 @@ found in the LICENSE file. --> android:label="flavors"> <activity android:name=".MainActivity" + android:exported="true" android:launchMode="singleTop" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" diff --git a/dev/integration_tests/gradle_deprecated_settings/android/build.gradle b/dev/integration_tests/gradle_deprecated_settings/android/build.gradle index 57ace36b3c61b..a2a8866952986 100644 --- a/dev/integration_tests/gradle_deprecated_settings/android/build.gradle +++ b/dev/integration_tests/gradle_deprecated_settings/android/build.gradle @@ -7,14 +7,14 @@ // See dev/tools/bin/generate_gradle_lockfiles.dart. buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/dev/integration_tests/gradle_deprecated_settings/android/buildscript-gradle.lockfile b/dev/integration_tests/gradle_deprecated_settings/android/buildscript-gradle.lockfile index 2577f0aa37e26..eb605802d98f5 100644 --- a/dev/integration_tests/gradle_deprecated_settings/android/buildscript-gradle.lockfile +++ b/dev/integration_tests/gradle_deprecated_settings/android/buildscript-gradle.lockfile @@ -1,120 +1,152 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.databinding:databinding-common:4.1.3=classpath -androidx.databinding:databinding-compiler-common:4.1.3=classpath -com.android.databinding:baseLibrary:4.1.3=classpath -com.android.tools.analytics-library:crash:27.1.3=classpath -com.android.tools.analytics-library:protos:27.1.3=classpath -com.android.tools.analytics-library:shared:27.1.3=classpath -com.android.tools.analytics-library:tracker:27.1.3=classpath -com.android.tools.build.jetifier:jetifier-core:1.0.0-beta09=classpath -com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta09=classpath -com.android.tools.build:aapt2-proto:4.1.3-6503028=classpath -com.android.tools.build:aaptcompiler:4.1.3=classpath -com.android.tools.build:apksig:4.1.3=classpath -com.android.tools.build:apkzlib:4.1.3=classpath -com.android.tools.build:builder-model:4.1.3=classpath -com.android.tools.build:builder-test-api:4.1.3=classpath -com.android.tools.build:builder:4.1.3=classpath -com.android.tools.build:bundletool:0.14.0=classpath -com.android.tools.build:gradle-api:4.1.3=classpath -com.android.tools.build:gradle:4.1.3=classpath -com.android.tools.build:manifest-merger:27.1.3=classpath +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath -com.android.tools.ddms:ddmlib:27.1.3=classpath -com.android.tools.layoutlib:layoutlib-api:27.1.3=classpath -com.android.tools.lint:lint-gradle-api:27.1.3=classpath -com.android.tools.lint:lint-model:27.1.3=classpath -com.android.tools:annotations:27.1.3=classpath -com.android.tools:common:27.1.3=classpath -com.android.tools:dvlib:27.1.3=classpath -com.android.tools:repository:27.1.3=classpath -com.android.tools:sdk-common:27.1.3=classpath -com.android.tools:sdklib:27.1.3=classpath -com.android:signflinger:4.1.3=classpath -com.android:zipflinger:4.1.3=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath com.github.gundy:semver4j:0.16.4=classpath +com.google.android:annotations:4.1.1.4=classpath +com.google.api.grpc:proto-google-common-protos:2.0.1=classpath com.google.auto.value:auto-value-annotations:1.6.2=classpath com.google.code.findbugs:jsr305:3.0.2=classpath -com.google.code.gson:gson:2.8.6=classpath +com.google.code.gson:gson:2.8.9=classpath com.google.crypto.tink:tink:1.3.0-rc2=classpath -com.google.errorprone:error_prone_annotations:2.3.4=classpath +com.google.dagger:dagger:2.28.3=classpath +com.google.errorprone:error_prone_annotations:2.4.0=classpath com.google.flatbuffers:flatbuffers-java:1.12.0=classpath com.google.guava:failureaccess:1.0.1=classpath -com.google.guava:guava:29.0-jre=classpath +com.google.guava:guava:30.1-jre=classpath com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath com.google.j2objc:j2objc-annotations:1.3=classpath com.google.jimfs:jimfs:1.1=classpath -com.google.protobuf:protobuf-java-util:3.10.0=classpath -com.google.protobuf:protobuf-java:3.10.0=classpath -com.google.test.platform:core-proto:0.0.2-dev=classpath +com.google.protobuf:protobuf-java-util:3.17.2=classpath +com.google.protobuf:protobuf-java:3.17.2=classpath +com.google.testing.platform:core-proto:0.0.8-alpha07=classpath com.googlecode.json-simple:json-simple:1.1=classpath com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath com.squareup:javapoet:1.10.0=classpath com.squareup:javawriter:2.5.0=classpath com.sun.activation:javax.activation:1.2.0=classpath -com.sun.istack:istack-commons-runtime:3.0.7=classpath -com.sun.xml.fastinfoset:FastInfoset:1.2.15=classpath -commons-codec:commons-codec:1.10=classpath +com.sun.istack:istack-commons-runtime:3.0.8=classpath +com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath +commons-codec:commons-codec:1.11=classpath commons-io:commons-io:2.4=classpath commons-logging:commons-logging:1.2=classpath de.undercouch:gradle-download-task:4.1.1=classpath -it.unimi.dsi:fastutil:7.2.0=classpath -javax.activation:javax.activation-api:1.2.0=classpath +io.grpc:grpc-api:1.39.0=classpath +io.grpc:grpc-context:1.39.0=classpath +io.grpc:grpc-core:1.39.0=classpath +io.grpc:grpc-netty:1.39.0=classpath +io.grpc:grpc-protobuf-lite:1.39.0=classpath +io.grpc:grpc-protobuf:1.39.0=classpath +io.grpc:grpc-stub:1.39.0=classpath +io.netty:netty-buffer:4.1.52.Final=classpath +io.netty:netty-codec-http2:4.1.52.Final=classpath +io.netty:netty-codec-http:4.1.52.Final=classpath +io.netty:netty-codec-socks:4.1.52.Final=classpath +io.netty:netty-codec:4.1.52.Final=classpath +io.netty:netty-common:4.1.52.Final=classpath +io.netty:netty-handler-proxy:4.1.52.Final=classpath +io.netty:netty-handler:4.1.52.Final=classpath +io.netty:netty-resolver:4.1.52.Final=classpath +io.netty:netty-transport:4.1.52.Final=classpath +io.perfmark:perfmark-api:0.23.0=classpath +it.unimi.dsi:fastutil:8.4.0=classpath +jakarta.activation:jakarta.activation-api:1.2.1=classpath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath javax.inject:javax.inject:1=classpath -javax.xml.bind:jaxb-api:2.3.1=classpath +net.java.dev.jna:jna-platform:5.6.0=classpath +net.java.dev.jna:jna:5.6.0=classpath net.sf.jopt-simple:jopt-simple:4.9=classpath net.sf.kxml:kxml2:2.3.0=classpath -net.sf.proguard:proguard-base:6.0.3=classpath -net.sf.proguard:proguard-gradle:6.0.3=classpath -org.antlr:antlr4:4.5.3=classpath -org.apache.commons:commons-compress:1.12=classpath -org.apache.httpcomponents:httpclient:4.5.6=classpath -org.apache.httpcomponents:httpcore:4.4.10=classpath +org.apache.commons:commons-compress:1.20=classpath +org.apache.httpcomponents:httpclient:4.5.13=classpath +org.apache.httpcomponents:httpcore:4.4.13=classpath org.apache.httpcomponents:httpmime:4.5.6=classpath -org.bouncycastle:bcpkix-jdk15on:1.56=classpath -org.bouncycastle:bcprov-jdk15on:1.56=classpath -org.checkerframework:checker-qual:2.11.1=classpath -org.glassfish.jaxb:jaxb-runtime:2.3.1=classpath -org.glassfish.jaxb:txw2:2.3.1=classpath +org.bitbucket.b_c:jose4j:0.7.0=classpath +org.bouncycastle:bcpkix-jdk15on:1.67=classpath +org.bouncycastle:bcprov-jdk15on:1.67=classpath +org.checkerframework:checker-qual:3.5.0=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.19=classpath +org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath +org.glassfish.jaxb:txw2:2.3.2=classpath org.jdom:jdom2:2.0.6=classpath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=classpath -org.jetbrains.kotlin:kotlin-android-extensions:1.5.31=classpath -org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.5.31=classpath -org.jetbrains.kotlin:kotlin-build-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-runner:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-client:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31=classpath -org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-native-utils:1.5.31=classpath -org.jetbrains.kotlin:kotlin-project-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-reflect:1.3.72=classpath -org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=classpath -org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib:1.3.72=classpath -org.jetbrains.kotlin:kotlin-tooling-metadata:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-io:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-klib:1.5.31=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=classpath -org.jetbrains.trove4j:trove4j:20160824=classpath org.jetbrains:annotations:13.0=classpath org.json:json:20180813=classpath -org.jvnet.staxex:stax-ex:1.8=classpath -org.ow2.asm:asm-analysis:7.0=classpath -org.ow2.asm:asm-commons:7.0=classpath -org.ow2.asm:asm-tree:7.0=classpath -org.ow2.asm:asm-util:7.0=classpath -org.ow2.asm:asm:7.0=classpath -org.tensorflow:tensorflow-lite-metadata:0.1.0-rc1=classpath +org.jvnet.staxex:stax-ex:1.8.1=classpath +org.ow2.asm:asm-analysis:9.1=classpath +org.ow2.asm:asm-commons:9.1=classpath +org.ow2.asm:asm-tree:9.1=classpath +org.ow2.asm:asm-util:9.1=classpath +org.ow2.asm:asm:9.1=classpath +org.slf4j:slf4j-api:1.7.30=classpath +org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath +xerces:xercesImpl:2.12.0=classpath +xml-apis:xml-apis:1.4.01=classpath empty= diff --git a/dev/integration_tests/gradle_deprecated_settings/android/project-app.lockfile b/dev/integration_tests/gradle_deprecated_settings/android/project-app.lockfile index b5b0b745c3536..04eea481fb875 100644 --- a/dev/integration_tests/gradle_deprecated_settings/android/project-app.lockfile +++ b/dev/integration_tests/gradle_deprecated_settings/android/project-app.lockfile @@ -3,7 +3,8 @@ # This file is expected to be part of source control. androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath +androidx.annotation:annotation:1.5.0=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.arch.core:core-common:2.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.arch.core:core-runtime:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -21,8 +22,8 @@ androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugCompi androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window-java:1.0.0-beta03=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window:1.0.0-beta03=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.android.tools.analytics-library:protos:27.1.3=lintClassPath com.android.tools.analytics-library:shared:27.1.3=lintClassPath com.android.tools.analytics-library:tracker:27.1.3=lintClassPath @@ -87,15 +88,21 @@ org.codehaus.groovy:groovy-all:2.4.15=lintClassPath org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath org.glassfish.jaxb:txw2:2.3.1=lintClassPath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugAndroidTestCompileClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -103,8 +110,12 @@ org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -empty=androidApis,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestRuntimeClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestRuntimeClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile diff --git a/dev/integration_tests/gradle_deprecated_settings/android/project-camera_android.lockfile b/dev/integration_tests/gradle_deprecated_settings/android/project-camera_android.lockfile index 7a010b146fb16..360aac4122e64 100644 --- a/dev/integration_tests/gradle_deprecated_settings/android/project-camera_android.lockfile +++ b/dev/integration_tests/gradle_deprecated_settings/android/project-camera_android.lockfile @@ -26,45 +26,117 @@ androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroid androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.almworks.sqlite4java:sqlite4java:1.0.392=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.android.tools.analytics-library:protos:27.1.3=lintClassPath +com.android.tools.analytics-library:shared:27.1.3=lintClassPath +com.android.tools.analytics-library:tracker:27.1.3=lintClassPath +com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524=lintClassPath +com.android.tools.build:aapt2:4.1.3-6503028=_internal_aapt2_binary +com.android.tools.build:apksig:4.1.3=lintClassPath +com.android.tools.build:apkzlib:4.1.3=lintClassPath +com.android.tools.build:builder-model:4.1.3=lintClassPath +com.android.tools.build:builder-test-api:4.1.3=lintClassPath +com.android.tools.build:builder:4.1.3=lintClassPath +com.android.tools.build:gradle-api:4.1.3=lintClassPath +com.android.tools.build:manifest-merger:27.1.3=lintClassPath +com.android.tools.ddms:ddmlib:27.1.3=lintClassPath +com.android.tools.external.com-intellij:intellij-core:27.1.3=lintClassPath +com.android.tools.external.com-intellij:kotlin-compiler:27.1.3=lintClassPath +com.android.tools.external.org-jetbrains:uast:27.1.3=lintClassPath +com.android.tools.layoutlib:layoutlib-api:27.1.3=lintClassPath +com.android.tools.lint:lint-api:27.1.3=lintClassPath +com.android.tools.lint:lint-checks:27.1.3=lintClassPath +com.android.tools.lint:lint-gradle-api:27.1.3=lintClassPath +com.android.tools.lint:lint-gradle:27.1.3=lintClassPath +com.android.tools.lint:lint-model:27.1.3=lintClassPath +com.android.tools.lint:lint:27.1.3=lintClassPath +com.android.tools:annotations:27.1.3=lintClassPath +com.android.tools:common:27.1.3=lintClassPath +com.android.tools:dvlib:27.1.3=lintClassPath +com.android.tools:repository:27.1.3=lintClassPath +com.android.tools:sdk-common:27.1.3=lintClassPath +com.android.tools:sdklib:27.1.3=lintClassPath +com.android:signflinger:4.1.3=lintClassPath +com.android:zipflinger:4.1.3=lintClassPath com.google.auto.value:auto-value-annotations:1.6.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -com.google.code.findbugs:jsr305:3.0.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.code.findbugs:jsr305:3.0.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.code.gson:gson:2.8.5=lintClassPath com.google.errorprone:error_prone_annotations:2.2.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -com.google.guava:failureaccess:1.0.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.3.2=lintClassPath +com.google.guava:failureaccess:1.0.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.google.guava:guava:27.0.1-jre=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:28.1-jre=lintClassPath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.google.j2objc:j2objc-annotations:1.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.j2objc:j2objc-annotations:1.3=lintClassPath +com.google.jimfs:jimfs:1.1=lintClassPath +com.google.protobuf:protobuf-java:3.10.0=lintClassPath +com.googlecode.json-simple:json-simple:1.1=lintClassPath com.ibm.icu:icu4j:53.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.squareup:javawriter:2.5.0=lintClassPath +com.sun.activation:javax.activation:1.2.0=lintClassPath +com.sun.istack:istack-commons-runtime:3.0.7=lintClassPath +com.sun.xml.fastinfoset:FastInfoset:1.2.15=lintClassPath +commons-codec:commons-codec:1.10=lintClassPath +commons-logging:commons-logging:1.2=lintClassPath +it.unimi.dsi:fastutil:7.2.0=lintClassPath +javax.activation:javax.activation-api:1.2.0=lintClassPath javax.annotation:javax.annotation-api:1.3.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -javax.inject:javax.inject:1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +javax.inject:javax.inject:1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +javax.xml.bind:jaxb-api:2.3.1=lintClassPath junit:junit:4.13.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -net.bytebuddy:byte-buddy-agent:1.12.13=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -net.bytebuddy:byte-buddy:1.12.13=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.12.22=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.bytebuddy:byte-buddy:1.12.22=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.sf.jopt-simple:jopt-simple:4.9=lintClassPath +net.sf.kxml:kxml2:2.3.0=lintClassPath +org.apache.commons:commons-compress:1.12=lintClassPath +org.apache.httpcomponents:httpclient:4.5.6=lintClassPath +org.apache.httpcomponents:httpcore:4.4.10=lintClassPath +org.apache.httpcomponents:httpmime:4.5.6=lintClassPath +org.bouncycastle:bcpkix-jdk15on:1.56=lintClassPath +org.bouncycastle:bcprov-jdk15on:1.56=lintClassPath org.bouncycastle:bcprov-jdk15on:1.65=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.checkerframework:checker-qual:2.5.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.checkerframework:checker-qual:2.8.1=lintClassPath +org.codehaus.groovy:groovy-all:2.4.15=lintClassPath org.codehaus.mojo:animal-sniffer-annotations:1.17=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath +org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath +org.glassfish.jaxb:txw2:2.3.1=lintClassPath org.hamcrest:hamcrest-core:1.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath +org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib:1.7.10=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -org.mockito:mockito-core:4.7.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -org.mockito:mockito-inline:4.7.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -org.objenesis:objenesis:3.2=debugUnitTestRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.trove4j:trove4j:20160824=lintClassPath +org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jvnet.staxex:stax-ex:1.8=lintClassPath +org.mockito:mockito-core:5.0.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.mockito:mockito-inline:5.0.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.objenesis:objenesis:3.3=debugUnitTestRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm-analysis:7.0=lintClassPath org.ow2.asm:asm-analysis:9.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.ow2.asm:asm-analysis:9.1=androidJacocoAnt +org.ow2.asm:asm-commons:7.0=lintClassPath org.ow2.asm:asm-commons:9.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.ow2.asm:asm-commons:9.1=androidJacocoAnt +org.ow2.asm:asm-tree:7.0=lintClassPath org.ow2.asm:asm-tree:9.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.ow2.asm:asm-tree:9.1=androidJacocoAnt +org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm-util:9.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm:7.0=lintClassPath org.ow2.asm:asm:9.0=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.ow2.asm:asm:9.1=androidJacocoAnt org.robolectric:annotations:4.5=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -78,4 +150,4 @@ org.robolectric:shadowapi:4.5=debugUnitTestCompileClasspath,debugUnitTestRuntime org.robolectric:shadows-framework:4.5=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.robolectric:utils-reflector:4.5=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.robolectric:utils:4.5=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -empty=androidApis,androidJdkImage,androidTestUtil,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath,testCompile diff --git a/dev/integration_tests/gradle_deprecated_settings/android/project-flutter_plugin_android_lifecycle.lockfile b/dev/integration_tests/gradle_deprecated_settings/android/project-flutter_plugin_android_lifecycle.lockfile index 405d4e2b310f8..6b4d3af60ee23 100644 --- a/dev/integration_tests/gradle_deprecated_settings/android/project-flutter_plugin_android_lifecycle.lockfile +++ b/dev/integration_tests/gradle_deprecated_settings/android/project-flutter_plugin_android_lifecycle.lockfile @@ -3,7 +3,7 @@ # This file is expected to be part of source control. androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.arch.core:core-common:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.arch.core:core-runtime:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -21,8 +21,8 @@ androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugAndro androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window-java:1.0.0-beta03=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window:1.0.0-beta03=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.android.tools.analytics-library:protos:27.1.3=lintClassPath com.android.tools.analytics-library:shared:27.1.3=lintClassPath com.android.tools.analytics-library:tracker:27.1.3=lintClassPath @@ -74,7 +74,9 @@ it.unimi.dsi:fastutil:7.2.0=lintClassPath javax.activation:javax.activation-api:1.2.0=lintClassPath javax.inject:javax.inject:1=lintClassPath javax.xml.bind:jaxb-api:2.3.1=lintClassPath -junit:junit:4.12=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +junit:junit:4.13.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.12.22=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.bytebuddy:byte-buddy:1.12.22=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath net.sf.jopt-simple:jopt-simple:4.9=lintClassPath net.sf.kxml:kxml2:2.3.0=lintClassPath org.apache.commons:commons-compress:1.12=lintClassPath @@ -89,26 +91,34 @@ org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath org.glassfish.jaxb:txw2:2.3.1=lintClassPath org.hamcrest:hamcrest-core:1.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath -org.mockito:mockito-core:1.10.19=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -org.objenesis:objenesis:2.1=debugUnitTestRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.mockito:mockito-core:5.1.1=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.objenesis:objenesis:3.3=debugUnitTestRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestRuntimeClasspath org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -empty=androidApis,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath,testCompile +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath,testCompile diff --git a/dev/integration_tests/gradle_deprecated_settings/android/settings.gradle b/dev/integration_tests/gradle_deprecated_settings/android/settings.gradle index 03ae72d135c01..a020595962d63 100644 --- a/dev/integration_tests/gradle_deprecated_settings/android/settings.gradle +++ b/dev/integration_tests/gradle_deprecated_settings/android/settings.gradle @@ -8,8 +8,6 @@ include ':app' -enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') - def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml b/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml index e57cae496fb63..70db19b854d14 100644 --- a/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml +++ b/dev/integration_tests/gradle_deprecated_settings/pubspec.yaml @@ -7,21 +7,20 @@ environment: dependencies: flutter: sdk: flutter - camera: 0.10.5 - # This is explicit to allow pinning for https://github.com/flutter/flutter/issues/122039 - flutter_plugin_android_lifecycle: 2.0.8 + camera: 0.10.5+2 async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - camera_android: 0.10.8+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - camera_avfoundation: 0.9.13+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - camera_platform_interface: 2.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - camera_web: 0.3.1+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + camera_android: 0.10.8+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + camera_avfoundation: 0.9.13+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + camera_platform_interface: 2.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + camera_web: 0.3.1+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" cross_file: 0.3.3+4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + flutter_plugin_android_lifecycle: 2.0.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -33,10 +32,11 @@ dependencies: stream_transform: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 6b19 +# PUBSPEC CHECKSUM: f3fe diff --git a/dev/integration_tests/hybrid_android_views/android/build.gradle b/dev/integration_tests/hybrid_android_views/android/build.gradle index d3c7bfb8b2403..a2a8866952986 100644 --- a/dev/integration_tests/hybrid_android_views/android/build.gradle +++ b/dev/integration_tests/hybrid_android_views/android/build.gradle @@ -7,14 +7,14 @@ // See dev/tools/bin/generate_gradle_lockfiles.dart. buildscript { - ext.kotlin_version = '1.6.21' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/dev/integration_tests/hybrid_android_views/android/buildscript-gradle.lockfile b/dev/integration_tests/hybrid_android_views/android/buildscript-gradle.lockfile index 814eb1385c4e4..eb605802d98f5 100644 --- a/dev/integration_tests/hybrid_android_views/android/buildscript-gradle.lockfile +++ b/dev/integration_tests/hybrid_android_views/android/buildscript-gradle.lockfile @@ -1,46 +1,45 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.databinding:databinding-common:7.4.2=classpath -androidx.databinding:databinding-compiler-common:7.4.2=classpath -com.android.databinding:baseLibrary:7.4.2=classpath -com.android.tools.analytics-library:crash:30.4.2=classpath -com.android.tools.analytics-library:protos:30.4.2=classpath -com.android.tools.analytics-library:shared:30.4.2=classpath -com.android.tools.analytics-library:tracker:30.4.2=classpath +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath -com.android.tools.build:aapt2-proto:7.4.2-8841542=classpath -com.android.tools.build:aaptcompiler:7.4.2=classpath -com.android.tools.build:apksig:7.4.2=classpath -com.android.tools.build:apkzlib:7.4.2=classpath -com.android.tools.build:builder-model:7.4.2=classpath -com.android.tools.build:builder-test-api:7.4.2=classpath -com.android.tools.build:builder:7.4.2=classpath -com.android.tools.build:bundletool:1.11.4=classpath -com.android.tools.build:gradle-api:7.4.2=classpath -com.android.tools.build:gradle-settings-api:7.4.2=classpath -com.android.tools.build:gradle:7.4.2=classpath -com.android.tools.build:manifest-merger:30.4.2=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath -com.android.tools.ddms:ddmlib:30.4.2=classpath -com.android.tools.layoutlib:layoutlib-api:30.4.2=classpath -com.android.tools.lint:lint-model:30.4.2=classpath -com.android.tools.lint:lint-typedef-remover:30.4.2=classpath -com.android.tools.utp:android-device-provider-ddmlib-proto:30.4.2=classpath -com.android.tools.utp:android-device-provider-gradle-proto:30.4.2=classpath -com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.4.2=classpath -com.android.tools.utp:android-test-plugin-host-coverage-proto:30.4.2=classpath -com.android.tools.utp:android-test-plugin-host-retention-proto:30.4.2=classpath -com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.4.2=classpath -com.android.tools:annotations:30.4.2=classpath -com.android.tools:common:30.4.2=classpath -com.android.tools:dvlib:30.4.2=classpath -com.android.tools:repository:30.4.2=classpath -com.android.tools:sdk-common:30.4.2=classpath -com.android.tools:sdklib:30.4.2=classpath -com.android:signflinger:7.4.2=classpath -com.android:zipflinger:7.4.2=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath com.github.gundy:semver4j:0.16.4=classpath com.google.android:annotations:4.1.1.4=classpath com.google.api.grpc:proto-google-common-protos:2.0.1=classpath @@ -58,7 +57,8 @@ com.google.j2objc:j2objc-annotations:1.3=classpath com.google.jimfs:jimfs:1.1=classpath com.google.protobuf:protobuf-java-util:3.17.2=classpath com.google.protobuf:protobuf-java:3.17.2=classpath -com.google.testing.platform:core-proto:0.0.8-alpha08=classpath +com.google.testing.platform:core-proto:0.0.8-alpha07=classpath +com.googlecode.json-simple:json-simple:1.1=classpath com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath com.squareup:javapoet:1.10.0=classpath com.squareup:javawriter:2.5.0=classpath @@ -87,6 +87,7 @@ io.netty:netty-handler:4.1.52.Final=classpath io.netty:netty-resolver:4.1.52.Final=classpath io.netty:netty-transport:4.1.52.Final=classpath io.perfmark:perfmark-api:0.23.0=classpath +it.unimi.dsi:fastutil:8.4.0=classpath jakarta.activation:jakarta.activation-api:1.2.1=classpath jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath javax.annotation:javax.annotation-api:1.3.2=classpath @@ -108,40 +109,42 @@ org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath org.glassfish.jaxb:txw2:2.3.2=classpath org.jdom:jdom2:2.0.6=classpath org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath -org.jetbrains.kotlin:kotlin-android-extensions:1.6.21=classpath -org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.6.21=classpath -org.jetbrains.kotlin:kotlin-build-common:1.6.21=classpath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.6.21=classpath -org.jetbrains.kotlin:kotlin-compiler-runner:1.6.21=classpath -org.jetbrains.kotlin:kotlin-daemon-client:1.6.21=classpath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.6.21=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.6.21=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.6.21=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21=classpath -org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.6.21=classpath -org.jetbrains.kotlin:kotlin-native-utils:1.6.21=classpath -org.jetbrains.kotlin:kotlin-project-model:1.6.21=classpath -org.jetbrains.kotlin:kotlin-reflect:1.7.10=classpath -org.jetbrains.kotlin:kotlin-scripting-common:1.6.21=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.6.21=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.6.21=classpath -org.jetbrains.kotlin:kotlin-scripting-jvm:1.6.21=classpath -org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=classpath -org.jetbrains.kotlin:kotlin-stdlib:1.7.10=classpath -org.jetbrains.kotlin:kotlin-tooling-metadata:1.6.21=classpath -org.jetbrains.kotlin:kotlin-util-io:1.6.21=classpath -org.jetbrains.kotlin:kotlin-util-klib:1.6.21=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath org.jetbrains:annotations:13.0=classpath org.json:json:20180813=classpath org.jvnet.staxex:stax-ex:1.8.1=classpath -org.ow2.asm:asm-analysis:9.2=classpath -org.ow2.asm:asm-commons:9.2=classpath -org.ow2.asm:asm-tree:9.2=classpath -org.ow2.asm:asm-util:9.2=classpath -org.ow2.asm:asm:9.2=classpath +org.ow2.asm:asm-analysis:9.1=classpath +org.ow2.asm:asm-commons:9.1=classpath +org.ow2.asm:asm-tree:9.1=classpath +org.ow2.asm:asm-util:9.1=classpath +org.ow2.asm:asm:9.1=classpath org.slf4j:slf4j-api:1.7.30=classpath org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath xerces:xercesImpl:2.12.0=classpath diff --git a/dev/integration_tests/hybrid_android_views/android/project-app.lockfile b/dev/integration_tests/hybrid_android_views/android/project-app.lockfile index 9592202769b0a..04eea481fb875 100644 --- a/dev/integration_tests/hybrid_android_views/android/project-app.lockfile +++ b/dev/integration_tests/hybrid_android_views/android/project-app.lockfile @@ -55,14 +55,13 @@ com.android.tools:sdk-common:27.1.3=lintClassPath com.android.tools:sdklib:27.1.3=lintClassPath com.android:signflinger:4.1.3=lintClassPath com.android:zipflinger:4.1.3=lintClassPath -com.google.code.findbugs:jsr305:3.0.2=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.code.findbugs:jsr305:3.0.2=lintClassPath com.google.code.gson:gson:2.8.5=lintClassPath -com.google.errorprone:error_prone_annotations:2.3.2=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath -com.google.guava:failureaccess:1.0.1=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath -com.google.guava:guava:28.1-android=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.3.2=lintClassPath +com.google.guava:failureaccess:1.0.1=lintClassPath com.google.guava:guava:28.1-jre=lintClassPath -com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath -com.google.j2objc:j2objc-annotations:1.3=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=lintClassPath +com.google.j2objc:j2objc-annotations:1.3=lintClassPath com.google.jimfs:jimfs:1.1=lintClassPath com.google.protobuf:protobuf-java:3.10.0=lintClassPath com.googlecode.json-simple:json-simple:1.1=lintClassPath @@ -84,16 +83,15 @@ org.apache.httpcomponents:httpcore:4.4.10=lintClassPath org.apache.httpcomponents:httpmime:4.5.6=lintClassPath org.bouncycastle:bcpkix-jdk15on:1.56=lintClassPath org.bouncycastle:bcprov-jdk15on:1.56=lintClassPath -org.checkerframework:checker-compat-qual:2.5.5=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath org.checkerframework:checker-qual:2.8.1=lintClassPath org.codehaus.groovy:groovy-all:2.4.15=lintClassPath -org.codehaus.mojo:animal-sniffer-annotations:1.18=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath org.glassfish.jaxb:txw2:2.3.1=lintClassPath -org.jacoco:org.jacoco.agent:0.8.8=androidJacocoAnt -org.jacoco:org.jacoco.ant:0.8.8=androidJacocoAnt -org.jacoco:org.jacoco.core:0.8.8=androidJacocoAnt -org.jacoco:org.jacoco.report:0.8.8=androidJacocoAnt +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath @@ -112,12 +110,12 @@ org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath org.ow2.asm:asm-analysis:7.0=lintClassPath -org.ow2.asm:asm-analysis:9.2=androidJacocoAnt +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath -org.ow2.asm:asm-commons:9.2=androidJacocoAnt +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath -org.ow2.asm:asm-tree:9.2=androidJacocoAnt +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -org.ow2.asm:asm:9.2=androidJacocoAnt +org.ow2.asm:asm:9.1=androidJacocoAnt empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestRuntimeClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile diff --git a/dev/integration_tests/hybrid_android_views/android/project-path_provider_android.lockfile b/dev/integration_tests/hybrid_android_views/android/project-path_provider_android.lockfile new file mode 100644 index 0000000000000..2578824551a71 --- /dev/null +++ b/dev/integration_tests/hybrid_android_views/android/project-path_provider_android.lockfile @@ -0,0 +1,44 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core:1.6.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.fragment:fragment:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +junit:junit:4.13.2=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-core:1.3=debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt +org.ow2.asm:asm-commons:9.1=androidJacocoAnt +org.ow2.asm:asm-tree:9.1=androidJacocoAnt +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath diff --git a/dev/integration_tests/hybrid_android_views/android/settings.gradle b/dev/integration_tests/hybrid_android_views/android/settings.gradle index 03ae72d135c01..a020595962d63 100644 --- a/dev/integration_tests/hybrid_android_views/android/settings.gradle +++ b/dev/integration_tests/hybrid_android_views/android/settings.gradle @@ -8,8 +8,6 @@ include ':app' -enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') - def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/dev/integration_tests/hybrid_android_views/pubspec.yaml b/dev/integration_tests/hybrid_android_views/pubspec.yaml index 168c5d69003a4..460a189ce623d 100644 --- a/dev/integration_tests/hybrid_android_views/pubspec.yaml +++ b/dev/integration_tests/hybrid_android_views/pubspec.yaml @@ -24,16 +24,15 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" ffi: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - path_provider_android: 2.0.21 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path_provider_android: 2.0.27 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider_foundation: 2.2.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - path_provider_linux: 2.1.10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path_provider_linux: 2.1.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path_provider_platform_interface: 2.0.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - path_provider_windows: 2.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path_provider_windows: 2.1.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" platform: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" plugin_platform_interface: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" process: 4.2.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -43,32 +42,34 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - win32: 4.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + win32: 5.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" xdg_directories: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -80,7 +81,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -90,4 +91,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: cb10 +# PUBSPEC CHECKSUM: 6876 diff --git a/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml b/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml index 69528bc66411c..ab6b4f34a0f8f 100644 --- a/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml +++ b/dev/integration_tests/ios_add2app_life_cycle/flutterapp/pubspec.yaml @@ -26,10 +26,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -42,14 +42,14 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: # The following line ensures that the Material Icons font is @@ -99,4 +99,4 @@ flutter: androidPackage: com.example.iosadd2appflutter iosBundleIdentifier: com.example.iosAdd2appFlutter -# PUBSPEC CHECKSUM: edc5 +# PUBSPEC CHECKSUM: f5e9 diff --git a/dev/integration_tests/ios_app_with_extensions/ios/Runner/Info.plist b/dev/integration_tests/ios_app_with_extensions/ios/Runner/Info.plist index e8dc95fcff0cf..d6edc2b2190d8 100644 --- a/dev/integration_tests/ios_app_with_extensions/ios/Runner/Info.plist +++ b/dev/integration_tests/ios_app_with_extensions/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/integration_tests/ios_app_with_extensions/pubspec.yaml b/dev/integration_tests/ios_app_with_extensions/pubspec.yaml index 72d3cd391f679..2631ecbf165af 100644 --- a/dev/integration_tests/ios_app_with_extensions/pubspec.yaml +++ b/dev/integration_tests/ios_app_with_extensions/pubspec.yaml @@ -25,11 +25,11 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" device_info_platform_interface: 2.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" plugin_platform_interface: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -44,14 +44,14 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: @@ -91,4 +91,4 @@ flutter: # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages -# PUBSPEC CHECKSUM: b74b +# PUBSPEC CHECKSUM: e86f diff --git a/dev/integration_tests/ios_platform_view_tests/ios/Runner/Info.plist b/dev/integration_tests/ios_platform_view_tests/ios/Runner/Info.plist index 2fb961491d21f..9f9045be206d5 100644 --- a/dev/integration_tests/ios_platform_view_tests/ios/Runner/Info.plist +++ b/dev/integration_tests/ios_platform_view_tests/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>io.flutter.embedded_views_preview</key> <true/> <key>CADisableMinimumFrameDurationOnPhone</key> diff --git a/dev/integration_tests/ios_platform_view_tests/pubspec.yaml b/dev/integration_tests/ios_platform_view_tests/pubspec.yaml index 8bcc8d8deab79..3d03abe456cd1 100644 --- a/dev/integration_tests/ios_platform_view_tests/pubspec.yaml +++ b/dev/integration_tests/ios_platform_view_tests/pubspec.yaml @@ -16,8 +16,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -27,30 +26,32 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -62,7 +63,7 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -76,4 +77,4 @@ flutter: # the material Icons class. uses-material-design: true -# PUBSPEC CHECKSUM: 5030 +# PUBSPEC CHECKSUM: 968e diff --git a/dev/integration_tests/module_host_with_custom_build_v2_embedding/app/build.gradle b/dev/integration_tests/module_host_with_custom_build_v2_embedding/app/build.gradle index f0a37c3fcdff5..4cf5470e824c6 100644 --- a/dev/integration_tests/module_host_with_custom_build_v2_embedding/app/build.gradle +++ b/dev/integration_tests/module_host_with_custom_build_v2_embedding/app/build.gradle @@ -5,7 +5,7 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 31 + compileSdkVersion 33 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/dev/integration_tests/non_nullable/android/app/src/main/AndroidManifest.xml b/dev/integration_tests/non_nullable/android/app/src/main/AndroidManifest.xml index 5ea6187dba2c9..7415fbb6c6392 100644 --- a/dev/integration_tests/non_nullable/android/app/src/main/AndroidManifest.xml +++ b/dev/integration_tests/non_nullable/android/app/src/main/AndroidManifest.xml @@ -15,6 +15,7 @@ found in the LICENSE file. --> android:icon="@mipmap/ic_launcher"> <activity android:name=".MainActivity" + android:exported="true" android:launchMode="singleTop" android:theme="@style/NormalTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" diff --git a/dev/integration_tests/non_nullable/android/build.gradle b/dev/integration_tests/non_nullable/android/build.gradle index 57ace36b3c61b..a2a8866952986 100644 --- a/dev/integration_tests/non_nullable/android/build.gradle +++ b/dev/integration_tests/non_nullable/android/build.gradle @@ -7,14 +7,14 @@ // See dev/tools/bin/generate_gradle_lockfiles.dart. buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/dev/integration_tests/non_nullable/android/buildscript-gradle.lockfile b/dev/integration_tests/non_nullable/android/buildscript-gradle.lockfile index 2577f0aa37e26..eb605802d98f5 100644 --- a/dev/integration_tests/non_nullable/android/buildscript-gradle.lockfile +++ b/dev/integration_tests/non_nullable/android/buildscript-gradle.lockfile @@ -1,120 +1,152 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.databinding:databinding-common:4.1.3=classpath -androidx.databinding:databinding-compiler-common:4.1.3=classpath -com.android.databinding:baseLibrary:4.1.3=classpath -com.android.tools.analytics-library:crash:27.1.3=classpath -com.android.tools.analytics-library:protos:27.1.3=classpath -com.android.tools.analytics-library:shared:27.1.3=classpath -com.android.tools.analytics-library:tracker:27.1.3=classpath -com.android.tools.build.jetifier:jetifier-core:1.0.0-beta09=classpath -com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta09=classpath -com.android.tools.build:aapt2-proto:4.1.3-6503028=classpath -com.android.tools.build:aaptcompiler:4.1.3=classpath -com.android.tools.build:apksig:4.1.3=classpath -com.android.tools.build:apkzlib:4.1.3=classpath -com.android.tools.build:builder-model:4.1.3=classpath -com.android.tools.build:builder-test-api:4.1.3=classpath -com.android.tools.build:builder:4.1.3=classpath -com.android.tools.build:bundletool:0.14.0=classpath -com.android.tools.build:gradle-api:4.1.3=classpath -com.android.tools.build:gradle:4.1.3=classpath -com.android.tools.build:manifest-merger:27.1.3=classpath +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath -com.android.tools.ddms:ddmlib:27.1.3=classpath -com.android.tools.layoutlib:layoutlib-api:27.1.3=classpath -com.android.tools.lint:lint-gradle-api:27.1.3=classpath -com.android.tools.lint:lint-model:27.1.3=classpath -com.android.tools:annotations:27.1.3=classpath -com.android.tools:common:27.1.3=classpath -com.android.tools:dvlib:27.1.3=classpath -com.android.tools:repository:27.1.3=classpath -com.android.tools:sdk-common:27.1.3=classpath -com.android.tools:sdklib:27.1.3=classpath -com.android:signflinger:4.1.3=classpath -com.android:zipflinger:4.1.3=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath com.github.gundy:semver4j:0.16.4=classpath +com.google.android:annotations:4.1.1.4=classpath +com.google.api.grpc:proto-google-common-protos:2.0.1=classpath com.google.auto.value:auto-value-annotations:1.6.2=classpath com.google.code.findbugs:jsr305:3.0.2=classpath -com.google.code.gson:gson:2.8.6=classpath +com.google.code.gson:gson:2.8.9=classpath com.google.crypto.tink:tink:1.3.0-rc2=classpath -com.google.errorprone:error_prone_annotations:2.3.4=classpath +com.google.dagger:dagger:2.28.3=classpath +com.google.errorprone:error_prone_annotations:2.4.0=classpath com.google.flatbuffers:flatbuffers-java:1.12.0=classpath com.google.guava:failureaccess:1.0.1=classpath -com.google.guava:guava:29.0-jre=classpath +com.google.guava:guava:30.1-jre=classpath com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath com.google.j2objc:j2objc-annotations:1.3=classpath com.google.jimfs:jimfs:1.1=classpath -com.google.protobuf:protobuf-java-util:3.10.0=classpath -com.google.protobuf:protobuf-java:3.10.0=classpath -com.google.test.platform:core-proto:0.0.2-dev=classpath +com.google.protobuf:protobuf-java-util:3.17.2=classpath +com.google.protobuf:protobuf-java:3.17.2=classpath +com.google.testing.platform:core-proto:0.0.8-alpha07=classpath com.googlecode.json-simple:json-simple:1.1=classpath com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath com.squareup:javapoet:1.10.0=classpath com.squareup:javawriter:2.5.0=classpath com.sun.activation:javax.activation:1.2.0=classpath -com.sun.istack:istack-commons-runtime:3.0.7=classpath -com.sun.xml.fastinfoset:FastInfoset:1.2.15=classpath -commons-codec:commons-codec:1.10=classpath +com.sun.istack:istack-commons-runtime:3.0.8=classpath +com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath +commons-codec:commons-codec:1.11=classpath commons-io:commons-io:2.4=classpath commons-logging:commons-logging:1.2=classpath de.undercouch:gradle-download-task:4.1.1=classpath -it.unimi.dsi:fastutil:7.2.0=classpath -javax.activation:javax.activation-api:1.2.0=classpath +io.grpc:grpc-api:1.39.0=classpath +io.grpc:grpc-context:1.39.0=classpath +io.grpc:grpc-core:1.39.0=classpath +io.grpc:grpc-netty:1.39.0=classpath +io.grpc:grpc-protobuf-lite:1.39.0=classpath +io.grpc:grpc-protobuf:1.39.0=classpath +io.grpc:grpc-stub:1.39.0=classpath +io.netty:netty-buffer:4.1.52.Final=classpath +io.netty:netty-codec-http2:4.1.52.Final=classpath +io.netty:netty-codec-http:4.1.52.Final=classpath +io.netty:netty-codec-socks:4.1.52.Final=classpath +io.netty:netty-codec:4.1.52.Final=classpath +io.netty:netty-common:4.1.52.Final=classpath +io.netty:netty-handler-proxy:4.1.52.Final=classpath +io.netty:netty-handler:4.1.52.Final=classpath +io.netty:netty-resolver:4.1.52.Final=classpath +io.netty:netty-transport:4.1.52.Final=classpath +io.perfmark:perfmark-api:0.23.0=classpath +it.unimi.dsi:fastutil:8.4.0=classpath +jakarta.activation:jakarta.activation-api:1.2.1=classpath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath javax.inject:javax.inject:1=classpath -javax.xml.bind:jaxb-api:2.3.1=classpath +net.java.dev.jna:jna-platform:5.6.0=classpath +net.java.dev.jna:jna:5.6.0=classpath net.sf.jopt-simple:jopt-simple:4.9=classpath net.sf.kxml:kxml2:2.3.0=classpath -net.sf.proguard:proguard-base:6.0.3=classpath -net.sf.proguard:proguard-gradle:6.0.3=classpath -org.antlr:antlr4:4.5.3=classpath -org.apache.commons:commons-compress:1.12=classpath -org.apache.httpcomponents:httpclient:4.5.6=classpath -org.apache.httpcomponents:httpcore:4.4.10=classpath +org.apache.commons:commons-compress:1.20=classpath +org.apache.httpcomponents:httpclient:4.5.13=classpath +org.apache.httpcomponents:httpcore:4.4.13=classpath org.apache.httpcomponents:httpmime:4.5.6=classpath -org.bouncycastle:bcpkix-jdk15on:1.56=classpath -org.bouncycastle:bcprov-jdk15on:1.56=classpath -org.checkerframework:checker-qual:2.11.1=classpath -org.glassfish.jaxb:jaxb-runtime:2.3.1=classpath -org.glassfish.jaxb:txw2:2.3.1=classpath +org.bitbucket.b_c:jose4j:0.7.0=classpath +org.bouncycastle:bcpkix-jdk15on:1.67=classpath +org.bouncycastle:bcprov-jdk15on:1.67=classpath +org.checkerframework:checker-qual:3.5.0=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.19=classpath +org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath +org.glassfish.jaxb:txw2:2.3.2=classpath org.jdom:jdom2:2.0.6=classpath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=classpath -org.jetbrains.kotlin:kotlin-android-extensions:1.5.31=classpath -org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.5.31=classpath -org.jetbrains.kotlin:kotlin-build-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-runner:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-client:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31=classpath -org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-native-utils:1.5.31=classpath -org.jetbrains.kotlin:kotlin-project-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-reflect:1.3.72=classpath -org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=classpath -org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib:1.3.72=classpath -org.jetbrains.kotlin:kotlin-tooling-metadata:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-io:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-klib:1.5.31=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=classpath -org.jetbrains.trove4j:trove4j:20160824=classpath org.jetbrains:annotations:13.0=classpath org.json:json:20180813=classpath -org.jvnet.staxex:stax-ex:1.8=classpath -org.ow2.asm:asm-analysis:7.0=classpath -org.ow2.asm:asm-commons:7.0=classpath -org.ow2.asm:asm-tree:7.0=classpath -org.ow2.asm:asm-util:7.0=classpath -org.ow2.asm:asm:7.0=classpath -org.tensorflow:tensorflow-lite-metadata:0.1.0-rc1=classpath +org.jvnet.staxex:stax-ex:1.8.1=classpath +org.ow2.asm:asm-analysis:9.1=classpath +org.ow2.asm:asm-commons:9.1=classpath +org.ow2.asm:asm-tree:9.1=classpath +org.ow2.asm:asm-util:9.1=classpath +org.ow2.asm:asm:9.1=classpath +org.slf4j:slf4j-api:1.7.30=classpath +org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath +xerces:xercesImpl:2.12.0=classpath +xml-apis:xml-apis:1.4.01=classpath empty= diff --git a/dev/integration_tests/non_nullable/android/project-app.lockfile b/dev/integration_tests/non_nullable/android/project-app.lockfile index c573760e20686..5e0f607feb0aa 100644 --- a/dev/integration_tests/non_nullable/android/project-app.lockfile +++ b/dev/integration_tests/non_nullable/android/project-app.lockfile @@ -21,8 +21,8 @@ androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugApiDe androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window-java:1.0.0-beta03=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window:1.0.0-beta03=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.android.tools.analytics-library:protos:27.1.3=lintClassPath com.android.tools.analytics-library:shared:27.1.3=lintClassPath com.android.tools.analytics-library:tracker:27.1.3=lintClassPath @@ -74,6 +74,7 @@ it.unimi.dsi:fastutil:7.2.0=lintClassPath javax.activation:javax.activation-api:1.2.0=lintClassPath javax.inject:javax.inject:1=lintClassPath javax.xml.bind:jaxb-api:2.3.1=lintClassPath +net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath net.sf.jopt-simple:jopt-simple:4.9=lintClassPath net.sf.kxml:kxml2:2.3.0=lintClassPath org.apache.commons:commons-compress:1.12=lintClassPath @@ -87,22 +88,29 @@ org.codehaus.groovy:groovy-all:2.4.15=lintClassPath org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath org.glassfish.jaxb:txw2:2.3.1=lintClassPath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.5.31=kotlinKlibCommonizerClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.7.10=kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-reflect:1.5.31=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath -org.jetbrains.kotlin:kotlin-script-runtime:1.5.31=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-reflect:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-script-runtime:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugApiDependenciesMetadata,profileApiDependenciesMetadata,releaseApiDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30=debugApiDependenciesMetadata,profileApiDependenciesMetadata,releaseApiDependenciesMetadata -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlin:kotlin-stdlib:1.3.72=lintClassPath -org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugApiDependenciesMetadata,profileApiDependenciesMetadata,releaseApiDependenciesMetadata +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:atomicfu:0.16.3=debugApiDependenciesMetadata,debugImplementationDependenciesMetadata,profileApiDependenciesMetadata,profileImplementationDependenciesMetadata,releaseApiDependenciesMetadata,releaseImplementationDependenciesMetadata org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -110,8 +118,12 @@ org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugApiDependenciesMetadata,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,lintClassPath,profileApiDependenciesMetadata,profileCompileClasspath,profileImplementationDependenciesMetadata,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseApiDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -empty=androidApis,androidTestApiDependenciesMetadata,androidTestCompileOnlyDependenciesMetadata,androidTestDebugApiDependenciesMetadata,androidTestDebugCompileOnlyDependenciesMetadata,androidTestDebugImplementationDependenciesMetadata,androidTestDebugIntransitiveDependenciesMetadata,androidTestDebugRuntimeOnlyDependenciesMetadata,androidTestImplementationDependenciesMetadata,androidTestIntransitiveDependenciesMetadata,androidTestProfileApiDependenciesMetadata,androidTestProfileCompileOnlyDependenciesMetadata,androidTestProfileImplementationDependenciesMetadata,androidTestProfileIntransitiveDependenciesMetadata,androidTestProfileRuntimeOnlyDependenciesMetadata,androidTestReleaseApiDependenciesMetadata,androidTestReleaseCompileOnlyDependenciesMetadata,androidTestReleaseImplementationDependenciesMetadata,androidTestReleaseIntransitiveDependenciesMetadata,androidTestReleaseRuntimeOnlyDependenciesMetadata,androidTestRuntimeOnlyDependenciesMetadata,androidTestUtil,apiDependenciesMetadata,compile,compileOnlyDependenciesMetadata,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestApiDependenciesMetadata,debugAndroidTestCompileOnlyDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestIntransitiveDependenciesMetadata,debugAndroidTestRuntimeClasspath,debugAndroidTestRuntimeOnlyDependenciesMetadata,debugAnnotationProcessorClasspath,debugCompile,debugCompileOnly,debugCompileOnlyDependenciesMetadata,debugIntransitiveDependenciesMetadata,debugReverseMetadataValues,debugRuntimeOnlyDependenciesMetadata,debugUnitTestAnnotationProcessorClasspath,debugUnitTestApiDependenciesMetadata,debugUnitTestCompileOnlyDependenciesMetadata,debugUnitTestImplementationDependenciesMetadata,debugUnitTestIntransitiveDependenciesMetadata,debugUnitTestRuntimeOnlyDependenciesMetadata,debugWearBundling,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathProfile,kotlinCompilerPluginClasspathProfileUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseUnitTest,kotlinNativeCompilerPluginClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileCompile,profileCompileOnly,profileCompileOnlyDependenciesMetadata,profileIntransitiveDependenciesMetadata,profileReverseMetadataValues,profileRuntimeOnlyDependenciesMetadata,profileUnitTestAnnotationProcessorClasspath,profileUnitTestApiDependenciesMetadata,profileUnitTestCompileOnlyDependenciesMetadata,profileUnitTestImplementationDependenciesMetadata,profileUnitTestIntransitiveDependenciesMetadata,profileUnitTestRuntimeOnlyDependenciesMetadata,profileWearBundling,releaseAnnotationProcessorClasspath,releaseCompile,releaseCompileOnly,releaseCompileOnlyDependenciesMetadata,releaseIntransitiveDependenciesMetadata,releaseReverseMetadataValues,releaseRuntimeOnlyDependenciesMetadata,releaseUnitTestAnnotationProcessorClasspath,releaseUnitTestApiDependenciesMetadata,releaseUnitTestCompileOnlyDependenciesMetadata,releaseUnitTestImplementationDependenciesMetadata,releaseUnitTestIntransitiveDependenciesMetadata,releaseUnitTestRuntimeOnlyDependenciesMetadata,releaseWearBundling,runtimeOnlyDependenciesMetadata,testApiDependenciesMetadata,testCompile,testCompileOnlyDependenciesMetadata,testDebugApiDependenciesMetadata,testDebugCompileOnlyDependenciesMetadata,testDebugImplementationDependenciesMetadata,testDebugIntransitiveDependenciesMetadata,testDebugRuntimeOnlyDependenciesMetadata,testImplementationDependenciesMetadata,testIntransitiveDependenciesMetadata,testProfileApiDependenciesMetadata,testProfileCompileOnlyDependenciesMetadata,testProfileImplementationDependenciesMetadata,testProfileIntransitiveDependenciesMetadata,testProfileRuntimeOnlyDependenciesMetadata,testReleaseApiDependenciesMetadata,testReleaseCompileOnlyDependenciesMetadata,testReleaseImplementationDependenciesMetadata,testReleaseIntransitiveDependenciesMetadata,testReleaseRuntimeOnlyDependenciesMetadata,testRuntimeOnlyDependenciesMetadata +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestApiDependenciesMetadata,androidTestCompileOnlyDependenciesMetadata,androidTestDebugApiDependenciesMetadata,androidTestDebugCompileOnlyDependenciesMetadata,androidTestDebugImplementationDependenciesMetadata,androidTestDebugIntransitiveDependenciesMetadata,androidTestDebugRuntimeOnlyDependenciesMetadata,androidTestImplementationDependenciesMetadata,androidTestIntransitiveDependenciesMetadata,androidTestProfileApiDependenciesMetadata,androidTestProfileCompileOnlyDependenciesMetadata,androidTestProfileImplementationDependenciesMetadata,androidTestProfileIntransitiveDependenciesMetadata,androidTestProfileRuntimeOnlyDependenciesMetadata,androidTestReleaseApiDependenciesMetadata,androidTestReleaseCompileOnlyDependenciesMetadata,androidTestReleaseImplementationDependenciesMetadata,androidTestReleaseIntransitiveDependenciesMetadata,androidTestReleaseRuntimeOnlyDependenciesMetadata,androidTestRuntimeOnlyDependenciesMetadata,androidTestUtil,apiDependenciesMetadata,compile,compileOnlyDependenciesMetadata,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestApiDependenciesMetadata,debugAndroidTestCompileOnlyDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestIntransitiveDependenciesMetadata,debugAndroidTestRuntimeClasspath,debugAndroidTestRuntimeOnlyDependenciesMetadata,debugAnnotationProcessorClasspath,debugCompile,debugCompileOnly,debugCompileOnlyDependenciesMetadata,debugIntransitiveDependenciesMetadata,debugReverseMetadataValues,debugRuntimeOnlyDependenciesMetadata,debugUnitTestAnnotationProcessorClasspath,debugUnitTestApiDependenciesMetadata,debugUnitTestCompileOnlyDependenciesMetadata,debugUnitTestImplementationDependenciesMetadata,debugUnitTestIntransitiveDependenciesMetadata,debugUnitTestRuntimeOnlyDependenciesMetadata,debugWearBundling,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathProfile,kotlinCompilerPluginClasspathProfileUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseUnitTest,kotlinNativeCompilerPluginClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileCompile,profileCompileOnly,profileCompileOnlyDependenciesMetadata,profileIntransitiveDependenciesMetadata,profileReverseMetadataValues,profileRuntimeOnlyDependenciesMetadata,profileUnitTestAnnotationProcessorClasspath,profileUnitTestApiDependenciesMetadata,profileUnitTestCompileOnlyDependenciesMetadata,profileUnitTestImplementationDependenciesMetadata,profileUnitTestIntransitiveDependenciesMetadata,profileUnitTestRuntimeOnlyDependenciesMetadata,profileWearBundling,releaseAnnotationProcessorClasspath,releaseCompile,releaseCompileOnly,releaseCompileOnlyDependenciesMetadata,releaseIntransitiveDependenciesMetadata,releaseReverseMetadataValues,releaseRuntimeOnlyDependenciesMetadata,releaseUnitTestAnnotationProcessorClasspath,releaseUnitTestApiDependenciesMetadata,releaseUnitTestCompileOnlyDependenciesMetadata,releaseUnitTestImplementationDependenciesMetadata,releaseUnitTestIntransitiveDependenciesMetadata,releaseUnitTestRuntimeOnlyDependenciesMetadata,releaseWearBundling,runtimeOnlyDependenciesMetadata,testApiDependenciesMetadata,testCompile,testCompileOnlyDependenciesMetadata,testDebugApiDependenciesMetadata,testDebugCompileOnlyDependenciesMetadata,testDebugImplementationDependenciesMetadata,testDebugIntransitiveDependenciesMetadata,testDebugRuntimeOnlyDependenciesMetadata,testFixturesApiDependenciesMetadata,testFixturesCompileOnlyDependenciesMetadata,testFixturesDebugApiDependenciesMetadata,testFixturesDebugCompileOnlyDependenciesMetadata,testFixturesDebugImplementationDependenciesMetadata,testFixturesDebugIntransitiveDependenciesMetadata,testFixturesDebugRuntimeOnlyDependenciesMetadata,testFixturesImplementationDependenciesMetadata,testFixturesIntransitiveDependenciesMetadata,testFixturesProfileApiDependenciesMetadata,testFixturesProfileCompileOnlyDependenciesMetadata,testFixturesProfileImplementationDependenciesMetadata,testFixturesProfileIntransitiveDependenciesMetadata,testFixturesProfileRuntimeOnlyDependenciesMetadata,testFixturesReleaseApiDependenciesMetadata,testFixturesReleaseCompileOnlyDependenciesMetadata,testFixturesReleaseImplementationDependenciesMetadata,testFixturesReleaseIntransitiveDependenciesMetadata,testFixturesReleaseRuntimeOnlyDependenciesMetadata,testFixturesRuntimeOnlyDependenciesMetadata,testImplementationDependenciesMetadata,testIntransitiveDependenciesMetadata,testProfileApiDependenciesMetadata,testProfileCompileOnlyDependenciesMetadata,testProfileImplementationDependenciesMetadata,testProfileIntransitiveDependenciesMetadata,testProfileRuntimeOnlyDependenciesMetadata,testReleaseApiDependenciesMetadata,testReleaseCompileOnlyDependenciesMetadata,testReleaseImplementationDependenciesMetadata,testReleaseIntransitiveDependenciesMetadata,testReleaseRuntimeOnlyDependenciesMetadata,testRuntimeOnlyDependenciesMetadata diff --git a/dev/integration_tests/non_nullable/android/settings.gradle b/dev/integration_tests/non_nullable/android/settings.gradle index 03ae72d135c01..a020595962d63 100644 --- a/dev/integration_tests/non_nullable/android/settings.gradle +++ b/dev/integration_tests/non_nullable/android/settings.gradle @@ -8,8 +8,6 @@ include ':app' -enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') - def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/dev/integration_tests/non_nullable/ios/Runner/Info.plist b/dev/integration_tests/non_nullable/ios/Runner/Info.plist index 132b9d5d6ffd0..bbe2edc4f7869 100644 --- a/dev/integration_tests/non_nullable/ios/Runner/Info.plist +++ b/dev/integration_tests/non_nullable/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/integration_tests/non_nullable/pubspec.yaml b/dev/integration_tests/non_nullable/pubspec.yaml index 5c3e065883041..b1d61f8cec408 100644 --- a/dev/integration_tests/non_nullable/pubspec.yaml +++ b/dev/integration_tests/non_nullable/pubspec.yaml @@ -14,10 +14,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -27,16 +27,16 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: edc5 +# PUBSPEC CHECKSUM: f5e9 diff --git a/dev/integration_tests/platform_interaction/android/build.gradle b/dev/integration_tests/platform_interaction/android/build.gradle index d3c7bfb8b2403..a2a8866952986 100644 --- a/dev/integration_tests/platform_interaction/android/build.gradle +++ b/dev/integration_tests/platform_interaction/android/build.gradle @@ -7,14 +7,14 @@ // See dev/tools/bin/generate_gradle_lockfiles.dart. buildscript { - ext.kotlin_version = '1.6.21' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/dev/integration_tests/platform_interaction/android/buildscript-gradle.lockfile b/dev/integration_tests/platform_interaction/android/buildscript-gradle.lockfile index 814eb1385c4e4..eb605802d98f5 100644 --- a/dev/integration_tests/platform_interaction/android/buildscript-gradle.lockfile +++ b/dev/integration_tests/platform_interaction/android/buildscript-gradle.lockfile @@ -1,46 +1,45 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.databinding:databinding-common:7.4.2=classpath -androidx.databinding:databinding-compiler-common:7.4.2=classpath -com.android.databinding:baseLibrary:7.4.2=classpath -com.android.tools.analytics-library:crash:30.4.2=classpath -com.android.tools.analytics-library:protos:30.4.2=classpath -com.android.tools.analytics-library:shared:30.4.2=classpath -com.android.tools.analytics-library:tracker:30.4.2=classpath +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath -com.android.tools.build:aapt2-proto:7.4.2-8841542=classpath -com.android.tools.build:aaptcompiler:7.4.2=classpath -com.android.tools.build:apksig:7.4.2=classpath -com.android.tools.build:apkzlib:7.4.2=classpath -com.android.tools.build:builder-model:7.4.2=classpath -com.android.tools.build:builder-test-api:7.4.2=classpath -com.android.tools.build:builder:7.4.2=classpath -com.android.tools.build:bundletool:1.11.4=classpath -com.android.tools.build:gradle-api:7.4.2=classpath -com.android.tools.build:gradle-settings-api:7.4.2=classpath -com.android.tools.build:gradle:7.4.2=classpath -com.android.tools.build:manifest-merger:30.4.2=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath -com.android.tools.ddms:ddmlib:30.4.2=classpath -com.android.tools.layoutlib:layoutlib-api:30.4.2=classpath -com.android.tools.lint:lint-model:30.4.2=classpath -com.android.tools.lint:lint-typedef-remover:30.4.2=classpath -com.android.tools.utp:android-device-provider-ddmlib-proto:30.4.2=classpath -com.android.tools.utp:android-device-provider-gradle-proto:30.4.2=classpath -com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.4.2=classpath -com.android.tools.utp:android-test-plugin-host-coverage-proto:30.4.2=classpath -com.android.tools.utp:android-test-plugin-host-retention-proto:30.4.2=classpath -com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.4.2=classpath -com.android.tools:annotations:30.4.2=classpath -com.android.tools:common:30.4.2=classpath -com.android.tools:dvlib:30.4.2=classpath -com.android.tools:repository:30.4.2=classpath -com.android.tools:sdk-common:30.4.2=classpath -com.android.tools:sdklib:30.4.2=classpath -com.android:signflinger:7.4.2=classpath -com.android:zipflinger:7.4.2=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath com.github.gundy:semver4j:0.16.4=classpath com.google.android:annotations:4.1.1.4=classpath com.google.api.grpc:proto-google-common-protos:2.0.1=classpath @@ -58,7 +57,8 @@ com.google.j2objc:j2objc-annotations:1.3=classpath com.google.jimfs:jimfs:1.1=classpath com.google.protobuf:protobuf-java-util:3.17.2=classpath com.google.protobuf:protobuf-java:3.17.2=classpath -com.google.testing.platform:core-proto:0.0.8-alpha08=classpath +com.google.testing.platform:core-proto:0.0.8-alpha07=classpath +com.googlecode.json-simple:json-simple:1.1=classpath com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath com.squareup:javapoet:1.10.0=classpath com.squareup:javawriter:2.5.0=classpath @@ -87,6 +87,7 @@ io.netty:netty-handler:4.1.52.Final=classpath io.netty:netty-resolver:4.1.52.Final=classpath io.netty:netty-transport:4.1.52.Final=classpath io.perfmark:perfmark-api:0.23.0=classpath +it.unimi.dsi:fastutil:8.4.0=classpath jakarta.activation:jakarta.activation-api:1.2.1=classpath jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath javax.annotation:javax.annotation-api:1.3.2=classpath @@ -108,40 +109,42 @@ org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath org.glassfish.jaxb:txw2:2.3.2=classpath org.jdom:jdom2:2.0.6=classpath org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath -org.jetbrains.kotlin:kotlin-android-extensions:1.6.21=classpath -org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.6.21=classpath -org.jetbrains.kotlin:kotlin-build-common:1.6.21=classpath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.6.21=classpath -org.jetbrains.kotlin:kotlin-compiler-runner:1.6.21=classpath -org.jetbrains.kotlin:kotlin-daemon-client:1.6.21=classpath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.6.21=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.6.21=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.6.21=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21=classpath -org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.6.21=classpath -org.jetbrains.kotlin:kotlin-native-utils:1.6.21=classpath -org.jetbrains.kotlin:kotlin-project-model:1.6.21=classpath -org.jetbrains.kotlin:kotlin-reflect:1.7.10=classpath -org.jetbrains.kotlin:kotlin-scripting-common:1.6.21=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.6.21=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.6.21=classpath -org.jetbrains.kotlin:kotlin-scripting-jvm:1.6.21=classpath -org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10=classpath -org.jetbrains.kotlin:kotlin-stdlib:1.7.10=classpath -org.jetbrains.kotlin:kotlin-tooling-metadata:1.6.21=classpath -org.jetbrains.kotlin:kotlin-util-io:1.6.21=classpath -org.jetbrains.kotlin:kotlin-util-klib:1.6.21=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath org.jetbrains:annotations:13.0=classpath org.json:json:20180813=classpath org.jvnet.staxex:stax-ex:1.8.1=classpath -org.ow2.asm:asm-analysis:9.2=classpath -org.ow2.asm:asm-commons:9.2=classpath -org.ow2.asm:asm-tree:9.2=classpath -org.ow2.asm:asm-util:9.2=classpath -org.ow2.asm:asm:9.2=classpath +org.ow2.asm:asm-analysis:9.1=classpath +org.ow2.asm:asm-commons:9.1=classpath +org.ow2.asm:asm-tree:9.1=classpath +org.ow2.asm:asm-util:9.1=classpath +org.ow2.asm:asm:9.1=classpath org.slf4j:slf4j-api:1.7.30=classpath org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath xerces:xercesImpl:2.12.0=classpath diff --git a/dev/integration_tests/platform_interaction/android/project-app.lockfile b/dev/integration_tests/platform_interaction/android/project-app.lockfile index 0d87887fa38ec..e160a81ffd42a 100644 --- a/dev/integration_tests/platform_interaction/android/project-app.lockfile +++ b/dev/integration_tests/platform_interaction/android/project-app.lockfile @@ -87,10 +87,10 @@ org.codehaus.groovy:groovy-all:2.4.15=lintClassPath org.codehaus.mojo:animal-sniffer-annotations:1.18=lintClassPath org.glassfish.jaxb:jaxb-runtime:2.3.1=lintClassPath org.glassfish.jaxb:txw2:2.3.1=lintClassPath -org.jacoco:org.jacoco.agent:0.8.8=androidJacocoAnt -org.jacoco:org.jacoco.ant:0.8.8=androidJacocoAnt -org.jacoco:org.jacoco.core:0.8.8=androidJacocoAnt -org.jacoco:org.jacoco.report:0.8.8=androidJacocoAnt +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -107,12 +107,12 @@ org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath org.ow2.asm:asm-analysis:7.0=lintClassPath -org.ow2.asm:asm-analysis:9.2=androidJacocoAnt +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath -org.ow2.asm:asm-commons:9.2=androidJacocoAnt +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath -org.ow2.asm:asm-tree:9.2=androidJacocoAnt +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -org.ow2.asm:asm:9.2=androidJacocoAnt +org.ow2.asm:asm:9.1=androidJacocoAnt empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestRuntimeClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile diff --git a/dev/integration_tests/platform_interaction/android/settings.gradle b/dev/integration_tests/platform_interaction/android/settings.gradle index 03ae72d135c01..a020595962d63 100644 --- a/dev/integration_tests/platform_interaction/android/settings.gradle +++ b/dev/integration_tests/platform_interaction/android/settings.gradle @@ -8,8 +8,6 @@ include ':app' -enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') - def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/dev/integration_tests/platform_interaction/ios/Runner/Info.plist b/dev/integration_tests/platform_interaction/ios/Runner/Info.plist index 35bbdde377cd5..f979810dad779 100644 --- a/dev/integration_tests/platform_interaction/ios/Runner/Info.plist +++ b/dev/integration_tests/platform_interaction/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/integration_tests/platform_interaction/pubspec.yaml b/dev/integration_tests/platform_interaction/pubspec.yaml index f3ef4882c7c60..44875f9bbdad5 100644 --- a/dev/integration_tests/platform_interaction/pubspec.yaml +++ b/dev/integration_tests/platform_interaction/pubspec.yaml @@ -9,11 +9,11 @@ dependencies: sdk: flutter flutter_driver: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -23,13 +23,13 @@ dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -50,12 +50,13 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -64,4 +65,4 @@ dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: c373 +# PUBSPEC CHECKSUM: 3dd1 diff --git a/dev/integration_tests/release_smoke_test/android/app/src/main/AndroidManifest.xml b/dev/integration_tests/release_smoke_test/android/app/src/main/AndroidManifest.xml index 63a89a01652c6..b406c6d784e07 100644 --- a/dev/integration_tests/release_smoke_test/android/app/src/main/AndroidManifest.xml +++ b/dev/integration_tests/release_smoke_test/android/app/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ found in the LICENSE file. --> <activity android:name=".MainActivity" android:launchMode="singleTop" + android:exported="true" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" diff --git a/dev/integration_tests/release_smoke_test/android/build.gradle b/dev/integration_tests/release_smoke_test/android/build.gradle index 57ace36b3c61b..a2a8866952986 100644 --- a/dev/integration_tests/release_smoke_test/android/build.gradle +++ b/dev/integration_tests/release_smoke_test/android/build.gradle @@ -7,14 +7,14 @@ // See dev/tools/bin/generate_gradle_lockfiles.dart. buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/dev/integration_tests/release_smoke_test/android/buildscript-gradle.lockfile b/dev/integration_tests/release_smoke_test/android/buildscript-gradle.lockfile index 2577f0aa37e26..eb605802d98f5 100644 --- a/dev/integration_tests/release_smoke_test/android/buildscript-gradle.lockfile +++ b/dev/integration_tests/release_smoke_test/android/buildscript-gradle.lockfile @@ -1,120 +1,152 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.databinding:databinding-common:4.1.3=classpath -androidx.databinding:databinding-compiler-common:4.1.3=classpath -com.android.databinding:baseLibrary:4.1.3=classpath -com.android.tools.analytics-library:crash:27.1.3=classpath -com.android.tools.analytics-library:protos:27.1.3=classpath -com.android.tools.analytics-library:shared:27.1.3=classpath -com.android.tools.analytics-library:tracker:27.1.3=classpath -com.android.tools.build.jetifier:jetifier-core:1.0.0-beta09=classpath -com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta09=classpath -com.android.tools.build:aapt2-proto:4.1.3-6503028=classpath -com.android.tools.build:aaptcompiler:4.1.3=classpath -com.android.tools.build:apksig:4.1.3=classpath -com.android.tools.build:apkzlib:4.1.3=classpath -com.android.tools.build:builder-model:4.1.3=classpath -com.android.tools.build:builder-test-api:4.1.3=classpath -com.android.tools.build:builder:4.1.3=classpath -com.android.tools.build:bundletool:0.14.0=classpath -com.android.tools.build:gradle-api:4.1.3=classpath -com.android.tools.build:gradle:4.1.3=classpath -com.android.tools.build:manifest-merger:27.1.3=classpath +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath -com.android.tools.ddms:ddmlib:27.1.3=classpath -com.android.tools.layoutlib:layoutlib-api:27.1.3=classpath -com.android.tools.lint:lint-gradle-api:27.1.3=classpath -com.android.tools.lint:lint-model:27.1.3=classpath -com.android.tools:annotations:27.1.3=classpath -com.android.tools:common:27.1.3=classpath -com.android.tools:dvlib:27.1.3=classpath -com.android.tools:repository:27.1.3=classpath -com.android.tools:sdk-common:27.1.3=classpath -com.android.tools:sdklib:27.1.3=classpath -com.android:signflinger:4.1.3=classpath -com.android:zipflinger:4.1.3=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath com.github.gundy:semver4j:0.16.4=classpath +com.google.android:annotations:4.1.1.4=classpath +com.google.api.grpc:proto-google-common-protos:2.0.1=classpath com.google.auto.value:auto-value-annotations:1.6.2=classpath com.google.code.findbugs:jsr305:3.0.2=classpath -com.google.code.gson:gson:2.8.6=classpath +com.google.code.gson:gson:2.8.9=classpath com.google.crypto.tink:tink:1.3.0-rc2=classpath -com.google.errorprone:error_prone_annotations:2.3.4=classpath +com.google.dagger:dagger:2.28.3=classpath +com.google.errorprone:error_prone_annotations:2.4.0=classpath com.google.flatbuffers:flatbuffers-java:1.12.0=classpath com.google.guava:failureaccess:1.0.1=classpath -com.google.guava:guava:29.0-jre=classpath +com.google.guava:guava:30.1-jre=classpath com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath com.google.j2objc:j2objc-annotations:1.3=classpath com.google.jimfs:jimfs:1.1=classpath -com.google.protobuf:protobuf-java-util:3.10.0=classpath -com.google.protobuf:protobuf-java:3.10.0=classpath -com.google.test.platform:core-proto:0.0.2-dev=classpath +com.google.protobuf:protobuf-java-util:3.17.2=classpath +com.google.protobuf:protobuf-java:3.17.2=classpath +com.google.testing.platform:core-proto:0.0.8-alpha07=classpath com.googlecode.json-simple:json-simple:1.1=classpath com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath com.squareup:javapoet:1.10.0=classpath com.squareup:javawriter:2.5.0=classpath com.sun.activation:javax.activation:1.2.0=classpath -com.sun.istack:istack-commons-runtime:3.0.7=classpath -com.sun.xml.fastinfoset:FastInfoset:1.2.15=classpath -commons-codec:commons-codec:1.10=classpath +com.sun.istack:istack-commons-runtime:3.0.8=classpath +com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath +commons-codec:commons-codec:1.11=classpath commons-io:commons-io:2.4=classpath commons-logging:commons-logging:1.2=classpath de.undercouch:gradle-download-task:4.1.1=classpath -it.unimi.dsi:fastutil:7.2.0=classpath -javax.activation:javax.activation-api:1.2.0=classpath +io.grpc:grpc-api:1.39.0=classpath +io.grpc:grpc-context:1.39.0=classpath +io.grpc:grpc-core:1.39.0=classpath +io.grpc:grpc-netty:1.39.0=classpath +io.grpc:grpc-protobuf-lite:1.39.0=classpath +io.grpc:grpc-protobuf:1.39.0=classpath +io.grpc:grpc-stub:1.39.0=classpath +io.netty:netty-buffer:4.1.52.Final=classpath +io.netty:netty-codec-http2:4.1.52.Final=classpath +io.netty:netty-codec-http:4.1.52.Final=classpath +io.netty:netty-codec-socks:4.1.52.Final=classpath +io.netty:netty-codec:4.1.52.Final=classpath +io.netty:netty-common:4.1.52.Final=classpath +io.netty:netty-handler-proxy:4.1.52.Final=classpath +io.netty:netty-handler:4.1.52.Final=classpath +io.netty:netty-resolver:4.1.52.Final=classpath +io.netty:netty-transport:4.1.52.Final=classpath +io.perfmark:perfmark-api:0.23.0=classpath +it.unimi.dsi:fastutil:8.4.0=classpath +jakarta.activation:jakarta.activation-api:1.2.1=classpath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath javax.inject:javax.inject:1=classpath -javax.xml.bind:jaxb-api:2.3.1=classpath +net.java.dev.jna:jna-platform:5.6.0=classpath +net.java.dev.jna:jna:5.6.0=classpath net.sf.jopt-simple:jopt-simple:4.9=classpath net.sf.kxml:kxml2:2.3.0=classpath -net.sf.proguard:proguard-base:6.0.3=classpath -net.sf.proguard:proguard-gradle:6.0.3=classpath -org.antlr:antlr4:4.5.3=classpath -org.apache.commons:commons-compress:1.12=classpath -org.apache.httpcomponents:httpclient:4.5.6=classpath -org.apache.httpcomponents:httpcore:4.4.10=classpath +org.apache.commons:commons-compress:1.20=classpath +org.apache.httpcomponents:httpclient:4.5.13=classpath +org.apache.httpcomponents:httpcore:4.4.13=classpath org.apache.httpcomponents:httpmime:4.5.6=classpath -org.bouncycastle:bcpkix-jdk15on:1.56=classpath -org.bouncycastle:bcprov-jdk15on:1.56=classpath -org.checkerframework:checker-qual:2.11.1=classpath -org.glassfish.jaxb:jaxb-runtime:2.3.1=classpath -org.glassfish.jaxb:txw2:2.3.1=classpath +org.bitbucket.b_c:jose4j:0.7.0=classpath +org.bouncycastle:bcpkix-jdk15on:1.67=classpath +org.bouncycastle:bcprov-jdk15on:1.67=classpath +org.checkerframework:checker-qual:3.5.0=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.19=classpath +org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath +org.glassfish.jaxb:txw2:2.3.2=classpath org.jdom:jdom2:2.0.6=classpath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=classpath -org.jetbrains.kotlin:kotlin-android-extensions:1.5.31=classpath -org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.5.31=classpath -org.jetbrains.kotlin:kotlin-build-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-runner:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-client:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31=classpath -org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-native-utils:1.5.31=classpath -org.jetbrains.kotlin:kotlin-project-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-reflect:1.3.72=classpath -org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=classpath -org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72=classpath -org.jetbrains.kotlin:kotlin-stdlib:1.3.72=classpath -org.jetbrains.kotlin:kotlin-tooling-metadata:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-io:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-klib:1.5.31=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=classpath -org.jetbrains.trove4j:trove4j:20160824=classpath org.jetbrains:annotations:13.0=classpath org.json:json:20180813=classpath -org.jvnet.staxex:stax-ex:1.8=classpath -org.ow2.asm:asm-analysis:7.0=classpath -org.ow2.asm:asm-commons:7.0=classpath -org.ow2.asm:asm-tree:7.0=classpath -org.ow2.asm:asm-util:7.0=classpath -org.ow2.asm:asm:7.0=classpath -org.tensorflow:tensorflow-lite-metadata:0.1.0-rc1=classpath +org.jvnet.staxex:stax-ex:1.8.1=classpath +org.ow2.asm:asm-analysis:9.1=classpath +org.ow2.asm:asm-commons:9.1=classpath +org.ow2.asm:asm-tree:9.1=classpath +org.ow2.asm:asm-util:9.1=classpath +org.ow2.asm:asm:9.1=classpath +org.slf4j:slf4j-api:1.7.30=classpath +org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath +xerces:xercesImpl:2.12.0=classpath +xml-apis:xml-apis:1.4.01=classpath empty= diff --git a/dev/integration_tests/release_smoke_test/android/project-app.lockfile b/dev/integration_tests/release_smoke_test/android/project-app.lockfile index 26224567600a1..f496f6d84a592 100644 --- a/dev/integration_tests/release_smoke_test/android/project-app.lockfile +++ b/dev/integration_tests/release_smoke_test/android/project-app.lockfile @@ -26,8 +26,8 @@ androidx.test:runner:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRunt androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window-java:1.0.0-beta03=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window:1.0.0-beta03=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.android.tools.analytics-library:protos:27.1.3=lintClassPath com.android.tools.analytics-library:shared:27.1.3=lintClassPath com.android.tools.analytics-library:tracker:27.1.3=lintClassPath @@ -100,6 +100,10 @@ org.glassfish.jaxb:txw2:2.3.1=lintClassPath org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.hamcrest:hamcrest-integration:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -116,8 +120,12 @@ org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -empty=androidApis,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,debugWearBundling,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileReverseMetadataValues,profileUnitTestAnnotationProcessorClasspath,profileWearBundling,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues,releaseUnitTestAnnotationProcessorClasspath,releaseWearBundling,testCompile diff --git a/dev/integration_tests/release_smoke_test/android/project-integration_test.lockfile b/dev/integration_tests/release_smoke_test/android/project-integration_test.lockfile index ca99c9e81b5be..79f963cac4aa2 100644 --- a/dev/integration_tests/release_smoke_test/android/project-integration_test.lockfile +++ b/dev/integration_tests/release_smoke_test/android/project-integration_test.lockfile @@ -26,8 +26,8 @@ androidx.test:runner:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRunt androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window-java:1.0.0-beta03=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath -androidx.window:window:1.0.0-beta03=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath com.android.tools.analytics-library:protos:27.1.3=lintClassPath com.android.tools.analytics-library:shared:27.1.3=lintClassPath com.android.tools.analytics-library:tracker:27.1.3=lintClassPath @@ -99,6 +99,10 @@ org.glassfish.jaxb:txw2:2.3.1=lintClassPath org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.hamcrest:hamcrest-integration:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt org.jetbrains.kotlin:kotlin-reflect:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72=lintClassPath org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath @@ -115,8 +119,12 @@ org.jetbrains.trove4j:trove4j:20160824=lintClassPath org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,lintClassPath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath org.jvnet.staxex:stax-ex:1.8=lintClassPath org.ow2.asm:asm-analysis:7.0=lintClassPath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt org.ow2.asm:asm-commons:7.0=lintClassPath +org.ow2.asm:asm-commons:9.1=androidJacocoAnt org.ow2.asm:asm-tree:7.0=lintClassPath +org.ow2.asm:asm-tree:9.1=androidJacocoAnt org.ow2.asm:asm-util:7.0=lintClassPath org.ow2.asm:asm:7.0=lintClassPath -empty=androidApis,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath,testCompile +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,compile,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath,testCompile diff --git a/dev/integration_tests/release_smoke_test/android/settings.gradle b/dev/integration_tests/release_smoke_test/android/settings.gradle index 03ae72d135c01..a020595962d63 100644 --- a/dev/integration_tests/release_smoke_test/android/settings.gradle +++ b/dev/integration_tests/release_smoke_test/android/settings.gradle @@ -8,8 +8,6 @@ include ':app' -enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') - def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/dev/integration_tests/release_smoke_test/ios/Runner/Info.plist b/dev/integration_tests/release_smoke_test/ios/Runner/Info.plist index b6f13eced9d26..36a79faffd5d4 100644 --- a/dev/integration_tests/release_smoke_test/ios/Runner/Info.plist +++ b/dev/integration_tests/release_smoke_test/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/integration_tests/release_smoke_test/pubspec.yaml b/dev/integration_tests/release_smoke_test/pubspec.yaml index 64b71f89acd21..7426fb46cd40e 100644 --- a/dev/integration_tests/release_smoke_test/pubspec.yaml +++ b/dev/integration_tests/release_smoke_test/pubspec.yaml @@ -9,10 +9,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -24,14 +24,14 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: e9d4 +# PUBSPEC CHECKSUM: b2fa diff --git a/dev/integration_tests/spell_check/android/build.gradle b/dev/integration_tests/spell_check/android/build.gradle index b843584d52321..a2a8866952986 100644 --- a/dev/integration_tests/spell_check/android/build.gradle +++ b/dev/integration_tests/spell_check/android/build.gradle @@ -2,17 +2,25 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This file is auto generated. +// To update all the build.gradle files in the Flutter repo, +// See dev/tools/bin/generate_gradle_lockfiles.dart. + buildscript { - ext.kotlin_version = '1.6.10' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.1.2' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } + + configurations.classpath { + resolutionStrategy.activateDependencyLocking() + } } allprojects { @@ -23,11 +31,19 @@ allprojects { } rootProject.buildDir = '../build' + subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') + dependencyLocking { + ignoredDependencies.add('io.flutter:*') + lockFile = file("${rootProject.projectDir}/project-${project.name}.lockfile") + if (!project.hasProperty('local-engine-repo')) { + lockAllConfigurations() + } + } } tasks.register("clean", Delete) { diff --git a/dev/integration_tests/spell_check/android/buildscript-gradle.lockfile b/dev/integration_tests/spell_check/android/buildscript-gradle.lockfile new file mode 100644 index 0000000000000..eb605802d98f5 --- /dev/null +++ b/dev/integration_tests/spell_check/android/buildscript-gradle.lockfile @@ -0,0 +1,152 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath +com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath +com.github.gundy:semver4j:0.16.4=classpath +com.google.android:annotations:4.1.1.4=classpath +com.google.api.grpc:proto-google-common-protos:2.0.1=classpath +com.google.auto.value:auto-value-annotations:1.6.2=classpath +com.google.code.findbugs:jsr305:3.0.2=classpath +com.google.code.gson:gson:2.8.9=classpath +com.google.crypto.tink:tink:1.3.0-rc2=classpath +com.google.dagger:dagger:2.28.3=classpath +com.google.errorprone:error_prone_annotations:2.4.0=classpath +com.google.flatbuffers:flatbuffers-java:1.12.0=classpath +com.google.guava:failureaccess:1.0.1=classpath +com.google.guava:guava:30.1-jre=classpath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath +com.google.j2objc:j2objc-annotations:1.3=classpath +com.google.jimfs:jimfs:1.1=classpath +com.google.protobuf:protobuf-java-util:3.17.2=classpath +com.google.protobuf:protobuf-java:3.17.2=classpath +com.google.testing.platform:core-proto:0.0.8-alpha07=classpath +com.googlecode.json-simple:json-simple:1.1=classpath +com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath +com.squareup:javapoet:1.10.0=classpath +com.squareup:javawriter:2.5.0=classpath +com.sun.activation:javax.activation:1.2.0=classpath +com.sun.istack:istack-commons-runtime:3.0.8=classpath +com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath +commons-codec:commons-codec:1.11=classpath +commons-io:commons-io:2.4=classpath +commons-logging:commons-logging:1.2=classpath +de.undercouch:gradle-download-task:4.1.1=classpath +io.grpc:grpc-api:1.39.0=classpath +io.grpc:grpc-context:1.39.0=classpath +io.grpc:grpc-core:1.39.0=classpath +io.grpc:grpc-netty:1.39.0=classpath +io.grpc:grpc-protobuf-lite:1.39.0=classpath +io.grpc:grpc-protobuf:1.39.0=classpath +io.grpc:grpc-stub:1.39.0=classpath +io.netty:netty-buffer:4.1.52.Final=classpath +io.netty:netty-codec-http2:4.1.52.Final=classpath +io.netty:netty-codec-http:4.1.52.Final=classpath +io.netty:netty-codec-socks:4.1.52.Final=classpath +io.netty:netty-codec:4.1.52.Final=classpath +io.netty:netty-common:4.1.52.Final=classpath +io.netty:netty-handler-proxy:4.1.52.Final=classpath +io.netty:netty-handler:4.1.52.Final=classpath +io.netty:netty-resolver:4.1.52.Final=classpath +io.netty:netty-transport:4.1.52.Final=classpath +io.perfmark:perfmark-api:0.23.0=classpath +it.unimi.dsi:fastutil:8.4.0=classpath +jakarta.activation:jakarta.activation-api:1.2.1=classpath +jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath +javax.inject:javax.inject:1=classpath +net.java.dev.jna:jna-platform:5.6.0=classpath +net.java.dev.jna:jna:5.6.0=classpath +net.sf.jopt-simple:jopt-simple:4.9=classpath +net.sf.kxml:kxml2:2.3.0=classpath +org.apache.commons:commons-compress:1.20=classpath +org.apache.httpcomponents:httpclient:4.5.13=classpath +org.apache.httpcomponents:httpcore:4.4.13=classpath +org.apache.httpcomponents:httpmime:4.5.6=classpath +org.bitbucket.b_c:jose4j:0.7.0=classpath +org.bouncycastle:bcpkix-jdk15on:1.67=classpath +org.bouncycastle:bcprov-jdk15on:1.67=classpath +org.checkerframework:checker-qual:3.5.0=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.19=classpath +org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath +org.glassfish.jaxb:txw2:2.3.2=classpath +org.jdom:jdom2:2.0.6=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath +org.jetbrains:annotations:13.0=classpath +org.json:json:20180813=classpath +org.jvnet.staxex:stax-ex:1.8.1=classpath +org.ow2.asm:asm-analysis:9.1=classpath +org.ow2.asm:asm-commons:9.1=classpath +org.ow2.asm:asm-tree:9.1=classpath +org.ow2.asm:asm-util:9.1=classpath +org.ow2.asm:asm:9.1=classpath +org.slf4j:slf4j-api:1.7.30=classpath +org.tensorflow:tensorflow-lite-metadata:0.1.0-rc2=classpath +xerces:xercesImpl:2.12.0=classpath +xml-apis:xml-apis:1.4.01=classpath +empty= diff --git a/dev/integration_tests/spell_check/android/project-app.lockfile b/dev/integration_tests/spell_check/android/project-app.lockfile new file mode 100644 index 0000000000000..33db6f26e122a --- /dev/null +++ b/dev/integration_tests/spell_check/android/project-app.lockfile @@ -0,0 +1,70 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core:1.6.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.fragment:fragment:1.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-core:3.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-idling-resource:3.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:monitor:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:rules:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:runner:1.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.code.findbugs:jsr305:2.0.1=debugAndroidTestCompileClasspath +com.google.code.findbugs:jsr305:3.0.2=debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.3.2=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:failureaccess:1.0.1=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:28.1-android=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.google.j2objc:j2objc-annotations:1.3=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +com.squareup:javawriter:2.1.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +javax.inject:javax.inject:1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +junit:junit:4.12=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.java.dev.jna:jna:5.6.0=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +net.sf.kxml:kxml2:2.3.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.checkerframework:checker-compat-qual:2.5.5=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.18=debugRuntimeClasspath,debugUnitTestRuntimeClasspath,profileRuntimeClasspath,profileUnitTestRuntimeClasspath,releaseRuntimeClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-integration:1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.intellij.deps:trove4j:1.0.20200330=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.7.10=kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-reflect:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-script-runtime:1.7.10=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath +org.jetbrains.kotlin:kotlin-stdlib-common:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.7.10=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt +org.ow2.asm:asm-commons:9.1=androidJacocoAnt +org.ow2.asm:asm-tree:9.1=androidJacocoAnt +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestApiDependenciesMetadata,androidTestCompileOnlyDependenciesMetadata,androidTestDebugApiDependenciesMetadata,androidTestDebugCompileOnlyDependenciesMetadata,androidTestDebugImplementationDependenciesMetadata,androidTestDebugIntransitiveDependenciesMetadata,androidTestDebugRuntimeOnlyDependenciesMetadata,androidTestImplementationDependenciesMetadata,androidTestIntransitiveDependenciesMetadata,androidTestProfileApiDependenciesMetadata,androidTestProfileCompileOnlyDependenciesMetadata,androidTestProfileImplementationDependenciesMetadata,androidTestProfileIntransitiveDependenciesMetadata,androidTestProfileRuntimeOnlyDependenciesMetadata,androidTestReleaseApiDependenciesMetadata,androidTestReleaseCompileOnlyDependenciesMetadata,androidTestReleaseImplementationDependenciesMetadata,androidTestReleaseIntransitiveDependenciesMetadata,androidTestReleaseRuntimeOnlyDependenciesMetadata,androidTestRuntimeOnlyDependenciesMetadata,androidTestUtil,compileOnlyDependenciesMetadata,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAndroidTestApiDependenciesMetadata,debugAndroidTestCompileOnlyDependenciesMetadata,debugAndroidTestImplementationDependenciesMetadata,debugAndroidTestIntransitiveDependenciesMetadata,debugAndroidTestRuntimeClasspath,debugAndroidTestRuntimeOnlyDependenciesMetadata,debugAnnotationProcessorClasspath,debugCompileOnlyDependenciesMetadata,debugIntransitiveDependenciesMetadata,debugReverseMetadataValues,debugRuntimeOnlyDependenciesMetadata,debugUnitTestAnnotationProcessorClasspath,debugUnitTestApiDependenciesMetadata,debugUnitTestCompileOnlyDependenciesMetadata,debugUnitTestImplementationDependenciesMetadata,debugUnitTestIntransitiveDependenciesMetadata,debugUnitTestRuntimeOnlyDependenciesMetadata,debugWearBundling,intransitiveDependenciesMetadata,kotlinCompilerPluginClasspath,kotlinCompilerPluginClasspathDebug,kotlinCompilerPluginClasspathDebugAndroidTest,kotlinCompilerPluginClasspathDebugUnitTest,kotlinCompilerPluginClasspathProfile,kotlinCompilerPluginClasspathProfileUnitTest,kotlinCompilerPluginClasspathRelease,kotlinCompilerPluginClasspathReleaseUnitTest,kotlinNativeCompilerPluginClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileCompileOnlyDependenciesMetadata,profileIntransitiveDependenciesMetadata,profileReverseMetadataValues,profileRuntimeOnlyDependenciesMetadata,profileUnitTestAnnotationProcessorClasspath,profileUnitTestApiDependenciesMetadata,profileUnitTestCompileOnlyDependenciesMetadata,profileUnitTestImplementationDependenciesMetadata,profileUnitTestIntransitiveDependenciesMetadata,profileUnitTestRuntimeOnlyDependenciesMetadata,profileWearBundling,releaseAnnotationProcessorClasspath,releaseCompileOnlyDependenciesMetadata,releaseIntransitiveDependenciesMetadata,releaseReverseMetadataValues,releaseRuntimeOnlyDependenciesMetadata,releaseUnitTestAnnotationProcessorClasspath,releaseUnitTestApiDependenciesMetadata,releaseUnitTestCompileOnlyDependenciesMetadata,releaseUnitTestImplementationDependenciesMetadata,releaseUnitTestIntransitiveDependenciesMetadata,releaseUnitTestRuntimeOnlyDependenciesMetadata,releaseWearBundling,runtimeOnlyDependenciesMetadata,testApiDependenciesMetadata,testCompileOnlyDependenciesMetadata,testDebugApiDependenciesMetadata,testDebugCompileOnlyDependenciesMetadata,testDebugImplementationDependenciesMetadata,testDebugIntransitiveDependenciesMetadata,testDebugRuntimeOnlyDependenciesMetadata,testFixturesApiDependenciesMetadata,testFixturesCompileOnlyDependenciesMetadata,testFixturesDebugApiDependenciesMetadata,testFixturesDebugCompileOnlyDependenciesMetadata,testFixturesDebugImplementationDependenciesMetadata,testFixturesDebugIntransitiveDependenciesMetadata,testFixturesDebugRuntimeOnlyDependenciesMetadata,testFixturesImplementationDependenciesMetadata,testFixturesIntransitiveDependenciesMetadata,testFixturesProfileApiDependenciesMetadata,testFixturesProfileCompileOnlyDependenciesMetadata,testFixturesProfileImplementationDependenciesMetadata,testFixturesProfileIntransitiveDependenciesMetadata,testFixturesProfileRuntimeOnlyDependenciesMetadata,testFixturesReleaseApiDependenciesMetadata,testFixturesReleaseCompileOnlyDependenciesMetadata,testFixturesReleaseImplementationDependenciesMetadata,testFixturesReleaseIntransitiveDependenciesMetadata,testFixturesReleaseRuntimeOnlyDependenciesMetadata,testFixturesRuntimeOnlyDependenciesMetadata,testImplementationDependenciesMetadata,testIntransitiveDependenciesMetadata,testProfileApiDependenciesMetadata,testProfileCompileOnlyDependenciesMetadata,testProfileImplementationDependenciesMetadata,testProfileIntransitiveDependenciesMetadata,testProfileRuntimeOnlyDependenciesMetadata,testReleaseApiDependenciesMetadata,testReleaseCompileOnlyDependenciesMetadata,testReleaseImplementationDependenciesMetadata,testReleaseIntransitiveDependenciesMetadata,testReleaseRuntimeOnlyDependenciesMetadata,testRuntimeOnlyDependenciesMetadata diff --git a/dev/integration_tests/spell_check/android/project-integration_test.lockfile b/dev/integration_tests/spell_check/android/project-integration_test.lockfile new file mode 100644 index 0000000000000..f45da3d417cf0 --- /dev/null +++ b/dev/integration_tests/spell_check/android/project-integration_test.lockfile @@ -0,0 +1,62 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +androidx.activity:activity:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation-experimental:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.annotation:annotation:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-common:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.arch.core:core-runtime:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.core:core:1.6.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.fragment:fragment:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common-java8:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-livedata:2.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.savedstate:savedstate:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-core:3.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test.espresso:espresso-idling-resource:3.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:monitor:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:rules:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.test:runner:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.tracing:tracing:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window-java:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +androidx.window:window:1.0.0-beta04=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.code.findbugs:jsr305:3.0.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.errorprone:error_prone_annotations:2.3.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:failureaccess:1.0.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:guava:28.1-android=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.google.j2objc:j2objc-annotations:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +com.squareup:javawriter:2.1.1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +javax.inject:javax.inject:1=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +junit:junit:4.12=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +net.sf.kxml:kxml2:2.3.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.checkerframework:checker-compat-qual:2.5.5=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.codehaus.mojo:animal-sniffer-annotations:1.18=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-integration:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jacoco:org.jacoco.agent:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.ant:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.core:0.8.7=androidJacocoAnt +org.jacoco:org.jacoco.report:0.8.7=androidJacocoAnt +org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.30=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlin:kotlin-stdlib:1.5.31=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,profileCompileClasspath,profileRuntimeClasspath,profileUnitTestCompileClasspath,profileUnitTestRuntimeClasspath,releaseCompileClasspath,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath +org.ow2.asm:asm-analysis:9.1=androidJacocoAnt +org.ow2.asm:asm-commons:9.1=androidJacocoAnt +org.ow2.asm:asm-tree:9.1=androidJacocoAnt +org.ow2.asm:asm:9.1=androidJacocoAnt +empty=androidApis,androidJdkImage,androidTestUtil,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,profileAnnotationProcessorClasspath,profileUnitTestAnnotationProcessorClasspath,releaseAnnotationProcessorClasspath,releaseUnitTestAnnotationProcessorClasspath diff --git a/dev/integration_tests/spell_check/android/settings.gradle b/dev/integration_tests/spell_check/android/settings.gradle index d3b6a4013d714..a020595962d63 100644 --- a/dev/integration_tests/spell_check/android/settings.gradle +++ b/dev/integration_tests/spell_check/android/settings.gradle @@ -2,6 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// This file is auto generated. +// To update all the settings.gradle files in the Flutter repo, +// See dev/tools/bin/generate_gradle_lockfiles.dart. + include ':app' def localPropertiesFile = new File(rootProject.projectDir, "local.properties") diff --git a/dev/integration_tests/spell_check/ios/Runner/Info.plist b/dev/integration_tests/spell_check/ios/Runner/Info.plist index 7d0689b67a1f0..7e765608fa11c 100644 --- a/dev/integration_tests/spell_check/ios/Runner/Info.plist +++ b/dev/integration_tests/spell_check/ios/Runner/Info.plist @@ -41,8 +41,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/integration_tests/spell_check/pubspec.yaml b/dev/integration_tests/spell_check/pubspec.yaml index d1c2c257a3bbb..91be571793695 100644 --- a/dev/integration_tests/spell_check/pubspec.yaml +++ b/dev/integration_tests/spell_check/pubspec.yaml @@ -36,10 +36,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -58,15 +58,15 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: @@ -106,4 +106,4 @@ flutter: # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages -# PUBSPEC CHECKSUM: e07c +# PUBSPEC CHECKSUM: 85a2 diff --git a/dev/integration_tests/ui/android/build.gradle b/dev/integration_tests/ui/android/build.gradle index 2de20623576a8..a2a8866952986 100644 --- a/dev/integration_tests/ui/android/build.gradle +++ b/dev/integration_tests/ui/android/build.gradle @@ -7,14 +7,14 @@ // See dev/tools/bin/generate_gradle_lockfiles.dart. buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.7.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/dev/integration_tests/ui/android/buildscript-gradle.lockfile b/dev/integration_tests/ui/android/buildscript-gradle.lockfile index efe13277310ee..eb605802d98f5 100644 --- a/dev/integration_tests/ui/android/buildscript-gradle.lockfile +++ b/dev/integration_tests/ui/android/buildscript-gradle.lockfile @@ -1,69 +1,62 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. -androidx.databinding:databinding-common:7.2.0=classpath -androidx.databinding:databinding-compiler-common:7.2.0=classpath -com.android.databinding:baseLibrary:7.2.0=classpath -com.android.tools.analytics-library:crash:30.2.0=classpath -com.android.tools.analytics-library:protos:30.2.0=classpath -com.android.tools.analytics-library:shared:30.2.0=classpath -com.android.tools.analytics-library:tracker:30.2.0=classpath -com.android.tools.build.jetifier:jetifier-core:1.0.0-beta09=classpath -com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta09=classpath -com.android.tools.build:aapt2-proto:7.2.0-7984345=classpath -com.android.tools.build:aaptcompiler:7.2.0=classpath -com.android.tools.build:apksig:7.2.0=classpath -com.android.tools.build:apkzlib:7.2.0=classpath -com.android.tools.build:builder-model:7.2.0=classpath -com.android.tools.build:builder-test-api:7.2.0=classpath -com.android.tools.build:builder:7.2.0=classpath -com.android.tools.build:bundletool:1.8.2=classpath -com.android.tools.build:gradle-api:7.2.0=classpath -com.android.tools.build:gradle:7.2.0=classpath -com.android.tools.build:manifest-merger:30.2.0=classpath +androidx.databinding:databinding-common:7.3.0=classpath +androidx.databinding:databinding-compiler-common:7.3.0=classpath +com.android.databinding:baseLibrary:7.3.0=classpath +com.android.tools.analytics-library:crash:30.3.0=classpath +com.android.tools.analytics-library:protos:30.3.0=classpath +com.android.tools.analytics-library:shared:30.3.0=classpath +com.android.tools.analytics-library:tracker:30.3.0=classpath +com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath +com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath +com.android.tools.build:aapt2-proto:7.3.0-8691043=classpath +com.android.tools.build:aaptcompiler:7.3.0=classpath +com.android.tools.build:apksig:7.3.0=classpath +com.android.tools.build:apkzlib:7.3.0=classpath +com.android.tools.build:builder-model:7.3.0=classpath +com.android.tools.build:builder-test-api:7.3.0=classpath +com.android.tools.build:builder:7.3.0=classpath +com.android.tools.build:bundletool:1.9.0=classpath +com.android.tools.build:gradle-api:7.3.0=classpath +com.android.tools.build:gradle:7.3.0=classpath +com.android.tools.build:manifest-merger:30.3.0=classpath com.android.tools.build:transform-api:2.0.0-deprecated-use-gradle-api=classpath -com.android.tools.ddms:ddmlib:30.2.0=classpath -com.android.tools.layoutlib:layoutlib-api:30.2.0=classpath -com.android.tools.lint:lint-model:30.2.0=classpath -com.android.tools.lint:lint-typedef-remover:30.2.0=classpath -com.android.tools.utp:android-device-provider-ddmlib-proto:30.2.0=classpath -com.android.tools.utp:android-device-provider-gradle-proto:30.2.0=classpath -com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.2.0=classpath -com.android.tools.utp:android-test-plugin-host-coverage-proto:30.2.0=classpath -com.android.tools.utp:android-test-plugin-host-retention-proto:30.2.0=classpath -com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.2.0=classpath -com.android.tools:annotations:30.2.0=classpath -com.android.tools:common:30.2.0=classpath -com.android.tools:dvlib:30.2.0=classpath -com.android.tools:repository:30.2.0=classpath -com.android.tools:sdk-common:30.2.0=classpath -com.android.tools:sdklib:30.2.0=classpath -com.android:signflinger:7.2.0=classpath -com.android:zipflinger:7.2.0=classpath -com.fasterxml.jackson.core:jackson-annotations:2.11.1=classpath -com.fasterxml.jackson.core:jackson-core:2.11.1=classpath -com.fasterxml.jackson.core:jackson-databind:2.11.1=classpath -com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.11.1=classpath -com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.11.1=classpath -com.fasterxml.jackson.module:jackson-module-kotlin:2.11.1=classpath -com.fasterxml.woodstox:woodstox-core:6.2.1=classpath +com.android.tools.ddms:ddmlib:30.3.0=classpath +com.android.tools.layoutlib:layoutlib-api:30.3.0=classpath +com.android.tools.lint:lint-model:30.3.0=classpath +com.android.tools.lint:lint-typedef-remover:30.3.0=classpath +com.android.tools.utp:android-device-provider-ddmlib-proto:30.3.0=classpath +com.android.tools.utp:android-device-provider-gradle-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-coverage-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-host-retention-proto:30.3.0=classpath +com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:30.3.0=classpath +com.android.tools:annotations:30.3.0=classpath +com.android.tools:common:30.3.0=classpath +com.android.tools:dvlib:30.3.0=classpath +com.android.tools:repository:30.3.0=classpath +com.android.tools:sdk-common:30.3.0=classpath +com.android.tools:sdklib:30.3.0=classpath +com.android:signflinger:7.3.0=classpath +com.android:zipflinger:7.3.0=classpath com.github.gundy:semver4j:0.16.4=classpath com.google.android:annotations:4.1.1.4=classpath -com.google.api.grpc:proto-google-common-protos:1.12.0=classpath +com.google.api.grpc:proto-google-common-protos:2.0.1=classpath com.google.auto.value:auto-value-annotations:1.6.2=classpath com.google.code.findbugs:jsr305:3.0.2=classpath -com.google.code.gson:gson:2.8.6=classpath +com.google.code.gson:gson:2.8.9=classpath com.google.crypto.tink:tink:1.3.0-rc2=classpath com.google.dagger:dagger:2.28.3=classpath -com.google.errorprone:error_prone_annotations:2.3.4=classpath +com.google.errorprone:error_prone_annotations:2.4.0=classpath com.google.flatbuffers:flatbuffers-java:1.12.0=classpath com.google.guava:failureaccess:1.0.1=classpath com.google.guava:guava:30.1-jre=classpath com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath com.google.j2objc:j2objc-annotations:1.3=classpath com.google.jimfs:jimfs:1.1=classpath -com.google.protobuf:protobuf-java-util:3.10.0=classpath -com.google.protobuf:protobuf-java:3.10.0=classpath +com.google.protobuf:protobuf-java-util:3.17.2=classpath +com.google.protobuf:protobuf-java:3.17.2=classpath com.google.testing.platform:core-proto:0.0.8-alpha07=classpath com.googlecode.json-simple:json-simple:1.1=classpath com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath @@ -76,80 +69,76 @@ commons-codec:commons-codec:1.11=classpath commons-io:commons-io:2.4=classpath commons-logging:commons-logging:1.2=classpath de.undercouch:gradle-download-task:4.1.1=classpath -io.grpc:grpc-api:1.21.1=classpath -io.grpc:grpc-context:1.21.1=classpath -io.grpc:grpc-core:1.21.1=classpath -io.grpc:grpc-netty:1.21.1=classpath -io.grpc:grpc-protobuf-lite:1.21.1=classpath -io.grpc:grpc-protobuf:1.21.1=classpath -io.grpc:grpc-stub:1.21.1=classpath -io.netty:netty-buffer:4.1.34.Final=classpath -io.netty:netty-codec-http2:4.1.34.Final=classpath -io.netty:netty-codec-http:4.1.34.Final=classpath -io.netty:netty-codec-socks:4.1.34.Final=classpath -io.netty:netty-codec:4.1.34.Final=classpath -io.netty:netty-common:4.1.34.Final=classpath -io.netty:netty-handler-proxy:4.1.34.Final=classpath -io.netty:netty-handler:4.1.34.Final=classpath -io.netty:netty-resolver:4.1.34.Final=classpath -io.netty:netty-transport:4.1.34.Final=classpath -io.opencensus:opencensus-api:0.21.0=classpath -io.opencensus:opencensus-contrib-grpc-metrics:0.21.0=classpath +io.grpc:grpc-api:1.39.0=classpath +io.grpc:grpc-context:1.39.0=classpath +io.grpc:grpc-core:1.39.0=classpath +io.grpc:grpc-netty:1.39.0=classpath +io.grpc:grpc-protobuf-lite:1.39.0=classpath +io.grpc:grpc-protobuf:1.39.0=classpath +io.grpc:grpc-stub:1.39.0=classpath +io.netty:netty-buffer:4.1.52.Final=classpath +io.netty:netty-codec-http2:4.1.52.Final=classpath +io.netty:netty-codec-http:4.1.52.Final=classpath +io.netty:netty-codec-socks:4.1.52.Final=classpath +io.netty:netty-codec:4.1.52.Final=classpath +io.netty:netty-common:4.1.52.Final=classpath +io.netty:netty-handler-proxy:4.1.52.Final=classpath +io.netty:netty-handler:4.1.52.Final=classpath +io.netty:netty-resolver:4.1.52.Final=classpath +io.netty:netty-transport:4.1.52.Final=classpath +io.perfmark:perfmark-api:0.23.0=classpath it.unimi.dsi:fastutil:8.4.0=classpath jakarta.activation:jakarta.activation-api:1.2.1=classpath jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath +javax.annotation:javax.annotation-api:1.3.2=classpath javax.inject:javax.inject:1=classpath net.java.dev.jna:jna-platform:5.6.0=classpath net.java.dev.jna:jna:5.6.0=classpath net.sf.jopt-simple:jopt-simple:4.9=classpath net.sf.kxml:kxml2:2.3.0=classpath org.apache.commons:commons-compress:1.20=classpath -org.apache.httpcomponents:httpclient:4.5.9=classpath -org.apache.httpcomponents:httpcore:4.4.11=classpath +org.apache.httpcomponents:httpclient:4.5.13=classpath +org.apache.httpcomponents:httpcore:4.4.13=classpath org.apache.httpcomponents:httpmime:4.5.6=classpath org.bitbucket.b_c:jose4j:0.7.0=classpath -org.bouncycastle:bcpkix-jdk15on:1.56=classpath -org.bouncycastle:bcprov-jdk15on:1.56=classpath +org.bouncycastle:bcpkix-jdk15on:1.67=classpath +org.bouncycastle:bcprov-jdk15on:1.67=classpath org.checkerframework:checker-qual:3.5.0=classpath -org.codehaus.mojo:animal-sniffer-annotations:1.17=classpath -org.codehaus.woodstox:stax2-api:4.2.1=classpath +org.codehaus.mojo:animal-sniffer-annotations:1.19=classpath org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath org.glassfish.jaxb:txw2:2.3.2=classpath org.jdom:jdom2:2.0.6=classpath -org.jetbrains.dokka:dokka-core:1.4.32=classpath -org.jetbrains.intellij.deps:trove4j:1.0.20181211=classpath -org.jetbrains.kotlin:kotlin-android-extensions:1.5.31=classpath -org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.5.31=classpath -org.jetbrains.kotlin:kotlin-build-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-compiler-runner:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-client:1.5.31=classpath -org.jetbrains.kotlin:kotlin-daemon-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.5.31=classpath -org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31=classpath -org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.5.31=classpath -org.jetbrains.kotlin:kotlin-native-utils:1.5.31=classpath -org.jetbrains.kotlin:kotlin-project-model:1.5.31=classpath +org.jetbrains.intellij.deps:trove4j:1.0.20200330=classpath +org.jetbrains.kotlin:kotlin-android-extensions:1.7.10=classpath +org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.7.10=classpath +org.jetbrains.kotlin:kotlin-build-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-compiler-runner:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-client:1.7.10=classpath +org.jetbrains.kotlin:kotlin-daemon-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-idea:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.7.10=classpath +org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10=classpath +org.jetbrains.kotlin:kotlin-klib-commonizer-api:1.7.10=classpath +org.jetbrains.kotlin:kotlin-native-utils:1.7.10=classpath +org.jetbrains.kotlin:kotlin-project-model:1.7.10=classpath org.jetbrains.kotlin:kotlin-reflect:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-common:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.5.31=classpath -org.jetbrains.kotlin:kotlin-scripting-jvm:1.5.31=classpath +org.jetbrains.kotlin:kotlin-scripting-common:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.7.10=classpath +org.jetbrains.kotlin:kotlin-scripting-jvm:1.7.10=classpath org.jetbrains.kotlin:kotlin-stdlib-common:1.5.31=classpath org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.5.31=classpath org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.31=classpath org.jetbrains.kotlin:kotlin-stdlib:1.5.31=classpath -org.jetbrains.kotlin:kotlin-tooling-metadata:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-io:1.5.31=classpath -org.jetbrains.kotlin:kotlin-util-klib:1.5.31=classpath +org.jetbrains.kotlin:kotlin-tooling-core:1.7.10=classpath +org.jetbrains.kotlin:kotlin-tooling-metadata:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-io:1.7.10=classpath +org.jetbrains.kotlin:kotlin-util-klib:1.7.10=classpath org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.5.0=classpath -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0=classpath org.jetbrains:annotations:13.0=classpath -org.jetbrains:markdown-jvm:0.2.1=classpath -org.jetbrains:markdown:0.2.1=classpath org.json:json:20180813=classpath -org.jsoup:jsoup:1.13.1=classpath org.jvnet.staxex:stax-ex:1.8.1=classpath org.ow2.asm:asm-analysis:9.1=classpath org.ow2.asm:asm-commons:9.1=classpath diff --git a/dev/integration_tests/ui/android/gradle/wrapper/gradle-wrapper.properties b/dev/integration_tests/ui/android/gradle/wrapper/gradle-wrapper.properties index f338a880848d1..cb24abda10ae7 100644 --- a/dev/integration_tests/ui/android/gradle/wrapper/gradle-wrapper.properties +++ b/dev/integration_tests/ui/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/dev/integration_tests/ui/android/settings.gradle b/dev/integration_tests/ui/android/settings.gradle index 03ae72d135c01..a020595962d63 100644 --- a/dev/integration_tests/ui/android/settings.gradle +++ b/dev/integration_tests/ui/android/settings.gradle @@ -8,8 +8,6 @@ include ':app' -enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT') - def localPropertiesFile = new File(rootProject.projectDir, "local.properties") def properties = new Properties() diff --git a/dev/integration_tests/ui/ios/Runner/Info.plist b/dev/integration_tests/ui/ios/Runner/Info.plist index b6980212d10d4..48648e5091560 100644 --- a/dev/integration_tests/ui/ios/Runner/Info.plist +++ b/dev/integration_tests/ui/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/integration_tests/ui/linux/flutter/generated_plugins.cmake b/dev/integration_tests/ui/linux/flutter/generated_plugins.cmake deleted file mode 100644 index 2e1de87a7eb61..0000000000000 --- a/dev/integration_tests/ui/linux/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/dev/integration_tests/ui/pubspec.yaml b/dev/integration_tests/ui/pubspec.yaml index 58a508270eec6..4613a4e7090c8 100644 --- a/dev/integration_tests/ui/pubspec.yaml +++ b/dev/integration_tests/ui/pubspec.yaml @@ -11,11 +11,11 @@ dependencies: sdk: flutter integration_test: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -25,13 +25,13 @@ dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -52,11 +52,12 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -65,7 +66,7 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - test_api: 0.5.2 + test_api: 0.6.0 clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -75,4 +76,4 @@ flutter: assets: - assets/foo.png -# PUBSPEC CHECKSUM: 5030 +# PUBSPEC CHECKSUM: 968e diff --git a/dev/integration_tests/ui/windows/flutter/generated_plugins.cmake b/dev/integration_tests/ui/windows/flutter/generated_plugins.cmake deleted file mode 100644 index b93c4c30c1670..0000000000000 --- a/dev/integration_tests/ui/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/dev/integration_tests/ui/windows/runner/flutter_window.cpp b/dev/integration_tests/ui/windows/runner/flutter_window.cpp index 7f66913a0293c..252aa267868b6 100644 --- a/dev/integration_tests/ui/windows/runner/flutter_window.cpp +++ b/dev/integration_tests/ui/windows/runner/flutter_window.cpp @@ -36,8 +36,8 @@ bool FlutterWindow::OnCreate() { }); // Flutter can complete the first frame before the "show window" callback is - // registered. Ensure a frame is pending to ensure the window is shown. - // This no-ops if the first frame hasn't completed yet. + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; diff --git a/dev/integration_tests/web/pubspec.yaml b/dev/integration_tests/web/pubspec.yaml index eaeb4091d9672..71008e7ea2266 100644 --- a/dev/integration_tests/web/pubspec.yaml +++ b/dev/integration_tests/web/pubspec.yaml @@ -16,9 +16,9 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 3e9c +# PUBSPEC CHECKSUM: a9c0 diff --git a/dev/integration_tests/web_compile_tests/pubspec.yaml b/dev/integration_tests/web_compile_tests/pubspec.yaml index 6eed3c4b13148..9dc5925ff4257 100644 --- a/dev/integration_tests/web_compile_tests/pubspec.yaml +++ b/dev/integration_tests/web_compile_tests/pubspec.yaml @@ -8,9 +8,9 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 3e9c +# PUBSPEC CHECKSUM: a9c0 diff --git a/dev/integration_tests/web_e2e_tests/pubspec.yaml b/dev/integration_tests/web_e2e_tests/pubspec.yaml index 7d1f35d6cacc8..37e371439dec2 100644 --- a/dev/integration_tests/web_e2e_tests/pubspec.yaml +++ b/dev/integration_tests/web_e2e_tests/pubspec.yaml @@ -29,8 +29,7 @@ dependencies: collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -40,29 +39,31 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_goldens: sdk: flutter http: 0.13.6 - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -76,11 +77,11 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 6015 +# PUBSPEC CHECKSUM: ab73 diff --git a/dev/integration_tests/web_e2e_tests/test_driver/url_strategy_integration.dart b/dev/integration_tests/web_e2e_tests/test_driver/url_strategy_integration.dart index 6122172aa35af..bba6a6de1e426 100644 --- a/dev/integration_tests/web_e2e_tests/test_driver/url_strategy_integration.dart +++ b/dev/integration_tests/web_e2e_tests/test_driver/url_strategy_integration.dart @@ -46,11 +46,6 @@ void main() { /// It keeps a list of history entries and event listeners in memory and /// manipulates them in order to achieve the desired functionality. class TestUrlStrategy extends UrlStrategy { - /// Creates a instance of [TestUrlStrategy] with an empty string as the - /// path. - factory TestUrlStrategy() => - TestUrlStrategy.fromEntry(const TestHistoryEntry(null, null, '')); - /// Creates an instance of [TestUrlStrategy] and populates it with a list /// that has [initialEntry] as the only item. TestUrlStrategy.fromEntry(TestHistoryEntry initialEntry) @@ -64,8 +59,6 @@ class TestUrlStrategy extends UrlStrategy { dynamic getState() => currentEntry.state; int _currentEntryIndex; - int get currentEntryIndex => _currentEntryIndex; - final List<TestHistoryEntry> history; TestHistoryEntry get currentEntry { @@ -105,16 +98,6 @@ class TestUrlStrategy extends UrlStrategy { currentEntry = TestHistoryEntry(state, title, url); } - /// This simulates the case where a user types in a url manually. It causes - /// a new state to be pushed, and all event listeners will be invoked. - Future<void> simulateUserTypingUrl(String url) { - assert(withinAppHistory); - return _nextEventLoop(() { - pushState(null, '', url); - _firePopStateEvent(); - }); - } - @override Future<void> go(int count) { assert(withinAppHistory); diff --git a/dev/integration_tests/wide_gamut_test/ios/Runner/Info.plist b/dev/integration_tests/wide_gamut_test/ios/Runner/Info.plist index 6317e5ef2274b..7ade06a48ab8f 100644 --- a/dev/integration_tests/wide_gamut_test/ios/Runner/Info.plist +++ b/dev/integration_tests/wide_gamut_test/ios/Runner/Info.plist @@ -41,8 +41,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/integration_tests/wide_gamut_test/pubspec.yaml b/dev/integration_tests/wide_gamut_test/pubspec.yaml index 5d358eed0126d..e7eeb65b71a05 100644 --- a/dev/integration_tests/wide_gamut_test/pubspec.yaml +++ b/dev/integration_tests/wide_gamut_test/pubspec.yaml @@ -13,10 +13,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -28,17 +28,17 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: e9d4 +# PUBSPEC CHECKSUM: b2fa diff --git a/dev/integration_tests/windows_startup_test/pubspec.yaml b/dev/integration_tests/windows_startup_test/pubspec.yaml index f4235a9f0a585..e786d59a3176c 100644 --- a/dev/integration_tests/windows_startup_test/pubspec.yaml +++ b/dev/integration_tests/windows_startup_test/pubspec.yaml @@ -9,11 +9,11 @@ dependencies: sdk: flutter flutter_driver: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -23,13 +23,13 @@ dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -50,15 +50,16 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: c373 +# PUBSPEC CHECKSUM: 3dd1 diff --git a/dev/integration_tests/windows_startup_test/windows/flutter/generated_plugins.cmake b/dev/integration_tests/windows_startup_test/windows/flutter/generated_plugins.cmake deleted file mode 100644 index b93c4c30c1670..0000000000000 --- a/dev/integration_tests/windows_startup_test/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/dev/integration_tests/windows_startup_test/windows/runner/flutter_window.cpp b/dev/integration_tests/windows_startup_test/windows/runner/flutter_window.cpp index d7ab09e986924..33480aef2327d 100644 --- a/dev/integration_tests/windows_startup_test/windows/runner/flutter_window.cpp +++ b/dev/integration_tests/windows_startup_test/windows/runner/flutter_window.cpp @@ -64,8 +64,8 @@ bool FlutterWindow::OnCreate() { }); // Flutter can complete the first frame before the "show window" callback is - // registered. Ensure a frame is pending to ensure the window is shown. - // This no-ops if the first frame hasn't completed yet. + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); // Create a method channel to check the window's visibility. diff --git a/dev/manual_tests/ios/Runner/Info.plist b/dev/manual_tests/ios/Runner/Info.plist index 65bdf88199951..43fe8f09a63e0 100644 --- a/dev/manual_tests/ios/Runner/Info.plist +++ b/dev/manual_tests/ios/Runner/Info.plist @@ -41,8 +41,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <true/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/dev/manual_tests/lib/actions.dart b/dev/manual_tests/lib/actions.dart index ced46243e8606..0e494d12b3f49 100644 --- a/dev/manual_tests/lib/actions.dart +++ b/dev/manual_tests/lib/actions.dart @@ -56,13 +56,6 @@ class Memento extends Object with Diagnosticable { /// An [ActionDispatcher] subclass that manages the invocation of undoable /// actions. class UndoableActionDispatcher extends ActionDispatcher implements Listenable { - /// Constructs a new [UndoableActionDispatcher]. - /// - /// The [maxUndoLevels] argument must not be null. - UndoableActionDispatcher({ - int maxUndoLevels = _defaultMaxUndoLevels, - }) : _maxUndoLevels = maxUndoLevels; - // A stack of actions that have been performed. The most recent action // performed is at the end of the list. final DoubleLinkedQueue<Memento> _completedActions = DoubleLinkedQueue<Memento>(); @@ -70,19 +63,12 @@ class UndoableActionDispatcher extends ActionDispatcher implements Listenable { // at the end of the list. final List<Memento> _undoneActions = <Memento>[]; - static const int _defaultMaxUndoLevels = 1000; - /// The maximum number of undo levels allowed. /// /// If this value is set to a value smaller than the number of completed /// actions, then the stack of completed actions is truncated to only include /// the last [maxUndoLevels] actions. - int get maxUndoLevels => _maxUndoLevels; - int _maxUndoLevels; - set maxUndoLevels(int value) { - _maxUndoLevels = value; - _pruneActions(); - } + int get maxUndoLevels => 1000; final Set<VoidCallback> _listeners = <VoidCallback>{}; @@ -121,7 +107,7 @@ class UndoableActionDispatcher extends ActionDispatcher implements Listenable { // Enforces undo level limit. void _pruneActions() { - while (_completedActions.length > _maxUndoLevels) { + while (_completedActions.length > maxUndoLevels) { _completedActions.removeFirst(); } } @@ -237,26 +223,12 @@ class RedoAction extends Action<RedoIntent> { } /// An action that can be undone. -abstract class UndoableAction<T extends Intent> extends Action<T> { - /// The [Intent] this action was originally invoked with. - Intent? get invocationIntent => _invocationTag; - Intent? _invocationTag; - - @protected - set invocationIntent(Intent? value) => _invocationTag = value; - - @override - @mustCallSuper - void invoke(T intent) { - invocationIntent = intent; - } -} +abstract class UndoableAction<T extends Intent> extends Action<T> { } class UndoableFocusActionBase<T extends Intent> extends UndoableAction<T> { @override @mustCallSuper Memento invoke(T intent) { - super.invoke(intent); final FocusNode? previousFocus = primaryFocus; return Memento(name: previousFocus!.debugLabel!, undo: () { previousFocus.requestFocus(); @@ -295,8 +267,6 @@ class UndoablePreviousFocusAction extends UndoableFocusActionBase<PreviousFocusI } class UndoableDirectionalFocusAction extends UndoableFocusActionBase<DirectionalFocusIntent> { - TraversalDirection? direction; - @override Memento invoke(DirectionalFocusIntent intent) { final Memento memento = super.invoke(intent); diff --git a/dev/manual_tests/lib/density.dart b/dev/manual_tests/lib/density.dart index 696d987929bb0..02e252e4da4be 100644 --- a/dev/manual_tests/lib/density.dart +++ b/dev/manual_tests/lib/density.dart @@ -30,15 +30,13 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { return const MaterialApp( title: _title, - home: MyHomePage(title: _title), + home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - final String title; + const MyHomePage({super.key}); @override State<MyHomePage> createState() => _MyHomePageState(); @@ -92,12 +90,6 @@ class OptionModel extends ChangeNotifier { bool get longText => _longText; bool _longText = false; - set longText(bool longText) { - if (longText != _longText) { - _longText = longText; - notifyListeners(); - } - } void reset() { final OptionModel defaultModel = OptionModel(); diff --git a/dev/manual_tests/lib/menu_anchor.dart b/dev/manual_tests/lib/menu_anchor.dart index 7b3e0dbc0255f..b6f98d34b7e07 100644 --- a/dev/manual_tests/lib/menu_anchor.dart +++ b/dev/manual_tests/lib/menu_anchor.dart @@ -778,9 +778,4 @@ enum TestMenu { final String acceleratorLabel; // Strip the accelerator markers. String get label => MenuAcceleratorLabel.stripAcceleratorMarkers(acceleratorLabel); - int get acceleratorIndex { - int index = -1; - MenuAcceleratorLabel.stripAcceleratorMarkers(acceleratorLabel, setIndex: (int i) => index = i); - return index; - } } diff --git a/dev/manual_tests/linux/flutter/generated_plugin_registrant.cc b/dev/manual_tests/linux/flutter/generated_plugin_registrant.cc deleted file mode 100644 index e71a16d23d058..0000000000000 --- a/dev/manual_tests/linux/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void fl_register_plugins(FlPluginRegistry* registry) { -} diff --git a/dev/manual_tests/linux/flutter/generated_plugin_registrant.h b/dev/manual_tests/linux/flutter/generated_plugin_registrant.h deleted file mode 100644 index e0f0a47bc08f3..0000000000000 --- a/dev/manual_tests/linux/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include <flutter_linux/flutter_linux.h> - -// Registers Flutter plugins. -void fl_register_plugins(FlPluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/dev/manual_tests/linux/flutter/generated_plugins.cmake b/dev/manual_tests/linux/flutter/generated_plugins.cmake deleted file mode 100644 index 2e1de87a7eb61..0000000000000 --- a/dev/manual_tests/linux/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/dev/manual_tests/pubspec.yaml b/dev/manual_tests/pubspec.yaml index 8138f0b2d3b95..ce15e76cabb4e 100644 --- a/dev/manual_tests/pubspec.yaml +++ b/dev/manual_tests/pubspec.yaml @@ -9,10 +9,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -22,16 +22,16 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 8a1e +# PUBSPEC CHECKSUM: b642 diff --git a/dev/manual_tests/windows/flutter/generated_plugin_registrant.cc b/dev/manual_tests/windows/flutter/generated_plugin_registrant.cc deleted file mode 100644 index 8b6d4680af388..0000000000000 --- a/dev/manual_tests/windows/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void RegisterPlugins(flutter::PluginRegistry* registry) { -} diff --git a/dev/manual_tests/windows/flutter/generated_plugin_registrant.h b/dev/manual_tests/windows/flutter/generated_plugin_registrant.h deleted file mode 100644 index dc139d85a9310..0000000000000 --- a/dev/manual_tests/windows/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include <flutter/plugin_registry.h> - -// Registers Flutter plugins. -void RegisterPlugins(flutter::PluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/dev/manual_tests/windows/flutter/generated_plugins.cmake b/dev/manual_tests/windows/flutter/generated_plugins.cmake deleted file mode 100644 index b93c4c30c1670..0000000000000 --- a/dev/manual_tests/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/dev/manual_tests/windows/runner/flutter_window.cpp b/dev/manual_tests/windows/runner/flutter_window.cpp index 7f66913a0293c..252aa267868b6 100644 --- a/dev/manual_tests/windows/runner/flutter_window.cpp +++ b/dev/manual_tests/windows/runner/flutter_window.cpp @@ -36,8 +36,8 @@ bool FlutterWindow::OnCreate() { }); // Flutter can complete the first frame before the "show window" callback is - // registered. Ensure a frame is pending to ensure the window is shown. - // This no-ops if the first frame hasn't completed yet. + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; diff --git a/dev/tools/examples_smoke_test.dart b/dev/tools/examples_smoke_test.dart index 732eb31c84461..f4877ff11a222 100644 --- a/dev/tools/examples_smoke_test.dart +++ b/dev/tools/examples_smoke_test.dart @@ -81,13 +81,12 @@ Future<void> runSmokeTests({ // A class to hold information related to an example, used to generate names // from for the tests. class ExampleInfo { - ExampleInfo(this.file, Directory examplesLibDir) + ExampleInfo(File file, Directory examplesLibDir) : importPath = _getImportPath(file, examplesLibDir), importName = '' { importName = importPath.replaceAll(RegExp(r'\.dart$'), '').replaceAll(RegExp(r'\W'), '_'); } - final File file; final String importPath; String importName; diff --git a/dev/tools/gen_defaults/README.md b/dev/tools/gen_defaults/README.md index 017c6362d17f8..7990079f5092e 100644 --- a/dev/tools/gen_defaults/README.md +++ b/dev/tools/gen_defaults/README.md @@ -1,15 +1,15 @@ ## Token Defaults Generator -Script that generates widget component theme data defaults -based on the Material Token database. These tokens were -extracted into a JSON file from an internal Google database. +Script that generates component theme data defaults based on token data. ## Usage Run this program from the root of the git repository: ``` -dart dev/tools/gen_defaults/bin/gen_defaults.dart +dart dev/tools/gen_defaults/bin/gen_defaults.dart [-v] ``` +This updates `generated/used_tokens.csv` and the various component theme files. + ## Templates There is a template file for every component that needs defaults from @@ -18,10 +18,18 @@ the token database. These templates are implemented as subclasses of for adding a new block of generated code to the bottom of a given file. Templates need to override the `generate` method to provide the generated -code block as a string. The tokens are represented as a `Map<String, dynamic>` -that is loaded from `data/material-tokens.json`. Templates can look up -whatever properties are needed in this structure to provide the properties -needed for the component. +code block as a string. See `lib/fab_template.dart` for an example that generates defaults for the Floating Action Button. + +## Tokens + +Tokens are stored in JSON files in `data/`, and are sourced from +an internal Google database. + +`template.dart` should provide nearly all useful token resolvers +(e.g. `color`, `shape`, etc.). For special cases in which one shouldn't +be defined, use `getToken` to get the raw token value. The script, through +the various revolvers and `getToken`, validates tokens, keeps track of +which tokens are used, and generates `generated/used_tokens.csv`. diff --git a/dev/tools/gen_defaults/bin/gen_defaults.dart b/dev/tools/gen_defaults/bin/gen_defaults.dart index 506aeb3acc3a9..ee3acd11f9453 100644 --- a/dev/tools/gen_defaults/bin/gen_defaults.dart +++ b/dev/tools/gen_defaults/bin/gen_defaults.dart @@ -2,21 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// Generate component theme data defaults based on the Material -// Design Token database. These tokens were extracted into a -// JSON file from the internal Google database. -// // ## Usage // // Run this program from the root of the git repository. // // ``` -// dart dev/tools/gen_defaults/bin/gen_defaults.dart +// dart dev/tools/gen_defaults/bin/gen_defaults.dart [-v] // ``` import 'dart:convert'; import 'dart:io'; +import 'package:args/args.dart'; import 'package:gen_defaults/action_chip_template.dart'; import 'package:gen_defaults/app_bar_template.dart'; import 'package:gen_defaults/badge_template.dart'; @@ -26,6 +23,7 @@ import 'package:gen_defaults/bottom_sheet_template.dart'; import 'package:gen_defaults/button_template.dart'; import 'package:gen_defaults/card_template.dart'; import 'package:gen_defaults/checkbox_template.dart'; +import 'package:gen_defaults/chip_template.dart'; import 'package:gen_defaults/color_scheme_template.dart'; import 'package:gen_defaults/date_picker_template.dart'; import 'package:gen_defaults/dialog_template.dart'; @@ -55,90 +53,52 @@ import 'package:gen_defaults/switch_template.dart'; import 'package:gen_defaults/tabs_template.dart'; import 'package:gen_defaults/text_field_template.dart'; import 'package:gen_defaults/time_picker_template.dart'; +import 'package:gen_defaults/token_logger.dart'; import 'package:gen_defaults/typography_template.dart'; -Map<String, dynamic> _readTokenFile(String fileName) { - return jsonDecode(File('dev/tools/gen_defaults/data/$fileName').readAsStringSync()) as Map<String, dynamic>; +Map<String, dynamic> _readTokenFile(File file) { + return jsonDecode(file.readAsStringSync()) as Map<String, dynamic>; } +const String materialLib = 'packages/flutter/lib/src/material'; +const String dataDir = 'dev/tools/gen_defaults/data'; + Future<void> main(List<String> args) async { - const String materialLib = 'packages/flutter/lib/src/material'; - const List<String> tokenFiles = <String>[ - 'badge.json', - 'banner.json', - 'badge.json', - 'bottom_app_bar.json', - 'button_elevated.json', - 'button_filled.json', - 'button_filled_tonal.json', - 'button_outlined.json', - 'button_text.json', - 'card_elevated.json', - 'card_filled.json', - 'card_outlined.json', - 'checkbox.json', - 'chip_assist.json', - 'chip_filter.json', - 'chip_input.json', - 'chip_suggestion.json', - 'color_dark.json', - 'color_light.json', - 'date_picker_docked.json', - 'date_picker_modal.json', - 'dialog.json', - 'dialog_fullscreen.json', - 'divider.json', - 'elevation.json', - 'fab_extended_primary.json', - 'fab_large_primary.json', - 'fab_primary.json', - 'fab_small_primary.json', - 'icon_button.json', - 'icon_button_filled.json', - 'icon_button_filled_tonal.json', - 'icon_button_outlined.json', - 'list.json', - 'menu.json', - 'motion.json', - 'navigation_bar.json', - 'navigation_drawer.json', - 'navigation_rail.json', - 'navigation_tab_primary.json', - 'navigation_tab_secondary.json', - 'palette.json', - 'progress_indicator_circular.json', - 'progress_indicator_linear.json', - 'radio_button.json', - 'search_bar.json', - 'search_view.json', - 'segmented_button_outlined.json', - 'shape.json', - 'sheet_bottom.json', - 'slider.json', - 'snackbar.json', - 'state.json', - 'switch.json', - 'text_field_filled.json', - 'text_field_outlined.json', - 'text_style.json', - 'time_picker.json', - 'top_app_bar_large.json', - 'top_app_bar_medium.json', - 'top_app_bar_small.json', - 'typeface.json', - ]; + // Parse arguments + final ArgParser parser = ArgParser(); + parser.addFlag( + 'verbose', + abbr: 'v', + help: 'Enable verbose output', + negatable: false, + ); + final ArgResults argResults = parser.parse(args); + final bool verbose = argResults['verbose'] as bool; - // Generate a map with all the tokens to simplify the template interface. + // Map of version number to list of data files that use that version. + final Map<String, List<String>> versionMap = <String, List<String>>{}; + // Map of all tokens to their values. final Map<String, dynamic> tokens = <String, dynamic>{}; - for (final String tokenFile in tokenFiles) { - tokens.addAll(_readTokenFile(tokenFile)); - } - // Special case the light and dark color schemes. - tokens['colorsLight'] = _readTokenFile('color_light.json'); - tokens['colorsDark'] = _readTokenFile('color_dark.json'); + // Initialize. + for (final FileSystemEntity tokenFile in Directory(dataDir).listSync()) { + final Map<String, dynamic> tokenFileTokens = _readTokenFile(tokenFile as File); + final String version = tokenFileTokens['version'] as String; + tokenFileTokens.remove('version'); + if (versionMap[version] == null) { + versionMap[version] = List<String>.empty(growable: true); + } + versionMap[version]!.add(tokenFile.uri.pathSegments.last); + + tokens.addAll(tokenFileTokens); + } + tokenLogger.init(allTokens: tokens, versionMap: versionMap); + // Handle light/dark color tokens separately because they share identical token names. + final Map<String, dynamic> colorLightTokens = _readTokenFile(File('$dataDir/color_light.json')); + final Map<String, dynamic> colorDarkTokens = _readTokenFile(File('$dataDir/color_dark.json')); - ActionChipTemplate('Chip', '$materialLib/chip.dart', tokens).updateFile(); + // Generate tokens files. + ChipTemplate('Chip', '$materialLib/chip.dart', tokens).updateFile(); ActionChipTemplate('ActionChip', '$materialLib/action_chip.dart', tokens).updateFile(); AppBarTemplate('AppBar', '$materialLib/app_bar.dart', tokens).updateFile(); BottomAppBarTemplate('BottomAppBar', '$materialLib/bottom_app_bar.dart', tokens).updateFile(); @@ -153,7 +113,7 @@ Future<void> main(List<String> args) async { ButtonTemplate('md.comp.text-button', 'TextButton', '$materialLib/text_button.dart', tokens).updateFile(); CardTemplate('Card', '$materialLib/card.dart', tokens).updateFile(); CheckboxTemplate('Checkbox', '$materialLib/checkbox.dart', tokens).updateFile(); - ColorSchemeTemplate('ColorScheme', '$materialLib/theme_data.dart', tokens).updateFile(); + ColorSchemeTemplate(colorLightTokens, colorDarkTokens, 'ColorScheme', '$materialLib/theme_data.dart', tokens).updateFile(); DatePickerTemplate('DatePicker', '$materialLib/date_picker_theme.dart', tokens).updateFile(); DialogFullscreenTemplate('DialogFullscreen', '$materialLib/dialog.dart', tokens).updateFile(); DialogTemplate('Dialog', '$materialLib/dialog.dart', tokens).updateFile(); @@ -188,4 +148,12 @@ Future<void> main(List<String> args) async { TextFieldTemplate('TextField', '$materialLib/text_field.dart', tokens).updateFile(); TabsTemplate('Tabs', '$materialLib/tabs.dart', tokens).updateFile(); TypographyTemplate('Typography', '$materialLib/typography.dart', tokens).updateFile(); + + tokenLogger.printVersionUsage(verbose: verbose); + tokenLogger.printTokensUsage(verbose: verbose); + if (!verbose) { + print('\nTo see detailed version and token usage, run with --verbose (-v).'); + } + + tokenLogger.dumpToFile('dev/tools/gen_defaults/generated/used_tokens.csv'); } diff --git a/dev/tools/gen_defaults/generated/used_tokens.csv b/dev/tools/gen_defaults/generated/used_tokens.csv new file mode 100644 index 0000000000000..fd5cf2e88c0e7 --- /dev/null +++ b/dev/tools/gen_defaults/generated/used_tokens.csv @@ -0,0 +1,929 @@ +Versions used, v0_162, v0_158 +md.comp.assist-chip.container.shape, +md.comp.assist-chip.container.surface-tint-layer.color, +md.comp.assist-chip.elevated.container.elevation, +md.comp.assist-chip.elevated.container.shadow-color, +md.comp.assist-chip.elevated.disabled.container.color, +md.comp.assist-chip.elevated.disabled.container.elevation, +md.comp.assist-chip.elevated.disabled.container.opacity, +md.comp.assist-chip.elevated.pressed.container.elevation, +md.comp.assist-chip.flat.container.elevation, +md.comp.assist-chip.flat.disabled.outline.color, +md.comp.assist-chip.flat.disabled.outline.opacity, +md.comp.assist-chip.flat.outline.color, +md.comp.assist-chip.flat.outline.width, +md.comp.assist-chip.label-text.text-style, +md.comp.assist-chip.with-icon.disabled.icon.color, +md.comp.assist-chip.with-icon.icon.color, +md.comp.assist-chip.with-icon.icon.size, +md.comp.badge.color, +md.comp.badge.large.label-text.color, +md.comp.badge.large.label-text.text-style, +md.comp.badge.large.size, +md.comp.badge.size, +md.comp.banner.container.color, +md.comp.banner.container.elevation, +md.comp.banner.container.surface-tint-layer.color, +md.comp.banner.supporting-text.text-style, +md.comp.bottom-app-bar.container.color, +md.comp.bottom-app-bar.container.elevation, +md.comp.bottom-app-bar.container.height, +md.comp.bottom-app-bar.container.shape, +md.comp.bottom-app-bar.container.surface-tint-layer.color, +md.comp.checkbox.error.focus.state-layer.color, +md.comp.checkbox.error.focus.state-layer.opacity, +md.comp.checkbox.error.hover.state-layer.color, +md.comp.checkbox.error.hover.state-layer.opacity, +md.comp.checkbox.error.pressed.state-layer.color, +md.comp.checkbox.error.pressed.state-layer.opacity, +md.comp.checkbox.selected.container.color, +md.comp.checkbox.selected.disabled.container.color, +md.comp.checkbox.selected.disabled.container.opacity, +md.comp.checkbox.selected.disabled.icon.color, +md.comp.checkbox.selected.error.container.color, +md.comp.checkbox.selected.error.icon.color, +md.comp.checkbox.selected.focus.state-layer.color, +md.comp.checkbox.selected.focus.state-layer.opacity, +md.comp.checkbox.selected.hover.state-layer.color, +md.comp.checkbox.selected.hover.state-layer.opacity, +md.comp.checkbox.selected.icon.color, +md.comp.checkbox.selected.outline.width, +md.comp.checkbox.selected.pressed.state-layer.color, +md.comp.checkbox.selected.pressed.state-layer.opacity, +md.comp.checkbox.state-layer.size, +md.comp.checkbox.unselected.disabled.container.opacity, +md.comp.checkbox.unselected.disabled.outline.color, +md.comp.checkbox.unselected.disabled.outline.width, +md.comp.checkbox.unselected.error.outline.color, +md.comp.checkbox.unselected.focus.outline.color, +md.comp.checkbox.unselected.focus.outline.width, +md.comp.checkbox.unselected.focus.state-layer.color, +md.comp.checkbox.unselected.focus.state-layer.opacity, +md.comp.checkbox.unselected.hover.outline.color, +md.comp.checkbox.unselected.hover.outline.width, +md.comp.checkbox.unselected.hover.state-layer.color, +md.comp.checkbox.unselected.hover.state-layer.opacity, +md.comp.checkbox.unselected.outline.color, +md.comp.checkbox.unselected.outline.width, +md.comp.checkbox.unselected.pressed.outline.color, +md.comp.checkbox.unselected.pressed.outline.width, +md.comp.checkbox.unselected.pressed.state-layer.color, +md.comp.checkbox.unselected.pressed.state-layer.opacity, +md.comp.circular-progress-indicator.active-indicator.color, +md.comp.date-picker.modal.container.color, +md.comp.date-picker.modal.container.elevation, +md.comp.date-picker.modal.container.shape, +md.comp.date-picker.modal.container.surface-tint-layer.color, +md.comp.date-picker.modal.date.focus.state-layer.opacity, +md.comp.date-picker.modal.date.hover.state-layer.opacity, +md.comp.date-picker.modal.date.label-text.text-style, +md.comp.date-picker.modal.date.pressed.state-layer.opacity, +md.comp.date-picker.modal.date.selected.container.color, +md.comp.date-picker.modal.date.selected.focus.state-layer.color, +md.comp.date-picker.modal.date.selected.hover.state-layer.color, +md.comp.date-picker.modal.date.selected.label-text.color, +md.comp.date-picker.modal.date.selected.pressed.state-layer.color, +md.comp.date-picker.modal.date.today.container.outline.color, +md.comp.date-picker.modal.date.today.container.outline.width, +md.comp.date-picker.modal.date.today.label-text.color, +md.comp.date-picker.modal.date.unselected.focus.state-layer.color, +md.comp.date-picker.modal.date.unselected.hover.state-layer.color, +md.comp.date-picker.modal.date.unselected.label-text.color, +md.comp.date-picker.modal.date.unselected.pressed.state-layer.color, +md.comp.date-picker.modal.header.headline.color, +md.comp.date-picker.modal.header.headline.text-style, +md.comp.date-picker.modal.header.supporting-text.text-style, +md.comp.date-picker.modal.range-selection.active-indicator.container.color, +md.comp.date-picker.modal.range-selection.container.elevation, +md.comp.date-picker.modal.range-selection.container.shape, +md.comp.date-picker.modal.range-selection.date.in-range.focus.state-layer.color, +md.comp.date-picker.modal.range-selection.date.in-range.focus.state-layer.opacity, +md.comp.date-picker.modal.range-selection.date.in-range.hover.state-layer.color, +md.comp.date-picker.modal.range-selection.date.in-range.hover.state-layer.opacity, +md.comp.date-picker.modal.range-selection.date.in-range.pressed.state-layer.color, +md.comp.date-picker.modal.range-selection.date.in-range.pressed.state-layer.opacity, +md.comp.date-picker.modal.range-selection.header.headline.text-style, +md.comp.date-picker.modal.range-selection.month.subhead.text-style, +md.comp.date-picker.modal.weekdays.label-text.color, +md.comp.date-picker.modal.weekdays.label-text.text-style, +md.comp.date-picker.modal.year-selection.year.focus.state-layer.opacity, +md.comp.date-picker.modal.year-selection.year.hover.state-layer.opacity, +md.comp.date-picker.modal.year-selection.year.label-text.text-style, +md.comp.date-picker.modal.year-selection.year.pressed.state-layer.opacity, +md.comp.date-picker.modal.year-selection.year.selected.container.color, +md.comp.date-picker.modal.year-selection.year.selected.focus.state-layer.color, +md.comp.date-picker.modal.year-selection.year.selected.hover.state-layer.color, +md.comp.date-picker.modal.year-selection.year.selected.label-text.color, +md.comp.date-picker.modal.year-selection.year.selected.pressed.state-layer.color, +md.comp.date-picker.modal.year-selection.year.unselected.focus.state-layer.color, +md.comp.date-picker.modal.year-selection.year.unselected.hover.state-layer.color, +md.comp.date-picker.modal.year-selection.year.unselected.label-text.color, +md.comp.date-picker.modal.year-selection.year.unselected.pressed.state-layer.color, +md.comp.dialog.container.color, +md.comp.dialog.container.elevation, +md.comp.dialog.container.shape, +md.comp.dialog.container.surface-tint-layer.color, +md.comp.dialog.headline.text-style, +md.comp.dialog.supporting-text.text-style, +md.comp.divider.color, +md.comp.divider.thickness, +md.comp.elevated-button.container.color, +md.comp.elevated-button.container.elevation, +md.comp.elevated-button.container.height, +md.comp.elevated-button.container.shadow-color, +md.comp.elevated-button.container.shape, +md.comp.elevated-button.container.surface-tint-layer.color, +md.comp.elevated-button.disabled.container.color, +md.comp.elevated-button.disabled.container.elevation, +md.comp.elevated-button.disabled.container.opacity, +md.comp.elevated-button.disabled.label-text.color, +md.comp.elevated-button.disabled.label-text.opacity, +md.comp.elevated-button.focus.container.elevation, +md.comp.elevated-button.focus.state-layer.color, +md.comp.elevated-button.focus.state-layer.opacity, +md.comp.elevated-button.hover.container.elevation, +md.comp.elevated-button.hover.state-layer.color, +md.comp.elevated-button.hover.state-layer.opacity, +md.comp.elevated-button.label-text.color, +md.comp.elevated-button.label-text.text-style, +md.comp.elevated-button.pressed.container.elevation, +md.comp.elevated-button.pressed.state-layer.color, +md.comp.elevated-button.pressed.state-layer.opacity, +md.comp.elevated-card.container.color, +md.comp.elevated-card.container.elevation, +md.comp.elevated-card.container.shadow-color, +md.comp.elevated-card.container.shape, +md.comp.elevated-card.container.surface-tint-layer.color, +md.comp.extended-fab.primary.container.height, +md.comp.extended-fab.primary.container.shape, +md.comp.extended-fab.primary.icon.size, +md.comp.extended-fab.primary.label-text.text-style, +md.comp.fab.primary.container.color, +md.comp.fab.primary.container.elevation, +md.comp.fab.primary.container.height, +md.comp.fab.primary.container.shape, +md.comp.fab.primary.container.width, +md.comp.fab.primary.focus.container.elevation, +md.comp.fab.primary.focus.state-layer.color, +md.comp.fab.primary.focus.state-layer.opacity, +md.comp.fab.primary.hover.container.elevation, +md.comp.fab.primary.hover.state-layer.color, +md.comp.fab.primary.hover.state-layer.opacity, +md.comp.fab.primary.icon.color, +md.comp.fab.primary.icon.size, +md.comp.fab.primary.large.container.height, +md.comp.fab.primary.large.container.shape, +md.comp.fab.primary.large.container.width, +md.comp.fab.primary.large.icon.size, +md.comp.fab.primary.pressed.container.elevation, +md.comp.fab.primary.pressed.state-layer.color, +md.comp.fab.primary.pressed.state-layer.opacity, +md.comp.fab.primary.small.container.height, +md.comp.fab.primary.small.container.shape, +md.comp.fab.primary.small.container.width, +md.comp.fab.primary.small.icon.size, +md.comp.filled-button.container.color, +md.comp.filled-button.container.elevation, +md.comp.filled-button.container.height, +md.comp.filled-button.container.shadow-color, +md.comp.filled-button.container.shape, +md.comp.filled-button.disabled.container.color, +md.comp.filled-button.disabled.container.elevation, +md.comp.filled-button.disabled.container.opacity, +md.comp.filled-button.disabled.label-text.color, +md.comp.filled-button.disabled.label-text.opacity, +md.comp.filled-button.focus.container.elevation, +md.comp.filled-button.focus.state-layer.color, +md.comp.filled-button.focus.state-layer.opacity, +md.comp.filled-button.hover.container.elevation, +md.comp.filled-button.hover.state-layer.color, +md.comp.filled-button.hover.state-layer.opacity, +md.comp.filled-button.label-text.color, +md.comp.filled-button.label-text.text-style, +md.comp.filled-button.pressed.container.elevation, +md.comp.filled-button.pressed.state-layer.color, +md.comp.filled-button.pressed.state-layer.opacity, +md.comp.filled-icon-button.container.color, +md.comp.filled-icon-button.container.shape, +md.comp.filled-icon-button.container.size, +md.comp.filled-icon-button.disabled.container.color, +md.comp.filled-icon-button.disabled.container.opacity, +md.comp.filled-icon-button.disabled.icon.color, +md.comp.filled-icon-button.disabled.icon.opacity, +md.comp.filled-icon-button.focus.state-layer.color, +md.comp.filled-icon-button.focus.state-layer.opacity, +md.comp.filled-icon-button.hover.state-layer.color, +md.comp.filled-icon-button.hover.state-layer.opacity, +md.comp.filled-icon-button.icon.color, +md.comp.filled-icon-button.icon.size, +md.comp.filled-icon-button.pressed.state-layer.color, +md.comp.filled-icon-button.pressed.state-layer.opacity, +md.comp.filled-icon-button.selected.container.color, +md.comp.filled-icon-button.toggle.selected.focus.state-layer.color, +md.comp.filled-icon-button.toggle.selected.hover.state-layer.color, +md.comp.filled-icon-button.toggle.selected.icon.color, +md.comp.filled-icon-button.toggle.selected.pressed.state-layer.color, +md.comp.filled-icon-button.toggle.unselected.focus.state-layer.color, +md.comp.filled-icon-button.toggle.unselected.hover.state-layer.color, +md.comp.filled-icon-button.toggle.unselected.icon.color, +md.comp.filled-icon-button.toggle.unselected.pressed.state-layer.color, +md.comp.filled-icon-button.unselected.container.color, +md.comp.filled-text-field.active-indicator.color, +md.comp.filled-text-field.active-indicator.height, +md.comp.filled-text-field.container.color, +md.comp.filled-text-field.disabled.active-indicator.color, +md.comp.filled-text-field.disabled.active-indicator.height, +md.comp.filled-text-field.disabled.active-indicator.opacity, +md.comp.filled-text-field.disabled.container.color, +md.comp.filled-text-field.disabled.container.opacity, +md.comp.filled-text-field.disabled.label-text.color, +md.comp.filled-text-field.disabled.label-text.opacity, +md.comp.filled-text-field.disabled.supporting-text.color, +md.comp.filled-text-field.disabled.supporting-text.opacity, +md.comp.filled-text-field.disabled.trailing-icon.color, +md.comp.filled-text-field.disabled.trailing-icon.opacity, +md.comp.filled-text-field.error.active-indicator.color, +md.comp.filled-text-field.error.focus.active-indicator.color, +md.comp.filled-text-field.error.focus.label-text.color, +md.comp.filled-text-field.error.focus.supporting-text.color, +md.comp.filled-text-field.error.focus.trailing-icon.color, +md.comp.filled-text-field.error.hover.active-indicator.color, +md.comp.filled-text-field.error.hover.label-text.color, +md.comp.filled-text-field.error.hover.supporting-text.color, +md.comp.filled-text-field.error.label-text.color, +md.comp.filled-text-field.error.leading-icon.color, +md.comp.filled-text-field.error.supporting-text.color, +md.comp.filled-text-field.error.trailing-icon.color, +md.comp.filled-text-field.focus.active-indicator.color, +md.comp.filled-text-field.focus.active-indicator.height, +md.comp.filled-text-field.focus.label-text.color, +md.comp.filled-text-field.focus.leading-icon.color, +md.comp.filled-text-field.focus.supporting-text.color, +md.comp.filled-text-field.focus.trailing-icon.color, +md.comp.filled-text-field.hover.active-indicator.color, +md.comp.filled-text-field.hover.active-indicator.height, +md.comp.filled-text-field.hover.label-text.color, +md.comp.filled-text-field.hover.leading-icon.color, +md.comp.filled-text-field.hover.supporting-text.color, +md.comp.filled-text-field.hover.trailing-icon.color, +md.comp.filled-text-field.label-text.color, +md.comp.filled-text-field.label-text.text-style, +md.comp.filled-text-field.leading-icon.color, +md.comp.filled-text-field.supporting-text.color, +md.comp.filled-text-field.supporting-text.text-style, +md.comp.filled-text-field.trailing-icon.color, +md.comp.filled-tonal-button.container.color, +md.comp.filled-tonal-button.container.elevation, +md.comp.filled-tonal-button.container.height, +md.comp.filled-tonal-button.container.shadow-color, +md.comp.filled-tonal-button.container.shape, +md.comp.filled-tonal-button.disabled.container.color, +md.comp.filled-tonal-button.disabled.container.elevation, +md.comp.filled-tonal-button.disabled.container.opacity, +md.comp.filled-tonal-button.disabled.label-text.color, +md.comp.filled-tonal-button.disabled.label-text.opacity, +md.comp.filled-tonal-button.focus.container.elevation, +md.comp.filled-tonal-button.focus.state-layer.color, +md.comp.filled-tonal-button.focus.state-layer.opacity, +md.comp.filled-tonal-button.hover.container.elevation, +md.comp.filled-tonal-button.hover.state-layer.color, +md.comp.filled-tonal-button.hover.state-layer.opacity, +md.comp.filled-tonal-button.label-text.color, +md.comp.filled-tonal-button.label-text.text-style, +md.comp.filled-tonal-button.pressed.container.elevation, +md.comp.filled-tonal-button.pressed.state-layer.color, +md.comp.filled-tonal-button.pressed.state-layer.opacity, +md.comp.filled-tonal-icon-button.container.color, +md.comp.filled-tonal-icon-button.container.shape, +md.comp.filled-tonal-icon-button.container.size, +md.comp.filled-tonal-icon-button.disabled.container.color, +md.comp.filled-tonal-icon-button.disabled.container.opacity, +md.comp.filled-tonal-icon-button.disabled.icon.color, +md.comp.filled-tonal-icon-button.disabled.icon.opacity, +md.comp.filled-tonal-icon-button.focus.state-layer.color, +md.comp.filled-tonal-icon-button.focus.state-layer.opacity, +md.comp.filled-tonal-icon-button.hover.state-layer.color, +md.comp.filled-tonal-icon-button.hover.state-layer.opacity, +md.comp.filled-tonal-icon-button.icon.color, +md.comp.filled-tonal-icon-button.icon.size, +md.comp.filled-tonal-icon-button.pressed.state-layer.color, +md.comp.filled-tonal-icon-button.pressed.state-layer.opacity, +md.comp.filled-tonal-icon-button.selected.container.color, +md.comp.filled-tonal-icon-button.toggle.selected.focus.state-layer.color, +md.comp.filled-tonal-icon-button.toggle.selected.hover.state-layer.color, +md.comp.filled-tonal-icon-button.toggle.selected.icon.color, +md.comp.filled-tonal-icon-button.toggle.selected.pressed.state-layer.color, +md.comp.filled-tonal-icon-button.toggle.unselected.focus.state-layer.color, +md.comp.filled-tonal-icon-button.toggle.unselected.hover.state-layer.color, +md.comp.filled-tonal-icon-button.toggle.unselected.icon.color, +md.comp.filled-tonal-icon-button.toggle.unselected.pressed.state-layer.color, +md.comp.filled-tonal-icon-button.unselected.container.color, +md.comp.filter-chip.container.shape, +md.comp.filter-chip.container.surface-tint-layer.color, +md.comp.filter-chip.elevated.container.elevation, +md.comp.filter-chip.elevated.container.shadow-color, +md.comp.filter-chip.elevated.disabled.container.color, +md.comp.filter-chip.elevated.disabled.container.elevation, +md.comp.filter-chip.elevated.disabled.container.opacity, +md.comp.filter-chip.elevated.pressed.container.elevation, +md.comp.filter-chip.elevated.selected.container.color, +md.comp.filter-chip.flat.container.elevation, +md.comp.filter-chip.flat.disabled.selected.container.color, +md.comp.filter-chip.flat.disabled.selected.container.opacity, +md.comp.filter-chip.flat.disabled.unselected.outline.color, +md.comp.filter-chip.flat.disabled.unselected.outline.opacity, +md.comp.filter-chip.flat.selected.container.color, +md.comp.filter-chip.flat.unselected.outline.color, +md.comp.filter-chip.flat.unselected.outline.width, +md.comp.filter-chip.label-text.text-style, +md.comp.filter-chip.with-icon.icon.size, +md.comp.filter-chip.with-leading-icon.disabled.leading-icon.color, +md.comp.filter-chip.with-leading-icon.selected.leading-icon.color, +md.comp.filter-chip.with-trailing-icon.selected.trailing-icon.color, +md.comp.full-screen-dialog.container.color, +md.comp.icon-button.disabled.icon.color, +md.comp.icon-button.disabled.icon.opacity, +md.comp.icon-button.icon.size, +md.comp.icon-button.selected.focus.state-layer.color, +md.comp.icon-button.selected.focus.state-layer.opacity, +md.comp.icon-button.selected.hover.state-layer.color, +md.comp.icon-button.selected.hover.state-layer.opacity, +md.comp.icon-button.selected.icon.color, +md.comp.icon-button.selected.pressed.state-layer.color, +md.comp.icon-button.selected.pressed.state-layer.opacity, +md.comp.icon-button.state-layer.shape, +md.comp.icon-button.state-layer.size, +md.comp.icon-button.unselected.focus.state-layer.color, +md.comp.icon-button.unselected.focus.state-layer.opacity, +md.comp.icon-button.unselected.hover.state-layer.color, +md.comp.icon-button.unselected.hover.state-layer.opacity, +md.comp.icon-button.unselected.icon.color, +md.comp.icon-button.unselected.pressed.state-layer.color, +md.comp.icon-button.unselected.pressed.state-layer.opacity, +md.comp.input-chip.container.elevation, +md.comp.input-chip.container.shape, +md.comp.input-chip.disabled.selected.container.color, +md.comp.input-chip.disabled.selected.container.opacity, +md.comp.input-chip.disabled.unselected.outline.color, +md.comp.input-chip.disabled.unselected.outline.opacity, +md.comp.input-chip.label-text.text-style, +md.comp.input-chip.selected.container.color, +md.comp.input-chip.unselected.outline.color, +md.comp.input-chip.unselected.outline.width, +md.comp.input-chip.with-leading-icon.disabled.leading-icon.color, +md.comp.input-chip.with-leading-icon.leading-icon.size, +md.comp.input-chip.with-trailing-icon.selected.trailing-icon.color, +md.comp.linear-progress-indicator.active-indicator.color, +md.comp.linear-progress-indicator.track.color, +md.comp.linear-progress-indicator.track.height, +md.comp.list.list-item.container.shape, +md.comp.list.list-item.disabled.label-text.color, +md.comp.list.list-item.disabled.label-text.opacity, +md.comp.list.list-item.disabled.leading-icon.color, +md.comp.list.list-item.disabled.leading-icon.opacity, +md.comp.list.list-item.focus.label-text.color, +md.comp.list.list-item.focus.leading-icon.icon.color, +md.comp.list.list-item.focus.state-layer.color, +md.comp.list.list-item.focus.state-layer.opacity, +md.comp.list.list-item.hover.label-text.color, +md.comp.list.list-item.hover.leading-icon.icon.color, +md.comp.list.list-item.hover.state-layer.color, +md.comp.list.list-item.hover.state-layer.opacity, +md.comp.list.list-item.label-text.color, +md.comp.list.list-item.label-text.text-style, +md.comp.list.list-item.leading-icon.color, +md.comp.list.list-item.pressed.label-text.color, +md.comp.list.list-item.pressed.leading-icon.icon.color, +md.comp.list.list-item.pressed.state-layer.color, +md.comp.list.list-item.pressed.state-layer.opacity, +md.comp.list.list-item.selected.trailing-icon.color, +md.comp.list.list-item.supporting-text.color, +md.comp.list.list-item.supporting-text.text-style, +md.comp.list.list-item.trailing-icon.color, +md.comp.list.list-item.trailing-supporting-text.color, +md.comp.list.list-item.trailing-supporting-text.text-style, +md.comp.menu.container.color, +md.comp.menu.container.elevation, +md.comp.menu.container.shadow-color, +md.comp.menu.container.shape, +md.comp.menu.container.surface-tint-layer.color, +md.comp.navigation-bar.active-indicator.color, +md.comp.navigation-bar.active-indicator.shape, +md.comp.navigation-bar.active.icon.color, +md.comp.navigation-bar.active.label-text.color, +md.comp.navigation-bar.container.color, +md.comp.navigation-bar.container.elevation, +md.comp.navigation-bar.container.height, +md.comp.navigation-bar.container.surface-tint-layer.color, +md.comp.navigation-bar.icon.size, +md.comp.navigation-bar.inactive.icon.color, +md.comp.navigation-bar.inactive.label-text.color, +md.comp.navigation-bar.label-text.text-style, +md.comp.navigation-drawer.active-indicator.color, +md.comp.navigation-drawer.active-indicator.height, +md.comp.navigation-drawer.active-indicator.shape, +md.comp.navigation-drawer.active-indicator.width, +md.comp.navigation-drawer.active.icon.color, +md.comp.navigation-drawer.active.label-text.color, +md.comp.navigation-drawer.container.color, +md.comp.navigation-drawer.container.surface-tint-layer.color, +md.comp.navigation-drawer.icon.size, +md.comp.navigation-drawer.inactive.icon.color, +md.comp.navigation-drawer.inactive.label-text.color, +md.comp.navigation-drawer.label-text.text-style, +md.comp.navigation-drawer.modal.container.elevation, +md.comp.navigation-rail.active-indicator.color, +md.comp.navigation-rail.active-indicator.shape, +md.comp.navigation-rail.active.focus.label-text.color, +md.comp.navigation-rail.active.icon.color, +md.comp.navigation-rail.container.color, +md.comp.navigation-rail.container.elevation, +md.comp.navigation-rail.container.width, +md.comp.navigation-rail.icon.size, +md.comp.navigation-rail.inactive.focus.label-text.color, +md.comp.navigation-rail.inactive.icon.color, +md.comp.navigation-rail.label-text.text-style, +md.comp.outlined-button.container.height, +md.comp.outlined-button.container.shape, +md.comp.outlined-button.disabled.label-text.color, +md.comp.outlined-button.disabled.label-text.opacity, +md.comp.outlined-button.disabled.outline.color, +md.comp.outlined-button.disabled.outline.opacity, +md.comp.outlined-button.focus.state-layer.color, +md.comp.outlined-button.focus.state-layer.opacity, +md.comp.outlined-button.hover.state-layer.color, +md.comp.outlined-button.hover.state-layer.opacity, +md.comp.outlined-button.label-text.color, +md.comp.outlined-button.label-text.text-style, +md.comp.outlined-button.outline.color, +md.comp.outlined-button.outline.width, +md.comp.outlined-button.pressed.state-layer.color, +md.comp.outlined-button.pressed.state-layer.opacity, +md.comp.outlined-icon-button.container.shape, +md.comp.outlined-icon-button.container.size, +md.comp.outlined-icon-button.disabled.icon.color, +md.comp.outlined-icon-button.disabled.icon.opacity, +md.comp.outlined-icon-button.disabled.selected.container.color, +md.comp.outlined-icon-button.disabled.selected.container.opacity, +md.comp.outlined-icon-button.disabled.unselected.outline.color, +md.comp.outlined-icon-button.disabled.unselected.outline.opacity, +md.comp.outlined-icon-button.focus.state-layer.opacity, +md.comp.outlined-icon-button.hover.state-layer.opacity, +md.comp.outlined-icon-button.icon.size, +md.comp.outlined-icon-button.pressed.state-layer.opacity, +md.comp.outlined-icon-button.selected.container.color, +md.comp.outlined-icon-button.selected.focus.state-layer.color, +md.comp.outlined-icon-button.selected.hover.state-layer.color, +md.comp.outlined-icon-button.selected.icon.color, +md.comp.outlined-icon-button.selected.pressed.state-layer.color, +md.comp.outlined-icon-button.unselected.focus.state-layer.color, +md.comp.outlined-icon-button.unselected.hover.state-layer.color, +md.comp.outlined-icon-button.unselected.icon.color, +md.comp.outlined-icon-button.unselected.outline.color, +md.comp.outlined-icon-button.unselected.pressed.state-layer.color, +md.comp.outlined-segmented-button.container.height, +md.comp.outlined-segmented-button.disabled.label-text.color, +md.comp.outlined-segmented-button.disabled.label-text.opacity, +md.comp.outlined-segmented-button.disabled.outline.color, +md.comp.outlined-segmented-button.disabled.outline.opacity, +md.comp.outlined-segmented-button.focus.state-layer.opacity, +md.comp.outlined-segmented-button.hover.state-layer.opacity, +md.comp.outlined-segmented-button.label-text.text-style, +md.comp.outlined-segmented-button.outline.color, +md.comp.outlined-segmented-button.outline.width, +md.comp.outlined-segmented-button.pressed.state-layer.opacity, +md.comp.outlined-segmented-button.selected.container.color, +md.comp.outlined-segmented-button.selected.focus.label-text.color, +md.comp.outlined-segmented-button.selected.focus.state-layer.color, +md.comp.outlined-segmented-button.selected.hover.label-text.color, +md.comp.outlined-segmented-button.selected.hover.state-layer.color, +md.comp.outlined-segmented-button.selected.label-text.color, +md.comp.outlined-segmented-button.selected.pressed.label-text.color, +md.comp.outlined-segmented-button.selected.pressed.state-layer.color, +md.comp.outlined-segmented-button.shape, +md.comp.outlined-segmented-button.unselected.focus.label-text.color, +md.comp.outlined-segmented-button.unselected.focus.state-layer.color, +md.comp.outlined-segmented-button.unselected.hover.label-text.color, +md.comp.outlined-segmented-button.unselected.hover.state-layer.color, +md.comp.outlined-segmented-button.unselected.label-text.color, +md.comp.outlined-segmented-button.unselected.pressed.label-text.color, +md.comp.outlined-segmented-button.unselected.pressed.state-layer.color, +md.comp.outlined-segmented-button.with-icon.icon.size, +md.comp.outlined-text-field.disabled.outline.color, +md.comp.outlined-text-field.disabled.outline.opacity, +md.comp.outlined-text-field.disabled.outline.width, +md.comp.outlined-text-field.error.focus.outline.color, +md.comp.outlined-text-field.error.hover.outline.color, +md.comp.outlined-text-field.error.outline.color, +md.comp.outlined-text-field.focus.outline.color, +md.comp.outlined-text-field.focus.outline.width, +md.comp.outlined-text-field.hover.outline.color, +md.comp.outlined-text-field.hover.outline.width, +md.comp.outlined-text-field.outline.color, +md.comp.outlined-text-field.outline.width, +md.comp.primary-navigation-tab.active-indicator.color, +md.comp.primary-navigation-tab.active-indicator.height, +md.comp.primary-navigation-tab.active.focus.state-layer.color, +md.comp.primary-navigation-tab.active.focus.state-layer.opacity, +md.comp.primary-navigation-tab.active.hover.state-layer.color, +md.comp.primary-navigation-tab.active.hover.state-layer.opacity, +md.comp.primary-navigation-tab.active.pressed.state-layer.color, +md.comp.primary-navigation-tab.active.pressed.state-layer.opacity, +md.comp.primary-navigation-tab.divider.color, +md.comp.primary-navigation-tab.inactive.focus.state-layer.color, +md.comp.primary-navigation-tab.inactive.focus.state-layer.opacity, +md.comp.primary-navigation-tab.inactive.hover.state-layer.color, +md.comp.primary-navigation-tab.inactive.hover.state-layer.opacity, +md.comp.primary-navigation-tab.inactive.pressed.state-layer.color, +md.comp.primary-navigation-tab.inactive.pressed.state-layer.opacity, +md.comp.primary-navigation-tab.with-label-text.active.label-text.color, +md.comp.primary-navigation-tab.with-label-text.inactive.label-text.color, +md.comp.primary-navigation-tab.with-label-text.label-text.text-style, +md.comp.radio-button.disabled.selected.icon.color, +md.comp.radio-button.disabled.selected.icon.opacity, +md.comp.radio-button.disabled.unselected.icon.color, +md.comp.radio-button.disabled.unselected.icon.opacity, +md.comp.radio-button.selected.focus.icon.color, +md.comp.radio-button.selected.focus.state-layer.color, +md.comp.radio-button.selected.focus.state-layer.opacity, +md.comp.radio-button.selected.hover.icon.color, +md.comp.radio-button.selected.hover.state-layer.color, +md.comp.radio-button.selected.hover.state-layer.opacity, +md.comp.radio-button.selected.icon.color, +md.comp.radio-button.selected.pressed.icon.color, +md.comp.radio-button.selected.pressed.state-layer.color, +md.comp.radio-button.selected.pressed.state-layer.opacity, +md.comp.radio-button.unselected.focus.icon.color, +md.comp.radio-button.unselected.focus.state-layer.color, +md.comp.radio-button.unselected.focus.state-layer.opacity, +md.comp.radio-button.unselected.hover.icon.color, +md.comp.radio-button.unselected.hover.state-layer.color, +md.comp.radio-button.unselected.hover.state-layer.opacity, +md.comp.radio-button.unselected.icon.color, +md.comp.radio-button.unselected.pressed.icon.color, +md.comp.radio-button.unselected.pressed.state-layer.color, +md.comp.radio-button.unselected.pressed.state-layer.opacity, +md.comp.search-bar.container.color, +md.comp.search-bar.container.elevation, +md.comp.search-bar.container.height, +md.comp.search-bar.container.shape, +md.comp.search-bar.container.surface-tint-layer.color, +md.comp.search-bar.hover.state-layer.color, +md.comp.search-bar.hover.state-layer.opacity, +md.comp.search-bar.input-text.color, +md.comp.search-bar.input-text.text-style, +md.comp.search-bar.pressed.state-layer.color, +md.comp.search-bar.pressed.state-layer.opacity, +md.comp.search-bar.supporting-text.color, +md.comp.search-bar.supporting-text.text-style, +md.comp.search-view.container.color, +md.comp.search-view.container.elevation, +md.comp.search-view.container.surface-tint-layer.color, +md.comp.search-view.divider.color, +md.comp.search-view.docked.container.shape, +md.comp.search-view.full-screen.container.shape, +md.comp.search-view.full-screen.header.container.height, +md.comp.search-view.header.input-text.color, +md.comp.search-view.header.input-text.text-style, +md.comp.search-view.header.supporting-text.color, +md.comp.search-view.header.supporting-text.text-style, +md.comp.secondary-navigation-tab.active.label-text.color, +md.comp.secondary-navigation-tab.divider.color, +md.comp.secondary-navigation-tab.focus.state-layer.color, +md.comp.secondary-navigation-tab.focus.state-layer.opacity, +md.comp.secondary-navigation-tab.hover.state-layer.color, +md.comp.secondary-navigation-tab.hover.state-layer.opacity, +md.comp.secondary-navigation-tab.inactive.label-text.color, +md.comp.secondary-navigation-tab.label-text.text-style, +md.comp.secondary-navigation-tab.pressed.state-layer.color, +md.comp.secondary-navigation-tab.pressed.state-layer.opacity, +md.comp.sheet.bottom.docked.container.color, +md.comp.sheet.bottom.docked.container.shape, +md.comp.sheet.bottom.docked.container.surface-tint-layer.color, +md.comp.sheet.bottom.docked.drag-handle.color, +md.comp.sheet.bottom.docked.drag-handle.height, +md.comp.sheet.bottom.docked.drag-handle.opacity, +md.comp.sheet.bottom.docked.drag-handle.width, +md.comp.sheet.bottom.docked.modal.container.elevation, +md.comp.sheet.bottom.docked.standard.container.elevation, +md.comp.slider.active.track.color, +md.comp.slider.active.track.height, +md.comp.slider.disabled.active.track.color, +md.comp.slider.disabled.active.track.opacity, +md.comp.slider.disabled.handle.color, +md.comp.slider.disabled.handle.opacity, +md.comp.slider.disabled.inactive.track.color, +md.comp.slider.disabled.inactive.track.opacity, +md.comp.slider.focus.state-layer.color, +md.comp.slider.focus.state-layer.opacity, +md.comp.slider.handle.color, +md.comp.slider.hover.state-layer.color, +md.comp.slider.hover.state-layer.opacity, +md.comp.slider.inactive.track.color, +md.comp.slider.label.label-text.color, +md.comp.slider.label.label-text.text-style, +md.comp.slider.pressed.state-layer.color, +md.comp.slider.pressed.state-layer.opacity, +md.comp.slider.with-tick-marks.active.container.color, +md.comp.slider.with-tick-marks.active.container.opacity, +md.comp.slider.with-tick-marks.disabled.container.color, +md.comp.slider.with-tick-marks.disabled.container.opacity, +md.comp.slider.with-tick-marks.inactive.container.color, +md.comp.slider.with-tick-marks.inactive.container.opacity, +md.comp.snackbar.action.focus.label-text.color, +md.comp.snackbar.action.hover.label-text.color, +md.comp.snackbar.action.label-text.color, +md.comp.snackbar.action.pressed.label-text.color, +md.comp.snackbar.container.color, +md.comp.snackbar.container.elevation, +md.comp.snackbar.container.shape, +md.comp.snackbar.icon.color, +md.comp.snackbar.supporting-text.color, +md.comp.snackbar.supporting-text.text-style, +md.comp.switch.disabled.selected.handle.color, +md.comp.switch.disabled.selected.handle.opacity, +md.comp.switch.disabled.selected.icon.color, +md.comp.switch.disabled.selected.icon.opacity, +md.comp.switch.disabled.selected.track.color, +md.comp.switch.disabled.track.opacity, +md.comp.switch.disabled.unselected.handle.color, +md.comp.switch.disabled.unselected.handle.opacity, +md.comp.switch.disabled.unselected.icon.color, +md.comp.switch.disabled.unselected.icon.opacity, +md.comp.switch.disabled.unselected.track.color, +md.comp.switch.disabled.unselected.track.outline.color, +md.comp.switch.pressed.handle.width, +md.comp.switch.selected.focus.handle.color, +md.comp.switch.selected.focus.icon.color, +md.comp.switch.selected.focus.state-layer.color, +md.comp.switch.selected.focus.state-layer.opacity, +md.comp.switch.selected.focus.track.color, +md.comp.switch.selected.handle.color, +md.comp.switch.selected.handle.width, +md.comp.switch.selected.hover.handle.color, +md.comp.switch.selected.hover.icon.color, +md.comp.switch.selected.hover.state-layer.color, +md.comp.switch.selected.hover.state-layer.opacity, +md.comp.switch.selected.hover.track.color, +md.comp.switch.selected.icon.color, +md.comp.switch.selected.pressed.handle.color, +md.comp.switch.selected.pressed.icon.color, +md.comp.switch.selected.pressed.state-layer.color, +md.comp.switch.selected.pressed.state-layer.opacity, +md.comp.switch.selected.pressed.track.color, +md.comp.switch.selected.track.color, +md.comp.switch.state-layer.size, +md.comp.switch.track.height, +md.comp.switch.track.outline.width, +md.comp.switch.track.width, +md.comp.switch.unselected.focus.handle.color, +md.comp.switch.unselected.focus.icon.color, +md.comp.switch.unselected.focus.state-layer.color, +md.comp.switch.unselected.focus.state-layer.opacity, +md.comp.switch.unselected.focus.track.color, +md.comp.switch.unselected.handle.color, +md.comp.switch.unselected.handle.width, +md.comp.switch.unselected.hover.handle.color, +md.comp.switch.unselected.hover.icon.color, +md.comp.switch.unselected.hover.state-layer.color, +md.comp.switch.unselected.hover.state-layer.opacity, +md.comp.switch.unselected.hover.track.color, +md.comp.switch.unselected.icon.color, +md.comp.switch.unselected.icon.size, +md.comp.switch.unselected.pressed.handle.color, +md.comp.switch.unselected.pressed.icon.color, +md.comp.switch.unselected.pressed.state-layer.color, +md.comp.switch.unselected.pressed.state-layer.opacity, +md.comp.switch.unselected.pressed.track.color, +md.comp.switch.unselected.track.color, +md.comp.switch.unselected.track.outline.color, +md.comp.switch.with-icon.handle.width, +md.comp.text-button.container.height, +md.comp.text-button.container.shape, +md.comp.text-button.disabled.label-text.color, +md.comp.text-button.disabled.label-text.opacity, +md.comp.text-button.focus.state-layer.color, +md.comp.text-button.focus.state-layer.opacity, +md.comp.text-button.hover.state-layer.color, +md.comp.text-button.hover.state-layer.opacity, +md.comp.text-button.label-text.color, +md.comp.text-button.label-text.text-style, +md.comp.text-button.pressed.state-layer.color, +md.comp.text-button.pressed.state-layer.opacity, +md.comp.time-picker.clock-dial.color, +md.comp.time-picker.clock-dial.container.size, +md.comp.time-picker.clock-dial.label-text.text-style, +md.comp.time-picker.clock-dial.selected.label-text.color, +md.comp.time-picker.clock-dial.selector.center.container.size, +md.comp.time-picker.clock-dial.selector.handle.container.color, +md.comp.time-picker.clock-dial.selector.handle.container.size, +md.comp.time-picker.clock-dial.selector.track.container.width, +md.comp.time-picker.clock-dial.unselected.label-text.color, +md.comp.time-picker.container.color, +md.comp.time-picker.container.elevation, +md.comp.time-picker.container.shape, +md.comp.time-picker.headline.color, +md.comp.time-picker.headline.text-style, +md.comp.time-picker.period-selector.container.shape, +md.comp.time-picker.period-selector.horizontal.container.height, +md.comp.time-picker.period-selector.horizontal.container.width, +md.comp.time-picker.period-selector.label-text.text-style, +md.comp.time-picker.period-selector.outline.color, +md.comp.time-picker.period-selector.outline.width, +md.comp.time-picker.period-selector.selected.container.color, +md.comp.time-picker.period-selector.selected.focus.label-text.color, +md.comp.time-picker.period-selector.selected.hover.label-text.color, +md.comp.time-picker.period-selector.selected.label-text.color, +md.comp.time-picker.period-selector.selected.pressed.label-text.color, +md.comp.time-picker.period-selector.unselected.focus.label-text.color, +md.comp.time-picker.period-selector.unselected.hover.label-text.color, +md.comp.time-picker.period-selector.unselected.pressed.label-text.color, +md.comp.time-picker.period-selector.vertical.container.height, +md.comp.time-picker.period-selector.vertical.container.width, +md.comp.time-picker.time-selector.24h-vertical.container.width, +md.comp.time-picker.time-selector.container.height, +md.comp.time-picker.time-selector.container.shape, +md.comp.time-picker.time-selector.container.width, +md.comp.time-picker.time-selector.focus.state-layer.opacity, +md.comp.time-picker.time-selector.hover.state-layer.opacity, +md.comp.time-picker.time-selector.label-text.text-style, +md.comp.time-picker.time-selector.selected.container.color, +md.comp.time-picker.time-selector.selected.focus.label-text.color, +md.comp.time-picker.time-selector.selected.focus.state-layer.color, +md.comp.time-picker.time-selector.selected.hover.label-text.color, +md.comp.time-picker.time-selector.selected.hover.state-layer.color, +md.comp.time-picker.time-selector.selected.label-text.color, +md.comp.time-picker.time-selector.selected.pressed.label-text.color, +md.comp.time-picker.time-selector.selected.pressed.state-layer.color, +md.comp.time-picker.time-selector.unselected.container.color, +md.comp.time-picker.time-selector.unselected.focus.label-text.color, +md.comp.time-picker.time-selector.unselected.focus.state-layer.color, +md.comp.time-picker.time-selector.unselected.hover.label-text.color, +md.comp.time-picker.time-selector.unselected.hover.state-layer.color, +md.comp.time-picker.time-selector.unselected.label-text.color, +md.comp.time-picker.time-selector.unselected.pressed.label-text.color, +md.comp.time-picker.time-selector.unselected.pressed.state-layer.color, +md.comp.top-app-bar.large.container.height, +md.comp.top-app-bar.large.headline.color, +md.comp.top-app-bar.large.headline.text-style, +md.comp.top-app-bar.medium.container.height, +md.comp.top-app-bar.medium.headline.color, +md.comp.top-app-bar.medium.headline.text-style, +md.comp.top-app-bar.small.container.color, +md.comp.top-app-bar.small.container.elevation, +md.comp.top-app-bar.small.container.height, +md.comp.top-app-bar.small.container.surface-tint-layer.color, +md.comp.top-app-bar.small.headline.color, +md.comp.top-app-bar.small.headline.text-style, +md.comp.top-app-bar.small.leading-icon.color, +md.comp.top-app-bar.small.leading-icon.size, +md.comp.top-app-bar.small.on-scroll.container.elevation, +md.comp.top-app-bar.small.trailing-icon.color, +md.comp.top-app-bar.small.trailing-icon.size, +md.ref.palette.error10, +md.ref.palette.error100, +md.ref.palette.error20, +md.ref.palette.error30, +md.ref.palette.error40, +md.ref.palette.error80, +md.ref.palette.error90, +md.ref.palette.neutral-variant30, +md.ref.palette.neutral-variant50, +md.ref.palette.neutral-variant60, +md.ref.palette.neutral-variant80, +md.ref.palette.neutral-variant90, +md.ref.palette.neutral0, +md.ref.palette.neutral10, +md.ref.palette.neutral20, +md.ref.palette.neutral90, +md.ref.palette.neutral95, +md.ref.palette.neutral99, +md.ref.palette.primary10, +md.ref.palette.primary100, +md.ref.palette.primary20, +md.ref.palette.primary30, +md.ref.palette.primary40, +md.ref.palette.primary80, +md.ref.palette.primary90, +md.ref.palette.secondary10, +md.ref.palette.secondary100, +md.ref.palette.secondary20, +md.ref.palette.secondary30, +md.ref.palette.secondary40, +md.ref.palette.secondary80, +md.ref.palette.secondary90, +md.ref.palette.tertiary10, +md.ref.palette.tertiary100, +md.ref.palette.tertiary20, +md.ref.palette.tertiary30, +md.ref.palette.tertiary40, +md.ref.palette.tertiary80, +md.ref.palette.tertiary90, +md.ref.typeface.weight-medium, +md.ref.typeface.weight-regular, +md.sys.color.background, +md.sys.color.error, +md.sys.color.error-container, +md.sys.color.inverse-on-surface, +md.sys.color.inverse-primary, +md.sys.color.inverse-surface, +md.sys.color.on-background, +md.sys.color.on-error, +md.sys.color.on-error-container, +md.sys.color.on-primary, +md.sys.color.on-primary-container, +md.sys.color.on-secondary, +md.sys.color.on-secondary-container, +md.sys.color.on-surface, +md.sys.color.on-surface-variant, +md.sys.color.on-tertiary, +md.sys.color.on-tertiary-container, +md.sys.color.outline, +md.sys.color.outline-variant, +md.sys.color.primary, +md.sys.color.primary-container, +md.sys.color.scrim, +md.sys.color.secondary, +md.sys.color.secondary-container, +md.sys.color.shadow, +md.sys.color.surface, +md.sys.color.surface-variant, +md.sys.color.tertiary, +md.sys.color.tertiary-container, +md.sys.elevation.level0, +md.sys.elevation.level1, +md.sys.elevation.level2, +md.sys.elevation.level3, +md.sys.elevation.level4, +md.sys.elevation.level5, +md.sys.shape.corner.extra-large, +md.sys.shape.corner.extra-large.top, +md.sys.shape.corner.extra-small, +md.sys.shape.corner.full, +md.sys.shape.corner.large, +md.sys.shape.corner.medium, +md.sys.shape.corner.none, +md.sys.shape.corner.small, +md.sys.state.focus.state-layer-opacity, +md.sys.state.hover.state-layer-opacity, +md.sys.state.pressed.state-layer-opacity, +md.sys.typescale.body-large.line-height, +md.sys.typescale.body-large.size, +md.sys.typescale.body-large.tracking, +md.sys.typescale.body-large.weight, +md.sys.typescale.body-medium.line-height, +md.sys.typescale.body-medium.size, +md.sys.typescale.body-medium.tracking, +md.sys.typescale.body-medium.weight, +md.sys.typescale.body-small.line-height, +md.sys.typescale.body-small.size, +md.sys.typescale.body-small.tracking, +md.sys.typescale.body-small.weight, +md.sys.typescale.display-large.line-height, +md.sys.typescale.display-large.size, +md.sys.typescale.display-large.tracking, +md.sys.typescale.display-large.weight, +md.sys.typescale.display-medium.line-height, +md.sys.typescale.display-medium.size, +md.sys.typescale.display-medium.tracking, +md.sys.typescale.display-medium.weight, +md.sys.typescale.display-small.line-height, +md.sys.typescale.display-small.size, +md.sys.typescale.display-small.tracking, +md.sys.typescale.display-small.weight, +md.sys.typescale.headline-large.line-height, +md.sys.typescale.headline-large.size, +md.sys.typescale.headline-large.tracking, +md.sys.typescale.headline-large.weight, +md.sys.typescale.headline-medium.line-height, +md.sys.typescale.headline-medium.size, +md.sys.typescale.headline-medium.tracking, +md.sys.typescale.headline-medium.weight, +md.sys.typescale.headline-small.line-height, +md.sys.typescale.headline-small.size, +md.sys.typescale.headline-small.tracking, +md.sys.typescale.headline-small.weight, +md.sys.typescale.label-large.line-height, +md.sys.typescale.label-large.size, +md.sys.typescale.label-large.tracking, +md.sys.typescale.label-large.weight, +md.sys.typescale.label-medium.line-height, +md.sys.typescale.label-medium.size, +md.sys.typescale.label-medium.tracking, +md.sys.typescale.label-medium.weight, +md.sys.typescale.label-small.line-height, +md.sys.typescale.label-small.size, +md.sys.typescale.label-small.tracking, +md.sys.typescale.label-small.weight, +md.sys.typescale.title-large.line-height, +md.sys.typescale.title-large.size, +md.sys.typescale.title-large.tracking, +md.sys.typescale.title-large.weight, +md.sys.typescale.title-medium.line-height, +md.sys.typescale.title-medium.size, +md.sys.typescale.title-medium.tracking, +md.sys.typescale.title-medium.weight, +md.sys.typescale.title-small.line-height, +md.sys.typescale.title-small.size, +md.sys.typescale.title-small.tracking, +md.sys.typescale.title-small.weight diff --git a/dev/tools/gen_defaults/lib/action_chip_template.dart b/dev/tools/gen_defaults/lib/action_chip_template.dart index 03fb3a5ede21f..54027c13d75d2 100644 --- a/dev/tools/gen_defaults/lib/action_chip_template.dart +++ b/dev/tools/gen_defaults/lib/action_chip_template.dart @@ -11,58 +11,73 @@ class ActionChipTemplate extends TokenTemplate { }); static const String tokenGroup = 'md.comp.assist-chip'; - static const String variant = '.flat'; + static const String flatVariant = '.flat'; + static const String elevatedVariant = '.elevated'; @override String generate() => ''' class _${blockName}DefaultsM3 extends ChipThemeData { - _${blockName}DefaultsM3(this.context, this.isEnabled) + _${blockName}DefaultsM3(this.context, this.isEnabled, this._chipVariant) : super( - elevation: ${elevation("$tokenGroup$variant.container")}, shape: ${shape("$tokenGroup.container")}, showCheckmark: true, ); final BuildContext context; final bool isEnabled; + final _ChipVariant _chipVariant; late final ColorScheme _colors = Theme.of(context).colorScheme; late final TextTheme _textTheme = Theme.of(context).textTheme; @override - TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")}; + double? get elevation => _chipVariant == _ChipVariant.flat + ? ${elevation("$tokenGroup$flatVariant.container")} + : isEnabled ? ${elevation("$tokenGroup$elevatedVariant.container")} : ${elevation("$tokenGroup$elevatedVariant.disabled.container")}; @override - Color? get backgroundColor => ${componentColor("$tokenGroup$variant.container")}; + double? get pressElevation => ${elevation("$tokenGroup$elevatedVariant.pressed.container")}; @override - Color? get shadowColor => ${colorOrTransparent("$tokenGroup.container.shadow-color")}; + TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")}; @override - Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")}; + MaterialStateProperty<Color?>? get color => + MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.disabled)) { + return _chipVariant == _ChipVariant.flat + ? ${componentColor("$tokenGroup$flatVariant.disabled.container")} + : ${componentColor("$tokenGroup$elevatedVariant.disabled.container")}; + } + return ${componentColor("$tokenGroup$flatVariant.container")}; + }); @override - Color? get selectedColor => ${componentColor("$tokenGroup$variant.selected.container")}; + Color? get shadowColor => _chipVariant == _ChipVariant.flat + ? ${colorOrTransparent("$tokenGroup$flatVariant.container.shadow-color")} + : ${colorOrTransparent("$tokenGroup$elevatedVariant.container.shadow-color")}; @override - Color? get checkmarkColor => ${color("$tokenGroup.with-icon.selected.icon.color")}; + Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")}; @override - Color? get disabledColor => ${componentColor("$tokenGroup$variant.disabled.container")}; + Color? get checkmarkColor => ${color("$tokenGroup.with-icon.selected.icon.color")}; @override Color? get deleteIconColor => ${color("$tokenGroup.with-icon.selected.icon.color")}; @override - BorderSide? get side => isEnabled - ? ${border('$tokenGroup$variant.outline')} - : ${border('$tokenGroup$variant.disabled.outline')}; + BorderSide? get side => _chipVariant == _ChipVariant.flat + ? isEnabled + ? ${border('$tokenGroup$flatVariant.outline')} + : ${border('$tokenGroup$flatVariant.disabled.outline')} + : const BorderSide(color: Colors.transparent); @override IconThemeData? get iconTheme => IconThemeData( color: isEnabled ? ${color("$tokenGroup.with-icon.icon.color")} : ${color("$tokenGroup.with-icon.disabled.icon.color")}, - size: ${tokens["$tokenGroup.with-icon.icon.size"]}, + size: ${getToken("$tokenGroup.with-icon.icon.size")}, ); @override diff --git a/dev/tools/gen_defaults/lib/app_bar_template.dart b/dev/tools/gen_defaults/lib/app_bar_template.dart index efa22b6b35e0b..11558141b1280 100644 --- a/dev/tools/gen_defaults/lib/app_bar_template.dart +++ b/dev/tools/gen_defaults/lib/app_bar_template.dart @@ -19,7 +19,7 @@ class _${blockName}DefaultsM3 extends AppBarTheme { elevation: ${elevation('md.comp.top-app-bar.small.container')}, scrolledUnderElevation: ${elevation('md.comp.top-app-bar.small.on-scroll.container')}, titleSpacing: NavigationToolbar.kMiddleSpacing, - toolbarHeight: ${tokens['md.comp.top-app-bar.small.container.height']}, + toolbarHeight: ${getToken('md.comp.top-app-bar.small.container.height')}, ); final BuildContext context; @@ -42,13 +42,13 @@ class _${blockName}DefaultsM3 extends AppBarTheme { @override IconThemeData? get iconTheme => IconThemeData( color: ${componentColor('md.comp.top-app-bar.small.leading-icon')}, - size: ${tokens['md.comp.top-app-bar.small.leading-icon.size']}, + size: ${getToken('md.comp.top-app-bar.small.leading-icon.size')}, ); @override IconThemeData? get actionsIconTheme => IconThemeData( color: ${componentColor('md.comp.top-app-bar.small.trailing-icon')}, - size: ${tokens['md.comp.top-app-bar.small.trailing-icon.size']}, + size: ${getToken('md.comp.top-app-bar.small.trailing-icon.size')}, ); @override @@ -67,8 +67,8 @@ class _MediumScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig { late final ColorScheme _colors = _theme.colorScheme; late final TextTheme _textTheme = _theme.textTheme; - static const double collapsedHeight = ${tokens['md.comp.top-app-bar.small.container.height']}; - static const double expandedHeight = ${tokens['md.comp.top-app-bar.medium.container.height']}; + static const double collapsedHeight = ${getToken('md.comp.top-app-bar.small.container.height')}; + static const double expandedHeight = ${getToken('md.comp.top-app-bar.medium.container.height')}; @override TextStyle? get collapsedTextStyle => @@ -79,7 +79,7 @@ class _MediumScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig { ${textStyle('md.comp.top-app-bar.medium.headline')}?.apply(color: ${color('md.comp.top-app-bar.medium.headline.color')}); @override - EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20); + EdgeInsetsGeometry get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20); } class _LargeScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig { @@ -90,8 +90,8 @@ class _LargeScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig { late final ColorScheme _colors = _theme.colorScheme; late final TextTheme _textTheme = _theme.textTheme; - static const double collapsedHeight = ${tokens['md.comp.top-app-bar.small.container.height']}; - static const double expandedHeight = ${tokens['md.comp.top-app-bar.large.container.height']}; + static const double collapsedHeight = ${getToken('md.comp.top-app-bar.small.container.height')}; + static const double expandedHeight = ${getToken('md.comp.top-app-bar.large.container.height')}; @override TextStyle? get collapsedTextStyle => @@ -102,7 +102,7 @@ class _LargeScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig { ${textStyle('md.comp.top-app-bar.large.headline')}?.apply(color: ${color('md.comp.top-app-bar.large.headline.color')}); @override - EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28); + EdgeInsetsGeometry get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28); } '''; } diff --git a/dev/tools/gen_defaults/lib/badge_template.dart b/dev/tools/gen_defaults/lib/badge_template.dart index 38bec9e076460..9aedd4d361391 100644 --- a/dev/tools/gen_defaults/lib/badge_template.dart +++ b/dev/tools/gen_defaults/lib/badge_template.dart @@ -13,8 +13,8 @@ class BadgeTemplate extends TokenTemplate { String generate() => ''' class _${blockName}DefaultsM3 extends BadgeThemeData { _${blockName}DefaultsM3(this.context) : super( - smallSize: ${tokens["md.comp.badge.size"]}, - largeSize: ${tokens["md.comp.badge.large.size"]}, + smallSize: ${getToken("md.comp.badge.size")}, + largeSize: ${getToken("md.comp.badge.large.size")}, padding: const EdgeInsets.symmetric(horizontal: 4), alignment: AlignmentDirectional.topEnd, ); diff --git a/dev/tools/gen_defaults/lib/bottom_app_bar_template.dart b/dev/tools/gen_defaults/lib/bottom_app_bar_template.dart index 8edcff2275fba..00e0e987127d8 100644 --- a/dev/tools/gen_defaults/lib/bottom_app_bar_template.dart +++ b/dev/tools/gen_defaults/lib/bottom_app_bar_template.dart @@ -15,7 +15,7 @@ class _${blockName}DefaultsM3 extends BottomAppBarTheme { _${blockName}DefaultsM3(this.context) : super( elevation: ${elevation('md.comp.bottom-app-bar.container')}, - height: ${tokens['md.comp.bottom-app-bar.container.height']}, + height: ${getToken('md.comp.bottom-app-bar.container.height')}, shape: const AutomaticNotchedShape(${shape('md.comp.bottom-app-bar.container', '')}), ); diff --git a/dev/tools/gen_defaults/lib/button_template.dart b/dev/tools/gen_defaults/lib/button_template.dart index e109bc39c9642..7d4c65c05b7ce 100644 --- a/dev/tools/gen_defaults/lib/button_template.dart +++ b/dev/tools/gen_defaults/lib/button_template.dart @@ -12,7 +12,7 @@ class ButtonTemplate extends TokenTemplate { final String tokenGroup; String _backgroundColor() { - if (tokens.containsKey('$tokenGroup.container.color')) { + if (tokenAvailable('$tokenGroup.container.color')) { return ''' MaterialStateProperty.resolveWith((Set<MaterialState> states) { @@ -28,7 +28,7 @@ class ButtonTemplate extends TokenTemplate { } String _elevation() { - if (tokens.containsKey('$tokenGroup.container.elevation')) { + if (tokenAvailable('$tokenGroup.container.elevation')) { return ''' MaterialStateProperty.resolveWith((Set<MaterialState> states) { @@ -53,7 +53,7 @@ class ButtonTemplate extends TokenTemplate { } String _elevationColor(String token) { - if (tokens.containsKey(token)) { + if (tokenAvailable(token)) { return 'MaterialStatePropertyAll<Color>(${color(token)})'; } else { return 'const MaterialStatePropertyAll<Color>(Colors.transparent)'; @@ -121,7 +121,7 @@ class _${blockName}DefaultsM3 extends ButtonStyle { @override MaterialStateProperty<Size>? get minimumSize => - const MaterialStatePropertyAll<Size>(Size(64.0, ${tokens["$tokenGroup.container.height"]})); + const MaterialStatePropertyAll<Size>(Size(64.0, ${getToken("$tokenGroup.container.height")})); // No default fixedSize @@ -129,7 +129,7 @@ class _${blockName}DefaultsM3 extends ButtonStyle { MaterialStateProperty<Size>? get maximumSize => const MaterialStatePropertyAll<Size>(Size.infinite); -${tokens.containsKey("$tokenGroup.outline.color") ? ''' +${tokenAvailable("$tokenGroup.outline.color") ? ''' @override MaterialStateProperty<BorderSide>? get side => MaterialStateProperty.resolveWith((Set<MaterialState> states) { diff --git a/dev/tools/gen_defaults/lib/checkbox_template.dart b/dev/tools/gen_defaults/lib/checkbox_template.dart index f11617c597ca0..a641dfe3ca38c 100644 --- a/dev/tools/gen_defaults/lib/checkbox_template.dart +++ b/dev/tools/gen_defaults/lib/checkbox_template.dart @@ -24,26 +24,26 @@ class _${blockName}DefaultsM3 extends CheckboxThemeData { return MaterialStateBorderSide.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.disabled)) { if (states.contains(MaterialState.selected)) { - return const BorderSide(width: ${tokens['md.comp.checkbox.unselected.disabled.outline.width']}, color: Colors.transparent); + return const BorderSide(width: ${getToken('md.comp.checkbox.unselected.disabled.outline.width')}, color: Colors.transparent); } - return BorderSide(width: ${tokens['md.comp.checkbox.unselected.disabled.outline.width']}, color: ${componentColor('md.comp.checkbox.unselected.disabled.outline')}.withOpacity(${tokens['md.comp.checkbox.unselected.disabled.container.opacity']})); + return BorderSide(width: ${getToken('md.comp.checkbox.unselected.disabled.outline.width')}, color: ${componentColor('md.comp.checkbox.unselected.disabled.outline')}.withOpacity(${getToken('md.comp.checkbox.unselected.disabled.container.opacity')})); } if (states.contains(MaterialState.selected)) { - return const BorderSide(width: ${tokens['md.comp.checkbox.selected.outline.width']}, color: Colors.transparent); + return const BorderSide(width: ${getToken('md.comp.checkbox.selected.outline.width')}, color: Colors.transparent); } if (states.contains(MaterialState.error)) { - return BorderSide(width: ${tokens['md.comp.checkbox.unselected.disabled.outline.width']}, color: ${componentColor('md.comp.checkbox.unselected.error.outline')}); + return BorderSide(width: ${getToken('md.comp.checkbox.unselected.disabled.outline.width')}, color: ${componentColor('md.comp.checkbox.unselected.error.outline')}); } if (states.contains(MaterialState.pressed)) { - return BorderSide(width: ${tokens['md.comp.checkbox.unselected.pressed.outline.width']}, color: ${componentColor('md.comp.checkbox.unselected.pressed.outline')}); + return BorderSide(width: ${getToken('md.comp.checkbox.unselected.pressed.outline.width')}, color: ${componentColor('md.comp.checkbox.unselected.pressed.outline')}); } if (states.contains(MaterialState.hovered)) { - return BorderSide(width: ${tokens['md.comp.checkbox.unselected.hover.outline.width']}, color: ${componentColor('md.comp.checkbox.unselected.hover.outline')}); + return BorderSide(width: ${getToken('md.comp.checkbox.unselected.hover.outline.width')}, color: ${componentColor('md.comp.checkbox.unselected.hover.outline')}); } if (states.contains(MaterialState.focused)) { - return BorderSide(width: ${tokens['md.comp.checkbox.unselected.focus.outline.width']}, color: ${componentColor('md.comp.checkbox.unselected.focus.outline')}); + return BorderSide(width: ${getToken('md.comp.checkbox.unselected.focus.outline.width')}, color: ${componentColor('md.comp.checkbox.unselected.focus.outline')}); } - return BorderSide(width: ${tokens['md.comp.checkbox.unselected.outline.width']}, color: ${componentColor('md.comp.checkbox.unselected.outline')}); + return BorderSide(width: ${getToken('md.comp.checkbox.unselected.outline.width')}, color: ${componentColor('md.comp.checkbox.unselected.outline')}); }); } @@ -125,7 +125,7 @@ class _${blockName}DefaultsM3 extends CheckboxThemeData { } @override - double get splashRadius => ${tokens['md.comp.checkbox.state-layer.size']} / 2; + double get splashRadius => ${getToken('md.comp.checkbox.state-layer.size')} / 2; @override MaterialTapTargetSize get materialTapTargetSize => _theme.materialTapTargetSize; @@ -135,7 +135,7 @@ class _${blockName}DefaultsM3 extends CheckboxThemeData { @override OutlinedBorder get shape => const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(${tokens['md.comp.checkbox.unselected.outline.width']})), + borderRadius: BorderRadius.all(Radius.circular(${getToken('md.comp.checkbox.unselected.outline.width')})), ); } '''; diff --git a/dev/tools/gen_defaults/lib/chip_template.dart b/dev/tools/gen_defaults/lib/chip_template.dart new file mode 100644 index 0000000000000..8d9c05b7ccb73 --- /dev/null +++ b/dev/tools/gen_defaults/lib/chip_template.dart @@ -0,0 +1,77 @@ +// 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 'template.dart'; + +class ChipTemplate extends TokenTemplate { + const ChipTemplate(super.blockName, super.fileName, super.tokens, { + super.colorSchemePrefix = '_colors.', + super.textThemePrefix = '_textTheme.' + }); + + static const String tokenGroup = 'md.comp.assist-chip'; + static const String variant = '.flat'; + + @override + String generate() => ''' +class _${blockName}DefaultsM3 extends ChipThemeData { + _${blockName}DefaultsM3(this.context, this.isEnabled) + : super( + elevation: ${elevation("$tokenGroup$variant.container")}, + shape: ${shape("$tokenGroup.container")}, + showCheckmark: true, + ); + + final BuildContext context; + final bool isEnabled; + late final ColorScheme _colors = Theme.of(context).colorScheme; + late final TextTheme _textTheme = Theme.of(context).textTheme; + + @override + TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")}; + + @override + MaterialStateProperty<Color?>? get color => null; // Subclasses override this getter + + @override + Color? get shadowColor => ${colorOrTransparent("$tokenGroup.container.shadow-color")}; + + @override + Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")}; + + @override + Color? get checkmarkColor => ${color("$tokenGroup.with-icon.selected.icon.color")}; + + @override + Color? get deleteIconColor => ${color("$tokenGroup.with-icon.selected.icon.color")}; + + @override + BorderSide? get side => isEnabled + ? ${border('$tokenGroup$variant.outline')} + : ${border('$tokenGroup$variant.disabled.outline')}; + + @override + IconThemeData? get iconTheme => IconThemeData( + color: isEnabled + ? ${color("$tokenGroup.with-icon.icon.color")} + : ${color("$tokenGroup.with-icon.disabled.icon.color")}, + size: ${getToken("$tokenGroup.with-icon.icon.size")}, + ); + + @override + EdgeInsetsGeometry? get padding => const EdgeInsets.all(8.0); + + /// The chip at text scale 1 starts with 8px on each side and as text scaling + /// gets closer to 2, the label padding is linearly interpolated from 8px to 4px. + /// Once the widget has a text scaling of 2 or higher than the label padding + /// remains 4px. + @override + EdgeInsetsGeometry? get labelPadding => EdgeInsets.lerp( + const EdgeInsets.symmetric(horizontal: 8.0), + const EdgeInsets.symmetric(horizontal: 4.0), + clampDouble(MediaQuery.textScaleFactorOf(context) - 1.0, 0.0, 1.0), + )!; +} +'''; +} diff --git a/dev/tools/gen_defaults/lib/color_scheme_template.dart b/dev/tools/gen_defaults/lib/color_scheme_template.dart index 1e5ccd94a4c7e..a09ad6e4a2b18 100644 --- a/dev/tools/gen_defaults/lib/color_scheme_template.dart +++ b/dev/tools/gen_defaults/lib/color_scheme_template.dart @@ -3,86 +3,97 @@ // found in the LICENSE file. import 'template.dart'; +import 'token_logger.dart'; class ColorSchemeTemplate extends TokenTemplate { - ColorSchemeTemplate(super.blockName, super.fileName, super.tokens); + ColorSchemeTemplate(this._colorTokensLight, this._colorTokensDark, super.blockName, super.fileName, super.tokens); // Map of light color scheme token data from tokens. - late Map<String, dynamic> colorTokensLight = tokens['colorsLight'] as Map<String, dynamic>; + final Map<String, dynamic> _colorTokensLight; // Map of dark color scheme token data from tokens. - late Map<String, dynamic> colorTokensDark = tokens['colorsDark'] as Map<String, dynamic>; + final Map<String, dynamic> _colorTokensDark; + + dynamic light(String tokenName) { + tokenLogger.log(tokenName); + return getToken(_colorTokensLight[tokenName] as String); + } + + dynamic dark(String tokenName) { + tokenLogger.log(tokenName); + return getToken(_colorTokensDark[tokenName] as String); + } @override String generate() => ''' const ColorScheme _colorSchemeLightM3 = ColorScheme( brightness: Brightness.light, - primary: Color(${tokens[colorTokensLight['md.sys.color.primary']]}), - onPrimary: Color(${tokens[colorTokensLight['md.sys.color.on-primary']]}), - primaryContainer: Color(${tokens[colorTokensLight['md.sys.color.primary-container']]}), - onPrimaryContainer: Color(${tokens[colorTokensLight['md.sys.color.on-primary-container']]}), - secondary: Color(${tokens[colorTokensLight['md.sys.color.secondary']]}), - onSecondary: Color(${tokens[colorTokensLight['md.sys.color.on-secondary']]}), - secondaryContainer: Color(${tokens[colorTokensLight['md.sys.color.secondary-container']]}), - onSecondaryContainer: Color(${tokens[colorTokensLight['md.sys.color.on-secondary-container']]}), - tertiary: Color(${tokens[colorTokensLight['md.sys.color.tertiary']]}), - onTertiary: Color(${tokens[colorTokensLight['md.sys.color.on-tertiary']]}), - tertiaryContainer: Color(${tokens[colorTokensLight['md.sys.color.tertiary-container']]}), - onTertiaryContainer: Color(${tokens[colorTokensLight['md.sys.color.on-tertiary-container']]}), - error: Color(${tokens[colorTokensLight['md.sys.color.error']]}), - onError: Color(${tokens[colorTokensLight['md.sys.color.on-error']]}), - errorContainer: Color(${tokens[colorTokensLight['md.sys.color.error-container']]}), - onErrorContainer: Color(${tokens[colorTokensLight['md.sys.color.on-error-container']]}), - background: Color(${tokens[colorTokensLight['md.sys.color.background']]}), - onBackground: Color(${tokens[colorTokensLight['md.sys.color.on-background']]}), - surface: Color(${tokens[colorTokensLight['md.sys.color.surface']]}), - onSurface: Color(${tokens[colorTokensLight['md.sys.color.on-surface']]}), - surfaceVariant: Color(${tokens[colorTokensLight['md.sys.color.surface-variant']]}), - onSurfaceVariant: Color(${tokens[colorTokensLight['md.sys.color.on-surface-variant']]}), - outline: Color(${tokens[colorTokensLight['md.sys.color.outline']]}), - outlineVariant: Color(${tokens[colorTokensLight['md.sys.color.outline-variant']]}), - shadow: Color(${tokens[colorTokensLight['md.sys.color.shadow']]}), - scrim: Color(${tokens[colorTokensLight['md.sys.color.scrim']]}), - inverseSurface: Color(${tokens[colorTokensLight['md.sys.color.inverse-surface']]}), - onInverseSurface: Color(${tokens[colorTokensLight['md.sys.color.inverse-on-surface']]}), - inversePrimary: Color(${tokens[colorTokensLight['md.sys.color.inverse-primary']]}), + primary: Color(${light('md.sys.color.primary')}), + onPrimary: Color(${light('md.sys.color.on-primary')}), + primaryContainer: Color(${light('md.sys.color.primary-container')}), + onPrimaryContainer: Color(${light('md.sys.color.on-primary-container')}), + secondary: Color(${light('md.sys.color.secondary')}), + onSecondary: Color(${light('md.sys.color.on-secondary')}), + secondaryContainer: Color(${light('md.sys.color.secondary-container')}), + onSecondaryContainer: Color(${light('md.sys.color.on-secondary-container')}), + tertiary: Color(${light('md.sys.color.tertiary')}), + onTertiary: Color(${light('md.sys.color.on-tertiary')}), + tertiaryContainer: Color(${light('md.sys.color.tertiary-container')}), + onTertiaryContainer: Color(${light('md.sys.color.on-tertiary-container')}), + error: Color(${light('md.sys.color.error')}), + onError: Color(${light('md.sys.color.on-error')}), + errorContainer: Color(${light('md.sys.color.error-container')}), + onErrorContainer: Color(${light('md.sys.color.on-error-container')}), + background: Color(${light('md.sys.color.background')}), + onBackground: Color(${light('md.sys.color.on-background')}), + surface: Color(${light('md.sys.color.surface')}), + onSurface: Color(${light('md.sys.color.on-surface')}), + surfaceVariant: Color(${light('md.sys.color.surface-variant')}), + onSurfaceVariant: Color(${light('md.sys.color.on-surface-variant')}), + outline: Color(${light('md.sys.color.outline')}), + outlineVariant: Color(${light('md.sys.color.outline-variant')}), + shadow: Color(${light('md.sys.color.shadow')}), + scrim: Color(${light('md.sys.color.scrim')}), + inverseSurface: Color(${light('md.sys.color.inverse-surface')}), + onInverseSurface: Color(${light('md.sys.color.inverse-on-surface')}), + inversePrimary: Color(${light('md.sys.color.inverse-primary')}), // The surfaceTint color is set to the same color as the primary. - surfaceTint: Color(${tokens[colorTokensLight['md.sys.color.primary']]}), + surfaceTint: Color(${light('md.sys.color.primary')}), ); const ColorScheme _colorSchemeDarkM3 = ColorScheme( brightness: Brightness.dark, - primary: Color(${tokens[colorTokensDark['md.sys.color.primary']]}), - onPrimary: Color(${tokens[colorTokensDark['md.sys.color.on-primary']]}), - primaryContainer: Color(${tokens[colorTokensDark['md.sys.color.primary-container']]}), - onPrimaryContainer: Color(${tokens[colorTokensDark['md.sys.color.on-primary-container']]}), - secondary: Color(${tokens[colorTokensDark['md.sys.color.secondary']]}), - onSecondary: Color(${tokens[colorTokensDark['md.sys.color.on-secondary']]}), - secondaryContainer: Color(${tokens[colorTokensDark['md.sys.color.secondary-container']]}), - onSecondaryContainer: Color(${tokens[colorTokensDark['md.sys.color.on-secondary-container']]}), - tertiary: Color(${tokens[colorTokensDark['md.sys.color.tertiary']]}), - onTertiary: Color(${tokens[colorTokensDark['md.sys.color.on-tertiary']]}), - tertiaryContainer: Color(${tokens[colorTokensDark['md.sys.color.tertiary-container']]}), - onTertiaryContainer: Color(${tokens[colorTokensDark['md.sys.color.on-tertiary-container']]}), - error: Color(${tokens[colorTokensDark['md.sys.color.error']]}), - onError: Color(${tokens[colorTokensDark['md.sys.color.on-error']]}), - errorContainer: Color(${tokens[colorTokensDark['md.sys.color.error-container']]}), - onErrorContainer: Color(${tokens[colorTokensDark['md.sys.color.on-error-container']]}), - background: Color(${tokens[colorTokensDark['md.sys.color.background']]}), - onBackground: Color(${tokens[colorTokensDark['md.sys.color.on-background']]}), - surface: Color(${tokens[colorTokensDark['md.sys.color.surface']]}), - onSurface: Color(${tokens[colorTokensDark['md.sys.color.on-surface']]}), - surfaceVariant: Color(${tokens[colorTokensDark['md.sys.color.surface-variant']]}), - onSurfaceVariant: Color(${tokens[colorTokensDark['md.sys.color.on-surface-variant']]}), - outline: Color(${tokens[colorTokensDark['md.sys.color.outline']]}), - outlineVariant: Color(${tokens[colorTokensDark['md.sys.color.outline-variant']]}), - shadow: Color(${tokens[colorTokensDark['md.sys.color.shadow']]}), - scrim: Color(${tokens[colorTokensDark['md.sys.color.scrim']]}), - inverseSurface: Color(${tokens[colorTokensDark['md.sys.color.inverse-surface']]}), - onInverseSurface: Color(${tokens[colorTokensDark['md.sys.color.inverse-on-surface']]}), - inversePrimary: Color(${tokens[colorTokensDark['md.sys.color.inverse-primary']]}), + primary: Color(${dark('md.sys.color.primary')}), + onPrimary: Color(${dark('md.sys.color.on-primary')}), + primaryContainer: Color(${dark('md.sys.color.primary-container')}), + onPrimaryContainer: Color(${dark('md.sys.color.on-primary-container')}), + secondary: Color(${dark('md.sys.color.secondary')}), + onSecondary: Color(${dark('md.sys.color.on-secondary')}), + secondaryContainer: Color(${dark('md.sys.color.secondary-container')}), + onSecondaryContainer: Color(${dark('md.sys.color.on-secondary-container')}), + tertiary: Color(${dark('md.sys.color.tertiary')}), + onTertiary: Color(${dark('md.sys.color.on-tertiary')}), + tertiaryContainer: Color(${dark('md.sys.color.tertiary-container')}), + onTertiaryContainer: Color(${dark('md.sys.color.on-tertiary-container')}), + error: Color(${dark('md.sys.color.error')}), + onError: Color(${dark('md.sys.color.on-error')}), + errorContainer: Color(${dark('md.sys.color.error-container')}), + onErrorContainer: Color(${dark('md.sys.color.on-error-container')}), + background: Color(${dark('md.sys.color.background')}), + onBackground: Color(${dark('md.sys.color.on-background')}), + surface: Color(${dark('md.sys.color.surface')}), + onSurface: Color(${dark('md.sys.color.on-surface')}), + surfaceVariant: Color(${dark('md.sys.color.surface-variant')}), + onSurfaceVariant: Color(${dark('md.sys.color.on-surface-variant')}), + outline: Color(${dark('md.sys.color.outline')}), + outlineVariant: Color(${dark('md.sys.color.outline-variant')}), + shadow: Color(${dark('md.sys.color.shadow')}), + scrim: Color(${dark('md.sys.color.scrim')}), + inverseSurface: Color(${dark('md.sys.color.inverse-surface')}), + onInverseSurface: Color(${dark('md.sys.color.inverse-on-surface')}), + inversePrimary: Color(${dark('md.sys.color.inverse-primary')}), // The surfaceTint color is set to the same color as the primary. - surfaceTint: Color(${tokens[colorTokensDark['md.sys.color.primary']]}), + surfaceTint: Color(${dark('md.sys.color.primary')}), ); '''; } diff --git a/dev/tools/gen_defaults/lib/date_picker_template.dart b/dev/tools/gen_defaults/lib/date_picker_template.dart index 144182e95e2b2..6f45fdf35f67b 100644 --- a/dev/tools/gen_defaults/lib/date_picker_template.dart +++ b/dev/tools/gen_defaults/lib/date_picker_template.dart @@ -11,10 +11,10 @@ class DatePickerTemplate extends TokenTemplate { }); String _layerOpacity(String layerToken) { - if (tokens.containsKey(layerToken)) { - final String? layerValue = tokens[layerToken] as String?; - if (tokens.containsKey(layerValue)) { - final String? opacityValue = opacity(layerValue!); + if (tokenAvailable(layerToken)) { + final String layerValue = getToken(layerToken) as String; + if (tokenAvailable(layerValue)) { + final String? opacityValue = opacity(layerValue); if (opacityValue != null) { return '.withOpacity($opacityValue)'; } diff --git a/dev/tools/gen_defaults/lib/divider_template.dart b/dev/tools/gen_defaults/lib/divider_template.dart index 03ed0202c6a67..1c8a70bf4ae43 100644 --- a/dev/tools/gen_defaults/lib/divider_template.dart +++ b/dev/tools/gen_defaults/lib/divider_template.dart @@ -12,7 +12,7 @@ class DividerTemplate extends TokenTemplate { class _${blockName}DefaultsM3 extends DividerThemeData { const _${blockName}DefaultsM3(this.context) : super( space: 16, - thickness: ${tokens["md.comp.divider.thickness"]}, + thickness: ${getToken("md.comp.divider.thickness")}, indent: 0, endIndent: 0, ); diff --git a/dev/tools/gen_defaults/lib/fab_template.dart b/dev/tools/gen_defaults/lib/fab_template.dart index 9d9f8c1fb2af5..63f42c7e74d9c 100644 --- a/dev/tools/gen_defaults/lib/fab_template.dart +++ b/dev/tools/gen_defaults/lib/fab_template.dart @@ -21,19 +21,19 @@ class _${blockName}DefaultsM3 extends FloatingActionButtonThemeData { highlightElevation: ${elevation("md.comp.fab.primary.pressed.container")}, enableFeedback: true, sizeConstraints: const BoxConstraints.tightFor( - width: ${tokens["md.comp.fab.primary.container.width"]}, - height: ${tokens["md.comp.fab.primary.container.height"]}, + width: ${getToken("md.comp.fab.primary.container.width")}, + height: ${getToken("md.comp.fab.primary.container.height")}, ), smallSizeConstraints: const BoxConstraints.tightFor( - width: ${tokens["md.comp.fab.primary.small.container.width"]}, - height: ${tokens["md.comp.fab.primary.small.container.height"]}, + width: ${getToken("md.comp.fab.primary.small.container.width")}, + height: ${getToken("md.comp.fab.primary.small.container.height")}, ), largeSizeConstraints: const BoxConstraints.tightFor( - width: ${tokens["md.comp.fab.primary.large.container.width"]}, - height: ${tokens["md.comp.fab.primary.large.container.height"]}, + width: ${getToken("md.comp.fab.primary.large.container.width")}, + height: ${getToken("md.comp.fab.primary.large.container.height")}, ), extendedSizeConstraints: const BoxConstraints.tightFor( - height: ${tokens["md.comp.extended-fab.primary.container.height"]}, + height: ${getToken("md.comp.extended-fab.primary.container.height")}, ), extendedIconLabelSpacing: 8.0, ); @@ -69,10 +69,10 @@ class _${blockName}DefaultsM3 extends FloatingActionButtonThemeData { @override double? get iconSize { switch (type) { - case _FloatingActionButtonType.regular: return ${tokens["md.comp.fab.primary.icon.size"]}; - case _FloatingActionButtonType.small: return ${tokens["md.comp.fab.primary.small.icon.size"]}; - case _FloatingActionButtonType.large: return ${tokens["md.comp.fab.primary.large.icon.size"]}; - case _FloatingActionButtonType.extended: return ${tokens["md.comp.extended-fab.primary.icon.size"]}; + case _FloatingActionButtonType.regular: return ${getToken("md.comp.fab.primary.icon.size")}; + case _FloatingActionButtonType.small: return ${getToken("md.comp.fab.primary.small.icon.size")}; + case _FloatingActionButtonType.large: return ${getToken("md.comp.fab.primary.large.icon.size")}; + case _FloatingActionButtonType.extended: return ${getToken("md.comp.extended-fab.primary.icon.size")}; } } diff --git a/dev/tools/gen_defaults/lib/filter_chip_template.dart b/dev/tools/gen_defaults/lib/filter_chip_template.dart index 7840086d13347..6609613cde24e 100644 --- a/dev/tools/gen_defaults/lib/filter_chip_template.dart +++ b/dev/tools/gen_defaults/lib/filter_chip_template.dart @@ -11,14 +11,18 @@ class FilterChipTemplate extends TokenTemplate { }); static const String tokenGroup = 'md.comp.filter-chip'; - static const String variant = '.flat'; + static const String flatVariant = '.flat'; + static const String elevatedVariant = '.elevated'; @override String generate() => ''' class _${blockName}DefaultsM3 extends ChipThemeData { - _${blockName}DefaultsM3(this.context, this.isEnabled, this.isSelected) - : super( - elevation: ${elevation("$tokenGroup$variant.container")}, + _${blockName}DefaultsM3( + this.context, + this.isEnabled, + this.isSelected, + this._chipVariant, + ) : super( shape: ${shape("$tokenGroup.container")}, showCheckmark: true, ); @@ -26,42 +30,63 @@ class _${blockName}DefaultsM3 extends ChipThemeData { final BuildContext context; final bool isEnabled; final bool isSelected; + final _ChipVariant _chipVariant; late final ColorScheme _colors = Theme.of(context).colorScheme; late final TextTheme _textTheme = Theme.of(context).textTheme; @override - TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")}; + double? get elevation => _chipVariant == _ChipVariant.flat + ? ${elevation("$tokenGroup$flatVariant.container")} + : isEnabled ? ${elevation("$tokenGroup$elevatedVariant.container")} : ${elevation("$tokenGroup$elevatedVariant.disabled.container")}; @override - Color? get backgroundColor => ${componentColor("$tokenGroup$variant.container")}; + double? get pressElevation => ${elevation("$tokenGroup$elevatedVariant.pressed.container")}; @override - Color? get shadowColor => ${colorOrTransparent("$tokenGroup.container.shadow-color")}; + TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")}; @override - Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")}; + MaterialStateProperty<Color?>? get color => + MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) { + return _chipVariant == _ChipVariant.flat + ? ${componentColor("$tokenGroup$flatVariant.disabled.selected.container")} + : ${componentColor("$tokenGroup$elevatedVariant.disabled.container")}; + } + if (states.contains(MaterialState.disabled)) { + return _chipVariant == _ChipVariant.flat + ? ${componentColor("$tokenGroup$flatVariant.disabled.unselected.container")} + : ${componentColor("$tokenGroup$elevatedVariant.disabled.container")}; + } + if (states.contains(MaterialState.selected)) { + return _chipVariant == _ChipVariant.flat + ? ${componentColor("$tokenGroup$flatVariant.selected.container")} + : ${componentColor("$tokenGroup$elevatedVariant.selected.container")}; + } + return _chipVariant == _ChipVariant.flat + ? ${componentColor("$tokenGroup$flatVariant.container")} + : ${componentColor("$tokenGroup$elevatedVariant.container")}; + }); @override - Color? get selectedColor => isEnabled - ? ${componentColor("$tokenGroup$variant.selected.container")} - : ${componentColor("$tokenGroup$variant.disabled.selected.container")}; + Color? get shadowColor => _chipVariant == _ChipVariant.flat + ? ${colorOrTransparent("$tokenGroup$flatVariant.container.shadow-color")} + : ${colorOrTransparent("$tokenGroup$elevatedVariant.container.shadow-color")}; @override - Color? get checkmarkColor => ${color("$tokenGroup.with-leading-icon.selected.leading-icon.color")}; + Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")}; @override - Color? get disabledColor => isSelected - ? ${componentColor("$tokenGroup$variant.disabled.selected.container")} - : ${componentColor("$tokenGroup$variant.disabled.unselected.container")}; + Color? get checkmarkColor => ${color("$tokenGroup.with-leading-icon.selected.leading-icon.color")}; @override Color? get deleteIconColor => ${color("$tokenGroup.with-trailing-icon.selected.trailing-icon.color")}; @override - BorderSide? get side => !isSelected + BorderSide? get side => _chipVariant == _ChipVariant.flat && !isSelected ? isEnabled - ? ${border('$tokenGroup$variant.unselected.outline')} - : ${border('$tokenGroup$variant.disabled.unselected.outline')} + ? ${border('$tokenGroup$flatVariant.unselected.outline')} + : ${border('$tokenGroup$flatVariant.disabled.unselected.outline')} : const BorderSide(color: Colors.transparent); @override @@ -69,7 +94,7 @@ class _${blockName}DefaultsM3 extends ChipThemeData { color: isEnabled ? ${color("$tokenGroup.with-icon.icon.color")} : ${color("$tokenGroup.with-leading-icon.disabled.leading-icon.color")}, - size: ${tokens["$tokenGroup.with-icon.icon.size"]}, + size: ${getToken("$tokenGroup.with-icon.icon.size")}, ); @override diff --git a/dev/tools/gen_defaults/lib/icon_button_template.dart b/dev/tools/gen_defaults/lib/icon_button_template.dart index 1455b1534f219..7031fd3cce255 100644 --- a/dev/tools/gen_defaults/lib/icon_button_template.dart +++ b/dev/tools/gen_defaults/lib/icon_button_template.dart @@ -185,19 +185,19 @@ class IconButtonTemplate extends TokenTemplate { } String _minimumSize() { - if (tokens.containsKey('$tokenGroup.container.size')) { + if (tokenAvailable('$tokenGroup.container.size')) { return ''' - const MaterialStatePropertyAll<Size>(Size(${tokens['$tokenGroup.container.size']}, ${tokens['$tokenGroup.container.size']}))'''; + const MaterialStatePropertyAll<Size>(Size(${getToken('$tokenGroup.container.size')}, ${getToken('$tokenGroup.container.size')}))'''; } else { return ''' - const MaterialStatePropertyAll<Size>(Size(${tokens['$tokenGroup.state-layer.size']}, ${tokens['$tokenGroup.state-layer.size']}))'''; + const MaterialStatePropertyAll<Size>(Size(${getToken('$tokenGroup.state-layer.size')}, ${getToken('$tokenGroup.state-layer.size')}))'''; } } String _shape() { - if (tokens.containsKey('$tokenGroup.container.shape')) { + if (tokenAvailable('$tokenGroup.container.shape')) { return ''' const MaterialStatePropertyAll<OutlinedBorder>(${shape("$tokenGroup.container", "")})'''; @@ -209,7 +209,7 @@ class IconButtonTemplate extends TokenTemplate { } String _side() { - if (tokens.containsKey('$tokenGroup.unselected.outline.color')) { + if (tokenAvailable('$tokenGroup.unselected.outline.color')) { return ''' MaterialStateProperty.resolveWith((Set<MaterialState> states) { @@ -227,7 +227,7 @@ class IconButtonTemplate extends TokenTemplate { } String _elevationColor(String token) { - if (tokens.containsKey(token)) { + if (tokenAvailable(token)) { return 'MaterialStatePropertyAll<Color>(${color(token)})'; } else { return 'const MaterialStatePropertyAll<Color>(Colors.transparent)'; @@ -286,7 +286,7 @@ class _${blockName}DefaultsM3 extends ButtonStyle { @override MaterialStateProperty<double>? get iconSize => - const MaterialStatePropertyAll<double>(${tokens["$tokenGroup.icon.size"]}); + const MaterialStatePropertyAll<double>(${getToken("$tokenGroup.icon.size")}); @override MaterialStateProperty<BorderSide?>? get side =>${_side()}; diff --git a/dev/tools/gen_defaults/lib/input_chip_template.dart b/dev/tools/gen_defaults/lib/input_chip_template.dart index 577211987835f..245a171b9073d 100644 --- a/dev/tools/gen_defaults/lib/input_chip_template.dart +++ b/dev/tools/gen_defaults/lib/input_chip_template.dart @@ -33,7 +33,19 @@ class _${blockName}DefaultsM3 extends ChipThemeData { TextStyle? get labelStyle => ${textStyle("$tokenGroup.label-text")}; @override - Color? get backgroundColor => ${componentColor("$tokenGroup$variant.container")}; + MaterialStateProperty<Color?>? get color => + MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) { + return ${componentColor("$tokenGroup$variant.disabled.selected.container")}; + } + if (states.contains(MaterialState.disabled)) { + return ${componentColor("$tokenGroup$variant.disabled.container")}; + } + if (states.contains(MaterialState.selected)) { + return ${componentColor("$tokenGroup$variant.selected.container")}; + } + return ${componentColor("$tokenGroup$variant.container")}; + }); @override Color? get shadowColor => ${colorOrTransparent("$tokenGroup.container.shadow-color")}; @@ -41,17 +53,9 @@ class _${blockName}DefaultsM3 extends ChipThemeData { @override Color? get surfaceTintColor => ${colorOrTransparent("$tokenGroup.container.surface-tint-layer.color")}; - @override - Color? get selectedColor => isEnabled - ? ${componentColor("$tokenGroup$variant.selected.container")} - : ${componentColor("$tokenGroup$variant.disabled.selected.container")}; - @override Color? get checkmarkColor => ${color("$tokenGroup.with-icon.selected.icon.color")}; - @override - Color? get disabledColor => ${componentColor("$tokenGroup$variant.disabled.container")}; - @override Color? get deleteIconColor => ${color("$tokenGroup.with-trailing-icon.selected.trailing-icon.color")}; @@ -67,7 +71,7 @@ class _${blockName}DefaultsM3 extends ChipThemeData { color: isEnabled ? ${color("$tokenGroup.with-leading-icon.leading-icon.color")} : ${color("$tokenGroup.with-leading-icon.disabled.leading-icon.color")}, - size: ${tokens["$tokenGroup.with-leading-icon.leading-icon.size"]}, + size: ${getToken("$tokenGroup.with-leading-icon.leading-icon.size")}, ); @override diff --git a/dev/tools/gen_defaults/lib/input_decorator_template.dart b/dev/tools/gen_defaults/lib/input_decorator_template.dart index b5daa3bb60f7d..6c7363bf11465 100644 --- a/dev/tools/gen_defaults/lib/input_decorator_template.dart +++ b/dev/tools/gen_defaults/lib/input_decorator_template.dart @@ -215,10 +215,10 @@ class _${blockName}DefaultsM3 extends InputDecorationTheme { ? componentColor(componentToken1) : componentColor(componentToken2); final double width = ( - tokens['$componentToken1.width'] ?? - tokens['$componentToken1.height'] ?? - tokens['$componentToken2.width'] ?? - tokens['$componentToken2.height'] ?? + getToken('$componentToken1.width') ?? + getToken('$componentToken1.height') ?? + getToken('$componentToken2.width') ?? + getToken('$componentToken2.height') ?? 1.0) as double; return 'BorderSide(color: $borderColor${width != 1.0 ? ", width: $width" : ""})'; } diff --git a/dev/tools/gen_defaults/lib/list_tile_template.dart b/dev/tools/gen_defaults/lib/list_tile_template.dart index 728f8f310f9c6..1832fc0d2789b 100644 --- a/dev/tools/gen_defaults/lib/list_tile_template.dart +++ b/dev/tools/gen_defaults/lib/list_tile_template.dart @@ -10,6 +10,8 @@ class ListTileTemplate extends TokenTemplate { super.textThemePrefix = '_textTheme.', }); + static const String tokenGroup = 'md.comp.list.list-item'; + @override String generate() => ''' class _${blockName}DefaultsM3 extends ListTileThemeData { @@ -18,7 +20,7 @@ class _${blockName}DefaultsM3 extends ListTileThemeData { contentPadding: const EdgeInsetsDirectional.only(start: 16.0, end: 24.0), minLeadingWidth: 24, minVerticalPadding: 8, - shape: ${shape("md.comp.list.list-item.container")}, + shape: ${shape("$tokenGroup.container")}, ); final BuildContext context; @@ -30,19 +32,19 @@ class _${blockName}DefaultsM3 extends ListTileThemeData { Color? get tileColor => Colors.transparent; @override - TextStyle? get titleTextStyle => ${textStyle("md.comp.list.list-item.label-text")}; + TextStyle? get titleTextStyle => ${textStyle("$tokenGroup.label-text")}!.copyWith(color: ${componentColor('$tokenGroup.label-text')}); @override - TextStyle? get subtitleTextStyle => ${textStyle("md.comp.list.list-item.supporting-text")}; + TextStyle? get subtitleTextStyle => ${textStyle("$tokenGroup.supporting-text")}!.copyWith(color: ${componentColor('$tokenGroup.supporting-text')}); @override - TextStyle? get leadingAndTrailingTextStyle => ${textStyle("md.comp.list.list-item.trailing-supporting-text")}; + TextStyle? get leadingAndTrailingTextStyle => ${textStyle("$tokenGroup.trailing-supporting-text")}!.copyWith(color: ${componentColor('$tokenGroup.trailing-supporting-text')}); @override - Color? get selectedColor => ${componentColor('md.comp.list.list-item.selected.trailing-icon')}; + Color? get selectedColor => ${componentColor('$tokenGroup.selected.trailing-icon')}; @override - Color? get iconColor => ${componentColor('md.comp.list.list-item.trailing-icon')}; + Color? get iconColor => ${componentColor('$tokenGroup.trailing-icon')}; } '''; } diff --git a/dev/tools/gen_defaults/lib/navigation_bar_template.dart b/dev/tools/gen_defaults/lib/navigation_bar_template.dart index 48d47e35c1f0a..8298eb1bafb39 100644 --- a/dev/tools/gen_defaults/lib/navigation_bar_template.dart +++ b/dev/tools/gen_defaults/lib/navigation_bar_template.dart @@ -15,7 +15,7 @@ class NavigationBarTemplate extends TokenTemplate { class _${blockName}DefaultsM3 extends NavigationBarThemeData { _${blockName}DefaultsM3(this.context) : super( - height: ${tokens["md.comp.navigation-bar.container.height"]}, + height: ${getToken("md.comp.navigation-bar.container.height")}, elevation: ${elevation("md.comp.navigation-bar.container")}, labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, ); @@ -33,7 +33,7 @@ class _${blockName}DefaultsM3 extends NavigationBarThemeData { @override MaterialStateProperty<IconThemeData?>? get iconTheme { return MaterialStateProperty.resolveWith((Set<MaterialState> states) { return IconThemeData( - size: ${tokens["md.comp.navigation-bar.icon.size"]}, + size: ${getToken("md.comp.navigation-bar.icon.size")}, color: states.contains(MaterialState.selected) ? ${componentColor("md.comp.navigation-bar.active.icon")} : ${componentColor("md.comp.navigation-bar.inactive.icon")}, diff --git a/dev/tools/gen_defaults/lib/navigation_drawer_template.dart b/dev/tools/gen_defaults/lib/navigation_drawer_template.dart index 87d95b0799d2e..53718220e84a5 100644 --- a/dev/tools/gen_defaults/lib/navigation_drawer_template.dart +++ b/dev/tools/gen_defaults/lib/navigation_drawer_template.dart @@ -16,9 +16,9 @@ class _${blockName}DefaultsM3 extends NavigationDrawerThemeData { _${blockName}DefaultsM3(this.context) : super( elevation: ${elevation("md.comp.navigation-drawer.modal.container")}, - tileHeight: ${tokens["md.comp.navigation-drawer.active-indicator.height"]}, + tileHeight: ${getToken("md.comp.navigation-drawer.active-indicator.height")}, indicatorShape: ${shape("md.comp.navigation-drawer.active-indicator")}, - indicatorSize: const Size(${tokens["md.comp.navigation-drawer.active-indicator.width"]}, ${tokens["md.comp.navigation-drawer.active-indicator.height"]}), + indicatorSize: const Size(${getToken("md.comp.navigation-drawer.active-indicator.width")}, ${getToken("md.comp.navigation-drawer.active-indicator.height")}), ); final BuildContext context; @@ -41,9 +41,9 @@ class _${blockName}DefaultsM3 extends NavigationDrawerThemeData { MaterialStateProperty<IconThemeData?>? get iconTheme { return MaterialStateProperty.resolveWith((Set<MaterialState> states) { return IconThemeData( - size: ${tokens["md.comp.navigation-drawer.icon.size"]}, + size: ${getToken("md.comp.navigation-drawer.icon.size")}, color: states.contains(MaterialState.selected) - ? ${componentColor("md.comp.navigation-drawer.active.icon.")} + ? ${componentColor("md.comp.navigation-drawer.active.icon")} : ${componentColor("md.comp.navigation-drawer.inactive.icon")}, ); }); diff --git a/dev/tools/gen_defaults/lib/navigation_rail_template.dart b/dev/tools/gen_defaults/lib/navigation_rail_template.dart index 36192197f368f..555566729cf6d 100644 --- a/dev/tools/gen_defaults/lib/navigation_rail_template.dart +++ b/dev/tools/gen_defaults/lib/navigation_rail_template.dart @@ -19,7 +19,7 @@ class _${blockName}DefaultsM3 extends NavigationRailThemeData { groupAlignment: -1, labelType: NavigationRailLabelType.none, useIndicator: true, - minWidth: ${tokens["md.comp.navigation-rail.container.width"]}, + minWidth: ${getToken('md.comp.navigation-rail.container.width')}, minExtendedWidth: 256, ); @@ -39,14 +39,14 @@ class _${blockName}DefaultsM3 extends NavigationRailThemeData { @override IconThemeData? get unselectedIconTheme { return IconThemeData( - size: ${tokens["md.comp.navigation-rail.icon.size"]}, + size: ${getToken("md.comp.navigation-rail.icon.size")}, color: ${componentColor("md.comp.navigation-rail.inactive.icon")}, ); } @override IconThemeData? get selectedIconTheme { return IconThemeData( - size: ${tokens["md.comp.navigation-rail.icon.size"]}, + size: ${getToken("md.comp.navigation-rail.icon.size")}, color: ${componentColor("md.comp.navigation-rail.active.icon")}, ); } diff --git a/dev/tools/gen_defaults/lib/progress_indicator_template.dart b/dev/tools/gen_defaults/lib/progress_indicator_template.dart index 5f51ea478fad0..6e4d889791a62 100644 --- a/dev/tools/gen_defaults/lib/progress_indicator_template.dart +++ b/dev/tools/gen_defaults/lib/progress_indicator_template.dart @@ -34,7 +34,7 @@ class _Linear${blockName}DefaultsM3 extends ProgressIndicatorThemeData { Color get linearTrackColor => ${componentColor('md.comp.linear-progress-indicator.track')}; @override - double get linearMinHeight => ${tokens['md.comp.linear-progress-indicator.track.height']}; + double get linearMinHeight => ${getToken('md.comp.linear-progress-indicator.track.height')}; } '''; } diff --git a/dev/tools/gen_defaults/lib/search_bar_template.dart b/dev/tools/gen_defaults/lib/search_bar_template.dart index 6e8849de511a6..5a6d7a660c1ac 100644 --- a/dev/tools/gen_defaults/lib/search_bar_template.dart +++ b/dev/tools/gen_defaults/lib/search_bar_template.dart @@ -70,7 +70,7 @@ class _SearchBarDefaultsM3 extends SearchBarThemeData { @override BoxConstraints get constraints => - const BoxConstraints(minWidth: 360.0, maxWidth: 800.0, minHeight: ${tokens['md.comp.search-bar.container.height']}); + const BoxConstraints(minWidth: 360.0, maxWidth: 800.0, minHeight: ${getToken('md.comp.search-bar.container.height')}); } '''; } diff --git a/dev/tools/gen_defaults/lib/search_view_template.dart b/dev/tools/gen_defaults/lib/search_view_template.dart index 551f5c8ede0a4..44f7b5c42728d 100644 --- a/dev/tools/gen_defaults/lib/search_view_template.dart +++ b/dev/tools/gen_defaults/lib/search_view_template.dart @@ -20,7 +20,7 @@ class _${blockName}DefaultsM3 extends ${blockName}ThemeData { late final ColorScheme _colors = Theme.of(context).colorScheme; late final TextTheme _textTheme = Theme.of(context).textTheme; - static double fullScreenBarHeight = ${tokens['md.comp.search-view.full-screen.header.container.height']}; + static double fullScreenBarHeight = ${getToken('md.comp.search-view.full-screen.header.container.height')}; @override Color? get backgroundColor => ${componentColor('md.comp.search-view.container')}; diff --git a/dev/tools/gen_defaults/lib/segmented_button_template.dart b/dev/tools/gen_defaults/lib/segmented_button_template.dart index c8268f2d4c52b..51f859a67dff9 100644 --- a/dev/tools/gen_defaults/lib/segmented_button_template.dart +++ b/dev/tools/gen_defaults/lib/segmented_button_template.dart @@ -12,10 +12,10 @@ class SegmentedButtonTemplate extends TokenTemplate { final String tokenGroup; String _layerOpacity(String layerToken) { - if (tokens.containsKey(layerToken)) { - final String? layerValue = tokens[layerToken] as String?; - if (tokens.containsKey(layerValue)) { - final String? opacityValue = opacity(layerValue!); + if (tokenAvailable(layerToken)) { + final String layerValue = getToken(layerToken) as String; + if (tokenAvailable(layerValue)) { + final String? opacityValue = opacity(layerValue); if (opacityValue != null) { return '.withOpacity($opacityValue)'; } @@ -106,7 +106,7 @@ class _${blockName}DefaultsM3 extends SegmentedButtonThemeData { }), surfaceTintColor: const MaterialStatePropertyAll<Color>(Colors.transparent), elevation: const MaterialStatePropertyAll<double>(0), - iconSize: const MaterialStatePropertyAll<double?>(${tokens['$tokenGroup.with-icon.icon.size']}), + iconSize: const MaterialStatePropertyAll<double?>(${getToken('$tokenGroup.with-icon.icon.size')}), side: MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.disabled)) { return ${border("$tokenGroup.disabled.outline")}; @@ -114,7 +114,7 @@ class _${blockName}DefaultsM3 extends SegmentedButtonThemeData { return ${border("$tokenGroup.outline")}; }), shape: const MaterialStatePropertyAll<OutlinedBorder>(${shape(tokenGroup, '')}), - minimumSize: const MaterialStatePropertyAll<Size?>(Size.fromHeight(${tokens['$tokenGroup.container.height']})), + minimumSize: const MaterialStatePropertyAll<Size?>(Size.fromHeight(${getToken('$tokenGroup.container.height')})), ); } @override diff --git a/dev/tools/gen_defaults/lib/slider_template.dart b/dev/tools/gen_defaults/lib/slider_template.dart index ca3f15bd9ca1e..8ced06757f280 100644 --- a/dev/tools/gen_defaults/lib/slider_template.dart +++ b/dev/tools/gen_defaults/lib/slider_template.dart @@ -15,7 +15,7 @@ class SliderTemplate extends TokenTemplate { String generate() => ''' class _${blockName}DefaultsM3 extends SliderThemeData { _${blockName}DefaultsM3(this.context) - : super(trackHeight: ${tokens['$tokenGroup.active.track.height']}); + : super(trackHeight: ${getToken('$tokenGroup.active.track.height')}); final BuildContext context; late final ColorScheme _colors = Theme.of(context).colorScheme; diff --git a/dev/tools/gen_defaults/lib/surface_tint.dart b/dev/tools/gen_defaults/lib/surface_tint.dart index ea7a0ea4bab10..3c4c0c89d8811 100644 --- a/dev/tools/gen_defaults/lib/surface_tint.dart +++ b/dev/tools/gen_defaults/lib/surface_tint.dart @@ -14,12 +14,12 @@ class SurfaceTintTemplate extends TokenTemplate { // https://m3.material.io/styles/color/the-color-system/color-roles // Ordered by increasing elevation. const List<_ElevationOpacity> _surfaceTintElevationOpacities = <_ElevationOpacity>[ - _ElevationOpacity(${tokens['md.sys.elevation.level0']}, 0.0), // Elevation level 0 - _ElevationOpacity(${tokens['md.sys.elevation.level1']}, 0.05), // Elevation level 1 - _ElevationOpacity(${tokens['md.sys.elevation.level2']}, 0.08), // Elevation level 2 - _ElevationOpacity(${tokens['md.sys.elevation.level3']}, 0.11), // Elevation level 3 - _ElevationOpacity(${tokens['md.sys.elevation.level4']}, 0.12), // Elevation level 4 - _ElevationOpacity(${tokens['md.sys.elevation.level5']}, 0.14), // Elevation level 5 + _ElevationOpacity(${getToken('md.sys.elevation.level0')}, 0.0), // Elevation level 0 + _ElevationOpacity(${getToken('md.sys.elevation.level1')}, 0.05), // Elevation level 1 + _ElevationOpacity(${getToken('md.sys.elevation.level2')}, 0.08), // Elevation level 2 + _ElevationOpacity(${getToken('md.sys.elevation.level3')}, 0.11), // Elevation level 3 + _ElevationOpacity(${getToken('md.sys.elevation.level4')}, 0.12), // Elevation level 4 + _ElevationOpacity(${getToken('md.sys.elevation.level5')}, 0.14), // Elevation level 5 ]; '''; } diff --git a/dev/tools/gen_defaults/lib/switch_template.dart b/dev/tools/gen_defaults/lib/switch_template.dart index 95a4da74a71b9..e6bd8470c6322 100644 --- a/dev/tools/gen_defaults/lib/switch_template.dart +++ b/dev/tools/gen_defaults/lib/switch_template.dart @@ -127,10 +127,10 @@ class _${blockName}DefaultsM3 extends SwitchThemeData { } @override - MaterialStatePropertyAll<double> get trackOutlineWidth => const MaterialStatePropertyAll<double>(${tokens['md.comp.switch.track.outline.width']}); + MaterialStatePropertyAll<double> get trackOutlineWidth => const MaterialStatePropertyAll<double>(${getToken('md.comp.switch.track.outline.width')}); @override - double get splashRadius => ${tokens['md.comp.switch.state-layer.size']} / 2; + double get splashRadius => ${getToken('md.comp.switch.state-layer.size')} / 2; } class _SwitchConfigM3 with _SwitchConfig { @@ -140,10 +140,10 @@ class _SwitchConfigM3 with _SwitchConfig { BuildContext context; final ColorScheme _colors; - static const double iconSize = ${tokens['md.comp.switch.unselected.icon.size']}; + static const double iconSize = ${getToken('md.comp.switch.unselected.icon.size')}; @override - double get activeThumbRadius => ${tokens['md.comp.switch.selected.handle.width']} / 2; + double get activeThumbRadius => ${getToken('md.comp.switch.selected.handle.width')} / 2; @override MaterialStateProperty<Color> get iconColor { @@ -180,10 +180,10 @@ class _SwitchConfigM3 with _SwitchConfig { } @override - double get inactiveThumbRadius => ${tokens['md.comp.switch.unselected.handle.width']} / 2; + double get inactiveThumbRadius => ${getToken('md.comp.switch.unselected.handle.width')} / 2; @override - double get pressedThumbRadius => ${tokens['md.comp.switch.pressed.handle.width']} / 2; + double get pressedThumbRadius => ${getToken('md.comp.switch.pressed.handle.width')} / 2; @override double get switchHeight => _kSwitchMinSize + 8.0; @@ -195,16 +195,16 @@ class _SwitchConfigM3 with _SwitchConfig { double get switchWidth => trackWidth - 2 * (trackHeight / 2.0) + _kSwitchMinSize; @override - double get thumbRadiusWithIcon => ${tokens['md.comp.switch.with-icon.handle.width']} / 2; + double get thumbRadiusWithIcon => ${getToken('md.comp.switch.with-icon.handle.width')} / 2; @override List<BoxShadow>? get thumbShadow => kElevationToShadow[0]; @override - double get trackHeight => ${tokens['md.comp.switch.track.height']}; + double get trackHeight => ${getToken('md.comp.switch.track.height')}; @override - double get trackWidth => ${tokens['md.comp.switch.track.width']}; + double get trackWidth => ${getToken('md.comp.switch.track.width')}; // The thumb size at the middle of the track. Hand coded default based on the animation specs. @override diff --git a/dev/tools/gen_defaults/lib/tabs_template.dart b/dev/tools/gen_defaults/lib/tabs_template.dart index 17e5b20060a99..f6388a87458d3 100644 --- a/dev/tools/gen_defaults/lib/tabs_template.dart +++ b/dev/tools/gen_defaults/lib/tabs_template.dart @@ -73,7 +73,7 @@ class _${blockName}PrimaryDefaultsM3 extends TabBarTheme { @override TabAlignment? get tabAlignment => isScrollable ? TabAlignment.start : TabAlignment.fill; - static double indicatorWeight = ${tokens['md.comp.primary-navigation-tab.active-indicator.height']}; + static double indicatorWeight = ${getToken('md.comp.primary-navigation-tab.active-indicator.height')}; } class _${blockName}SecondaryDefaultsM3 extends TabBarTheme { diff --git a/dev/tools/gen_defaults/lib/template.dart b/dev/tools/gen_defaults/lib/template.dart index d713865dc853c..fffff0461bd14 100644 --- a/dev/tools/gen_defaults/lib/template.dart +++ b/dev/tools/gen_defaults/lib/template.dart @@ -4,8 +4,11 @@ import 'dart:io'; +import 'token_logger.dart'; + +/// Base class for code generation templates. abstract class TokenTemplate { - const TokenTemplate(this.blockName, this.fileName, this.tokens, { + const TokenTemplate(this.blockName, this.fileName, this._tokens, { this.colorSchemePrefix = 'Theme.of(context).colorScheme.', this.textThemePrefix = 'Theme.of(context).textTheme.' }); @@ -19,7 +22,7 @@ abstract class TokenTemplate { final String fileName; /// Map of token data extracted from the Material Design token database. - final Map<String, dynamic> tokens; + final Map<String, dynamic> _tokens; /// Optional prefix prepended to color definitions. /// @@ -31,6 +34,15 @@ abstract class TokenTemplate { /// Defaults to 'Theme.of(context).textTheme.' final String textThemePrefix; + /// Check if a token is available. + bool tokenAvailable(String tokenName) => _tokens.containsKey(tokenName); + + /// Resolve a token while logging its usage. + dynamic getToken(String tokenName) { + tokenLogger.log(tokenName); + return _tokens[tokenName]; + } + static const String beginGeneratedComment = ''' // BEGIN GENERATED TOKEN PROPERTIES'''; @@ -78,7 +90,6 @@ abstract class TokenTemplate { final StringBuffer buffer = StringBuffer(contentBeforeBlock); buffer.write(beginComment); buffer.write(headerComment); - buffer.write('// Token database version: ${tokens['version']}\n\n'); buffer.write(generate()); buffer.write(endComment); buffer.write(contentAfterBlock); @@ -102,8 +113,8 @@ abstract class TokenTemplate { /// See also: /// * [componentColor], that provides support for an optional opacity. String color(String colorToken, [String defaultValue = 'null']) { - return tokens.containsKey(colorToken) - ? '$colorSchemePrefix${tokens[colorToken]}' + return tokenAvailable(colorToken) + ? '$colorSchemePrefix${getToken(colorToken)}' : defaultValue; } @@ -133,19 +144,22 @@ abstract class TokenTemplate { /// * [color], that provides support for looking up a raw color token. String componentColor(String componentToken) { final String colorToken = '$componentToken.color'; - if (!tokens.containsKey(colorToken)) { + if (!tokenAvailable(colorToken)) { return 'null'; } String value = color(colorToken); final String opacityToken = '$componentToken.opacity'; - if (tokens.containsKey(opacityToken)) { + if (tokenAvailable(opacityToken)) { value += '.withOpacity(${opacity(opacityToken)})'; } return value; } /// Generate the opacity value for the given token. - String? opacity(String token) => _numToString(tokens[token]); + String? opacity(String token) { + tokenLogger.log(token); + return _numToString(getToken(token)); + } String? _numToString(Object? value, [int? digits]) { if (value == null) { @@ -157,12 +171,12 @@ abstract class TokenTemplate { } return digits == null ? value.toString() : value.toStringAsFixed(digits); } - return tokens[value].toString(); + return getToken(value as String).toString(); } /// Generate an elevation value for the given component token. String elevation(String componentToken) { - return tokens[tokens['$componentToken.elevation']!]!.toString(); + return getToken(getToken('$componentToken.elevation')! as String)!.toString(); } /// Generate a size value for the given component token. @@ -170,17 +184,17 @@ abstract class TokenTemplate { /// Non-square sizes are specified as width and height. String size(String componentToken) { final String sizeToken = '$componentToken.size'; - if (!tokens.containsKey(sizeToken)) { + if (!tokenAvailable(sizeToken)) { final String widthToken = '$componentToken.width'; final String heightToken = '$componentToken.height'; - if (!tokens.containsKey(widthToken) && !tokens.containsKey(heightToken)) { + if (!tokenAvailable(widthToken) && !tokenAvailable(heightToken)) { throw Exception('Unable to find width, height, or size tokens for $componentToken'); } - final String? width = _numToString(tokens.containsKey(widthToken) ? tokens[widthToken]! as num : double.infinity, 0); - final String? height = _numToString(tokens.containsKey(heightToken) ? tokens[heightToken]! as num : double.infinity, 0); + final String? width = _numToString(tokenAvailable(widthToken) ? getToken(widthToken)! as num : double.infinity, 0); + final String? height = _numToString(tokenAvailable(heightToken) ? getToken(heightToken)! as num : double.infinity, 0); return 'const Size($width, $height)'; } - return 'const Size.square(${_numToString(tokens[sizeToken])})'; + return 'const Size.square(${_numToString(getToken(sizeToken))})'; } /// Generate a shape constant for the given component token. @@ -189,7 +203,8 @@ abstract class TokenTemplate { /// - "SHAPE_FAMILY_ROUNDED_CORNERS" which maps to [RoundedRectangleBorder]. /// - "SHAPE_FAMILY_CIRCULAR" which maps to a [StadiumBorder]. String shape(String componentToken, [String prefix = 'const ']) { - final Map<String, dynamic> shape = tokens[tokens['$componentToken.shape']!]! as Map<String, dynamic>; + + final Map<String, dynamic> shape = getToken(getToken('$componentToken.shape') as String) as Map<String, dynamic>; switch (shape['family']) { case 'SHAPE_FAMILY_ROUNDED_CORNERS': final double topLeft = shape['topLeft'] as double; @@ -224,25 +239,28 @@ abstract class TokenTemplate { /// Generate a [BorderSide] for the given component. String border(String componentToken) { - if (!tokens.containsKey('$componentToken.color')) { + + if (!tokenAvailable('$componentToken.color')) { return 'null'; } final String borderColor = componentColor(componentToken); - final double width = (tokens['$componentToken.width'] ?? tokens['$componentToken.height'] ?? 1.0) as double; + final double width = (getToken('$componentToken.width') ?? getToken('$componentToken.height') ?? 1.0) as double; return 'BorderSide(color: $borderColor${width != 1.0 ? ", width: $width" : ""})'; } /// Generate a [TextTheme] text style name for the given component token. String textStyle(String componentToken) { - return '$textThemePrefix${tokens["$componentToken.text-style"]}'; + + return '$textThemePrefix${getToken("$componentToken.text-style")}'; } String textStyleWithColor(String componentToken) { - if (!tokens.containsKey('$componentToken.text-style')) { + + if (!tokenAvailable('$componentToken.text-style')) { return 'null'; } String style = textStyle(componentToken); - if (tokens.containsKey('$componentToken.color')) { + if (tokenAvailable('$componentToken.color')) { style = '$style?.copyWith(color: ${componentColor(componentToken)})'; } return style; diff --git a/dev/tools/gen_defaults/lib/token_logger.dart b/dev/tools/gen_defaults/lib/token_logger.dart new file mode 100644 index 0000000000000..3347e5fbd9033 --- /dev/null +++ b/dev/tools/gen_defaults/lib/token_logger.dart @@ -0,0 +1,88 @@ +// 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:collection'; +import 'dart:io'; + +final TokenLogger tokenLogger = TokenLogger(); + +/// Class to keep track of used tokens and versions. +class TokenLogger { + TokenLogger(); + + void init({ + required Map<String, dynamic> allTokens, + required Map<String, List<String>> versionMap + }){ + _allTokens = allTokens; + _versionMap = versionMap; + } + + /// Map of all tokens to their values. + late Map<String, dynamic> _allTokens; + + // Map of versions to their token files. + late Map<String, List<String>> _versionMap; + + // Sorted set of used tokens. + final SplayTreeSet<String> _usedTokens = SplayTreeSet<String>(); + + void clear() { + _allTokens.clear(); + _versionMap.clear(); + _usedTokens.clear(); + } + + /// Logs a token. + void log(String token) { + if (!_allTokens.containsKey(token)) { + print('\x1B[31m' 'Token unavailable: $token' '\x1B[0m'); + return; + } + _usedTokens.add(token); + } + + /// Prints version usage to the console. + void printVersionUsage({required bool verbose}) { + final String versionsString = 'Versions used: ${_versionMap.keys.join(', ')}'; + print(versionsString); + if (verbose) { + for (final String version in _versionMap.keys) { + print(' $version:'); + final List<String> files = List<String>.from(_versionMap[version]!); + files.sort(); + for (final String file in files) { + print(' $file'); + } + } + print(''); + } + } + + /// Prints tokens usage to the console. + void printTokensUsage({required bool verbose}) { + final Set<String> allTokensSet = _allTokens.keys.toSet(); + + if (verbose) { + for (final String token in SplayTreeSet<String>.from(allTokensSet).toList()) { + if (_usedTokens.contains(token)) { + print('✅ $token'); + } else { + print('❌ $token'); + } + } + print(''); + } + + print('Tokens used: ${_usedTokens.length}/${_allTokens.length}'); + } + + /// Dumps version and tokens usage to a file. + void dumpToFile(String path) { + final File file = File(path); + file.createSync(recursive: true); + final String versionsString = 'Versions used, ${_versionMap.keys.join(', ')}'; + file.writeAsStringSync('$versionsString\n${_usedTokens.join(',\n')}\n'); + } +} diff --git a/dev/tools/gen_defaults/lib/typography_template.dart b/dev/tools/gen_defaults/lib/typography_template.dart index be4d1708fa7f2..8e3754ac648fc 100644 --- a/dev/tools/gen_defaults/lib/typography_template.dart +++ b/dev/tools/gen_defaults/lib/typography_template.dart @@ -41,13 +41,13 @@ class _M3Typography { return theme.toString(); } - String _textStyleDef(String tokenName, String debugLabel, String baseline) { + String _textStyleDef(String tokenPrefix, String debugLabel, String baseline) { final StringBuffer style = StringBuffer("TextStyle(debugLabel: '$debugLabel'"); style.write(', inherit: false'); - style.write(', fontSize: ${_fontSize(tokenName)}'); - style.write(', fontWeight: ${_fontWeight(tokenName)}'); - style.write(', letterSpacing: ${_fontSpacing(tokenName)}'); - style.write(', height: ${_fontHeight(tokenName)}'); + style.write(', fontSize: ${_fontSize(tokenPrefix)}'); + style.write(', fontWeight: ${_fontWeight(tokenPrefix)}'); + style.write(', letterSpacing: ${_fontSpacing(tokenPrefix)}'); + style.write(', height: ${_fontHeight(tokenPrefix)}'); style.write(', textBaseline: TextBaseline.$baseline'); style.write(', leadingDistribution: TextLeadingDistribution.even'); style.write(')'); @@ -55,21 +55,21 @@ class _M3Typography { } String _fontSize(String textStyleTokenName) { - return tokens['$textStyleTokenName.size']!.toString(); + return getToken('$textStyleTokenName.size').toString(); } String _fontWeight(String textStyleTokenName) { - final String weightValue = tokens[tokens['$textStyleTokenName.weight']!]!.toString(); + final String weightValue = getToken(getToken('$textStyleTokenName.weight') as String).toString(); return 'FontWeight.w$weightValue'; } String _fontSpacing(String textStyleTokenName) { - return tokens['$textStyleTokenName.tracking']!.toString(); + return getToken('$textStyleTokenName.tracking').toString(); } String _fontHeight(String textStyleTokenName) { - final double size = tokens['$textStyleTokenName.size']! as double; - final double lineHeight = tokens['$textStyleTokenName.line-height']! as double; + final double size = getToken('$textStyleTokenName.size') as double; + final double lineHeight = getToken('$textStyleTokenName.line-height') as double; return (lineHeight / size).toStringAsFixed(2); } } diff --git a/dev/tools/gen_defaults/pubspec.yaml b/dev/tools/gen_defaults/pubspec.yaml index 5d5a0146cc3fc..c4665a8c1d9ed 100644 --- a/dev/tools/gen_defaults/pubspec.yaml +++ b/dev/tools/gen_defaults/pubspec.yaml @@ -6,14 +6,14 @@ environment: sdk: '>=3.0.0-0 <4.0.0' dependencies: + args: 2.4.2 dev_dependencies: path: 1.8.3 - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -22,13 +22,13 @@ dev_dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -46,13 +46,13 @@ dev_dependencies: stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 8771 +# PUBSPEC CHECKSUM: df79 diff --git a/dev/tools/gen_defaults/test.json b/dev/tools/gen_defaults/test.json new file mode 100644 index 0000000000000..ae29d7ff44c1b --- /dev/null +++ b/dev/tools/gen_defaults/test.json @@ -0,0 +1,3 @@ +Versions used, v1.0.0, v2.0.0 +bar, +foo diff --git a/dev/tools/gen_defaults/test/gen_defaults_test.dart b/dev/tools/gen_defaults/test/gen_defaults_test.dart index 30478af8fe8c6..a921b9be19455 100644 --- a/dev/tools/gen_defaults/test/gen_defaults_test.dart +++ b/dev/tools/gen_defaults/test/gen_defaults_test.dart @@ -2,13 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; import 'dart:io'; import 'package:gen_defaults/template.dart'; +import 'package:gen_defaults/token_logger.dart'; import 'package:path/path.dart' as path; import 'package:test/test.dart'; void main() { + final TokenLogger logger = tokenLogger; + logger.init(allTokens: <String, dynamic>{}, versionMap: <String, List<String>>{}); + test('Templates will append to the end of a file', () { final Directory tempDir = Directory.systemTemp.createTempSync('gen_defaults'); try { @@ -38,8 +43,6 @@ void main() { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: 0.0 - static final String tokenFoo = 'Foobar'; static final String tokenBar = 'Barfoo'; @@ -69,8 +72,6 @@ static final String tokenBar = 'Barfoo'; // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: 0.0 - static final String tokenFoo = 'Foobar'; static final String tokenBar = 'Barfoo'; @@ -94,8 +95,6 @@ static final String tokenBar = 'Barfoo'; // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: 0.0 - static final String tokenFoo = 'foo'; static final String tokenBar = 'bar'; @@ -136,8 +135,6 @@ static final String tokenBar = 'bar'; // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: 0.0 - static final String tokenFoo = 'foo'; static final String tokenBar = 'bar'; @@ -162,8 +159,6 @@ static final String tokenBar = 'bar'; // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: 0.0 - static final String tokenFoo = 'foo'; static final String tokenBar = 'bar'; @@ -176,8 +171,6 @@ static final String tokenBar = 'bar'; // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: 0.0 - static final String tokenFoo = 'bar'; static final String tokenBar = 'foo'; @@ -202,8 +195,6 @@ static final String tokenBar = 'foo'; // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: 0.0 - static final String tokenFoo = 'FOO'; static final String tokenBar = 'BAR'; @@ -216,8 +207,6 @@ static final String tokenBar = 'BAR'; // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: 0.0 - static final String tokenFoo = 'bar'; static final String tokenBar = 'foo'; @@ -248,6 +237,127 @@ static final String tokenBar = 'foo'; expect(template.shape('foo'), 'const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(1.0), topRight: Radius.circular(2.0), bottomLeft: Radius.circular(3.0), bottomRight: Radius.circular(4.0)))'); expect(template.shape('bar'), 'const StadiumBorder()'); }); + + group('Tokens logger', () { + final List<String> printLog = List<String>.empty(growable: true); + final Map<String, List<String>> versionMap = <String, List<String>>{}; + final Map<String, dynamic> allTokens = <String, dynamic>{}; + + // Add to printLog instead of printing to stdout + void Function() overridePrint(void Function() testFn) => () { + final ZoneSpecification spec = ZoneSpecification( + print: (_, __, ___, String msg) { + printLog.add(msg); + } + ); + return Zone.current.fork(specification: spec).run<void>(testFn); + }; + + setUp(() { + logger.init(allTokens: allTokens, versionMap: versionMap); + }); + + tearDown(() { + logger.clear(); + printLog.clear(); + versionMap.clear(); + allTokens.clear(); + }); + + String errorColoredString(String str) => '\x1B[31m$str\x1B[0m'; + + const Map<String, List<String>> testVersions = <String, List<String>>{ + 'v1.0.0': <String>['file_1.json'], + 'v2.0.0': <String>['file_2.json, file_3.json'], + }; + + test('can print empty usage', overridePrint(() { + logger.printVersionUsage(verbose: true); + expect(printLog, contains('Versions used: ')); + + logger.printTokensUsage(verbose: true); + expect(printLog, contains('Tokens used: 0/0')); + })); + + test('can print version usage', overridePrint(() { + versionMap.addAll(testVersions); + + logger.printVersionUsage(verbose: false); + + expect(printLog, contains('Versions used: v1.0.0, v2.0.0')); + })); + + test('can print version usage (verbose)', overridePrint(() { + versionMap.addAll(testVersions); + + logger.printVersionUsage(verbose: true); + + expect(printLog, contains('Versions used: v1.0.0, v2.0.0')); + expect(printLog, contains(' v1.0.0:')); + expect(printLog, contains(' file_1.json')); + expect(printLog, contains(' v2.0.0:')); + expect(printLog, contains(' file_2.json, file_3.json')); + })); + + test('can log and print tokens usage', overridePrint(() { + allTokens['foo'] = 'value'; + + logger.log('foo'); + logger.printTokensUsage(verbose: false); + + expect(printLog, contains('Tokens used: 1/1')); + })); + + test('can log and print tokens usage (verbose)', overridePrint(() { + allTokens['foo'] = 'value'; + + logger.log('foo'); + logger.printTokensUsage(verbose: true); + + expect(printLog, contains('✅ foo')); + expect(printLog, contains('Tokens used: 1/1')); + })); + + test('detects invalid logs', overridePrint(() { + allTokens['foo'] = 'value'; + + logger.log('baz'); + logger.log('foobar'); + logger.printTokensUsage(verbose: true); + + expect(printLog, contains(errorColoredString('Token unavailable: baz'))); + expect(printLog, contains(errorColoredString('Token unavailable: foobar'))); + expect(printLog, contains('❌ foo')); + expect(printLog, contains('Tokens used: 0/1')); + })); + + test('can log and dump versions & tokens to a file', overridePrint(() { + versionMap.addAll(testVersions); + allTokens['foo'] = 'value'; + allTokens['bar'] = 'value'; + + logger.log('foo'); + logger.log('bar'); + logger.dumpToFile('test.json'); + + final String fileContent = File('test.json').readAsStringSync(); + expect(fileContent, contains('Versions used, v1.0.0, v2.0.0')); + expect(fileContent, contains('bar,')); + expect(fileContent, contains('foo')); + })); + + test('integration test', overridePrint(() { + allTokens['foo'] = 'value'; + allTokens['bar'] = 'value'; + + TestTemplate('block', 'filename', allTokens).generate(); + logger.printTokensUsage(verbose: true); + + expect(printLog, contains('✅ foo')); + expect(printLog, contains('✅ bar')); + expect(printLog, contains('Tokens used: 2/2')); + })); + }); } class TestTemplate extends TokenTemplate { @@ -255,7 +365,7 @@ class TestTemplate extends TokenTemplate { @override String generate() => ''' -static final String tokenFoo = '${tokens['foo']}'; -static final String tokenBar = '${tokens['bar']}'; +static final String tokenFoo = '${getToken('foo')}'; +static final String tokenBar = '${getToken('bar')}'; '''; } diff --git a/dev/tools/gen_keycodes/pubspec.yaml b/dev/tools/gen_keycodes/pubspec.yaml index f9507d67231e6..df0953cc1d728 100644 --- a/dev/tools/gen_keycodes/pubspec.yaml +++ b/dev/tools/gen_keycodes/pubspec.yaml @@ -5,7 +5,7 @@ environment: sdk: '>=3.0.0-0 <4.0.0' dependencies: - args: 2.4.1 + args: 2.4.2 http: 0.13.6 meta: 1.9.1 path: 1.8.3 @@ -20,23 +20,23 @@ dependencies: typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.24.2 - test_api: 0.5.2 + test: 1.24.3 + test_api: 0.6.0 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -50,11 +50,11 @@ dev_dependencies: source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 0368 +# PUBSPEC CHECKSUM: 5270 diff --git a/dev/tools/pubspec.yaml b/dev/tools/pubspec.yaml index 5d96d09408aec..13c5c0edbb227 100644 --- a/dev/tools/pubspec.yaml +++ b/dev/tools/pubspec.yaml @@ -6,7 +6,7 @@ environment: dependencies: archive: 3.3.2 - args: 2.4.1 + args: 2.4.2 http: 0.13.6 intl: 0.18.1 meta: 1.9.1 @@ -26,21 +26,21 @@ dependencies: typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.24.2 - test_api: 0.5.2 + test: 1.24.3 + test_api: 0.6.0 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -54,11 +54,11 @@ dev_dependencies: source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 1082 +# PUBSPEC CHECKSUM: cc8a diff --git a/dev/tools/vitool/pubspec.yaml b/dev/tools/vitool/pubspec.yaml index 8056f93cfb1bb..59a59762161e3 100644 --- a/dev/tools/vitool/pubspec.yaml +++ b/dev/tools/vitool/pubspec.yaml @@ -9,16 +9,16 @@ environment: dependencies: flutter: sdk: flutter - args: 2.4.1 + args: 2.4.2 vector_math: 2.1.4 xml: 6.3.0 characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" petitparser: 5.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -28,13 +28,13 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 62c5 +# PUBSPEC CHECKSUM: f2ea diff --git a/dev/tracing_tests/pubspec.yaml b/dev/tracing_tests/pubspec.yaml index 14f618b5a61a0..203fb17208fe5 100644 --- a/dev/tracing_tests/pubspec.yaml +++ b/dev/tracing_tests/pubspec.yaml @@ -8,14 +8,14 @@ dependencies: flutter: sdk: flutter - vm_service: 11.6.0 + vm_service: 11.7.1 characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -25,13 +25,13 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: e9d4 +# PUBSPEC CHECKSUM: b2fa diff --git a/examples/api/analysis_options.yaml b/examples/api/analysis_options.yaml index 76be938a6b861..dec61f90edbcc 100644 --- a/examples/api/analysis_options.yaml +++ b/examples/api/analysis_options.yaml @@ -6,3 +6,5 @@ linter: rules: # Samples want to print things pretty often. avoid_print: false + # Samples are sometimes incomplete and don't show usage of everything. + unreachable_from_main: false diff --git a/examples/api/ios/Runner/Info.plist b/examples/api/ios/Runner/Info.plist index 037c2ffdecddb..a48d067fdea11 100644 --- a/examples/api/ios/Runner/Info.plist +++ b/examples/api/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/examples/api/lib/material/dropdown_menu/dropdown_menu.1.dart b/examples/api/lib/material/dropdown_menu/dropdown_menu.1.dart new file mode 100644 index 0000000000000..dbb22177f3d98 --- /dev/null +++ b/examples/api/lib/material/dropdown_menu/dropdown_menu.1.dart @@ -0,0 +1,58 @@ +// 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/material.dart'; + +/// Flutter code sample for [DropdownMenu]. + +const List<String> list = <String>['One', 'Two', 'Three', 'Four']; + +void main() => runApp(const DropdownMenuApp()); + +class DropdownMenuApp extends StatelessWidget { + const DropdownMenuApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData(useMaterial3:true), + home: Scaffold( + appBar: AppBar(title: const Text('DropdownMenu Sample')), + body: const Center( + child: DropdownMenuExample(), + ), + ), + ); + } +} + +class DropdownMenuExample extends StatefulWidget { + const DropdownMenuExample({super.key}); + + @override + State<DropdownMenuExample> createState() => _DropdownMenuExampleState(); +} + +class _DropdownMenuExampleState extends State<DropdownMenuExample> { + String dropdownValue = list.first; + + @override + Widget build(BuildContext context) { + return DropdownMenu<String>( + initialSelection: list.first, + onSelected: (String? value) { + // This is called when the user selects an item. + setState(() { + dropdownValue = value!; + }); + }, + dropdownMenuEntries: list.map<DropdownMenuEntry<String>>((String value) { + return DropdownMenuEntry<String>( + value: value, + label: value + ); + }).toList(), + ); + } +} diff --git a/examples/api/lib/material/floating_action_button/floating_action_button.0.dart b/examples/api/lib/material/floating_action_button/floating_action_button.0.dart index 26b2a5baf835a..30a0b3295ffaa 100644 --- a/examples/api/lib/material/floating_action_button/floating_action_button.0.dart +++ b/examples/api/lib/material/floating_action_button/floating_action_button.0.dart @@ -6,21 +6,38 @@ import 'package:flutter/material.dart'; /// Flutter code sample for [FloatingActionButton]. -void main() => runApp(const FloatingActionButtonExampleApp()); +void main() { + runApp(const FloatingActionButtonExampleApp()); +} class FloatingActionButtonExampleApp extends StatelessWidget { - const FloatingActionButtonExampleApp({super.key}); + const FloatingActionButtonExampleApp({ super.key }); @override Widget build(BuildContext context) { - return const MaterialApp( - home: FabExample(), + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const FloatingActionButtonExample(), ); } } -class FabExample extends StatelessWidget { - const FabExample({super.key}); +class FloatingActionButtonExample extends StatefulWidget { + const FloatingActionButtonExample({ super.key }); + + @override + State<FloatingActionButtonExample> createState() => _FloatingActionButtonExampleState(); +} + +class _FloatingActionButtonExampleState extends State<FloatingActionButtonExample> { + // The FAB's foregroundColor, backgroundColor, and shape + static const List<(Color?, Color? background, ShapeBorder?)> customizations = <(Color?, Color?, ShapeBorder?)>[ + (null, null, null), // The FAB uses its default for null parameters. + (null, Colors.green, null), + (Colors.white, Colors.green, null), + (Colors.white, Colors.green, CircleBorder()), + ]; + int index = 0; // Selects the customization. @override Widget build(BuildContext context) { @@ -31,9 +48,13 @@ class FabExample extends StatelessWidget { body: const Center(child: Text('Press the button below!')), floatingActionButton: FloatingActionButton( onPressed: () { - // Add your onPressed code here! + setState(() { + index = (index + 1) % customizations.length; + }); }, - backgroundColor: Colors.green, + foregroundColor: customizations[index].$1, + backgroundColor: customizations[index].$2, + shape: customizations[index].$3, child: const Icon(Icons.navigation), ), ); diff --git a/examples/api/lib/material/floating_action_button/floating_action_button.1.dart b/examples/api/lib/material/floating_action_button/floating_action_button.1.dart index 7180a52d43be8..9650247a25edd 100644 --- a/examples/api/lib/material/floating_action_button/floating_action_button.1.dart +++ b/examples/api/lib/material/floating_action_button/floating_action_button.1.dart @@ -13,8 +13,9 @@ class FloatingActionButtonExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: FabExample(), + return MaterialApp( + theme: ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true), + home: const FabExample(), ); } } @@ -28,16 +29,77 @@ class FabExample extends StatelessWidget { appBar: AppBar( title: const Text('FloatingActionButton Sample'), ), - body: const Center( - child: Text('Press the button with a label below!'), - ), - floatingActionButton: FloatingActionButton.extended( - onPressed: () { - // Add your onPressed code here! - }, - label: const Text('Approve'), - icon: const Icon(Icons.thumb_up), - backgroundColor: Colors.pink, + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: <Widget>[ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: <Widget>[ + const Text('Small'), + const SizedBox(width: 16), + // An example of the small floating action button. + // + // https://m3.material.io/components/floating-action-button/specs#669a1be8-7271-48cb-a74d-dd502d73bda4 + FloatingActionButton.small( + onPressed: () { + // Add your onPressed code here! + }, + child: const Icon(Icons.add), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: <Widget>[ + const Text('Regular'), + const SizedBox(width: 16), + // An example of the regular floating action button. + // + // https://m3.material.io/components/floating-action-button/specs#71504201-7bd1-423d-8bb7-07e0291743e5 + FloatingActionButton( + onPressed: () { + // Add your onPressed code here! + }, + child: const Icon(Icons.add), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: <Widget>[ + const Text('Large'), + const SizedBox(width: 16), + // An example of the large floating action button. + // + // https://m3.material.io/components/floating-action-button/specs#9d7d3d6a-bab7-47cb-be32-5596fbd660fe + FloatingActionButton.large( + onPressed: () { + // Add your onPressed code here! + }, + child: const Icon(Icons.add), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: <Widget>[ + const Text('Extended'), + const SizedBox(width: 16), + // An example of the extended floating action button. + // + // https://m3.material.io/components/extended-fab/specs#686cb8af-87c9-48e8-a3e1-db9da6f6c69b + FloatingActionButton.extended( + onPressed: () { + // Add your onPressed code here! + }, + label: const Text('Add'), + icon: const Icon(Icons.add), + ), + ], + ), + ], + ), ), ); } diff --git a/examples/api/lib/material/floating_action_button/floating_action_button.2.dart b/examples/api/lib/material/floating_action_button/floating_action_button.2.dart deleted file mode 100644 index 4eedfc1a2b863..0000000000000 --- a/examples/api/lib/material/floating_action_button/floating_action_button.2.dart +++ /dev/null @@ -1,44 +0,0 @@ -// 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/material.dart'; - -/// Flutter code sample for [FloatingActionButton]. - -void main() => runApp(const FloatingActionButtonExampleApp()); - -class FloatingActionButtonExampleApp extends StatelessWidget { - const FloatingActionButtonExampleApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - theme: ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true), - home: const FabExample(), - ); - } -} - -class FabExample extends StatelessWidget { - const FabExample({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('FloatingActionButton Sample'), - ), - body: const Center(child: Text('Press the button below!')), - // An example of the floating action button. - // - // https://m3.material.io/components/floating-action-button/specs - floatingActionButton: FloatingActionButton( - onPressed: () { - // Add your onPressed code here! - }, - child: const Icon(Icons.add), - ), - ); - } -} diff --git a/examples/api/lib/material/floating_action_button/floating_action_button.3.dart b/examples/api/lib/material/floating_action_button/floating_action_button.3.dart deleted file mode 100644 index 9650247a25edd..0000000000000 --- a/examples/api/lib/material/floating_action_button/floating_action_button.3.dart +++ /dev/null @@ -1,106 +0,0 @@ -// 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/material.dart'; - -/// Flutter code sample for [FloatingActionButton]. - -void main() => runApp(const FloatingActionButtonExampleApp()); - -class FloatingActionButtonExampleApp extends StatelessWidget { - const FloatingActionButtonExampleApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - theme: ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true), - home: const FabExample(), - ); - } -} - -class FabExample extends StatelessWidget { - const FabExample({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('FloatingActionButton Sample'), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: <Widget>[ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: <Widget>[ - const Text('Small'), - const SizedBox(width: 16), - // An example of the small floating action button. - // - // https://m3.material.io/components/floating-action-button/specs#669a1be8-7271-48cb-a74d-dd502d73bda4 - FloatingActionButton.small( - onPressed: () { - // Add your onPressed code here! - }, - child: const Icon(Icons.add), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: <Widget>[ - const Text('Regular'), - const SizedBox(width: 16), - // An example of the regular floating action button. - // - // https://m3.material.io/components/floating-action-button/specs#71504201-7bd1-423d-8bb7-07e0291743e5 - FloatingActionButton( - onPressed: () { - // Add your onPressed code here! - }, - child: const Icon(Icons.add), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: <Widget>[ - const Text('Large'), - const SizedBox(width: 16), - // An example of the large floating action button. - // - // https://m3.material.io/components/floating-action-button/specs#9d7d3d6a-bab7-47cb-be32-5596fbd660fe - FloatingActionButton.large( - onPressed: () { - // Add your onPressed code here! - }, - child: const Icon(Icons.add), - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: <Widget>[ - const Text('Extended'), - const SizedBox(width: 16), - // An example of the extended floating action button. - // - // https://m3.material.io/components/extended-fab/specs#686cb8af-87c9-48e8-a3e1-db9da6f6c69b - FloatingActionButton.extended( - onPressed: () { - // Add your onPressed code here! - }, - label: const Text('Add'), - icon: const Icon(Icons.add), - ), - ], - ), - ], - ), - ), - ); - } -} diff --git a/examples/api/lib/material/input_decorator/input_decoration.0.dart b/examples/api/lib/material/input_decorator/input_decoration.0.dart index 51454591a8e02..40c0f1f3bd352 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.0.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.0.dart @@ -14,6 +14,7 @@ class InputDecorationExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: true), home: Scaffold( appBar: AppBar(title: const Text('InputDecoration Sample')), body: const InputDecorationExample(), diff --git a/examples/api/lib/material/input_decorator/input_decoration.1.dart b/examples/api/lib/material/input_decorator/input_decoration.1.dart index 15e8feac84dcf..afcfdc39e73ed 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.1.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.1.dart @@ -14,6 +14,7 @@ class InputDecorationExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: true), home: Scaffold( appBar: AppBar(title: const Text('InputDecoration Sample')), body: const InputDecorationExample(), diff --git a/examples/api/lib/material/input_decorator/input_decoration.2.dart b/examples/api/lib/material/input_decorator/input_decoration.2.dart index ce9493f1ae898..07d1275a76c49 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.2.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.2.dart @@ -14,6 +14,7 @@ class InputDecorationExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: true), home: Scaffold( appBar: AppBar(title: const Text('InputDecoration Sample')), body: const InputDecorationExample(), diff --git a/examples/api/lib/material/input_decorator/input_decoration.3.dart b/examples/api/lib/material/input_decorator/input_decoration.3.dart index ce63e2e99cc75..09b8e8f5c7156 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.3.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.3.dart @@ -14,6 +14,7 @@ class InputDecorationExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: true), home: Scaffold( appBar: AppBar(title: const Text('InputDecoration Sample')), body: const InputDecorationExample(), diff --git a/examples/api/lib/material/input_decorator/input_decoration.floating_label_style_error.0.dart b/examples/api/lib/material/input_decorator/input_decoration.floating_label_style_error.0.dart index a7a3f2beb7569..dab7208a6cc5a 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.floating_label_style_error.0.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.floating_label_style_error.0.dart @@ -14,6 +14,7 @@ class FloatingLabelStyleErrorExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: true), home: Scaffold( appBar: AppBar(title: const Text('InputDecorator Sample')), body: const Center( diff --git a/examples/api/lib/material/input_decorator/input_decoration.label.0.dart b/examples/api/lib/material/input_decorator/input_decoration.label.0.dart index f8199678bcc1d..3bec377d2da42 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.label.0.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.label.0.dart @@ -14,6 +14,7 @@ class LabelExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: true), home: Scaffold( appBar: AppBar(title: const Text('InputDecoration.label Sample')), body: const LabelExample(), diff --git a/examples/api/lib/material/input_decorator/input_decoration.label_style_error.0.dart b/examples/api/lib/material/input_decorator/input_decoration.label_style_error.0.dart index a87a0563cab5a..186395015ad3c 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.label_style_error.0.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.label_style_error.0.dart @@ -14,6 +14,7 @@ class LabelStyleErrorExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: true), home: Scaffold( appBar: AppBar(title: const Text('InputDecorator Sample')), body: const Center( diff --git a/examples/api/lib/material/input_decorator/input_decoration.material_state.0.dart b/examples/api/lib/material/input_decorator/input_decoration.material_state.0.dart index e7b607259c0ca..a361606fc1611 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.material_state.0.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.material_state.0.dart @@ -14,6 +14,7 @@ class MaterialStateExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: true), home: Scaffold( appBar: AppBar(title: const Text('InputDecoration Sample')), body: const MaterialStateExample(), diff --git a/examples/api/lib/material/input_decorator/input_decoration.material_state.1.dart b/examples/api/lib/material/input_decorator/input_decoration.material_state.1.dart index 5a1930dd8a776..69a478cf3187b 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.material_state.1.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.material_state.1.dart @@ -14,6 +14,7 @@ class MaterialStateExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: true), home: Scaffold( appBar: AppBar(title: const Text('InputDecoration Sample')), body: const MaterialStateExample(), diff --git a/examples/api/lib/material/input_decorator/input_decoration.prefix_icon.0.dart b/examples/api/lib/material/input_decorator/input_decoration.prefix_icon.0.dart index 1a45563723087..cf11e9cfe81a3 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.prefix_icon.0.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.prefix_icon.0.dart @@ -13,8 +13,9 @@ class PrefixIconExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: Scaffold(body: InputDecoratorExample()), + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Scaffold(body: InputDecoratorExample()), ); } } diff --git a/examples/api/lib/material/input_decorator/input_decoration.prefix_icon_constraints.0.dart b/examples/api/lib/material/input_decorator/input_decoration.prefix_icon_constraints.0.dart index eb781afee09f0..ccc0eb3b07f12 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.prefix_icon_constraints.0.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.prefix_icon_constraints.0.dart @@ -14,6 +14,7 @@ class PrefixIconConstraintsExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: true), home: Scaffold( appBar: AppBar(title: const Text('InputDecoration Sample')), body: const PrefixIconConstraintsExample(), diff --git a/examples/api/lib/material/input_decorator/input_decoration.suffix_icon.0.dart b/examples/api/lib/material/input_decorator/input_decoration.suffix_icon.0.dart index eeda66917ff24..a3869ada885b3 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.suffix_icon.0.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.suffix_icon.0.dart @@ -13,8 +13,9 @@ class SuffixIconExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: Scaffold(body: InputDecoratorExample()), + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Scaffold(body: InputDecoratorExample()), ); } } diff --git a/examples/api/lib/material/input_decorator/input_decoration.suffix_icon_constraints.0.dart b/examples/api/lib/material/input_decorator/input_decoration.suffix_icon_constraints.0.dart index df1422d7abd05..1395bbb1e1dd8 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.suffix_icon_constraints.0.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.suffix_icon_constraints.0.dart @@ -14,6 +14,7 @@ class SuffixIconConstraintsExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: true), home: Scaffold( appBar: AppBar(title: const Text('InputDecoration Sample')), body: const SuffixIconConstraintsExample(), diff --git a/examples/api/lib/material/list_tile/custom_list_item.1.dart b/examples/api/lib/material/list_tile/custom_list_item.1.dart index d04b2614a0148..7cc63e1070674 100644 --- a/examples/api/lib/material/list_tile/custom_list_item.1.dart +++ b/examples/api/lib/material/list_tile/custom_list_item.1.dart @@ -13,8 +13,9 @@ class CustomListItemApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: CustomListItemExample(), + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const CustomListItemExample(), ); } } @@ -39,51 +40,38 @@ class _ArticleDescription extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: <Widget>[ - Text( - title, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), - ), - const Padding(padding: EdgeInsets.only(bottom: 2.0)), - Text( - subtitle, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 12.0, - color: Colors.black54, - ), - ), - ], + Text( + title, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, ), ), + const Padding(padding: EdgeInsets.only(bottom: 2.0)), Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.end, - children: <Widget>[ - Text( - author, - style: const TextStyle( - fontSize: 12.0, - color: Colors.black87, - ), - ), - Text( - '$publishDate - $readDuration', - style: const TextStyle( - fontSize: 12.0, - color: Colors.black54, - ), - ), - ], + child: Text( + subtitle, + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 12.0, + color: Colors.black54, + ), + ), + ), + Text( + author, + style: const TextStyle( + fontSize: 12.0, + color: Colors.black87, + ), + ), + Text( + '$publishDate - $readDuration', + style: const TextStyle( + fontSize: 12.0, + color: Colors.black54, ), ), ], diff --git a/examples/api/lib/material/list_tile/list_tile.0.dart b/examples/api/lib/material/list_tile/list_tile.0.dart index 50e712234f3f7..26226fb29de75 100644 --- a/examples/api/lib/material/list_tile/list_tile.0.dart +++ b/examples/api/lib/material/list_tile/list_tile.0.dart @@ -20,19 +20,19 @@ class ListTileApp extends StatelessWidget { ), useMaterial3: true, ), - home: const LisTileExample(), + home: const ListTileExample(), ); } } -class LisTileExample extends StatefulWidget { - const LisTileExample({super.key}); +class ListTileExample extends StatefulWidget { + const ListTileExample({super.key}); @override - State<LisTileExample> createState() => _LisTileExampleState(); + State<ListTileExample> createState() => _ListTileExampleState(); } -class _LisTileExampleState extends State<LisTileExample> with TickerProviderStateMixin { +class _ListTileExampleState extends State<ListTileExample> with TickerProviderStateMixin { late final AnimationController _fadeController; late final AnimationController _sizeController; late final Animation<double> _fadeAnimation; diff --git a/examples/api/lib/material/list_tile/list_tile.1.dart b/examples/api/lib/material/list_tile/list_tile.1.dart index 3ad480ac6e142..0f5f8bb6f776f 100644 --- a/examples/api/lib/material/list_tile/list_tile.1.dart +++ b/examples/api/lib/material/list_tile/list_tile.1.dart @@ -15,13 +15,13 @@ class ListTileApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( theme: ThemeData(useMaterial3: true), - home: const LisTileExample(), + home: const ListTileExample(), ); } } -class LisTileExample extends StatelessWidget { - const LisTileExample({super.key}); +class ListTileExample extends StatelessWidget { + const ListTileExample({super.key}); @override Widget build(BuildContext context) { diff --git a/examples/api/lib/material/list_tile/list_tile.selected.0.dart b/examples/api/lib/material/list_tile/list_tile.selected.0.dart index 513bddefc5899..c326774f6db51 100644 --- a/examples/api/lib/material/list_tile/list_tile.selected.0.dart +++ b/examples/api/lib/material/list_tile/list_tile.selected.0.dart @@ -15,19 +15,19 @@ class ListTileApp extends StatelessWidget { Widget build(BuildContext context) { return MaterialApp( theme: ThemeData(useMaterial3: true), - home: const LisTileExample(), + home: const ListTileExample(), ); } } -class LisTileExample extends StatefulWidget { - const LisTileExample({super.key}); +class ListTileExample extends StatefulWidget { + const ListTileExample({super.key}); @override - State<LisTileExample> createState() => _LisTileExampleState(); + State<ListTileExample> createState() => _ListTileExampleState(); } -class _LisTileExampleState extends State<LisTileExample> { +class _ListTileExampleState extends State<ListTileExample> { int _selectedIndex = 0; @override diff --git a/examples/api/lib/material/menu_anchor/menu_accelerator_label.0.dart b/examples/api/lib/material/menu_anchor/menu_accelerator_label.0.dart index 16869c6c6bd32..802cb9c66d07e 100644 --- a/examples/api/lib/material/menu_anchor/menu_accelerator_label.0.dart +++ b/examples/api/lib/material/menu_anchor/menu_accelerator_label.0.dart @@ -103,6 +103,7 @@ class MenuAcceleratorApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: true), home: Shortcuts( shortcuts: <ShortcutActivator, Intent>{ const SingleActivator(LogicalKeyboardKey.keyT, control: true): VoidCallbackIntent(() { diff --git a/examples/api/lib/material/menu_anchor/menu_anchor.0.dart b/examples/api/lib/material/menu_anchor/menu_anchor.0.dart index 0b3c37452564c..413879d665d30 100644 --- a/examples/api/lib/material/menu_anchor/menu_anchor.0.dart +++ b/examples/api/lib/material/menu_anchor/menu_anchor.0.dart @@ -202,8 +202,9 @@ class MenuApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: Scaffold(body: MyCascadingMenu(message: kMessage)), + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Scaffold(body: MyCascadingMenu(message: kMessage)), ); } } diff --git a/examples/api/lib/material/menu_anchor/menu_anchor.1.dart b/examples/api/lib/material/menu_anchor/menu_anchor.1.dart index b46509491a729..7ce3e78c11819 100644 --- a/examples/api/lib/material/menu_anchor/menu_anchor.1.dart +++ b/examples/api/lib/material/menu_anchor/menu_anchor.1.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -41,6 +42,7 @@ class _MyContextMenuState extends State<MyContextMenu> { final FocusNode _buttonFocusNode = FocusNode(debugLabel: 'Menu Button'); final MenuController _menuController = MenuController(); ShortcutRegistryEntry? _shortcutsEntry; + bool _menuWasEnabled = false; Color get backgroundColor => _backgroundColor; Color _backgroundColor = Colors.red; @@ -62,6 +64,12 @@ class _MyContextMenuState extends State<MyContextMenu> { } } + @override + void initState() { + super.initState(); + _disableContextMenu(); + } + @override void didChangeDependencies() { super.didChangeDependencies(); @@ -84,15 +92,38 @@ class _MyContextMenuState extends State<MyContextMenu> { void dispose() { _shortcutsEntry?.dispose(); _buttonFocusNode.dispose(); + _reenableContextMenu(); super.dispose(); } + Future<void> _disableContextMenu() async { + if (!kIsWeb) { + // Does nothing on non-web platforms. + return; + } + _menuWasEnabled = BrowserContextMenu.enabled; + if (_menuWasEnabled) { + await BrowserContextMenu.disableContextMenu(); + } + } + + void _reenableContextMenu() { + if (!kIsWeb) { + // Does nothing on non-web platforms. + return; + } + if (_menuWasEnabled && !BrowserContextMenu.enabled) { + BrowserContextMenu.enableContextMenu(); + } + } + @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(50), child: GestureDetector( onTapDown: _handleTapDown, + onSecondaryTapDown: _handleSecondaryTapDown, child: MenuAnchor( controller: _menuController, anchorTapClosesMenu: true, @@ -142,7 +173,7 @@ class _MyContextMenuState extends State<MyContextMenu> { children: <Widget>[ const Padding( padding: EdgeInsets.all(8.0), - child: Text('Ctrl-click anywhere on the background to show the menu.'), + child: Text('Right-click anywhere on the background to show the menu.'), ), Padding( padding: const EdgeInsets.all(12.0), @@ -185,12 +216,28 @@ class _MyContextMenuState extends State<MyContextMenu> { } } + void _handleSecondaryTapDown(TapDownDetails details) { + _menuController.open(position: details.localPosition); + } + void _handleTapDown(TapDownDetails details) { - if (!HardwareKeyboard.instance.logicalKeysPressed.contains(LogicalKeyboardKey.controlLeft) && - !HardwareKeyboard.instance.logicalKeysPressed.contains(LogicalKeyboardKey.controlRight)) { - return; + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + // Don't open the menu on these platforms with a Ctrl-tap (or a + // tap). + break; + case TargetPlatform.iOS: + case TargetPlatform.macOS: + // Only open the menu on these platforms if the control button is down + // when the tap occurs. + if (HardwareKeyboard.instance.logicalKeysPressed.contains(LogicalKeyboardKey.controlLeft) || + HardwareKeyboard.instance.logicalKeysPressed.contains(LogicalKeyboardKey.controlRight)) { + _menuController.open(position: details.localPosition); + } } - _menuController.open(position: details.localPosition); } } @@ -201,8 +248,9 @@ class ContextMenuApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: Scaffold(body: MyContextMenu(message: kMessage)), + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Scaffold(body: MyContextMenu(message: kMessage)), ); } } diff --git a/examples/api/lib/material/menu_anchor/menu_anchor.2.dart b/examples/api/lib/material/menu_anchor/menu_anchor.2.dart new file mode 100644 index 0000000000000..0a411a13a0864 --- /dev/null +++ b/examples/api/lib/material/menu_anchor/menu_anchor.2.dart @@ -0,0 +1,66 @@ +// 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/material.dart'; + +/// Flutter code sample for [MenuAnchor]. + +// This is the type used by the menu below. +enum SampleItem { itemOne, itemTwo, itemThree } + +void main() => runApp(const MenuAnchorApp()); + +class MenuAnchorApp extends StatelessWidget { + const MenuAnchorApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const MenuAnchorExample(), + ); + } +} + +class MenuAnchorExample extends StatefulWidget { + const MenuAnchorExample({super.key}); + + @override + State<MenuAnchorExample> createState() => _MenuAnchorExampleState(); +} + +class _MenuAnchorExampleState extends State<MenuAnchorExample> { + SampleItem? selectedMenu; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('MenuAnchorButton')), + body: Center( + child: MenuAnchor( + builder: + (BuildContext context, MenuController controller, Widget? child) { + return IconButton( + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + icon: const Icon(Icons.more_horiz), + tooltip: 'Show menu', + );}, + menuChildren: List<MenuItemButton>.generate( + 3, + (int index) => MenuItemButton( + onPressed: () => setState(() => selectedMenu = SampleItem.values[index]), + child: Text('Item ${index + 1}'), + ), + ), + ), + ), + ); + } +} diff --git a/examples/api/lib/material/navigation_bar/navigation_bar.0.dart b/examples/api/lib/material/navigation_bar/navigation_bar.0.dart index 689b97316b7b6..b5ff01d313111 100644 --- a/examples/api/lib/material/navigation_bar/navigation_bar.0.dart +++ b/examples/api/lib/material/navigation_bar/navigation_bar.0.dart @@ -36,20 +36,22 @@ class _NavigationExampleState extends State<NavigationExample> { currentPageIndex = index; }); }, + indicatorColor: Colors.amber[800], selectedIndex: currentPageIndex, destinations: const <Widget>[ NavigationDestination( - icon: Icon(Icons.explore), - label: 'Explore', + selectedIcon: Icon(Icons.home), + icon: Icon(Icons.home_outlined), + label: 'Home', ), NavigationDestination( - icon: Icon(Icons.commute), - label: 'Commute', + icon: Icon(Icons.business), + label: 'Business', ), NavigationDestination( - selectedIcon: Icon(Icons.bookmark), - icon: Icon(Icons.bookmark_border), - label: 'Saved', + selectedIcon: Icon(Icons.school), + icon: Icon(Icons.school_outlined), + label: 'School', ), ], ), diff --git a/examples/api/lib/material/navigation_drawer/navigation_drawer.1.dart b/examples/api/lib/material/navigation_drawer/navigation_drawer.1.dart new file mode 100644 index 0000000000000..344f410481773 --- /dev/null +++ b/examples/api/lib/material/navigation_drawer/navigation_drawer.1.dart @@ -0,0 +1,60 @@ +// 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/material.dart'; + +/// Flutter code sample for [NavigationDrawer]. + +void main() => runApp(const MyApp()); + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + debugShowCheckedModeBanner: false, + theme: ThemeData( + useMaterial3: true, + ), + home: const MyHomePage(), + ); + } +} + +class MyHomePage extends StatelessWidget { + const MyHomePage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Drawer Demo'), + ), + drawer: NavigationDrawer( + children: <Widget>[ + Padding( + padding: const EdgeInsets.fromLTRB(28, 16, 16, 10), + child: Text( + 'Drawer Header', + style: Theme.of(context).textTheme.titleSmall, + ), + ), + const NavigationDrawerDestination( + icon: Icon(Icons.message), + label: Text('Messages'), + ), + const NavigationDrawerDestination( + icon: Icon(Icons.account_circle), + label: Text('Profile'), + ), + const NavigationDrawerDestination( + icon: Icon(Icons.settings), + label: Text('Settings'), + ), + ]) + ); + } +} diff --git a/examples/api/lib/material/selectable_region/selectable_region.0.dart b/examples/api/lib/material/selectable_region/selectable_region.0.dart index 276cfd23a19dc..da20ccf695026 100644 --- a/examples/api/lib/material/selectable_region/selectable_region.0.dart +++ b/examples/api/lib/material/selectable_region/selectable_region.0.dart @@ -157,6 +157,7 @@ class _RenderSelectableAdapter extends RenderProxyBox with Selectable, Selection hasContent: true, startSelectionPoint: isReversed ? secondSelectionPoint : firstSelectionPoint, endSelectionPoint: isReversed ? firstSelectionPoint : secondSelectionPoint, + selectionRects: <Rect>[selectionRect], ); } } diff --git a/examples/api/lib/material/toggle_buttons/toggle_buttons.0.dart b/examples/api/lib/material/toggle_buttons/toggle_buttons.0.dart index e903fbbac32bd..3aef1c179c2a9 100644 --- a/examples/api/lib/material/toggle_buttons/toggle_buttons.0.dart +++ b/examples/api/lib/material/toggle_buttons/toggle_buttons.0.dart @@ -23,8 +23,9 @@ class ToggleButtonsExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: ToggleButtonsSample(title: 'ToggleButtons Sample'), + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const ToggleButtonsSample(title: 'ToggleButtons Sample'), ); } } diff --git a/examples/api/lib/material/toggle_buttons/toggle_buttons.1.dart b/examples/api/lib/material/toggle_buttons/toggle_buttons.1.dart new file mode 100644 index 0000000000000..7e872bea22537 --- /dev/null +++ b/examples/api/lib/material/toggle_buttons/toggle_buttons.1.dart @@ -0,0 +1,105 @@ +// 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/material.dart'; + +/// Flutter code sample for migrating from [ToggleButtons] to [SegmentedButton]. + +void main() { + runApp(const ToggleButtonsApp()); +} + +class ToggleButtonsApp extends StatelessWidget { + const ToggleButtonsApp({super.key}); + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Scaffold( + body: ToggleButtonsExample(), + ), + ); + } +} + +enum ShirtSize { extraSmall, small, medium, large, extraLarge } +const List<(ShirtSize, String)> shirtSizeOptions = <(ShirtSize, String)>[ + (ShirtSize.extraSmall, 'XS'), + (ShirtSize.small, 'S'), + (ShirtSize.medium, 'M'), + (ShirtSize.large, 'L'), + (ShirtSize.extraLarge, 'XL'), +]; + +class ToggleButtonsExample extends StatefulWidget { + const ToggleButtonsExample({super.key}); + + @override + State<ToggleButtonsExample> createState() => _ToggleButtonsExampleState(); +} + +class _ToggleButtonsExampleState extends State<ToggleButtonsExample> { + final List<bool> _toggleButtonsSelection = ShirtSize.values.map((ShirtSize e) => e == ShirtSize.medium).toList(); + Set<ShirtSize> _segmentedButtonSelection = <ShirtSize>{ShirtSize.medium}; + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: <Widget>[ + const Text('ToggleButtons'), + const SizedBox(height: 10), + // This ToggleButtons allows multiple or no selection. + ToggleButtons( + // ToggleButtons uses a List<bool> to track its selection state. + isSelected: _toggleButtonsSelection, + // This callback return the index of the child that was pressed. + onPressed: (int index) { + setState(() { + _toggleButtonsSelection[index] = !_toggleButtonsSelection[index]; + }); + }, + // Constraints are used to determine the size of each child widget. + constraints: const BoxConstraints( + minHeight: 32.0, + minWidth: 56.0, + ), + // ToggleButtons uses a List<Widget> to build its children. + children: shirtSizeOptions + .map(((ShirtSize, String) shirt) => Text(shirt.$2)) + .toList(), + ), + const SizedBox(height: 20), + const Text('SegmentedButton'), + const SizedBox(height: 10), + SegmentedButton<ShirtSize>( + // ToggleButtons above allows multiple or no selection. + // Set `multiSelectionEnabled` and `emptySelectionAllowed` to true + // to match the behavior of ToggleButtons. + multiSelectionEnabled: true, + emptySelectionAllowed: true, + // Hide the selected icon to match the behavior of ToggleButtons. + showSelectedIcon: false, + // SegmentedButton uses a Set<T> to track its selection state. + selected: _segmentedButtonSelection, + // This callback updates the set of selected segment values. + onSelectionChanged: (Set<ShirtSize> newSelection) { + setState(() { + _segmentedButtonSelection = newSelection; + }); + }, + // SegmentedButton uses a List<ButtonSegment<T>> to build its children + // instead of a List<Widget> like ToggleButtons. + segments: shirtSizeOptions + .map<ButtonSegment<ShirtSize>>(((ShirtSize, String) shirt) { + return ButtonSegment<ShirtSize>(value: shirt.$1, label: Text(shirt.$2)); + }) + .toList(), + ), + ], + ), + ); + } +} diff --git a/examples/api/lib/services/binding/handle_request_app_exit.0.dart b/examples/api/lib/services/binding/handle_request_app_exit.0.dart index 3578f435fbabd..3757b315c09ec 100644 --- a/examples/api/lib/services/binding/handle_request_app_exit.0.dart +++ b/examples/api/lib/services/binding/handle_request_app_exit.0.dart @@ -79,30 +79,29 @@ class _BodyState extends State<Body> with WidgetsBindingObserver { return Center( child: SizedBox( width: 300, - child: IntrinsicHeight( - child: Column( - children: <Widget>[ - RadioListTile<bool>( - title: const Text('Do Not Allow Exit'), - groupValue: _shouldExit, - value: false, - onChanged: _radioChanged, - ), - RadioListTile<bool>( - title: const Text('Allow Exit'), - groupValue: _shouldExit, - value: true, - onChanged: _radioChanged, - ), - const SizedBox(height: 30), - ElevatedButton( - onPressed: _quit, - child: const Text('Quit'), - ), - const SizedBox(height: 30), - Text(lastResponse), - ], - ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: <Widget>[ + RadioListTile<bool>( + title: const Text('Do Not Allow Exit'), + groupValue: _shouldExit, + value: false, + onChanged: _radioChanged, + ), + RadioListTile<bool>( + title: const Text('Allow Exit'), + groupValue: _shouldExit, + value: true, + onChanged: _radioChanged, + ), + const SizedBox(height: 30), + ElevatedButton( + onPressed: _quit, + child: const Text('Quit'), + ), + const SizedBox(height: 30), + Text(lastResponse), + ], ), ), ); diff --git a/examples/api/lib/widgets/app_lifecycle_listener/app_lifecycle_listener.0.dart b/examples/api/lib/widgets/app_lifecycle_listener/app_lifecycle_listener.0.dart new file mode 100644 index 0000000000000..09ad44d79f117 --- /dev/null +++ b/examples/api/lib/widgets/app_lifecycle_listener/app_lifecycle_listener.0.dart @@ -0,0 +1,100 @@ +// 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/material.dart'; +import 'package:flutter/scheduler.dart'; + +/// Flutter code sample for [AppLifecycleListener]. + +void main() { + runApp(const AppLifecycleListenerExample()); +} + +class AppLifecycleListenerExample extends StatelessWidget { + const AppLifecycleListenerExample({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: Scaffold(body: AppLifecycleDisplay()), + ); + } +} + +class AppLifecycleDisplay extends StatefulWidget { + const AppLifecycleDisplay({super.key}); + + @override + State<AppLifecycleDisplay> createState() => _AppLifecycleDisplayState(); +} + +class _AppLifecycleDisplayState extends State<AppLifecycleDisplay> { + late final AppLifecycleListener _listener; + final ScrollController _scrollController = ScrollController(); + final List<String> _states = <String>[]; + late AppLifecycleState? _state; + + @override + void initState() { + super.initState(); + _state = SchedulerBinding.instance.lifecycleState; + _listener = AppLifecycleListener( + onShow: () => _handleTransition('show'), + onResume: () => _handleTransition('resume'), + onHide: () => _handleTransition('hide'), + onInactive: () => _handleTransition('inactive'), + onPause: () => _handleTransition('pause'), + onDetach: () => _handleTransition('detach'), + onRestart: () => _handleTransition('restart'), + // This fires for each state change. Callbacks above fire only for + // specific state transitions. + onStateChange: _handleStateChange, + ); + if (_state != null) { + _states.add(_state!.name); + } + } + + @override + void dispose() { + _listener.dispose(); + super.dispose(); + } + + void _handleTransition(String name) { + setState(() { + _states.add(name); + }); + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); + } + + void _handleStateChange(AppLifecycleState state) { + setState(() { + _state = state; + }); + } + + @override + Widget build(BuildContext context) { + return Center( + child: SizedBox( + width: 300, + child: SingleChildScrollView( + controller: _scrollController, + child: Column( + children: <Widget>[ + Text('Current State: ${_state ?? 'Not initialized yet'}'), + const SizedBox(height: 30), + Text('State History:\n ${_states.join('\n ')}'), + ], + ), + ), + ), + ); + } +} diff --git a/examples/api/lib/widgets/app_lifecycle_listener/app_lifecycle_listener.1.dart b/examples/api/lib/widgets/app_lifecycle_listener/app_lifecycle_listener.1.dart new file mode 100644 index 0000000000000..c35945e12a663 --- /dev/null +++ b/examples/api/lib/widgets/app_lifecycle_listener/app_lifecycle_listener.1.dart @@ -0,0 +1,108 @@ +// 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:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +/// Flutter code sample for [AppLifecycleListener]. + +void main() { + runApp(const AppLifecycleListenerExample()); +} + +class AppLifecycleListenerExample extends StatelessWidget { + const AppLifecycleListenerExample({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: Scaffold(body: ApplicationExitControl()), + ); + } +} + +class ApplicationExitControl extends StatefulWidget { + const ApplicationExitControl({super.key}); + + @override + State<ApplicationExitControl> createState() => _ApplicationExitControlState(); +} + +class _ApplicationExitControlState extends State<ApplicationExitControl> { + late final AppLifecycleListener _listener; + bool _shouldExit = false; + String _lastExitResponse = 'No exit requested yet'; + + @override + void initState() { + super.initState(); + _listener = AppLifecycleListener( + onExitRequested: _handleExitRequest, + ); + } + + @override + void dispose() { + _listener.dispose(); + super.dispose(); + } + + Future<void> _quit() async { + final AppExitType exitType = _shouldExit ? AppExitType.required : AppExitType.cancelable; + await ServicesBinding.instance.exitApplication(exitType); + } + + Future<AppExitResponse> _handleExitRequest() async { + final AppExitResponse response = _shouldExit ? AppExitResponse.exit : AppExitResponse.cancel; + setState(() { + _lastExitResponse = 'App responded ${response.name} to exit request'; + }); + return response; + } + + void _radioChanged(bool? value) { + value ??= true; + if (_shouldExit == value) { + return; + } + setState(() { + _shouldExit = value!; + }); + } + + @override + Widget build(BuildContext context) { + return Center( + child: SizedBox( + width: 300, + child: Column( + mainAxisSize: MainAxisSize.min, + children: <Widget>[ + RadioListTile<bool>( + title: const Text('Do Not Allow Exit'), + groupValue: _shouldExit, + value: false, + onChanged: _radioChanged, + ), + RadioListTile<bool>( + title: const Text('Allow Exit'), + groupValue: _shouldExit, + value: true, + onChanged: _radioChanged, + ), + const SizedBox(height: 30), + ElevatedButton( + onPressed: _quit, + child: const Text('Quit'), + ), + const SizedBox(height: 30), + Text('Exit Request: $_lastExitResponse'), + ], + ), + ), + ); + } +} diff --git a/examples/api/lib/widgets/basic/custom_multi_child_layout.0.dart b/examples/api/lib/widgets/basic/custom_multi_child_layout.0.dart index 92ab2a2cc6566..800e8330ef199 100644 --- a/examples/api/lib/widgets/basic/custom_multi_child_layout.0.dart +++ b/examples/api/lib/widgets/basic/custom_multi_child_layout.0.dart @@ -102,7 +102,7 @@ class CustomMultiChildLayoutExample extends StatelessWidget { ), children: <Widget>[ // Create all of the colored boxes in the colors map. - for (MapEntry<String, Color> entry in _colors.entries) + for (final MapEntry<String, Color> entry in _colors.entries) // The "id" can be any Object, not just a String. LayoutId( id: entry.key, diff --git a/examples/api/lib/widgets/basic/indexed_stack.0.dart b/examples/api/lib/widgets/basic/indexed_stack.0.dart index f88ab1f470766..8d19cca4f725f 100644 --- a/examples/api/lib/widgets/basic/indexed_stack.0.dart +++ b/examples/api/lib/widgets/basic/indexed_stack.0.dart @@ -76,7 +76,7 @@ class _IndexedStackExampleState extends State<IndexedStackExample> { children: <Widget>[ IndexedStack( index: index, - children: <Widget>[for (String name in names) PersonTracker(name: name)], + children: <Widget>[for (final String name in names) PersonTracker(name: name)], ) ], ), diff --git a/examples/api/lib/widgets/heroes/hero.1.dart b/examples/api/lib/widgets/heroes/hero.1.dart index f71686208c351..63d6931c38c36 100644 --- a/examples/api/lib/widgets/heroes/hero.1.dart +++ b/examples/api/lib/widgets/heroes/hero.1.dart @@ -18,8 +18,9 @@ class HeroApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: HeroExample(), + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const HeroExample(), ); } } diff --git a/examples/api/lib/widgets/implicit_animations/animated_slide.0.dart b/examples/api/lib/widgets/implicit_animations/animated_slide.0.dart index 8f6b94f827dd3..968955d154c9c 100644 --- a/examples/api/lib/widgets/implicit_animations/animated_slide.0.dart +++ b/examples/api/lib/widgets/implicit_animations/animated_slide.0.dart @@ -13,8 +13,9 @@ class AnimatedSlideApp extends StatelessWidget { @override Widget build(BuildContext context) { - return const MaterialApp( - home: AnimatedSlideExample(), + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const AnimatedSlideExample(), ); } } diff --git a/examples/api/lib/widgets/sliver/decorated_sliver.0.dart b/examples/api/lib/widgets/sliver/decorated_sliver.0.dart new file mode 100644 index 0000000000000..5ccd9b93cfb05 --- /dev/null +++ b/examples/api/lib/widgets/sliver/decorated_sliver.0.dart @@ -0,0 +1,58 @@ +// 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/material.dart'; + +void main() => runApp(const SliverDecorationExampleApp()); + +class SliverDecorationExampleApp extends StatelessWidget { + const SliverDecorationExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('SliverDecoration Sample')), + body: const SliverDecorationExample(), + ), + ); + } +} + +class SliverDecorationExample extends StatelessWidget { + const SliverDecorationExample({super.key}); + + @override + Widget build(BuildContext context) { + return CustomScrollView( + slivers: <Widget>[ + DecoratedSliver( + decoration: const BoxDecoration( + gradient: RadialGradient( + center: Alignment(-0.5, -0.6), + radius: 0.15, + colors: <Color>[ + Color(0xFFEEEEEE), + Color(0xFF111133), + ], + stops: <double>[0.9, 1.0], + ), + ), + sliver: SliverList( + delegate: SliverChildListDelegate(<Widget>[ + const Text('Goodnight Moon'), + ]), + ), + ), + const DecoratedSliver( + decoration: BoxDecoration( + color: Colors.amber, + borderRadius: BorderRadius.all(Radius.circular(50)) + ), + sliver: SliverToBoxAdapter(child: SizedBox(height: 300)), + ), + ], + ); + } +} diff --git a/examples/api/lib/widgets/sliver/sliver_main_axis_group.0.dart b/examples/api/lib/widgets/sliver/sliver_main_axis_group.0.dart new file mode 100644 index 0000000000000..14fdb3ac4200a --- /dev/null +++ b/examples/api/lib/widgets/sliver/sliver_main_axis_group.0.dart @@ -0,0 +1,75 @@ +// 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/material.dart'; + +void main() => runApp(const SliverMainAxisGroupExampleApp()); + +class SliverMainAxisGroupExampleApp extends StatelessWidget { + const SliverMainAxisGroupExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('SliverMainAxisGroup Sample')), + body: const SliverMainAxisGroupExample(), + ), + ); + } +} + +class SliverMainAxisGroupExample extends StatelessWidget { + const SliverMainAxisGroupExample({super.key}); + + @override + Widget build(BuildContext context) { + return CustomScrollView( + slivers: <Widget>[ + SliverMainAxisGroup( + slivers: <Widget>[ + const SliverAppBar( + title: Text('Section Title'), + expandedHeight: 70.0, + pinned: true, + ), + SliverList.builder( + itemBuilder: (BuildContext context, int index) { + return Container( + color: index.isEven ? Colors.amber[300] : Colors.blue[300], + height: 100.0, + child: Center( + child: Text( + 'Item $index', + style: const TextStyle(fontSize: 24), + ), + ), + ); + }, + itemCount: 5, + ), + SliverToBoxAdapter( + child: Container( + color: Colors.cyan, + height: 100, + child: const Center( + child: Text('Another sliver child', style: TextStyle(fontSize: 24)), + ), + ), + ) + ], + ), + SliverToBoxAdapter( + child: Container( + height: 1000, + decoration: const BoxDecoration(color: Colors.greenAccent), + child: const Center( + child: Text('Hello World!', style: TextStyle(fontSize: 24)), + ), + ), + ), + ], + ); + } +} diff --git a/examples/api/lib/widgets/text_magnifier/text_magnifier.0.dart b/examples/api/lib/widgets/text_magnifier/text_magnifier.0.dart index 3aa5f35c5d1a2..03daf7e8f0e29 100644 --- a/examples/api/lib/widgets/text_magnifier/text_magnifier.0.dart +++ b/examples/api/lib/widgets/text_magnifier/text_magnifier.0.dart @@ -20,6 +20,7 @@ class TextMagnifierExampleApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: true), home: Scaffold( body: Padding( padding: const EdgeInsets.symmetric(horizontal: 48.0), diff --git a/examples/api/linux/flutter/generated_plugin_registrant.cc b/examples/api/linux/flutter/generated_plugin_registrant.cc deleted file mode 100644 index e71a16d23d058..0000000000000 --- a/examples/api/linux/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void fl_register_plugins(FlPluginRegistry* registry) { -} diff --git a/examples/api/linux/flutter/generated_plugin_registrant.h b/examples/api/linux/flutter/generated_plugin_registrant.h deleted file mode 100644 index e0f0a47bc08f3..0000000000000 --- a/examples/api/linux/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include <flutter_linux/flutter_linux.h> - -// Registers Flutter plugins. -void fl_register_plugins(FlPluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/examples/api/linux/flutter/generated_plugins.cmake b/examples/api/linux/flutter/generated_plugins.cmake deleted file mode 100644 index 2e1de87a7eb61..0000000000000 --- a/examples/api/linux/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/examples/api/pubspec.yaml b/examples/api/pubspec.yaml index 83fb83b0585ae..eed8da11cee1a 100644 --- a/examples/api/pubspec.yaml +++ b/examples/api/pubspec.yaml @@ -17,10 +17,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: integration_test: @@ -31,11 +31,11 @@ dev_dependencies: sdk: flutter flutter_test: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -45,12 +45,13 @@ dev_dependencies: fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -71,10 +72,10 @@ dev_dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -84,4 +85,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: a4de +# PUBSPEC CHECKSUM: 0b3d diff --git a/examples/api/test/material/floating_action_button/floating_action_button.0_test.dart b/examples/api/test/material/floating_action_button/floating_action_button.0_test.dart index 9fa5b6c49746e..bb1f1cbb4bd7d 100644 --- a/examples/api/test/material/floating_action_button/floating_action_button.0_test.dart +++ b/examples/api/test/material/floating_action_button/floating_action_button.0_test.dart @@ -16,11 +16,30 @@ void main() { expect(find.byType(FloatingActionButton), findsOneWidget); expect(find.byIcon(Icons.navigation), findsOneWidget); - final Finder materialButtonFinder = find.byType(RawMaterialButton); RawMaterialButton getRawMaterialButtonWidget() { - return tester.widget<RawMaterialButton>(materialButtonFinder); + return tester.widget<RawMaterialButton>(find.byType(RawMaterialButton)); } + + Color? getIconColor() { + final RichText iconRichText = tester.widget<RichText>( + find.descendant(of: find.byIcon(Icons.navigation), matching: find.byType(RichText)), + ); + return iconRichText.text.style?.color; + } + + await tester.tap(find.byType(FloatingActionButton)); + await tester.pumpAndSettle(); // Wait for the animation to finish. + expect(getRawMaterialButtonWidget().fillColor, Colors.green); + + await tester.tap(find.byType(FloatingActionButton)); + await tester.pumpAndSettle(); // Wait for the animation to finish. + expect(getRawMaterialButtonWidget().fillColor, Colors.green); + expect(getIconColor(), Colors.white); + + await tester.tap(find.byType(FloatingActionButton)); + await tester.pumpAndSettle(); // Wait for the animation to finish. expect(getRawMaterialButtonWidget().fillColor, Colors.green); + expect(getIconColor(), Colors.white); expect(getRawMaterialButtonWidget().shape, const CircleBorder()); }); } diff --git a/examples/api/test/material/floating_action_button/floating_action_button.1_test.dart b/examples/api/test/material/floating_action_button/floating_action_button.1_test.dart index 049d63576b3c8..eb6ba0e8e7c0c 100644 --- a/examples/api/test/material/floating_action_button/floating_action_button.1_test.dart +++ b/examples/api/test/material/floating_action_button/floating_action_button.1_test.dart @@ -8,20 +8,42 @@ import 'package:flutter_api_samples/material/floating_action_button/floating_act import 'package:flutter_test/flutter_test.dart'; void main() { - testWidgets('FloatingActionButton.extended', (WidgetTester tester) async { + testWidgets('FloatingActionButton variants', (WidgetTester tester) async { + RawMaterialButton getRawMaterialButtonWidget(Finder finder) { + return tester.widget<RawMaterialButton>(finder); + } + await tester.pumpWidget( const example.FloatingActionButtonExampleApp(), ); - expect(find.byType(FloatingActionButton), findsOneWidget); - expect(find.text('Approve'), findsOneWidget); - expect(find.byIcon(Icons.thumb_up), findsOneWidget); + final ThemeData theme = ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true); - final Finder materialButtonFinder = find.byType(RawMaterialButton); - RawMaterialButton getRawMaterialButtonWidget() { - return tester.widget<RawMaterialButton>(materialButtonFinder); - } - expect(getRawMaterialButtonWidget().fillColor, Colors.pink); - expect(getRawMaterialButtonWidget().shape, const StadiumBorder()); + expect(find.byType(FloatingActionButton), findsNWidgets(4)); + expect(find.byIcon(Icons.add), findsNWidgets(4)); + + final Finder smallFabMaterialButton = find.byType(RawMaterialButton).at(0); + final RenderBox smallFabRenderBox = tester.renderObject(smallFabMaterialButton); + expect(smallFabRenderBox.size, const Size(48.0, 48.0)); + expect(getRawMaterialButtonWidget(smallFabMaterialButton).fillColor, theme.colorScheme.primaryContainer); + expect(getRawMaterialButtonWidget(smallFabMaterialButton).shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0))); + + final Finder regularFABMaterialButton = find.byType(RawMaterialButton).at(1); + final RenderBox regularFABRenderBox = tester.renderObject(regularFABMaterialButton); + expect(regularFABRenderBox.size, const Size(56.0, 56.0)); + expect(getRawMaterialButtonWidget(regularFABMaterialButton).fillColor, theme.colorScheme.primaryContainer); + expect(getRawMaterialButtonWidget(regularFABMaterialButton).shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0))); + + final Finder largeFABMaterialButton = find.byType(RawMaterialButton).at(2); + final RenderBox largeFABRenderBox = tester.renderObject(largeFABMaterialButton); + expect(largeFABRenderBox.size, const Size(96.0, 96.0)); + expect(getRawMaterialButtonWidget(largeFABMaterialButton).fillColor, theme.colorScheme.primaryContainer); + expect(getRawMaterialButtonWidget(largeFABMaterialButton).shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(28.0))); + + final Finder extendedFABMaterialButton = find.byType(RawMaterialButton).at(3); + final RenderBox extendedFABRenderBox = tester.renderObject(extendedFABMaterialButton); + expect(extendedFABRenderBox.size, const Size(111.0, 56.0)); + expect(getRawMaterialButtonWidget(extendedFABMaterialButton).fillColor, theme.colorScheme.primaryContainer); + expect(getRawMaterialButtonWidget(extendedFABMaterialButton).shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0))); }); } diff --git a/examples/api/test/material/floating_action_button/floating_action_button.2_test.dart b/examples/api/test/material/floating_action_button/floating_action_button.2_test.dart deleted file mode 100644 index d7cd0edd95477..0000000000000 --- a/examples/api/test/material/floating_action_button/floating_action_button.2_test.dart +++ /dev/null @@ -1,27 +0,0 @@ -// 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/material.dart'; -import 'package:flutter_api_samples/material/floating_action_button/floating_action_button.2.dart' - as example; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('FloatingActionButton - Material 3', (WidgetTester tester) async { - RawMaterialButton getRawMaterialButtonWidget(Finder finder) { - return tester.widget<RawMaterialButton>(finder); - } - await tester.pumpWidget( - const example.FloatingActionButtonExampleApp(), - ); - - expect(find.byType(FloatingActionButton), findsOneWidget); - expect(find.byIcon(Icons.add), findsOneWidget); - - final ThemeData theme = ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true); - final Finder materialButtonFinder = find.byType(RawMaterialButton); - expect(getRawMaterialButtonWidget(materialButtonFinder).fillColor, theme.colorScheme.primaryContainer); - expect(getRawMaterialButtonWidget(materialButtonFinder).shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0))); - }); -} diff --git a/examples/api/test/material/floating_action_button/floating_action_button.3_test.dart b/examples/api/test/material/floating_action_button/floating_action_button.3_test.dart deleted file mode 100644 index 173ef748d0b4d..0000000000000 --- a/examples/api/test/material/floating_action_button/floating_action_button.3_test.dart +++ /dev/null @@ -1,49 +0,0 @@ -// 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/material.dart'; -import 'package:flutter_api_samples/material/floating_action_button/floating_action_button.3.dart' - as example; -import 'package:flutter_test/flutter_test.dart'; - -void main() { - testWidgets('FloatingActionButton variants', (WidgetTester tester) async { - RawMaterialButton getRawMaterialButtonWidget(Finder finder) { - return tester.widget<RawMaterialButton>(finder); - } - - await tester.pumpWidget( - const example.FloatingActionButtonExampleApp(), - ); - - final ThemeData theme = ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true); - - expect(find.byType(FloatingActionButton), findsNWidgets(4)); - expect(find.byIcon(Icons.add), findsNWidgets(4)); - - final Finder smallFabMaterialButton = find.byType(RawMaterialButton).at(0); - final RenderBox smallFabRenderBox = tester.renderObject(smallFabMaterialButton); - expect(smallFabRenderBox.size, const Size(48.0, 48.0)); - expect(getRawMaterialButtonWidget(smallFabMaterialButton).fillColor, theme.colorScheme.primaryContainer); - expect(getRawMaterialButtonWidget(smallFabMaterialButton).shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.0))); - - final Finder regularFABMaterialButton = find.byType(RawMaterialButton).at(1); - final RenderBox regularFABRenderBox = tester.renderObject(regularFABMaterialButton); - expect(regularFABRenderBox.size, const Size(56.0, 56.0)); - expect(getRawMaterialButtonWidget(regularFABMaterialButton).fillColor, theme.colorScheme.primaryContainer); - expect(getRawMaterialButtonWidget(regularFABMaterialButton).shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0))); - - final Finder largeFABMaterialButton = find.byType(RawMaterialButton).at(2); - final RenderBox largeFABRenderBox = tester.renderObject(largeFABMaterialButton); - expect(largeFABRenderBox.size, const Size(96.0, 96.0)); - expect(getRawMaterialButtonWidget(largeFABMaterialButton).fillColor, theme.colorScheme.primaryContainer); - expect(getRawMaterialButtonWidget(largeFABMaterialButton).shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(28.0))); - - final Finder extendedFABMaterialButton = find.byType(RawMaterialButton).at(3); - final RenderBox extendedFABRenderBox = tester.renderObject(extendedFABMaterialButton); - expect(extendedFABRenderBox.size, const Size(111.0, 56.0)); - expect(getRawMaterialButtonWidget(extendedFABMaterialButton).fillColor, theme.colorScheme.primaryContainer); - expect(getRawMaterialButtonWidget(extendedFABMaterialButton).shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.0))); - }); -} diff --git a/examples/api/test/material/input_decorator/input_decoration.prefix_icon.0_test.dart b/examples/api/test/material/input_decorator/input_decoration.prefix_icon.0_test.dart index fcfa9a10070cb..359f1fd1f5556 100644 --- a/examples/api/test/material/input_decorator/input_decoration.prefix_icon.0_test.dart +++ b/examples/api/test/material/input_decorator/input_decoration.prefix_icon.0_test.dart @@ -11,6 +11,6 @@ void main() { await tester.pumpWidget( const example.PrefixIconExampleApp(), ); - expect(tester.getCenter(find.byIcon(Icons.person)).dy, 28.0); + expect(tester.getCenter(find.byIcon(Icons.person)).dy, 32.0); }); } diff --git a/examples/api/test/material/input_decorator/input_decoration.suffix_icon.0_test.dart b/examples/api/test/material/input_decorator/input_decoration.suffix_icon.0_test.dart index 7dd5538eaa14d..0d26b422e4a11 100644 --- a/examples/api/test/material/input_decorator/input_decoration.suffix_icon.0_test.dart +++ b/examples/api/test/material/input_decorator/input_decoration.suffix_icon.0_test.dart @@ -11,6 +11,6 @@ void main() { await tester.pumpWidget( const example.SuffixIconExampleApp(), ); - expect(tester.getCenter(find.byIcon(Icons.remove_red_eye)).dy, 28.0); + expect(tester.getCenter(find.byIcon(Icons.remove_red_eye)).dy, 32.0); }); } diff --git a/examples/api/test/material/list_tile/custom_list_item.1_test.dart b/examples/api/test/material/list_tile/custom_list_item.1_test.dart index 0002d0125dad8..862350b60c27a 100644 --- a/examples/api/test/material/list_tile/custom_list_item.1_test.dart +++ b/examples/api/test/material/list_tile/custom_list_item.1_test.dart @@ -20,7 +20,7 @@ void main() { expect(thumbnailAspectRatio.aspectRatio, 1.0); // The Expanded widget is used to control the size of the text. - Expanded textExpanded = tester.widget(find.ancestor( + final Expanded textExpanded = tester.widget(find.ancestor( of: find.text('Flutter 1.0 Launch'), matching: find.byType(Expanded).at(0), )); @@ -32,12 +32,5 @@ void main() { matching: find.byType(AspectRatio), )); expect(thumbnailAspectRatio.aspectRatio, 1.0); - - // The Expanded widget is used to control the size of the text. - textExpanded = tester.widget(find.ancestor( - of: find.text('Flutter 1.2 Release - Continual updates to the framework'), - matching: find.byType(Expanded).at(3), - )); - expect(textExpanded.flex, 1); }); } diff --git a/examples/api/test/material/menu_anchor/menu_accelerator_label.0_test.dart b/examples/api/test/material/menu_anchor/menu_accelerator_label.0_test.dart index d32700dbce29e..0268dcab84ee0 100644 --- a/examples/api/test/material/menu_anchor/menu_accelerator_label.0_test.dart +++ b/examples/api/test/material/menu_anchor/menu_accelerator_label.0_test.dart @@ -29,7 +29,7 @@ void main() { expect(find.text('About', findRichText: true), findsOneWidget); expect( tester.getRect(findMenu('About')), - equals(const Rect.fromLTRB(4.0, 48.0, 98.0, 208.0)), + equals(const Rect.fromLTRB(4.0, 48.0, 111.0, 208.0)), ); expect(find.text('Save', findRichText: true), findsOneWidget); expect(find.text('Quit', findRichText: true), findsOneWidget); @@ -45,10 +45,10 @@ void main() { expect(find.text('Quit', findRichText: true), findsNothing); expect(find.text('Magnify', findRichText: true), findsNothing); expect(find.text('Minify', findRichText: true), findsNothing); - expect(find.text('CLOSE'), findsOneWidget); + expect(find.text('Close'), findsOneWidget); - await tester.tap(find.text('CLOSE')); + await tester.tap(find.text('Close')); await tester.pumpAndSettle(); - expect(find.text('CLOSE'), findsNothing); + expect(find.text('Close'), findsNothing); }); } diff --git a/examples/api/test/material/menu_anchor/menu_anchor.1_test.dart b/examples/api/test/material/menu_anchor/menu_anchor.1_test.dart index c1c92c2bf7fa5..b9dc670d1c8fc 100644 --- a/examples/api/test/material/menu_anchor/menu_anchor.1_test.dart +++ b/examples/api/test/material/menu_anchor/menu_anchor.1_test.dart @@ -2,6 +2,7 @@ // 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/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_api_samples/material/menu_anchor/menu_anchor.1.dart' as example; @@ -18,17 +19,15 @@ void main() { await tester.pumpWidget(const example.ContextMenuApp()); - await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight); - await tester.tapAt(const Offset(100, 200)); + await tester.tapAt(const Offset(100, 200), buttons: kSecondaryButton); await tester.pump(); - expect(tester.getRect(findMenu()), equals(const Rect.fromLTRB(100.0, 200.0, 388.0, 360.0))); + expect(tester.getRect(findMenu()), equals(const Rect.fromLTRB(100.0, 200.0, 433.0, 360.0))); // Make sure tapping in a different place causes the menu to move. - await tester.tapAt(const Offset(200, 100)); + await tester.tapAt(const Offset(200, 100), buttons: kSecondaryButton); await tester.pump(); - await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight); - expect(tester.getRect(findMenu()), equals(const Rect.fromLTRB(200.0, 100.0, 488.0, 260.0))); + expect(tester.getRect(findMenu()), equals(const Rect.fromLTRB(200.0, 100.0, 533.0, 260.0))); expect(find.text(example.MenuEntry.about.label), findsOneWidget); expect(find.text(example.MenuEntry.showMessage.label), findsOneWidget); @@ -67,8 +66,7 @@ void main() { ); // Open the menu so we can look for state changes reflected in the menu. - await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight); - await tester.tapAt(const Offset(100, 200)); + await tester.tapAt(const Offset(100, 200), buttons: kSecondaryButton); await tester.pump(); expect(find.text(example.MenuEntry.showMessage.label), findsOneWidget); diff --git a/examples/api/test/material/navigation_bar/navigation_bar.0_test.dart b/examples/api/test/material/navigation_bar/navigation_bar.0_test.dart index b78688985bdfd..f18598a397cf0 100644 --- a/examples/api/test/material/navigation_bar/navigation_bar.0_test.dart +++ b/examples/api/test/material/navigation_bar/navigation_bar.0_test.dart @@ -16,20 +16,20 @@ void main() { final NavigationBar navigationBarWidget = tester.firstWidget(find.byType(NavigationBar)); /// NavigationDestinations must be rendered - expect(find.text('Explore'), findsOneWidget); - expect(find.text('Commute'), findsOneWidget); - expect(find.text('Saved'), findsOneWidget); + expect(find.text('Home'), findsOneWidget); + expect(find.text('Business'), findsOneWidget); + expect(find.text('School'), findsOneWidget); /// initial index must be zero expect(navigationBarWidget.selectedIndex, 0); /// switch to second tab - await tester.tap(find.text('Commute')); + await tester.tap(find.text('Business')); await tester.pumpAndSettle(); expect(find.text('Page 2'), findsOneWidget); /// switch to third tab - await tester.tap(find.text('Saved')); + await tester.tap(find.text('School')); await tester.pumpAndSettle(); expect(find.text('Page 3'), findsOneWidget); }); diff --git a/examples/api/test/material/tabs/tab_bar.2_test.dart b/examples/api/test/material/tabs/tab_bar.2_test.dart index d5ff520f71420..1bca1ffa9d369 100644 --- a/examples/api/test/material/tabs/tab_bar.2_test.dart +++ b/examples/api/test/material/tabs/tab_bar.2_test.dart @@ -24,45 +24,40 @@ void main() { final TabBar secondaryTabBar = tester.widget<TabBar>(find.byType(TabBar).first); expect(secondaryTabBar.tabs.length, 2); - final Finder primaryTab1 = find.widgetWithText(Tab, primaryTabLabel1); - final Finder primaryTab2 = find.widgetWithText(Tab, primaryTabLabel2); - final Finder primaryTab3 = find.widgetWithText(Tab, primaryTabLabel3); - final Finder secondaryTab2 = find.widgetWithText(Tab, secondaryTabLabel2); - String tabBarViewText = '$primaryTabLabel2: $secondaryTabLabel1 tab'; expect(find.text(tabBarViewText), findsOneWidget); - await tester.tap(primaryTab1); + await tester.tap(find.text(primaryTabLabel1)); await tester.pumpAndSettle(); tabBarViewText = '$primaryTabLabel1: $secondaryTabLabel1 tab'; expect(find.text(tabBarViewText), findsOneWidget); - await tester.tap(secondaryTab2); + await tester.tap(find.text(secondaryTabLabel2)); await tester.pumpAndSettle(); tabBarViewText = '$primaryTabLabel1: $secondaryTabLabel2 tab'; expect(find.text(tabBarViewText), findsOneWidget); - await tester.tap(primaryTab2); + await tester.tap(find.text(primaryTabLabel2)); await tester.pumpAndSettle(); tabBarViewText = '$primaryTabLabel2: $secondaryTabLabel1 tab'; expect(find.text(tabBarViewText), findsOneWidget); - await tester.tap(secondaryTab2); + await tester.tap(find.text(secondaryTabLabel2)); await tester.pumpAndSettle(); tabBarViewText = '$primaryTabLabel2: $secondaryTabLabel2 tab'; expect(find.text(tabBarViewText), findsOneWidget); - await tester.tap(primaryTab3); + await tester.tap(find.text(primaryTabLabel3)); await tester.pumpAndSettle(); tabBarViewText = '$primaryTabLabel3: $secondaryTabLabel1 tab'; expect(find.text(tabBarViewText), findsOneWidget); - await tester.tap(secondaryTab2); + await tester.tap(find.text(secondaryTabLabel2)); await tester.pumpAndSettle(); tabBarViewText = '$primaryTabLabel3: $secondaryTabLabel2 tab'; diff --git a/examples/api/test/material/toggle_buttons/toggle_buttons.0_test.dart b/examples/api/test/material/toggle_buttons/toggle_buttons.0_test.dart index e183031b4176f..e2b5063491124 100644 --- a/examples/api/test/material/toggle_buttons/toggle_buttons.0_test.dart +++ b/examples/api/test/material/toggle_buttons/toggle_buttons.0_test.dart @@ -24,10 +24,13 @@ void main() { TextButton secondButton = findButton('Banana'); TextButton thirdButton = findButton('Orange'); + const Color selectedColor = Color(0xffef9a9a); + const Color unselectedColor = Color(0x00fffbfe); + /// First button is selected. - expect(firstButton.style!.backgroundColor!.resolve(enabled), const Color(0xffef9a9a)); - expect(secondButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); - expect(thirdButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + expect(firstButton.style!.backgroundColor!.resolve(enabled), selectedColor); + expect(secondButton.style!.backgroundColor!.resolve(enabled), unselectedColor); + expect(thirdButton.style!.backgroundColor!.resolve(enabled), unselectedColor); /// Tap on second button. await tester.tap(find.widgetWithText(TextButton, 'Banana')); @@ -38,9 +41,9 @@ void main() { thirdButton = findButton('Orange'); /// Only second button is selected. - expect(firstButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); - expect(secondButton.style!.backgroundColor!.resolve(enabled), const Color(0xffef9a9a)); - expect(thirdButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + expect(firstButton.style!.backgroundColor!.resolve(enabled), unselectedColor); + expect(secondButton.style!.backgroundColor!.resolve(enabled), selectedColor); + expect(thirdButton.style!.backgroundColor!.resolve(enabled), unselectedColor); }); testWidgets('Multi-select ToggleButtons', (WidgetTester tester) async { @@ -59,10 +62,13 @@ void main() { TextButton secondButton = findButton('Potatoes'); TextButton thirdButton = findButton('Carrots'); + const Color selectedColor = Color(0xffa5d6a7); + const Color unselectedColor = Color(0x00fffbfe); + /// Second button is selected. - expect(firstButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); - expect(secondButton.style!.backgroundColor!.resolve(enabled), const Color(0xffa5d6a7)); - expect(thirdButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + expect(firstButton.style!.backgroundColor!.resolve(enabled), unselectedColor); + expect(secondButton.style!.backgroundColor!.resolve(enabled), selectedColor); + expect(thirdButton.style!.backgroundColor!.resolve(enabled), unselectedColor); /// Tap on other two buttons. await tester.tap(find.widgetWithText(TextButton, 'Tomatoes')); @@ -74,9 +80,9 @@ void main() { thirdButton = findButton('Carrots'); /// All buttons are selected. - expect(firstButton.style!.backgroundColor!.resolve(enabled), const Color(0xffa5d6a7)); - expect(secondButton.style!.backgroundColor!.resolve(enabled), const Color(0xffa5d6a7)); - expect(thirdButton.style!.backgroundColor!.resolve(enabled), const Color(0xffa5d6a7)); + expect(firstButton.style!.backgroundColor!.resolve(enabled), selectedColor); + expect(secondButton.style!.backgroundColor!.resolve(enabled), selectedColor); + expect(thirdButton.style!.backgroundColor!.resolve(enabled), selectedColor); }); testWidgets('Icon-only ToggleButtons', (WidgetTester tester) async { @@ -95,10 +101,14 @@ void main() { TextButton secondButton = findButton(Icons.cloud); TextButton thirdButton = findButton(Icons.ac_unit); + const Color selectedColor = Color(0xff90caf9); + const Color unselectedColor = Color(0x00fffbfe); + + /// Third button is selected. - expect(firstButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); - expect(secondButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); - expect(thirdButton.style!.backgroundColor!.resolve(enabled), const Color(0xff90caf9)); + expect(firstButton.style!.backgroundColor!.resolve(enabled), unselectedColor); + expect(secondButton.style!.backgroundColor!.resolve(enabled), unselectedColor); + expect(thirdButton.style!.backgroundColor!.resolve(enabled), selectedColor); /// Tap on the first button. await tester.tap(find.widgetWithIcon(TextButton, Icons.sunny)); @@ -109,9 +119,9 @@ void main() { thirdButton = findButton(Icons.ac_unit); /// First button os selected. - expect(firstButton.style!.backgroundColor!.resolve(enabled), const Color(0xff90caf9)); - expect(secondButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); - expect(thirdButton.style!.backgroundColor!.resolve(enabled), const Color(0x00ffffff)); + expect(firstButton.style!.backgroundColor!.resolve(enabled), selectedColor); + expect(secondButton.style!.backgroundColor!.resolve(enabled), unselectedColor); + expect(thirdButton.style!.backgroundColor!.resolve(enabled), unselectedColor); }); } diff --git a/examples/api/test/material/toggle_buttons/toggle_buttons.1_test.dart b/examples/api/test/material/toggle_buttons/toggle_buttons.1_test.dart new file mode 100644 index 0000000000000..cc8d7ef1bd968 --- /dev/null +++ b/examples/api/test/material/toggle_buttons/toggle_buttons.1_test.dart @@ -0,0 +1,137 @@ +// 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/material.dart'; +import 'package:flutter_api_samples/material/toggle_buttons/toggle_buttons.1.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('ToggleButtons allows multiple or no selection', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + Finder findButton(String text) { + return find.descendant( + of: find.byType(ToggleButtons), + matching: find.widgetWithText(TextButton, text), + ); + } + + await tester.pumpWidget(const example.ToggleButtonsApp()); + + TextButton toggleMButton = tester.widget<TextButton>(findButton('M')); + TextButton toggleXLButton = tester.widget<TextButton>(findButton('XL')); + + // Initially, only M is selected. + expect( + toggleMButton.style!.backgroundColor!.resolve(enabled), + theme.colorScheme.primary.withOpacity(0.12), + ); + expect( + toggleXLButton.style!.backgroundColor!.resolve(enabled), + theme.colorScheme.surface.withOpacity(0.0), + ); + + // Tap on XL. + await tester.tap(findButton('XL')); + await tester.pumpAndSettle(); + + // Now both M and XL are selected. + toggleMButton = tester.widget<TextButton>(findButton('M')); + toggleXLButton = tester.widget<TextButton>(findButton('XL')); + + expect( + toggleMButton.style!.backgroundColor!.resolve(enabled), + theme.colorScheme.primary.withOpacity(0.12), + ); + expect( + toggleXLButton.style!.backgroundColor!.resolve(enabled), + theme.colorScheme.primary.withOpacity(0.12), + ); + + // Tap M to deselect it. + await tester.tap(findButton('M')); + await tester.pumpAndSettle(); + + // Tap XL to deselect it. + await tester.tap(findButton('XL')); + await tester.pumpAndSettle(); + + // Now neither M nor XL are selected. + toggleMButton = tester.widget<TextButton>(findButton('M')); + toggleXLButton = tester.widget<TextButton>(findButton('XL')); + + expect( + toggleMButton.style!.backgroundColor!.resolve(enabled), + theme.colorScheme.surface.withOpacity(0.0), + ); + expect( + toggleXLButton.style!.backgroundColor!.resolve(enabled), + theme.colorScheme.surface.withOpacity(0.0), + ); + }); + + testWidgets('SegmentedButton allows multiple or no selection', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + Finder findButton(String text) { + return find.descendant( + of: find.byType(SegmentedButton<example.ShirtSize>), + matching: find.widgetWithText(TextButton, text), + ); + } + + await tester.pumpWidget(const example.ToggleButtonsApp()); + + Material segmentMButton = tester.widget<Material>(find.descendant( + of: findButton('M'), + matching: find.byType(Material), + )); + Material segmentXLButton = tester.widget<Material>(find.descendant( + of: findButton('XL'), + matching: find.byType(Material), + )); + + // Initially, only M is selected. + expect(segmentMButton.color, theme.colorScheme.secondaryContainer); + expect(segmentXLButton.color, Colors.transparent); + + // Tap on XL. + await tester.tap(findButton('XL')); + await tester.pumpAndSettle(); + + // // Now both M and XL are selected. + segmentMButton = tester.widget<Material>(find.descendant( + of: findButton('M'), + matching: find.byType(Material), + )); + segmentXLButton = tester.widget<Material>(find.descendant( + of: findButton('XL'), + matching: find.byType(Material), + )); + + expect(segmentMButton.color, theme.colorScheme.secondaryContainer); + expect(segmentXLButton.color, theme.colorScheme.secondaryContainer); + + // Tap M to deselect it. + await tester.tap(findButton('M')); + await tester.pumpAndSettle(); + + // Tap XL to deselect it. + await tester.tap(findButton('XL')); + await tester.pumpAndSettle(); + + // Now neither M nor XL are selected. + segmentMButton = tester.widget<Material>(find.descendant( + of: findButton('M'), + matching: find.byType(Material), + )); + segmentXLButton = tester.widget<Material>(find.descendant( + of: findButton('XL'), + matching: find.byType(Material), + )); + + expect(segmentMButton.color, Colors.transparent); + expect(segmentXLButton.color, Colors.transparent); + }); +} + +Set<MaterialState> enabled = <MaterialState>{ }; diff --git a/examples/api/test/painting/linear_gradient.0_test.dart b/examples/api/test/painting/linear_gradient.0_test.dart index a3ced8c877aba..12f0ed011fcb6 100644 --- a/examples/api/test/painting/linear_gradient.0_test.dart +++ b/examples/api/test/painting/linear_gradient.0_test.dart @@ -20,8 +20,9 @@ void main() { testWidgets('gradient matches golden', (WidgetTester tester) async { await tester.pumpWidget( - const MaterialApp( - home: SizedBox( + MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const SizedBox( width: 800, height: 600, child: RepaintBoundary( diff --git a/examples/api/test/widgets/app_lifecycle_listener/app_lifecycle_listener.0_test.dart b/examples/api/test/widgets/app_lifecycle_listener/app_lifecycle_listener.0_test.dart new file mode 100644 index 0000000000000..6c37d9e10ae3b --- /dev/null +++ b/examples/api/test/widgets/app_lifecycle_listener/app_lifecycle_listener.0_test.dart @@ -0,0 +1,17 @@ +// 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_api_samples/widgets/app_lifecycle_listener/app_lifecycle_listener.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('AppLifecycleListener example', (WidgetTester tester) async { + await tester.pumpWidget( + const example.AppLifecycleListenerExample(), + ); + + expect(find.textContaining('Current State:'), findsOneWidget); + expect(find.textContaining('State History:'), findsOneWidget); + }); +} diff --git a/examples/api/test/widgets/app_lifecycle_listener/app_lifecycle_listener.1_test.dart b/examples/api/test/widgets/app_lifecycle_listener/app_lifecycle_listener.1_test.dart new file mode 100644 index 0000000000000..3218f7348ba99 --- /dev/null +++ b/examples/api/test/widgets/app_lifecycle_listener/app_lifecycle_listener.1_test.dart @@ -0,0 +1,27 @@ +// 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_api_samples/widgets/app_lifecycle_listener/app_lifecycle_listener.1.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('AppLifecycleListener example', (WidgetTester tester) async { + await tester.pumpWidget( + const example.AppLifecycleListenerExample(), + ); + + expect(find.text('Do Not Allow Exit'), findsOneWidget); + expect(find.text('Allow Exit'), findsOneWidget); + expect(find.text('Quit'), findsOneWidget); + expect(find.textContaining('Exit Request:'), findsOneWidget); + await tester.tap(find.text('Quit')); + await tester.pump(); + // Responding to the the quit request happens in a Future that we don't have + // visibility for, so to avoid a flaky test with a delay, we just check to + // see if the request string prefix is still there, rather than the request + // response string. Testing it wasn't worth exposing a Completer in the + // example code. + expect(find.textContaining('Exit Request:'), findsOneWidget); + }); +} diff --git a/examples/api/test/widgets/binding/widget_binding_observer.0_test.dart b/examples/api/test/widgets/binding/widget_binding_observer.0_test.dart index 9637c830ff832..6da54447e5edd 100644 --- a/examples/api/test/widgets/binding/widget_binding_observer.0_test.dart +++ b/examples/api/test/widgets/binding/widget_binding_observer.0_test.dart @@ -33,14 +33,14 @@ void main() { await setAppLifeCycleState(AppLifecycleState.paused); await tester.pumpAndSettle(); - await setAppLifeCycleState(AppLifecycleState.resumed); - await tester.pumpAndSettle(); - expect(find.text('state is: AppLifecycleState.paused'), findsOneWidget); + // Can't look for paused text here because rendering is paused. - await setAppLifeCycleState(AppLifecycleState.detached); + await setAppLifeCycleState(AppLifecycleState.inactive); await tester.pumpAndSettle(); + expect(find.text('state is: AppLifecycleState.inactive'), findsNWidgets(2)); + await setAppLifeCycleState(AppLifecycleState.resumed); await tester.pumpAndSettle(); - expect(find.text('state is: AppLifecycleState.detached'), findsOneWidget); + expect(find.text('state is: AppLifecycleState.resumed'), findsNWidgets(2)); }); } diff --git a/examples/api/test/widgets/heroes/hero.1_test.dart b/examples/api/test/widgets/heroes/hero.1_test.dart index 18cd6011e7025..1fab344d82902 100644 --- a/examples/api/test/widgets/heroes/hero.1_test.dart +++ b/examples/api/test/widgets/heroes/hero.1_test.dart @@ -22,7 +22,7 @@ void main() { // Jump 25% into the transition (total length = 300ms) await tester.pump(const Duration(milliseconds: 75)); // 25% of 300ms heroSize = tester.getSize(find.byType(Container).first); - expect(heroSize.width.roundToDouble(), 171.0); + expect(heroSize.width.roundToDouble(), 170.0); expect(heroSize.height.roundToDouble(), 73.0); // Jump to 50% into the transition. @@ -61,7 +61,7 @@ void main() { // Jump to 75% into the transition. await tester.pump(const Duration(milliseconds: 75)); // 25% of 300ms heroSize = tester.getSize(find.byType(Container).first); - expect(heroSize.width.roundToDouble(), 171.0); + expect(heroSize.width.roundToDouble(), 170.0); expect(heroSize.height.roundToDouble(), 73.0); // Jump to 100% into the transition. diff --git a/examples/api/test/widgets/implicit_animations/animated_slide.0_test.dart b/examples/api/test/widgets/implicit_animations/animated_slide.0_test.dart index 13354bdc38820..10746def6f156 100644 --- a/examples/api/test/widgets/implicit_animations/animated_slide.0_test.dart +++ b/examples/api/test/widgets/implicit_animations/animated_slide.0_test.dart @@ -23,7 +23,7 @@ void main() { logoOffset = tester.getCenter(find.byType(FlutterLogo)); expect(logoOffset.dx.roundToDouble(), 376.0); - expect(logoOffset.dy.roundToDouble(), 140.0); + expect(logoOffset.dy.roundToDouble(), 137.0); // Test X axis slider. final Offset x = tester.getCenter(find.text('X')); @@ -32,6 +32,6 @@ void main() { logoOffset = tester.getCenter(find.byType(FlutterLogo)); expect(logoOffset.dx.roundToDouble(), 178.0); - expect(logoOffset.dy.roundToDouble(), 140.0); + expect(logoOffset.dy.roundToDouble(), 137.0); }); } diff --git a/examples/api/test/widgets/sliver/sliver_main_axis_group.0_test.dart b/examples/api/test/widgets/sliver/sliver_main_axis_group.0_test.dart new file mode 100644 index 0000000000000..563b54739d843 --- /dev/null +++ b/examples/api/test/widgets/sliver/sliver_main_axis_group.0_test.dart @@ -0,0 +1,30 @@ +// 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/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_api_samples/widgets/sliver/sliver_main_axis_group.0.dart' + as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('SliverMainAxisGroup example', (WidgetTester tester) async { + await tester.pumpWidget( + const example.SliverMainAxisGroupExampleApp(), + ); + + final RenderSliverMainAxisGroup renderSliverGroup = tester.renderObject(find.byType(SliverMainAxisGroup)); + expect(renderSliverGroup, isNotNull); + + final RenderSliverPersistentHeader renderAppBar = tester.renderObject<RenderSliverPersistentHeader>(find.byType(SliverAppBar)); + final RenderSliverList renderSliverList = tester.renderObject<RenderSliverList>(find.byType(SliverList)); + final RenderSliverToBoxAdapter renderSliverAdapter = tester.renderObject<RenderSliverToBoxAdapter>(find.byType(SliverToBoxAdapter)); + + // renderAppBar, renderSliverList, and renderSliverAdapter1 are part of the same sliver group. + expect(renderAppBar.geometry!.scrollExtent, equals(70.0)); + expect(renderSliverList.geometry!.scrollExtent, equals(100.0 * 5)); + expect(renderSliverAdapter.geometry!.scrollExtent, equals(100.0)); + expect(renderSliverGroup.geometry!.scrollExtent, equals(70.0 + 100.0 * 5 + 100.0)); + }); +} diff --git a/examples/api/windows/flutter/generated_plugin_registrant.cc b/examples/api/windows/flutter/generated_plugin_registrant.cc deleted file mode 100644 index 8b6d4680af388..0000000000000 --- a/examples/api/windows/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void RegisterPlugins(flutter::PluginRegistry* registry) { -} diff --git a/examples/api/windows/flutter/generated_plugin_registrant.h b/examples/api/windows/flutter/generated_plugin_registrant.h deleted file mode 100644 index dc139d85a9310..0000000000000 --- a/examples/api/windows/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include <flutter/plugin_registry.h> - -// Registers Flutter plugins. -void RegisterPlugins(flutter::PluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/examples/api/windows/flutter/generated_plugins.cmake b/examples/api/windows/flutter/generated_plugins.cmake deleted file mode 100644 index b93c4c30c1670..0000000000000 --- a/examples/api/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/examples/flutter_view/ios/Runner/Info.plist b/examples/flutter_view/ios/Runner/Info.plist index a74ef720affde..e8267951bd6ec 100644 --- a/examples/flutter_view/ios/Runner/Info.plist +++ b/examples/flutter_view/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/examples/flutter_view/pubspec.yaml b/examples/flutter_view/pubspec.yaml index cba74967d6032..e8d77492ba79b 100644 --- a/examples/flutter_view/pubspec.yaml +++ b/examples/flutter_view/pubspec.yaml @@ -10,14 +10,14 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: uses-material-design: true assets: - assets/flutter-mark-square-64.png -# PUBSPEC CHECKSUM: 3e9c +# PUBSPEC CHECKSUM: a9c0 diff --git a/examples/flutter_view/windows/flutter/generated_plugins.cmake b/examples/flutter_view/windows/flutter/generated_plugins.cmake deleted file mode 100644 index b93c4c30c1670..0000000000000 --- a/examples/flutter_view/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/examples/flutter_view/windows/runner/flutter_window.cpp b/examples/flutter_view/windows/runner/flutter_window.cpp index 7f66913a0293c..252aa267868b6 100644 --- a/examples/flutter_view/windows/runner/flutter_window.cpp +++ b/examples/flutter_view/windows/runner/flutter_window.cpp @@ -36,8 +36,8 @@ bool FlutterWindow::OnCreate() { }); // Flutter can complete the first frame before the "show window" callback is - // registered. Ensure a frame is pending to ensure the window is shown. - // This no-ops if the first frame hasn't completed yet. + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; diff --git a/examples/hello_world/ios/Runner/GeneratedPluginRegistrant.h b/examples/hello_world/ios/Runner/GeneratedPluginRegistrant.h deleted file mode 100644 index 7a890927193a6..0000000000000 --- a/examples/hello_world/ios/Runner/GeneratedPluginRegistrant.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GeneratedPluginRegistrant_h -#define GeneratedPluginRegistrant_h - -#import <Flutter/Flutter.h> - -NS_ASSUME_NONNULL_BEGIN - -@interface GeneratedPluginRegistrant : NSObject -+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry; -@end - -NS_ASSUME_NONNULL_END -#endif /* GeneratedPluginRegistrant_h */ diff --git a/examples/hello_world/ios/Runner/GeneratedPluginRegistrant.m b/examples/hello_world/ios/Runner/GeneratedPluginRegistrant.m deleted file mode 100644 index efe65ecccf693..0000000000000 --- a/examples/hello_world/ios/Runner/GeneratedPluginRegistrant.m +++ /dev/null @@ -1,14 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#import "GeneratedPluginRegistrant.h" - -@implementation GeneratedPluginRegistrant - -+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry { -} - -@end diff --git a/examples/hello_world/ios/Runner/Info.plist b/examples/hello_world/ios/Runner/Info.plist index f5dc66fc91061..b33e2ca14b2f5 100644 --- a/examples/hello_world/ios/Runner/Info.plist +++ b/examples/hello_world/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/examples/hello_world/linux/flutter/generated_plugin_registrant.cc b/examples/hello_world/linux/flutter/generated_plugin_registrant.cc deleted file mode 100644 index e71a16d23d058..0000000000000 --- a/examples/hello_world/linux/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void fl_register_plugins(FlPluginRegistry* registry) { -} diff --git a/examples/hello_world/linux/flutter/generated_plugin_registrant.h b/examples/hello_world/linux/flutter/generated_plugin_registrant.h deleted file mode 100644 index e0f0a47bc08f3..0000000000000 --- a/examples/hello_world/linux/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include <flutter_linux/flutter_linux.h> - -// Registers Flutter plugins. -void fl_register_plugins(FlPluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/examples/hello_world/linux/flutter/generated_plugins.cmake b/examples/hello_world/linux/flutter/generated_plugins.cmake deleted file mode 100644 index 2e1de87a7eb61..0000000000000 --- a/examples/hello_world/linux/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/examples/hello_world/pubspec.yaml b/examples/hello_world/pubspec.yaml index 71af85a3eb0f0..637943e4bf191 100644 --- a/examples/hello_world/pubspec.yaml +++ b/examples/hello_world/pubspec.yaml @@ -9,21 +9,21 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_driver: sdk: flutter flutter_test: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -33,12 +33,13 @@ dev_dependencies: fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -57,14 +58,14 @@ dev_dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 5030 +# PUBSPEC CHECKSUM: 968e diff --git a/examples/hello_world/windows/flutter/generated_plugin_registrant.cc b/examples/hello_world/windows/flutter/generated_plugin_registrant.cc deleted file mode 100644 index 8b6d4680af388..0000000000000 --- a/examples/hello_world/windows/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void RegisterPlugins(flutter::PluginRegistry* registry) { -} diff --git a/examples/hello_world/windows/flutter/generated_plugin_registrant.h b/examples/hello_world/windows/flutter/generated_plugin_registrant.h deleted file mode 100644 index dc139d85a9310..0000000000000 --- a/examples/hello_world/windows/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include <flutter/plugin_registry.h> - -// Registers Flutter plugins. -void RegisterPlugins(flutter::PluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/examples/hello_world/windows/flutter/generated_plugins.cmake b/examples/hello_world/windows/flutter/generated_plugins.cmake deleted file mode 100644 index b93c4c30c1670..0000000000000 --- a/examples/hello_world/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/examples/hello_world/windows/runner/flutter_window.cpp b/examples/hello_world/windows/runner/flutter_window.cpp index 7f66913a0293c..252aa267868b6 100644 --- a/examples/hello_world/windows/runner/flutter_window.cpp +++ b/examples/hello_world/windows/runner/flutter_window.cpp @@ -36,8 +36,8 @@ bool FlutterWindow::OnCreate() { }); // Flutter can complete the first frame before the "show window" callback is - // registered. Ensure a frame is pending to ensure the window is shown. - // This no-ops if the first frame hasn't completed yet. + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; diff --git a/examples/image_list/ios/Runner/Info.plist b/examples/image_list/ios/Runner/Info.plist index c9198ba663b78..2f1b82c8bada5 100644 --- a/examples/image_list/ios/Runner/Info.plist +++ b/examples/image_list/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/examples/image_list/pubspec.yaml b/examples/image_list/pubspec.yaml index 5b30e1e5e3339..ab1be6fd8ec80 100644 --- a/examples/image_list/pubspec.yaml +++ b/examples/image_list/pubspec.yaml @@ -16,10 +16,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -34,14 +34,14 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: @@ -54,4 +54,4 @@ flutter: assets: - images/coast.jpg -# PUBSPEC CHECKSUM: edc5 +# PUBSPEC CHECKSUM: f5e9 diff --git a/examples/layers/ios/Runner/Info.plist b/examples/layers/ios/Runner/Info.plist index 2990f68a2ec5f..04927c5cef2aa 100644 --- a/examples/layers/ios/Runner/Info.plist +++ b/examples/layers/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/examples/layers/macos/Runner/DebugProfile.entitlements b/examples/layers/macos/Runner/DebugProfile.entitlements index dddb8a30c851e..3ba6c1266f218 100644 --- a/examples/layers/macos/Runner/DebugProfile.entitlements +++ b/examples/layers/macos/Runner/DebugProfile.entitlements @@ -6,6 +6,8 @@ <true/> <key>com.apple.security.cs.allow-jit</key> <true/> + <key>com.apple.security.network.client</key> + <true/> <key>com.apple.security.network.server</key> <true/> </dict> diff --git a/examples/layers/pubspec.yaml b/examples/layers/pubspec.yaml index 50a41b3dd4e48..1e09ede979b4b 100644 --- a/examples/layers/pubspec.yaml +++ b/examples/layers/pubspec.yaml @@ -9,10 +9,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -22,18 +22,18 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: assets: - services/data.json uses-material-design: true -# PUBSPEC CHECKSUM: 8a1e +# PUBSPEC CHECKSUM: b642 diff --git a/examples/layers/raw/canvas.dart b/examples/layers/raw/canvas.dart index 2d178cf2bed78..8af0ca2fd47b2 100644 --- a/examples/layers/raw/canvas.dart +++ b/examples/layers/raw/canvas.dart @@ -96,7 +96,7 @@ ui.Picture paint(ui.Rect paintBounds) { // the rectangle and smaller circle. canvas.drawCircle(const ui.Offset(150.0, 300.0), radius, paint); - // When we're done issuing painting commands, we end the recording an receive + // When we're done issuing painting commands, we end the recording and receive // a Picture, which is an immutable record of the commands we've issued. You // can draw a Picture into another canvas or include it as part of a // composited scene. diff --git a/examples/layers/windows/runner/flutter_window.cpp b/examples/layers/windows/runner/flutter_window.cpp index 7f66913a0293c..252aa267868b6 100644 --- a/examples/layers/windows/runner/flutter_window.cpp +++ b/examples/layers/windows/runner/flutter_window.cpp @@ -36,8 +36,8 @@ bool FlutterWindow::OnCreate() { }); // Flutter can complete the first frame before the "show window" callback is - // registered. Ensure a frame is pending to ensure the window is shown. - // This no-ops if the first frame hasn't completed yet. + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; diff --git a/examples/platform_channel/ios/Runner/Info.plist b/examples/platform_channel/ios/Runner/Info.plist index ecc52c8487fdc..97a48d5f7b0c4 100644 --- a/examples/platform_channel/ios/Runner/Info.plist +++ b/examples/platform_channel/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/examples/platform_channel/linux/flutter/generated_plugin_registrant.cc b/examples/platform_channel/linux/flutter/generated_plugin_registrant.cc deleted file mode 100644 index e71a16d23d058..0000000000000 --- a/examples/platform_channel/linux/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void fl_register_plugins(FlPluginRegistry* registry) { -} diff --git a/examples/platform_channel/linux/flutter/generated_plugin_registrant.h b/examples/platform_channel/linux/flutter/generated_plugin_registrant.h deleted file mode 100644 index e0f0a47bc08f3..0000000000000 --- a/examples/platform_channel/linux/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include <flutter_linux/flutter_linux.h> - -// Registers Flutter plugins. -void fl_register_plugins(FlPluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/examples/platform_channel/linux/flutter/generated_plugins.cmake b/examples/platform_channel/linux/flutter/generated_plugins.cmake deleted file mode 100644 index 2e1de87a7eb61..0000000000000 --- a/examples/platform_channel/linux/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/examples/platform_channel/linux/my_application.cc b/examples/platform_channel/linux/my_application.cc index 776d5637657ac..9abcb1bbb4783 100644 --- a/examples/platform_channel/linux/my_application.cc +++ b/examples/platform_channel/linux/my_application.cc @@ -217,12 +217,13 @@ static void my_application_activate(GApplication* application) { G_CALLBACK(up_device_added_cb), self); g_signal_connect_swapped(self->up_client, "device-removed", G_CALLBACK(up_device_removed_cb), self); -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" -// TODO(cbracken): https://github.com/flutter/flutter/issues/127506 -// Migrate to up_client_get_devices2 once available. +#if UP_CHECK_VERSION(0, 99, 8) + // up_client_get_devices was deprecated and replaced with + // up_client_get_devices2 in libupower 0.99.8. + g_autoptr(GPtrArray) devices = up_client_get_devices2(self->up_client); +#else g_autoptr(GPtrArray) devices = up_client_get_devices(self->up_client); -#pragma clang diagnostic pop +#endif for (guint i = 0; i < devices->len; i++) { g_autoptr(UpDevice) device = static_cast<UpDevice*>(g_ptr_array_index(devices, i)); diff --git a/examples/platform_channel/pubspec.yaml b/examples/platform_channel/pubspec.yaml index c8be5a8361bf7..415db71426f59 100644 --- a/examples/platform_channel/pubspec.yaml +++ b/examples/platform_channel/pubspec.yaml @@ -9,21 +9,21 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter flutter_driver: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -33,12 +33,13 @@ dev_dependencies: fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -57,10 +58,10 @@ dev_dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -70,4 +71,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 5030 +# PUBSPEC CHECKSUM: 968e diff --git a/examples/platform_channel/windows/flutter/generated_plugin_registrant.cc b/examples/platform_channel/windows/flutter/generated_plugin_registrant.cc deleted file mode 100644 index 8b6d4680af388..0000000000000 --- a/examples/platform_channel/windows/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,11 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - - -void RegisterPlugins(flutter::PluginRegistry* registry) { -} diff --git a/examples/platform_channel/windows/flutter/generated_plugin_registrant.h b/examples/platform_channel/windows/flutter/generated_plugin_registrant.h deleted file mode 100644 index dc139d85a9310..0000000000000 --- a/examples/platform_channel/windows/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include <flutter/plugin_registry.h> - -// Registers Flutter plugins. -void RegisterPlugins(flutter::PluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/examples/platform_channel/windows/flutter/generated_plugins.cmake b/examples/platform_channel/windows/flutter/generated_plugins.cmake deleted file mode 100644 index b93c4c30c1670..0000000000000 --- a/examples/platform_channel/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/examples/platform_channel/windows/runner/flutter_window.cpp b/examples/platform_channel/windows/runner/flutter_window.cpp index 21a907935b009..97c041167a9c3 100644 --- a/examples/platform_channel/windows/runner/flutter_window.cpp +++ b/examples/platform_channel/windows/runner/flutter_window.cpp @@ -99,8 +99,8 @@ bool FlutterWindow::OnCreate() { }); // Flutter can complete the first frame before the "show window" callback is - // registered. Ensure a frame is pending to ensure the window is shown. - // This no-ops if the first frame hasn't completed yet. + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; diff --git a/examples/platform_channel_swift/ios/Runner/Info.plist b/examples/platform_channel_swift/ios/Runner/Info.plist index ecc52c8487fdc..97a48d5f7b0c4 100644 --- a/examples/platform_channel_swift/ios/Runner/Info.plist +++ b/examples/platform_channel_swift/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/examples/platform_channel_swift/pubspec.yaml b/examples/platform_channel_swift/pubspec.yaml index 78441e7c3c157..728d5ecaa7286 100644 --- a/examples/platform_channel_swift/pubspec.yaml +++ b/examples/platform_channel_swift/pubspec.yaml @@ -9,21 +9,21 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: sdk: flutter flutter_driver: sdk: flutter - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -33,12 +33,13 @@ dev_dependencies: fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -57,10 +58,10 @@ dev_dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -70,4 +71,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 5030 +# PUBSPEC CHECKSUM: 968e diff --git a/examples/platform_view/ios/Runner/Info.plist b/examples/platform_view/ios/Runner/Info.plist index b67884258a873..ca51ebe361e86 100644 --- a/examples/platform_view/ios/Runner/Info.plist +++ b/examples/platform_view/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/examples/platform_view/pubspec.yaml b/examples/platform_view/pubspec.yaml index d51f59ac4661e..6d1111bc69050 100644 --- a/examples/platform_view/pubspec.yaml +++ b/examples/platform_view/pubspec.yaml @@ -9,10 +9,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: @@ -20,4 +20,4 @@ flutter: assets: - assets/flutter-mark-square-64.png -# PUBSPEC CHECKSUM: 3e9c +# PUBSPEC CHECKSUM: a9c0 diff --git a/examples/platform_view/windows/flutter/generated_plugins.cmake b/examples/platform_view/windows/flutter/generated_plugins.cmake deleted file mode 100644 index b93c4c30c1670..0000000000000 --- a/examples/platform_view/windows/flutter/generated_plugins.cmake +++ /dev/null @@ -1,23 +0,0 @@ -# -# Generated file, do not edit. -# - -list(APPEND FLUTTER_PLUGIN_LIST -) - -list(APPEND FLUTTER_FFI_PLUGIN_LIST -) - -set(PLUGIN_BUNDLED_LIBRARIES) - -foreach(plugin ${FLUTTER_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) - target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) - list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) -endforeach(plugin) - -foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) - add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) - list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) -endforeach(ffi_plugin) diff --git a/examples/splash/pubspec.yaml b/examples/splash/pubspec.yaml index 4d5ada71a928c..478640f70f7b5 100644 --- a/examples/splash/pubspec.yaml +++ b/examples/splash/pubspec.yaml @@ -9,10 +9,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -22,13 +22,13 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 8a1e +# PUBSPEC CHECKSUM: b642 diff --git a/examples/texture/README.md b/examples/texture/README.md new file mode 100644 index 0000000000000..8a46fe3023ee0 --- /dev/null +++ b/examples/texture/README.md @@ -0,0 +1,3 @@ +# Flutter Texture + +An example to show how to use custom Flutter textures. diff --git a/examples/texture/lib/main.dart b/examples/texture/lib/main.dart new file mode 100644 index 0000000000000..2f0cc7eed357f --- /dev/null +++ b/examples/texture/lib/main.dart @@ -0,0 +1,94 @@ +// 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/material.dart'; +import 'package:flutter/services.dart'; + +class TexturePage extends StatefulWidget { + const TexturePage({super.key}); + + @override + State<TexturePage> createState() => _TexturePageState(); +} + +class _TexturePageState extends State<TexturePage> { + static const int textureWidth = 300; + static const int textureHeight = 300; + static const MethodChannel channel = + MethodChannel('samples.flutter.io/texture'); + final Future<int?> textureId = + channel.invokeMethod('create', <int>[textureWidth, textureHeight]); + + // Set the color of the texture. + Future<void> setColor(int r, int g, int b) async { + await channel.invokeMethod('setColor', <int>[r, g, b]); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: const Text('Texture Example'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: <Widget>[ + FutureBuilder<int?>( + future: textureId, + builder: (BuildContext context, AsyncSnapshot<int?> snapshot) { + if (snapshot.hasData) { + if (snapshot.data != null) { + return SizedBox( + width: textureWidth.toDouble(), + height: textureHeight.toDouble(), + child: Texture(textureId: snapshot.data!), + ); + } else { + return const Text('Error creating texture'); + } + } else { + return const Text('Creating texture...'); + } + }, + ), + const SizedBox(height: 10), + OutlinedButton( + child: const Text('Flutter Navy'), + onPressed: () => setColor(0x04, 0x2b, 0x59)), + const SizedBox(height: 10), + OutlinedButton( + child: const Text('Flutter Blue'), + onPressed: () => setColor(0x05, 0x53, 0xb1)), + const SizedBox(height: 10), + OutlinedButton( + child: const Text('Flutter Sky'), + onPressed: () => setColor(0x02, 0x7d, 0xfd)), + const SizedBox(height: 10), + OutlinedButton( + child: const Text('Red'), + onPressed: () => setColor(0xf2, 0x5d, 0x50)), + const SizedBox(height: 10), + OutlinedButton( + child: const Text('Yellow'), + onPressed: () => setColor(0xff, 0xf2, 0x75)), + const SizedBox(height: 10), + OutlinedButton( + child: const Text('Purple'), + onPressed: () => setColor(0x62, 0x00, 0xee)), + const SizedBox(height: 10), + OutlinedButton( + child: const Text('Green'), + onPressed: () => setColor(0x1c, 0xda, 0xc5)), + ], + ), + ), + ); + } +} + +void main() { + runApp(const MaterialApp(home: TexturePage())); +} diff --git a/examples/texture/linux/CMakeLists.txt b/examples/texture/linux/CMakeLists.txt new file mode 100644 index 0000000000000..af26da2160e32 --- /dev/null +++ b/examples/texture/linux/CMakeLists.txt @@ -0,0 +1,141 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 17) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "texture") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "io.flutter.examples.texture") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "my_texture.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/examples/texture/linux/flutter/CMakeLists.txt b/examples/texture/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000000000..d5bd01648a96d --- /dev/null +++ b/examples/texture/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/examples/texture/linux/main.cc b/examples/texture/linux/main.cc new file mode 100644 index 0000000000000..281a29e16b599 --- /dev/null +++ b/examples/texture/linux/main.cc @@ -0,0 +1,10 @@ +// 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. + +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/examples/texture/linux/my_application.cc b/examples/texture/linux/my_application.cc new file mode 100644 index 0000000000000..f46cf9ca47154 --- /dev/null +++ b/examples/texture/linux/my_application.cc @@ -0,0 +1,172 @@ +// 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. + +#include "my_application.h" + +#include <flutter_linux/flutter_linux.h> + +#include "my_texture.h" +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + + // Channel to receive texture requests from Flutter. + FlMethodChannel* texture_channel; + + // Texture we've created. + MyTexture* texture; + + FlView* view; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Handle request to create the texture. +static FlMethodResponse* handle_create(MyApplication* self, + FlMethodCall* method_call) { + if (self->texture != nullptr) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + "Error", "texture already created", nullptr)); + } + + FlValue* args = fl_method_call_get_args(method_call); + if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST || + fl_value_get_length(args) != 2) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + "Invalid args", "Invalid create args", nullptr)); + } + FlValue* width_value = fl_value_get_list_value(args, 0); + FlValue* height_value = fl_value_get_list_value(args, 1); + if (fl_value_get_type(width_value) != FL_VALUE_TYPE_INT || + fl_value_get_type(height_value) != FL_VALUE_TYPE_INT) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + "Invalid args", "Invalid create args", nullptr)); + } + + FlEngine* engine = fl_view_get_engine(self->view); + FlTextureRegistrar* texture_registrar = + fl_engine_get_texture_registrar(engine); + + self->texture = + my_texture_new(fl_value_get_int(width_value), + fl_value_get_int(height_value), 0x05, 0x53, 0xb1); + if (!fl_texture_registrar_register_texture(texture_registrar, + FL_TEXTURE(self->texture))) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + "Error", "Failed to register texture", nullptr)); + } + + // Return the texture ID to Flutter so it can use this texture. + g_autoptr(FlValue) id = + fl_value_new_int(fl_texture_get_id(FL_TEXTURE(self->texture))); + return FL_METHOD_RESPONSE(fl_method_success_response_new(id)); +} + +// Handle request to set the texture color. +static FlMethodResponse* handle_set_color(MyApplication* self, + FlMethodCall* method_call) { + FlValue* args = fl_method_call_get_args(method_call); + if (fl_value_get_type(args) != FL_VALUE_TYPE_LIST || + fl_value_get_length(args) != 3) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + "Invalid args", "Invalid setColor args", nullptr)); + } + FlValue* r_value = fl_value_get_list_value(args, 0); + FlValue* g_value = fl_value_get_list_value(args, 1); + FlValue* b_value = fl_value_get_list_value(args, 2); + if (fl_value_get_type(r_value) != FL_VALUE_TYPE_INT || + fl_value_get_type(g_value) != FL_VALUE_TYPE_INT || + fl_value_get_type(b_value) != FL_VALUE_TYPE_INT) { + return FL_METHOD_RESPONSE(fl_method_error_response_new( + "Invalid args", "Invalid setColor args", nullptr)); + } + + FlEngine* engine = fl_view_get_engine(self->view); + FlTextureRegistrar* texture_registrar = + fl_engine_get_texture_registrar(engine); + + // Redraw in requested color. + my_texture_set_color(self->texture, fl_value_get_int(r_value), + fl_value_get_int(g_value), fl_value_get_int(b_value)); + + // Notify Flutter the texture has changed. + fl_texture_registrar_mark_texture_frame_available(texture_registrar, + FL_TEXTURE(self->texture)); + + return FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); +} + +// Handle texture requests from Flutter. +static void texture_channel_method_cb(FlMethodChannel* channel, + FlMethodCall* method_call, + gpointer user_data) { + MyApplication* self = MY_APPLICATION(user_data); + + const char* name = fl_method_call_get_name(method_call); + if (g_str_equal(name, "create")) { + g_autoptr(FlMethodResponse) response = handle_create(self, method_call); + fl_method_call_respond(method_call, response, NULL); + } else if (g_str_equal(name, "setColor")) { + g_autoptr(FlMethodResponse) response = handle_set_color(self, method_call); + fl_method_call_respond(method_call, response, NULL); + } else { + fl_method_call_respond_not_implemented(method_call, NULL); + } +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + + g_clear_object(&self->texture_channel); + g_clear_object(&self->texture); + + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + self->view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(self->view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(self->view)); + + // Create channel to handle texture requests from Flutter. + FlEngine* engine = fl_view_get_engine(self->view); + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + self->texture_channel = fl_method_channel_new( + fl_engine_get_binary_messenger(engine), "samples.flutter.io/texture", + FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler( + self->texture_channel, texture_channel_method_cb, self, nullptr); + + fl_register_plugins(FL_PLUGIN_REGISTRY(self->view)); + + gtk_widget_grab_focus(GTK_WIDGET(self->view)); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; + G_APPLICATION_CLASS(klass)->activate = my_application_activate; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); +} diff --git a/examples/texture/linux/my_application.h b/examples/texture/linux/my_application.h new file mode 100644 index 0000000000000..8c66ec485434d --- /dev/null +++ b/examples/texture/linux/my_application.h @@ -0,0 +1,22 @@ +// 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. + +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include <gtk/gtk.h> + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/examples/texture/linux/my_texture.cc b/examples/texture/linux/my_texture.cc new file mode 100644 index 0000000000000..5690ad000455e --- /dev/null +++ b/examples/texture/linux/my_texture.cc @@ -0,0 +1,72 @@ +// 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. + +#include "my_texture.h" + +// An object that generates a texture for Flutter. +struct _MyTexture { + FlPixelBufferTexture parent_instance; + + // Dimensions of texture. + uint32_t width; + uint32_t height; + + // Buffer used to store texture. + uint8_t* buffer; +}; + +G_DEFINE_TYPE(MyTexture, my_texture, fl_pixel_buffer_texture_get_type()) + +// Implements GObject::dispose. +static void my_texture_dispose(GObject* object) { + MyTexture* self = MY_TEXTURE(object); + + free(self->buffer); + + G_OBJECT_CLASS(my_texture_parent_class)->dispose(object); +} + +// Implements FlPixelBufferTexture::copy_pixels. +static gboolean my_texture_copy_pixels(FlPixelBufferTexture* texture, + const uint8_t** out_buffer, + uint32_t* width, uint32_t* height, + GError** error) { + MyTexture* self = MY_TEXTURE(texture); + *out_buffer = self->buffer; + *width = self->width; + *height = self->height; + return TRUE; +} + +static void my_texture_class_init(MyTextureClass* klass) { + G_OBJECT_CLASS(klass)->dispose = my_texture_dispose; + FL_PIXEL_BUFFER_TEXTURE_CLASS(klass)->copy_pixels = my_texture_copy_pixels; +} + +static void my_texture_init(MyTexture* self) {} + +MyTexture* my_texture_new(uint32_t width, uint32_t height, uint8_t r, uint8_t g, + uint8_t b) { + MyTexture* self = MY_TEXTURE(g_object_new(my_texture_get_type(), nullptr)); + self->width = width; + self->height = height; + self->buffer = static_cast<uint8_t*>(malloc(self->width * self->height * 4)); + my_texture_set_color(self, r, g, b); + return self; +} + +// Draws the texture with the requested color. +void my_texture_set_color(MyTexture* self, uint8_t r, uint8_t g, uint8_t b) { + g_return_if_fail(MY_IS_TEXTURE(self)); + + for (size_t y = 0; y < self->height; y++) { + for (size_t x = 0; x < self->width; x++) { + uint8_t* pixel = self->buffer + (y * self->width * 4) + (x * 4); + pixel[0] = r; + pixel[1] = g; + pixel[2] = b; + pixel[3] = 255; + } + } +} diff --git a/examples/texture/linux/my_texture.h b/examples/texture/linux/my_texture.h new file mode 100644 index 0000000000000..2b8cf82ab6577 --- /dev/null +++ b/examples/texture/linux/my_texture.h @@ -0,0 +1,39 @@ +// 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. + +#ifndef FLUTTER_MY_TEXTURE_H_ +#define FLUTTER_MY_TEXTURE_H_ + +#include <flutter_linux/flutter_linux.h> + +G_DECLARE_FINAL_TYPE(MyTexture, my_texture, MY, TEXTURE, FlPixelBufferTexture) + +/** + * my_texture_new: + * @self: a #MyTexture. + * @width: width of texture in pixels. + * @height: height of texture in pixels. + * @r: red value for texture color. + * @g: green value for texture color. + * @b: blue value for texture color. + * + * Creates a new texture containing a single color for Flutter to render with. + * + * Returns: a new #MyTexture. + */ +MyTexture* my_texture_new(uint32_t width, uint32_t height, uint8_t r, uint8_t g, + uint8_t b); + +/** + * my_texture_new: + * @self: a #MyTexture. + * @r: red value for texture color. + * @g: green value for texture color. + * @b: blue value for texture color. + * + * Sets the color the texture contains. + */ +void my_texture_set_color(MyTexture* self, uint8_t r, uint8_t g, uint8_t b); + +#endif // FLUTTER_MY_TEXTURE_H_ diff --git a/examples/texture/pubspec.yaml b/examples/texture/pubspec.yaml new file mode 100644 index 0000000000000..20cbb697abf18 --- /dev/null +++ b/examples/texture/pubspec.yaml @@ -0,0 +1,67 @@ +name: texture + +environment: + sdk: '>=3.0.0-0 <4.0.0' + +dependencies: + flutter: + sdk: flutter + + characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +dev_dependencies: + flutter_test: + sdk: flutter + test: 1.24.3 + + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pool: 1.5.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + pub_semver: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf: 1.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_packages_handler: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_static: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +# PUBSPEC CHECKSUM: 3549 diff --git a/examples/texture/test/texture_test.dart b/examples/texture/test/texture_test.dart new file mode 100644 index 0000000000000..bd8faa994d984 --- /dev/null +++ b/examples/texture/test/texture_test.dart @@ -0,0 +1,16 @@ +// 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_test/flutter_test.dart'; +import 'package:texture/main.dart' as texture; + +void main() { + testWidgets('Texture smoke test', (WidgetTester tester) async { + texture + .main(); // builds the app and schedules a frame but doesn't trigger one + await tester.pump(); // triggers a frame + + expect(find.text('Fluter Blue'), findsOneWidget); + }); +} diff --git a/packages/flutter/lib/fix_data/fix_services.yaml b/packages/flutter/lib/fix_data/fix_services.yaml index b409e6c7564ae..35c8a238d191f 100644 --- a/packages/flutter/lib/fix_data/fix_services.yaml +++ b/packages/flutter/lib/fix_data/fix_services.yaml @@ -18,6 +18,20 @@ # * Fixes in this file are from the Services library. * version: 1 transforms: + # Changes made in https://github.com/flutter/flutter/pull/122446 + - title: "Migrate to empty 'text' string" + date: 2023-06-26 + element: + uris: [ 'services.dart' ] + constructor: '' + inClass: 'ClipboardData' + changes: + - kind: 'changeParameterType' + name: 'text' + nullability: 'non_null' + argumentValue: + expression: "''" + # Changes made in https://github.com/flutter/flutter/pull/60320 - title: "Migrate to 'viewId'" date: 2020-07-05 diff --git a/packages/flutter/lib/foundation.dart b/packages/flutter/lib/foundation.dart index dff7553436b3f..43ac8133870bf 100644 --- a/packages/flutter/lib/foundation.dart +++ b/packages/flutter/lib/foundation.dart @@ -46,4 +46,5 @@ export 'src/foundation/serialization.dart'; export 'src/foundation/service_extensions.dart'; export 'src/foundation/stack_frame.dart'; export 'src/foundation/synchronous_future.dart'; +export 'src/foundation/timeline.dart'; export 'src/foundation/unicode.dart'; diff --git a/packages/flutter/lib/rendering.dart b/packages/flutter/lib/rendering.dart index 4b47bc196d5d6..20705ce17cca7 100644 --- a/packages/flutter/lib/rendering.dart +++ b/packages/flutter/lib/rendering.dart @@ -37,6 +37,7 @@ export 'src/rendering/custom_layout.dart'; export 'src/rendering/custom_paint.dart'; export 'src/rendering/debug.dart'; export 'src/rendering/debug_overflow_indicator.dart'; +export 'src/rendering/decorated_sliver.dart'; export 'src/rendering/editable.dart'; export 'src/rendering/error.dart'; export 'src/rendering/flex.dart'; diff --git a/packages/flutter/lib/services.dart b/packages/flutter/lib/services.dart index 9bca45367455d..9cfde5e909806 100644 --- a/packages/flutter/lib/services.dart +++ b/packages/flutter/lib/services.dart @@ -25,6 +25,7 @@ export 'src/services/hardware_keyboard.dart'; export 'src/services/keyboard_inserted_content.dart'; export 'src/services/keyboard_key.g.dart'; export 'src/services/keyboard_maps.g.dart'; +export 'src/services/live_text.dart'; export 'src/services/message_codec.dart'; export 'src/services/message_codecs.dart'; export 'src/services/mouse_cursor.dart'; diff --git a/packages/flutter/lib/src/cupertino/adaptive_text_selection_toolbar.dart b/packages/flutter/lib/src/cupertino/adaptive_text_selection_toolbar.dart index 19302c2feafd1..33f5e502eabac 100644 --- a/packages/flutter/lib/src/cupertino/adaptive_text_selection_toolbar.dart +++ b/packages/flutter/lib/src/cupertino/adaptive_text_selection_toolbar.dart @@ -94,6 +94,7 @@ class CupertinoAdaptiveTextSelectionToolbar extends StatelessWidget { required VoidCallback? onCut, required VoidCallback? onPaste, required VoidCallback? onSelectAll, + required VoidCallback? onLiveTextInput, required this.anchors, }) : children = null, buttonItems = EditableText.getEditableButtonItems( @@ -102,6 +103,7 @@ class CupertinoAdaptiveTextSelectionToolbar extends StatelessWidget { onCut: onCut, onPaste: onPaste, onSelectAll: onSelectAll, + onLiveTextInput: onLiveTextInput ); /// Create an instance of [CupertinoAdaptiveTextSelectionToolbar] with the diff --git a/packages/flutter/lib/src/cupertino/context_menu.dart b/packages/flutter/lib/src/cupertino/context_menu.dart index 98fae480f354c..f0d610c459b2b 100644 --- a/packages/flutter/lib/src/cupertino/context_menu.dart +++ b/packages/flutter/lib/src/cupertino/context_menu.dart @@ -1448,7 +1448,7 @@ class _ContextMenuSheet extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.stretch, children: <Widget>[ actions.first, - for (Widget action in actions.skip(1)) + for (final Widget action in actions.skip(1)) DecoratedBox( decoration: BoxDecoration( border: Border( diff --git a/packages/flutter/lib/src/cupertino/dialog.dart b/packages/flutter/lib/src/cupertino/dialog.dart index 1f9e0e9b46b74..0cb4cf8adc105 100644 --- a/packages/flutter/lib/src/cupertino/dialog.dart +++ b/packages/flutter/lib/src/cupertino/dialog.dart @@ -1556,7 +1556,7 @@ class _ActionButtonParentDataWidget parentData.isPressed = isPressed; // Force a repaint. - final AbstractNode? targetParent = renderObject.parent; + final RenderObject? targetParent = renderObject.parent; if (targetParent is RenderObject) { targetParent.markNeedsPaint(); } diff --git a/packages/flutter/lib/src/cupertino/icons.dart b/packages/flutter/lib/src/cupertino/icons.dart index 9d9eda6210ba1..b91746efab868 100644 --- a/packages/flutter/lib/src/cupertino/icons.dart +++ b/packages/flutter/lib/src/cupertino/icons.dart @@ -59,6 +59,7 @@ import 'package:flutter/widgets.dart'; /// See also: /// /// * [Icon], used to show these icons. +/// {@hideConstantImplementations} @staticIconProvider abstract final class CupertinoIcons { /// The icon font used for Cupertino icons. diff --git a/packages/flutter/lib/src/cupertino/scrollbar.dart b/packages/flutter/lib/src/cupertino/scrollbar.dart index 033b7481845ea..b3423554319e9 100644 --- a/packages/flutter/lib/src/cupertino/scrollbar.dart +++ b/packages/flutter/lib/src/cupertino/scrollbar.dart @@ -53,8 +53,7 @@ const double _kScrollbarCrossAxisMargin = 3.0; /// {@tool dartpad} /// When [thumbVisibility] is true, the scrollbar thumb will remain visible without the /// fade animation. This requires that a [ScrollController] is provided to controller, -/// or that the [PrimaryScrollController] is available. [isAlwaysShown] is -/// deprecated in favor of `thumbVisibility`. +/// or that the [PrimaryScrollController] is available. /// /// ** See code in examples/api/lib/cupertino/scrollbar/cupertino_scrollbar.1.dart ** /// {@end-tool} @@ -82,20 +81,10 @@ class CupertinoScrollbar extends RawScrollbar { this.radiusWhileDragging = defaultRadiusWhileDragging, ScrollNotificationPredicate? notificationPredicate, super.scrollbarOrientation, - @Deprecated( - 'Use thumbVisibility instead. ' - 'This feature was deprecated after v2.9.0-1.0.pre.', - ) - bool? isAlwaysShown, }) : assert(thickness < double.infinity), assert(thicknessWhileDragging < double.infinity), - assert( - isAlwaysShown == null || thumbVisibility == null, - 'Scrollbar thumb appearance should only be controlled with thumbVisibility, ' - 'isAlwaysShown is deprecated.' - ), super( - thumbVisibility: isAlwaysShown ?? thumbVisibility ?? false, + thumbVisibility: thumbVisibility ?? false, fadeDuration: _kScrollbarFadeDuration, timeToFade: _kScrollbarTimeToFade, pressDuration: const Duration(milliseconds: 100), diff --git a/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart b/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart index 9ff7dfc31b1c2..e4703a60f1d94 100644 --- a/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart +++ b/packages/flutter/lib/src/cupertino/text_selection_toolbar.dart @@ -15,7 +15,7 @@ import 'theme.dart'; // Values extracted from https://developer.apple.com/design/resources/. // The height of the toolbar, including the arrow. -const double _kToolbarHeight = 43.0; +const double _kToolbarHeight = 45.0; // Vertical distance between the tip of the arrow and the line of text the arrow // is pointing to. The value used here is eyeballed. const double _kToolbarContentDistance = 8.0; @@ -28,15 +28,29 @@ const double _kArrowScreenPadding = 26.0; // Values extracted from https://developer.apple.com/design/resources/. const Radius _kToolbarBorderRadius = Radius.circular(8); +// Color was measured from a screenshot of iOS 16.0.2 +// TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/41507. +const CupertinoDynamicColor _kToolbarBackgroundColor = CupertinoDynamicColor.withBrightness( + color: Color(0xFFF6F6F6), + darkColor: Color(0xFF222222), +); + +const double _kToolbarChevronSize = 10; +const double _kToolbarChevronThickness = 2; + +// Color was measured from a screenshot of iOS 16.0.2. const CupertinoDynamicColor _kToolbarDividerColor = CupertinoDynamicColor.withBrightness( - // This value was extracted from a screenshot of iOS 16.0.3, as light mode - // didn't appear in the Apple design resources assets linked below. - color: Color(0xFFB6B6B6), - // Color extracted from https://developer.apple.com/design/resources/. - // TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/41507. - darkColor: Color(0xFF808080), + color: Color(0xFFD6D6D6), + darkColor: Color(0xFF424242), +); + +const CupertinoDynamicColor _kToolbarTextColor = CupertinoDynamicColor.withBrightness( + color: CupertinoColors.black, + darkColor: CupertinoColors.white, ); +const Duration _kToolbarTransitionDuration = Duration(milliseconds: 125); + /// The type for a Function that builds a toolbar's container with the given /// child. /// @@ -54,13 +68,6 @@ typedef CupertinoToolbarBuilder = Widget Function( Widget child, ); -class _CupertinoToolbarButtonDivider extends StatelessWidget { - @override - Widget build(BuildContext context) { - return SizedBox(width: 1.0 / MediaQuery.devicePixelRatioOf(context)); - } -} - /// An iOS-style text selection toolbar. /// /// Typically displays buttons for text manipulation, e.g. copying and pasting @@ -117,29 +124,14 @@ class CupertinoTextSelectionToolbar extends StatelessWidget { /// * [TextSelectionToolbar], which uses this same value as well. static const double kToolbarScreenPadding = 8.0; - // Add the visual vertical line spacer between children buttons. - static List<Widget> _addChildrenSpacers(List<Widget> children) { - final List<Widget> nextChildren = <Widget>[]; - for (int i = 0; i < children.length; i++) { - final Widget child = children[i]; - if (i != 0) { - nextChildren.add(_CupertinoToolbarButtonDivider()); - } - nextChildren.add(child); - } - return nextChildren; - } - // Builds a toolbar just like the default iOS toolbar, with the right color // background and a rounded cutout with an arrow. static Widget _defaultToolbarBuilder(BuildContext context, Offset anchor, bool isAbove, Widget child) { final Widget outputChild = _CupertinoTextSelectionToolbarShape( anchor: anchor, isAbove: isAbove, - child: DecoratedBox( - decoration: BoxDecoration( - color: _kToolbarDividerColor.resolveFrom(context), - ), + child: ColoredBox( + color: _kToolbarBackgroundColor.resolveFrom(context), child: child, ), ); @@ -209,7 +201,7 @@ class CupertinoTextSelectionToolbar extends StatelessWidget { anchor: fitsAbove ? anchorAboveAdjusted : anchorBelowAdjusted, isAbove: fitsAbove, toolbarBuilder: toolbarBuilder, - children: _addChildrenSpacers(children), + children: children, ), ), ); @@ -449,19 +441,43 @@ class _CupertinoTextSelectionToolbarContent extends StatefulWidget { class _CupertinoTextSelectionToolbarContentState extends State<_CupertinoTextSelectionToolbarContent> with TickerProviderStateMixin { // Controls the fading of the buttons within the menu during page transitions. late AnimationController _controller; - int _page = 0; int? _nextPage; + int _page = 0; + + final GlobalKey _toolbarItemsKey = GlobalKey(); + + void _onHorizontalDragEnd(DragEndDetails details) { + final double? velocity = details.primaryVelocity; + + if (velocity != null && velocity != 0) { + if (velocity > 0) { + _handlePreviousPage(); + } else { + _handleNextPage(); + } + } + } void _handleNextPage() { - _controller.reverse(); - _controller.addStatusListener(_statusListener); - _nextPage = _page + 1; + final RenderBox? renderToolbar = + _toolbarItemsKey.currentContext?.findRenderObject() as RenderBox?; + + if (renderToolbar is _RenderCupertinoTextSelectionToolbarItems && renderToolbar.hasNextPage) { + _controller.reverse(); + _controller.addStatusListener(_statusListener); + _nextPage = _page + 1; + } } void _handlePreviousPage() { - _controller.reverse(); - _controller.addStatusListener(_statusListener); - _nextPage = _page - 1; + final RenderBox? renderToolbar = + _toolbarItemsKey.currentContext?.findRenderObject() as RenderBox?; + + if (renderToolbar is _RenderCupertinoTextSelectionToolbarItems && renderToolbar.hasPreviousPage) { + _controller.reverse(); + _controller.addStatusListener(_statusListener); + _nextPage = _page - 1; + } } void _statusListener(AnimationStatus status) { @@ -484,7 +500,7 @@ class _CupertinoTextSelectionToolbarContentState extends State<_CupertinoTextSel value: 1.0, vsync: this, // This was eyeballed on a physical iOS device running iOS 13. - duration: const Duration(milliseconds: 150), + duration: _kToolbarTransitionDuration, ); } @@ -506,53 +522,137 @@ class _CupertinoTextSelectionToolbarContentState extends State<_CupertinoTextSel super.dispose(); } + Widget _createChevron({required bool isLeft}) { + final Color color = _kToolbarTextColor.resolveFrom(context); + + return IgnorePointer( + child: Center( + // If widthFactor is not set to 0, the button is given unbounded width. + widthFactor: 0, + child: CustomPaint( + painter: isLeft + ? _LeftCupertinoChevronPainter(color: color) + : _RightCupertinoChevronPainter(color: color), + size: const Size.square(_kToolbarChevronSize), + ), + ), + ); + } + @override Widget build(BuildContext context) { return widget.toolbarBuilder(context, widget.anchor, widget.isAbove, FadeTransition( opacity: _controller, - child: _CupertinoTextSelectionToolbarItems( - page: _page, - backButton: CupertinoTextSelectionToolbarButton.text( - onPressed: _handlePreviousPage, - text: '◀', - ), - dividerWidth: 1.0 / MediaQuery.devicePixelRatioOf(context), - nextButton: CupertinoTextSelectionToolbarButton.text( - onPressed: _handleNextPage, - text: '▶', - ), - nextButtonDisabled: const CupertinoTextSelectionToolbarButton.text( - text: '▶', + child: AnimatedSize( + duration: _kToolbarTransitionDuration, + curve: Curves.decelerate, + child: GestureDetector( + onHorizontalDragEnd: _onHorizontalDragEnd, + child: _CupertinoTextSelectionToolbarItems( + key: _toolbarItemsKey, + page: _page, + backButton: CupertinoTextSelectionToolbarButton( + onPressed: _handlePreviousPage, + child: _createChevron(isLeft: true), + ), + dividerColor: _kToolbarDividerColor.resolveFrom(context), + dividerWidth: 1.0 / MediaQuery.devicePixelRatioOf(context), + nextButton: CupertinoTextSelectionToolbarButton( + onPressed: _handleNextPage, + child: _createChevron(isLeft: false), + ), + children: widget.children, + ), ), - children: widget.children, ), )); } } +// These classes help to test the chevrons. As _CupertinoChevronPainter must be +// private, it's possible to check the runtimeType of each chevron to know if +// they should be pointing left or right. +class _LeftCupertinoChevronPainter extends _CupertinoChevronPainter { + _LeftCupertinoChevronPainter({required super.color}) : super(isLeft: true); +} +class _RightCupertinoChevronPainter extends _CupertinoChevronPainter { + _RightCupertinoChevronPainter({required super.color}) : super(isLeft: false); +} +abstract class _CupertinoChevronPainter extends CustomPainter { + _CupertinoChevronPainter({ + required this.color, + required this.isLeft, + }); + + final Color color; + + /// If this is true the chevron will point left, else it will point right. + final bool isLeft; + + @override + void paint(Canvas canvas, Size size) { + assert(size.height == size.width, 'size must have the same height and width'); + + final double iconSize = size.height; + + // The chevron is half of a square rotated 45˚, so it needs a margin of 1/4 + // its size on each side to be centered horizontally. + // + // If pointing left, it means the left half of a square is being used and + // the offset is positive. If pointing right, the right half is being used + // and the offset is negative. + final Offset centerOffset = Offset( + iconSize / 4 * (isLeft ? 1 : -1), + 0, + ); + + final Offset firstPoint = Offset(iconSize / 2, 0) + centerOffset; + final Offset middlePoint = Offset(isLeft ? 0 : iconSize, iconSize / 2) + centerOffset; + final Offset lowerPoint = Offset(iconSize / 2, iconSize) + centerOffset; + + final Paint paint = Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = _kToolbarChevronThickness + ..strokeCap = StrokeCap.round + ..strokeJoin = StrokeJoin.round; + + // `drawLine` is used here because it's testable. When using `drawPath`, + // there's no way to test that the chevron points to the correct side. + canvas.drawLine(firstPoint, middlePoint, paint); + canvas.drawLine(middlePoint, lowerPoint, paint); + } + + @override + bool shouldRepaint(_CupertinoChevronPainter oldDelegate) => + oldDelegate.color != color || oldDelegate.isLeft != isLeft; +} + // The custom RenderObjectWidget that, together with // _RenderCupertinoTextSelectionToolbarItems and // _CupertinoTextSelectionToolbarItemsElement, paginates the menu items. class _CupertinoTextSelectionToolbarItems extends RenderObjectWidget { _CupertinoTextSelectionToolbarItems({ + super.key, required this.page, required this.children, required this.backButton, + required this.dividerColor, required this.dividerWidth, required this.nextButton, - required this.nextButtonDisabled, }) : assert(children.isNotEmpty); final Widget backButton; final List<Widget> children; + final Color dividerColor; final double dividerWidth; final Widget nextButton; - final Widget nextButtonDisabled; final int page; @override _RenderCupertinoTextSelectionToolbarItems createRenderObject(BuildContext context) { return _RenderCupertinoTextSelectionToolbarItems( + dividerColor: dividerColor, dividerWidth: dividerWidth, page: page, ); @@ -562,6 +662,7 @@ class _CupertinoTextSelectionToolbarItems extends RenderObjectWidget { void updateRenderObject(BuildContext context, _RenderCupertinoTextSelectionToolbarItems renderObject) { renderObject ..page = page + ..dividerColor = dividerColor ..dividerWidth = dividerWidth; } @@ -591,8 +692,6 @@ class _CupertinoTextSelectionToolbarItemsElement extends RenderObjectElement { renderObject.backButton = child; case _CupertinoTextSelectionToolbarItemsSlot.nextButton: renderObject.nextButton = child; - case _CupertinoTextSelectionToolbarItemsSlot.nextButtonDisabled: - renderObject.nextButtonDisabled = child; } } @@ -683,7 +782,6 @@ class _CupertinoTextSelectionToolbarItemsElement extends RenderObjectElement { final _CupertinoTextSelectionToolbarItems toolbarItems = widget as _CupertinoTextSelectionToolbarItems; _mountChild(toolbarItems.backButton, _CupertinoTextSelectionToolbarItemsSlot.backButton); _mountChild(toolbarItems.nextButton, _CupertinoTextSelectionToolbarItemsSlot.nextButton); - _mountChild(toolbarItems.nextButtonDisabled, _CupertinoTextSelectionToolbarItemsSlot.nextButtonDisabled); // Mount list children. _children = List<Element>.filled(toolbarItems.children.length, _NullElement.instance); @@ -718,7 +816,6 @@ class _CupertinoTextSelectionToolbarItemsElement extends RenderObjectElement { final _CupertinoTextSelectionToolbarItems toolbarItems = widget as _CupertinoTextSelectionToolbarItems; _mountChild(toolbarItems.backButton, _CupertinoTextSelectionToolbarItemsSlot.backButton); _mountChild(toolbarItems.nextButton, _CupertinoTextSelectionToolbarItemsSlot.nextButton); - _mountChild(toolbarItems.nextButtonDisabled, _CupertinoTextSelectionToolbarItemsSlot.nextButtonDisabled); // Update list children. _children = updateChildren(_children, toolbarItems.children, forgottenChildren: _forgottenChildren); @@ -729,14 +826,19 @@ class _CupertinoTextSelectionToolbarItemsElement extends RenderObjectElement { // The custom RenderBox that helps paginate the menu items. class _RenderCupertinoTextSelectionToolbarItems extends RenderBox with ContainerRenderObjectMixin<RenderBox, ToolbarItemsParentData>, RenderBoxContainerDefaultsMixin<RenderBox, ToolbarItemsParentData> { _RenderCupertinoTextSelectionToolbarItems({ + required Color dividerColor, required double dividerWidth, required int page, - }) : _dividerWidth = dividerWidth, + }) : _dividerColor = dividerColor, + _dividerWidth = dividerWidth, _page = page, super(); final Map<_CupertinoTextSelectionToolbarItemsSlot, RenderBox> slottedChildren = <_CupertinoTextSelectionToolbarItemsSlot, RenderBox>{}; + late bool hasNextPage; + late bool hasPreviousPage; + RenderBox? _updateChild(RenderBox? oldChild, RenderBox? newChild, _CupertinoTextSelectionToolbarItemsSlot slot) { if (oldChild != null) { dropChild(oldChild); @@ -750,7 +852,7 @@ class _RenderCupertinoTextSelectionToolbarItems extends RenderBox with Container } bool _isSlottedChild(RenderBox child) { - return child == _backButton || child == _nextButton || child == _nextButtonDisabled; + return child == _backButton || child == _nextButton; } int _page; @@ -763,6 +865,16 @@ class _RenderCupertinoTextSelectionToolbarItems extends RenderBox with Container markNeedsLayout(); } + Color _dividerColor; + Color get dividerColor => _dividerColor; + set dividerColor(Color value) { + if (value == _dividerColor) { + return; + } + _dividerColor = value; + markNeedsLayout(); + } + double _dividerWidth; double get dividerWidth => _dividerWidth; set dividerWidth(double value) { @@ -785,12 +897,6 @@ class _RenderCupertinoTextSelectionToolbarItems extends RenderBox with Container _nextButton = _updateChild(_nextButton, value, _CupertinoTextSelectionToolbarItemsSlot.nextButton); } - RenderBox? _nextButtonDisabled; - RenderBox? get nextButtonDisabled => _nextButtonDisabled; - set nextButtonDisabled(RenderBox? value) { - _nextButtonDisabled = _updateChild(_nextButtonDisabled, value, _CupertinoTextSelectionToolbarItemsSlot.nextButtonDisabled); - } - @override void performLayout() { if (firstChild == null) { @@ -801,7 +907,6 @@ class _RenderCupertinoTextSelectionToolbarItems extends RenderBox with Container // Layout slotted children. _backButton!.layout(constraints.loosen(), parentUsesSize: true); _nextButton!.layout(constraints.loosen(), parentUsesSize: true); - _nextButtonDisabled!.layout(constraints.loosen(), parentUsesSize: true); final double subsequentPageButtonsWidth = _backButton!.size.width + _nextButton!.size.width; @@ -828,7 +933,7 @@ class _RenderCupertinoTextSelectionToolbarItems extends RenderBox with Container // If this is the last child, it's ok to fit without a forward button. // Note childCount doesn't include slotted children which come before the list ones. paginationButtonsWidth = - i == childCount + 2 ? 0.0 : _nextButton!.size.width; + i == childCount + 1 ? 0.0 : _nextButton!.size.width; } else { paginationButtonsWidth = subsequentPageButtonsWidth; } @@ -881,17 +986,10 @@ class _RenderCupertinoTextSelectionToolbarItems extends RenderBox with Container if (currentPage > 0) { final ToolbarItemsParentData nextButtonParentData = _nextButton!.parentData! as ToolbarItemsParentData; - final ToolbarItemsParentData nextButtonDisabledParentData = - _nextButtonDisabled!.parentData! as ToolbarItemsParentData; final ToolbarItemsParentData backButtonParentData = _backButton!.parentData! as ToolbarItemsParentData; - // The forward button always shows if there is more than one page, even on - // the last page (it's just disabled). - if (page == currentPage) { - nextButtonDisabledParentData.offset = Offset(toolbarWidth, 0.0); - nextButtonDisabledParentData.shouldPaint = true; - toolbarWidth += nextButtonDisabled!.size.width; - } else { + // The forward button only shows when there's a page after this one. + if (page != currentPage) { nextButtonParentData.offset = Offset(toolbarWidth, 0.0); nextButtonParentData.shouldPaint = true; toolbarWidth += nextButton!.size.width; @@ -903,6 +1001,11 @@ class _RenderCupertinoTextSelectionToolbarItems extends RenderBox with Container // already been taken care of when laying out the children to // accommodate the back button. } + + // Update previous/next page values so that we can check in the horizontal + // drag gesture callback if it's possible to navigate. + hasNextPage = page != currentPage; + hasPreviousPage = page > 0; } else { // No divider for the next button when there's only one page. toolbarWidth -= dividerWidth; @@ -920,6 +1023,18 @@ class _RenderCupertinoTextSelectionToolbarItems extends RenderBox with Container if (childParentData.shouldPaint) { final Offset childOffset = childParentData.offset + offset; context.paintChild(child, childOffset); + + // backButton is a slotted child and is not in the children list, so its + // childParentData.nextSibling is null. So either when there's a + // nextSibling or when child is the backButton, draw a divider to the + // child's right. + if (childParentData.nextSibling != null || child == backButton) { + context.canvas.drawLine( + Offset(child.size.width, 0) + childOffset, + Offset(child.size.width, child.size.height) + childOffset, + Paint()..color = dividerColor, + ); + } } }); } @@ -977,9 +1092,6 @@ class _RenderCupertinoTextSelectionToolbarItems extends RenderBox with Container if (hitTestChild(nextButton, result, position: position)) { return true; } - if (hitTestChild(nextButtonDisabled, result, position: position)) { - return true; - } return false; } @@ -1023,9 +1135,6 @@ class _RenderCupertinoTextSelectionToolbarItems extends RenderBox with Container if (_nextButton != null) { visitor(_nextButton!); } - if (_nextButtonDisabled != null) { - visitor(_nextButtonDisabled!); - } // Visit the list children. super.visitChildren(visitor); } @@ -1051,8 +1160,6 @@ class _RenderCupertinoTextSelectionToolbarItems extends RenderBox with Container value.add(child.toDiagnosticsNode(name: 'back button')); } else if (child == nextButton) { value.add(child.toDiagnosticsNode(name: 'next button')); - } else if (child == nextButtonDisabled) { - value.add(child.toDiagnosticsNode(name: 'next button disabled')); // List children. } else { @@ -1068,7 +1175,6 @@ class _RenderCupertinoTextSelectionToolbarItems extends RenderBox with Container enum _CupertinoTextSelectionToolbarItemsSlot { backButton, nextButton, - nextButtonDisabled, } class _NullElement extends Element { diff --git a/packages/flutter/lib/src/cupertino/text_selection_toolbar_button.dart b/packages/flutter/lib/src/cupertino/text_selection_toolbar_button.dart index 2d547df0f52d1..f07d88e4b5ec6 100644 --- a/packages/flutter/lib/src/cupertino/text_selection_toolbar_button.dart +++ b/packages/flutter/lib/src/cupertino/text_selection_toolbar_button.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:math'; + import 'package:flutter/widgets.dart'; import 'button.dart'; @@ -11,30 +13,26 @@ import 'localizations.dart'; const TextStyle _kToolbarButtonFontStyle = TextStyle( inherit: false, - fontSize: 14.0, + fontSize: 15.0, letterSpacing: -0.15, fontWeight: FontWeight.w400, ); -// Colors extracted from https://developer.apple.com/design/resources/. -// TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/41507. -const CupertinoDynamicColor _kToolbarBackgroundColor = CupertinoDynamicColor.withBrightness( - // This value was extracted from a screenshot of iOS 16.0.3, as light mode - // didn't appear in the Apple design resources assets linked above. - color: Color(0xEBF7F7F7), - darkColor: Color(0xEB202020), -); - const CupertinoDynamicColor _kToolbarTextColor = CupertinoDynamicColor.withBrightness( color: CupertinoColors.black, darkColor: CupertinoColors.white, ); -// Eyeballed value. -const EdgeInsets _kToolbarButtonPadding = EdgeInsets.symmetric(vertical: 16.0, horizontal: 18.0); +const CupertinoDynamicColor _kToolbarPressedColor = CupertinoDynamicColor.withBrightness( + color: Color(0x10000000), + darkColor: Color(0x10FFFFFF), +); + +// Value measured from screenshot of iOS 16.0.2 +const EdgeInsets _kToolbarButtonPadding = EdgeInsets.symmetric(vertical: 18.0, horizontal: 16.0); /// A button in the style of the iOS text selection toolbar buttons. -class CupertinoTextSelectionToolbarButton extends StatelessWidget { +class CupertinoTextSelectionToolbarButton extends StatefulWidget { /// Create an instance of [CupertinoTextSelectionToolbarButton]. /// /// [child] cannot be null. @@ -107,32 +105,146 @@ class CupertinoTextSelectionToolbarButton extends StatelessWidget { return localizations.pasteButtonLabel; case ContextMenuButtonType.selectAll: return localizations.selectAllButtonLabel; + case ContextMenuButtonType.liveTextInput: case ContextMenuButtonType.delete: case ContextMenuButtonType.custom: return ''; } } + @override + State<StatefulWidget> createState() => _CupertinoTextSelectionToolbarButtonState(); +} + +class _CupertinoTextSelectionToolbarButtonState extends State<CupertinoTextSelectionToolbarButton> { + bool isPressed = false; + + void _onTapDown(TapDownDetails details) { + setState(() => isPressed = true); + } + + void _onTapUp(TapUpDetails details) { + setState(() => isPressed = false); + widget.onPressed?.call(); + } + + void _onTapCancel() { + setState(() => isPressed = false); + } + @override Widget build(BuildContext context) { - final Widget child = this.child ?? Text( - text ?? getButtonLabel(context, buttonItem!), - overflow: TextOverflow.ellipsis, - style: _kToolbarButtonFontStyle.copyWith( - color: onPressed != null - ? _kToolbarTextColor.resolveFrom(context) - : CupertinoColors.inactiveGray, - ), - ); - - return CupertinoButton( + final Widget content = _getContentWidget(context); + final Widget child = CupertinoButton( + color: isPressed + ? _kToolbarPressedColor.resolveFrom(context) + : const Color(0x00000000), borderRadius: null, - color: _kToolbarBackgroundColor, - disabledColor: _kToolbarBackgroundColor, - onPressed: onPressed, + disabledColor: const Color(0x00000000), + // This CupertinoButton does not actually handle the onPressed callback, + // this is only here to correctly enable/disable the button (see + // GestureDetector comment below). + onPressed: widget.onPressed, padding: _kToolbarButtonPadding, - pressedOpacity: onPressed == null ? 1.0 : 0.7, - child: child, + // There's no foreground fade on iOS toolbar anymore, just the background + // is darkened. + pressedOpacity: 1.0, + child: content, + ); + + if (widget.onPressed != null) { + // As it's needed to change the CupertinoButton's backgroundColor when + // pressed, not its opacity, this GestureDetector handles both the + // onPressed callback and the backgroundColor change. + return GestureDetector( + onTapDown: _onTapDown, + onTapUp: _onTapUp, + onTapCancel: _onTapCancel, + child: child, + ); + } else { + return child; + } + } + + Widget _getContentWidget(BuildContext context) { + if (widget.child != null) { + return widget.child!; + } + final Widget textWidget = Text( + widget.text ?? CupertinoTextSelectionToolbarButton.getButtonLabel(context, widget.buttonItem!), + overflow: TextOverflow.ellipsis, + style: _kToolbarButtonFontStyle.copyWith( + color: widget.onPressed != null + ? _kToolbarTextColor.resolveFrom(context) + : CupertinoColors.inactiveGray, + ), ); + if (widget.buttonItem == null) { + return textWidget; + } + switch (widget.buttonItem!.type) { + case ContextMenuButtonType.cut: + case ContextMenuButtonType.copy: + case ContextMenuButtonType.paste: + case ContextMenuButtonType.selectAll: + case ContextMenuButtonType.delete: + case ContextMenuButtonType.custom: + return textWidget; + case ContextMenuButtonType.liveTextInput: + return SizedBox( + width: 13.0, + height: 13.0, + child: CustomPaint( + painter: _LiveTextIconPainter(color: _kToolbarTextColor.resolveFrom(context)), + ), + ); + } + } +} + +class _LiveTextIconPainter extends CustomPainter { + _LiveTextIconPainter({required this.color}); + + final Color color; + + final Paint _painter = Paint() + ..strokeCap = StrokeCap.round + ..strokeJoin = StrokeJoin.round + ..strokeWidth = 1.0 + ..style = PaintingStyle.stroke; + + @override + void paint(Canvas canvas, Size size) { + _painter.color = color; + canvas.save(); + canvas.translate(size.width / 2.0, size.height / 2.0); + + final Offset origin = Offset(-size.width / 2.0, -size.height / 2.0); + // Path for the one corner. + final Path path = Path() + ..moveTo(origin.dx, origin.dy + 3.5) + ..lineTo(origin.dx, origin.dy + 1.0) + ..arcToPoint(Offset(origin.dx + 1.0, origin.dy), radius: const Radius.circular(1)) + ..lineTo(origin.dx + 3.5, origin.dy); + + // Rotate to draw corner four times. + final Matrix4 rotationMatrix = Matrix4.identity()..rotateZ(pi / 2.0); + for (int i = 0; i < 4; i += 1) { + canvas.drawPath(path, _painter); + canvas.transform(rotationMatrix.storage); + } + + // Draw three lines. + canvas.drawLine(const Offset(-3.0, -3.0), const Offset(3.0, -3.0), _painter); + canvas.drawLine(const Offset(-3.0, 0.0), const Offset(3.0, 0.0), _painter); + canvas.drawLine(const Offset(-3.0, 3.0), const Offset(1.0, 3.0), _painter); + + canvas.restore(); + } + + @override + bool shouldRepaint(covariant _LiveTextIconPainter oldDelegate) { + return oldDelegate.color != color; } } diff --git a/packages/flutter/lib/src/foundation/_capabilities_web.dart b/packages/flutter/lib/src/foundation/_capabilities_web.dart index 86deb78d0dd8f..a71c517d8ef3c 100644 --- a/packages/flutter/lib/src/foundation/_capabilities_web.dart +++ b/packages/flutter/lib/src/foundation/_capabilities_web.dart @@ -2,11 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// For now, we're hiding dart:js_interop's `@JS` to avoid a conflict with -// package:js' `@JS`. In the future, we should be able to remove package:js -// altogether and just import dart:js_interop. -import 'dart:js_interop' hide JS; -import 'package:js/js.dart'; +import 'dart:js_interop'; // This value is set by the engine. It is used to determine if the application is // using canvaskit. diff --git a/packages/flutter/lib/src/foundation/_platform_web.dart b/packages/flutter/lib/src/foundation/_platform_web.dart index 788cbf4c7f26d..babeee421a23f 100644 --- a/packages/flutter/lib/src/foundation/_platform_web.dart +++ b/packages/flutter/lib/src/foundation/_platform_web.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui' as ui; +import 'dart:ui_web' as ui_web; import '../services/dom.dart'; @@ -23,9 +23,7 @@ platform.TargetPlatform get defaultTargetPlatform { final platform.TargetPlatform? _testPlatform = () { platform.TargetPlatform? result; assert(() { - // This member is only available in the web's dart:ui implementation. - // ignore: undefined_prefixed_name - if (ui.debugEmulateFlutterTesterEnvironment as bool) { + if (ui_web.debugEmulateFlutterTesterEnvironment) { result = platform.TargetPlatform.android; } return true; diff --git a/packages/flutter/lib/src/foundation/_timeline_io.dart b/packages/flutter/lib/src/foundation/_timeline_io.dart new file mode 100644 index 0000000000000..8c4886b382c0a --- /dev/null +++ b/packages/flutter/lib/src/foundation/_timeline_io.dart @@ -0,0 +1,11 @@ +// 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:developer'; + +/// Returns the current timestamp in microseconds from a monotonically +/// increasing clock. +/// +/// This is the Dart VM implementation. +double get performanceTimestamp => Timeline.now.toDouble(); diff --git a/packages/flutter/lib/src/foundation/_timeline_web.dart b/packages/flutter/lib/src/foundation/_timeline_web.dart new file mode 100644 index 0000000000000..133f096cbe964 --- /dev/null +++ b/packages/flutter/lib/src/foundation/_timeline_web.dart @@ -0,0 +1,27 @@ +// 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:js_interop'; + +/// Returns the current timestamp in microseconds from a monotonically +/// increasing clock. +/// +/// This is the web implementation, which uses `window.performance.now` as the +/// source of the timestamp. +/// +/// See: +/// * https://developer.mozilla.org/en-US/docs/Web/API/Performance/now +double get performanceTimestamp => 1000 * _performance.now(); + +@JS() +@staticInterop +class _DomPerformance {} + +@JS('performance') +external _DomPerformance get _performance; + +extension _DomPerformanceExtension on _DomPerformance { + @JS() + external double now(); +} diff --git a/packages/flutter/lib/src/foundation/assertions.dart b/packages/flutter/lib/src/foundation/assertions.dart index 66103bf2e218d..3cc1c3ba9480e 100644 --- a/packages/flutter/lib/src/foundation/assertions.dart +++ b/packages/flutter/lib/src/foundation/assertions.dart @@ -1117,7 +1117,7 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti // Only include packages we actually elided from. final List<String> where = <String>[ - for (MapEntry<String, int> entry in removedPackagesAndClasses.entries) + for (final MapEntry<String, int> entry in removedPackagesAndClasses.entries) if (entry.value > 0) entry.key, ]..sort(); diff --git a/packages/flutter/lib/src/foundation/binding.dart b/packages/flutter/lib/src/foundation/binding.dart index 3b19fc2cc8ef5..bc451ac718c54 100644 --- a/packages/flutter/lib/src/foundation/binding.dart +++ b/packages/flutter/lib/src/foundation/binding.dart @@ -20,6 +20,7 @@ import 'object.dart'; import 'platform.dart'; import 'print.dart'; import 'service_extensions.dart'; +import 'timeline.dart'; export 'dart:ui' show PlatformDispatcher, SingletonFlutterWindow; // ignore: deprecated_member_use @@ -141,7 +142,9 @@ abstract class BindingBase { /// [initServiceExtensions] to have bindings initialize their /// VM service extensions, if any. BindingBase() { - developer.Timeline.startSync('Framework initialization'); + if (!kReleaseMode) { + FlutterTimeline.startSync('Framework initialization'); + } assert(() { _debugConstructed = true; return true; @@ -157,7 +160,9 @@ abstract class BindingBase { developer.postEvent('Flutter.FrameworkInitialization', <String, String>{}); - developer.Timeline.finishSync(); + if (!kReleaseMode) { + FlutterTimeline.finishSync(); + } } bool _debugConstructed = false; @@ -221,11 +226,12 @@ abstract class BindingBase { /// [BindingBase], e.g., [ServicesBinding], [RendererBinding], and /// [WidgetsBinding]. Each of these bindings define behaviors that interact /// with a [ui.PlatformDispatcher], e.g., [ServicesBinding] registers - /// listeners with the [ChannelBuffers], and [RendererBinding] + /// listeners with the [ChannelBuffers], [RendererBinding] /// registers [ui.PlatformDispatcher.onMetricsChanged], - /// [ui.PlatformDispatcher.onTextScaleFactorChanged], - /// [ui.PlatformDispatcher.onSemanticsEnabledChanged], and - /// [ui.PlatformDispatcher.onSemanticsAction] handlers. + /// [ui.PlatformDispatcher.onTextScaleFactorChanged], and [SemanticsBinding] + /// registers [ui.PlatformDispatcher.onSemanticsEnabledChanged], + /// [ui.PlatformDispatcher.onSemanticsActionEvent], and + /// [ui.PlatformDispatcher.onAccessibilityFeaturesChanged] handlers. /// /// Each of these other bindings could individually access a /// [ui.PlatformDispatcher] statically, but that would preclude the ability to diff --git a/packages/flutter/lib/src/foundation/diagnostics.dart b/packages/flutter/lib/src/foundation/diagnostics.dart index 280a8818d132c..f4f9d5c4c0bc9 100644 --- a/packages/flutter/lib/src/foundation/diagnostics.dart +++ b/packages/flutter/lib/src/foundation/diagnostics.dart @@ -3292,6 +3292,8 @@ mixin Diagnosticable { /// {@end-tool} /// /// Used by [toDiagnosticsNode] and [toString]. + /// + /// Do not add values, that have lifetime shorter than the object. @protected @mustCallSuper void debugFillProperties(DiagnosticPropertiesBuilder properties) { } diff --git a/packages/flutter/lib/src/foundation/node.dart b/packages/flutter/lib/src/foundation/node.dart index 6e830f180a381..ca383fb1524c1 100644 --- a/packages/flutter/lib/src/foundation/node.dart +++ b/packages/flutter/lib/src/foundation/node.dart @@ -8,6 +8,10 @@ import 'package:meta/meta.dart'; // during device lab performance tests. When editing this file, check to make sure // that it didn't break that test. +/// Deprecated. Unused by the framework and will be removed in a future version +/// of Flutter. If needed, inline any required functionality of this class +/// directly in the subclass. +/// /// An abstract node in a tree. /// /// AbstractNode has as notion of depth, attachment, and parent, but does not @@ -39,6 +43,10 @@ import 'package:meta/meta.dart'; /// moved to be a child of A, sibling of B, then the numbers won't change. C's /// [depth] will still be 2. The [depth] is automatically maintained by the /// [adoptChild] and [dropChild] methods. +@Deprecated( + 'If needed, inline any required functionality of AbstractNode in your class directly. ' + 'This feature was deprecated after v3.12.0-4.0.pre.', +) class AbstractNode { /// The depth of this node in the tree. /// diff --git a/packages/flutter/lib/src/foundation/timeline.dart b/packages/flutter/lib/src/foundation/timeline.dart new file mode 100644 index 0000000000000..b110ea0cb0f89 --- /dev/null +++ b/packages/flutter/lib/src/foundation/timeline.dart @@ -0,0 +1,432 @@ +// 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:developer'; +import 'dart:typed_data'; + +import 'package:meta/meta.dart'; + +import '_timeline_io.dart' + if (dart.library.js_util) '_timeline_web.dart' as impl; +import 'constants.dart'; + +/// Measures how long blocks of code take to run. +/// +/// This class can be used as a drop-in replacement for [Timeline] as it +/// provides methods compatible with [Timeline] signature-wise, and it has +/// minimal overhead. +/// +/// Provides [debugReset] and [debugCollect] methods that make it convenient to use in +/// frame-oriented environment where collected metrics can be attributed to a +/// frame, then aggregated into frame statistics, e.g. frame averages. +/// +/// Forwards measurements to [Timeline] so they appear in Flutter DevTools. +abstract final class FlutterTimeline { + static _BlockBuffer _buffer = _BlockBuffer(); + + /// Whether block timings are collected and can be retrieved using the + /// [debugCollect] method. + /// + /// This is always false in release mode. + static bool get debugCollectionEnabled => _collectionEnabled; + + /// Enables metric collection. + /// + /// Metric collection can only be enabled in non-release modes. It is most + /// useful in profile mode where application performance is representative + /// of a deployed application. + /// + /// When disabled, resets collected data by calling [debugReset]. + /// + /// Throws a [StateError] if invoked in release mode. + static set debugCollectionEnabled(bool value) { + if (kReleaseMode) { + throw _createReleaseModeNotSupportedError(); + } + if (value == _collectionEnabled) { + return; + } + _collectionEnabled = value; + debugReset(); + } + + static StateError _createReleaseModeNotSupportedError() { + return StateError('FlutterTimeline metric collection not supported in release mode.'); + } + + static bool _collectionEnabled = false; + + /// Start a synchronous operation labeled `name`. + /// + /// Optionally takes a map of `arguments`. This slice may also optionally be + /// associated with a [Flow] event. This operation must be finished by calling + /// [finishSync] before returning to the event queue. + /// + /// This is a drop-in replacement for [Timeline.startSync]. + static void startSync(String name, { Map<String, Object?>? arguments, Flow? flow }) { + Timeline.startSync(name, arguments: arguments, flow: flow); + if (!kReleaseMode && _collectionEnabled) { + _buffer.startSync(name, arguments: arguments, flow: flow); + } + } + + /// Finish the last synchronous operation that was started. + /// + /// This is a drop-in replacement for [Timeline.finishSync]. + static void finishSync() { + Timeline.finishSync(); + if (!kReleaseMode && _collectionEnabled) { + _buffer.finishSync(); + } + } + + /// Emit an instant event. + /// + /// This is a drop-in replacement for [Timeline.instantSync]. + static void instantSync(String name, { Map<String, Object?>? arguments }) { + Timeline.instantSync(name, arguments: arguments); + } + + /// A utility method to time a synchronous `function`. Internally calls + /// `function` bracketed by calls to [startSync] and [finishSync]. + /// + /// This is a drop-in replacement for [Timeline.timeSync]. + static T timeSync<T>(String name, TimelineSyncFunction<T> function, + { Map<String, Object?>? arguments, Flow? flow }) { + startSync(name, arguments: arguments, flow: flow); + try { + return function(); + } finally { + finishSync(); + } + } + + /// The current time stamp from the clock used by the timeline in + /// microseconds. + /// + /// When run on the Dart VM, uses the same monotonic clock as the embedding + /// API's `Dart_TimelineGetMicros`. + /// + /// When run on the web, uses `window.performance.now`. + /// + /// This is a drop-in replacement for [Timeline.now]. + static int get now => impl.performanceTimestamp.toInt(); + + /// Returns timings collected since [debugCollectionEnabled] was set to true, + /// since the previous [debugCollect], or since the previous [debugReset], + /// whichever was last. + /// + /// Resets the collected timings. + /// + /// This is only meant to be used in non-release modes, typically in profile + /// mode that provides timings close to release mode timings. + static AggregatedTimings debugCollect() { + if (kReleaseMode) { + throw _createReleaseModeNotSupportedError(); + } + if (!_collectionEnabled) { + throw StateError('Timeline metric collection not enabled.'); + } + final AggregatedTimings result = AggregatedTimings(_buffer.computeTimings()); + debugReset(); + return result; + } + + /// Forgets all previously collected timing data. + /// + /// Use this method to scope metrics to a frame, a pointer event, or any + /// other event. To do that, call [debugReset] at the start of the event, then + /// call [debugCollect] at the end of the event. + /// + /// This is only meant to be used in non-release modes. + static void debugReset() { + if (kReleaseMode) { + throw _createReleaseModeNotSupportedError(); + } + _buffer = _BlockBuffer(); + } +} + +/// Provides [start], [end], and [duration] of a named block of code, timed by +/// [FlutterTimeline]. +@immutable +final class TimedBlock { + /// Creates a timed block of code from a [name], [start], and [end]. + /// + /// The [name] should be sufficiently unique and descriptive for someone to + /// easily tell which part of code was measured. + const TimedBlock({ + required this.name, + required this.start, + required this.end, + }) : assert(end >= start, 'The start timestamp must not be greater than the end timestamp.'); + + /// A readable label for a block of code that was measured. + /// + /// This field should be sufficiently unique and descriptive for someone to + /// easily tell which part of code was measured. + final String name; + + /// The timestamp in microseconds that marks the beginning of the measured + /// block of code. + final double start; + + /// The timestamp in microseconds that marks the end of the measured block of + /// code. + final double end; + + /// How long the measured block of code took to execute in microseconds. + double get duration => end - start; + + @override + String toString() { + return 'TimedBlock($name, $start, $end, $duration)'; + } +} + +/// Provides aggregated results for timings collected by [FlutterTimeline]. +@immutable +final class AggregatedTimings { + /// Creates aggregated timings for the provided timed blocks. + AggregatedTimings(this.timedBlocks); + + /// All timed blocks collected between the last reset and [FlutterTimeline.debugCollect]. + final List<TimedBlock> timedBlocks; + + /// Aggregated timed blocks collected between the last reset and [FlutterTimeline.debugCollect]. + /// + /// Does not guarantee that all code blocks will be reported. Only those that + /// executed since the last reset are listed here. Use [getAggregated] for + /// graceful handling of missing code blocks. + late final List<AggregatedTimedBlock> aggregatedBlocks = _computeAggregatedBlocks(); + + List<AggregatedTimedBlock> _computeAggregatedBlocks() { + final Map<String, (double, int)> aggregate = <String, (double, int)>{}; + for (final TimedBlock block in timedBlocks) { + final (double, int) previousValue = aggregate.putIfAbsent(block.name, () => (0, 0)); + aggregate[block.name] = (previousValue.$1 + block.duration, previousValue.$2 + 1); + } + return aggregate.entries.map<AggregatedTimedBlock>( + (MapEntry<String, (double, int)> entry) { + return AggregatedTimedBlock(name: entry.key, duration: entry.value.$1, count: entry.value.$2); + } + ).toList(); + } + + /// Returns aggregated numbers for a named block of code. + /// + /// If the block in question never executed since the last reset, returns an + /// aggregation with zero duration and count. + AggregatedTimedBlock getAggregated(String name) { + return aggregatedBlocks.singleWhere( + (AggregatedTimedBlock block) => block.name == name, + // Handle the case where there are no recorded blocks of the specified + // type. In this case, the aggregated duration is simply zero, and so is + // the number of occurrences (i.e. count). + orElse: () => AggregatedTimedBlock(name: name, duration: 0, count: 0), + ); + } +} + +/// Aggregates multiple [TimedBlock] objects that share a [name]. +/// +/// It is common for the same block of code to be executed multiple times within +/// a frame. It is useful to combine multiple executions and report the total +/// amount of time attributed to that block of code. +@immutable +final class AggregatedTimedBlock { + /// Creates a timed block of code from a [name] and [duration]. + /// + /// The [name] should be sufficiently unique and descriptive for someone to + /// easily tell which part of code was measured. + const AggregatedTimedBlock({ + required this.name, + required this.duration, + required this.count, + }) : assert(duration >= 0); + + /// A readable label for a block of code that was measured. + /// + /// This field should be sufficiently unique and descriptive for someone to + /// easily tell which part of code was measured. + final String name; + + /// The sum of [TimedBlock.duration] values of aggretaged blocks. + final double duration; + + /// The number of [TimedBlock] objects aggregated. + final int count; + + @override + String toString() { + return 'AggregatedTimedBlock($name, $duration, $count)'; + } +} + +const int _kSliceSize = 500; + +/// A growable list of float64 values with predictable [add] performance. +/// +/// The list is organized into a "chain" of [Float64List]s. The object starts +/// with a `Float64List` "slice". When [add] is called, the value is added to +/// the slice. Once the slice is full, it is moved into the chain, and a new +/// slice is allocated. Slice size is static and therefore its allocation has +/// predictable cost. This is unlike the default [List] implementation, which, +/// when full, doubles its buffer size and copies all old elements into the new +/// buffer, leading to unpredictable performance. This makes it a poor choice +/// for recording performance because buffer reallocation would affect the +/// runtime. +/// +/// The trade-off is that reading values back from the chain is more expensive +/// compared to [List] because it requires iterating over multiple slices. This +/// is a reasonable trade-off for performance metrics, because it is more +/// important to minimize the overhead while recording metrics, than it is when +/// reading them. +final class _Float64ListChain { + _Float64ListChain(); + + final List<Float64List> _chain = <Float64List>[]; + Float64List _slice = Float64List(_kSliceSize); + int _pointer = 0; + + int get length => _length; + int _length = 0; + + /// Adds and [element] to this chain. + void add(double element) { + _slice[_pointer] = element; + _pointer += 1; + _length += 1; + if (_pointer >= _kSliceSize) { + _chain.add(_slice); + _slice = Float64List(_kSliceSize); + _pointer = 0; + } + } + + /// Returns all elements added to this chain. + /// + /// This getter is not optimized to be fast. It is assumed that when metrics + /// are read back, they do not affect the timings of the work being + /// benchmarked. + List<double> extractElements() { + final List<double> result = <double>[]; + _chain.forEach(result.addAll); + for (int i = 0; i < _pointer; i++) { + result.add(_slice[i]); + } + return result; + } +} + +/// Same as [_Float64ListChain] but for recording string values. +final class _StringListChain { + _StringListChain(); + + final List<List<String?>> _chain = <List<String?>>[]; + List<String?> _slice = List<String?>.filled(_kSliceSize, null); + int _pointer = 0; + + int get length => _length; + int _length = 0; + + /// Adds and [element] to this chain. + void add(String element) { + _slice[_pointer] = element; + _pointer += 1; + _length += 1; + if (_pointer >= _kSliceSize) { + _chain.add(_slice); + _slice = List<String?>.filled(_kSliceSize, null); + _pointer = 0; + } + } + + /// Returns all elements added to this chain. + /// + /// This getter is not optimized to be fast. It is assumed that when metrics + /// are read back, they do not affect the timings of the work being + /// benchmarked. + List<String> extractElements() { + final List<String> result = <String>[]; + for (final List<String?> slice in _chain) { + for (final String? element in slice) { + result.add(element!); + } + } + for (int i = 0; i < _pointer; i++) { + result.add(_slice[i]!); + } + return result; + } +} + +/// A buffer that records starts and ends of code blocks, and their names. +final class _BlockBuffer { + // Start-finish blocks can be nested. Track this nestedness by stacking the + // start timestamps. Finish timestamps will pop timings from the stack and + // add the (start, finish) tuple to the _block. + static const int _stackDepth = 1000; + static final Float64List _startStack = Float64List(_stackDepth); + static final List<String?> _nameStack = List<String?>.filled(_stackDepth, null); + static int _stackPointer = 0; + + final _Float64ListChain _starts = _Float64ListChain(); + final _Float64ListChain _finishes = _Float64ListChain(); + final _StringListChain _names = _StringListChain(); + + List<TimedBlock> computeTimings() { + assert( + _stackPointer == 0, + 'Invalid sequence of `startSync` and `finishSync`.\n' + 'The operation stack was not empty. The following operations are still ' + 'waiting to be finished via the `finishSync` method:\n' + '${List<String>.generate(_stackPointer, (int i) => _nameStack[i]!).join(', ')}' + ); + + final List<TimedBlock> result = <TimedBlock>[]; + final int length = _finishes.length; + final List<double> starts = _starts.extractElements(); + final List<double> finishes = _finishes.extractElements(); + final List<String> names = _names.extractElements(); + + assert(starts.length == length); + assert(finishes.length == length); + assert(names.length == length); + + for (int i = 0; i < length; i++) { + result.add(TimedBlock( + start: starts[i], + end: finishes[i], + name: names[i], + )); + } + + return result; + } + + void startSync(String name, { Map<String, Object?>? arguments, Flow? flow }) { + _startStack[_stackPointer] = impl.performanceTimestamp; + _nameStack[_stackPointer] = name; + _stackPointer += 1; + } + + void finishSync() { + assert( + _stackPointer > 0, + 'Invalid sequence of `startSync` and `finishSync`.\n' + 'Attempted to finish timing a block of code, but there are no pending ' + '`startSync` calls.' + ); + + final double finishTime = impl.performanceTimestamp; + final double startTime = _startStack[_stackPointer - 1]; + final String name = _nameStack[_stackPointer - 1]!; + _stackPointer -= 1; + + _starts.add(startTime); + _finishes.add(finishTime); + _names.add(name); + } +} diff --git a/packages/flutter/lib/src/gestures/binding.dart b/packages/flutter/lib/src/gestures/binding.dart index 67f665ad85cab..ccb6be31be4d0 100644 --- a/packages/flutter/lib/src/gestures/binding.dart +++ b/packages/flutter/lib/src/gestures/binding.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - import 'dart:async'; import 'dart:collection'; import 'dart:ui' as ui show PointerDataPacket; @@ -288,7 +287,7 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H // We convert pointer data to logical pixels so that e.g. the touch slop can be // defined in a device-independent manner. try { - _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, platformDispatcher.implicitView!.devicePixelRatio)); + _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, _devicePixelRatioForView)); if (!locked) { _flushPointerEventQueue(); } @@ -302,6 +301,10 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H } } + double? _devicePixelRatioForView(int viewId) { + return platformDispatcher.view(id: viewId)?.devicePixelRatio; + } + /// Dispatch a [PointerCancelEvent] for the given pointer soon. /// /// The pointer event will be dispatched before the next pointer event and @@ -334,8 +337,18 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H /// State for all pointers which are currently down. /// - /// The state of hovering pointers is not tracked because that would require - /// hit-testing on every frame. + /// This map caches the hit test result done when the pointer goes down + /// ([PointerDownEvent] and [PointerPanZoomStartEvent]). This hit test result + /// will be used throughout the entire pointer interaction; that is, the + /// pointer is seen as pointing to the same place even if it has moved away + /// until pointer goes up ([PointerUpEvent] and [PointerPanZoomEndEvent]). + /// This matches the expected gesture interaction with a button, and allows + /// devices that don't support hovering to perform as few hit tests as + /// possible. + /// + /// On the other hand, hovering requires hit testing on almost every frame. + /// This is handled in [RendererBinding] and [MouseTracker], and will ignore + /// the results cached here. final Map<int, HitTestResult> _hitTests = <int, HitTestResult>{}; /// Dispatch an event to the targets found by a hit test on its position. @@ -368,7 +381,7 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent || event is PointerPanZoomStartEvent) { assert(!_hitTests.containsKey(event.pointer), 'Pointer of ${event.toString(minLevel: DiagnosticLevel.debug)} unexpectedly has a HitTestResult associated with it.'); hitTestResult = HitTestResult(); - hitTest(hitTestResult, event.position); + hitTestInView(hitTestResult, event.position, event.viewId); if (event is PointerDownEvent || event is PointerPanZoomStartEvent) { _hitTests[event.pointer] = hitTestResult; } @@ -401,12 +414,22 @@ mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, H } } - /// Determine which [HitTestTarget] objects are located at a given position. + /// Determine which [HitTestTarget] objects are located at a given position in + /// the specified view. @override // from HitTestable - void hitTest(HitTestResult result, Offset position) { + void hitTestInView(HitTestResult result, Offset position, int viewId) { result.add(HitTestEntry(this)); } + @override // from HitTestable + @Deprecated( + 'Use hitTestInView and specify the view to hit test. ' + 'This feature was deprecated after v3.11.0-20.0.pre.', + ) + void hitTest(HitTestResult result, Offset position) { + hitTestInView(result, position, platformDispatcher.implicitView!.viewId); + } + /// Dispatch an event to [pointerRouter] and the path of a hit test result. /// /// The `event` is routed to [pointerRouter]. If the `hitTestResult` is not diff --git a/packages/flutter/lib/src/gestures/converter.dart b/packages/flutter/lib/src/gestures/converter.dart index e40f29e02e91a..21095d1e58064 100644 --- a/packages/flutter/lib/src/gestures/converter.dart +++ b/packages/flutter/lib/src/gestures/converter.dart @@ -32,6 +32,18 @@ int _synthesiseDownButtons(int buttons, PointerDeviceKind kind) { } } +/// Signature for a callback that returns the device pixel ratio of a +/// [FlutterView] identified by the provided `viewId`. +/// +/// Returns null if no view with the provided ID exists. +/// +/// Used by [PointerEventConverter.expand]. +/// +/// See also: +/// +/// * [FlutterView.devicePixelRatio] for an explanation of device pixel ratio. +typedef DevicePixelRatioGetter = double? Function(int viewId); + /// Converts from engine pointer data to framework pointer events. /// /// This takes [PointerDataPacket] objects, as received from the engine via @@ -41,14 +53,19 @@ abstract final class PointerEventConverter { /// Expand the given packet of pointer data into a sequence of framework /// pointer events. /// - /// The `devicePixelRatio` argument (usually given the value from - /// [dart:ui.FlutterView.devicePixelRatio]) is used to convert the incoming data - /// from physical coordinates to logical pixels. See the discussion at - /// [PointerEvent] for more details on the [PointerEvent] coordinate space. - static Iterable<PointerEvent> expand(Iterable<ui.PointerData> data, double devicePixelRatio) { + /// The `devicePixelRatioForView` is used to obtain the device pixel ratio for + /// the view a particular event occurred in to convert its data from physical + /// coordinates to logical pixels. See the discussion at [PointerEvent] for + /// more details on the [PointerEvent] coordinate space. + static Iterable<PointerEvent> expand(Iterable<ui.PointerData> data, DevicePixelRatioGetter devicePixelRatioForView) { return data .where((ui.PointerData datum) => datum.signalKind != ui.PointerSignalKind.unknown) .map<PointerEvent?>((ui.PointerData datum) { + final double? devicePixelRatio = devicePixelRatioForView(datum.viewId); + if (devicePixelRatio == null) { + // View doesn't exist anymore. + return null; + } final Offset position = Offset(datum.physicalX, datum.physicalY) / devicePixelRatio; final Offset delta = Offset(datum.physicalDeltaX, datum.physicalDeltaY) / devicePixelRatio; final double radiusMinor = _toLogicalPixels(datum.radiusMinor, devicePixelRatio); @@ -62,6 +79,7 @@ abstract final class PointerEventConverter { switch (datum.change) { case ui.PointerChange.add: return PointerAddedEvent( + viewId: datum.viewId, timeStamp: timeStamp, kind: kind, device: datum.device, @@ -79,6 +97,7 @@ abstract final class PointerEventConverter { ); case ui.PointerChange.hover: return PointerHoverEvent( + viewId: datum.viewId, timeStamp: timeStamp, kind: kind, device: datum.device, @@ -102,6 +121,7 @@ abstract final class PointerEventConverter { ); case ui.PointerChange.down: return PointerDownEvent( + viewId: datum.viewId, timeStamp: timeStamp, pointer: datum.pointerIdentifier, kind: kind, @@ -124,6 +144,7 @@ abstract final class PointerEventConverter { ); case ui.PointerChange.move: return PointerMoveEvent( + viewId: datum.viewId, timeStamp: timeStamp, pointer: datum.pointerIdentifier, kind: kind, @@ -149,6 +170,7 @@ abstract final class PointerEventConverter { ); case ui.PointerChange.up: return PointerUpEvent( + viewId: datum.viewId, timeStamp: timeStamp, pointer: datum.pointerIdentifier, kind: kind, @@ -172,6 +194,7 @@ abstract final class PointerEventConverter { ); case ui.PointerChange.cancel: return PointerCancelEvent( + viewId: datum.viewId, timeStamp: timeStamp, pointer: datum.pointerIdentifier, kind: kind, @@ -194,6 +217,7 @@ abstract final class PointerEventConverter { ); case ui.PointerChange.remove: return PointerRemovedEvent( + viewId: datum.viewId, timeStamp: timeStamp, kind: kind, device: datum.device, @@ -208,6 +232,7 @@ abstract final class PointerEventConverter { ); case ui.PointerChange.panZoomStart: return PointerPanZoomStartEvent( + viewId: datum.viewId, timeStamp: timeStamp, pointer: datum.pointerIdentifier, device: datum.device, @@ -221,6 +246,7 @@ abstract final class PointerEventConverter { final Offset panDelta = Offset(datum.panDeltaX, datum.panDeltaY) / devicePixelRatio; return PointerPanZoomUpdateEvent( + viewId: datum.viewId, timeStamp: timeStamp, pointer: datum.pointerIdentifier, device: datum.device, @@ -234,6 +260,7 @@ abstract final class PointerEventConverter { ); case ui.PointerChange.panZoomEnd: return PointerPanZoomEndEvent( + viewId: datum.viewId, timeStamp: timeStamp, pointer: datum.pointerIdentifier, device: datum.device, @@ -249,6 +276,7 @@ abstract final class PointerEventConverter { final Offset scrollDelta = Offset(datum.scrollDeltaX, datum.scrollDeltaY) / devicePixelRatio; return PointerScrollEvent( + viewId: datum.viewId, timeStamp: timeStamp, kind: kind, device: datum.device, @@ -258,6 +286,7 @@ abstract final class PointerEventConverter { ); case ui.PointerSignalKind.scrollInertiaCancel: return PointerScrollInertiaCancelEvent( + viewId: datum.viewId, timeStamp: timeStamp, kind: kind, device: datum.device, @@ -266,6 +295,7 @@ abstract final class PointerEventConverter { ); case ui.PointerSignalKind.scale: return PointerScaleEvent( + viewId: datum.viewId, timeStamp: timeStamp, kind: kind, device: datum.device, @@ -274,11 +304,6 @@ abstract final class PointerEventConverter { scale: datum.scale, ); case ui.PointerSignalKind.unknown: - default: // ignore: no_default_cases, to allow adding a new [PointerSignalKind] - PointerStylusAuxiliaryAction - // TODO(louisehsu): remove after landing engine PR https://github.com/flutter/engine/pull/39637 - // This branch should already have 'unknown' filtered out, but - // we don't want to return anything or miss if someone adds a new - // enumeration to PointerSignalKind. throw StateError('Unreachable'); } }).whereType<PointerEvent>(); diff --git a/packages/flutter/lib/src/gestures/debug.dart b/packages/flutter/lib/src/gestures/debug.dart index 59173dd79af18..0bc9343e35394 100644 --- a/packages/flutter/lib/src/gestures/debug.dart +++ b/packages/flutter/lib/src/gestures/debug.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - import 'package:flutter/foundation.dart'; // Any changes to this file should be reflected in the debugAssertAllGesturesVarsUnset() diff --git a/packages/flutter/lib/src/gestures/events.dart b/packages/flutter/lib/src/gestures/events.dart index 1a1b5f2b576cc..be147d6d154cc 100644 --- a/packages/flutter/lib/src/gestures/events.dart +++ b/packages/flutter/lib/src/gestures/events.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - import 'dart:ui' show Offset, PointerDeviceKind; import 'package:flutter/foundation.dart'; @@ -245,6 +244,7 @@ abstract class PointerEvent with Diagnosticable { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. const PointerEvent({ + this.viewId = 0, this.embedderId = 0, this.timeStamp = Duration.zero, this.pointer = 0, @@ -273,6 +273,9 @@ abstract class PointerEvent with Diagnosticable { this.original, }); + /// The ID of the [FlutterView] which this event originated from. + final int viewId; + /// Unique identifier that ties the [PointerEvent] to the embedder event that created it. /// /// No two pointer events can have the same [embedderId] on platforms that set it. @@ -536,6 +539,7 @@ abstract class PointerEvent with Diagnosticable { /// Calling this method on a transformed event will return a new transformed /// event based on the current [transform] and the provided properties. PointerEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -619,7 +623,6 @@ abstract class PointerEvent with Diagnosticable { // A mixin that adds implementation for [debugFillProperties] and [toStringFull] // to [PointerEvent]. mixin _PointerEventDescription on PointerEvent { - @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); @@ -650,6 +653,7 @@ mixin _PointerEventDescription on PointerEvent { properties.add(FlagProperty('obscured', value: obscured, ifTrue: 'obscured', level: DiagnosticLevel.debug)); properties.add(FlagProperty('synthesized', value: synthesized, ifTrue: 'synthesized', level: DiagnosticLevel.debug)); properties.add(IntProperty('embedderId', embedderId, defaultValue: 0, level: DiagnosticLevel.debug)); + properties.add(IntProperty('viewId', viewId, defaultValue: 0, level: DiagnosticLevel.debug)); } /// Returns a complete textual description of this event. @@ -666,7 +670,6 @@ abstract class _AbstractPointerEvent implements PointerEvent { } // matrix. It defers all field getters to the original event, except for // [localPosition] and [localDelta], which are calculated when first used. abstract class _TransformedPointerEvent extends _AbstractPointerEvent with Diagnosticable, _PointerEventDescription { - @override PointerEvent get original; @@ -758,11 +761,15 @@ abstract class _TransformedPointerEvent extends _AbstractPointerEvent with Diagn untransformedEndPosition: position, transformedEndPosition: localPosition, ); + + @override + int get viewId => original.viewId; } mixin _CopyPointerAddedEvent on PointerEvent { @override PointerAddedEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -787,6 +794,7 @@ mixin _CopyPointerAddedEvent on PointerEvent { int? embedderId, }) { return PointerAddedEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, kind: kind ?? this.kind, device: device ?? this.device, @@ -814,6 +822,7 @@ class PointerAddedEvent extends PointerEvent with _PointerEventDescription, _Cop /// /// All of the arguments must be non-null. const PointerAddedEvent({ + super.viewId, super.timeStamp, super.pointer, super.kind, @@ -858,6 +867,7 @@ class _TransformedPointerAddedEvent extends _TransformedPointerEvent with _CopyP mixin _CopyPointerRemovedEvent on PointerEvent { @override PointerRemovedEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -882,6 +892,7 @@ mixin _CopyPointerRemovedEvent on PointerEvent { int? embedderId, }) { return PointerRemovedEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, kind: kind ?? this.kind, device: device ?? this.device, @@ -906,6 +917,7 @@ class PointerRemovedEvent extends PointerEvent with _PointerEventDescription, _C /// /// All of the arguments must be non-null. const PointerRemovedEvent({ + super.viewId, super.timeStamp, super.pointer, super.kind, @@ -948,6 +960,7 @@ class _TransformedPointerRemovedEvent extends _TransformedPointerEvent with _Cop mixin _CopyPointerHoverEvent on PointerEvent { @override PointerHoverEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -972,6 +985,7 @@ mixin _CopyPointerHoverEvent on PointerEvent { int? embedderId, }) { return PointerHoverEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, kind: kind ?? this.kind, device: device ?? this.device, @@ -1013,6 +1027,7 @@ class PointerHoverEvent extends PointerEvent with _PointerEventDescription, _Cop /// /// All of the arguments must be non-null. const PointerHoverEvent({ + super.viewId, super.timeStamp, super.kind, super.pointer, @@ -1064,6 +1079,7 @@ class _TransformedPointerHoverEvent extends _TransformedPointerEvent with _CopyP mixin _CopyPointerEnterEvent on PointerEvent { @override PointerEnterEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -1088,6 +1104,7 @@ mixin _CopyPointerEnterEvent on PointerEvent { int? embedderId, }) { return PointerEnterEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, kind: kind ?? this.kind, device: device ?? this.device, @@ -1129,6 +1146,7 @@ class PointerEnterEvent extends PointerEvent with _PointerEventDescription, _Cop /// /// All of the arguments must be non-null. const PointerEnterEvent({ + super.viewId, super.timeStamp, super.pointer, super.kind, @@ -1162,6 +1180,7 @@ class PointerEnterEvent extends PointerEvent with _PointerEventDescription, _Cop /// /// This is used by the [MouseTracker] to synthesize enter events. factory PointerEnterEvent.fromMouseEvent(PointerEvent event) => PointerEnterEvent( + viewId: event.viewId, timeStamp: event.timeStamp, pointer: event.pointer, kind: event.kind, @@ -1210,6 +1229,7 @@ class _TransformedPointerEnterEvent extends _TransformedPointerEvent with _CopyP mixin _CopyPointerExitEvent on PointerEvent { @override PointerExitEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -1234,6 +1254,7 @@ mixin _CopyPointerExitEvent on PointerEvent { int? embedderId, }) { return PointerExitEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, kind: kind ?? this.kind, device: device ?? this.device, @@ -1275,6 +1296,7 @@ class PointerExitEvent extends PointerEvent with _PointerEventDescription, _Copy /// /// All of the arguments must be non-null. const PointerExitEvent({ + super.viewId, super.timeStamp, super.kind, super.pointer, @@ -1306,6 +1328,7 @@ class PointerExitEvent extends PointerEvent with _PointerEventDescription, _Copy /// /// This is used by the [MouseTracker] to synthesize exit events. factory PointerExitEvent.fromMouseEvent(PointerEvent event) => PointerExitEvent( + viewId: event.viewId, timeStamp: event.timeStamp, pointer: event.pointer, kind: event.kind, @@ -1355,6 +1378,7 @@ class _TransformedPointerExitEvent extends _TransformedPointerEvent with _CopyPo mixin _CopyPointerDownEvent on PointerEvent { @override PointerDownEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -1379,6 +1403,7 @@ mixin _CopyPointerDownEvent on PointerEvent { int? embedderId, }) { return PointerDownEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, pointer: pointer ?? this.pointer, kind: kind ?? this.kind, @@ -1413,6 +1438,7 @@ class PointerDownEvent extends PointerEvent with _PointerEventDescription, _Copy /// /// All of the arguments must be non-null. const PointerDownEvent({ + super.viewId, super.timeStamp, super.pointer, super.kind, @@ -1463,6 +1489,7 @@ class _TransformedPointerDownEvent extends _TransformedPointerEvent with _CopyPo mixin _CopyPointerMoveEvent on PointerEvent { @override PointerMoveEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -1487,6 +1514,7 @@ mixin _CopyPointerMoveEvent on PointerEvent { int? embedderId, }) { return PointerMoveEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, pointer: pointer ?? this.pointer, kind: kind ?? this.kind, @@ -1526,6 +1554,7 @@ class PointerMoveEvent extends PointerEvent with _PointerEventDescription, _Copy /// /// All of the arguments must be non-null. const PointerMoveEvent({ + super.viewId, super.timeStamp, super.pointer, super.kind, @@ -1580,6 +1609,7 @@ class _TransformedPointerMoveEvent extends _TransformedPointerEvent with _CopyPo mixin _CopyPointerUpEvent on PointerEvent { @override PointerUpEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -1605,6 +1635,7 @@ mixin _CopyPointerUpEvent on PointerEvent { int? embedderId, }) { return PointerUpEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, pointer: pointer ?? this.pointer, kind: kind ?? this.kind, @@ -1640,6 +1671,7 @@ class PointerUpEvent extends PointerEvent with _PointerEventDescription, _CopyPo /// /// All of the arguments must be non-null. const PointerUpEvent({ + super.viewId, super.timeStamp, super.pointer, super.kind, @@ -1705,6 +1737,7 @@ abstract class PointerSignalEvent extends PointerEvent { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. const PointerSignalEvent({ + super.viewId, super.timeStamp, super.pointer, super.kind = PointerDeviceKind.mouse, @@ -1720,6 +1753,7 @@ mixin _CopyPointerScrollEvent on PointerEvent { @override PointerScrollEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -1744,6 +1778,7 @@ mixin _CopyPointerScrollEvent on PointerEvent { int? embedderId, }) { return PointerScrollEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, kind: kind ?? this.kind, device: device ?? this.device, @@ -1770,6 +1805,7 @@ class PointerScrollEvent extends PointerSignalEvent with _PointerEventDescriptio /// /// All of the arguments must be non-null. const PointerScrollEvent({ + super.viewId, super.timeStamp, super.kind, super.device, @@ -1821,6 +1857,7 @@ class _TransformedPointerScrollEvent extends _TransformedPointerEvent with _Copy mixin _CopyPointerScrollInertiaCancelEvent on PointerEvent { @override PointerScrollInertiaCancelEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -1845,6 +1882,7 @@ mixin _CopyPointerScrollInertiaCancelEvent on PointerEvent { int? embedderId, }) { return PointerScrollInertiaCancelEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, kind: kind ?? this.kind, device: device ?? this.device, @@ -1870,6 +1908,7 @@ class PointerScrollInertiaCancelEvent extends PointerSignalEvent with _PointerEv /// /// All of the arguments must be non-null. const PointerScrollInertiaCancelEvent({ + super.viewId, super.timeStamp, super.kind, super.device, @@ -1905,6 +1944,7 @@ mixin _CopyPointerScaleEvent on PointerEvent { @override PointerScaleEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -1930,6 +1970,7 @@ mixin _CopyPointerScaleEvent on PointerEvent { double? scale, }) { return PointerScaleEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, kind: kind ?? this.kind, device: device ?? this.device, @@ -1956,6 +1997,7 @@ class PointerScaleEvent extends PointerSignalEvent with _PointerEventDescription /// /// All of the arguments must be non-null. const PointerScaleEvent({ + super.viewId, super.timeStamp, super.kind, super.device, @@ -1995,6 +2037,7 @@ class _TransformedPointerScaleEvent extends _TransformedPointerEvent with _CopyP mixin _CopyPointerPanZoomStartEvent on PointerEvent { @override PointerPanZoomStartEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -2020,6 +2063,7 @@ mixin _CopyPointerPanZoomStartEvent on PointerEvent { }) { assert(kind == null || identical(kind, PointerDeviceKind.trackpad)); return PointerPanZoomStartEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, device: device ?? this.device, position: position ?? this.position, @@ -2039,6 +2083,7 @@ class PointerPanZoomStartEvent extends PointerEvent with _PointerEventDescriptio /// /// All of the arguments must be non-null. const PointerPanZoomStartEvent({ + super.viewId, super.timeStamp, super.device, super.pointer, @@ -2085,6 +2130,7 @@ mixin _CopyPointerPanZoomUpdateEvent on PointerEvent { @override PointerPanZoomUpdateEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -2116,6 +2162,7 @@ mixin _CopyPointerPanZoomUpdateEvent on PointerEvent { }) { assert(kind == null || identical(kind, PointerDeviceKind.trackpad)); return PointerPanZoomUpdateEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, device: device ?? this.device, position: position ?? this.position, @@ -2139,6 +2186,7 @@ class PointerPanZoomUpdateEvent extends PointerEvent with _PointerEventDescripti /// /// All of the arguments must be non-null. const PointerPanZoomUpdateEvent({ + super.viewId, super.timeStamp, super.device, super.pointer, @@ -2212,6 +2260,7 @@ class _TransformedPointerPanZoomUpdateEvent extends _TransformedPointerEvent wit mixin _CopyPointerPanZoomEndEvent on PointerEvent { @override PointerPanZoomEndEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -2237,6 +2286,7 @@ mixin _CopyPointerPanZoomEndEvent on PointerEvent { }) { assert(kind == null || identical(kind, PointerDeviceKind.trackpad)); return PointerPanZoomEndEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, device: device ?? this.device, position: position ?? this.position, @@ -2256,6 +2306,7 @@ class PointerPanZoomEndEvent extends PointerEvent with _PointerEventDescription, /// /// All of the arguments must be non-null. const PointerPanZoomEndEvent({ + super.viewId, super.timeStamp, super.device, super.pointer, @@ -2289,6 +2340,7 @@ class _TransformedPointerPanZoomEndEvent extends _TransformedPointerEvent with _ mixin _CopyPointerCancelEvent on PointerEvent { @override PointerCancelEvent copyWith({ + int? viewId, Duration? timeStamp, int? pointer, PointerDeviceKind? kind, @@ -2313,6 +2365,7 @@ mixin _CopyPointerCancelEvent on PointerEvent { int? embedderId, }) { return PointerCancelEvent( + viewId: viewId ?? this.viewId, timeStamp: timeStamp ?? this.timeStamp, pointer: pointer ?? this.pointer, kind: kind ?? this.kind, @@ -2347,6 +2400,7 @@ class PointerCancelEvent extends PointerEvent with _PointerEventDescription, _Co /// /// All of the arguments must be non-null. const PointerCancelEvent({ + super.viewId, super.timeStamp, super.pointer, super.kind, diff --git a/packages/flutter/lib/src/gestures/hit_test.dart b/packages/flutter/lib/src/gestures/hit_test.dart index 4a8fd11bfba51..b620872044ce0 100644 --- a/packages/flutter/lib/src/gestures/hit_test.dart +++ b/packages/flutter/lib/src/gestures/hit_test.dart @@ -16,11 +16,16 @@ export 'events.dart' show PointerEvent; /// An object that can hit-test pointers. abstract interface class HitTestable { - /// Check whether the given position hits this object. - /// - /// If this given position hits this object, consider adding a [HitTestEntry] - /// to the given hit test result. + /// Deprecated. Use [hitTestInView] instead. + @Deprecated( + 'Use hitTestInView and specify the view to hit test. ' + 'This feature was deprecated after v3.11.0-20.0.pre.', + ) void hitTest(HitTestResult result, Offset position); + + /// Fills the provided [HitTestResult] with [HitTestEntry]s for objects that + /// are hit at the given `position` in the view identified by `viewId`. + void hitTestInView(HitTestResult result, Offset position, int viewId); } /// An object that can dispatch events. diff --git a/packages/flutter/lib/src/gestures/long_press.dart b/packages/flutter/lib/src/gestures/long_press.dart index 91a11c781ac2a..1c759c2161227 100644 --- a/packages/flutter/lib/src/gestures/long_press.dart +++ b/packages/flutter/lib/src/gestures/long_press.dart @@ -645,7 +645,7 @@ class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { _initialButtons = event.buttons; _checkLongPressDown(event); } else if (event is PointerMoveEvent) { - if (event.buttons != _initialButtons) { + if (event.buttons != _initialButtons && !_longPressAccepted) { resolve(GestureDisposition.rejected); stopTrackingPointer(primaryPointer!); } else if (_longPressAccepted) { diff --git a/packages/flutter/lib/src/gestures/monodrag.dart b/packages/flutter/lib/src/gestures/monodrag.dart index 5af864224402f..31a2a028486b8 100644 --- a/packages/flutter/lib/src/gestures/monodrag.dart +++ b/packages/flutter/lib/src/gestures/monodrag.dart @@ -77,6 +77,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { super.debugOwner, this.dragStartBehavior = DragStartBehavior.start, this.velocityTrackerBuilder = _defaultBuilder, + this.onlyAcceptDragOnThreshold = false, super.supportedDevices, AllowedButtonsFilter? allowedButtonsFilter, }) : super(allowedButtonsFilter: allowedButtonsFilter ?? _defaultButtonAcceptBehavior); @@ -210,6 +211,25 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { /// If null then [kMaxFlingVelocity] is used. double? maxFlingVelocity; + /// Whether the drag threshold should be met before dispatching any drag callbacks. + /// + /// The drag threshold is met when the global distance traveled by a pointer has + /// exceeded the defined threshold on the relevant axis, i.e. y-axis for the + /// [VerticalDragGestureRecognizer], x-axis for the [HorizontalDragGestureRecognizer], + /// and the entire plane for [PanGestureRecognizer]. The threshold for both + /// [VerticalDragGestureRecognizer] and [HorizontalDragGestureRecognizer] are + /// calculated by [computeHitSlop], while [computePanSlop] is used for + /// [PanGestureRecognizer]. + /// + /// If true, the drag callbacks will only be dispatched when this recognizer has + /// won the arena and the drag threshold has been met. + /// + /// If false, the drag callbacks will be dispatched immediately when this recognizer + /// has won the arena. + /// + /// This value defaults to false. + bool onlyAcceptDragOnThreshold; + /// Determines the type of velocity estimation method to use for a potential /// drag gesture, when a new pointer is added. /// @@ -284,6 +304,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { Offset _getDeltaForDetails(Offset delta); double? _getPrimaryValueFromOffset(Offset value); bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind, double? deviceTouchSlop); + bool _hasDragThresholdBeenMet = false; final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{}; @@ -386,7 +407,12 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { untransformedEndPosition: localPosition ).distance * (_getPrimaryValueFromOffset(movedLocally) ?? 1).sign; if (_hasSufficientGlobalDistanceToAccept(event.kind, gestureSettings?.touchSlop)) { - resolve(GestureDisposition.accepted); + _hasDragThresholdBeenMet = true; + if (_acceptedActivePointers.contains(event.pointer)) { + _checkDrag(event.pointer); + } else { + resolve(GestureDisposition.accepted); + } } } } @@ -401,45 +427,8 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { void acceptGesture(int pointer) { assert(!_acceptedActivePointers.contains(pointer)); _acceptedActivePointers.add(pointer); - if (_state != _DragState.accepted) { - _state = _DragState.accepted; - final OffsetPair delta = _pendingDragOffset; - final Duration? timestamp = _lastPendingEventTimestamp; - final Matrix4? transform = _lastTransform; - final Offset localUpdateDelta; - switch (dragStartBehavior) { - case DragStartBehavior.start: - _initialPosition = _initialPosition + delta; - localUpdateDelta = Offset.zero; - case DragStartBehavior.down: - localUpdateDelta = _getDeltaForDetails(delta.local); - } - _pendingDragOffset = OffsetPair.zero; - _lastPendingEventTimestamp = null; - _lastTransform = null; - _checkStart(timestamp, pointer); - if (localUpdateDelta != Offset.zero && onUpdate != null) { - final Matrix4? localToGlobal = transform != null ? Matrix4.tryInvert(transform) : null; - final Offset correctedLocalPosition = _initialPosition.local + localUpdateDelta; - final Offset globalUpdateDelta = PointerEvent.transformDeltaViaPositions( - untransformedEndPosition: correctedLocalPosition, - untransformedDelta: localUpdateDelta, - transform: localToGlobal, - ); - final OffsetPair updateDelta = OffsetPair(local: localUpdateDelta, global: globalUpdateDelta); - final OffsetPair correctedPosition = _initialPosition + updateDelta; // Only adds delta for down behaviour - _checkUpdate( - sourceTimeStamp: timestamp, - delta: localUpdateDelta, - primaryDelta: _getPrimaryValueFromOffset(localUpdateDelta), - globalPosition: correctedPosition.global, - localPosition: correctedPosition.local, - ); - } - // This acceptGesture might have been called only for one pointer, instead - // of all pointers. Resolve all pointers to `accepted`. This won't cause - // infinite recursion because an accepted pointer won't be accepted again. - resolve(GestureDisposition.accepted); + if (!onlyAcceptDragOnThreshold || _hasDragThresholdBeenMet) { + _checkDrag(pointer); } } @@ -462,6 +451,7 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { case _DragState.accepted: _checkEnd(pointer); } + _hasDragThresholdBeenMet = false; _velocityTrackers.clear(); _initialButtons = null; _state = _DragState.ready; @@ -486,6 +476,50 @@ abstract class DragGestureRecognizer extends OneSequenceGestureRecognizer { } } + void _checkDrag(int pointer) { + if (_state == _DragState.accepted) { + return; + } + _state = _DragState.accepted; + final OffsetPair delta = _pendingDragOffset; + final Duration? timestamp = _lastPendingEventTimestamp; + final Matrix4? transform = _lastTransform; + final Offset localUpdateDelta; + switch (dragStartBehavior) { + case DragStartBehavior.start: + _initialPosition = _initialPosition + delta; + localUpdateDelta = Offset.zero; + case DragStartBehavior.down: + localUpdateDelta = _getDeltaForDetails(delta.local); + } + _pendingDragOffset = OffsetPair.zero; + _lastPendingEventTimestamp = null; + _lastTransform = null; + _checkStart(timestamp, pointer); + if (localUpdateDelta != Offset.zero && onUpdate != null) { + final Matrix4? localToGlobal = transform != null ? Matrix4.tryInvert(transform) : null; + final Offset correctedLocalPosition = _initialPosition.local + localUpdateDelta; + final Offset globalUpdateDelta = PointerEvent.transformDeltaViaPositions( + untransformedEndPosition: correctedLocalPosition, + untransformedDelta: localUpdateDelta, + transform: localToGlobal, + ); + final OffsetPair updateDelta = OffsetPair(local: localUpdateDelta, global: globalUpdateDelta); + final OffsetPair correctedPosition = _initialPosition + updateDelta; // Only adds delta for down behaviour + _checkUpdate( + sourceTimeStamp: timestamp, + delta: localUpdateDelta, + primaryDelta: _getPrimaryValueFromOffset(localUpdateDelta), + globalPosition: correctedPosition.global, + localPosition: correctedPosition.local, + ); + } + // This acceptGesture might have been called only for one pointer, instead + // of all pointers. Resolve all pointers to `accepted`. This won't cause + // infinite recursion because an accepted pointer won't be accepted again. + resolve(GestureDisposition.accepted); + } + void _checkStart(Duration? timestamp, int pointer) { if (onStart != null) { final DragStartDetails details = DragStartDetails( diff --git a/packages/flutter/lib/src/gestures/pointer_signal_resolver.dart b/packages/flutter/lib/src/gestures/pointer_signal_resolver.dart index 8018266551bb6..e2e4ce242736f 100644 --- a/packages/flutter/lib/src/gestures/pointer_signal_resolver.dart +++ b/packages/flutter/lib/src/gestures/pointer_signal_resolver.dart @@ -61,8 +61,21 @@ class PointerSignalResolver { /// Registers interest in handling [event]. /// - /// See the documentation for the [PointerSignalResolver] class on when and - /// how this method should be used. + /// This method may be called multiple times (typically from different parts of the + /// widget hierarchy) for the same `event`, with differenet `callback`s, as the event + /// is being dispatched across the tree. Once the dispatching is complete, the + /// [GestureBinding] calls [resolve], and the first registered callback is called. + /// + /// The `callback` is invoked with one argument, the `event`. + /// + /// Once the [register] method has been called with a particular `event`, it must + /// not be called for other `event`s until after [resolve] has been called. Only one + /// event disambiguation can be in flight at a time. In normal use this is achieved + /// by only registering callbacks for an event as it is actively being dispatched + /// (for example, in [Listener.onPointerSignal]). + /// + /// See the documentation for the [PointerSignalResolver] class for an example of + /// using this method. void register(PointerSignalEvent event, PointerSignalResolvedCallback callback) { assert(_currentEvent == null || _isSameEvent(_currentEvent!, event)); if (_firstRegisteredCallback != null) { diff --git a/packages/flutter/lib/src/gestures/resampler.dart b/packages/flutter/lib/src/gestures/resampler.dart index e2a67e0739a4f..0890097b81d2c 100644 --- a/packages/flutter/lib/src/gestures/resampler.dart +++ b/packages/flutter/lib/src/gestures/resampler.dart @@ -54,6 +54,7 @@ class PointerEventResampler { int buttons, ) { return PointerHoverEvent( + viewId: event.viewId, timeStamp: timeStamp, kind: event.kind, device: event.device, @@ -86,6 +87,7 @@ class PointerEventResampler { int buttons, ) { return PointerMoveEvent( + viewId: event.viewId, timeStamp: timeStamp, pointer: pointerIdentifier, kind: event.kind, diff --git a/packages/flutter/lib/src/material/action_chip.dart b/packages/flutter/lib/src/material/action_chip.dart index a6bbb3430b56c..725570fd56dfb 100644 --- a/packages/flutter/lib/src/material/action_chip.dart +++ b/packages/flutter/lib/src/material/action_chip.dart @@ -10,10 +10,13 @@ import 'chip_theme.dart'; import 'color_scheme.dart'; import 'colors.dart'; import 'debug.dart'; +import 'material_state.dart'; import 'text_theme.dart'; import 'theme.dart'; import 'theme_data.dart'; +enum _ChipVariant { flat, elevated } + /// A Material Design action chip. /// /// Action chips are a set of options which trigger an action related to primary @@ -47,6 +50,22 @@ import 'theme_data.dart'; /// Material Design 3. If [ThemeData.useMaterial3] is true, then [ActionChip] /// will be styled to match the Material Design 3 Assist and Suggestion chips. /// +/// ### Creating an Assist chip +/// +/// Assist chips are used to provide a quick way to perform an action. +/// To create an Action chip, set the icon property to the icon +/// that represents the action and set the label to the name of the action. +/// +/// +/// ### Creating a Suggestion chip +/// +/// Suggestion chips usually display generated suggestions for the user, +/// like a suggested response to a message. +/// +/// To create a Suggestion chip, set the label to the suggestion +/// and don't set the icon property. +// +/// /// See also: /// /// * [Chip], a chip that displays information and can be deleted. @@ -79,6 +98,40 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip this.clipBehavior = Clip.none, this.focusNode, this.autofocus = false, + this.color, + this.backgroundColor, + this.disabledColor, + this.padding, + this.visualDensity, + this.materialTapTargetSize, + this.elevation, + this.shadowColor, + this.surfaceTintColor, + this.iconTheme, + }) : assert(pressElevation == null || pressElevation >= 0.0), + assert(elevation == null || elevation >= 0.0), + _chipVariant = _ChipVariant.flat; + + /// Create an elevated chip that acts like a button. + /// + /// The [label], [onPressed], [autofocus], and [clipBehavior] arguments must + /// not be null. The [pressElevation] and [elevation] must be null or + /// non-negative. Typically, [pressElevation] is greater than [elevation]. + const ActionChip.elevated({ + super.key, + this.avatar, + required this.label, + this.labelStyle, + this.labelPadding, + this.onPressed, + this.pressElevation, + this.tooltip, + this.side, + this.shape, + this.clipBehavior = Clip.none, + this.focusNode, + this.autofocus = false, + this.color, this.backgroundColor, this.disabledColor, this.padding, @@ -89,7 +142,8 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip this.surfaceTintColor, this.iconTheme, }) : assert(pressElevation == null || pressElevation >= 0.0), - assert(elevation == null || elevation >= 0.0); + assert(elevation == null || elevation >= 0.0), + _chipVariant = _ChipVariant.elevated; @override final Widget? avatar; @@ -116,6 +170,8 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip @override final bool autofocus; @override + final MaterialStateProperty<Color?>? color; + @override final Color? backgroundColor; @override final Color? disabledColor; @@ -137,11 +193,13 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip @override bool get isEnabled => onPressed != null; + final _ChipVariant _chipVariant; + @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); final ChipThemeData? defaults = Theme.of(context).useMaterial3 - ? _ActionChipDefaultsM3(context, isEnabled) + ? _ActionChipDefaultsM3(context, isEnabled, _chipVariant) : null; return RawChip( defaultProperties: defaults, @@ -151,6 +209,7 @@ class ActionChip extends StatelessWidget implements ChipAttributes, TappableChip pressElevation: pressElevation, tooltip: tooltip, labelStyle: labelStyle, + color: color, backgroundColor: backgroundColor, side: side, shape: shape, @@ -177,49 +236,61 @@ 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_162 - class _ActionChipDefaultsM3 extends ChipThemeData { - _ActionChipDefaultsM3(this.context, this.isEnabled) + _ActionChipDefaultsM3(this.context, this.isEnabled, this._chipVariant) : super( - elevation: 0.0, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))), showCheckmark: true, ); final BuildContext context; final bool isEnabled; + final _ChipVariant _chipVariant; late final ColorScheme _colors = Theme.of(context).colorScheme; late final TextTheme _textTheme = Theme.of(context).textTheme; @override - TextStyle? get labelStyle => _textTheme.labelLarge; + double? get elevation => _chipVariant == _ChipVariant.flat + ? 0.0 + : isEnabled ? 1.0 : 0.0; @override - Color? get backgroundColor => null; + double? get pressElevation => 1.0; @override - Color? get shadowColor => Colors.transparent; + TextStyle? get labelStyle => _textTheme.labelLarge; @override - Color? get surfaceTintColor => _colors.surfaceTint; + MaterialStateProperty<Color?>? get color => + MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.disabled)) { + return _chipVariant == _ChipVariant.flat + ? null + : _colors.onSurface.withOpacity(0.12); + } + return null; + }); @override - Color? get selectedColor => null; + Color? get shadowColor => _chipVariant == _ChipVariant.flat + ? Colors.transparent + : _colors.shadow; @override - Color? get checkmarkColor => null; + Color? get surfaceTintColor => _colors.surfaceTint; @override - Color? get disabledColor => null; + Color? get checkmarkColor => null; @override Color? get deleteIconColor => null; @override - BorderSide? get side => isEnabled - ? BorderSide(color: _colors.outline) - : BorderSide(color: _colors.onSurface.withOpacity(0.12)); + BorderSide? get side => _chipVariant == _ChipVariant.flat + ? isEnabled + ? BorderSide(color: _colors.outline) + : BorderSide(color: _colors.onSurface.withOpacity(0.12)) + : const BorderSide(color: Colors.transparent); @override IconThemeData? get iconTheme => IconThemeData( diff --git a/packages/flutter/lib/src/material/adaptive_text_selection_toolbar.dart b/packages/flutter/lib/src/material/adaptive_text_selection_toolbar.dart index e39e70928d92e..0d32cfef1f2d1 100644 --- a/packages/flutter/lib/src/material/adaptive_text_selection_toolbar.dart +++ b/packages/flutter/lib/src/material/adaptive_text_selection_toolbar.dart @@ -103,6 +103,7 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget { required VoidCallback? onCut, required VoidCallback? onPaste, required VoidCallback? onSelectAll, + required VoidCallback? onLiveTextInput, required this.anchors, }) : children = null, buttonItems = EditableText.getEditableButtonItems( @@ -111,6 +112,7 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget { onCut: onCut, onPaste: onPaste, onSelectAll: onSelectAll, + onLiveTextInput: onLiveTextInput ); /// Create an instance of [AdaptiveTextSelectionToolbar] with the default @@ -213,6 +215,8 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget { return localizations.selectAllButtonLabel; case ContextMenuButtonType.delete: return localizations.deleteButtonTooltip.toUpperCase(); + case ContextMenuButtonType.liveTextInput: + return localizations.scanTextButtonLabel; case ContextMenuButtonType.custom: return ''; } @@ -242,9 +246,8 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget { switch (Theme.of(context).platform) { case TargetPlatform.iOS: return buttonItems.map((ContextMenuButtonItem buttonItem) { - return CupertinoTextSelectionToolbarButton.text( - onPressed: buttonItem.onPressed, - text: getButtonLabel(context, buttonItem), + return CupertinoTextSelectionToolbarButton.buttonItem( + buttonItem: buttonItem, ); }); case TargetPlatform.fuchsia: diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index dcddbde68f706..d0aabc3ca0d19 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -31,6 +31,8 @@ import 'theme.dart'; // late String _logoAsset; // double _myToolbarHeight = 250.0; +typedef _FlexibleConfigBuilder = _ScrollUnderFlexibleConfig Function(BuildContext); + const double _kLeadingWidth = kToolbarHeight; // So the leading button is square. const double _kMaxTitleTextScaleFactor = 1.34; // TODO(perc): Add link to Material spec when available, https://github.com/flutter/flutter/issues/58769. @@ -1259,17 +1261,15 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { final double toolbarOpacity = !pinned || isPinnedWithOpacityFade ? clampDouble(visibleToolbarHeight / (toolbarHeight ?? kToolbarHeight), 0.0, 1.0) : 1.0; - final Widget? effectiveTitle; - if (variant == _SliverAppVariant.small) { - effectiveTitle = title; - } else { - effectiveTitle = AnimatedOpacity( + final Widget? effectiveTitle = switch (variant) { + _SliverAppVariant.small => title, + _SliverAppVariant.medium || _SliverAppVariant.large => AnimatedOpacity( opacity: isScrolledUnder ? 1 : 0, duration: const Duration(milliseconds: 500), curve: const Cubic(0.2, 0.0, 0.0, 1.0), child: title, - ); - } + ), + }; final Widget appBar = FlexibleSpaceBar.createSettings( minExtent: minExtent, @@ -1971,8 +1971,7 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix effectiveFlexibleSpace = widget.flexibleSpace ?? _ScrollUnderFlexibleSpace( title: widget.title, foregroundColor: widget.foregroundColor, - variant: _ScrollUnderFlexibleVariant.medium, - primary: widget.primary, + configBuilder: _MediumScrollUnderFlexibleConfig.new, titleTextStyle: widget.titleTextStyle, bottomHeight: bottomHeight, ); @@ -1984,8 +1983,7 @@ class _SliverAppBarState extends State<SliverAppBar> with TickerProviderStateMix effectiveFlexibleSpace = widget.flexibleSpace ?? _ScrollUnderFlexibleSpace( title: widget.title, foregroundColor: widget.foregroundColor, - variant: _ScrollUnderFlexibleVariant.large, - primary: widget.primary, + configBuilder: _LargeScrollUnderFlexibleConfig.new, titleTextStyle: widget.titleTextStyle, bottomHeight: bottomHeight, ); @@ -2081,79 +2079,48 @@ class _RenderAppBarTitleBox extends RenderAligningShiftedBox { } } -enum _ScrollUnderFlexibleVariant { medium, large } - class _ScrollUnderFlexibleSpace extends StatelessWidget { const _ScrollUnderFlexibleSpace({ this.title, this.foregroundColor, - required this.variant, - this.primary = true, + required this.configBuilder, this.titleTextStyle, required this.bottomHeight, }); final Widget? title; final Color? foregroundColor; - final _ScrollUnderFlexibleVariant variant; - final bool primary; + final _FlexibleConfigBuilder configBuilder; final TextStyle? titleTextStyle; final double bottomHeight; @override Widget build(BuildContext context) { - late final ThemeData theme = Theme.of(context); late final AppBarTheme appBarTheme = AppBarTheme.of(context); - final AppBarTheme defaults = theme.useMaterial3 ? _AppBarDefaultsM3(context) : _AppBarDefaultsM2(context); + late final AppBarTheme defaults = Theme.of(context).useMaterial3 ? _AppBarDefaultsM3(context) : _AppBarDefaultsM2(context); final double textScaleFactor = math.min(MediaQuery.textScaleFactorOf(context), _kMaxTitleTextScaleFactor); // TODO(tahatesser): Add link to Material spec when available, https://github.com/flutter/flutter/issues/58769. final FlexibleSpaceBarSettings settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!; - final double topPadding = primary ? MediaQuery.viewPaddingOf(context).top : 0; - final double collapsedHeight = settings.minExtent - topPadding - bottomHeight; - final double scrollUnderHeight = settings.maxExtent - (settings.minExtent / textScaleFactor) + bottomHeight; - final _ScrollUnderFlexibleConfig config; - switch (variant) { - case _ScrollUnderFlexibleVariant.medium: - config = _MediumScrollUnderFlexibleConfig(context); - case _ScrollUnderFlexibleVariant.large: - config = _LargeScrollUnderFlexibleConfig(context); - } + final _ScrollUnderFlexibleConfig config = configBuilder(context); + assert( + config.expandedTitlePadding.isNonNegative, + 'The _ExpandedTitleWithPadding widget assumes that the expanded title padding is non-negative. ' + 'Update its implementation to handle negative padding.', + ); - late final Widget? expandedTitle; - if (title != null) { - final TextStyle? expandedTextStyle = titleTextStyle - ?? appBarTheme.titleTextStyle - ?? config.expandedTextStyle?.copyWith( - color: foregroundColor ?? appBarTheme.foregroundColor ?? defaults.foregroundColor, - ); - expandedTitle = config.expandedTextStyle != null - ? DefaultTextStyle( - style: expandedTextStyle!, - child: title!, - ) - : title; - } + final TextStyle? expandedTextStyle = titleTextStyle + ?? appBarTheme.titleTextStyle + ?? config.expandedTextStyle?.copyWith(color: foregroundColor ?? appBarTheme.foregroundColor ?? defaults.foregroundColor); - EdgeInsetsGeometry expandedTitlePadding() { - final EdgeInsets padding = config.expandedTitlePadding!.resolve(Directionality.of(context)); - if (bottomHeight > 0) { - return EdgeInsets.fromLTRB(padding.left, 0, padding.right, bottomHeight); - } - if (theme.useMaterial3) { - final TextStyle textStyle = config.expandedTextStyle!; - // Substract the bottom line height from the bottom padding. - // TODO(tahatesser): Figure out why this is done. - // https://github.com/flutter/flutter/issues/120672 - final double adjustBottomPadding = padding.bottom - - (textStyle.fontSize! * textStyle.height! - textStyle.fontSize!) / 2; - return EdgeInsets.fromLTRB( - padding.left, - 0, - padding.right, - adjustBottomPadding / textScaleFactor, - ); - } - return padding; - } + final Widget? expandedTitle = switch ((title, expandedTextStyle)) { + (null, _) => null, + (final Widget title, null) => title, + (final Widget title, final TextStyle textStyle) => DefaultTextStyle(style: textStyle, child: title), + }; + + final EdgeInsets resolvedTitlePadding = config.expandedTitlePadding.resolve(Directionality.of(context)); + final EdgeInsetsGeometry expandedTitlePadding = bottomHeight > 0 + ? resolvedTitlePadding.copyWith(bottom: 0) + : resolvedTitlePadding; // Set maximum text scale factor to [_kMaxTitleTextScaleFactor] for the // title to keep the visual hierarchy the same even with larger font @@ -2162,35 +2129,167 @@ class _ScrollUnderFlexibleSpace extends StatelessWidget { // `MediaQuery.textScaleFactorOf(context)`. return MediaQuery( data: MediaQuery.of(context).copyWith(textScaleFactor: textScaleFactor), + // This column will assume the full height of the parent Stack. child: Column( children: <Widget>[ - Padding( - padding: EdgeInsets.only(top: collapsedHeight + topPadding), - ), + Padding(padding: EdgeInsets.only(top: settings.minExtent - bottomHeight)), Flexible( child: ClipRect( - child: OverflowBox( - minHeight: scrollUnderHeight, - maxHeight: scrollUnderHeight, - alignment: Alignment.bottomLeft, - child: Container( - alignment: AlignmentDirectional.bottomStart, - padding: expandedTitlePadding(), - child: expandedTitle, - ), + child: _ExpandedTitleWithPadding( + padding: expandedTitlePadding, + maxExtent: settings.maxExtent - settings.minExtent, + child: expandedTitle, ), ), ), + // Reserve space for AppBar.bottom, which is a sibling of this widget, + // on the parent Stack. + if (bottomHeight > 0) Padding(padding: EdgeInsets.only(bottom: bottomHeight)), ], ), ); } } +// A widget that bottom-start aligns its child (the expanded title widget), and +// insets the child according to the specified padding. +// +// This widget gives the child an infinite max height constraint, and will also +// attempt to vertically limit the child's bounding box (not including the +// padding) to within the y range [0, maxExtent], to make sure the child is +// visible when the AppBar is fully expanded. +class _ExpandedTitleWithPadding extends SingleChildRenderObjectWidget { + const _ExpandedTitleWithPadding({ + required this.padding, + required this.maxExtent, + super.child, + }); + + final EdgeInsetsGeometry padding; + final double maxExtent; + + @override + _RenderExpandedTitleBox createRenderObject(BuildContext context) { + final TextDirection textDirection = Directionality.of(context); + return _RenderExpandedTitleBox( + padding.resolve(textDirection), + AlignmentDirectional.bottomStart.resolve(textDirection), + maxExtent, + null, + ); + } + + @override + void updateRenderObject(BuildContext context, _RenderExpandedTitleBox renderObject) { + final TextDirection textDirection = Directionality.of(context); + renderObject + ..padding = padding.resolve(textDirection) + ..titleAlignment = AlignmentDirectional.bottomStart.resolve(textDirection) + ..maxExtent = maxExtent; + } +} + +class _RenderExpandedTitleBox extends RenderShiftedBox { + _RenderExpandedTitleBox(this._padding, this._titleAlignment, this._maxExtent, super.child); + + EdgeInsets get padding => _padding; + EdgeInsets _padding; + set padding(EdgeInsets value) { + if (_padding == value) { + return; + } + assert(value.isNonNegative); + _padding = value; + markNeedsLayout(); + } + + Alignment get titleAlignment => _titleAlignment; + Alignment _titleAlignment; + set titleAlignment(Alignment value) { + if (_titleAlignment == value) { + return; + } + _titleAlignment = value; + markNeedsLayout(); + } + + double get maxExtent => _maxExtent; + double _maxExtent; + set maxExtent(double value) { + if (_maxExtent == value) { + return; + } + _maxExtent = value; + markNeedsLayout(); + } + + @override + double computeMaxIntrinsicHeight(double width) { + final RenderBox? child = this.child; + return child == null ? 0.0 : child.getMaxIntrinsicHeight(math.max(0, width - padding.horizontal)) + padding.vertical; + } + + @override + double computeMaxIntrinsicWidth(double height) { + final RenderBox? child = this.child; + return child == null ? 0.0 : child.getMaxIntrinsicWidth(double.infinity) + padding.horizontal; + } + + @override + double computeMinIntrinsicHeight(double width) { + final RenderBox? child = this.child; + return child == null ? 0.0 : child.getMinIntrinsicHeight(math.max(0, width - padding.horizontal)) + padding.vertical; + } + + @override + double computeMinIntrinsicWidth(double height) { + final RenderBox? child = this.child; + return child == null ? 0.0 : child.getMinIntrinsicWidth(double.infinity) + padding.horizontal; + } + + Size _computeSize(BoxConstraints constraints, ChildLayouter layoutChild) { + final RenderBox? child = this.child; + if (child == null) { + return Size.zero; + } + layoutChild(child, constraints.widthConstraints().deflate(padding)); + return constraints.biggest; + } + + @override + Size computeDryLayout(BoxConstraints constraints) => _computeSize(constraints, ChildLayoutHelper.dryLayoutChild); + + @override + void performLayout() { + final RenderBox? child = this.child; + if (child == null) { + this.size = constraints.smallest; + return; + } + final Size size = this.size = _computeSize(constraints, ChildLayoutHelper.layoutChild); + final Size childSize = child.size; + + assert(padding.isNonNegative); + assert(titleAlignment.y == 1.0); + // yAdjustement is the minimum additional y offset to shift the child in + // the visible vertical space when AppBar is fully expanded. The goal is to + // prevent the expanded title from being clipped when the expanded title + // widget + the bottom padding is too tall to fit in the flexible space (the + // top padding is basically ignored since the expanded title is + // bottom-aligned). + final double yAdjustement = clampDouble(childSize.height + padding.bottom - maxExtent, 0, padding.bottom); + final double offsetY = size.height - childSize.height - padding.bottom + yAdjustement; + final double offsetX = (titleAlignment.x + 1) / 2 * (size.width - padding.horizontal - childSize.width) + padding.left; + + final BoxParentData childParentData = child.parentData! as BoxParentData; + childParentData.offset = Offset(offsetX, offsetY); + } +} + mixin _ScrollUnderFlexibleConfig { TextStyle? get collapsedTextStyle; TextStyle? get expandedTextStyle; - EdgeInsetsGeometry? get expandedTitlePadding; + EdgeInsetsGeometry get expandedTitlePadding; } // Hand coded defaults based on Material Design 2. @@ -2230,8 +2329,6 @@ class _AppBarDefaultsM2 extends AppBarTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _AppBarDefaultsM3 extends AppBarTheme { _AppBarDefaultsM3(this.context) : super( @@ -2298,7 +2395,7 @@ class _MediumScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig { _textTheme.headlineSmall?.apply(color: _colors.onSurface); @override - EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20); + EdgeInsetsGeometry get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 20); } class _LargeScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig { @@ -2321,7 +2418,7 @@ class _LargeScrollUnderFlexibleConfig with _ScrollUnderFlexibleConfig { _textTheme.headlineMedium?.apply(color: _colors.onSurface); @override - EdgeInsetsGeometry? get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28); + EdgeInsetsGeometry get expandedTitlePadding => const EdgeInsets.fromLTRB(16, 0, 16, 28); } // END GENERATED TOKEN PROPERTIES - AppBar diff --git a/packages/flutter/lib/src/material/badge.dart b/packages/flutter/lib/src/material/badge.dart index 2aa30046aa762..aa2ec80a4489b 100644 --- a/packages/flutter/lib/src/material/badge.dart +++ b/packages/flutter/lib/src/material/badge.dart @@ -287,8 +287,6 @@ class _RenderBadge extends RenderAligningShiftedBox { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _BadgeDefaultsM3 extends BadgeThemeData { _BadgeDefaultsM3(this.context) : super( smallSize: 6.0, diff --git a/packages/flutter/lib/src/material/banner.dart b/packages/flutter/lib/src/material/banner.dart index 62f1ceefc6a5a..b3a47a56a793e 100644 --- a/packages/flutter/lib/src/material/banner.dart +++ b/packages/flutter/lib/src/material/banner.dart @@ -464,8 +464,6 @@ class _BannerDefaultsM2 extends MaterialBannerThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _BannerDefaultsM3 extends MaterialBannerThemeData { _BannerDefaultsM3(this.context) : super(elevation: 1.0); diff --git a/packages/flutter/lib/src/material/bottom_app_bar.dart b/packages/flutter/lib/src/material/bottom_app_bar.dart index 4138851634e42..430c717e5c53f 100644 --- a/packages/flutter/lib/src/material/bottom_app_bar.dart +++ b/packages/flutter/lib/src/material/bottom_app_bar.dart @@ -296,8 +296,6 @@ class _BottomAppBarDefaultsM2 extends BottomAppBarTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _BottomAppBarDefaultsM3 extends BottomAppBarTheme { _BottomAppBarDefaultsM3(this.context) : super( diff --git a/packages/flutter/lib/src/material/bottom_navigation_bar.dart b/packages/flutter/lib/src/material/bottom_navigation_bar.dart index ebcb53638a524..f85a58e8e9c32 100644 --- a/packages/flutter/lib/src/material/bottom_navigation_bar.dart +++ b/packages/flutter/lib/src/material/bottom_navigation_bar.dart @@ -100,15 +100,36 @@ enum BottomNavigationBarLandscapeLayout { /// /// ## Updating to [NavigationBar] /// -/// There is an updated version of this component, [NavigationBar], -/// that's preferred for new applications and applications that are -/// configured for Material 3 (see [ThemeData.useMaterial3]). The -/// [NavigationBar] widget's visuals are a little bit different, see -/// the Material 3 spec at +/// The [NavigationBar] widget's visuals +/// are a little bit different, see the Material 3 spec at /// <https://m3.material.io/components/navigation-bar/overview> for -/// more details. The API is similar, destinations are defined with -/// [NavigationDestination]s and [NavigationBar.onDestinationSelected] -/// is called when a destination is tapped. +/// more details. +/// +/// The [NavigationBar] widget's API is also slightly different. +/// To update from [BottomNavigationBar] to [NavigationBar], you will +/// need to make the following changes. +/// +/// 1. Instead of using [BottomNavigationBar.items], which +/// takes a list of [BottomNavigationBarItem]s, use +/// [NavigationBar.destinations], which takes a list of widgets. +/// Usually, you use a list of [NavigationDestination] widgets. +/// Just like [BottomNavigationBarItem]s, [NavigationDestination]s +/// have a label and icon field. +/// +/// 2. Instead of using [BottomNavigationBar.onTap], +/// use [NavigationBar.onDestinationSelected], which is also +/// a callback that is called when the user taps on a +/// navigation bar item. +/// +/// 3. Instead of using [BottomNavigationBar.currentIndex], +/// use [NavigationBar.selectedIndex], which is also an integer +/// that represents the index of the selected destination. +/// +/// 4. You may also need to make changes to the styling of the +/// [NavigationBar], see the properties in the [NavigationBar] +/// constructor for more details. +/// +/// ## Using [BottomNavigationBar] /// /// {@tool dartpad} /// This example shows a [BottomNavigationBar] as it is used within a [Scaffold] @@ -122,6 +143,13 @@ enum BottomNavigationBarLandscapeLayout { /// {@end-tool} /// /// {@tool dartpad} +/// This example shows how you would migrate the above [BottomNavigationBar] +/// to the new [NavigationBar]. +/// +/// ** See code in examples/api/lib/material/navigation_bar/navigation_bar.0.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} /// This example shows a [BottomNavigationBar] as it is used within a [Scaffold] /// widget. The [BottomNavigationBar] has four [BottomNavigationBarItem] /// widgets, which means it defaults to [BottomNavigationBarType.shifting], and diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart index 7177a5fc06452..c59afd7889124 100644 --- a/packages/flutter/lib/src/material/bottom_sheet.dart +++ b/packages/flutter/lib/src/material/bottom_sheet.dart @@ -357,15 +357,14 @@ class _BottomSheetState extends State<BottomSheet> { dragHandleColor: widget.dragHandleColor, dragHandleSize: widget.dragHandleSize, ); - // Only add [GestureDetector] to the drag handle when the rest of the + // Only add [_BottomSheetGestureDetector] to the drag handle when the rest of the // bottom sheet is not draggable. If the whole bottom sheet is draggable, // no need to add it. if (!widget.enableDrag) { - dragHandle = GestureDetector( + dragHandle = _BottomSheetGestureDetector( onVerticalDragStart: _handleDragStart, onVerticalDragUpdate: _handleDragUpdate, onVerticalDragEnd: _handleDragEnd, - excludeFromSemantics: true, child: dragHandle, ); } @@ -407,11 +406,10 @@ class _BottomSheetState extends State<BottomSheet> { ); } - return !widget.enableDrag ? bottomSheet : GestureDetector( + return !widget.enableDrag ? bottomSheet : _BottomSheetGestureDetector( onVerticalDragStart: _handleDragStart, onVerticalDragUpdate: _handleDragUpdate, onVerticalDragEnd: _handleDragEnd, - excludeFromSemantics: true, child: bottomSheet, ); } @@ -1300,7 +1298,39 @@ PersistentBottomSheetController<T> showBottomSheet<T>({ ); } +class _BottomSheetGestureDetector extends StatelessWidget { + const _BottomSheetGestureDetector({ + required this.child, + required this.onVerticalDragStart, + required this.onVerticalDragUpdate, + required this.onVerticalDragEnd, + }); + + final Widget child; + final GestureDragStartCallback onVerticalDragStart; + final GestureDragUpdateCallback onVerticalDragUpdate; + final GestureDragEndCallback onVerticalDragEnd; + @override + Widget build(BuildContext context) { + return RawGestureDetector( + excludeFromSemantics: true, + gestures: <Type, GestureRecognizerFactory<GestureRecognizer>>{ + VerticalDragGestureRecognizer : GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>( + () => VerticalDragGestureRecognizer(debugOwner: this), + (VerticalDragGestureRecognizer instance) { + instance + ..onStart = onVerticalDragStart + ..onUpdate = onVerticalDragUpdate + ..onEnd = onVerticalDragEnd + ..onlyAcceptDragOnThreshold = true; + }, + ), + }, + child: child, + ); + } +} // BEGIN GENERATED TOKEN PROPERTIES - BottomSheet @@ -1309,8 +1339,6 @@ PersistentBottomSheetController<T> showBottomSheet<T>({ // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _BottomSheetDefaultsM3 extends BottomSheetThemeData { _BottomSheetDefaultsM3(this.context) : super( diff --git a/packages/flutter/lib/src/material/button_bar.dart b/packages/flutter/lib/src/material/button_bar.dart index 765563f55fe66..e3e36ec80f59c 100644 --- a/packages/flutter/lib/src/material/button_bar.dart +++ b/packages/flutter/lib/src/material/button_bar.dart @@ -12,6 +12,37 @@ import 'dialog.dart'; /// An end-aligned row of buttons, laying out into a column if there is not /// enough horizontal space. /// +/// ## Updating to [OverflowBar] +/// +/// [ButtonBar] has been replace by a more efficient widget, [OverflowBar]. +/// +/// ```dart +/// // Before +/// ButtonBar( +/// alignment: MainAxisAlignment.spaceEvenly, +/// children: <Widget>[ +/// TextButton( child: const Text('Button 1'), onPressed: () {}), +/// TextButton( child: const Text('Button 2'), onPressed: () {}), +/// TextButton( child: const Text('Button 3'), onPressed: () {}), +/// ], +/// ); +/// ``` +/// ```dart +/// // After +/// OverflowBar( +/// alignment: MainAxisAlignment.spaceEvenly, +/// children: <Widget>[ +/// TextButton( child: const Text('Button 1'), onPressed: () {}), +/// TextButton( child: const Text('Button 2'), onPressed: () {}), +/// TextButton( child: const Text('Button 3'), onPressed: () {}), +/// ], +/// ); +/// ``` +/// +/// See the [OverflowBar] documentation for more details. +/// +/// ## Using [ButtonBar] +/// /// Places the buttons horizontally according to the [buttonPadding]. The /// children are laid out in a [Row] with [MainAxisAlignment.end]. When the /// [Directionality] is [TextDirection.ltr], the button bar's children are diff --git a/packages/flutter/lib/src/material/button_style_button.dart b/packages/flutter/lib/src/material/button_style_button.dart index f35d10bf48bee..9155e49ef69a4 100644 --- a/packages/flutter/lib/src/material/button_style_button.dart +++ b/packages/flutter/lib/src/material/button_style_button.dart @@ -42,6 +42,7 @@ abstract class ButtonStyleButton extends StatefulWidget { required this.autofocus, required this.clipBehavior, this.statesController, + this.isSemanticButton = true, required this.child, }); @@ -100,6 +101,15 @@ abstract class ButtonStyleButton extends StatefulWidget { /// {@macro flutter.material.inkwell.statesController} final MaterialStatesController? statesController; + /// Determine whether this subtree represents a button. + /// + /// If this is null, the screen reader will not announce "button" when this + /// is focused. This is useful for [MenuItemButton] and [SubmenuButton] when we + /// traverse the menu system. + /// + /// Defaults to true. + final bool? isSemanticButton; + /// Typically the button's label. /// /// {@macro flutter.widgets.ProxyWidget.child} @@ -425,7 +435,7 @@ class _ButtonStyleState extends State<ButtonStyleButton> with TickerProviderStat return Semantics( container: true, - button: true, + button: widget.isSemanticButton, enabled: widget.enabled, child: _InputPadding( minSize: minSize, diff --git a/packages/flutter/lib/src/material/calendar_date_picker.dart b/packages/flutter/lib/src/material/calendar_date_picker.dart index 00322907d09bb..641ad3ba5e124 100644 --- a/packages/flutter/lib/src/material/calendar_date_picker.dart +++ b/packages/flutter/lib/src/material/calendar_date_picker.dart @@ -1013,6 +1013,8 @@ class _DayPickerState extends State<_DayPicker> { // for the day of month. To do that we prepend day of month to the // formatted full date. label: '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}$semanticLabelSuffix', + // Set button to true to make the date selectable. + button: true, selected: isSelectedDay, excludeSemantics: true, child: dayWidget, diff --git a/packages/flutter/lib/src/material/card.dart b/packages/flutter/lib/src/material/card.dart index 7013d7486cfda..a39ff240b08d0 100644 --- a/packages/flutter/lib/src/material/card.dart +++ b/packages/flutter/lib/src/material/card.dart @@ -215,8 +215,6 @@ class _CardDefaultsM2 extends CardTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _CardDefaultsM3 extends CardTheme { _CardDefaultsM3(this.context) : super( diff --git a/packages/flutter/lib/src/material/checkbox.dart b/packages/flutter/lib/src/material/checkbox.dart index 95facb1b31d99..bfead13fe1d83 100644 --- a/packages/flutter/lib/src/material/checkbox.dart +++ b/packages/flutter/lib/src/material/checkbox.dart @@ -895,8 +895,6 @@ class _CheckboxDefaultsM2 extends CheckboxThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _CheckboxDefaultsM3 extends CheckboxThemeData { _CheckboxDefaultsM3(BuildContext context) : _theme = Theme.of(context), diff --git a/packages/flutter/lib/src/material/chip.dart b/packages/flutter/lib/src/material/chip.dart index f6b3510a17022..eb457147e068d 100644 --- a/packages/flutter/lib/src/material/chip.dart +++ b/packages/flutter/lib/src/material/chip.dart @@ -137,6 +137,13 @@ abstract interface class ChipAttributes { /// {@macro flutter.widgets.Focus.autofocus} bool get autofocus; + /// The color that fills the chip, in all [MaterialState]s. + /// + /// Resolves in the following states: + /// * [MaterialState.selected]. + /// * [MaterialState.disabled]. + MaterialStateProperty<Color?>? get color; + /// Color to be used for the unselected, enabled chip's background. /// /// The default is light grey. @@ -561,6 +568,7 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri this.clipBehavior = Clip.none, this.focusNode, this.autofocus = false, + this.color, this.backgroundColor, this.padding, this.visualDensity, @@ -595,6 +603,8 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri @override final bool autofocus; @override + final MaterialStateProperty<Color?>? color; + @override final Color? backgroundColor; @override final EdgeInsetsGeometry? padding; @@ -644,6 +654,7 @@ class Chip extends StatelessWidget implements ChipAttributes, DeletableChipAttri clipBehavior: clipBehavior, focusNode: focusNode, autofocus: autofocus, + color: color, backgroundColor: backgroundColor, padding: padding, visualDensity: visualDensity, @@ -729,6 +740,7 @@ class RawChip extends StatefulWidget this.clipBehavior = Clip.none, this.focusNode, this.autofocus = false, + this.color, this.backgroundColor, this.materialTapTargetSize, this.elevation, @@ -798,6 +810,8 @@ class RawChip extends StatefulWidget @override final bool autofocus; @override + final MaterialStateProperty<Color?>? color; + @override final Color? backgroundColor; @override final EdgeInsetsGeometry? padding; @@ -987,23 +1001,47 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid return resolvedShape.copyWith(side: resolvedSide); } + Color? resolveColor({ + MaterialStateProperty<Color?>? color, + Color? selectedColor, + Color? backgroundColor, + Color? disabledColor, + MaterialStateProperty<Color?>? defaultColor, + }) { + return _IndividualOverrides( + color: color, + selectedColor: selectedColor, + backgroundColor: backgroundColor, + disabledColor: disabledColor, + ).resolve(materialStates) ?? defaultColor?.resolve(materialStates); + } + /// Picks between three different colors, depending upon the state of two /// different animations. Color? _getBackgroundColor(ThemeData theme, ChipThemeData chipTheme, ChipThemeData chipDefaults) { if (theme.useMaterial3) { + final Color? disabledColor = resolveColor( + color: widget.color ?? chipTheme.color, + disabledColor: widget.disabledColor ?? chipTheme.disabledColor, + defaultColor: chipDefaults.color, + ); + final Color? backgroundColor = resolveColor( + color: widget.color ?? chipTheme.color, + backgroundColor: widget.backgroundColor ?? chipTheme.backgroundColor, + defaultColor: chipDefaults.color, + ); + final Color? selectedColor = resolveColor( + color: widget.color ?? chipTheme.color, + selectedColor: widget.selectedColor ?? chipTheme.selectedColor, + defaultColor: chipDefaults.color, + ); final ColorTween backgroundTween = ColorTween( - begin: widget.disabledColor - ?? chipTheme.disabledColor - ?? chipDefaults.disabledColor, - end: widget.backgroundColor - ?? chipTheme.backgroundColor - ?? chipDefaults.backgroundColor, + begin: disabledColor, + end: backgroundColor, ); final ColorTween selectTween = ColorTween( begin: backgroundTween.evaluate(enableController), - end: widget.selectedColor - ?? chipTheme.selectedColor - ?? chipDefaults.selectedColor, + end: selectedColor, ); return selectTween.evaluate(selectionFade); } else { @@ -1295,6 +1333,37 @@ class _RawChipState extends State<RawChip> with MaterialStateMixin, TickerProvid } } +class _IndividualOverrides extends MaterialStateProperty<Color?> { + _IndividualOverrides({ + this.color, + this.backgroundColor, + this.selectedColor, + this.disabledColor, + }); + + final MaterialStateProperty<Color?>? color; + final Color? backgroundColor; + final Color? selectedColor; + final Color? disabledColor; + + @override + Color? resolve(Set<MaterialState> states) { + if (color != null) { + return color!.resolve(states); + } + if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) { + return selectedColor; + } + if (states.contains(MaterialState.disabled)) { + return disabledColor; + } + if (states.contains(MaterialState.selected)) { + return selectedColor; + } + return backgroundColor; + } +} + /// Redirects the [buttonRect.dy] passed to [RenderBox.hitTest] to the vertical /// center of the widget. /// @@ -2159,8 +2228,6 @@ bool _hitIsOnDeleteIcon({ // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _ChipDefaultsM3 extends ChipThemeData { _ChipDefaultsM3(this.context, this.isEnabled) : super( @@ -2178,7 +2245,7 @@ class _ChipDefaultsM3 extends ChipThemeData { TextStyle? get labelStyle => _textTheme.labelLarge; @override - Color? get backgroundColor => null; + MaterialStateProperty<Color?>? get color => null; // Subclasses override this getter @override Color? get shadowColor => Colors.transparent; @@ -2186,15 +2253,9 @@ class _ChipDefaultsM3 extends ChipThemeData { @override Color? get surfaceTintColor => _colors.surfaceTint; - @override - Color? get selectedColor => null; - @override Color? get checkmarkColor => null; - @override - Color? get disabledColor => null; - @override Color? get deleteIconColor => null; @@ -2215,7 +2276,7 @@ class _ChipDefaultsM3 extends ChipThemeData { EdgeInsetsGeometry? get padding => const EdgeInsets.all(8.0); /// The chip at text scale 1 starts with 8px on each side and as text scaling - /// gets closer to 2 the label padding is linearly interpolated from 8px to 4px. + /// gets closer to 2, the label padding is linearly interpolated from 8px to 4px. /// Once the widget has a text scaling of 2 or higher than the label padding /// remains 4px. @override diff --git a/packages/flutter/lib/src/material/chip_theme.dart b/packages/flutter/lib/src/material/chip_theme.dart index 32213d590839a..4c9a052d9e039 100644 --- a/packages/flutter/lib/src/material/chip_theme.dart +++ b/packages/flutter/lib/src/material/chip_theme.dart @@ -9,6 +9,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'colors.dart'; +import 'material_state.dart'; import 'theme.dart'; /// Applies a chip theme to descendant [RawChip]-based widgets, like [Chip], @@ -178,6 +179,7 @@ class ChipThemeData with Diagnosticable { /// This will rarely be used directly. It is used by [lerp] to /// create intermediate themes based on two themes. const ChipThemeData({ + this.color, this.backgroundColor, this.deleteIconColor, this.disabledColor, @@ -268,6 +270,12 @@ class ChipThemeData with Diagnosticable { ); } + /// Overrides the default for [ChipAttributes.color]. + /// + /// This property applies to [ActionChip], [Chip], [ChoiceChip], + /// [FilterChip], [InputChip], [RawChip]. + final MaterialStateProperty<Color?>? color; + /// Overrides the default for [ChipAttributes.backgroundColor] /// which is used for unselected, enabled chip backgrounds. /// @@ -433,6 +441,7 @@ class ChipThemeData with Diagnosticable { /// Creates a copy of this object but with the given fields replaced with the /// new values. ChipThemeData copyWith({ + MaterialStateProperty<Color?>? color, Color? backgroundColor, Color? deleteIconColor, Color? disabledColor, @@ -455,6 +464,7 @@ class ChipThemeData with Diagnosticable { IconThemeData? iconTheme, }) { return ChipThemeData( + color: color ?? this.color, backgroundColor: backgroundColor ?? this.backgroundColor, deleteIconColor: deleteIconColor ?? this.deleteIconColor, disabledColor: disabledColor ?? this.disabledColor, @@ -488,6 +498,7 @@ class ChipThemeData with Diagnosticable { return a; } return ChipThemeData( + color: MaterialStateProperty.lerp<Color?>(a?.color, b?.color, t, Color.lerp), backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t), deleteIconColor: Color.lerp(a?.deleteIconColor, b?.deleteIconColor, t), disabledColor: Color.lerp(a?.disabledColor, b?.disabledColor, t), @@ -537,6 +548,7 @@ class ChipThemeData with Diagnosticable { @override int get hashCode => Object.hashAll(<Object?>[ + color, backgroundColor, deleteIconColor, disabledColor, @@ -568,6 +580,7 @@ class ChipThemeData with Diagnosticable { return false; } return other is ChipThemeData + && other.color == color && other.backgroundColor == backgroundColor && other.deleteIconColor == deleteIconColor && other.disabledColor == disabledColor @@ -593,6 +606,7 @@ class ChipThemeData with Diagnosticable { @override void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); + properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('color', color, defaultValue: null)); properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null)); properties.add(ColorProperty('deleteIconColor', deleteIconColor, defaultValue: null)); properties.add(ColorProperty('disabledColor', disabledColor, defaultValue: null)); diff --git a/packages/flutter/lib/src/material/choice_chip.dart b/packages/flutter/lib/src/material/choice_chip.dart index f8602ea9d748d..7b05db36229a9 100644 --- a/packages/flutter/lib/src/material/choice_chip.dart +++ b/packages/flutter/lib/src/material/choice_chip.dart @@ -10,10 +10,13 @@ import 'chip_theme.dart'; import 'color_scheme.dart'; import 'colors.dart'; import 'debug.dart'; +import 'material_state.dart'; import 'text_theme.dart'; import 'theme.dart'; import 'theme_data.dart'; +enum _ChipVariant { flat, elevated } + /// A Material Design choice chip. /// /// [ChoiceChip]s represent a single choice from a set. Choice chips contain @@ -76,6 +79,46 @@ class ChoiceChip extends StatelessWidget this.clipBehavior = Clip.none, this.focusNode, this.autofocus = false, + this.color, + this.backgroundColor, + this.padding, + this.visualDensity, + this.materialTapTargetSize, + this.elevation, + this.shadowColor, + this.surfaceTintColor, + this.iconTheme, + this.selectedShadowColor, + this.showCheckmark, + this.checkmarkColor, + this.avatarBorder = const CircleBorder(), + }) : assert(pressElevation == null || pressElevation >= 0.0), + assert(elevation == null || elevation >= 0.0), + _chipVariant = _ChipVariant.flat; + + /// Create an elevated chip that acts like a radio button. + /// + /// The [label], [selected], [autofocus], and [clipBehavior] arguments must + /// not be null. The [pressElevation] and [elevation] must be null or + /// non-negative. Typically, [pressElevation] is greater than [elevation]. + const ChoiceChip.elevated({ + super.key, + this.avatar, + required this.label, + this.labelStyle, + this.labelPadding, + this.onSelected, + this.pressElevation, + required this.selected, + this.selectedColor, + this.disabledColor, + this.tooltip, + this.side, + this.shape, + this.clipBehavior = Clip.none, + this.focusNode, + this.autofocus = false, + this.color, this.backgroundColor, this.padding, this.visualDensity, @@ -89,7 +132,8 @@ class ChoiceChip extends StatelessWidget this.checkmarkColor, this.avatarBorder = const CircleBorder(), }) : assert(pressElevation == null || pressElevation >= 0.0), - assert(elevation == null || elevation >= 0.0); + assert(elevation == null || elevation >= 0.0), + _chipVariant = _ChipVariant.elevated; @override final Widget? avatar; @@ -122,6 +166,8 @@ class ChoiceChip extends StatelessWidget @override final bool autofocus; @override + final MaterialStateProperty<Color?>? color; + @override final Color? backgroundColor; @override final EdgeInsetsGeometry? padding; @@ -149,12 +195,14 @@ class ChoiceChip extends StatelessWidget @override bool get isEnabled => onSelected != null; + final _ChipVariant _chipVariant; + @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); final ChipThemeData chipTheme = ChipTheme.of(context); final ChipThemeData? defaults = Theme.of(context).useMaterial3 - ? _ChoiceChipDefaultsM3(context, isEnabled, selected) + ? _ChoiceChipDefaultsM3(context, isEnabled, selected, _chipVariant) : null; return RawChip( defaultProperties: defaults, @@ -175,6 +223,7 @@ class ChoiceChip extends StatelessWidget autofocus: autofocus, disabledColor: disabledColor, selectedColor: selectedColor ?? chipTheme.secondarySelectedColor, + color: color, backgroundColor: backgroundColor, padding: padding, visualDensity: visualDensity, @@ -197,12 +246,13 @@ class ChoiceChip extends StatelessWidget // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _ChoiceChipDefaultsM3 extends ChipThemeData { - _ChoiceChipDefaultsM3(this.context, this.isEnabled, this.isSelected) - : super( - elevation: 0.0, + _ChoiceChipDefaultsM3( + this.context, + this.isEnabled, + this.isSelected, + this._chipVariant, + ) : super( shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))), showCheckmark: true, ); @@ -210,39 +260,60 @@ class _ChoiceChipDefaultsM3 extends ChipThemeData { final BuildContext context; final bool isEnabled; final bool isSelected; + final _ChipVariant _chipVariant; late final ColorScheme _colors = Theme.of(context).colorScheme; late final TextTheme _textTheme = Theme.of(context).textTheme; @override - TextStyle? get labelStyle => _textTheme.labelLarge; + double? get elevation => _chipVariant == _ChipVariant.flat + ? 0.0 + : isEnabled ? 1.0 : 0.0; @override - Color? get backgroundColor => null; + double? get pressElevation => 1.0; @override - Color? get shadowColor => Colors.transparent; + TextStyle? get labelStyle => _textTheme.labelLarge; @override - Color? get surfaceTintColor => _colors.surfaceTint; + MaterialStateProperty<Color?>? get color => + MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) { + return _chipVariant == _ChipVariant.flat + ? _colors.onSurface.withOpacity(0.12) + : _colors.onSurface.withOpacity(0.12); + } + if (states.contains(MaterialState.disabled)) { + return _chipVariant == _ChipVariant.flat + ? null + : _colors.onSurface.withOpacity(0.12); + } + if (states.contains(MaterialState.selected)) { + return _chipVariant == _ChipVariant.flat + ? _colors.secondaryContainer + : _colors.secondaryContainer; + } + return _chipVariant == _ChipVariant.flat + ? null + : null; + }); @override - Color? get selectedColor => isEnabled - ? _colors.secondaryContainer - : _colors.onSurface.withOpacity(0.12); + Color? get shadowColor => _chipVariant == _ChipVariant.flat + ? Colors.transparent + : _colors.shadow; @override - Color? get checkmarkColor => _colors.onSecondaryContainer; + Color? get surfaceTintColor => _colors.surfaceTint; @override - Color? get disabledColor => isSelected - ? _colors.onSurface.withOpacity(0.12) - : null; + Color? get checkmarkColor => _colors.onSecondaryContainer; @override Color? get deleteIconColor => _colors.onSecondaryContainer; @override - BorderSide? get side => !isSelected + BorderSide? get side => _chipVariant == _ChipVariant.flat && !isSelected ? isEnabled ? BorderSide(color: _colors.outline) : BorderSide(color: _colors.onSurface.withOpacity(0.12)) diff --git a/packages/flutter/lib/src/material/color_scheme.dart b/packages/flutter/lib/src/material/color_scheme.dart index 709c8e3363853..3296678c532ce 100644 --- a/packages/flutter/lib/src/material/color_scheme.dart +++ b/packages/flutter/lib/src/material/color_scheme.dart @@ -18,6 +18,10 @@ import 'theme_data.dart'; /// that can be used to configure the color properties of most components. /// {@endtemplate} /// +/// ### Colors in Material 3 +/// +/// {@macro flutter.material.colors.colorRoles} +/// /// The main accent color groups in the scheme are [primary], [secondary], /// and [tertiary]. /// @@ -44,10 +48,10 @@ import 'theme_data.dart'; /// contrast ratio with their matching colors of at least 4.5:1 in order to /// be readable. /// -/// The [Theme] has a color scheme, [ThemeData.colorScheme], which can either be -/// passed in as a parameter to the constructor or by using 'brightness' and -/// 'colorSchemeSeed' parameters (which are used to generate a scheme with -/// [ColorScheme.fromSeed]). +/// ### Setting Colors in Flutter +/// +///{@macro flutter.material.colors.settingColors} +// @immutable class ColorScheme with Diagnosticable { /// Create a ColorScheme instance from the given colors. @@ -894,29 +898,37 @@ class ColorScheme with Diagnosticable { /// Generate a [ColorScheme] derived from the given `imageProvider`. /// /// Material Color Utilities extracts the dominant color from the - /// supplied [ImageProvider]. Using this color, a set of tonal palettes are - /// constructed. These tonal palettes are based on the Material 3 Color - /// system and provide all the needed colors for a [ColorScheme]. These - /// colors are designed to work well together and meet contrast - /// requirements for accessibility. + /// supplied [ImageProvider]. Using this color, a [ColorScheme] is generated + /// with harmnonious colors that meet contrast requirements for accessibility. /// /// If any of the optional color parameters are non-null, they will be /// used in place of the generated colors for that field in the resulting - /// color scheme. This allows apps to override specific colors for their + /// [ColorScheme]. This allows apps to override specific colors for their /// needs. /// /// Given the nature of the algorithm, the most dominant color of the - /// `imageProvider` may not wind up as one of the ColorScheme colors. + /// `imageProvider` may not wind up as one of the [ColorScheme] colors. /// /// The provided image will be scaled down to a maximum size of 112x112 pixels /// during color extraction. /// + /// {@tool dartpad} + /// This sample shows how to use [ColorScheme.fromImageProvider] to create + /// content-based dynamic color schemes. + /// + /// ** See code in examples/api/lib/material/color_scheme/dynamic_content_color.0.dart ** + /// {@end-tool} + /// /// See also: /// + /// * [M3 Guidelines: Dynamic color from content](https://m3.material.io/styles/color/dynamic-color/user-generated-color#8af550b9-a19e-4e9f-bb0a-7f611fed5d0f) + /// * <https://pub.dev/packages/dynamic_color>, a package to create + /// [ColorScheme]s based on a platform's implementation of dynamic color. /// * <https://m3.material.io/styles/color/the-color-system/color-roles>, the /// Material 3 Color system specification. /// * <https://pub.dev/packages/material_color_utilities>, the package - /// used to generate the base color and tonal palettes needed for the scheme. + /// used to algorightmically determine the dominant color and to generate + /// the [ColorScheme]. static Future<ColorScheme> fromImageProvider({ required ImageProvider provider, Brightness brightness = Brightness.light, diff --git a/packages/flutter/lib/src/material/colors.dart b/packages/flutter/lib/src/material/colors.dart index 21c8da4c53c8f..2fadbd844ed66 100644 --- a/packages/flutter/lib/src/material/colors.dart +++ b/packages/flutter/lib/src/material/colors.dart @@ -10,6 +10,83 @@ import 'package:flutter/painting.dart'; /// darker the color. There are 10 valid indices: 50, 100, 200, ..., 900. /// The value of this color should the same the value of index 500 and [shade500]. /// +/// ## Updating to [ColorScheme] +/// +/// The [ColorScheme] is preferred for +/// representing colors in applications that are configured +/// for Material 3 (see [ThemeData.useMaterial3]). +/// For more information on colors in Material 3 see +/// the spec at <https://m3.material.io/styles/color/the-color-system>. +/// +///{@template flutter.material.colors.colorRoles} +/// In Material 3, colors are represented using color roles and +/// corresponding tokens. Each property in the [ColorScheme] class +/// represents one color role as defined in the spec above. +/// {@endtemplate} +/// +/// ### Material 3 Colors in Flutter +/// +///{@template flutter.material.colors.settingColors} +/// Flutter's Material widgets can be assigned colors at the widget level +/// using widget properties, +/// or at the app level using theme classes. +/// +/// For example, you can set the background of the [AppBar] by +/// setting the [AppBar.backgroundColor] to a specific [Color] value. +/// +/// To globally set the AppBar background color for your app, you +/// can set the [ThemeData.appBarTheme] property for your [MaterialApp] +/// using the [ThemeData] class. You can also override +/// the default appearance of all the [AppBar]s in a widget subtree by +/// placing the [AppBarTheme] at the root of the subtree. +/// +/// Alternatively, you can set the [ThemeData.colorScheme] property +/// to a custom [ColorScheme]. This creates a unified [ColorScheme] to be +/// used across the app. The [AppBar.backgroundColor] uses the +/// [ColorScheme.surface] by default. +///{@endtemplate} +/// +/// ### Migrating from [MaterialColor] to [ColorScheme] +/// +/// In most cases, there are new properties in Flutter widgets that +/// accept a [ColorScheme] instead of a [MaterialColor]. +/// +/// For example, you may have previously constructed a [ThemeData] +/// using a primarySwatch: +/// +/// ```dart +/// ThemeData( +/// primarySwatch: Colors.amber, +/// ) +/// ``` +/// +/// In Material 3, you can use the [ColorScheme] class to +/// construct a [ThemeData] with the same color palette +/// by using the [ColorScheme.fromSeed] constructor: +/// +/// ```dart +/// ThemeData( +/// colorScheme: ColorScheme.fromSeed(seedColor: Colors.amber), +/// ) +/// ``` +/// +/// The [ColorScheme.fromSeed] constructor +/// will generate a set of tonal palettes, +/// which are used to create the color scheme. +/// +/// Alternatively you can use the [ColorScheme.fromSwatch] constructor: +/// +/// ```dart +/// ThemeData( +/// colorScheme: ColorScheme.fromSwatch(primarySwatch: Colors.amber), +/// ) +/// ``` +/// +/// The [ColorScheme.fromSwatch] constructor will +/// create the color scheme directly from the specific +/// color values used in the [MaterialColor]. +/// +/// /// See also: /// /// * [Colors], which defines all of the standard material colors. diff --git a/packages/flutter/lib/src/material/data_table.dart b/packages/flutter/lib/src/material/data_table.dart index 970292c8eaff0..b1c01ce207994 100644 --- a/packages/flutter/lib/src/material/data_table.dart +++ b/packages/flutter/lib/src/material/data_table.dart @@ -4,7 +4,6 @@ import 'dart:math' as math; -import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -1202,7 +1201,7 @@ class TableRowInkWell extends InkResponse { RectCallback getRectCallback(RenderBox referenceBox) { return () { RenderObject cell = referenceBox; - AbstractNode? table = cell.parent; + RenderObject? table = cell.parent; final Matrix4 transform = Matrix4.identity(); while (table is RenderObject && table is! RenderTable) { table.applyPaintTransform(cell, transform); diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index 6017077cb02b9..afb72d4b84ba7 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -13,7 +13,6 @@ import 'back_button.dart'; import 'button_style.dart'; import 'calendar_date_picker.dart'; import 'color_scheme.dart'; -import 'colors.dart'; import 'date.dart'; import 'date_picker_theme.dart'; import 'debug.dart'; @@ -1588,6 +1587,7 @@ class _CalendarRangePickerDialog extends StatelessWidget { final DatePickerThemeData themeData = DatePickerTheme.of(context); final DatePickerThemeData defaults = DatePickerTheme.defaults(context); final Color? dialogBackground = themeData.rangePickerBackgroundColor ?? defaults.rangePickerBackgroundColor; + final Color? headerBackground = themeData.rangePickerHeaderBackgroundColor ?? defaults.rangePickerHeaderBackgroundColor; final Color? headerForeground = themeData.rangePickerHeaderForegroundColor ?? defaults.rangePickerHeaderForegroundColor; final Color? headerDisabledForeground = headerForeground?.withOpacity(0.38); final TextStyle? headlineStyle = themeData.rangePickerHeaderHeadlineStyle ?? defaults.rangePickerHeaderHeadlineStyle; @@ -1616,7 +1616,7 @@ class _CalendarRangePickerDialog extends StatelessWidget { actionsIconTheme: iconTheme, elevation: useMaterial3 ? 0 : null, scrolledUnderElevation: useMaterial3 ? 0 : null, - backgroundColor: useMaterial3 ? Colors.transparent : null, + backgroundColor: useMaterial3 ? headerBackground : null, leading: CloseButton( onPressed: onCancel, ), diff --git a/packages/flutter/lib/src/material/date_picker_theme.dart b/packages/flutter/lib/src/material/date_picker_theme.dart index be40707bb00cf..a970ffbd5dbd2 100644 --- a/packages/flutter/lib/src/material/date_picker_theme.dart +++ b/packages/flutter/lib/src/material/date_picker_theme.dart @@ -9,6 +9,7 @@ import 'package:flutter/widgets.dart'; import 'color_scheme.dart'; import 'colors.dart'; +import 'input_decorator.dart'; import 'material_state.dart'; import 'text_theme.dart'; import 'theme.dart'; @@ -68,6 +69,7 @@ class DatePickerThemeData with Diagnosticable { this.rangeSelectionBackgroundColor, this.rangeSelectionOverlayColor, this.dividerColor, + this.inputDecorationTheme, }); /// Overrides the default value of [Dialog.backgroundColor]. @@ -288,6 +290,10 @@ class DatePickerThemeData with Diagnosticable { /// and vertical divider when the dialog is in landscape orientation. final Color? dividerColor; + /// Overrides the [InputDatePickerFormField]'s input decoration theme. + /// If this is null, [ThemeData.inputDecorationTheme] is used instead. + final InputDecorationTheme? inputDecorationTheme; + /// Creates a copy of this object with the given fields replaced with the /// new values. DatePickerThemeData copyWith({ @@ -324,6 +330,7 @@ class DatePickerThemeData with Diagnosticable { Color? rangeSelectionBackgroundColor, MaterialStateProperty<Color?>? rangeSelectionOverlayColor, Color? dividerColor, + InputDecorationTheme? inputDecorationTheme, }) { return DatePickerThemeData( backgroundColor: backgroundColor ?? this.backgroundColor, @@ -359,6 +366,7 @@ class DatePickerThemeData with Diagnosticable { rangeSelectionBackgroundColor: rangeSelectionBackgroundColor ?? this.rangeSelectionBackgroundColor, rangeSelectionOverlayColor: rangeSelectionOverlayColor ?? this.rangeSelectionOverlayColor, dividerColor: dividerColor ?? this.dividerColor, + inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme, ); } @@ -401,6 +409,7 @@ class DatePickerThemeData with Diagnosticable { rangeSelectionBackgroundColor: Color.lerp(a?.rangeSelectionBackgroundColor, b?.rangeSelectionBackgroundColor, t), rangeSelectionOverlayColor: MaterialStateProperty.lerp<Color?>(a?.rangeSelectionOverlayColor, b?.rangeSelectionOverlayColor, t, Color.lerp), dividerColor: Color.lerp(a?.dividerColor, b?.dividerColor, t), + inputDecorationTheme: t < 0.5 ? a?.inputDecorationTheme : b?.inputDecorationTheme, ); } @@ -449,6 +458,7 @@ class DatePickerThemeData with Diagnosticable { rangeSelectionBackgroundColor, rangeSelectionOverlayColor, dividerColor, + inputDecorationTheme, ]); @override @@ -489,7 +499,8 @@ class DatePickerThemeData with Diagnosticable { && other.rangePickerHeaderHelpStyle == rangePickerHeaderHelpStyle && other.rangeSelectionBackgroundColor == rangeSelectionBackgroundColor && other.rangeSelectionOverlayColor == rangeSelectionOverlayColor - && other.dividerColor == dividerColor; + && other.dividerColor == dividerColor + && other.inputDecorationTheme == inputDecorationTheme; } @override @@ -528,6 +539,7 @@ class DatePickerThemeData with Diagnosticable { properties.add(ColorProperty('rangeSelectionBackgroundColor', rangeSelectionBackgroundColor, defaultValue: null)); properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('rangeSelectionOverlayColor', rangeSelectionOverlayColor, defaultValue: null)); properties.add(ColorProperty('dividerColor', dividerColor, defaultValue: null)); + properties.add(DiagnosticsProperty<InputDecorationTheme>('inputDecorationTheme', inputDecorationTheme, defaultValue: null)); } } @@ -789,8 +801,6 @@ class _DatePickerDefaultsM2 extends DatePickerThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _DatePickerDefaultsM3 extends DatePickerThemeData { _DatePickerDefaultsM3(this.context) : super( diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index f13b630f3ba8c..181aff65f5ca0 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart @@ -1628,8 +1628,6 @@ class _DialogDefaultsM2 extends DialogTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _DialogDefaultsM3 extends DialogTheme { _DialogDefaultsM3(this.context) : super( @@ -1673,8 +1671,6 @@ class _DialogDefaultsM3 extends DialogTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - 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 b51fae40f1a1b..20f3164f983e0 100644 --- a/packages/flutter/lib/src/material/divider.dart +++ b/packages/flutter/lib/src/material/divider.dart @@ -328,8 +328,6 @@ class _DividerDefaultsM2 extends DividerThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _DividerDefaultsM3 extends DividerThemeData { const _DividerDefaultsM3(this.context) : super( space: 16, diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index 7f7dd65a23615..ecaecb2c0d482 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -130,6 +130,12 @@ const Duration _kBaseSettleDuration = Duration(milliseconds: 246); /// ``` /// {@end-tool} /// +/// {@tool snippet} +/// This example shows how to migrate the above [Drawer] to a [NavigationDrawer]. +/// +/// See code in examples/api/lib/material/navigation_drawer/navigation_drawer.1.dart ** +/// {@end-tool} +/// /// An open drawer may be closed with a swipe to close gesture, pressing the /// escape key, by tapping the scrim, or by calling pop route function such as /// [Navigator.pop]. For example a drawer item might close the drawer when tapped: @@ -833,8 +839,6 @@ class _DrawerDefaultsM2 extends DrawerThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _DrawerDefaultsM3 extends DrawerThemeData { _DrawerDefaultsM3(this.context) : super(elevation: 1.0); diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index 3b74bc8110edf..a115b0982b134 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -788,6 +788,44 @@ class DropdownButtonHideUnderline extends InheritedWidget { /// shows the currently selected item as well as an arrow that opens a menu for /// selecting another item. /// +/// ## Updating to [DropdownMenu] +/// +/// There is a Material 3 version of this component, +/// [DropdownMenu] that is preferred for applications that are configured +/// for Material 3 (see [ThemeData.useMaterial3]). +/// The [DropdownMenu] widget's visuals +/// are a little bit different, see the Material 3 spec at +/// <https://m3.material.io/components/menus/guidelines> for +/// more details. +/// +/// The [DropdownMenu] widget's API is also slightly different. +/// To update from [DropdownButton] to [DropdownMenu], you will +/// need to make the following changes: +/// +/// 1. Instead of using [DropdownButton.items], which +/// takes a list of [DropdownMenuItem]s, use +/// [DropdownMenu.dropdownMenuEntries], which +/// takes a list of [DropdownMenuEntry]'s. +/// +/// 2. Instead of using [DropdownButton.onChanged], +/// use [DropdownMenu.onSelected], which is also +/// a callback that is called when the user selects an entry. +/// +/// 3. In [DropdownMenu] it is not required to track +/// the current selection in your app's state. +/// So, instead of tracking the current selection in +/// the [DropdownButton.value] property, you can set the +/// [DropdownMenu.initialSelection] property to the +/// item that should be selected before there is any user action. +/// +/// 4. You may also need to make changes to the styling of the +/// [DropdownMenu], see the properties in the [DropdownMenu] +/// constructor for more details. +/// +/// See the sample below for an example of migrating +/// from [DropdownButton] to [DropdownMenu]. +/// +/// ## Using [DropdownButton] /// {@youtube 560 315 https://www.youtube.com/watch?v=ZzQ_PWrFihg} /// /// One ancestor must be a [Material] widget and typically this is @@ -802,6 +840,7 @@ class DropdownButtonHideUnderline extends InheritedWidget { /// dropdown's value. It should also call [State.setState] to rebuild the /// dropdown with the new value. /// +/// /// {@tool dartpad} /// This sample shows a [DropdownButton] with a large arrow icon, /// purple text style, and bold purple underline, whose value is one of "One", @@ -819,9 +858,13 @@ class DropdownButtonHideUnderline extends InheritedWidget { /// [disabledHint] is null and [hint] is non-null, the [hint] widget will /// instead be displayed. /// -/// Requires one of its ancestors to be a [Material] widget. +/// {@tool dartpad} +/// This sample shows how you would rewrite the above [DropdownButton] +/// to use the [DropdownMenu]. +/// +/// ** See code in examples/api/lib/material/dropdown_menu/dropdown_menu.1.dart ** +/// {@end-tool} /// -/// {@youtube 560 315 https://www.youtube.com/watch?v=ZzQ_PWrFihg} /// /// See also: /// @@ -1178,7 +1221,6 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi Orientation? _lastOrientation; FocusNode? _internalNode; FocusNode? get focusNode => widget.focusNode ?? _internalNode; - bool _hasPrimaryFocus = false; late Map<Type, Action<Intent>> _actionMap; // Only used if needed to create _internalNode. @@ -1201,14 +1243,12 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi onInvoke: (ButtonActivateIntent intent) => _handleTap(), ), }; - focusNode!.addListener(_handleFocusChanged); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _removeDropdownRoute(); - focusNode!.removeListener(_handleFocusChanged); _internalNode?.dispose(); super.dispose(); } @@ -1219,25 +1259,11 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi _lastOrientation = null; } - void _handleFocusChanged() { - if (_hasPrimaryFocus != focusNode!.hasPrimaryFocus) { - setState(() { - _hasPrimaryFocus = focusNode!.hasPrimaryFocus; - }); - } - } - - @override void didUpdateWidget(DropdownButton<T> oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.focusNode != oldWidget.focusNode) { - oldWidget.focusNode?.removeListener(_handleFocusChanged); - if (widget.focusNode == null) { - _internalNode ??= _createFocusNode(); - } - _hasPrimaryFocus = focusNode!.hasPrimaryFocus; - focusNode!.addListener(_handleFocusChanged); + if (widget.focusNode == null) { + _internalNode ??= _createFocusNode(); } _updateSelectedIndex(); } diff --git a/packages/flutter/lib/src/material/dropdown_menu.dart b/packages/flutter/lib/src/material/dropdown_menu.dart index a29a48d607f9e..0d7125d6be56b 100644 --- a/packages/flutter/lib/src/material/dropdown_menu.dart +++ b/packages/flutter/lib/src/material/dropdown_menu.dart @@ -284,6 +284,7 @@ class DropdownMenu<T> extends StatefulWidget { class _DropdownMenuState<T> extends State<DropdownMenu<T>> { final GlobalKey _anchorKey = GlobalKey(); final GlobalKey _leadingKey = GlobalKey(); + late List<GlobalKey> buttonItemKeys; final MenuController _controller = MenuController(); late final TextEditingController _textEditingController; late bool _enableFilter; @@ -299,6 +300,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> { _textEditingController = widget.controller ?? TextEditingController(); _enableFilter = widget.enableFilter; filteredEntries = widget.dropdownMenuEntries; + buttonItemKeys = List<GlobalKey>.generate(filteredEntries.length, (int index) => GlobalKey()); _menuHasEnabledItem = filteredEntries.any((DropdownMenuEntry<T> entry) => entry.enabled); final int index = filteredEntries.indexWhere((DropdownMenuEntry<T> entry) => entry.value == widget.initialSelection); @@ -313,7 +315,15 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> { @override void didUpdateWidget(DropdownMenu<T> oldWidget) { super.didUpdateWidget(oldWidget); + if (oldWidget.enableSearch != widget.enableSearch) { + if (!widget.enableSearch) { + currentHighlight = null; + } + } if (oldWidget.dropdownMenuEntries != widget.dropdownMenuEntries) { + currentHighlight = null; + filteredEntries = widget.dropdownMenuEntries; + buttonItemKeys = List<GlobalKey>.generate(filteredEntries.length, (int index) => GlobalKey()); _menuHasEnabledItem = filteredEntries.any((DropdownMenuEntry<T> entry) => entry.enabled); } if (oldWidget.leadingIcon != widget.leadingIcon) { @@ -354,11 +364,20 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> { }); } + void scrollToHighlight() { + WidgetsBinding.instance.addPostFrameCallback((_) { + final BuildContext? highlightContext = buttonItemKeys[currentHighlight!].currentContext; + if (highlightContext != null) { + Scrollable.ensureVisible(highlightContext); + } + }); + } + double? getWidth(GlobalKey key) { final BuildContext? context = key.currentContext; if (context != null) { final RenderBox box = context.findRenderObject()! as RenderBox; - return box.size.width; + return box.hasSize ? box.size.width : null; } return null; } @@ -384,7 +403,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> { List<DropdownMenuEntry<T>> filteredEntries, TextEditingController textEditingController, TextDirection textDirection, - { int? focusedIndex } + { int? focusedIndex, bool enableScrollToHighlight = true} ) { final List<Widget> result = <Widget>[]; final double padding = leadingPadding ?? _kDefaultHorizontalPadding; @@ -416,6 +435,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> { : effectiveStyle; final MenuItemButton menuItemButton = MenuItemButton( + key: enableScrollToHighlight ? buttonItemKeys[i] : null, style: effectiveStyle, leadingIcon: entry.leadingIcon, trailingIcon: entry.trailingIcon, @@ -490,7 +510,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> { @override Widget build(BuildContext context) { final TextDirection textDirection = Directionality.of(context); - _initialMenu ??= _buildButtons(widget.dropdownMenuEntries, _textEditingController, textDirection); + _initialMenu ??= _buildButtons(widget.dropdownMenuEntries, _textEditingController, textDirection, enableScrollToHighlight: false); final DropdownMenuThemeData theme = DropdownMenuTheme.of(context); final DropdownMenuThemeData defaults = _DropdownMenuDefaultsM3(context); @@ -500,6 +520,9 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> { if (widget.enableSearch) { currentHighlight = search(filteredEntries, _textEditingController); + if (currentHighlight != null) { + scrollToHighlight(); + } } final List<Widget> menu = _buildButtons(filteredEntries, _textEditingController, textDirection, focusedIndex: currentHighlight); @@ -614,7 +637,7 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> { suffixIcon: trailingButton, ).applyDefaults(effectiveInputDecorationTheme) ), - for (Widget c in _initialMenu!) c, + for (final Widget c in _initialMenu!) c, trailingButton, leadingButton, ], diff --git a/packages/flutter/lib/src/material/elevated_button.dart b/packages/flutter/lib/src/material/elevated_button.dart index 8d8af37419907..92d57fb5d072b 100644 --- a/packages/flutter/lib/src/material/elevated_button.dart +++ b/packages/flutter/lib/src/material/elevated_button.dart @@ -543,8 +543,6 @@ class _ElevatedButtonWithIconChild extends StatelessWidget { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _ElevatedButtonDefaultsM3 extends ButtonStyle { _ElevatedButtonDefaultsM3(this.context) : super( diff --git a/packages/flutter/lib/src/material/elevation_overlay.dart b/packages/flutter/lib/src/material/elevation_overlay.dart index e0554d48290d4..24fcbd34b131d 100644 --- a/packages/flutter/lib/src/material/elevation_overlay.dart +++ b/packages/flutter/lib/src/material/elevation_overlay.dart @@ -156,8 +156,6 @@ class _ElevationOpacity { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - // Surface tint opacities based on elevations according to the // Material Design 3 specification: // https://m3.material.io/styles/color/the-color-system/color-roles diff --git a/packages/flutter/lib/src/material/expansion_panel.dart b/packages/flutter/lib/src/material/expansion_panel.dart index e994ae85c94f1..409bb1c96c717 100644 --- a/packages/flutter/lib/src/material/expansion_panel.dart +++ b/packages/flutter/lib/src/material/expansion_panel.dart @@ -313,9 +313,7 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> { return widget.children[index].isExpanded; } - void _handlePressed(bool isExpanded, int index) { - widget.expansionCallback?.call(index, isExpanded); - + void _handlePressed(bool isExpanded, int index) { if (widget._allowOnlyOnePanelOpen) { final ExpansionPanelRadio pressedChild = widget.children[index] as ExpansionPanelRadio; @@ -334,6 +332,8 @@ class _ExpansionPanelListState extends State<ExpansionPanelList> { _currentOpenPanel = isExpanded ? null : pressedChild; }); } + // !isExpanded is passed because, when _handlePressed, the state of the panel to expand is not yet expanded. + widget.expansionCallback?.call(index, !isExpanded); } ExpansionPanelRadio? searchPanelByValue(List<ExpansionPanelRadio> panels, Object? value) { diff --git a/packages/flutter/lib/src/material/expansion_tile.dart b/packages/flutter/lib/src/material/expansion_tile.dart index 006ae06de47be..10ca52d5aab44 100644 --- a/packages/flutter/lib/src/material/expansion_tile.dart +++ b/packages/flutter/lib/src/material/expansion_tile.dart @@ -759,8 +759,6 @@ class _ExpansionTileDefaultsM2 extends ExpansionTileThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _ExpansionTileDefaultsM3 extends ExpansionTileThemeData { _ExpansionTileDefaultsM3(this.context); diff --git a/packages/flutter/lib/src/material/filled_button.dart b/packages/flutter/lib/src/material/filled_button.dart index 7276597c857d1..d0933f891bb5a 100644 --- a/packages/flutter/lib/src/material/filled_button.dart +++ b/packages/flutter/lib/src/material/filled_button.dart @@ -553,8 +553,6 @@ class _FilledButtonWithIconChild extends StatelessWidget { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _FilledButtonDefaultsM3 extends ButtonStyle { _FilledButtonDefaultsM3(this.context) : super( @@ -677,8 +675,6 @@ class _FilledButtonDefaultsM3 extends ButtonStyle { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _FilledTonalButtonDefaultsM3 extends ButtonStyle { _FilledTonalButtonDefaultsM3(this.context) : super( diff --git a/packages/flutter/lib/src/material/filter_chip.dart b/packages/flutter/lib/src/material/filter_chip.dart index d599fbe4e7ffa..887bb05c87c1c 100644 --- a/packages/flutter/lib/src/material/filter_chip.dart +++ b/packages/flutter/lib/src/material/filter_chip.dart @@ -10,10 +10,13 @@ import 'chip_theme.dart'; import 'color_scheme.dart'; import 'colors.dart'; import 'debug.dart'; +import 'material_state.dart'; import 'text_theme.dart'; import 'theme.dart'; import 'theme_data.dart'; +enum _ChipVariant { flat, elevated } + /// A Material Design filter chip. /// /// Filter chips use tags or descriptive words as a way to filter content. @@ -78,6 +81,46 @@ class FilterChip extends StatelessWidget this.clipBehavior = Clip.none, this.focusNode, this.autofocus = false, + this.color, + this.backgroundColor, + this.padding, + this.visualDensity, + this.materialTapTargetSize, + this.elevation, + this.shadowColor, + this.surfaceTintColor, + this.iconTheme, + this.selectedShadowColor, + this.showCheckmark, + this.checkmarkColor, + this.avatarBorder = const CircleBorder(), + }) : assert(pressElevation == null || pressElevation >= 0.0), + assert(elevation == null || elevation >= 0.0), + _chipVariant = _ChipVariant.flat; + + /// Create an elevated chip that acts like a checkbox. + /// + /// The [selected], [label], [autofocus], and [clipBehavior] arguments must + /// not be null. The [pressElevation] and [elevation] must be null or + /// non-negative. Typically, [pressElevation] is greater than [elevation]. + const FilterChip.elevated({ + super.key, + this.avatar, + required this.label, + this.labelStyle, + this.labelPadding, + this.selected = false, + required this.onSelected, + this.pressElevation, + this.disabledColor, + this.selectedColor, + this.tooltip, + this.side, + this.shape, + this.clipBehavior = Clip.none, + this.focusNode, + this.autofocus = false, + this.color, this.backgroundColor, this.padding, this.visualDensity, @@ -91,7 +134,8 @@ class FilterChip extends StatelessWidget this.checkmarkColor, this.avatarBorder = const CircleBorder(), }) : assert(pressElevation == null || pressElevation >= 0.0), - assert(elevation == null || elevation >= 0.0); + assert(elevation == null || elevation >= 0.0), + _chipVariant = _ChipVariant.elevated; @override final Widget? avatar; @@ -124,6 +168,8 @@ class FilterChip extends StatelessWidget @override final bool autofocus; @override + final MaterialStateProperty<Color?>? color; + @override final Color? backgroundColor; @override final EdgeInsetsGeometry? padding; @@ -151,11 +197,13 @@ class FilterChip extends StatelessWidget @override bool get isEnabled => onSelected != null; + final _ChipVariant _chipVariant; + @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); final ChipThemeData? defaults = Theme.of(context).useMaterial3 - ? _FilterChipDefaultsM3(context, isEnabled, selected) + ? _FilterChipDefaultsM3(context, isEnabled, selected, _chipVariant) : null; return RawChip( defaultProperties: defaults, @@ -172,6 +220,7 @@ class FilterChip extends StatelessWidget clipBehavior: clipBehavior, focusNode: focusNode, autofocus: autofocus, + color: color, backgroundColor: backgroundColor, disabledColor: disabledColor, selectedColor: selectedColor, @@ -197,12 +246,13 @@ class FilterChip extends StatelessWidget // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _FilterChipDefaultsM3 extends ChipThemeData { - _FilterChipDefaultsM3(this.context, this.isEnabled, this.isSelected) - : super( - elevation: 0.0, + _FilterChipDefaultsM3( + this.context, + this.isEnabled, + this.isSelected, + this._chipVariant, + ) : super( shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))), showCheckmark: true, ); @@ -210,39 +260,60 @@ class _FilterChipDefaultsM3 extends ChipThemeData { final BuildContext context; final bool isEnabled; final bool isSelected; + final _ChipVariant _chipVariant; late final ColorScheme _colors = Theme.of(context).colorScheme; late final TextTheme _textTheme = Theme.of(context).textTheme; @override - TextStyle? get labelStyle => _textTheme.labelLarge; + double? get elevation => _chipVariant == _ChipVariant.flat + ? 0.0 + : isEnabled ? 1.0 : 0.0; @override - Color? get backgroundColor => null; + double? get pressElevation => 1.0; @override - Color? get shadowColor => Colors.transparent; + TextStyle? get labelStyle => _textTheme.labelLarge; @override - Color? get surfaceTintColor => _colors.surfaceTint; + MaterialStateProperty<Color?>? get color => + MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) { + return _chipVariant == _ChipVariant.flat + ? _colors.onSurface.withOpacity(0.12) + : _colors.onSurface.withOpacity(0.12); + } + if (states.contains(MaterialState.disabled)) { + return _chipVariant == _ChipVariant.flat + ? null + : _colors.onSurface.withOpacity(0.12); + } + if (states.contains(MaterialState.selected)) { + return _chipVariant == _ChipVariant.flat + ? _colors.secondaryContainer + : _colors.secondaryContainer; + } + return _chipVariant == _ChipVariant.flat + ? null + : null; + }); @override - Color? get selectedColor => isEnabled - ? _colors.secondaryContainer - : _colors.onSurface.withOpacity(0.12); + Color? get shadowColor => _chipVariant == _ChipVariant.flat + ? Colors.transparent + : _colors.shadow; @override - Color? get checkmarkColor => _colors.onSecondaryContainer; + Color? get surfaceTintColor => _colors.surfaceTint; @override - Color? get disabledColor => isSelected - ? _colors.onSurface.withOpacity(0.12) - : null; + Color? get checkmarkColor => _colors.onSecondaryContainer; @override Color? get deleteIconColor => _colors.onSecondaryContainer; @override - BorderSide? get side => !isSelected + BorderSide? get side => _chipVariant == _ChipVariant.flat && !isSelected ? isEnabled ? BorderSide(color: _colors.outline) : BorderSide(color: _colors.onSurface.withOpacity(0.12)) diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart index 09f98a0492e16..3b3fd352a7692 100644 --- a/packages/flutter/lib/src/material/flexible_space_bar.dart +++ b/packages/flutter/lib/src/material/flexible_space_bar.dart @@ -6,6 +6,7 @@ import 'dart:math' as math; import 'dart:ui' as ui; import 'package:flutter/foundation.dart' show clampDouble; +import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'colors.dart'; @@ -245,17 +246,18 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> { constraints.maxHeight > height) { height = constraints.maxHeight; } + final double topPadding = _getCollapsePadding(t, settings); children.add(Positioned( - top: _getCollapsePadding(t, settings), + top: topPadding, left: 0.0, right: 0.0, height: height, - child: Opacity( + child: _FlexibleSpaceHeaderOpacity( // IOS is relying on this semantics node to correctly traverse // through the app bar when it is collapsed. alwaysIncludeSemantics: true, opacity: opacity, - child: widget.background, + child: widget.background ), )); @@ -420,3 +422,50 @@ class FlexibleSpaceBarSettings extends InheritedWidget { || isScrolledUnder != oldWidget.isScrolledUnder; } } + +// We need the child widget to repaint, however both the opacity +// and potentially `widget.background` can be constant which won't +// lead to repainting. +// see: https://github.com/flutter/flutter/issues/127836 +class _FlexibleSpaceHeaderOpacity extends SingleChildRenderObjectWidget { + const _FlexibleSpaceHeaderOpacity({required this.opacity, required super.child, required this.alwaysIncludeSemantics}); + + final double opacity; + final bool alwaysIncludeSemantics; + + @override + RenderObject createRenderObject(BuildContext context) { + return _RenderFlexibleSpaceHeaderOpacity(opacity: opacity, alwaysIncludeSemantics: alwaysIncludeSemantics); + } + + @override + void updateRenderObject(BuildContext context, covariant _RenderFlexibleSpaceHeaderOpacity renderObject) { + renderObject + ..alwaysIncludeSemantics = alwaysIncludeSemantics + ..opacity = opacity; + } +} + +class _RenderFlexibleSpaceHeaderOpacity extends RenderOpacity { + _RenderFlexibleSpaceHeaderOpacity({super.opacity, super.alwaysIncludeSemantics}); + + @override + bool get isRepaintBoundary => false; + + @override + void paint(PaintingContext context, Offset offset) { + if (child == null) { + return; + } + if (opacity == 0) { + layer = null; + return; + } + assert(needsCompositing); + layer = context.pushOpacity(offset, (opacity * 255).round(), super.paint, oldLayer: layer as OpacityLayer?); + assert(() { + layer!.debugCreator = debugCreator; + return true; + }()); + } +} diff --git a/packages/flutter/lib/src/material/floating_action_button.dart b/packages/flutter/lib/src/material/floating_action_button.dart index 1a580b33666ab..e0f11dc6045c3 100644 --- a/packages/flutter/lib/src/material/floating_action_button.dart +++ b/packages/flutter/lib/src/material/floating_action_button.dart @@ -52,39 +52,21 @@ enum _FloatingActionButtonType { /// action button. /// /// {@tool dartpad} -/// This example shows how to display a [FloatingActionButton] in a -/// [Scaffold], with a pink [backgroundColor] and a thumbs up [Icon]. -/// -/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button.png) +/// This example shows a [FloatingActionButton] in its usual position within a +/// [Scaffold]. Pressing the button cycles it through a few variations in its +/// [foregroundColor], [backgroundColor], and [shape]. The button automatically +/// animates its segue from one set of visual parameters to another. /// /// ** See code in examples/api/lib/material/floating_action_button/floating_action_button.0.dart ** /// {@end-tool} /// /// {@tool dartpad} -/// This example shows how to make an extended [FloatingActionButton] in a -/// [Scaffold], with a pink [backgroundColor], a thumbs up [Icon] and a -/// [Text] label that reads "Approve". -/// -/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/floating_action_button_label.png) +/// This sample shows all the variants of [FloatingActionButton] widget as +/// described in: https://m3.material.io/components/floating-action-button/overview. /// /// ** See code in examples/api/lib/material/floating_action_button/floating_action_button.1.dart ** /// {@end-tool} /// -/// Material Design 3 introduced new types of floating action buttons. -/// {@tool dartpad} -/// This sample shows the creation of [FloatingActionButton] widget in the typical location in a Scaffold, -/// as described in: https://m3.material.io/components/floating-action-button/overview -/// -/// ** See code in examples/api/lib/material/floating_action_button/floating_action_button.2.dart ** -/// {@end-tool} -/// -/// {@tool dartpad} -/// This sample shows the creation of all the variants of [FloatingActionButton] widget as -/// described in: https://m3.material.io/components/floating-action-button/overview -/// -/// ** See code in examples/api/lib/material/floating_action_button/floating_action_button.3.dart ** -/// {@end-tool} -/// /// See also: /// /// * [Scaffold], in which floating action buttons typically live. @@ -789,8 +771,6 @@ class _FABDefaultsM2 extends FloatingActionButtonThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _FABDefaultsM3 extends FloatingActionButtonThemeData { _FABDefaultsM3(this.context, this.type, this.hasChild) : super( diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart index 7acd7576226e3..8fea4c9461be5 100644 --- a/packages/flutter/lib/src/material/icon_button.dart +++ b/packages/flutter/lib/src/material/icon_button.dart @@ -874,7 +874,10 @@ class _SelectableIconButtonState extends State<_SelectableIconButton> { onPressed: widget.onPressed, variant: widget.variant, toggleable: toggleable, - child: widget.child, + child: Semantics( + selected: widget.isSelected, + child: widget.child, + ), ); } } @@ -1081,8 +1084,6 @@ class _IconButtonDefaultMouseCursor extends MaterialStateProperty<MouseCursor?> // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _IconButtonDefaultsM3 extends ButtonStyle { _IconButtonDefaultsM3(this.context, this.toggleable) : super( @@ -1204,8 +1205,6 @@ class _IconButtonDefaultsM3 extends ButtonStyle { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _FilledIconButtonDefaultsM3 extends ButtonStyle { _FilledIconButtonDefaultsM3(this.context, this.toggleable) : super( @@ -1352,8 +1351,6 @@ class _FilledIconButtonDefaultsM3 extends ButtonStyle { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _FilledTonalIconButtonDefaultsM3 extends ButtonStyle { _FilledTonalIconButtonDefaultsM3(this.context, this.toggleable) : super( @@ -1500,8 +1497,6 @@ class _FilledTonalIconButtonDefaultsM3 extends ButtonStyle { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _OutlinedIconButtonDefaultsM3 extends ButtonStyle { _OutlinedIconButtonDefaultsM3(this.context, this.toggleable) : super( diff --git a/packages/flutter/lib/src/material/icons.dart b/packages/flutter/lib/src/material/icons.dart index 68631a5afab9a..59f3b088499f5 100644 --- a/packages/flutter/lib/src/material/icons.dart +++ b/packages/flutter/lib/src/material/icons.dart @@ -149,6 +149,7 @@ final class PlatformAdaptiveIcons implements Icons { /// * [IconButton] /// * <https://material.io/resources/icons> /// * [AnimatedIcons], for the list of available animated Material Icons. +/// {@hideConstantImplementations} @staticIconProvider abstract final class Icons { /// A set of platform-adaptive Material Design icons. diff --git a/packages/flutter/lib/src/material/ink_sparkle.dart b/packages/flutter/lib/src/material/ink_sparkle.dart index 44678581b54db..a46fd28cd00e2 100644 --- a/packages/flutter/lib/src/material/ink_sparkle.dart +++ b/packages/flutter/lib/src/material/ink_sparkle.dart @@ -14,11 +14,8 @@ import 'material.dart'; /// Begin a Material 3 ink sparkle ripple, centered at the tap or click position /// relative to the [referenceBox]. /// -/// This effect relies on a shader, and therefore hardware acceleration. -/// Currently, this is only supported by certain C++ engine platforms. The -/// platforms that are currently supported are Android, iOS, MacOS, Windows, -/// and Linux. Support for CanvasKit web can be tracked here: -/// - https://github.com/flutter/flutter/issues/85238 +/// This effect relies on a shader and therefore is unsupported on the Flutter +/// Web HTML backend. /// /// To use this effect, pass an instance of [splashFactory] to the /// `splashFactory` parameter of either the Material [ThemeData] or any diff --git a/packages/flutter/lib/src/material/input_chip.dart b/packages/flutter/lib/src/material/input_chip.dart index 8dcee824f38e1..756d557ee1bf0 100644 --- a/packages/flutter/lib/src/material/input_chip.dart +++ b/packages/flutter/lib/src/material/input_chip.dart @@ -11,6 +11,7 @@ import 'color_scheme.dart'; import 'colors.dart'; import 'debug.dart'; import 'icons.dart'; +import 'material_state.dart'; import 'text_theme.dart'; import 'theme.dart'; import 'theme_data.dart'; @@ -99,6 +100,7 @@ class InputChip extends StatelessWidget this.clipBehavior = Clip.none, this.focusNode, this.autofocus = false, + this.color, this.backgroundColor, this.padding, this.visualDensity, @@ -162,6 +164,8 @@ class InputChip extends StatelessWidget @override final bool autofocus; @override + final MaterialStateProperty<Color?>? color; + @override final Color? backgroundColor; @override final EdgeInsetsGeometry? padding; @@ -223,6 +227,7 @@ class InputChip extends StatelessWidget clipBehavior: clipBehavior, focusNode: focusNode, autofocus: autofocus, + color: color, backgroundColor: backgroundColor, padding: padding, visualDensity: visualDensity, @@ -246,8 +251,6 @@ class InputChip extends StatelessWidget // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _InputChipDefaultsM3 extends ChipThemeData { _InputChipDefaultsM3(this.context, this.isEnabled, this.isSelected) : super( @@ -266,7 +269,19 @@ class _InputChipDefaultsM3 extends ChipThemeData { TextStyle? get labelStyle => _textTheme.labelLarge; @override - Color? get backgroundColor => null; + MaterialStateProperty<Color?>? get color => + MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected) && states.contains(MaterialState.disabled)) { + return _colors.onSurface.withOpacity(0.12); + } + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return _colors.secondaryContainer; + } + return null; + }); @override Color? get shadowColor => Colors.transparent; @@ -274,17 +289,9 @@ class _InputChipDefaultsM3 extends ChipThemeData { @override Color? get surfaceTintColor => Colors.transparent; - @override - Color? get selectedColor => isEnabled - ? _colors.secondaryContainer - : _colors.onSurface.withOpacity(0.12); - @override Color? get checkmarkColor => null; - @override - Color? get disabledColor => null; - @override Color? get deleteIconColor => _colors.onSecondaryContainer; diff --git a/packages/flutter/lib/src/material/input_date_picker_form_field.dart b/packages/flutter/lib/src/material/input_date_picker_form_field.dart index 460ebee72dddf..4a82273b78248 100644 --- a/packages/flutter/lib/src/material/input_date_picker_form_field.dart +++ b/packages/flutter/lib/src/material/input_date_picker_form_field.dart @@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart'; import 'date.dart'; +import 'date_picker_theme.dart'; import 'input_border.dart'; import 'input_decorator.dart'; import 'material_localizations.dart'; @@ -248,16 +249,19 @@ class _InputDatePickerFormFieldState extends State<InputDatePickerFormField> { final ThemeData theme = Theme.of(context); final bool useMaterial3 = theme.useMaterial3; final MaterialLocalizations localizations = MaterialLocalizations.of(context); + final DatePickerThemeData datePickerTheme = theme.datePickerTheme; final InputDecorationTheme inputTheme = theme.inputDecorationTheme; - final InputBorder inputBorder = inputTheme.border + final InputBorder effectiveInputBorder = datePickerTheme.inputDecorationTheme?.border + ?? theme.inputDecorationTheme.border ?? (useMaterial3 ? const OutlineInputBorder() : const UnderlineInputBorder()); return TextFormField( decoration: InputDecoration( - border: inputBorder, - filled: inputTheme.filled, hintText: widget.fieldHintText ?? localizations.dateHelpText, labelText: widget.fieldLabelText ?? localizations.dateInputLabel, + ).applyDefaults(inputTheme + .merge(datePickerTheme.inputDecorationTheme) + .copyWith(border: effectiveInputBorder), ), validator: _validateDate, keyboardType: widget.keyboardType ?? TextInputType.datetime, diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 18f8ffd2b6115..d957c7aaf2948 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -302,6 +302,7 @@ class _HelperError extends StatefulWidget { this.helperText, this.helperStyle, this.helperMaxLines, + this.error, this.errorText, this.errorStyle, this.errorMaxLines, @@ -311,6 +312,7 @@ class _HelperError extends StatefulWidget { final String? helperText; final TextStyle? helperStyle; final int? helperMaxLines; + final Widget? error; final String? errorText; final TextStyle? errorStyle; final int? errorMaxLines; @@ -328,6 +330,8 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta Widget? _helper; Widget? _error; + bool get _hasError => widget.errorText != null || widget.error != null; + @override void initState() { super.initState(); @@ -335,7 +339,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta duration: _kTransitionDuration, vsync: this, ); - if (widget.errorText != null) { + if (_hasError) { _error = _buildError(); _controller.value = 1.0; } else if (widget.helperText != null) { @@ -360,16 +364,19 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta void didUpdateWidget(_HelperError old) { super.didUpdateWidget(old); + final Widget? newError = widget.error; final String? newErrorText = widget.errorText; final String? newHelperText = widget.helperText; + final Widget? oldError = old.error; final String? oldErrorText = old.errorText; final String? oldHelperText = old.helperText; + final bool errorStateChanged = (newError != null) != (oldError != null); final bool errorTextStateChanged = (newErrorText != null) != (oldErrorText != null); final bool helperTextStateChanged = newErrorText == null && (newHelperText != null) != (oldHelperText != null); - if (errorTextStateChanged || helperTextStateChanged) { - if (newErrorText != null) { + if (errorStateChanged || errorTextStateChanged || helperTextStateChanged) { + if (newError != null || newErrorText != null) { _error = _buildError(); _controller.forward(); } else if (newHelperText != null) { @@ -399,7 +406,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta } Widget _buildError() { - assert(widget.errorText != null); + assert(widget.error != null || widget.errorText != null); return Semantics( container: true, child: FadeTransition( @@ -409,7 +416,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta begin: const Offset(0.0, -0.25), end: Offset.zero, ).evaluate(_controller.view), - child: Text( + child: widget.error ?? Text( widget.errorText!, style: widget.errorStyle, textAlign: widget.textAlign, @@ -435,7 +442,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta if (_controller.isCompleted) { _helper = null; - if (widget.errorText != null) { + if (_hasError) { return _error = _buildError(); } else { _error = null; @@ -443,7 +450,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta } } - if (_helper == null && widget.errorText != null) { + if (_helper == null && _hasError) { return _buildError(); } @@ -451,7 +458,7 @@ class _HelperErrorState extends State<_HelperError> with SingleTickerProviderSta return _buildHelper(); } - if (widget.errorText != null) { + if (_hasError) { return Stack( children: <Widget>[ FadeTransition( @@ -1403,7 +1410,7 @@ class _RenderDecoration extends RenderBox with SlottedContainerRenderObjectMixin double start = right - _boxSize(icon).width; double end = left; if (prefixIcon != null) { - start += contentPadding.left; + start += contentPadding.right; start -= centerLayout(prefixIcon!, start - prefixIcon!.size.width); } if (label != null) { @@ -2365,6 +2372,7 @@ class _InputDecoratorState extends State<InputDecorator> with TickerProviderStat helperText: decoration.helperText, helperStyle: _getHelperStyle(themeData, defaults), helperMaxLines: decoration.helperMaxLines, + error: decoration.error, errorText: decoration.errorText, errorStyle: _getErrorStyle(themeData, defaults), errorMaxLines: decoration.errorMaxLines, @@ -2562,6 +2570,7 @@ class InputDecoration { this.hintStyle, this.hintTextDirection, this.hintMaxLines, + this.error, this.errorText, this.errorStyle, this.errorMaxLines, @@ -2601,7 +2610,8 @@ class InputDecoration { this.constraints, }) : assert(!(label != null && labelText != null), 'Declaring both label and labelText is not supported.'), assert(!(prefix != null && prefixText != null), 'Declaring both prefix and prefixText is not supported.'), - assert(!(suffix != null && suffixText != null), 'Declaring both suffix and suffixText is not supported.'); + assert(!(suffix != null && suffixText != null), 'Declaring both suffix and suffixText is not supported.'), + assert(!(error != null && errorText != null), 'Declaring both error and errorText is not supported.'); /// Defines an [InputDecorator] that is the same size as the input field. /// @@ -2630,6 +2640,7 @@ class InputDecoration { helperStyle = null, helperMaxLines = null, hintMaxLines = null, + error = null, errorText = null, errorStyle = null, errorMaxLines = null, @@ -2842,6 +2853,13 @@ class InputDecoration { /// used to handle the overflow when it is limited to single line. final int? hintMaxLines; + /// Optional widget that appears below the [InputDecorator.child] and the border. + /// + /// If non-null, the border's color animates to red and the [helperText] is not shown. + /// + /// Only one of [error] and [errorText] can be specified. + final Widget? error; + /// Text that appears below the [InputDecorator.child] and the border. /// /// If non-null, the border's color animates to red and the [helperText] is @@ -2849,6 +2867,10 @@ class InputDecoration { /// /// In a [TextFormField], this is overridden by the value returned from /// [TextFormField.validator], if that is not null. + /// + /// If a more elaborate error is required, consider using [error] instead. + /// + /// Only one of [error] and [errorText] can be specified. final String? errorText; /// {@template flutter.material.inputDecoration.errorStyle} @@ -3485,6 +3507,7 @@ class InputDecoration { TextStyle? hintStyle, TextDirection? hintTextDirection, int? hintMaxLines, + Widget? error, String? errorText, TextStyle? errorStyle, int? errorMaxLines, @@ -3537,6 +3560,7 @@ class InputDecoration { hintStyle: hintStyle ?? this.hintStyle, hintTextDirection: hintTextDirection ?? this.hintTextDirection, hintMaxLines: hintMaxLines ?? this.hintMaxLines, + error: error ?? this.error, errorText: errorText ?? this.errorText, errorStyle: errorStyle ?? this.errorStyle, errorMaxLines: errorMaxLines ?? this.errorMaxLines, @@ -3593,11 +3617,14 @@ class InputDecoration { errorMaxLines: errorMaxLines ?? theme.errorMaxLines, floatingLabelBehavior: floatingLabelBehavior ?? theme.floatingLabelBehavior, floatingLabelAlignment: floatingLabelAlignment ?? theme.floatingLabelAlignment, - isCollapsed: isCollapsed, isDense: isDense ?? theme.isDense, contentPadding: contentPadding ?? theme.contentPadding, + isCollapsed: isCollapsed, + iconColor: iconColor ?? theme.iconColor, prefixStyle: prefixStyle ?? theme.prefixStyle, + prefixIconColor: prefixIconColor ?? theme.prefixIconColor, suffixStyle: suffixStyle ?? theme.suffixStyle, + suffixIconColor: suffixIconColor ?? theme.suffixIconColor, counterStyle: counterStyle ?? theme.counterStyle, filled: filled ?? theme.filled, fillColor: fillColor ?? theme.fillColor, @@ -3636,6 +3663,7 @@ class InputDecoration { && other.hintStyle == hintStyle && other.hintTextDirection == hintTextDirection && other.hintMaxLines == hintMaxLines + && other.error == error && other.errorText == errorText && other.errorStyle == errorStyle && other.errorMaxLines == errorMaxLines @@ -3691,6 +3719,7 @@ class InputDecoration { hintStyle, hintTextDirection, hintMaxLines, + error, errorText, errorStyle, errorMaxLines, @@ -3744,6 +3773,7 @@ class InputDecoration { if (helperMaxLines != null) 'helperMaxLines: "$helperMaxLines"', if (hintText != null) 'hintText: "$hintText"', if (hintMaxLines != null) 'hintMaxLines: "$hintMaxLines"', + if (error != null) 'error: "$error"', if (errorText != null) 'errorText: "$errorText"', if (errorStyle != null) 'errorStyle: "$errorStyle"', if (errorMaxLines != null) 'errorMaxLines: "$errorMaxLines"', @@ -4279,6 +4309,49 @@ class InputDecorationTheme with Diagnosticable { ); } + /// Returns a copy of this InputDecorationTheme where the non-null fields in + /// the given InputDecorationTheme override the corresponding nullable fields + /// in this InputDecorationTheme. + /// + /// The non-nullable fields of InputDecorationTheme, such as [floatingLabelBehavior], + /// [isDense], [isCollapsed], [filled], and [alignLabelWithHint] cannot be overridden. + /// + /// In other words, the fields of the provided [InputDecorationTheme] are used to + /// fill in the unspecified and nullable fields of this InputDecorationTheme. + InputDecorationTheme merge(InputDecorationTheme? inputDecorationTheme) { + if (inputDecorationTheme == null) { + return this; + } + return copyWith( + labelStyle: labelStyle ?? inputDecorationTheme.labelStyle, + floatingLabelStyle: floatingLabelStyle ?? inputDecorationTheme.floatingLabelStyle, + helperStyle: helperStyle ?? inputDecorationTheme.helperStyle, + helperMaxLines: helperMaxLines ?? inputDecorationTheme.helperMaxLines, + hintStyle: hintStyle ?? inputDecorationTheme.hintStyle, + errorStyle: errorStyle ?? inputDecorationTheme.errorStyle, + errorMaxLines: errorMaxLines ?? inputDecorationTheme.errorMaxLines, + contentPadding: contentPadding ?? inputDecorationTheme.contentPadding, + iconColor: iconColor ?? inputDecorationTheme.iconColor, + prefixStyle: prefixStyle ?? inputDecorationTheme.prefixStyle, + prefixIconColor: prefixIconColor ?? inputDecorationTheme.prefixIconColor, + suffixStyle: suffixStyle ?? inputDecorationTheme.suffixStyle, + suffixIconColor: suffixIconColor ?? inputDecorationTheme.suffixIconColor, + counterStyle: counterStyle ?? inputDecorationTheme.counterStyle, + fillColor: fillColor ?? inputDecorationTheme.fillColor, + activeIndicatorBorder: activeIndicatorBorder ?? inputDecorationTheme.activeIndicatorBorder, + outlineBorder: outlineBorder ?? inputDecorationTheme.outlineBorder, + focusColor: focusColor ?? inputDecorationTheme.focusColor, + hoverColor: hoverColor ?? inputDecorationTheme.hoverColor, + errorBorder: errorBorder ?? inputDecorationTheme.errorBorder, + focusedBorder: focusedBorder ?? inputDecorationTheme.focusedBorder, + focusedErrorBorder: focusedErrorBorder ?? inputDecorationTheme.focusedErrorBorder, + disabledBorder: disabledBorder ?? inputDecorationTheme.disabledBorder, + enabledBorder: enabledBorder ?? inputDecorationTheme.enabledBorder, + border: border ?? inputDecorationTheme.border, + constraints: constraints ?? inputDecorationTheme.constraints, + ); + } + @override int get hashCode => Object.hash( labelStyle, @@ -4531,8 +4604,6 @@ class _InputDecoratorDefaultsM2 extends InputDecorationTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _InputDecoratorDefaultsM3 extends InputDecorationTheme { _InputDecoratorDefaultsM3(this.context) : super(); diff --git a/packages/flutter/lib/src/material/list_tile.dart b/packages/flutter/lib/src/material/list_tile.dart index e96ea52819f33..074862beb3c68 100644 --- a/packages/flutter/lib/src/material/list_tile.dart +++ b/packages/flutter/lib/src/material/list_tile.dart @@ -296,8 +296,6 @@ enum ListTileTitleAlignment { /// Here is an example of a custom list item that resembles a YouTube-related /// video list item created with [Expanded] and [Container] widgets. /// -/// ![Custom list item a](https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_list_item_a.png) -/// /// ** See code in examples/api/lib/material/list_tile/custom_list_item.0.dart ** /// {@end-tool} /// @@ -306,8 +304,6 @@ enum ListTileTitleAlignment { /// subtitles. It utilizes [Row]s and [Column]s, as well as [Expanded] and /// [AspectRatio] widgets to organize its layout. /// -/// ![Custom list item b](https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_list_item_b.png) -/// /// ** See code in examples/api/lib/material/list_tile/custom_list_item.1.dart ** /// {@end-tool} /// @@ -513,22 +509,25 @@ class ListTile extends StatelessWidget { /// /// If this property is null, then [ListTileThemeData.titleTextStyle] is used. /// If that is also null and [ThemeData.useMaterial3] is true, [TextTheme.bodyLarge] - /// will be used. Otherwise, If ListTile style is [ListTileStyle.list], - /// [TextTheme.titleMedium] will be used and if ListTile style is [ListTileStyle.drawer], - /// [TextTheme.bodyLarge] will be used. + /// with [ColorScheme.onSurface] will be used. Otherwise, If ListTile style is + /// [ListTileStyle.list], [TextTheme.titleMedium] will be used and if ListTile style + /// is [ListTileStyle.drawer], [TextTheme.bodyLarge] will be used. final TextStyle? titleTextStyle; /// The text style for ListTile's [subtitle]. /// /// If this property is null, then [ListTileThemeData.subtitleTextStyle] is used. - /// If that is also null, [TextTheme.bodyMedium] will be used. + /// If that is also null and [ThemeData.useMaterial3] is true, [TextTheme.bodyMedium] + /// with [ColorScheme.onSurfaceVariant] will be used, otherwise [TextTheme.bodyMedium] + /// with [TextTheme.bodySmall] color will be used. final TextStyle? subtitleTextStyle; /// The text style for ListTile's [leading] and [trailing]. /// /// If this property is null, then [ListTileThemeData.leadingAndTrailingTextStyle] is used. /// If that is also null and [ThemeData.useMaterial3] is true, [TextTheme.labelSmall] - /// will be used, otherwise [TextTheme.bodyMedium] will be used. + /// with [ColorScheme.onSurfaceVariant] will be used, otherwise [TextTheme.bodyMedium] + /// will be used. final TextStyle? leadingAndTrailingTextStyle; /// Defines the font used for the [title]. @@ -802,7 +801,8 @@ class ListTile extends StatelessWidget { subtitleStyle = subtitleTextStyle ?? tileTheme.subtitleTextStyle ?? defaults.subtitleTextStyle!; - final Color? subtitleColor = effectiveColor ?? theme.textTheme.bodySmall!.color; + final Color? subtitleColor = effectiveColor + ?? (theme.useMaterial3 ? null : theme.textTheme.bodySmall!.color); subtitleStyle = subtitleStyle.copyWith( color: subtitleColor, fontSize: _isDenseLayout(theme, tileTheme) ? 12.0 : null, @@ -1561,8 +1561,6 @@ class _LisTileDefaultsM2 extends ListTileThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _LisTileDefaultsM3 extends ListTileThemeData { _LisTileDefaultsM3(this.context) : super( @@ -1581,13 +1579,13 @@ class _LisTileDefaultsM3 extends ListTileThemeData { Color? get tileColor => Colors.transparent; @override - TextStyle? get titleTextStyle => _textTheme.bodyLarge; + TextStyle? get titleTextStyle => _textTheme.bodyLarge!.copyWith(color: _colors.onSurface); @override - TextStyle? get subtitleTextStyle => _textTheme.bodyMedium; + TextStyle? get subtitleTextStyle => _textTheme.bodyMedium!.copyWith(color: _colors.onSurfaceVariant); @override - TextStyle? get leadingAndTrailingTextStyle => _textTheme.labelSmall; + TextStyle? get leadingAndTrailingTextStyle => _textTheme.labelSmall!.copyWith(color: _colors.onSurfaceVariant); @override Color? get selectedColor => _colors.primary; diff --git a/packages/flutter/lib/src/material/material.dart b/packages/flutter/lib/src/material/material.dart index 5d8ccf450ffe2..e7d76a439e072 100644 --- a/packages/flutter/lib/src/material/material.dart +++ b/packages/flutter/lib/src/material/material.dart @@ -762,7 +762,7 @@ abstract class InkFeature { final int toDepth = to.depth; if (fromDepth >= toDepth) { - final AbstractNode? fromParent = from.parent; + final RenderObject? fromParent = from.parent; // Return early if the 2 render objects are not in the same render tree, // or either of them is offscreen and thus won't get painted. if (fromParent is! RenderObject || !fromParent.paintsChild(from)) { @@ -773,7 +773,7 @@ abstract class InkFeature { } if (fromDepth <= toDepth) { - final AbstractNode? toParent = to.parent; + final RenderObject? toParent = to.parent; if (toParent is! RenderObject || !toParent.paintsChild(to)) { return null; } diff --git a/packages/flutter/lib/src/material/material_localizations.dart b/packages/flutter/lib/src/material/material_localizations.dart index d2286a488c684..489c23a2ca143 100644 --- a/packages/flutter/lib/src/material/material_localizations.dart +++ b/packages/flutter/lib/src/material/material_localizations.dart @@ -103,6 +103,9 @@ abstract class MaterialLocalizations { /// Label for "cut" edit buttons and menu items. String get cutButtonLabel; + /// Label for "scan text" OCR edit buttons and menu items. + String get scanTextButtonLabel; + /// Label for OK buttons and menu items. String get okButtonLabel; @@ -1159,6 +1162,9 @@ class DefaultMaterialLocalizations implements MaterialLocalizations { @override String get cutButtonLabel => 'Cut'; + @override + String get scanTextButtonLabel => 'Scan text'; + @override String get okButtonLabel => 'OK'; diff --git a/packages/flutter/lib/src/material/menu_anchor.dart b/packages/flutter/lib/src/material/menu_anchor.dart index 856222f387c80..7980783745f30 100644 --- a/packages/flutter/lib/src/material/menu_anchor.dart +++ b/packages/flutter/lib/src/material/menu_anchor.dart @@ -1062,6 +1062,7 @@ class _MenuItemButtonState extends State<MenuItemButton> { style: mergedStyle, statesController: widget.statesController, clipBehavior: widget.clipBehavior, + isSemanticButton: null, child: _MenuItemLabel( leadingIcon: widget.leadingIcon, shortcut: widget.shortcut, @@ -1903,6 +1904,7 @@ class _SubmenuButtonState extends State<SubmenuButton> { focusNode: _buttonFocusNode, onHover: _enabled ? (bool hovering) => handleHover(hovering, context) : null, onPressed: _enabled ? () => toggleShowMenu(context) : null, + isSemanticButton: null, child: _MenuItemLabel( leadingIcon: widget.leadingIcon, trailingIcon: widget.trailingIcon, @@ -3582,8 +3584,6 @@ bool _platformSupportsAccelerators() { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _MenuBarDefaultsM3 extends MenuStyle { _MenuBarDefaultsM3(this.context) : super( diff --git a/packages/flutter/lib/src/material/mergeable_material.dart b/packages/flutter/lib/src/material/mergeable_material.dart index da1af3b5a855a..1ff2982d794e7 100644 --- a/packages/flutter/lib/src/material/mergeable_material.dart +++ b/packages/flutter/lib/src/material/mergeable_material.dart @@ -470,7 +470,12 @@ class _MergeableMaterialState extends State<MergeableMaterial> with TickerProvid _removeChild(j); } while (i < newChildren.length) { - _insertChild(j, newChildren[i]); + final MergeableMaterialItem newChild = newChildren[i]; + _insertChild(j, newChild); + + if (newChild is MaterialGap) { + _animationTuples[newChild.key]!.controller.forward(); + } i += 1; j += 1; diff --git a/packages/flutter/lib/src/material/navigation_bar.dart b/packages/flutter/lib/src/material/navigation_bar.dart index 4cb2189c0ae7d..fff6c2b435ebf 100644 --- a/packages/flutter/lib/src/material/navigation_bar.dart +++ b/packages/flutter/lib/src/material/navigation_bar.dart @@ -1349,8 +1349,6 @@ class _NavigationBarDefaultsM2 extends NavigationBarThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _NavigationBarDefaultsM3 extends NavigationBarThemeData { _NavigationBarDefaultsM3(this.context) : super( diff --git a/packages/flutter/lib/src/material/navigation_drawer.dart b/packages/flutter/lib/src/material/navigation_drawer.dart index 686fa9aa55d4d..f52e20274f8de 100644 --- a/packages/flutter/lib/src/material/navigation_drawer.dart +++ b/packages/flutter/lib/src/material/navigation_drawer.dart @@ -218,7 +218,7 @@ class NavigationDrawerDestination extends StatelessWidget { /// /// The icon will use [NavigationDrawerThemeData.iconTheme] with /// [MaterialState.selected]. If this is null, the default [IconThemeData] - /// would use a size of 24.0 and [ColorScheme.onSurfaceVariant]. + /// would use a size of 24.0 and [ColorScheme.onSecondaryContainer]. final Widget? selectedIcon; /// The text label that appears on the right of the icon @@ -671,8 +671,6 @@ bool _isForwardOrCompleted(Animation<double> animation) { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _NavigationDrawerDefaultsM3 extends NavigationDrawerThemeData { _NavigationDrawerDefaultsM3(this.context) : super( @@ -704,7 +702,7 @@ class _NavigationDrawerDefaultsM3 extends NavigationDrawerThemeData { return IconThemeData( size: 24.0, color: states.contains(MaterialState.selected) - ? null + ? _colors.onSecondaryContainer : _colors.onSurfaceVariant, ); }); diff --git a/packages/flutter/lib/src/material/navigation_rail.dart b/packages/flutter/lib/src/material/navigation_rail.dart index 276d70d87faa8..4219d52d302ad 100644 --- a/packages/flutter/lib/src/material/navigation_rail.dart +++ b/packages/flutter/lib/src/material/navigation_rail.dart @@ -467,6 +467,7 @@ class _NavigationRailState extends State<NavigationRail> with TickerProviderStat tabIndex: i + 1, tabCount: widget.destinations.length, ), + disabled: widget.destinations[i].disabled, ), if (widget.trailing != null) widget.trailing!, @@ -545,6 +546,7 @@ class _RailDestination extends StatelessWidget { required this.useIndicator, this.indicatorColor, this.indicatorShape, + this.disabled = false, }) : _positionAnimation = CurvedAnimation( parent: ReverseAnimation(destinationAnimation), curve: Curves.easeInOut, @@ -567,6 +569,7 @@ class _RailDestination extends StatelessWidget { final bool useIndicator; final Color? indicatorColor; final ShapeBorder? indicatorShape; + final bool disabled; final Animation<double> _positionAnimation; @@ -577,12 +580,17 @@ class _RailDestination extends StatelessWidget { '[NavigationRail.indicatorColor] does not have an effect when [NavigationRail.useIndicator] is false', ); - final bool material3 = Theme.of(context).useMaterial3; + final ThemeData theme = Theme.of(context); + + final bool material3 = theme.useMaterial3; final EdgeInsets destinationPadding = (padding ?? EdgeInsets.zero).resolve(Directionality.of(context)); Offset indicatorOffset; + bool applyXOffset = false; final Widget themedIcon = IconTheme( - data: iconTheme, + data: disabled + ? iconTheme.copyWith(color: theme.colorScheme.onSurface.withOpacity(0.38)) + : iconTheme, child: icon, ); final Widget styledLabel = DefaultTextStyle( @@ -638,6 +646,7 @@ class _RailDestination extends StatelessWidget { ); } else { final Animation<double> labelFadeAnimation = extendedTransitionAnimation.drive(CurveTween(curve: const Interval(0.0, 0.25))); + applyXOffset = true; content = Padding( padding: padding ?? EdgeInsets.zero, child: ConstrainedBox( @@ -761,13 +770,14 @@ class _RailDestination extends StatelessWidget { Material( type: MaterialType.transparency, child: _IndicatorInkWell( - onTap: onTap, + onTap: disabled ? null : onTap, borderRadius: BorderRadius.all(Radius.circular(minWidth / 2.0)), customBorder: indicatorShape, splashColor: colors.primary.withOpacity(0.12), hoverColor: colors.primary.withOpacity(0.04), useMaterial3: material3, indicatorOffset: indicatorOffset, + applyXOffset: applyXOffset, child: content, ), ), @@ -790,6 +800,7 @@ class _IndicatorInkWell extends InkResponse { super.hoverColor, required this.useMaterial3, required this.indicatorOffset, + required this.applyXOffset, }) : super( containedInkWell: true, highlightShape: BoxShape.rectangle, @@ -798,14 +809,19 @@ class _IndicatorInkWell extends InkResponse { ); final bool useMaterial3; + // The offset used to position Ink highlight. final Offset indicatorOffset; + // Whether the horizontal offset from indicatorOffset should be used to position Ink highlight. + // If true, Ink highlight uses the indicator horizontal offset. If false, Ink highlight is centered horizontally. + final bool applyXOffset; @override RectCallback? getRectCallback(RenderBox referenceBox) { if (useMaterial3) { + final double indicatorHorizontalCenter = applyXOffset ? indicatorOffset.dx : referenceBox.size.width / 2; return () { return Rect.fromLTWH( - indicatorOffset.dx - (_kCircularIndicatorDiameter / 2), + indicatorHorizontalCenter - (_kCircularIndicatorDiameter / 2), indicatorOffset.dy, _kCircularIndicatorDiameter, _kIndicatorHeight, @@ -909,6 +925,7 @@ class NavigationRailDestination { this.indicatorShape, required this.label, this.padding, + this.disabled = false, }) : selectedIcon = selectedIcon ?? icon; /// The icon of the destination. @@ -954,6 +971,9 @@ class NavigationRailDestination { /// The amount of space to inset the destination item. final EdgeInsetsGeometry? padding; + + /// Indicates that this destination is inaccessible. + final bool disabled; } class _ExtendedNavigationRailAnimation extends InheritedWidget { @@ -1029,8 +1049,6 @@ class _NavigationRailDefaultsM2 extends NavigationRailThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _NavigationRailDefaultsM3 extends NavigationRailThemeData { _NavigationRailDefaultsM3(this.context) : super( diff --git a/packages/flutter/lib/src/material/outlined_button.dart b/packages/flutter/lib/src/material/outlined_button.dart index 59739ef6f569a..8ee11e3b3e72d 100644 --- a/packages/flutter/lib/src/material/outlined_button.dart +++ b/packages/flutter/lib/src/material/outlined_button.dart @@ -281,7 +281,7 @@ class OutlinedButton extends ButtonStyleButton { /// * hovered - Theme.colorScheme.primary(0.08) /// * focused or pressed - Theme.colorScheme.primary(0.12) /// * others - null - /// * `shadowColor` - null + /// * `shadowColor` - Colors.transparent, /// * `surfaceTintColor` - null /// * `elevation` - 0 /// * `padding` @@ -474,8 +474,6 @@ class _OutlinedButtonWithIconChild extends StatelessWidget { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _OutlinedButtonDefaultsM3 extends ButtonStyle { _OutlinedButtonDefaultsM3(this.context) : super( diff --git a/packages/flutter/lib/src/material/page_transitions_theme.dart b/packages/flutter/lib/src/material/page_transitions_theme.dart index d87d64c9a4b9b..7c960a8362f79 100644 --- a/packages/flutter/lib/src/material/page_transitions_theme.dart +++ b/packages/flutter/lib/src/material/page_transitions_theme.dart @@ -202,7 +202,7 @@ class _ZoomPageTransition extends StatelessWidget { /// /// See also: /// - /// * [TransitionRoute.allowSnapshotting], which defines wether the route + /// * [TransitionRoute.allowSnapshotting], which defines whether the route /// transition will prefer to animate a snapshot of the entering and exiting /// routes. final bool allowSnapshotting; diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index c830a8287553c..c4846e8dd80b7 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -339,9 +339,10 @@ class PopupMenuItemState<T, W extends PopupMenuItem<T>> extends State<W> { /// the menu route. @protected void handleTap() { - widget.onTap?.call(); - + // Need to pop the navigator first in case onTap may push new route onto navigator. Navigator.pop<T>(context, widget.value); + + widget.onTap?.call(); } @override @@ -1014,6 +1015,41 @@ typedef PopupMenuItemBuilder<T> = List<PopupMenuEntry<T>> Function(BuildContext /// If both are null, then a standard overflow icon is created (depending on the /// platform). /// +/// /// ## Updating to [MenuAnchor] +/// +/// There is a Material 3 component, +/// [MenuAnchor] that is preferred for applications that are configured +/// for Material 3 (see [ThemeData.useMaterial3]). +/// The [MenuAnchor] widget's visuals +/// are a little bit different, see the Material 3 spec at +/// <https://m3.material.io/components/menus/guidelines> for +/// more details. +/// +/// The [MenuAnchor] widget's API is also slightly different. +/// [MenuAnchor]'s were built to be lower level interface for +/// creating menus that are displayed from an anchor. +/// +/// There are a few steps you would take to migrate from +/// [PopupMenuButton] to [MenuAnchor]: +/// +/// 1. Instead of using the [PopupMenuButton.itemBuilder] to build +/// a list of [PopupMenuEntry]s, you would use the [MenuAnchor.menuChildren] +/// which takes a list of [Widget]s. Usually, you would use a list of +/// [MenuItemButton]s as shown in the example below. +/// +/// 2. Instead of using the [PopupMenuButton.onSelected] callback, you would +/// set individual callbacks for each of the [MenuItemButton]s using the +/// [MenuItemButton.onPressed] property. +/// +/// 3. To anchor the [MenuAnchor] to a widget, you would use the [MenuAnchor.builder] +/// to return the widget of choice - usually a [TextButton] or an [IconButton]. +/// +/// 4. You may want to style the [MenuItemButton]s, see the [MenuItemButton] +/// documentation for details. +/// +/// Use the sample below for an example of migrating from [PopupMenuButton] to +/// [MenuAnchor]. +/// /// {@tool dartpad} /// This example shows a menu with three items, selecting between an enum's /// values and setting a `selectedMenu` field based on the selection. @@ -1022,6 +1058,12 @@ typedef PopupMenuItemBuilder<T> = List<PopupMenuEntry<T>> Function(BuildContext /// {@end-tool} /// /// {@tool dartpad} +/// This example shows how to migrate the above to a [MenuAnchor]. +/// +/// ** See code in examples/api/lib/material/menu_anchor/menu_anchor.2.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} /// This sample shows the creation of a popup menu, as described in: /// https://m3.material.io/components/menus/overview /// @@ -1373,8 +1415,6 @@ class _PopupMenuDefaultsM2 extends PopupMenuThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _PopupMenuDefaultsM3 extends PopupMenuThemeData { _PopupMenuDefaultsM3(this.context) : super(elevation: 3.0); diff --git a/packages/flutter/lib/src/material/progress_indicator.dart b/packages/flutter/lib/src/material/progress_indicator.dart index 43541f6bf14fd..8dc13967c147b 100644 --- a/packages/flutter/lib/src/material/progress_indicator.dart +++ b/packages/flutter/lib/src/material/progress_indicator.dart @@ -1041,8 +1041,6 @@ class _LinearProgressIndicatorDefaultsM2 extends ProgressIndicatorThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - 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 380037181c264..b6d967f3f0a2d 100644 --- a/packages/flutter/lib/src/material/radio.dart +++ b/packages/flutter/lib/src/material/radio.dart @@ -629,8 +629,6 @@ class _RadioDefaultsM2 extends RadioThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _RadioDefaultsM3 extends RadioThemeData { _RadioDefaultsM3(this.context); diff --git a/packages/flutter/lib/src/material/radio_list_tile.dart b/packages/flutter/lib/src/material/radio_list_tile.dart index 0f354dced4dbb..0dfb4c556948d 100644 --- a/packages/flutter/lib/src/material/radio_list_tile.dart +++ b/packages/flutter/lib/src/material/radio_list_tile.dart @@ -437,8 +437,7 @@ class RadioListTile<T> extends StatelessWidget { final _RadioType _radioType; - /// Determines wether or not to use the checkbox style for the [CupertinoRadio] - /// control. + /// Whether to use the checkbox style for the [CupertinoRadio] control. /// /// Only usable under the [RadioListTile.adaptive] constructor. If set to /// true, on Apple platforms the radio button will appear as an iOS styled diff --git a/packages/flutter/lib/src/material/range_slider.dart b/packages/flutter/lib/src/material/range_slider.dart index a0391cc1c257f..99677cfff7560 100644 --- a/packages/flutter/lib/src/material/range_slider.dart +++ b/packages/flutter/lib/src/material/range_slider.dart @@ -833,7 +833,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix ..team = team ..onTapDown = _handleTapDown ..onTapUp = _handleTapUp - ..onTapCancel = _handleTapCancel ..gestureSettings = gestureSettings; _overlayAnimation = CurvedAnimation( parent: _state.overlayController, @@ -1221,6 +1220,10 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix } void _startInteraction(Offset globalPosition) { + if (_active) { + return; + } + _state.showValueIndicator(); final double tapValue = clampDouble(_getValueFromGlobalPosition(globalPosition), 0.0, 1.0); _lastThumbSelection = sliderTheme.thumbSelector!(textDirection, values, tapValue, _thumbSize, size, 0); @@ -1333,10 +1336,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix _endInteraction(); } - void _handleTapCancel() { - _endInteraction(); - } - @override bool hitTestSelf(Offset position) => true; diff --git a/packages/flutter/lib/src/material/reorderable_list.dart b/packages/flutter/lib/src/material/reorderable_list.dart index 3c95333cc60a7..a91f2a47e82bd 100644 --- a/packages/flutter/lib/src/material/reorderable_list.dart +++ b/packages/flutter/lib/src/material/reorderable_list.dart @@ -273,6 +273,8 @@ class ReorderableListView extends StatefulWidget { final Widget? prototypeItem; /// {@macro flutter.widgets.EdgeDraggingAutoScroller.velocityScalar} + /// + /// {@macro flutter.widgets.SliverReorderableList.autoScrollerVelocityScalar.default} final double? autoScrollerVelocityScalar; @override diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index d9d239c529072..fbcd472801923 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -1358,11 +1358,9 @@ class _FloatingActionButtonTransitionState extends State<_FloatingActionButtonTr void _handlePreviousAnimationStatusChanged(AnimationStatus status) { setState(() { - if (status == AnimationStatus.dismissed) { + if (widget.child != null && status == AnimationStatus.dismissed) { assert(widget.currentController.status == AnimationStatus.dismissed); - if (widget.child != null) { - widget.currentController.forward(); - } + widget.currentController.forward(); } }); } @@ -2312,6 +2310,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto bottomSheetKey.currentState!.close(); setState(() { + _showBodyScrim = false; + _bodyScrimColor = Colors.black.withOpacity(0.0); _currentBottomSheet = null; }); diff --git a/packages/flutter/lib/src/material/scrollbar.dart b/packages/flutter/lib/src/material/scrollbar.dart index a4067b7dfc19f..c274695eed355 100644 --- a/packages/flutter/lib/src/material/scrollbar.dart +++ b/packages/flutter/lib/src/material/scrollbar.dart @@ -95,26 +95,12 @@ class Scrollbar extends StatelessWidget { this.notificationPredicate, this.interactive, this.scrollbarOrientation, - @Deprecated( - 'Use thumbVisibility instead. ' - 'This feature was deprecated after v2.9.0-1.0.pre.', - ) - this.isAlwaysShown, @Deprecated( 'Use ScrollbarThemeData.trackVisibility to resolve based on the current state instead. ' 'This feature was deprecated after v3.4.0-19.0.pre.', ) this.showTrackOnHover, - @Deprecated( - 'Use ScrollbarThemeData.thickness to resolve based on the current state instead. ' - 'This feature was deprecated after v2.9.0-1.0.pre.', - ) - this.hoverThickness, - }) : assert( - thumbVisibility == null || isAlwaysShown == null, - 'Scrollbar thumb appearance should only be controlled with thumbVisibility, ' - 'isAlwaysShown is deprecated.' - ); + }); /// {@macro flutter.widgets.Scrollbar.child} final Widget child; @@ -131,20 +117,8 @@ class Scrollbar extends StatelessWidget { /// If the thumb visibility is related to the scrollbar's material state, /// use the global [ScrollbarThemeData.thumbVisibility] or override the /// sub-tree's theme data. - /// - /// Replaces deprecated [isAlwaysShown]. final bool? thumbVisibility; - /// {@macro flutter.widgets.Scrollbar.isAlwaysShown} - /// - /// To show the scrollbar thumb based on a [MaterialState], use - /// [ScrollbarThemeData.thumbVisibility]. - @Deprecated( - 'Use thumbVisibility instead. ' - 'This feature was deprecated after v2.9.0-1.0.pre.', - ) - final bool? isAlwaysShown; - /// {@macro flutter.widgets.Scrollbar.trackVisibility} /// /// If this property is null, then [ScrollbarThemeData.trackVisibility] of @@ -172,21 +146,6 @@ class Scrollbar extends StatelessWidget { ) final bool? showTrackOnHover; - /// The thickness of the scrollbar when a hover state is active and - /// [showTrackOnHover] is true. - /// - /// If this property is null, then [ScrollbarThemeData.thickness] of - /// [ThemeData.scrollbarTheme] is used to resolve a thickness. If that is also - /// null, the default value is 12.0 pixels. - /// - /// This is deprecated, use [ScrollbarThemeData.thickness] to resolve based on - /// the current state instead. - @Deprecated( - 'Use ScrollbarThemeData.thickness to resolve based on the current state instead. ' - 'This feature was deprecated after v2.9.0-1.0.pre.', - ) - final double? hoverThickness; - /// The thickness of the scrollbar in the cross axis of the scrollable. /// /// If null, the default value is platform dependent. On [TargetPlatform.android], @@ -216,7 +175,7 @@ class Scrollbar extends StatelessWidget { Widget build(BuildContext context) { if (Theme.of(context).platform == TargetPlatform.iOS) { return CupertinoScrollbar( - thumbVisibility: isAlwaysShown ?? thumbVisibility ?? false, + thumbVisibility: thumbVisibility ?? false, thickness: thickness ?? CupertinoScrollbar.defaultThickness, thicknessWhileDragging: thickness ?? CupertinoScrollbar.defaultThicknessWhileDragging, radius: radius ?? CupertinoScrollbar.defaultRadius, @@ -229,10 +188,9 @@ class Scrollbar extends StatelessWidget { } return _MaterialScrollbar( controller: controller, - thumbVisibility: isAlwaysShown ?? thumbVisibility, + thumbVisibility: thumbVisibility, trackVisibility: trackVisibility, showTrackOnHover: showTrackOnHover, - hoverThickness: hoverThickness, thickness: thickness, radius: radius, notificationPredicate: notificationPredicate, @@ -250,7 +208,6 @@ class _MaterialScrollbar extends RawScrollbar { super.thumbVisibility, super.trackVisibility, this.showTrackOnHover, - this.hoverThickness, super.thickness, super.radius, ScrollNotificationPredicate? notificationPredicate, @@ -264,7 +221,6 @@ class _MaterialScrollbar extends RawScrollbar { ); final bool? showTrackOnHover; - final double? hoverThickness; @override _MaterialScrollbarState createState() => _MaterialScrollbarState(); @@ -280,7 +236,7 @@ class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> { late bool _useAndroidScrollbar; @override - bool get showScrollbar => widget.thumbVisibility ?? _scrollbarTheme.thumbVisibility?.resolve(_states) ?? _scrollbarTheme.isAlwaysShown ?? false; + bool get showScrollbar => widget.thumbVisibility ?? _scrollbarTheme.thumbVisibility?.resolve(_states) ?? false; @override bool get enableGestures => widget.interactive ?? _scrollbarTheme.interactive ?? !_useAndroidScrollbar; @@ -370,8 +326,7 @@ class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> { MaterialStateProperty<double> get _thickness { return MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.hovered) && _trackVisibility.resolve(states)) { - return widget.hoverThickness - ?? _scrollbarTheme.thickness?.resolve(states) + return _scrollbarTheme.thickness?.resolve(states) ?? _kScrollbarThicknessWithTrack; } // The default scrollbar thickness is smaller on mobile. diff --git a/packages/flutter/lib/src/material/scrollbar_theme.dart b/packages/flutter/lib/src/material/scrollbar_theme.dart index b1d07888ce012..ac519a66b7a9e 100644 --- a/packages/flutter/lib/src/material/scrollbar_theme.dart +++ b/packages/flutter/lib/src/material/scrollbar_theme.dart @@ -45,26 +45,15 @@ class ScrollbarThemeData with Diagnosticable { this.mainAxisMargin, this.minThumbLength, this.interactive, - @Deprecated( - 'Use thumbVisibility instead. ' - 'This feature was deprecated after v2.9.0-1.0.pre.', - ) - this.isAlwaysShown, @Deprecated( 'Use ScrollbarThemeData.trackVisibility to resolve based on the current state instead. ' 'This feature was deprecated after v3.4.0-19.0.pre.', ) this.showTrackOnHover, - }) : assert( - isAlwaysShown == null || thumbVisibility == null, - 'Scrollbar thumb appearance should only be controlled with thumbVisibility, ' - 'isAlwaysShown is deprecated.' - ); + }); /// Overrides the default value of [Scrollbar.thumbVisibility] in all /// descendant [Scrollbar] widgets. - /// - /// Replaces deprecated [isAlwaysShown]. final MaterialStateProperty<bool?>? thumbVisibility; /// Overrides the default value of [Scrollbar.thickness] in all @@ -86,16 +75,6 @@ class ScrollbarThemeData with Diagnosticable { ) final bool? showTrackOnHover; - /// Overrides the default value of [Scrollbar.isAlwaysShown] in all - /// descendant [Scrollbar] widgets. - /// - /// Deprecated in favor of [thumbVisibility]. - @Deprecated( - 'Use thumbVisibility instead. ' - 'This feature was deprecated after v2.9.0-1.0.pre.', - ) - final bool? isAlwaysShown; - /// Overrides the default value of [Scrollbar.interactive] in all /// descendant [Scrollbar] widgets. final bool? interactive; @@ -169,12 +148,6 @@ class ScrollbarThemeData with Diagnosticable { double? crossAxisMargin, double? mainAxisMargin, double? minThumbLength, - @Deprecated( - 'Use thumbVisibility instead. ' - 'This feature was deprecated after v2.9.0-1.0.pre.', - ) - bool? isAlwaysShown, - @Deprecated( 'Use ScrollbarThemeData.trackVisibility to resolve based on the current state instead. ' 'This feature was deprecated after v3.4.0-19.0.pre.', @@ -186,7 +159,6 @@ class ScrollbarThemeData with Diagnosticable { thickness: thickness ?? this.thickness, trackVisibility: trackVisibility ?? this.trackVisibility, showTrackOnHover: showTrackOnHover ?? this.showTrackOnHover, - isAlwaysShown: isAlwaysShown ?? this.isAlwaysShown, interactive: interactive ?? this.interactive, radius: radius ?? this.radius, thumbColor: thumbColor ?? this.thumbColor, @@ -212,7 +184,6 @@ class ScrollbarThemeData with Diagnosticable { thickness: MaterialStateProperty.lerp<double?>(a?.thickness, b?.thickness, t, lerpDouble), trackVisibility: MaterialStateProperty.lerp<bool?>(a?.trackVisibility, b?.trackVisibility, t, _lerpBool), showTrackOnHover: _lerpBool(a?.showTrackOnHover, b?.showTrackOnHover, t), - isAlwaysShown: _lerpBool(a?.isAlwaysShown, b?.isAlwaysShown, t), interactive: _lerpBool(a?.interactive, b?.interactive, t), radius: Radius.lerp(a?.radius, b?.radius, t), thumbColor: MaterialStateProperty.lerp<Color?>(a?.thumbColor, b?.thumbColor, t, Color.lerp), @@ -230,7 +201,6 @@ class ScrollbarThemeData with Diagnosticable { thickness, trackVisibility, showTrackOnHover, - isAlwaysShown, interactive, radius, thumbColor, @@ -254,7 +224,6 @@ class ScrollbarThemeData with Diagnosticable { && other.thickness == thickness && other.trackVisibility == trackVisibility && other.showTrackOnHover == showTrackOnHover - && other.isAlwaysShown == isAlwaysShown && other.interactive == interactive && other.radius == radius && other.thumbColor == thumbColor @@ -272,7 +241,6 @@ class ScrollbarThemeData with Diagnosticable { properties.add(DiagnosticsProperty<MaterialStateProperty<double?>>('thickness', thickness, defaultValue: null)); properties.add(DiagnosticsProperty<MaterialStateProperty<bool?>>('trackVisibility', trackVisibility, defaultValue: null)); properties.add(DiagnosticsProperty<bool>('showTrackOnHover', showTrackOnHover, defaultValue: null)); - properties.add(DiagnosticsProperty<bool>('isAlwaysShown', isAlwaysShown, defaultValue: null)); properties.add(DiagnosticsProperty<bool>('interactive', interactive, defaultValue: null)); properties.add(DiagnosticsProperty<Radius>('radius', radius, defaultValue: null)); properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('thumbColor', thumbColor, defaultValue: null)); diff --git a/packages/flutter/lib/src/material/search.dart b/packages/flutter/lib/src/material/search.dart index ab7eee7f4983e..e465c5492ebb0 100644 --- a/packages/flutter/lib/src/material/search.dart +++ b/packages/flutter/lib/src/material/search.dart @@ -211,6 +211,15 @@ abstract class SearchDelegate<T> { /// PreferredSizeWidget? buildBottom(BuildContext context) => null; + /// Widget to display a flexible space in the [AppBar]. + /// + /// Returns null by default, i.e. a flexible space widget is not included. + /// + /// See also: + /// + /// * [AppBar.flexibleSpace], the intended use for the return value of this method. + Widget? buildFlexibleSpace(BuildContext context) => null; + /// The theme used to configure the search page. /// /// The returned [ThemeData] will be used to wrap the entire search page, @@ -581,11 +590,10 @@ class _SearchPageState<T> extends State<_SearchPage<T>> { style: widget.delegate.searchFieldStyle ?? theme.textTheme.titleLarge, textInputAction: widget.delegate.textInputAction, keyboardType: widget.delegate.keyboardType, - onSubmitted: (String _) { - widget.delegate.showResults(context); - }, + onSubmitted: (String _) => widget.delegate.showResults(context), decoration: InputDecoration(hintText: searchFieldLabel), ), + flexibleSpace: widget.delegate.buildFlexibleSpace(context), actions: widget.delegate.buildActions(context), bottom: widget.delegate.buildBottom(context), ), diff --git a/packages/flutter/lib/src/material/search_anchor.dart b/packages/flutter/lib/src/material/search_anchor.dart index 0face90bc3242..e9b3037ffcfcb 100644 --- a/packages/flutter/lib/src/material/search_anchor.dart +++ b/packages/flutter/lib/src/material/search_anchor.dart @@ -489,7 +489,8 @@ class _SearchViewRoute extends PopupRoute<_SearchViewRoute> { } void updateTweens(BuildContext context) { - final Size screenSize = MediaQuery.of(context).size; + final RenderBox navigator = Navigator.of(context).context.findRenderObject()! as RenderBox; + final Size screenSize = navigator.size; final Rect anchorRect = getRect() ?? Rect.zero; final BoxConstraints effectiveConstraints = viewConstraints ?? viewTheme.constraints ?? viewDefaults.constraints!; @@ -996,6 +997,7 @@ class SearchBar extends StatefulWidget { this.trailing, this.onTap, this.onChanged, + this.onSubmitted, this.constraints, this.elevation, this.backgroundColor, @@ -1044,6 +1046,10 @@ class SearchBar extends StatefulWidget { /// Invoked upon user input. final ValueChanged<String>? onChanged; + /// Called when the user indicates that they are done editing the text in the + /// field. + final ValueChanged<String>? onSubmitted; + /// Optional size constraints for the search bar. /// /// If null, the value of [SearchBarThemeData.constraints] will be used. If @@ -1236,6 +1242,7 @@ class _SearchBarState extends State<SearchBar> { child: TextField( focusNode: _focusNode, onChanged: widget.onChanged, + onSubmitted: widget.onSubmitted, controller: widget.controller, style: effectiveTextStyle, decoration: InputDecoration( @@ -1248,7 +1255,10 @@ class _SearchBarState extends State<SearchBar> { enabledBorder: InputBorder.none, border: InputBorder.none, focusedBorder: InputBorder.none, - contentPadding: const EdgeInsets.symmetric(vertical: 12.0), + contentPadding: EdgeInsets.zero, + // Setting `isDense` to true to allow the text field height to be + // smaller than 48.0 + isDense: true, )), ), ), @@ -1271,8 +1281,6 @@ class _SearchBarState extends State<SearchBar> { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _SearchBarDefaultsM3 extends SearchBarThemeData { _SearchBarDefaultsM3(this.context); @@ -1343,8 +1351,6 @@ class _SearchBarDefaultsM3 extends SearchBarThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _SearchViewDefaultsM3 extends SearchViewThemeData { _SearchViewDefaultsM3(this.context, {required this.isFullScreen}); diff --git a/packages/flutter/lib/src/material/segmented_button.dart b/packages/flutter/lib/src/material/segmented_button.dart index af6bdfb214100..9e056b37d1f46 100644 --- a/packages/flutter/lib/src/material/segmented_button.dart +++ b/packages/flutter/lib/src/material/segmented_button.dart @@ -17,6 +17,7 @@ import 'segmented_button_theme.dart'; import 'text_button.dart'; import 'text_button_theme.dart'; import 'theme.dart'; +import 'tooltip.dart'; /// Data describing a segment of a [SegmentedButton]. class ButtonSegment<T> { @@ -27,6 +28,7 @@ class ButtonSegment<T> { required this.value, this.icon, this.label, + this.tooltip, this.enabled = true, }) : assert(icon != null || label != null); @@ -41,6 +43,9 @@ class ButtonSegment<T> { /// Optional label displayed in the segment. final Widget? label; + /// Optional tooltip for the segment + final String? tooltip; + /// Determines if the segment is available for selection. final bool enabled; } @@ -335,11 +340,18 @@ class SegmentedButton<T> extends StatelessWidget { child: label, ); + final Widget buttonWithTooltip = segment.tooltip != null + ? Tooltip( + message: segment.tooltip, + child: button, + ) + : button; + return MergeSemantics( child: Semantics( checked: segmentSelected, inMutuallyExclusiveGroup: multiSelectionEnabled ? null : true, - child: button, + child: buttonWithTooltip, ), ); } @@ -714,8 +726,6 @@ class _RenderSegmentedButton<T> extends RenderBox with // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _SegmentedButtonDefaultsM3 extends SegmentedButtonThemeData { _SegmentedButtonDefaultsM3(this.context); final BuildContext context; diff --git a/packages/flutter/lib/src/material/selection_area.dart b/packages/flutter/lib/src/material/selection_area.dart index 625f3aa1afaab..2334f545cc3bf 100644 --- a/packages/flutter/lib/src/material/selection_area.dart +++ b/packages/flutter/lib/src/material/selection_area.dart @@ -103,13 +103,7 @@ class SelectionArea extends StatefulWidget { } class _SelectionAreaState extends State<SelectionArea> { - FocusNode get _effectiveFocusNode { - if (widget.focusNode != null) { - return widget.focusNode!; - } - _internalNode ??= FocusNode(); - return _internalNode!; - } + FocusNode get _effectiveFocusNode => widget.focusNode ?? (_internalNode ??= FocusNode()); FocusNode? _internalNode; @override @@ -121,20 +115,12 @@ class _SelectionAreaState extends State<SelectionArea> { @override Widget build(BuildContext context) { assert(debugCheckHasMaterialLocalizations(context)); - TextSelectionControls? controls = widget.selectionControls; - switch (Theme.of(context).platform) { - case TargetPlatform.android: - case TargetPlatform.fuchsia: - controls ??= materialTextSelectionHandleControls; - case TargetPlatform.iOS: - controls ??= cupertinoTextSelectionHandleControls; - case TargetPlatform.linux: - case TargetPlatform.windows: - controls ??= desktopTextSelectionHandleControls; - case TargetPlatform.macOS: - controls ??= cupertinoDesktopTextSelectionHandleControls; - } - + final TextSelectionControls controls = widget.selectionControls ?? switch (Theme.of(context).platform) { + TargetPlatform.android || TargetPlatform.fuchsia => materialTextSelectionHandleControls, + TargetPlatform.linux || TargetPlatform.windows => desktopTextSelectionHandleControls, + TargetPlatform.iOS => cupertinoTextSelectionHandleControls, + TargetPlatform.macOS => cupertinoDesktopTextSelectionHandleControls, + }; return SelectableRegion( selectionControls: controls, focusNode: _effectiveFocusNode, diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index e4f8efb93bdd0..6680aaa144b17 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -1512,8 +1512,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { _state.interactionTimer?.cancel(); _state.interactionTimer = Timer(_minimumInteractionTime * timeDilation, () { _state.interactionTimer = null; - if (!_active && !hasFocus && - _state.valueIndicatorController.status == AnimationStatus.completed) { + if (!_active && _state.valueIndicatorController.status == AnimationStatus.completed) { _state.valueIndicatorController.reverse(); } }); @@ -1531,10 +1530,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { onChangeEnd?.call(_discretize(_currentDragValue)); _active = false; _currentDragValue = 0.0; - if (!hasFocus) { - _state.overlayController.reverse(); - } - + _state.overlayController.reverse(); if (showValueIndicator && _state.interactionTimer == null) { _state.valueIndicatorController.reverse(); } @@ -1977,8 +1973,6 @@ class _SliderDefaultsM2 extends SliderThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _SliderDefaultsM3 extends SliderThemeData { _SliderDefaultsM3(this.context) : super(trackHeight: 4.0); diff --git a/packages/flutter/lib/src/material/snack_bar.dart b/packages/flutter/lib/src/material/snack_bar.dart index 1fb77fac8744c..5bed2ab347ce5 100644 --- a/packages/flutter/lib/src/material/snack_bar.dart +++ b/packages/flutter/lib/src/material/snack_bar.dart @@ -842,11 +842,12 @@ class _SnackbarDefaultsM2 extends SnackBarThemeData { @override TextStyle? get contentTextStyle => ThemeData( - brightness: _theme.brightness == Brightness.light - ? Brightness.dark - : Brightness.light) - .textTheme - .titleMedium; + useMaterial3: _theme.useMaterial3, + brightness: _theme.brightness == Brightness.light + ? Brightness.dark + : Brightness.light) + .textTheme + .titleMedium; @override SnackBarBehavior get behavior => SnackBarBehavior.fixed; @@ -885,8 +886,6 @@ class _SnackbarDefaultsM2 extends SnackBarThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _SnackbarDefaultsM3 extends SnackBarThemeData { _SnackbarDefaultsM3(this.context); diff --git a/packages/flutter/lib/src/material/stepper.dart b/packages/flutter/lib/src/material/stepper.dart index 3d374ddf3e959..b6ab24a64a74c 100644 --- a/packages/flutter/lib/src/material/stepper.dart +++ b/packages/flutter/lib/src/material/stepper.dart @@ -202,6 +202,7 @@ class Stepper extends StatefulWidget { const Stepper({ super.key, required this.steps, + this.controller, this.physics, this.type = StepperType.vertical, this.currentStep = 0, @@ -230,6 +231,13 @@ class Stepper extends StatefulWidget { /// can be helpful to set this property to [ClampingScrollPhysics]. final ScrollPhysics? physics; + /// An object that can be used to control the position to which this scroll + /// view is scrolled. + /// + /// To control the initial scroll offset of the scroll view, provide a + /// [controller] with its [ScrollController.initialScrollOffset] property set. + final ScrollController? controller; + /// The type of stepper that determines the layout. In the case of /// [StepperType.horizontal], the content of the current step is displayed /// underneath as opposed to the [StepperType.vertical] case where it is @@ -765,6 +773,7 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin { Widget _buildVertical() { return ListView( + controller: widget.controller, shrinkWrap: true, physics: widget.physics, children: <Widget>[ @@ -858,6 +867,7 @@ class _StepperState extends State<Stepper> with TickerProviderStateMixin { ), Expanded( child: ListView( + controller: widget.controller, physics: widget.physics, padding: const EdgeInsets.all(24.0), children: <Widget>[ diff --git a/packages/flutter/lib/src/material/switch.dart b/packages/flutter/lib/src/material/switch.dart index ccc18c89d03e5..0cfc6ff81550e 100644 --- a/packages/flutter/lib/src/material/switch.dart +++ b/packages/flutter/lib/src/material/switch.dart @@ -1757,8 +1757,6 @@ class _SwitchDefaultsM2 extends SwitchThemeData { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _SwitchDefaultsM3 extends SwitchThemeData { _SwitchDefaultsM3(this.context); diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index e2dd48a4aeb52..756e61c7f2dbc 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -2165,8 +2165,6 @@ class _TabsDefaultsM2 extends TabBarTheme { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _TabsPrimaryDefaultsM3 extends TabBarTheme { _TabsPrimaryDefaultsM3(this.context, this.isScrollable) : super(indicatorSize: TabBarIndicatorSize.label); diff --git a/packages/flutter/lib/src/material/text_button.dart b/packages/flutter/lib/src/material/text_button.dart index 97b89254da679..8e5c2db54885a 100644 --- a/packages/flutter/lib/src/material/text_button.dart +++ b/packages/flutter/lib/src/material/text_button.dart @@ -87,6 +87,7 @@ class TextButton extends ButtonStyleButton { super.autofocus = false, super.clipBehavior = Clip.none, super.statesController, + super.isSemanticButton, required Widget super.child, }); @@ -314,7 +315,7 @@ class TextButton extends ButtonStyleButton { /// * hovered - Theme.colorScheme.primary(0.08) /// * focused or pressed - Theme.colorScheme.primary(0.12) /// * others - null - /// * `shadowColor` - null + /// * `shadowColor` - Colors.transparent, /// * `surfaceTintColor` - null /// * `elevation` - 0 /// * `padding` @@ -534,8 +535,6 @@ class _TextButtonWithIconChild extends StatelessWidget { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _TextButtonDefaultsM3 extends ButtonStyle { _TextButtonDefaultsM3(this.context) : super( diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 2800258346785..f2d6716f5ec79 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -1503,8 +1503,6 @@ TextStyle _m2CounterErrorStyle(BuildContext context) => // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - TextStyle? _m3StateInputStyle(BuildContext context) => MaterialStateTextStyle.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.disabled)) { return TextStyle(color: Theme.of(context).textTheme.bodyLarge!.color?.withOpacity(0.38)); diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index 35e77c1bb70cb..57dfafcddbb42 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -307,8 +307,8 @@ class ThemeData with Diagnosticable { VisualDensity? visualDensity, // COLOR // [colorScheme] is the preferred way to configure colors. The other color - // properties (as well as primaryColorBrightness, and primarySwatch) - // will gradually be phased out, see https://github.com/flutter/flutter/issues/91772. + // properties (as well as primarySwatch) will gradually be phased out, see + // https://github.com/flutter/flutter/issues/91772. Brightness? brightness, Color? canvasColor, Color? cardColor, @@ -388,11 +388,6 @@ class ThemeData with Diagnosticable { ToggleButtonsThemeData? toggleButtonsTheme, TooltipThemeData? tooltipTheme, // DEPRECATED (newest deprecations at the bottom) - @Deprecated( - 'No longer used by the framework, please remove any reference to it. ' - 'This feature was deprecated after v2.6.0-11.0.pre.', - ) - Brightness? primaryColorBrightness, @Deprecated( 'Use ThemeData.useMaterial3 or override ScrollBehavior.buildOverscrollIndicator. ' 'This feature was deprecated after v2.13.0-0.0.pre.' @@ -469,7 +464,6 @@ class ThemeData with Diagnosticable { // Default some of the color settings to values from the color scheme primaryColor ??= primarySurfaceColor; - primaryColorBrightness = ThemeData.estimateBrightnessForColor(primarySurfaceColor); canvasColor ??= colorScheme.background; scaffoldBackgroundColor ??= colorScheme.background; bottomAppBarColor ??= colorScheme.surface; @@ -599,7 +593,6 @@ class ThemeData with Diagnosticable { tooltipTheme ??= const TooltipThemeData(); // DEPRECATED (newest deprecations at the bottom) - primaryColorBrightness = estimatedPrimaryColorBrightness; errorColor ??= Colors.red[700]!; backgroundColor ??= isDark ? Colors.grey[700]! : primarySwatch[200]!; bottomAppBarColor ??= colorSchemeSeed != null ? colorScheme.surface : isDark ? Colors.grey[800]! : Colors.white; @@ -696,7 +689,6 @@ class ThemeData with Diagnosticable { toggleButtonsTheme: toggleButtonsTheme, tooltipTheme: tooltipTheme, // DEPRECATED (newest deprecations at the bottom) - primaryColorBrightness: primaryColorBrightness, androidOverscrollIndicator: androidOverscrollIndicator, toggleableActiveColor: toggleableActiveColor, selectedRowColor: selectedRowColor, @@ -808,11 +800,6 @@ class ThemeData with Diagnosticable { required this.toggleButtonsTheme, required this.tooltipTheme, // DEPRECATED (newest deprecations at the bottom) - @Deprecated( - 'No longer used by the framework, please remove any reference to it. ' - 'This feature was deprecated after v2.6.0-11.0.pre.', - ) - Brightness? primaryColorBrightness, @Deprecated( 'Use ThemeData.useMaterial3 or override ScrollBehavior.buildOverscrollIndicator. ' 'This feature was deprecated after v2.13.0-0.0.pre.' @@ -848,7 +835,6 @@ class ThemeData with Diagnosticable { }) : // DEPRECATED (newest deprecations at the bottom) // should not be `required`, use getter pattern to avoid breakages. - _primaryColorBrightness = primaryColorBrightness, _toggleableActiveColor = toggleableActiveColor, _selectedRowColor = selectedRowColor, _errorColor = errorColor, @@ -856,7 +842,6 @@ class ThemeData with Diagnosticable { _bottomAppBarColor = bottomAppBarColor, assert(toggleableActiveColor != null), // DEPRECATED (newest deprecations at the bottom) - assert(primaryColorBrightness != null), assert(errorColor != null), assert(backgroundColor != null); @@ -903,7 +888,6 @@ class ThemeData with Diagnosticable { colorScheme: colorScheme, brightness: colorScheme.brightness, primaryColor: primarySurfaceColor, - primaryColorBrightness: ThemeData.estimateBrightnessForColor(primarySurfaceColor), canvasColor: colorScheme.background, scaffoldBackgroundColor: colorScheme.background, bottomAppBarColor: colorScheme.surface, @@ -1524,22 +1508,6 @@ class ThemeData with Diagnosticable { // DEPRECATED (newest deprecations at the bottom) - /// Obsolete property that was originally used to determine the color - /// of text and icons placed on top of the primary color (e.g. toolbar text). - /// - /// The material library no longer uses this property. The [appBarTheme] can - /// be used to configure the appearance of [AppBar]s. The appearance of - /// Keyboards for [TextField]s now uses the overall theme's - /// [ThemeData.brightness] and can also be customized with - /// [TextField.keyboardAppearance]. The brightness of any color can be found - /// with [ThemeData.estimateBrightnessForColor]. - @Deprecated( - 'No longer used by the framework, please remove any reference to it. ' - 'This feature was deprecated after v2.6.0-11.0.pre.', - ) - Brightness get primaryColorBrightness => _primaryColorBrightness!; - final Brightness? _primaryColorBrightness; - /// Specifies which overscroll indicator to use on [TargetPlatform.android]. /// /// When null, the default value of @@ -1696,11 +1664,6 @@ class ThemeData with Diagnosticable { ToggleButtonsThemeData? toggleButtonsTheme, TooltipThemeData? tooltipTheme, // DEPRECATED (newest deprecations at the bottom) - @Deprecated( - 'No longer used by the framework, please remove any reference to it. ' - 'This feature was deprecated after v2.6.0-11.0.pre.', - ) - Brightness? primaryColorBrightness, @Deprecated( 'Use ThemeData.useMaterial3 or override ScrollBehavior.buildOverscrollIndicator. ' 'This feature was deprecated after v2.13.0-0.0.pre.' @@ -1827,7 +1790,6 @@ class ThemeData with Diagnosticable { toggleButtonsTheme: toggleButtonsTheme ?? this.toggleButtonsTheme, tooltipTheme: tooltipTheme ?? this.tooltipTheme, // DEPRECATED (newest deprecations at the bottom) - primaryColorBrightness: primaryColorBrightness ?? _primaryColorBrightness, androidOverscrollIndicator: androidOverscrollIndicator ?? this.androidOverscrollIndicator, toggleableActiveColor: toggleableActiveColor ?? _toggleableActiveColor, selectedRowColor: selectedRowColor ?? _selectedRowColor, @@ -2022,7 +1984,6 @@ class ThemeData with Diagnosticable { toggleButtonsTheme: ToggleButtonsThemeData.lerp(a.toggleButtonsTheme, b.toggleButtonsTheme, t)!, tooltipTheme: TooltipThemeData.lerp(a.tooltipTheme, b.tooltipTheme, t)!, // DEPRECATED (newest deprecations at the bottom) - primaryColorBrightness: t < 0.5 ? a.primaryColorBrightness : b.primaryColorBrightness, androidOverscrollIndicator:t < 0.5 ? a.androidOverscrollIndicator : b.androidOverscrollIndicator, toggleableActiveColor: Color.lerp(a.toggleableActiveColor, b.toggleableActiveColor, t), selectedRowColor: Color.lerp(a.selectedRowColor, b.selectedRowColor, t), @@ -2129,7 +2090,6 @@ class ThemeData with Diagnosticable { other.toggleButtonsTheme == toggleButtonsTheme && other.tooltipTheme == tooltipTheme && // DEPRECATED (newest deprecations at the bottom) - other.primaryColorBrightness == primaryColorBrightness && other.androidOverscrollIndicator == androidOverscrollIndicator && other.toggleableActiveColor == toggleableActiveColor && other.selectedRowColor == selectedRowColor && @@ -2233,7 +2193,6 @@ class ThemeData with Diagnosticable { toggleButtonsTheme, tooltipTheme, // DEPRECATED (newest deprecations at the bottom) - primaryColorBrightness, androidOverscrollIndicator, toggleableActiveColor, selectedRowColor, @@ -2339,7 +2298,6 @@ class ThemeData with Diagnosticable { properties.add(DiagnosticsProperty<ToggleButtonsThemeData>('toggleButtonsTheme', toggleButtonsTheme, level: DiagnosticLevel.debug)); properties.add(DiagnosticsProperty<TooltipThemeData>('tooltipTheme', tooltipTheme, level: DiagnosticLevel.debug)); // DEPRECATED (newest deprecations at the bottom) - properties.add(EnumProperty<Brightness>('primaryColorBrightness', primaryColorBrightness, defaultValue: defaultData.primaryColorBrightness, level: DiagnosticLevel.debug)); properties.add(EnumProperty<AndroidOverscrollIndicator>('androidOverscrollIndicator', androidOverscrollIndicator, defaultValue: null, level: DiagnosticLevel.debug)); properties.add(ColorProperty('toggleableActiveColor', toggleableActiveColor, defaultValue: defaultData.toggleableActiveColor, level: DiagnosticLevel.debug)); properties.add(ColorProperty('selectedRowColor', selectedRowColor, defaultValue: defaultData.selectedRowColor, level: DiagnosticLevel.debug)); @@ -2763,8 +2721,6 @@ class VisualDensity with Diagnosticable { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - const ColorScheme _colorSchemeLightM3 = ColorScheme( brightness: Brightness.light, primary: Color(0xFF6750A4), diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index 1b42f5fcc185f..479438aa15f27 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -3322,8 +3322,6 @@ class _TimePickerDefaultsM2 extends _TimePickerDefaults { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _TimePickerDefaultsM3 extends _TimePickerDefaults { _TimePickerDefaultsM3(this.context); diff --git a/packages/flutter/lib/src/material/toggle_buttons.dart b/packages/flutter/lib/src/material/toggle_buttons.dart index 526156841ba6b..4b786f3ca96da 100644 --- a/packages/flutter/lib/src/material/toggle_buttons.dart +++ b/packages/flutter/lib/src/material/toggle_buttons.dart @@ -30,8 +30,35 @@ import 'toggle_buttons_theme.dart'; /// correlated by their index in the list. The length of [isSelected] has to /// match the length of the [children] list. /// +/// There is a Material 3 version of this component, [SegmentedButton], +/// that's preferred for applications that are configured for Material 3 +/// (see [ThemeData.useMaterial3]). +/// /// {@youtube 560 315 https://www.youtube.com/watch?v=kVEguaQWGAY} /// +/// ## Updating to [SegmentedButton] +/// +/// There is a Material 3 version of this component, [SegmentedButton], +/// that's preferred for applications that are configured for Material 3 +/// (see [ThemeData.useMaterial3]). The [SegmentedButton] widget's visuals +/// are a little bit different, see the Material 3 spec at +/// <https://m3.material.io/components/segmented-buttons/overview> for +/// more details. The [SegmentedButton] widget's API is also slightly different. +/// While the [ToggleButtons] widget can have list of widgets, the +/// [SegmentedButton] widget has a list of [ButtonSegment]s with +/// a type value. While the [ToggleButtons] uses a list of boolean values +/// to determine the selection state of each button, the [SegmentedButton] +/// uses a set of type values to determine the selection state of each segment. +/// The [SegmentedButton.style] is a [ButtonStyle] style field, which can be +/// used to customize the entire segmented button and the individual segments. +/// +/// {@tool dartpad} +/// This sample shows how to migrate [ToggleButtons] that allows multiple +/// or no selection to [SegmentedButton] that allows multiple or no selection. +/// +/// ** See code in examples/api/lib/material/toggle_buttons/toggle_buttons.1.dart ** +/// {@end-tool} +/// /// {@tool dartpad} /// This example showcase [ToggleButtons] in various configurations. /// diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart index efced3bdbb8cc..2be81e1ba51cc 100644 --- a/packages/flutter/lib/src/material/tooltip.dart +++ b/packages/flutter/lib/src/material/tooltip.dart @@ -12,6 +12,7 @@ import 'package:flutter/widgets.dart'; import 'colors.dart'; import 'feedback.dart'; +import 'text_theme.dart'; import 'theme.dart'; import 'tooltip_theme.dart'; import 'tooltip_visibility.dart'; @@ -388,23 +389,17 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { static const bool _defaultEnableFeedback = true; static const TextAlign _defaultTextAlign = TextAlign.start; - late double _height; - late EdgeInsetsGeometry _padding; - late EdgeInsetsGeometry _margin; - late Decoration _decoration; - late TextStyle _textStyle; - late TextAlign _textAlign; - late double _verticalOffset; - late bool _preferBelow; - late bool _excludeFromSemantics; - OverlayEntry? _entry; - - late Duration _showDuration; - late Duration _hoverShowDuration; - late Duration _waitDuration; - late TooltipTriggerMode _triggerMode; - late bool _enableFeedback; + final OverlayPortalController _overlayController = OverlayPortalController(); + + // From InheritedWidgets late bool _visible; + late TooltipThemeData _tooltipTheme; + + Duration get _showDuration => widget.showDuration ?? _tooltipTheme.showDuration ?? _defaultShowDuration; + Duration get _hoverShowDuration => widget.showDuration ?? _tooltipTheme.showDuration ?? _defaultHoverShowDuration; + Duration get _waitDuration => widget.waitDuration ?? _tooltipTheme.waitDuration ?? _defaultWaitDuration; + TooltipTriggerMode get _triggerMode => widget.triggerMode ?? _tooltipTheme.triggerMode ?? _defaultTriggerMode; + bool get _enableFeedback => widget.enableFeedback ?? _tooltipTheme.enableFeedback ?? _defaultEnableFeedback; /// The plain text message for this tooltip. /// @@ -425,29 +420,32 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { TapGestureRecognizer? _tapRecognizer; // The ids of mouse devices that are keeping the tooltip from being dismissed. + // + // Device ids are added to this set in _handleMouseEnter, and removed in + // _handleMouseExit. The set is cleared in _handleTapToDismiss, typically when + // a PointerDown event interacts with some other UI component. final Set<int> _activeHoveringPointerDevices = <int>{}; + + static bool _isTooltipVisible(AnimationStatus status) { + return switch (status) { + AnimationStatus.completed || AnimationStatus.forward || AnimationStatus.reverse => true, + AnimationStatus.dismissed => false, + }; + } + AnimationStatus _animationStatus = AnimationStatus.dismissed; void _handleStatusChanged(AnimationStatus status) { assert(mounted); - final bool entryNeedsUpdating; - switch (status) { - case AnimationStatus.dismissed: - entryNeedsUpdating = _animationStatus != AnimationStatus.dismissed; - if (entryNeedsUpdating) { - _removeEntry(); - } - case AnimationStatus.completed: - case AnimationStatus.forward: - case AnimationStatus.reverse: - entryNeedsUpdating = _animationStatus == AnimationStatus.dismissed; - if (entryNeedsUpdating) { - _createNewEntry(); - SemanticsService.tooltip(_tooltipMessage); - } - } - - if (entryNeedsUpdating) { - setState(() { /* Rebuild to update the OverlayEntry */ }); + switch ((_isTooltipVisible(_animationStatus), _isTooltipVisible(status))) { + case (true, false): + Tooltip._openedTooltips.remove(this); + _overlayController.hide(); + case (false, true): + _overlayController.show(); + Tooltip._openedTooltips.add(this); + SemanticsService.tooltip(_tooltipMessage); + case (true, true) || (false, false): + break; } _animationStatus = status; } @@ -469,18 +467,14 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { 'timer must not be active when the tooltip is fading out', ); switch (_controller.status) { - case AnimationStatus.dismissed: - if (withDelay.inMicroseconds > 0) { - _timer ??= Timer(withDelay, show); - } else { - show(); - } + case AnimationStatus.dismissed when withDelay.inMicroseconds > 0: + _timer ??= Timer(withDelay, show); // If the tooltip is already fading in or fully visible, skip the // animation and show the tooltip immediately. + case AnimationStatus.dismissed: case AnimationStatus.forward: case AnimationStatus.reverse: case AnimationStatus.completed: - // Fade in if needed and schedule to hide. show(); } } @@ -488,13 +482,16 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { void _scheduleDismissTooltip({ required Duration withDelay }) { assert(mounted); assert( - !(_timer?.isActive ?? false) || _controller.status != AnimationStatus.reverse, + !(_timer?.isActive ?? false) || _backingController?.status != AnimationStatus.reverse, 'timer must not be active when the tooltip is fading out', ); _timer?.cancel(); _timer = null; - switch (_controller.status) { + // Use _backingController instead of _controller to prevent the lazy getter + // from instaniating an AnimationController unnecessarily. + switch (_backingController?.status) { + case null: case AnimationStatus.reverse: case AnimationStatus.dismissed: break; @@ -620,11 +617,6 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { // (even these tooltips are still hovered), // iii. The last hovering device leaves the tooltip. void _handleMouseEnter(PointerEnterEvent event) { - // The callback is also used in an OverlayEntry, so there's a chance that - // this widget is already unmounted. - if (!mounted) { - return; - } // _handleMouseEnter is only called when the mouse starts to hover over this // tooltip (including the actual tooltip it shows on the overlay), and this // tooltip is the first to be hit in the widget tree's hit testing order. @@ -646,10 +638,9 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { } void _handleMouseExit(PointerExitEvent event) { - if (!mounted) { + if (_activeHoveringPointerDevices.isEmpty) { return; } - assert(_activeHoveringPointerDevices.isNotEmpty); _activeHoveringPointerDevices.remove(event.device); if (_activeHoveringPointerDevices.isEmpty) { _scheduleDismissTooltip(withDelay: _hoverShowDuration); @@ -695,104 +686,95 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { void didChangeDependencies() { super.didChangeDependencies(); _visible = TooltipVisibility.of(context); + _tooltipTheme = TooltipTheme.of(context); } // https://material.io/components/tooltips#specs double _getDefaultTooltipHeight() { - final ThemeData theme = Theme.of(context); - switch (theme.platform) { - case TargetPlatform.macOS: - case TargetPlatform.linux: - case TargetPlatform.windows: - return 24.0; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.iOS: - return 32.0; - } + return switch (Theme.of(context).platform) { + TargetPlatform.macOS || + TargetPlatform.linux || + TargetPlatform.windows => 24.0, + TargetPlatform.android || + TargetPlatform.fuchsia || + TargetPlatform.iOS => 32.0, + }; } EdgeInsets _getDefaultPadding() { - final ThemeData theme = Theme.of(context); - switch (theme.platform) { - case TargetPlatform.macOS: - case TargetPlatform.linux: - case TargetPlatform.windows: - return const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0); - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.iOS: - return const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0); - } + return switch (Theme.of(context).platform) { + TargetPlatform.macOS || + TargetPlatform.linux || + TargetPlatform.windows => const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + TargetPlatform.android || + TargetPlatform.fuchsia || + TargetPlatform.iOS => const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), + }; } - double _getDefaultFontSize() { - final ThemeData theme = Theme.of(context); - switch (theme.platform) { - case TargetPlatform.macOS: - case TargetPlatform.linux: - case TargetPlatform.windows: - return 12.0; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.iOS: - return 14.0; - } + static double _getDefaultFontSize(TargetPlatform platform) { + return switch (platform) { + TargetPlatform.macOS || + TargetPlatform.linux || + TargetPlatform.windows => 12.0, + TargetPlatform.android || + TargetPlatform.fuchsia || + TargetPlatform.iOS => 14.0, + }; } - void _createNewEntry() { - final OverlayState overlayState = Overlay.of( - context, - debugRequiredFor: widget, - ); - - final RenderBox box = context.findRenderObject()! as RenderBox; + Widget _buildTooltipOverlay(BuildContext context) { + final OverlayState overlayState = Overlay.of(context, debugRequiredFor: widget); + final RenderBox box = this.context.findRenderObject()! as RenderBox; final Offset target = box.localToGlobal( box.size.center(Offset.zero), ancestor: overlayState.context.findRenderObject(), ); - // We create this widget outside of the overlay entry's builder to prevent - // updated values from happening to leak into the overlay when the overlay - // rebuilds. - final Widget overlay = Directionality( - textDirection: Directionality.of(context), - child: _TooltipOverlay( - richMessage: widget.richMessage ?? TextSpan(text: widget.message), - height: _height, - padding: _padding, - margin: _margin, - onEnter: _handleMouseEnter, - onExit: _handleMouseExit, - decoration: _decoration, - textStyle: _textStyle, - textAlign: _textAlign, - animation: CurvedAnimation( - parent: _controller, - curve: Curves.fastOutSlowIn, - ), - target: target, - verticalOffset: _verticalOffset, - preferBelow: _preferBelow, + final (TextStyle defaultTextStyle, BoxDecoration defaultDecoration) = switch (Theme.of(context)) { + ThemeData(brightness: Brightness.dark, :final TextTheme textTheme, :final TargetPlatform platform) => ( + textTheme.bodyMedium!.copyWith(color: Colors.black, fontSize: _getDefaultFontSize(platform)), + BoxDecoration(color: Colors.white.withOpacity(0.9), borderRadius: const BorderRadius.all(Radius.circular(4))), ), + ThemeData(brightness: Brightness.light, :final TextTheme textTheme, :final TargetPlatform platform) => ( + textTheme.bodyMedium!.copyWith(color: Colors.white, fontSize: _getDefaultFontSize(platform)), + BoxDecoration(color: Colors.grey[700]!.withOpacity(0.9), borderRadius: const BorderRadius.all(Radius.circular(4))), + ), + }; + + final TooltipThemeData tooltipTheme = _tooltipTheme; + final _TooltipOverlay overlayChild = _TooltipOverlay( + richMessage: widget.richMessage ?? TextSpan(text: widget.message), + height: widget.height ?? tooltipTheme.height ?? _getDefaultTooltipHeight(), + padding: widget.padding ?? tooltipTheme.padding ?? _getDefaultPadding(), + margin: widget.margin ?? tooltipTheme.margin ?? _defaultMargin, + onEnter: _handleMouseEnter, + onExit: _handleMouseExit, + decoration: widget.decoration ?? tooltipTheme.decoration ?? defaultDecoration, + textStyle: widget.textStyle ?? tooltipTheme.textStyle ?? defaultTextStyle, + textAlign: widget.textAlign ?? tooltipTheme.textAlign ?? _defaultTextAlign, + animation: CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn), + target: target, + verticalOffset: widget.verticalOffset ?? tooltipTheme.verticalOffset ?? _defaultVerticalOffset, + preferBelow: widget.preferBelow ?? tooltipTheme.preferBelow ?? _defaultPreferBelow, ); - final OverlayEntry entry = _entry = OverlayEntry(builder: (BuildContext context) => overlay); - overlayState.insert(entry); - Tooltip._openedTooltips.add(this); - } - void _removeEntry() { - Tooltip._openedTooltips.remove(this); - _entry?.remove(); - _entry?.dispose(); - _entry = null; + return SelectionContainer.maybeOf(context) == null + ? overlayChild + : SelectionContainer.disabled(child: overlayChild); } @override void dispose() { GestureBinding.instance.pointerRouter.removeGlobalRoute(_handleGlobalPointerEvent); - _removeEntry(); + Tooltip._openedTooltips.remove(this); + // _longPressRecognizer.dispose() and _tapRecognizer.dispose() may call + // their registered onCancel callbacks if there's a gesture in progress. + // Remove the onCancel callbacks to prevent the registered callbacks from + // triggering unnecessary side effects (such as animations). + _longPressRecognizer?.onLongPressCancel = null; _longPressRecognizer?.dispose(); + _tapRecognizer?.onTapCancel = null; _tapRecognizer?.dispose(); _timer?.cancel(); _backingController?.dispose(); @@ -808,47 +790,9 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { return widget.child ?? const SizedBox.shrink(); } assert(debugCheckHasOverlay(context)); - final ThemeData theme = Theme.of(context); - final TooltipThemeData tooltipTheme = TooltipTheme.of(context); - final TextStyle defaultTextStyle; - final BoxDecoration defaultDecoration; - if (theme.brightness == Brightness.dark) { - defaultTextStyle = theme.textTheme.bodyMedium!.copyWith( - color: Colors.black, - fontSize: _getDefaultFontSize(), - ); - defaultDecoration = BoxDecoration( - color: Colors.white.withOpacity(0.9), - borderRadius: const BorderRadius.all(Radius.circular(4)), - ); - } else { - defaultTextStyle = theme.textTheme.bodyMedium!.copyWith( - color: Colors.white, - fontSize: _getDefaultFontSize(), - ); - defaultDecoration = BoxDecoration( - color: Colors.grey[700]!.withOpacity(0.9), - borderRadius: const BorderRadius.all(Radius.circular(4)), - ); - } - - _height = widget.height ?? tooltipTheme.height ?? _getDefaultTooltipHeight(); - _padding = widget.padding ?? tooltipTheme.padding ?? _getDefaultPadding(); - _margin = widget.margin ?? tooltipTheme.margin ?? _defaultMargin; - _verticalOffset = widget.verticalOffset ?? tooltipTheme.verticalOffset ?? _defaultVerticalOffset; - _preferBelow = widget.preferBelow ?? tooltipTheme.preferBelow ?? _defaultPreferBelow; - _excludeFromSemantics = widget.excludeFromSemantics ?? tooltipTheme.excludeFromSemantics ?? _defaultExcludeFromSemantics; - _decoration = widget.decoration ?? tooltipTheme.decoration ?? defaultDecoration; - _textStyle = widget.textStyle ?? tooltipTheme.textStyle ?? defaultTextStyle; - _textAlign = widget.textAlign ?? tooltipTheme.textAlign ?? _defaultTextAlign; - _waitDuration = widget.waitDuration ?? tooltipTheme.waitDuration ?? _defaultWaitDuration; - _showDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultShowDuration; - _hoverShowDuration = widget.showDuration ?? tooltipTheme.showDuration ?? _defaultHoverShowDuration; - _triggerMode = widget.triggerMode ?? tooltipTheme.triggerMode ?? _defaultTriggerMode; - _enableFeedback = widget.enableFeedback ?? tooltipTheme.enableFeedback ?? _defaultEnableFeedback; - + final bool excludeFromSemantics = widget.excludeFromSemantics ?? _tooltipTheme.excludeFromSemantics ?? _defaultExcludeFromSemantics; Widget result = Semantics( - tooltip: _excludeFromSemantics ? null : _tooltipMessage, + tooltip: excludeFromSemantics ? null : _tooltipMessage, child: widget.child, ); @@ -864,8 +808,11 @@ class TooltipState extends State<Tooltip> with SingleTickerProviderStateMixin { ), ); } - - return result; + return OverlayPortal( + controller: _overlayController, + overlayChildBuilder: _buildTooltipOverlay, + child: result, + ); } } diff --git a/packages/flutter/lib/src/material/typography.dart b/packages/flutter/lib/src/material/typography.dart index 5d57ca8ce4645..16e463b14a87c 100644 --- a/packages/flutter/lib/src/material/typography.dart +++ b/packages/flutter/lib/src/material/typography.dart @@ -748,8 +748,6 @@ class Typography with Diagnosticable { // Design token database by the script: // dev/tools/gen_defaults/bin/gen_defaults.dart. -// Token database version: v0_162 - class _M3Typography { _M3Typography._(); diff --git a/packages/flutter/lib/src/painting/_network_image_io.dart b/packages/flutter/lib/src/painting/_network_image_io.dart index 827c781dd766d..8105227deda09 100644 --- a/packages/flutter/lib/src/painting/_network_image_io.dart +++ b/packages/flutter/lib/src/painting/_network_image_io.dart @@ -99,14 +99,14 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm static final HttpClient _sharedHttpClient = HttpClient()..autoUncompress = false; static HttpClient get _httpClient { - HttpClient client = _sharedHttpClient; + HttpClient? client; assert(() { if (debugNetworkImageHttpClientProvider != null) { client = debugNetworkImageHttpClientProvider!(); } return true; }()); - return client; + return client ?? _sharedHttpClient; } Future<ui.Codec> _loadAsync( diff --git a/packages/flutter/lib/src/painting/box_decoration.dart b/packages/flutter/lib/src/painting/box_decoration.dart index 8385edf65f307..444eb40608130 100644 --- a/packages/flutter/lib/src/painting/box_decoration.dart +++ b/packages/flutter/lib/src/painting/box_decoration.dart @@ -68,6 +68,8 @@ import 'image_provider.dart'; /// /// * [DecoratedBox] and [Container], widgets that can be configured with /// [BoxDecoration] objects. +/// * [DecoratedSliver], a widget that can be configured with a [BoxDecoration] +/// that is converted to render with slivers. /// * [CustomPaint], a widget that lets you draw arbitrary graphics. /// * [Decoration], the base class which lets you define other decorations. class BoxDecoration extends Decoration { diff --git a/packages/flutter/lib/src/painting/decoration_image.dart b/packages/flutter/lib/src/painting/decoration_image.dart index 92cdecc0d2f31..87ed9e0eefeaf 100644 --- a/packages/flutter/lib/src/painting/decoration_image.dart +++ b/packages/flutter/lib/src/painting/decoration_image.dart @@ -613,7 +613,7 @@ void paintImage({ developer.postEvent( 'Flutter.ImageSizesForFrame', <String, Object>{ - for (ImageSizeInfo imageSizeInfo in _pendingImageSizeInfo.values) + for (final ImageSizeInfo imageSizeInfo in _pendingImageSizeInfo.values) imageSizeInfo.source!: imageSizeInfo.toJson(), }, ); diff --git a/packages/flutter/lib/src/painting/geometry.dart b/packages/flutter/lib/src/painting/geometry.dart index a5f14871fc6dd..e57d3cfad2a89 100644 --- a/packages/flutter/lib/src/painting/geometry.dart +++ b/packages/flutter/lib/src/painting/geometry.dart @@ -48,7 +48,7 @@ Offset positionDependentBox({ // VERTICAL DIRECTION final bool fitsBelow = target.dy + verticalOffset + childSize.height <= size.height - margin; final bool fitsAbove = target.dy - verticalOffset - childSize.height >= margin; - final bool tooltipBelow = preferBelow ? fitsBelow || !fitsAbove : !(fitsAbove || !fitsBelow); + final bool tooltipBelow = fitsAbove == fitsBelow ? preferBelow : fitsBelow; final double y; if (tooltipBelow) { y = math.min(target.dy + verticalOffset, size.height - margin); @@ -56,19 +56,11 @@ Offset positionDependentBox({ y = math.max(target.dy - verticalOffset - childSize.height, margin); } // HORIZONTAL DIRECTION - final double x; - if (size.width - margin * 2.0 < childSize.width) { - x = (size.width - childSize.width) / 2.0; - } else { - final double normalizedTargetX = clampDouble(target.dx, margin, size.width - margin); - final double edge = margin + childSize.width / 2.0; - if (normalizedTargetX < edge) { - x = margin; - } else if (normalizedTargetX > size.width - edge) { - x = size.width - margin - childSize.width; - } else { - x = normalizedTargetX - childSize.width / 2.0; - } - } + final double flexibleSpace = size.width - childSize.width; + final double x = flexibleSpace <= 2 * margin + // If there's not enough horizontal space for margin + child, center the + // child. + ? flexibleSpace / 2.0 + : clampDouble(target.dx - childSize.width / 2, margin, flexibleSpace - margin); return Offset(x, y); } diff --git a/packages/flutter/lib/src/painting/image_resolution.dart b/packages/flutter/lib/src/painting/image_resolution.dart index 58d3acf71af3d..2644a05f72583 100644 --- a/packages/flutter/lib/src/painting/image_resolution.dart +++ b/packages/flutter/lib/src/painting/image_resolution.dart @@ -327,14 +327,10 @@ class AssetImage extends AssetBundleImageProvider { } AssetMetadata _chooseVariant(String mainAssetKey, ImageConfiguration config, Iterable<AssetMetadata>? candidateVariants) { - if (candidateVariants == null) { + if (candidateVariants == null || candidateVariants.isEmpty || config.devicePixelRatio == null) { return AssetMetadata(key: mainAssetKey, targetDevicePixelRatio: null, main: true); } - if (config.devicePixelRatio == null) { - return candidateVariants.firstWhere((AssetMetadata variant) => variant.main); - } - final SplayTreeMap<double, AssetMetadata> candidatesByDevicePixelRatio = SplayTreeMap<double, AssetMetadata>(); for (final AssetMetadata candidate in candidateVariants) { diff --git a/packages/flutter/lib/src/painting/inline_span.dart b/packages/flutter/lib/src/painting/inline_span.dart index 8398549f71a7a..9037b99c09a04 100644 --- a/packages/flutter/lib/src/painting/inline_span.dart +++ b/packages/flutter/lib/src/painting/inline_span.dart @@ -395,9 +395,6 @@ abstract class InlineSpan extends DiagnosticableTree { void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.whitespace; - - if (style != null) { - style!.debugFillProperties(properties); - } + style?.debugFillProperties(properties); } } diff --git a/packages/flutter/lib/src/painting/placeholder_span.dart b/packages/flutter/lib/src/painting/placeholder_span.dart index 35c912ab69f2f..628a374102f57 100644 --- a/packages/flutter/lib/src/painting/placeholder_span.dart +++ b/packages/flutter/lib/src/painting/placeholder_span.dart @@ -15,12 +15,16 @@ import 'text_style.dart'; /// An immutable placeholder that is embedded inline within text. /// /// [PlaceholderSpan] represents a placeholder that acts as a stand-in for other -/// content. A [PlaceholderSpan] by itself does not contain useful -/// information to change a [TextSpan]. Instead, this class must be extended -/// to define contents. +/// content. A [PlaceholderSpan] by itself does not contain useful information +/// to change a [TextSpan]. [WidgetSpan] from the widgets library extends +/// [PlaceholderSpan] and may be used instead to specify a widget as the contents +/// of the placeholder. +/// +/// Flutter widgets such as [TextField], [Text] and [RichText] do not recognize +/// [PlaceholderSpan] subclasses other than [WidgetSpan]. **Consider +/// implementing the [WidgetSpan] interface instead of the [Placeholder] +/// interface.** /// -/// [WidgetSpan] from the widgets library extends [PlaceholderSpan] and may be -/// used instead to specify a widget as the contents of the placeholder. /// /// See also: /// @@ -89,4 +93,10 @@ abstract class PlaceholderSpan extends InlineSpan { properties.add(EnumProperty<ui.PlaceholderAlignment>('alignment', alignment, defaultValue: null)); properties.add(EnumProperty<TextBaseline>('baseline', baseline, defaultValue: null)); } + + @override + bool debugAssertIsValid() { + assert(false, 'Consider implementing the WidgetSpan interface instead.'); + return super.debugAssertIsValid(); + } } diff --git a/packages/flutter/lib/src/painting/shape_decoration.dart b/packages/flutter/lib/src/painting/shape_decoration.dart index 9909128b05356..20785bced2150 100644 --- a/packages/flutter/lib/src/painting/shape_decoration.dart +++ b/packages/flutter/lib/src/painting/shape_decoration.dart @@ -237,7 +237,7 @@ class ShapeDecoration extends Decoration { return ShapeDecoration( color: Color.lerp(a?.color, b?.color, t), gradient: Gradient.lerp(a?.gradient, b?.gradient, t), - image: t < 0.5 ? a!.image : b!.image, // TODO(ianh): cross-fade the image + image: t < 0.5 ? a?.image : b?.image, // TODO(ianh): cross-fade the image shadows: BoxShadow.lerpList(a?.shadows, b?.shadows, t), shape: ShapeBorder.lerp(a?.shape, b?.shape, t)!, ); diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 377ae42a9e3cf..08ed58c612960 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -124,7 +124,14 @@ class PlaceholderDimensions { @override String toString() { - return 'PlaceholderDimensions($size, $baseline${baselineOffset == null ? ", $baselineOffset" : ""})'; + return switch (alignment) { + ui.PlaceholderAlignment.top || + ui.PlaceholderAlignment.bottom || + ui.PlaceholderAlignment.middle || + ui.PlaceholderAlignment.aboveBaseline || + ui.PlaceholderAlignment.belowBaseline => 'PlaceholderDimensions($size, $alignment)', + ui.PlaceholderAlignment.baseline => 'PlaceholderDimensions($size, $alignment($baselineOffset from top))', + }; } } @@ -271,9 +278,13 @@ class _TextLayout { // object when it's no logner needed. ui.Paragraph _paragraph; + /// Whether to enable the rounding in _applyFloatingPointHack and SkParagraph. + static const bool _shouldApplyFloatingPointHack = !bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK'); + // TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/31707 // remove this hack as well as the flooring in `layout`. - static double _applyFloatingPointHack(double layoutValue) => layoutValue.ceilToDouble(); + @pragma('vm:prefer-inline') + static double _applyFloatingPointHack(double layoutValue) => _shouldApplyFloatingPointHack ? layoutValue.ceilToDouble() : layoutValue; /// Whether this layout has been invalidated and disposed. /// @@ -351,13 +362,16 @@ class _TextPainterLayoutCacheWithOffset { static double _contentWidthFor(double minWidth, double maxWidth, TextWidthBasis widthBasis, _TextLayout layout) { // TODO(LongCatIsLooong): remove the rounding when _applyFloatingPointHack // is removed. - minWidth = minWidth.floorToDouble(); - maxWidth = maxWidth.floorToDouble(); + if (_TextLayout._shouldApplyFloatingPointHack) { + minWidth = minWidth.floorToDouble(); + maxWidth = maxWidth.floorToDouble(); + } return switch (widthBasis) { TextWidthBasis.longestLine => clampDouble(layout.longestLine, minWidth, maxWidth), TextWidthBasis.parent => clampDouble(layout.maxIntrinsicLineExtent, minWidth, maxWidth), }; } + // Try to resize the contentWidth to fit the new input constraints, by just // adjusting the paint offset (so no line-breaking changes needed). // @@ -387,8 +401,8 @@ class _TextPainterLayoutCacheWithOffset { assert(paragraph.width == double.infinity); return false; } - final double maxIntrinsicWidth = layout._paragraph.maxIntrinsicWidth; - if ((layout._paragraph.width - maxIntrinsicWidth) > -precisionErrorTolerance && (maxWidth - maxIntrinsicWidth) > -precisionErrorTolerance) { + final double maxIntrinsicWidth = paragraph.maxIntrinsicWidth; + if ((paragraph.width - maxIntrinsicWidth) > -precisionErrorTolerance && (maxWidth - maxIntrinsicWidth) > -precisionErrorTolerance) { // Adjust the paintOffset and contentWidth to the new input constraints. contentWidth = newContentWidth; return true; @@ -859,16 +873,6 @@ class TextPainter { return rawBoxes.map((TextBox box) => _shiftTextBox(box, offset)).toList(growable: false); } - /// An ordered list of scales for each placeholder in the paragraph. - /// - /// The scale is used as a multiplier on the height, width and baselineOffset of - /// the placeholder. Scale is primarily used to handle accessibility scaling. - /// - /// Each scale corresponds to a [PlaceholderSpan] in the order they were defined - /// in the [InlineSpan] tree. - List<double>? get inlinePlaceholderScales => _inlinePlaceholderScales; - List<double>? _inlinePlaceholderScales; - /// Sets the dimensions of each placeholder in [text]. /// /// The number of [PlaceholderDimensions] provided should be the same as the @@ -888,7 +892,7 @@ class TextPainter { if (span is PlaceholderSpan) { placeholderCount += 1; } - return value.length >= placeholderCount ; + return value.length >= placeholderCount; }); return placeholderCount == value.length; }()); @@ -1025,7 +1029,6 @@ class TextPainter { ui.Paragraph _createParagraph(InlineSpan text) { final ui.ParagraphBuilder builder = ui.ParagraphBuilder(_createParagraphStyle()); text.build(builder, textScaleFactor: textScaleFactor, dimensions: _placeholderDimensions); - _inlinePlaceholderScales = builder.placeholderScales; assert(() { _debugMarkNeedsLayoutCallStack = null; return true; @@ -1129,7 +1132,7 @@ class TextPainter { return true; }()); - final ui.Paragraph paragraph = layoutCache.layout._paragraph; + final ui.Paragraph paragraph = layoutCache.paragraph; // Unfortunately even if we know that there is only paint changes, there's // no API to only make those updates so the paragraph has to be recreated // and re-laid out. diff --git a/packages/flutter/lib/src/painting/text_style.dart b/packages/flutter/lib/src/painting/text_style.dart index 527a67735475a..b00e0191d0184 100644 --- a/packages/flutter/lib/src/painting/text_style.dart +++ b/packages/flutter/lib/src/painting/text_style.dart @@ -595,14 +595,19 @@ class TextStyle with Diagnosticable { // in the [fontFamilyFallback] getter. final String? _package; - /// The size of glyphs (in logical pixels) to use when painting the text. + /// The size of fonts (in logical pixels) to use when painting the text. + /// + /// The value specified matches the dimension of the + /// [em square](https://fonts.google.com/knowledge/glossary/em) of the + /// underlying font, and more often then not isn't exactly the height or the + /// width of glyphs in the font. /// /// During painting, the [fontSize] is multiplied by the current /// `textScaleFactor` to let users make it easier to read text by increasing /// its size. /// - /// [getParagraphStyle] will default to 14 logical pixels if the font size - /// isn't specified here. + /// The [getParagraphStyle] method defaults to 14 logical pixels if [fontSize] + /// is set to null. final double? fontSize; /// The typeface thickness to use when painting the text (e.g., bold). diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index fa40444a894f8..a90e062c42114 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:developer'; import 'dart:ui' as ui show SemanticsUpdate; import 'package:flutter/foundation.dart'; @@ -315,18 +314,21 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture @visibleForTesting void initMouseTracker([MouseTracker? tracker]) { _mouseTracker?.dispose(); - _mouseTracker = tracker ?? MouseTracker(); + _mouseTracker = tracker ?? MouseTracker((Offset position, int viewId) { + final HitTestResult result = HitTestResult(); + hitTestInView(result, position, viewId); + return result; + }); } @override // from GestureBinding void dispatchEvent(PointerEvent event, HitTestResult? hitTestResult) { _mouseTracker!.updateWithEvent( event, - // Enter and exit events should be triggered with or without buttons - // pressed. When the button is pressed, normal hit test uses a cached + // When the button is pressed, normal hit test uses a cached // result, but MouseTracker requires that the hit test is re-executed to // update the hovering events. - () => (hitTestResult == null || event is PointerMoveEvent) ? renderView.hitTestMouseTrackers(event.position) : hitTestResult, + event is PointerMoveEvent ? null : hitTestResult, ); super.dispatchEvent(event, hitTestResult); } @@ -372,7 +374,7 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture _debugMouseTrackerUpdateScheduled = false; return true; }()); - _mouseTracker!.updateAllDevices(renderView.hitTestMouseTrackers); + _mouseTracker!.updateAllDevices(); }); } @@ -504,13 +506,13 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture await super.performReassemble(); if (BindingBase.debugReassembleConfig?.widgetName == null) { if (!kReleaseMode) { - Timeline.startSync('Preparing Hot Reload (layout)'); + FlutterTimeline.startSync('Preparing Hot Reload (layout)'); } try { renderView.reassemble(); } finally { if (!kReleaseMode) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } } } @@ -518,10 +520,19 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture await endOfFrame; } + late final int _implicitViewId = platformDispatcher.implicitView!.viewId; + @override - void hitTest(HitTestResult result, Offset position) { + void hitTestInView(HitTestResult result, Offset position, int viewId) { + // Currently Flutter only supports one view, the implicit view `renderView`. + // TODO(dkwingsmt): After Flutter supports multi-view, look up the correct + // render view for the ID. + // https://github.com/flutter/flutter/issues/121573 + assert(viewId == _implicitViewId, + 'Unexpected view ID $viewId (expecting implicit view ID $_implicitViewId)'); + assert(viewId == renderView.flutterView.viewId); renderView.hitTest(result, position: position); - super.hitTest(result, position); + super.hitTestInView(result, position, viewId); } Future<void> _forceRepaint() { diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart index 0832908d6ee6f..6a9a00791ef15 100644 --- a/packages/flutter/lib/src/rendering/box.dart +++ b/packages/flutter/lib/src/rendering/box.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:developer' show Timeline; import 'dart:math' as math; import 'dart:ui' as ui show lerpDouble; @@ -1396,7 +1395,7 @@ abstract class RenderBox extends RenderObject { }()); if (!kReleaseMode) { if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) { - Timeline.startSync( + FlutterTimeline.startSync( '$runtimeType intrinsics', arguments: debugTimelineArguments, ); @@ -1411,7 +1410,7 @@ abstract class RenderBox extends RenderObject { if (!kReleaseMode) { _debugIntrinsicsDepth -= 1; if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } } return result; @@ -1832,7 +1831,7 @@ abstract class RenderBox extends RenderObject { }()); if (!kReleaseMode) { if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) { - Timeline.startSync( + FlutterTimeline.startSync( '$runtimeType.getDryLayout', arguments: debugTimelineArguments, ); @@ -1844,7 +1843,7 @@ abstract class RenderBox extends RenderObject { if (!kReleaseMode) { _debugIntrinsicsDepth -= 1; if (debugProfileLayoutsEnabled || _debugIntrinsicsDepth == 0) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } } return result; @@ -1985,7 +1984,7 @@ abstract class RenderBox extends RenderObject { } return true; }()); - return _size!; + return _size ?? (throw StateError('RenderBox was not laid out: $runtimeType#${shortHash(this)}')); } Size? _size; /// Setting the size, in debug mode, triggers some analysis of the render box, @@ -2106,7 +2105,7 @@ abstract class RenderBox extends RenderObject { @override void debugResetSize() { // updates the value of size._canBeUsedByParent if necessary - size = size; + size = size; // ignore: no_self_assignments } Map<TextBaseline, double?>? _cachedBaselines; @@ -2136,7 +2135,6 @@ abstract class RenderBox extends RenderObject { assert(!_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.'); assert(!debugNeedsLayout); assert(() { - final RenderObject? parent = this.parent as RenderObject?; if (owner!.debugDoingLayout) { return (RenderObject.debugActiveLayout == parent) && parent!.debugDoingThisLayout; } @@ -2144,7 +2142,6 @@ abstract class RenderBox extends RenderObject { return ((RenderObject.debugActivePaint == parent) && parent!.debugDoingThisPaint) || ((RenderObject.debugActivePaint == this) && debugDoingThisPaint); } - assert(parent == this.parent); return false; }()); assert(_debugSetDoingBaseline(true)); @@ -2170,8 +2167,7 @@ abstract class RenderBox extends RenderObject { double? getDistanceToActualBaseline(TextBaseline baseline) { assert(_debugDoingBaseline, 'Please see the documentation for computeDistanceToActualBaseline for the required calling conventions of this method.'); _cachedBaselines ??= <TextBaseline, double?>{}; - _cachedBaselines!.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline)); - return _cachedBaselines![baseline]; + return _cachedBaselines!.putIfAbsent(baseline, () => computeDistanceToActualBaseline(baseline)); } /// Returns the distance from the y-coordinate of the position of the box to diff --git a/packages/flutter/lib/src/rendering/decorated_sliver.dart b/packages/flutter/lib/src/rendering/decorated_sliver.dart new file mode 100644 index 0000000000000..68a85f7348ef2 --- /dev/null +++ b/packages/flutter/lib/src/rendering/decorated_sliver.dart @@ -0,0 +1,124 @@ +// 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 'object.dart'; +import 'proxy_box.dart'; +import 'proxy_sliver.dart'; +import 'sliver.dart'; + +/// Paints a [Decoration] either before or after its child paints. +/// +/// If the child has infinite scroll extent, then the [Decoration] paints itself up to the +/// bottom cache extent. +class RenderDecoratedSliver extends RenderProxySliver { + /// Creates a decorated sliver. + /// + /// The [decoration], [position], and [configuration] arguments must not be + /// null. By default the decoration paints behind the child. + /// + /// The [ImageConfiguration] will be passed to the decoration (with the size + /// filled in) to let it resolve images. + RenderDecoratedSliver({ + required Decoration decoration, + DecorationPosition position = DecorationPosition.background, + ImageConfiguration configuration = ImageConfiguration.empty, + }) : _decoration = decoration, + _position = position, + _configuration = configuration; + + /// What decoration to paint. + /// + /// Commonly a [BoxDecoration]. + Decoration get decoration => _decoration; + Decoration _decoration; + set decoration(Decoration value) { + if (value == decoration) { + return; + } + _decoration = value; + _painter?.dispose(); + _painter = decoration.createBoxPainter(markNeedsPaint); + markNeedsPaint(); + } + + /// Whether to paint the box decoration behind or in front of the child. + DecorationPosition get position => _position; + DecorationPosition _position; + set position(DecorationPosition value) { + if (value == position) { + return; + } + _position = value; + markNeedsPaint(); + } + + /// The settings to pass to the decoration when painting, so that it can + /// resolve images appropriately. See [ImageProvider.resolve] and + /// [BoxPainter.paint]. + /// + /// The [ImageConfiguration.textDirection] field is also used by + /// direction-sensitive [Decoration]s for painting and hit-testing. + ImageConfiguration get configuration => _configuration; + ImageConfiguration _configuration; + set configuration(ImageConfiguration value) { + if (value == configuration) { + return; + } + _configuration = value; + markNeedsPaint(); + } + + BoxPainter? _painter; + + @override + void attach(covariant PipelineOwner owner) { + _painter = decoration.createBoxPainter(markNeedsPaint); + super.attach(owner); + } + + @override + void detach() { + _painter?.dispose(); + _painter = null; + super.detach(); + } + + @override + void dispose() { + _painter?.dispose(); + _painter = null; + super.dispose(); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (child != null && child!.geometry!.visible) { + final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData; + final Size childSize; + final Offset scrollOffset; + + // In the case where the child sliver has infinite scroll extent, the decoration + // should only extend down to the bottom cache extent. + final double cappedMainAxisExtent = child!.geometry!.scrollExtent.isInfinite + ? constraints.scrollOffset + child!.geometry!.cacheExtent + constraints.cacheOrigin + : child!.geometry!.scrollExtent; + switch (constraints.axis) { + case Axis.vertical: + childSize = Size(constraints.crossAxisExtent, cappedMainAxisExtent); + scrollOffset = Offset(0.0, -constraints.scrollOffset); + case Axis.horizontal: + childSize = Size(cappedMainAxisExtent, constraints.crossAxisExtent); + scrollOffset = Offset(-constraints.scrollOffset, 0.0); + } + final Offset childOffset = offset + childParentData.paintOffset; + if (position == DecorationPosition.background) { + _painter!.paint(context.canvas, childOffset + scrollOffset, configuration.copyWith(size: childSize)); + } + context.paintChild(child!, childOffset); + if (position == DecorationPosition.foreground) { + _painter!.paint(context.canvas, childOffset + scrollOffset, configuration.copyWith(size: childSize)); + } + } + } +} diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index ffdad4cff6228..a357bde585572 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -15,6 +15,7 @@ import 'package:flutter/services.dart'; import 'box.dart'; import 'custom_paint.dart'; import 'layer.dart'; +import 'layout_helper.dart'; import 'object.dart'; import 'paragraph.dart'; import 'viewport_offset.dart'; @@ -24,15 +25,10 @@ const double _kCaretHeightOffset = 2.0; // pixels // The additional size on the x and y axis with which to expand the prototype // cursor to render the floating cursor in pixels. -const EdgeInsets _kFloatingCaretSizeIncrease = EdgeInsets.symmetric(horizontal: 0.5, vertical: 1.0); +const EdgeInsets _kFloatingCursorSizeIncrease = EdgeInsets.symmetric(horizontal: 0.5, vertical: 1.0); // The corner radius of the floating cursor in pixels. -const Radius _kFloatingCaretRadius = Radius.circular(1.0); - -/// Signature for the callback that reports when the caret location changes. -/// -/// Used by [RenderEditable.onCaretChanged]. -typedef CaretChangedHandler = void Function(Rect caretRect); +const Radius _kFloatingCursorRadius = Radius.circular(1.0); /// Represents the coordinates of the point in a selection, and the text /// direction at that point, relative to top left of the [RenderEditable] that @@ -259,13 +255,10 @@ class VerticalCaretMovementRun implements Iterator<TextPosition> { /// position. The cursor is shown while [showCursor] is true. It is painted in /// the [cursorColor]. /// -/// If, when the render object paints, the caret is found to have changed -/// location, [onCaretChanged] is called. -/// /// Keyboard handling, IME handling, scrolling, toggling the [showCursor] value /// to actually blink the cursor, and other features not mentioned above are the /// responsibility of higher layers and not handled by this object. -class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ContainerRenderObjectMixin<RenderBox, TextParentData>, RenderBoxContainerDefaultsMixin<RenderBox, TextParentData> implements TextLayoutMetrics { +class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ContainerRenderObjectMixin<RenderBox, TextParentData>, RenderInlineChildrenContainerDefaults implements TextLayoutMetrics { /// Creates a render object that implements the visual aspects of a text field. /// /// The [textAlign] argument must not be null. It defaults to [TextAlign.start]. @@ -298,7 +291,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, double textScaleFactor = 1.0, TextSelection? selection, required ViewportOffset offset, - this.onCaretChanged, this.ignorePointer = false, bool readOnly = false, bool forceLine = true, @@ -385,14 +377,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, _updateForegroundPainter(foregroundPainter); _updatePainter(painter); addAll(children); - _extractPlaceholderSpans(text); - } - - @override - void setupParentData(RenderBox child) { - if (child.parentData is! TextParentData) { - child.parentData = TextParentData(); - } } /// Child render objects @@ -435,17 +419,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, _foregroundPainter = newPainter; } - late List<PlaceholderSpan> _placeholderSpans; - void _extractPlaceholderSpans(InlineSpan? span) { - _placeholderSpans = <PlaceholderSpan>[]; - span?.visitChildren((InlineSpan span) { - if (span is PlaceholderSpan) { - _placeholderSpans.add(span); - } - return true; - }); - } - /// The [RenderEditablePainter] to use for painting above this /// [RenderEditable]'s text content. /// @@ -492,8 +465,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, } // Caret Painters: - // The floating painter. This painter paints the regular caret as well. - late final _FloatingCursorPainter _caretPainter = _FloatingCursorPainter(_onCaretChanged); + // A single painter for both the regular caret and the floating cursor. + late final _CaretPainter _caretPainter = _CaretPainter(); // Text Highlight painters: final _TextHighlightPainter _selectionPainter = _TextHighlightPainter(); @@ -533,19 +506,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ); } - Rect? _lastCaretRect; - // TODO(LongCatIsLooong): currently EditableText uses this callback to keep - // the text field visible. But we don't always paint the caret, for example - // when the selection is not collapsed. - /// Called during the paint phase when the caret location changes. - CaretChangedHandler? onCaretChanged; - void _onCaretChanged(Rect caretRect) { - if (_lastCaretRect != caretRect) { - onCaretChanged?.call(caretRect); - } - _lastCaretRect = onCaretChanged == null ? null : caretRect; - } - /// Whether the [handleEvent] will propagate pointer events to selection /// handlers. /// @@ -826,7 +786,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, _textPainter.text = value; _cachedAttributedValue = null; _cachedCombinedSemanticsInfos = null; - _extractPlaceholderSpans(value); + _canComputeIntrinsicsCached = null; markNeedsTextLayout(); markNeedsSemanticsUpdate(); } @@ -1287,7 +1247,9 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, // [assembleSemanticsNode] invocations. LinkedHashMap<Key, SemanticsNode>? _cachedChildNodes; - /// Returns a list of rects that bound the given selection. + /// Returns a list of rects that bound the given selection, and the text + /// direction. The text direction is used by the engine to calculate + /// the closest position to a given point. /// /// See [TextPainter.getBoxesForSelection] for more details. List<TextBox> getBoxesForSelection(TextSelection selection) { @@ -1412,13 +1374,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, children.elementAt(childIndex).isTagged(PlaceholderSpanIndexSemanticsTag(placeholderIndex))) { final SemanticsNode childNode = children.elementAt(childIndex); final TextParentData parentData = child!.parentData! as TextParentData; - assert(parentData.scale != null); - childNode.rect = Rect.fromLTWH( - childNode.rect.left, - childNode.rect.top, - childNode.rect.width * parentData.scale!, - childNode.rect.height * parentData.scale!, - ); + assert(parentData.offset != null); newChildren.add(childNode); childIndex += 1; } @@ -1931,9 +1887,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, @override @protected bool hitTestChildren(BoxHitTestResult result, { required Offset position }) { - // Hit test text spans. - bool hitText = false; - final InlineSpan? textSpan = _textPainter.text; if (textSpan != null) { final Offset effectivePosition = position - _paintOffset; @@ -1941,42 +1894,10 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, final Object? span = textSpan.getSpanForPosition(textPosition); if (span is HitTestTarget) { result.add(HitTestEntry(span)); - hitText = true; - } - } - // Hit test render object children - RenderBox? child = firstChild; - int childIndex = 0; - while (child != null && childIndex < _textPainter.inlinePlaceholderBoxes!.length) { - final TextParentData textParentData = child.parentData! as TextParentData; - final Matrix4 transform = Matrix4.translationValues( - textParentData.offset.dx, - textParentData.offset.dy, - 0.0, - )..scale( - textParentData.scale, - textParentData.scale, - textParentData.scale, - ); - final bool isHit = result.addWithPaintTransform( - transform: transform, - position: position, - hitTest: (BoxHitTestResult result, Offset transformed) { - assert(() { - final Offset manualPosition = (position - textParentData.offset) / textParentData.scale!; - return (transformed.dx - manualPosition.dx).abs() < precisionErrorTolerance - && (transformed.dy - manualPosition.dy).abs() < precisionErrorTolerance; - }()); - return child!.hitTest(result, position: transformed); - }, - ); - if (isHit) { return true; } - child = childAfter(child); - childIndex += 1; } - return hitText; + return hitTestInlineChildren(result, position); } late TapGestureRecognizer _tap; @@ -2124,9 +2045,9 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, void selectWordsInRange({ required Offset from, Offset? to, required SelectionChangedCause cause }) { _computeTextMetricsIfNeeded(); final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset)); - final TextSelection fromWord = _getWordAtOffset(fromPosition); + final TextSelection fromWord = getWordAtOffset(fromPosition); final TextPosition toPosition = to == null ? fromPosition : _textPainter.getPositionForOffset(globalToLocal(to - _paintOffset)); - final TextSelection toWord = toPosition == fromPosition ? fromWord : _getWordAtOffset(toPosition); + final TextSelection toWord = toPosition == fromPosition ? fromWord : getWordAtOffset(toPosition); final bool isFromWordBeforeToWord = fromWord.start < toWord.end; _setSelection( @@ -2156,7 +2077,10 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, _setSelection(newSelection, cause); } - TextSelection _getWordAtOffset(TextPosition position) { + /// Returns a [TextSelection] that encompasses the word at the given + /// [TextPosition]. + @visibleForTesting + TextSelection getWordAtOffset(TextPosition position) { debugAssertLayoutUpToDate(); // When long-pressing past the end of the text, we want a collapsed cursor. if (position.offset >= plainText.length) { @@ -2177,6 +2101,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, case TextAffinity.downstream: effectiveOffset = position.offset; } + assert(effectiveOffset >= 0); // On iOS, select the previous word if there is a previous word, or select // to the end of the next word if there is a next word. Select nothing if @@ -2185,8 +2110,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, // If the platform is Android and the text is read only, try to select the // previous word if there is one; otherwise, select the single whitespace at // the position. - if (TextLayoutMetrics.isWhitespace(plainText.codeUnitAt(effectiveOffset)) - && effectiveOffset > 0) { + if (effectiveOffset > 0 + && TextLayoutMetrics.isWhitespace(plainText.codeUnitAt(effectiveOffset))) { final TextRange? previousWord = _getPreviousWord(word.start); switch (defaultTargetPlatform) { case TargetPlatform.iOS: @@ -2235,77 +2160,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, // restored to the original values before final layout and painting. List<PlaceholderDimensions>? _placeholderDimensions; - // Layout the child inline widgets. We then pass the dimensions of the - // children to _textPainter so that appropriate placeholders can be inserted - // into the LibTxt layout. This does not do anything if no inline widgets were - // specified. - List<PlaceholderDimensions> _layoutChildren(BoxConstraints constraints, {bool dry = false}) { - if (childCount == 0) { - _textPainter.setPlaceholderDimensions(<PlaceholderDimensions>[]); - return <PlaceholderDimensions>[]; - } - RenderBox? child = firstChild; - final List<PlaceholderDimensions> placeholderDimensions = List<PlaceholderDimensions>.filled(childCount, PlaceholderDimensions.empty); - int childIndex = 0; - // Only constrain the width to the maximum width of the paragraph. - // Leave height unconstrained, which will overflow if expanded past. - BoxConstraints boxConstraints = BoxConstraints(maxWidth: constraints.maxWidth); - // The content will be enlarged by textScaleFactor during painting phase. - // We reduce constraints by textScaleFactor, so that the content will fit - // into the box once it is enlarged. - boxConstraints = boxConstraints / textScaleFactor; - while (child != null) { - double? baselineOffset; - final Size childSize; - if (!dry) { - child.layout( - boxConstraints, - parentUsesSize: true, - ); - childSize = child.size; - switch (_placeholderSpans[childIndex].alignment) { - case ui.PlaceholderAlignment.baseline: - baselineOffset = child.getDistanceToBaseline( - _placeholderSpans[childIndex].baseline!, - ); - case ui.PlaceholderAlignment.aboveBaseline: - case ui.PlaceholderAlignment.belowBaseline: - case ui.PlaceholderAlignment.bottom: - case ui.PlaceholderAlignment.middle: - case ui.PlaceholderAlignment.top: - baselineOffset = null; - } - } else { - assert(_placeholderSpans[childIndex].alignment != ui.PlaceholderAlignment.baseline); - childSize = child.getDryLayout(boxConstraints); - } - placeholderDimensions[childIndex] = PlaceholderDimensions( - size: childSize, - alignment: _placeholderSpans[childIndex].alignment, - baseline: _placeholderSpans[childIndex].baseline, - baselineOffset: baselineOffset, - ); - child = childAfter(child); - childIndex += 1; - } - return placeholderDimensions; - } - - void _setParentData() { - RenderBox? child = firstChild; - int childIndex = 0; - while (child != null && childIndex < _textPainter.inlinePlaceholderBoxes!.length) { - final TextParentData textParentData = child.parentData! as TextParentData; - textParentData.offset = Offset( - _textPainter.inlinePlaceholderBoxes![childIndex].left, - _textPainter.inlinePlaceholderBoxes![childIndex].top, - ); - textParentData.scale = _textPainter.inlinePlaceholderScales![childIndex]; - child = childAfter(child); - childIndex += 1; - } - } - void _layoutText({ double minWidth = 0.0, double maxWidth = double.infinity }) { final double availableMaxWidth = math.max(0.0, maxWidth - _caretMargin); final double availableMinWidth = math.min(minWidth, availableMaxWidth); @@ -2377,34 +2231,31 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ); } - bool _canComputeDryLayout() { - // Dry layout cannot be calculated without a full layout for - // alignments that require the baseline (baseline, aboveBaseline, - // belowBaseline). - for (final PlaceholderSpan span in _placeholderSpans) { - switch (span.alignment) { - case ui.PlaceholderAlignment.baseline: - case ui.PlaceholderAlignment.aboveBaseline: - case ui.PlaceholderAlignment.belowBaseline: - return false; - case ui.PlaceholderAlignment.top: - case ui.PlaceholderAlignment.middle: - case ui.PlaceholderAlignment.bottom: - continue; - } - } - return true; + bool _canComputeDryLayoutForInlineWidgets() { + return text?.visitChildren((InlineSpan span) { + return (span is! PlaceholderSpan) || switch (span.alignment) { + ui.PlaceholderAlignment.baseline || + ui.PlaceholderAlignment.aboveBaseline || + ui.PlaceholderAlignment.belowBaseline => false, + ui.PlaceholderAlignment.top || + ui.PlaceholderAlignment.middle || + ui.PlaceholderAlignment.bottom => true, + }; + }) ?? true; } + bool? _canComputeIntrinsicsCached; + bool get _canComputeIntrinsics => _canComputeIntrinsicsCached ??= _canComputeDryLayoutForInlineWidgets(); + @override Size computeDryLayout(BoxConstraints constraints) { - if (!_canComputeDryLayout()) { + if (!_canComputeIntrinsics) { assert(debugCannotComputeDryLayout( reason: 'Dry layout not available for alignments that require baseline.', )); return Size.zero; } - _textPainter.setPlaceholderDimensions(_layoutChildren(constraints, dry: true)); + _textPainter.setPlaceholderDimensions(layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.dryLayoutChild)); _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); final double width = forceLine ? constraints.maxWidth : constraints .constrainWidth(_textPainter.size.width + _caretMargin); @@ -2414,10 +2265,10 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, @override void performLayout() { final BoxConstraints constraints = this.constraints; - _placeholderDimensions = _layoutChildren(constraints); + _placeholderDimensions = layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.layoutChild); _textPainter.setPlaceholderDimensions(_placeholderDimensions); _computeTextMetricsIfNeeded(); - _setParentData(); + positionInlineChildren(_textPainter.inlinePlaceholderBoxes!); _computeCaretPrototype(); // We grab _textPainter.size here because assigning to `size` on the next // line will trigger us to validate our intrinsic sizes, which will change @@ -2523,8 +2374,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, _floatingCursorTextPosition = lastTextPosition; final double? animationValue = _resetFloatingCursorAnimationValue; final EdgeInsets sizeAdjustment = animationValue != null - ? EdgeInsets.lerp(_kFloatingCaretSizeIncrease, EdgeInsets.zero, animationValue)! - : _kFloatingCaretSizeIncrease; + ? EdgeInsets.lerp(_kFloatingCursorSizeIncrease, EdgeInsets.zero, animationValue)! + : _kFloatingCursorSizeIncrease; _caretPainter.floatingCursorRect = sizeAdjustment.inflateRect(_caretPrototype).shift(boundedOffset); } else { _caretPainter.floatingCursorRect = null; @@ -2592,31 +2443,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, } _textPainter.paint(context.canvas, effectiveOffset); - - RenderBox? child = firstChild; - int childIndex = 0; - // childIndex might be out of index of placeholder boxes. This can happen - // if engine truncates children due to ellipsis. Sadly, we would not know - // it until we finish layout, and RenderObject is in immutable state at - // this point. - while (child != null && childIndex < _textPainter.inlinePlaceholderBoxes!.length) { - final TextParentData textParentData = child.parentData! as TextParentData; - - final double scale = textParentData.scale!; - context.pushTransform( - needsCompositing, - effectiveOffset + textParentData.offset, - Matrix4.diagonal3Values(scale, scale, scale), - (PaintingContext context, Offset offset) { - context.paintChild( - child!, - offset, - ); - }, - ); - child = childAfter(child); - childIndex += 1; - } + paintInlineChildren(context, effectiveOffset); if (foregroundChild != null) { context.paintChild(foregroundChild, offset); @@ -2648,6 +2475,14 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, } } + @override + void applyPaintTransform(RenderBox child, Matrix4 transform) { + if (child == _foregroundRenderObject || child == _backgroundRenderObject) { + return; + } + defaultApplyPaintTransform(child, transform); + } + @override void paint(PaintingContext context, Offset offset) { _computeTextMetricsIfNeeded(); @@ -2920,8 +2755,8 @@ class _TextHighlightPainter extends RenderEditablePainter { } } -class _FloatingCursorPainter extends RenderEditablePainter { - _FloatingCursorPainter(this.caretPaintCallback); +class _CaretPainter extends RenderEditablePainter { + _CaretPainter(); bool get shouldPaint => _shouldPaint; bool _shouldPaint = true; @@ -2933,8 +2768,6 @@ class _FloatingCursorPainter extends RenderEditablePainter { notifyListeners(); } - CaretChangedHandler caretPaintCallback; - bool showRegularCaret = false; final Paint caretPaint = Paint(); @@ -3006,7 +2839,6 @@ class _FloatingCursorPainter extends RenderEditablePainter { canvas.drawRRect(caretRRect, caretPaint); } } - caretPaintCallback(integralRect); } @override @@ -3040,7 +2872,7 @@ class _FloatingCursorPainter extends RenderEditablePainter { } canvas.drawRRect( - RRect.fromRectAndRadius(floatingCursorRect, _kFloatingCaretRadius), + RRect.fromRectAndRadius(floatingCursorRect, _kFloatingCursorRadius), floatingCursorPaint..color = floatingCursorColor, ); } @@ -3054,7 +2886,7 @@ class _FloatingCursorPainter extends RenderEditablePainter { if (oldDelegate == null) { return shouldPaint; } - return oldDelegate is! _FloatingCursorPainter + return oldDelegate is! _CaretPainter || oldDelegate.shouldPaint != shouldPaint || oldDelegate.showRegularCaret != showRegularCaret || oldDelegate.caretColor != caretColor diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart index 2caade2c2f741..779574f3f872c 100644 --- a/packages/flutter/lib/src/rendering/layer.dart +++ b/packages/flutter/lib/src/rendering/layer.dart @@ -136,7 +136,7 @@ const String _flutterRenderingLibrary = 'package:flutter/rendering.dart'; /// /// * [RenderView.compositeFrame], which implements this recomposition protocol /// for painting [RenderObject] trees on the display. -abstract class Layer extends AbstractNode with DiagnosticableTreeMixin { +abstract class Layer with DiagnosticableTreeMixin { /// Creates an instance of Layer. Layer() { if (kFlutterMemoryAllocationsEnabled) { @@ -344,8 +344,8 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin { /// /// Only subclasses of [ContainerLayer] can have children in the layer tree. /// All other layer classes are used for leaves in the layer tree. - @override - ContainerLayer? get parent => super.parent as ContainerLayer?; + ContainerLayer? get parent => _parent; + ContainerLayer? _parent; // Whether this layer has any changes since its last call to [addToScene]. // @@ -495,6 +495,71 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin { _needsAddToScene = _needsAddToScene || alwaysNeedsAddToScene; } + /// The owner for this node (null if unattached). + /// + /// The entire subtree that this node belongs to will have the same owner. + Object? get owner => _owner; + Object? _owner; + + /// Whether this node is in a tree whose root is attached to something. + /// + /// This becomes true during the call to [attach]. + /// + /// This becomes false during the call to [detach]. + bool get attached => _owner != null; + + /// Mark this node as attached to the given owner. + /// + /// Typically called only from the [parent]'s [attach] method, and by the + /// [owner] to mark the root of a tree as attached. + /// + /// Subclasses with children should override this method to first call their + /// inherited [attach] method, and then [attach] all their children to the + /// same [owner]. + /// + /// Implementations of this method should start with a call to the inherited + /// method, as in `super.attach(owner)`. + @mustCallSuper + void attach(covariant Object owner) { + assert(_owner == null); + _owner = owner; + } + + /// Mark this node as detached. + /// + /// Typically called only from the [parent]'s [detach], and by the [owner] to + /// mark the root of a tree as detached. + /// + /// Subclasses with children should override this method to first call their + /// inherited [detach] method, and then [detach] all their children. + /// + /// Implementations of this method should end with a call to the inherited + /// method, as in `super.detach()`. + @mustCallSuper + void detach() { + assert(_owner != null); + _owner = null; + assert(parent == null || attached == parent!.attached); + } + + /// The depth of this node in the tree. + /// + /// The depth of nodes in a tree monotonically increases as you traverse down + /// the tree. + int get depth => _depth; + int _depth = 0; + + /// Adjust the [depth] of this node's children, if any. + /// + /// Override this method in subclasses with child nodes to call + /// [ContainerLayer.redepthChild] for each child. Do not call this method + /// directly. + @protected + void redepthChildren() { + // ContainerLayer provides an implementation since its the only one that + // can actually have children. + } + /// This layer's next sibling in the parent layer's child list. Layer? get nextSibling => _nextSibling; Layer? _nextSibling; @@ -503,30 +568,6 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin { Layer? get previousSibling => _previousSibling; Layer? _previousSibling; - @override - void dropChild(Layer child) { - assert(!_debugMutationsLocked); - if (!alwaysNeedsAddToScene) { - markNeedsAddToScene(); - } - if (child._compositionCallbackCount != 0) { - _updateSubtreeCompositionObserverCount(-child._compositionCallbackCount); - } - super.dropChild(child); - } - - @override - void adoptChild(Layer child) { - assert(!_debugMutationsLocked); - if (!alwaysNeedsAddToScene) { - markNeedsAddToScene(); - } - if (child._compositionCallbackCount != 0) { - _updateSubtreeCompositionObserverCount(child._compositionCallbackCount); - } - super.adoptChild(child); - } - /// Removes this layer from its parent layer's child list. /// /// This has no effect if the layer's parent is already null. @@ -1198,7 +1239,7 @@ class ContainerLayer extends Layer { assert(node != child); // indicates we are about to create a cycle return true; }()); - adoptChild(child); + _adoptChild(child); child._previousSibling = lastChild; if (lastChild != null) { lastChild!._nextSibling = child; @@ -1209,6 +1250,52 @@ class ContainerLayer extends Layer { assert(child.attached == attached); } + void _adoptChild(Layer child) { + assert(!_debugMutationsLocked); + if (!alwaysNeedsAddToScene) { + markNeedsAddToScene(); + } + if (child._compositionCallbackCount != 0) { + _updateSubtreeCompositionObserverCount(child._compositionCallbackCount); + } + assert(child._parent == null); + assert(() { + Layer node = this; + while (node.parent != null) { + node = node.parent!; + } + assert(node != child); // indicates we are about to create a cycle + return true; + }()); + child._parent = this; + if (attached) { + child.attach(_owner!); + } + redepthChild(child); + } + + @override + void redepthChildren() { + Layer? child = firstChild; + while (child != null) { + redepthChild(child); + child = child.nextSibling; + } + } + + /// Adjust the [depth] of the given [child] to be greater than this node's own + /// [depth]. + /// + /// Only call this method from overrides of [redepthChildren]. + @protected + void redepthChild(Layer child) { + assert(child.owner == owner); + if (child._depth <= _depth) { + child._depth = _depth + 1; + child.redepthChildren(); + } + } + // Implementation of [Layer.remove]. void _removeChild(Layer child) { assert(child.parent == this); @@ -1235,11 +1322,27 @@ class ContainerLayer extends Layer { assert(lastChild == null || _debugUltimatePreviousSiblingOf(lastChild!, equals: firstChild)); child._previousSibling = null; child._nextSibling = null; - dropChild(child); + _dropChild(child); child._parentHandle.layer = null; assert(!child.attached); } + void _dropChild(Layer child) { + assert(!_debugMutationsLocked); + if (!alwaysNeedsAddToScene) { + markNeedsAddToScene(); + } + if (child._compositionCallbackCount != 0) { + _updateSubtreeCompositionObserverCount(-child._compositionCallbackCount); + } + assert(child._parent == this); + assert(child.attached == attached); + child._parent = null; + if (attached) { + child.detach(); + } + } + /// Removes all of this layer's children from its child list. void removeAllChildren() { assert(!_debugMutationsLocked); @@ -1249,7 +1352,7 @@ class ContainerLayer extends Layer { child._previousSibling = null; child._nextSibling = null; assert(child.attached == attached); - dropChild(child); + _dropChild(child); child._parentHandle.layer = null; child = next; } diff --git a/packages/flutter/lib/src/rendering/list_wheel_viewport.dart b/packages/flutter/lib/src/rendering/list_wheel_viewport.dart index 82969107f8ea4..48bc762367999 100644 --- a/packages/flutter/lib/src/rendering/list_wheel_viewport.dart +++ b/packages/flutter/lib/src/rendering/list_wheel_viewport.dart @@ -1133,7 +1133,7 @@ class RenderListWheelViewport // `child` will be the last RenderObject before the viewport when walking up from `target`. RenderObject child = target; while (child.parent != this) { - child = child.parent! as RenderObject; + child = child.parent!; } final ListWheelParentData parentData = child.parentData! as ListWheelParentData; diff --git a/packages/flutter/lib/src/rendering/mouse_tracker.dart b/packages/flutter/lib/src/rendering/mouse_tracker.dart index 84eda806693fa..aa2efc7c6cda1 100644 --- a/packages/flutter/lib/src/rendering/mouse_tracker.dart +++ b/packages/flutter/lib/src/rendering/mouse_tracker.dart @@ -20,11 +20,11 @@ export 'package:flutter/services.dart' show MouseCursor, SystemMouseCursors; -/// Signature for searching for [MouseTrackerAnnotation]s at the given offset. +/// Signature for hit testing at the given offset for the specified view. /// /// It is used by the [MouseTracker] to fetch annotations for the mouse /// position. -typedef MouseDetectorAnnotationFinder = HitTestResult Function(Offset offset); +typedef MouseTrackerHitTest = HitTestResult Function(Offset offset, int viewId); // Various states of a connected mouse device used by [MouseTracker]. class _MouseState { @@ -161,6 +161,16 @@ class _MouseTrackerUpdateDetails with Diagnosticable { /// An instance of [MouseTracker] is owned by the global singleton /// [RendererBinding]. class MouseTracker extends ChangeNotifier { + /// Create a mouse tracker. + /// + /// The `hitTestInView` is used to find the render objects on a given + /// position in the specific view. It is typically provided by the + /// [RendererBinding]. + MouseTracker(MouseTrackerHitTest hitTestInView) + : _hitTestInView = hitTestInView; + + final MouseTrackerHitTest _hitTestInView; + final MouseCursorManager _mouseCursorMixin = MouseCursorManager( SystemMouseCursors.basic, ); @@ -224,7 +234,7 @@ class MouseTracker extends ChangeNotifier { || lastEvent.position != event.position; } - LinkedHashMap<MouseTrackerAnnotation, Matrix4> _hitTestResultToAnnotations(HitTestResult result) { + LinkedHashMap<MouseTrackerAnnotation, Matrix4> _hitTestInViewResultToAnnotations(HitTestResult result) { final LinkedHashMap<MouseTrackerAnnotation, Matrix4> annotations = LinkedHashMap<MouseTrackerAnnotation, Matrix4>(); for (final HitTestEntry entry in result.path) { final Object target = entry.target; @@ -240,14 +250,15 @@ class MouseTracker extends ChangeNotifier { // // If the device is not connected or not a mouse, an empty map is returned // without calling `hitTest`. - LinkedHashMap<MouseTrackerAnnotation, Matrix4> _findAnnotations(_MouseState state, MouseDetectorAnnotationFinder hitTest) { + LinkedHashMap<MouseTrackerAnnotation, Matrix4> _findAnnotations(_MouseState state) { final Offset globalPosition = state.latestEvent.position; final int device = state.device; + final int viewId = state.latestEvent.viewId; if (!_mouseStates.containsKey(device)) { return LinkedHashMap<MouseTrackerAnnotation, Matrix4>(); } - return _hitTestResultToAnnotations(hitTest(globalPosition)); + return _hitTestInViewResultToAnnotations(_hitTestInView(globalPosition, viewId)); } // A callback that is called on the update of a device. @@ -279,25 +290,34 @@ class MouseTracker extends ChangeNotifier { /// Whether or not at least one mouse is connected and has produced events. bool get mouseIsConnected => _mouseStates.isNotEmpty; - /// Trigger a device update with a new event and its corresponding hit test - /// result. + /// Perform a device update for one device according to the given new event. + /// + /// The [updateWithEvent] is typically called by [RendererBinding] during the + /// handler of a pointer event. All pointer events should call this method, + /// and let [MouseTracker] filter which to react to. /// - /// The [updateWithEvent] indicates that an event has been observed, and is - /// called during the handler of the event. It is typically called by - /// [RendererBinding], and should be called with all events received, and let - /// [MouseTracker] filter which to react to. + /// The `hitTestResult` serves as an optional optimization, and is the hit + /// test result already performed by [RendererBinding] for other gestures. It + /// can be null, but when it's not null, it should be identical to the result + /// from directly calling `hitTestInView` given in the constructor (which + /// means that it should not use the cached result for [PointerMoveEvent]). /// - /// The `getResult` is a function to return the hit test result at the - /// position of the event. It should not return a cached hit test - /// result, because the cache would not change during a tap sequence. - void updateWithEvent(PointerEvent event, ValueGetter<HitTestResult> getResult) { + /// The [updateWithEvent] is one of the two ways of updating mouse + /// states, the other one being [updateAllDevices]. + void updateWithEvent(PointerEvent event, HitTestResult? hitTestResult) { if (event.kind != PointerDeviceKind.mouse) { return; } if (event is PointerSignalEvent) { return; } - final HitTestResult result = event is PointerRemovedEvent ? HitTestResult() : getResult(); + final HitTestResult result; + if (event is PointerRemovedEvent) { + result = HitTestResult(); + } else { + final int viewId = event.viewId; + result = hitTestResult ?? _hitTestInView(event.position, viewId); + } final int device = event.device; final _MouseState? existingState = _mouseStates[device]; if (!_shouldMarkStateDirty(existingState, event)) { @@ -325,7 +345,7 @@ class MouseTracker extends ChangeNotifier { final PointerEvent lastEvent = targetState.replaceLatestEvent(event); final LinkedHashMap<MouseTrackerAnnotation, Matrix4> nextAnnotations = event is PointerRemovedEvent ? LinkedHashMap<MouseTrackerAnnotation, Matrix4>() : - _hitTestResultToAnnotations(result); + _hitTestInViewResultToAnnotations(result); final LinkedHashMap<MouseTrackerAnnotation, Matrix4> lastAnnotations = targetState.replaceAnnotations(nextAnnotations); _handleDeviceUpdate(_MouseTrackerUpdateDetails.byPointerEvent( @@ -338,21 +358,21 @@ class MouseTracker extends ChangeNotifier { }); } - /// Trigger a device update for all detected devices. + /// Perform a device update for all detected devices. /// /// The [updateAllDevices] is typically called during the post frame phase, - /// indicating a frame has passed and all objects have potentially moved. The - /// `hitTest` is a function that acquires the hit test result at a given - /// position, and must not be empty. - /// - /// For each connected device, the [updateAllDevices] will make a hit test on - /// the device's last seen position, and check if necessary changes need to be + /// indicating a frame has passed and all objects have potentially moved. For + /// each connected device, the [updateAllDevices] will make a hit test on the + /// device's last seen position, and check if necessary changes need to be /// made. - void updateAllDevices(MouseDetectorAnnotationFinder hitTest) { + /// + /// The [updateAllDevices] is one of the two ways of updating mouse + /// states, the other one being [updateWithEvent]. + void updateAllDevices() { _deviceUpdatePhase(() { for (final _MouseState dirtyState in _mouseStates.values) { final PointerEvent lastEvent = dirtyState.latestEvent; - final LinkedHashMap<MouseTrackerAnnotation, Matrix4> nextAnnotations = _findAnnotations(dirtyState, hitTest); + final LinkedHashMap<MouseTrackerAnnotation, Matrix4> nextAnnotations = _findAnnotations(dirtyState); final LinkedHashMap<MouseTrackerAnnotation, Matrix4> lastAnnotations = dirtyState.replaceAnnotations(nextAnnotations); _handleDeviceUpdate(_MouseTrackerUpdateDetails.byNewFrame( @@ -384,7 +404,7 @@ class MouseTracker extends ChangeNotifier { final LinkedHashMap<MouseTrackerAnnotation, Matrix4> nextAnnotations = details.nextAnnotations; // Order is important for mouse event callbacks. The - // `_hitTestResultToAnnotations` returns annotations in the visual order + // `_hitTestInViewResultToAnnotations` returns annotations in the visual order // from front to back, called the "hit-test order". The algorithm here is // explained in https://github.com/flutter/flutter/issues/41420 diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 0beedd716ea27..3e42a22ca2127 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:developer'; import 'dart:ui' as ui show PictureRecorder; import 'dart:ui'; @@ -929,11 +928,9 @@ class PipelineOwner { } /// The unique object managed by this pipeline that has no parent. - /// - /// This object does not have to be a [RenderObject]. - AbstractNode? get rootNode => _rootNode; - AbstractNode? _rootNode; - set rootNode(AbstractNode? value) { + RenderObject? get rootNode => _rootNode; + RenderObject? _rootNode; + set rootNode(RenderObject? value) { if (_rootNode == value) { return; } @@ -988,7 +985,7 @@ class PipelineOwner { } return true; }()); - Timeline.startSync( + FlutterTimeline.startSync( 'LAYOUT', arguments: debugTimelineArguments, ); @@ -1037,7 +1034,7 @@ class PipelineOwner { return true; }()); if (!kReleaseMode) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } } } @@ -1076,7 +1073,7 @@ class PipelineOwner { /// [flushPaint]. void flushCompositingBits() { if (!kReleaseMode) { - Timeline.startSync('UPDATING COMPOSITING BITS'); + FlutterTimeline.startSync('UPDATING COMPOSITING BITS'); } _nodesNeedingCompositingBitsUpdate.sort((RenderObject a, RenderObject b) => a.depth - b.depth); for (final RenderObject node in _nodesNeedingCompositingBitsUpdate) { @@ -1090,7 +1087,7 @@ class PipelineOwner { } assert(_nodesNeedingCompositingBitsUpdate.isEmpty, 'Child PipelineOwners must not dirty nodes in their parent.'); if (!kReleaseMode) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } } @@ -1124,7 +1121,7 @@ class PipelineOwner { } return true; }()); - Timeline.startSync( + FlutterTimeline.startSync( 'PAINT', arguments: debugTimelineArguments, ); @@ -1163,7 +1160,7 @@ class PipelineOwner { return true; }()); if (!kReleaseMode) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } } } @@ -1252,7 +1249,7 @@ class PipelineOwner { return; } if (!kReleaseMode) { - Timeline.startSync('SEMANTICS'); + FlutterTimeline.startSync('SEMANTICS'); } assert(_semanticsOwner != null); assert(() { @@ -1279,7 +1276,7 @@ class PipelineOwner { return true; }()); if (!kReleaseMode) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } } } @@ -1572,7 +1569,7 @@ const String _flutterRenderingLibrary = 'package:flutter/rendering.dart'; /// [RenderObject.markNeedsLayout] so that if a parent has queried the intrinsic /// or baseline information, it gets marked dirty whenever the child's geometry /// changes. -abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget { +abstract class RenderObject with DiagnosticableTreeMixin implements HitTestTarget { /// Initializes internal fields for subclasses. RenderObject() { if (kFlutterMemoryAllocationsEnabled) { @@ -1690,30 +1687,93 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im } } + /// The depth of this node in the tree. + /// + /// The depth of nodes in a tree monotonically increases as you traverse down + /// the tree. + /// + /// Nodes always have a [depth] greater than their ancestors'. There's no + /// guarantee regarding depth between siblings. The depth of a node is used to + /// ensure that nodes are processed in depth order. The [depth] of a child can + /// be more than one greater than the [depth] of the parent, because the [depth] + /// values are never decreased: all that matters is that it's greater than the + /// parent. Consider a tree with a root node A, a child B, and a grandchild C. + /// Initially, A will have [depth] 0, B [depth] 1, and C [depth] 2. If C is + /// moved to be a child of A, sibling of B, then the numbers won't change. C's + /// [depth] will still be 2. The [depth] is automatically maintained by the + /// [adoptChild] and [dropChild] methods. + int get depth => _depth; + int _depth = 0; + + /// Adjust the [depth] of the given [child] to be greater than this node's own + /// [depth]. + /// + /// Only call this method from overrides of [redepthChildren]. + @protected + void redepthChild(RenderObject child) { + assert(child.owner == owner); + if (child._depth <= _depth) { + child._depth = _depth + 1; + child.redepthChildren(); + } + } + + /// Adjust the [depth] of this node's children, if any. + /// + /// Override this method in subclasses with child nodes to call [redepthChild] + /// for each child. Do not call this method directly. + @protected + void redepthChildren() { } + + /// The parent of this node in the tree. + RenderObject? get parent => _parent; + RenderObject? _parent; + /// Called by subclasses when they decide a render object is a child. /// /// Only for use by subclasses when changing their child lists. Calling this /// in other cases will lead to an inconsistent tree and probably cause crashes. - @override + @mustCallSuper + @protected void adoptChild(RenderObject child) { + assert(child._parent == null); + assert(() { + RenderObject node = this; + while (node.parent != null) { + node = node.parent!; + } + assert(node != child); // indicates we are about to create a cycle + return true; + }()); + setupParentData(child); markNeedsLayout(); markNeedsCompositingBitsUpdate(); markNeedsSemanticsUpdate(); - super.adoptChild(child); + child._parent = this; + if (attached) { + child.attach(_owner!); + } + redepthChild(child); } /// Called by subclasses when they decide a render object is no longer a child. /// /// Only for use by subclasses when changing their child lists. Calling this /// in other cases will lead to an inconsistent tree and probably cause crashes. - @override + @mustCallSuper + @protected void dropChild(RenderObject child) { + assert(child._parent == this); + assert(child.attached == attached); assert(child.parentData != null); child._cleanRelayoutBoundary(); child.parentData!.detach(); child.parentData = null; - super.dropChild(child); + child._parent = null; + if (attached) { + child.detach(); + } markNeedsLayout(); markNeedsCompositingBitsUpdate(); markNeedsSemanticsUpdate(); @@ -1851,7 +1911,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im } if (!activeLayoutRoot._debugMutationsLocked) { - final AbstractNode? p = activeLayoutRoot.debugLayoutParent; + final RenderObject? p = activeLayoutRoot.debugLayoutParent; activeLayoutRoot = p is RenderObject ? p : null; } else { // activeLayoutRoot found. @@ -1946,20 +2006,41 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im RenderObject? get debugLayoutParent { RenderObject? layoutParent; assert(() { - final AbstractNode? parent = this.parent; - layoutParent = parent is RenderObject? ? parent : null; + layoutParent = parent; return true; }()); return layoutParent; } - @override - PipelineOwner? get owner => super.owner as PipelineOwner?; + /// The owner for this node (null if unattached). + /// + /// The entire subtree that this node belongs to will have the same owner. + PipelineOwner? get owner => _owner; + PipelineOwner? _owner; - @override + /// Whether this node is in a tree whose root is attached to something. + /// + /// This becomes true during the call to [attach]. + /// + /// This becomes false during the call to [detach]. + bool get attached => _owner != null; + + /// Mark this node as attached to the given owner. + /// + /// Typically called only from the [parent]'s [attach] method, and by the + /// [owner] to mark the root of a tree as attached. + /// + /// Subclasses with children should override this method to first call their + /// inherited [attach] method, and then [attach] all their children to the + /// same [owner]. + /// + /// Implementations of this method should start with a call to the inherited + /// method, as in `super.attach(owner)`. + @mustCallSuper void attach(PipelineOwner owner) { assert(!_debugDisposed); - super.attach(owner); + assert(_owner == null); + _owner = owner; // If the node was dirtied in some way while unattached, make sure to add // it to the appropriate dirty list now that an owner is available if (_needsLayout && _relayoutBoundary != null) { @@ -1986,6 +2067,23 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im } } + /// Mark this node as detached. + /// + /// Typically called only from the [parent]'s [detach], and by the [owner] to + /// mark the root of a tree as detached. + /// + /// Subclasses with children should override this method to first call their + /// inherited [detach] method, and then [detach] all their children. + /// + /// Implementations of this method should end with a call to the inherited + /// method, as in `super.detach()`. + @mustCallSuper + void detach() { + assert(_owner != null); + _owner = null; + assert(parent == null || attached == parent!.attached); + } + /// Whether this render object's layout information is dirty. /// /// This is only set in debug mode. In general, render objects should not need @@ -2050,7 +2148,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im while (node != _relayoutBoundary) { assert(node._relayoutBoundary == _relayoutBoundary); assert(node.parent != null); - node = node.parent! as RenderObject; + node = node.parent!; if ((!node._needsLayout) && (!node._debugDoingThisLayout)) { return false; } @@ -2144,7 +2242,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im assert(_debugCanPerformMutations); _needsLayout = true; assert(this.parent != null); - final RenderObject parent = this.parent! as RenderObject; + final RenderObject parent = this.parent!; if (!_doingThisLayoutWithCallback) { parent.markNeedsLayout(); } else { @@ -2176,7 +2274,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im if (_relayoutBoundary == this) { return; } - final RenderObject? parentRelayoutBoundary = (parent as RenderObject?)?._relayoutBoundary; + final RenderObject? parentRelayoutBoundary = parent?._relayoutBoundary; assert(parentRelayoutBoundary != null); if (parentRelayoutBoundary != _relayoutBoundary) { _relayoutBoundary = parentRelayoutBoundary; @@ -2280,7 +2378,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im } return true; }()); - Timeline.startSync( + FlutterTimeline.startSync( '$runtimeType', arguments: debugTimelineArguments, ); @@ -2290,10 +2388,11 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im informationCollector: () { final List<String> stack = StackTrace.current.toString().split('\n'); int? targetFrame; - final Pattern layoutFramePattern = RegExp(r'^#[0-9]+ +RenderObject.layout \('); + final Pattern layoutFramePattern = RegExp(r'^#[0-9]+ +Render(?:Object|Box).layout \('); for (int i = 0; i < stack.length; i += 1) { if (layoutFramePattern.matchAsPrefix(stack[i]) != null) { targetFrame = i + 1; + } else if (targetFrame != null) { break; } } @@ -2301,7 +2400,6 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im final Pattern targetFramePattern = RegExp(r'^#[0-9]+ +(.+)$'); final Match? targetFrameMatch = targetFramePattern.matchAsPrefix(stack[targetFrame]); final String? problemFunction = (targetFrameMatch != null && targetFrameMatch.groupCount > 0) ? targetFrameMatch.group(1) : stack[targetFrame].trim(); - // TODO(jacobr): this case is similar to displaying a single stack frame. return <DiagnosticsNode>[ ErrorDescription( "These invalid constraints were provided to $runtimeType's layout() " @@ -2317,7 +2415,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im assert(!_debugDoingThisResize); assert(!_debugDoingThisLayout); final bool isRelayoutBoundary = !parentUsesSize || sizedByParent || constraints.isTight || parent is! RenderObject; - final RenderObject relayoutBoundary = isRelayoutBoundary ? this : (parent! as RenderObject)._relayoutBoundary!; + final RenderObject relayoutBoundary = isRelayoutBoundary ? this : parent!._relayoutBoundary!; assert(() { _debugCanParentUseSize = parentUsesSize; return true; @@ -2344,7 +2442,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im } if (!kReleaseMode && debugProfileLayoutsEnabled) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } return; } @@ -2411,7 +2509,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im markNeedsPaint(); if (!kReleaseMode && debugProfileLayoutsEnabled) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } } @@ -2674,7 +2772,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im } _needsCompositingBitsUpdate = true; if (parent is RenderObject) { - final RenderObject parent = this.parent! as RenderObject; + final RenderObject parent = this.parent!; if (parent._needsCompositingBitsUpdate) { return; } @@ -2823,9 +2921,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im owner!.requestVisualUpdate(); } } else if (parent is RenderObject) { - final RenderObject parent = this.parent! as RenderObject; - parent.markNeedsPaint(); - assert(parent == this.parent); + parent!.markNeedsPaint(); } else { assert(() { if (debugPrintMarkNeedsPaintStacks) { @@ -2896,7 +2992,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im assert(_needsPaint || _needsCompositedLayerUpdate); assert(_layerHandle.layer != null); assert(!_layerHandle.layer!.attached); - AbstractNode? node = parent; + RenderObject? node = parent; while (node is RenderObject) { if (node.isRepaintBoundary) { if (node._layerHandle.layer == null) { @@ -2985,7 +3081,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im } return true; }()); - Timeline.startSync( + FlutterTimeline.startSync( '$runtimeType', arguments: debugTimelineArguments, ); @@ -2993,7 +3089,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im assert(() { if (_needsCompositingBitsUpdate) { if (parent is RenderObject) { - final RenderObject parent = this.parent! as RenderObject; + final RenderObject parent = this.parent!; bool visitedByParent = false; parent.visitChildren((RenderObject child) { if (child == this) { @@ -3069,7 +3165,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im return true; }()); if (!kReleaseMode && debugProfilePaintsEnabled) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } } @@ -3156,13 +3252,13 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im final bool ancestorSpecified = ancestor != null; assert(attached); if (ancestor == null) { - final AbstractNode? rootNode = owner!.rootNode; + final RenderObject? rootNode = owner!.rootNode; if (rootNode is RenderObject) { ancestor = rootNode; } } final List<RenderObject> renderers = <RenderObject>[]; - for (RenderObject renderer = this; renderer != ancestor; renderer = renderer.parent! as RenderObject) { + for (RenderObject renderer = this; renderer != ancestor; renderer = renderer.parent!) { renderers.add(renderer); assert(renderer.parent != null); // Failed to find ancestor in parent chain. } @@ -3294,8 +3390,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im if (_semantics != null && !_semantics!.isMergedIntoParent) { _semantics!.sendEvent(semanticsEvent); } else if (parent != null) { - final RenderObject renderParent = parent! as RenderObject; - renderParent.sendSemanticsEvent(semanticsEvent); + parent!.sendSemanticsEvent(semanticsEvent); } } @@ -3395,7 +3490,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im mayProduceSiblingNodes = false; } - node = node.parent! as RenderObject; + node = node.parent!; isEffectiveSemanticsBoundary = node._semanticsConfiguration.isSemanticBoundary; if (isEffectiveSemanticsBoundary && node._semantics == null) { // We have reached a semantics boundary that doesn't own a semantics node. @@ -3432,14 +3527,24 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im // The subtree is probably being kept alive by a viewport but not laid out. return; } + if (!kReleaseMode) { + FlutterTimeline.startSync('Semantics.GetFragment'); + } final _SemanticsFragment fragment = _getSemanticsForParent( mergeIntoParent: _semantics?.parent?.isPartOfNodeMerging ?? false, blockUserActions: _semantics?.areUserActionsBlocked ?? false, ); + if (!kReleaseMode) { + FlutterTimeline.finishSync(); + } assert(fragment is _InterestingSemanticsFragment); final _InterestingSemanticsFragment interestingFragment = fragment as _InterestingSemanticsFragment; final List<SemanticsNode> result = <SemanticsNode>[]; final List<SemanticsNode> siblingNodes = <SemanticsNode>[]; + + if (!kReleaseMode) { + FlutterTimeline.startSync('Semantics.compileChildren'); + } interestingFragment.compileChildren( parentSemanticsClipRect: _semantics?.parentSemanticsClipRect, parentPaintClipRect: _semantics?.parentPaintClipRect, @@ -3447,6 +3552,9 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im result: result, siblingNodes: siblingNodes, ); + if (!kReleaseMode) { + FlutterTimeline.finishSync(); + } // Result may contain sibling nodes that are irrelevant for this update. assert(interestingFragment.config == null && result.any((SemanticsNode node) => node == _semantics)); } @@ -3678,9 +3786,9 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im } if (_relayoutBoundary != null && _relayoutBoundary != this) { int count = 1; - RenderObject? target = parent as RenderObject?; + RenderObject? target = parent; while (target != null && target != _relayoutBoundary) { - target = target.parent as RenderObject?; + target = target.parent; count += 1; } header += ' relayoutBoundary=up$count'; @@ -3782,8 +3890,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im Curve curve = Curves.ease, }) { if (parent is RenderObject) { - final RenderObject renderParent = parent! as RenderObject; - renderParent.showOnScreen( + parent!.showOnScreen( descendant: descendant ?? this, rect: rect, duration: duration, @@ -4970,11 +5077,11 @@ class _SemanticsGeometry { Matrix4 clipRectTransform, ) { assert(clipRectTransform.isIdentity()); - RenderObject intermediateParent = child.parent! as RenderObject; + RenderObject intermediateParent = child.parent!; while (intermediateParent != ancestor) { intermediateParent.applyPaintTransform(child, transform); - intermediateParent = intermediateParent.parent! as RenderObject; - child = child.parent! as RenderObject; + intermediateParent = intermediateParent.parent!; + child = child.parent!; } ancestor.applyPaintTransform(child, transform); ancestor.applyPaintTransform(child, clipRectTransform); diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index e07283abea7e5..066f6137e715a 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -13,29 +13,13 @@ import 'package:flutter/services.dart'; import 'box.dart'; import 'debug.dart'; -import 'editable.dart'; import 'layer.dart'; +import 'layout_helper.dart'; import 'object.dart'; import 'selection.dart'; const String _kEllipsis = '\u2026'; -/// Parent data for use with [RenderParagraph] and [RenderEditable]. -class TextParentData extends ContainerBoxParentData<RenderBox> { - /// The scaling of the text. - double? scale; - - @override - String toString() { - final List<String> values = <String>[ - 'offset=$offset', - if (scale != null) 'scale=$scale', - super.toString(), - ]; - return values.join('; '); - } -} - /// Used by the [RenderParagraph] to map its rendering children to their /// corresponding semantics nodes. /// @@ -62,11 +46,210 @@ class PlaceholderSpanIndexSemanticsTag extends SemanticsTag { int get hashCode => Object.hash(PlaceholderSpanIndexSemanticsTag, index); } +/// Parent data used by [RenderParagraph] and [RenderEditable] to annotate +/// inline contents (such as [WidgetSpan]s) with. +class TextParentData extends ParentData with ContainerParentDataMixin<RenderBox> { + /// The offset at which to paint the child in the parent's coordinate system. + /// + /// A `null` value indicates this inline widget is not laid out. For instance, + /// when the inline widget has never been laid out, or the inline widget is + /// ellipsized away. + Offset? get offset => _offset; + Offset? _offset; + + /// The [PlaceholderSpan] associated with this render child. + /// + /// This field is usually set by a [ParentDataWidget], and is typically not + /// null when `performLayout` is called. + PlaceholderSpan? span; + + @override + void detach() { + span = null; + _offset = null; + super.detach(); + } + + @override + String toString() =>'widget: $span, ${offset == null ? "not laid out" : "offset: $offset"}'; +} + +/// A mixin that provides useful default behaviors for text [RenderBox]es +/// ([RenderParagraph] and [RenderEditable] for example) with inline content +/// children managed by the [ContainerRenderObjectMixin] mixin. +/// +/// This mixin assumes every child managed by the [ContainerRenderObjectMixin] +/// mixin corresponds to a [PlaceholderSpan], and they are organized in logical +/// order of the text (the order each [PlaceholderSpan] is encountered when the +/// user reads the text). +/// +/// To use this mixin in a [RenderBox] class: +/// +/// * Call [layoutInlineChildren] in the `performLayout` and `computeDryLayout` +/// implementation, and during intrinsic size calculations, to get the size +/// information of the inline widgets as a `List` of `PlaceholderDimensions`. +/// Determine the positioning of the inline widgets (which is usually done by +/// a [TextPainter] using its line break algorithm). +/// +/// * Call [positionInlineChildren] with the positioning information of the +/// inline widgets. +/// +/// * Implement [RenderBox.applyPaintTransform], optionally with +/// [defaultApplyPaintTransform]. +/// +/// * Call [paintInlineChildren] in [RenderBox.paint] to paint the inline widgets. +/// +/// * Call [hitTestInlineChildren] in [RenderBox.hitTestChildren] to hit test the +/// inline widgets. +/// +/// See also: +/// +/// * [WidgetSpan.extractFromInlineSpan], a helper function for extracting +/// [WidgetSpan]s from an [InlineSpan] tree. +mixin RenderInlineChildrenContainerDefaults on RenderBox, ContainerRenderObjectMixin<RenderBox, TextParentData> { + @override + void setupParentData(RenderBox child) { + if (child.parentData is! TextParentData) { + child.parentData = TextParentData(); + } + } + + static PlaceholderDimensions _layoutChild(RenderBox child, double maxWidth, ChildLayouter layoutChild) { + final TextParentData parentData = child.parentData! as TextParentData; + final PlaceholderSpan? span = parentData.span; + assert(span != null); + return span == null + ? PlaceholderDimensions.empty + : PlaceholderDimensions( + size: layoutChild(child, BoxConstraints(maxWidth: maxWidth)), + alignment: span.alignment, + baseline: span.baseline, + baselineOffset: switch (span.alignment) { + ui.PlaceholderAlignment.aboveBaseline || + ui.PlaceholderAlignment.belowBaseline || + ui.PlaceholderAlignment.bottom || + ui.PlaceholderAlignment.middle || + ui.PlaceholderAlignment.top => null, + ui.PlaceholderAlignment.baseline => child.getDistanceToBaseline(span.baseline!), + }, + ); + } + + /// Computes the layout for every inline child using the given `layoutChild` + /// function and the `maxWidth` constraint. + /// + /// Returns a list of [PlaceholderDimensions], representing the layout results + /// for each child managed by the [ContainerRenderObjectMixin] mixin. + /// + /// Since this method does not impose a maximum height constraint on the + /// inline children, some children may become taller than this [RenderBox]. + /// + /// See also: + /// + /// * [TextPainter.setPlaceholderDimensions], the method that usually takes + /// the layout results from this method as the input. + @protected + List<PlaceholderDimensions> layoutInlineChildren(double maxWidth, ChildLayouter layoutChild) { + return <PlaceholderDimensions>[ + for (RenderBox? child = firstChild; child != null; child = childAfter(child)) + _layoutChild(child, maxWidth, layoutChild), + ]; + } + + /// Positions each inline child according to the coordinates provided in the + /// `boxes` list. + /// + /// The `boxes` list must be in logical order, which is the order each child + /// is encountered when the user reads the text. Usually the length of the + /// list equals [childCount], but it can be less than that, when some children + /// are ommitted due to ellipsing. It never exceeds [childCount]. + /// + /// See also: + /// + /// * [TextPainter.inlinePlaceholderBoxes], the method that can be used to + /// get the input `boxes`. + @protected + void positionInlineChildren(List<ui.TextBox> boxes) { + RenderBox? child = firstChild; + for (final ui.TextBox box in boxes) { + if (child == null) { + assert(false, 'The length of boxes (${boxes.length}) should be greater than childCount ($childCount)'); + return; + } + final TextParentData textParentData = child.parentData! as TextParentData; + textParentData._offset = Offset(box.left, box.top); + child = childAfter(child); + } + while (child != null) { + final TextParentData textParentData = child.parentData! as TextParentData; + textParentData._offset = null; + child = childAfter(child); + } + } + + /// Applies the transform that would be applied when painting the given child + /// to the given matrix. + /// + /// Render children whose [TextParentData.offset] is null zeros out the + /// `transform` to indicate they're invisible thus should not be painted. + @protected + void defaultApplyPaintTransform(RenderBox child, Matrix4 transform) { + final TextParentData childParentData = child.parentData! as TextParentData; + final Offset? offset = childParentData.offset; + if (offset == null) { + transform.setZero(); + } else { + transform.translate(offset.dx, offset.dy); + } + } + + /// Paints each inline child. + /// + /// Render children whose [TextParentData.offset] is null will be skipped by + /// this method. + @protected + void paintInlineChildren(PaintingContext context, Offset offset) { + RenderBox? child = firstChild; + while (child != null) { + final TextParentData childParentData = child.parentData! as TextParentData; + final Offset? childOffset = childParentData.offset; + if (childOffset == null) { + return; + } + context.paintChild(child, childOffset + offset); + child = childAfter(child); + } + } + + /// Performs a hit test on each inline child. + /// + /// Render children whose [TextParentData.offset] is null will be skipped by + /// this method. + @protected + bool hitTestInlineChildren(BoxHitTestResult result, Offset position) { + RenderBox? child = firstChild; + while (child != null) { + final TextParentData childParentData = child.parentData! as TextParentData; + final Offset? childOffset = childParentData.offset; + if (childOffset == null) { + return false; + } + final bool isHit = result.addWithPaintOffset( + offset: childOffset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) => child!.hitTest(result, position: transformed), + ); + if (isHit) { + return true; + } + child = childAfter(child); + } + return false; + } +} + /// A render object that displays a paragraph of text. -class RenderParagraph extends RenderBox - with ContainerRenderObjectMixin<RenderBox, TextParentData>, - RenderBoxContainerDefaultsMixin<RenderBox, TextParentData>, - RelayoutWhenSystemFontsChangeMixin { +class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBox, TextParentData>, RenderInlineChildrenContainerDefaults, RelayoutWhenSystemFontsChangeMixin { /// Creates a paragraph render object. /// /// The [text], [textAlign], [textDirection], [overflow], [softWrap], and @@ -106,17 +289,9 @@ class RenderParagraph extends RenderBox textHeightBehavior: textHeightBehavior, ) { addAll(children); - _extractPlaceholderSpans(text); this.registrar = registrar; } - @override - void setupParentData(RenderBox child) { - if (child.parentData is! TextParentData) { - child.parentData = TextParentData(); - } - } - static final String _placeholderCharacter = String.fromCharCode(PlaceholderSpan.placeholderCodeUnit); final TextPainter _textPainter; @@ -137,8 +312,8 @@ class RenderParagraph extends RenderBox case RenderComparison.paint: _textPainter.text = value; _cachedAttributedLabels = null; + _canComputeIntrinsicsCached = null; _cachedCombinedSemanticsInfos = null; - _extractPlaceholderSpans(value); markNeedsPaint(); markNeedsSemanticsUpdate(); case RenderComparison.layout: @@ -146,7 +321,7 @@ class RenderParagraph extends RenderBox _overflowShader = null; _cachedAttributedLabels = null; _cachedCombinedSemanticsInfos = null; - _extractPlaceholderSpans(value); + _canComputeIntrinsicsCached = null; markNeedsLayout(); _removeSelectionRegistrarSubscription(); _disposeSelectableFragments(); @@ -256,17 +431,6 @@ class RenderParagraph extends RenderBox super.dispose(); } - late List<PlaceholderSpan> _placeholderSpans; - void _extractPlaceholderSpans(InlineSpan span) { - _placeholderSpans = <PlaceholderSpan>[]; - span.visitChildren((InlineSpan span) { - if (span is PlaceholderSpan) { - _placeholderSpans.add(span); - } - return true; - }); - } - /// How the text should be aligned horizontally. TextAlign get textAlign => _textPainter.textAlign; set textAlign(TextAlign value) { @@ -438,7 +602,10 @@ class RenderParagraph extends RenderBox if (!_canComputeIntrinsics()) { return 0.0; } - _computeChildrenWidthWithMinIntrinsics(height); + _textPainter.setPlaceholderDimensions(layoutInlineChildren( + double.infinity, + (RenderBox child, BoxConstraints constraints) => Size(child.getMinIntrinsicWidth(double.infinity), 0.0), + )); _layoutText(); // layout with infinite width. return _textPainter.minIntrinsicWidth; } @@ -448,7 +615,12 @@ class RenderParagraph extends RenderBox if (!_canComputeIntrinsics()) { return 0.0; } - _computeChildrenWidthWithMaxIntrinsics(height); + _textPainter.setPlaceholderDimensions(layoutInlineChildren( + double.infinity, + // Height and baseline is irrelevant as all text will be laid + // out in a single line. Therefore, using 0.0 as a dummy for the height. + (RenderBox child, BoxConstraints constraints) => Size(child.getMaxIntrinsicWidth(double.infinity), 0.0), + )); _layoutText(); // layout with infinite width. return _textPainter.maxIntrinsicWidth; } @@ -457,7 +629,7 @@ class RenderParagraph extends RenderBox if (!_canComputeIntrinsics()) { return 0.0; } - _computeChildrenHeightWithMinIntrinsics(width); + _textPainter.setPlaceholderDimensions(layoutInlineChildren(width, ChildLayoutHelper.dryLayoutChild)); _layoutText(minWidth: width, maxWidth: width); return _textPainter.height; } @@ -486,84 +658,36 @@ class RenderParagraph extends RenderBox return _textPainter.computeDistanceToActualBaseline(TextBaseline.alphabetic); } + /// Whether all inline widget children of this [RenderBox] support dry layout + /// calculation. + bool _canComputeDryLayoutForInlineWidgets() { + // Dry layout cannot be calculated without a full layout for + // alignments that require the baseline (baseline, aboveBaseline, + // belowBaseline). + return text.visitChildren((InlineSpan span) { + return (span is! PlaceholderSpan) || switch (span.alignment) { + ui.PlaceholderAlignment.baseline || + ui.PlaceholderAlignment.aboveBaseline || + ui.PlaceholderAlignment.belowBaseline => false, + ui.PlaceholderAlignment.top || + ui.PlaceholderAlignment.middle || + ui.PlaceholderAlignment.bottom => true, + }; + }); + } + + bool? _canComputeIntrinsicsCached; // Intrinsics cannot be calculated without a full layout for // alignments that require the baseline (baseline, aboveBaseline, // belowBaseline). bool _canComputeIntrinsics() { - for (final PlaceholderSpan span in _placeholderSpans) { - switch (span.alignment) { - case ui.PlaceholderAlignment.baseline: - case ui.PlaceholderAlignment.aboveBaseline: - case ui.PlaceholderAlignment.belowBaseline: - assert( - RenderObject.debugCheckingIntrinsics, - 'Intrinsics are not available for PlaceholderAlignment.baseline, ' - 'PlaceholderAlignment.aboveBaseline, or PlaceholderAlignment.belowBaseline.', - ); - return false; - case ui.PlaceholderAlignment.top: - case ui.PlaceholderAlignment.middle: - case ui.PlaceholderAlignment.bottom: - continue; - } - } - return true; - } - - void _computeChildrenWidthWithMaxIntrinsics(double height) { - RenderBox? child = firstChild; - final List<PlaceholderDimensions> placeholderDimensions = List<PlaceholderDimensions>.filled(childCount, PlaceholderDimensions.empty); - int childIndex = 0; - while (child != null) { - // Height and baseline is irrelevant as all text will be laid - // out in a single line. Therefore, using 0.0 as a dummy for the height. - placeholderDimensions[childIndex] = PlaceholderDimensions( - size: Size(child.getMaxIntrinsicWidth(double.infinity), 0.0), - alignment: _placeholderSpans[childIndex].alignment, - baseline: _placeholderSpans[childIndex].baseline, - ); - child = childAfter(child); - childIndex += 1; - } - _textPainter.setPlaceholderDimensions(placeholderDimensions); - } - - void _computeChildrenWidthWithMinIntrinsics(double height) { - RenderBox? child = firstChild; - final List<PlaceholderDimensions> placeholderDimensions = List<PlaceholderDimensions>.filled(childCount, PlaceholderDimensions.empty); - int childIndex = 0; - while (child != null) { - // Height and baseline is irrelevant; only looking for the widest word or - // placeholder. Therefore, using 0.0 as a dummy for height. - placeholderDimensions[childIndex] = PlaceholderDimensions( - size: Size(child.getMinIntrinsicWidth(double.infinity), 0.0), - alignment: _placeholderSpans[childIndex].alignment, - baseline: _placeholderSpans[childIndex].baseline, - ); - child = childAfter(child); - childIndex += 1; - } - _textPainter.setPlaceholderDimensions(placeholderDimensions); - } - - void _computeChildrenHeightWithMinIntrinsics(double width) { - RenderBox? child = firstChild; - final List<PlaceholderDimensions> placeholderDimensions = List<PlaceholderDimensions>.filled(childCount, PlaceholderDimensions.empty); - int childIndex = 0; - // Takes textScaleFactor into account because the content of the placeholder - // span will be scaled up when it paints. - width = width / textScaleFactor; - while (child != null) { - final Size size = child.getDryLayout(BoxConstraints(maxWidth: width)); - placeholderDimensions[childIndex] = PlaceholderDimensions( - size: size, - alignment: _placeholderSpans[childIndex].alignment, - baseline: _placeholderSpans[childIndex].baseline, + final bool returnValue = _canComputeIntrinsicsCached ??= _canComputeDryLayoutForInlineWidgets(); + assert( + returnValue || RenderObject.debugCheckingIntrinsics, + 'Intrinsics are not available for PlaceholderAlignment.baseline, ' + 'PlaceholderAlignment.aboveBaseline, or PlaceholderAlignment.belowBaseline.', ); - child = childAfter(child); - childIndex += 1; - } - _textPainter.setPlaceholderDimensions(placeholderDimensions); + return returnValue; } @override @@ -571,48 +695,13 @@ class RenderParagraph extends RenderBox @override bool hitTestChildren(BoxHitTestResult result, { required Offset position }) { - // Hit test text spans. - bool hitText = false; final TextPosition textPosition = _textPainter.getPositionForOffset(position); - final InlineSpan? span = _textPainter.text!.getSpanForPosition(textPosition); - if (span != null && span is HitTestTarget) { - result.add(HitTestEntry(span as HitTestTarget)); - hitText = true; - } - - // Hit test render object children - RenderBox? child = firstChild; - int childIndex = 0; - while (child != null && childIndex < _textPainter.inlinePlaceholderBoxes!.length) { - final TextParentData textParentData = child.parentData! as TextParentData; - final Matrix4 transform = Matrix4.translationValues( - textParentData.offset.dx, - textParentData.offset.dy, - 0.0, - )..scale( - textParentData.scale, - textParentData.scale, - textParentData.scale, - ); - final bool isHit = result.addWithPaintTransform( - transform: transform, - position: position, - hitTest: (BoxHitTestResult result, Offset transformed) { - assert(() { - final Offset manualPosition = (position - textParentData.offset) / textParentData.scale!; - return (transformed.dx - manualPosition.dx).abs() < precisionErrorTolerance - && (transformed.dy - manualPosition.dy).abs() < precisionErrorTolerance; - }()); - return child!.hitTest(result, position: transformed); - }, - ); - if (isHit) { - return true; - } - child = childAfter(child); - childIndex += 1; + final Object? span = _textPainter.text!.getSpanForPosition(textPosition); + if (span is HitTestTarget) { + result.add(HitTestEntry(span)); + return true; } - return hitText; + return hitTestInlineChildren(result, position); } bool _needsClipping = false; @@ -629,9 +718,7 @@ class RenderParagraph extends RenderBox final bool widthMatters = softWrap || overflow == TextOverflow.ellipsis; _textPainter.layout( minWidth: minWidth, - maxWidth: widthMatters ? - maxWidth : - double.infinity, + maxWidth: widthMatters ? maxWidth : double.infinity, ); } @@ -653,106 +740,15 @@ class RenderParagraph extends RenderBox _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); } - // Layout the child inline widgets. We then pass the dimensions of the - // children to _textPainter so that appropriate placeholders can be inserted - // into the LibTxt layout. This does not do anything if no inline widgets were - // specified. - List<PlaceholderDimensions> _layoutChildren(BoxConstraints constraints, {bool dry = false}) { - if (childCount == 0) { - return <PlaceholderDimensions>[]; - } - RenderBox? child = firstChild; - final List<PlaceholderDimensions> placeholderDimensions = List<PlaceholderDimensions>.filled(childCount, PlaceholderDimensions.empty); - int childIndex = 0; - // Only constrain the width to the maximum width of the paragraph. - // Leave height unconstrained, which will overflow if expanded past. - BoxConstraints boxConstraints = BoxConstraints(maxWidth: constraints.maxWidth); - // The content will be enlarged by textScaleFactor during painting phase. - // We reduce constraints by textScaleFactor, so that the content will fit - // into the box once it is enlarged. - boxConstraints = boxConstraints / textScaleFactor; - while (child != null) { - double? baselineOffset; - final Size childSize; - if (!dry) { - child.layout( - boxConstraints, - parentUsesSize: true, - ); - childSize = child.size; - switch (_placeholderSpans[childIndex].alignment) { - case ui.PlaceholderAlignment.baseline: - baselineOffset = child.getDistanceToBaseline( - _placeholderSpans[childIndex].baseline!, - ); - case ui.PlaceholderAlignment.aboveBaseline: - case ui.PlaceholderAlignment.belowBaseline: - case ui.PlaceholderAlignment.bottom: - case ui.PlaceholderAlignment.middle: - case ui.PlaceholderAlignment.top: - baselineOffset = null; - } - } else { - assert(_placeholderSpans[childIndex].alignment != ui.PlaceholderAlignment.baseline); - childSize = child.getDryLayout(boxConstraints); - } - placeholderDimensions[childIndex] = PlaceholderDimensions( - size: childSize, - alignment: _placeholderSpans[childIndex].alignment, - baseline: _placeholderSpans[childIndex].baseline, - baselineOffset: baselineOffset, - ); - child = childAfter(child); - childIndex += 1; - } - return placeholderDimensions; - } - - // Iterate through the laid-out children and set the parentData offsets based - // off of the placeholders inserted for each child. - void _setParentData() { - RenderBox? child = firstChild; - int childIndex = 0; - while (child != null && childIndex < _textPainter.inlinePlaceholderBoxes!.length) { - final TextParentData textParentData = child.parentData! as TextParentData; - textParentData.offset = Offset( - _textPainter.inlinePlaceholderBoxes![childIndex].left, - _textPainter.inlinePlaceholderBoxes![childIndex].top, - ); - textParentData.scale = _textPainter.inlinePlaceholderScales![childIndex]; - child = childAfter(child); - childIndex += 1; - } - } - - bool _canComputeDryLayout() { - // Dry layout cannot be calculated without a full layout for - // alignments that require the baseline (baseline, aboveBaseline, - // belowBaseline). - for (final PlaceholderSpan span in _placeholderSpans) { - switch (span.alignment) { - case ui.PlaceholderAlignment.baseline: - case ui.PlaceholderAlignment.aboveBaseline: - case ui.PlaceholderAlignment.belowBaseline: - return false; - case ui.PlaceholderAlignment.top: - case ui.PlaceholderAlignment.middle: - case ui.PlaceholderAlignment.bottom: - continue; - } - } - return true; - } - @override Size computeDryLayout(BoxConstraints constraints) { - if (!_canComputeDryLayout()) { + if (!_canComputeIntrinsics()) { assert(debugCannotComputeDryLayout( reason: 'Dry layout not available for alignments that require baseline.', )); return Size.zero; } - _textPainter.setPlaceholderDimensions(_layoutChildren(constraints, dry: true)); + _textPainter.setPlaceholderDimensions(layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.dryLayoutChild)); _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); return constraints.constrain(_textPainter.size); } @@ -760,9 +756,9 @@ class RenderParagraph extends RenderBox @override void performLayout() { final BoxConstraints constraints = this.constraints; - _placeholderDimensions = _layoutChildren(constraints); + _placeholderDimensions = layoutInlineChildren(constraints.maxWidth, ChildLayoutHelper.layoutChild); _layoutTextWithConstraints(constraints); - _setParentData(); + positionInlineChildren(_textPainter.inlinePlaceholderBoxes!); // We grab _textPainter.size and _textPainter.didExceedMaxLines here because // assigning to `size` will trigger us to validate our intrinsic sizes, @@ -830,6 +826,11 @@ class RenderParagraph extends RenderBox } } + @override + void applyPaintTransform(RenderBox child, Matrix4 transform) { + defaultApplyPaintTransform(child, transform); + } + @override void paint(PaintingContext context, Offset offset) { // Ideally we could compute the min/max intrinsic width/height with a @@ -864,32 +865,17 @@ class RenderParagraph extends RenderBox } context.canvas.clipRect(bounds); } + + if (_lastSelectableFragments != null) { + for (final _SelectableFragment fragment in _lastSelectableFragments!) { + fragment.paint(context, offset); + } + } + _textPainter.paint(context.canvas, offset); - RenderBox? child = firstChild; - int childIndex = 0; - // childIndex might be out of index of placeholder boxes. This can happen - // if engine truncates children due to ellipsis. Sadly, we would not know - // it until we finish layout, and RenderObject is in immutable state at - // this point. - while (child != null && childIndex < _textPainter.inlinePlaceholderBoxes!.length) { - final TextParentData textParentData = child.parentData! as TextParentData; + paintInlineChildren(context, offset); - final double scale = textParentData.scale!; - context.pushTransform( - needsCompositing, - offset + textParentData.offset, - Matrix4.diagonal3Values(scale, scale, scale), - (PaintingContext context, Offset offset) { - context.paintChild( - child!, - offset, - ); - }, - ); - child = childAfter(child); - childIndex += 1; - } if (_needsClipping) { if (_overflowShader != null) { context.canvas.translate(offset.dx, offset.dy); @@ -900,12 +886,6 @@ class RenderParagraph extends RenderBox } context.canvas.restore(); } - if (_lastSelectableFragments != null) { - for (final _SelectableFragment fragment in _lastSelectableFragments!) { - fragment.paint(context, offset); - } - } - super.paint(context, offset); } /// Returns the offset at which to paint the caret. @@ -1155,15 +1135,8 @@ class RenderParagraph extends RenderBox children.elementAt(childIndex).isTagged(PlaceholderSpanIndexSemanticsTag(placeholderIndex))) { final SemanticsNode childNode = children.elementAt(childIndex); final TextParentData parentData = child!.parentData! as TextParentData; - assert(parentData.scale != null || parentData.offset == Offset.zero); // parentData.scale may be null if the render object is truncated. - if (parentData.scale != null) { - childNode.rect = Rect.fromLTWH( - childNode.rect.left, - childNode.rect.top, - childNode.rect.width * parentData.scale!, - childNode.rect.height * parentData.scale!, - ); + if (parentData.offset != null) { newChildren.add(childNode); } childIndex += 1; @@ -1362,6 +1335,14 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM : paragraph._getOffsetForPosition(TextPosition(offset: selectionEnd)); final bool flipHandles = isReversed != (TextDirection.rtl == paragraph.textDirection); final Matrix4 paragraphToFragmentTransform = getTransformToParagraph()..invert(); + final TextSelection selection = TextSelection( + baseOffset: selectionStart, + extentOffset: selectionEnd, + ); + final List<Rect> selectionRects = <Rect>[]; + for (final TextBox textBox in paragraph.getBoxesForSelection(selection)) { + selectionRects.add(textBox.toRect()); + } return SelectionGeometry( startSelectionPoint: SelectionPoint( localPosition: MatrixUtils.transformPoint(paragraphToFragmentTransform, startOffsetInParagraphCoordinates), @@ -1373,6 +1354,7 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM lineHeight: paragraph._textPainter.preferredLineHeight, handleType: flipHandles ? TextSelectionHandleType.left : TextSelectionHandleType.right, ), + selectionRects: selectionRects, status: _textSelectionStart!.offset == _textSelectionEnd!.offset ? SelectionStatus.collapsed : SelectionStatus.uncollapsed, @@ -1505,12 +1487,17 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM } final TextRange word = paragraph.getWordBoundary(position); assert(word.isNormalized); + if (word.start < range.start && word.end < range.start) { + return SelectionResult.previous; + } else if (word.start > range.end && word.end > range.end) { + return SelectionResult.next; + } // Fragments are separated by placeholder span, the word boundary shouldn't // expand across fragments. assert(word.start >= range.start && word.end <= range.end); late TextPosition start; late TextPosition end; - if (position.offset >= word.end) { + if (position.offset > word.end) { start = end = TextPosition(offset: position.offset); } else { start = TextPosition(offset: word.start); diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index bdc906be0e7af..a089fbe145204 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -70,60 +70,40 @@ mixin RenderProxyBoxMixin<T extends RenderBox> on RenderBox, RenderObjectWithChi @override double computeMinIntrinsicWidth(double height) { - if (child != null) { - return child!.getMinIntrinsicWidth(height); - } - return 0.0; + return child?.getMinIntrinsicWidth(height) ?? 0.0; } @override double computeMaxIntrinsicWidth(double height) { - if (child != null) { - return child!.getMaxIntrinsicWidth(height); - } - return 0.0; + return child?.getMaxIntrinsicWidth(height) ?? 0.0; } @override double computeMinIntrinsicHeight(double width) { - if (child != null) { - return child!.getMinIntrinsicHeight(width); - } - return 0.0; + return child?.getMinIntrinsicHeight(width) ?? 0.0; } @override double computeMaxIntrinsicHeight(double width) { - if (child != null) { - return child!.getMaxIntrinsicHeight(width); - } - return 0.0; + return child?.getMaxIntrinsicHeight(width) ?? 0.0; } @override double? computeDistanceToActualBaseline(TextBaseline baseline) { - if (child != null) { - return child!.getDistanceToActualBaseline(baseline); - } - return super.computeDistanceToActualBaseline(baseline); + return child?.getDistanceToActualBaseline(baseline) + ?? super.computeDistanceToActualBaseline(baseline); } @override Size computeDryLayout(BoxConstraints constraints) { - if (child != null) { - return child!.getDryLayout(constraints); - } - return computeSizeForNoChild(constraints); + return child?.getDryLayout(constraints) ?? computeSizeForNoChild(constraints); } @override void performLayout() { - if (child != null) { - child!.layout(constraints, parentUsesSize: true); - size = child!.size; - } else { - size = computeSizeForNoChild(constraints); - } + size = (child?..layout(constraints, parentUsesSize: true))?.size + ?? computeSizeForNoChild(constraints); + return; } /// Calculate the size the [RenderProxyBox] would have under the given @@ -142,9 +122,11 @@ mixin RenderProxyBoxMixin<T extends RenderBox> on RenderBox, RenderObjectWithChi @override void paint(PaintingContext context, Offset offset) { - if (child != null) { - context.paintChild(child!, offset); + final RenderBox? child = this.child; + if (child == null) { + return; } + context.paintChild(child, offset); } } @@ -2017,7 +1999,6 @@ class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> { _updateClip(); final RRect offsetRRect = _clip!.shift(offset); - final Rect offsetBounds = offsetRRect.outerRect; final Path offsetRRectAsPath = Path()..addRRect(offsetRRect); bool paintShadows = true; assert(() { @@ -2038,14 +2019,6 @@ class RenderPhysicalModel extends _RenderPhysicalModelBase<RRect> { final Canvas canvas = context.canvas; if (elevation != 0.0 && paintShadows) { - // The drawShadow call doesn't add the region of the shadow to the - // picture's bounds, so we draw a hardcoded amount of extra space to - // account for the maximum potential area of the shadow. - // TODO(jsimmons): remove this when Skia does it for us. - canvas.drawRect( - offsetBounds.inflate(20.0), - _transparentPaint, - ); canvas.drawShadow( offsetRRectAsPath, shadowColor, diff --git a/packages/flutter/lib/src/rendering/selection.dart b/packages/flutter/lib/src/rendering/selection.dart index 38a47d7ce3d7b..beaf5a02700cf 100644 --- a/packages/flutter/lib/src/rendering/selection.dart +++ b/packages/flutter/lib/src/rendering/selection.dart @@ -576,8 +576,8 @@ enum SelectionStatus { /// The geometry of the current selection. /// /// This includes details such as the locations of the selection start and end, -/// line height, etc. This information is used for drawing selection controls -/// for mobile platforms. +/// line height, the rects that encompass the selection, etc. This information +/// is used for drawing selection controls for mobile platforms. /// /// The positions in geometry are in local coordinates of the [SelectionHandler] /// or [Selectable]. @@ -590,6 +590,7 @@ class SelectionGeometry { const SelectionGeometry({ this.startSelectionPoint, this.endSelectionPoint, + this.selectionRects = const <Rect>[], required this.status, required this.hasContent, }) : assert((startSelectionPoint == null && endSelectionPoint == null) || status != SelectionStatus.none); @@ -627,6 +628,10 @@ class SelectionGeometry { /// The status of ongoing selection in the [Selectable] or [SelectionHandler]. final SelectionStatus status; + /// The rects in the local coordinates of the containing [Selectable] that + /// represent the selection if there is any. + final List<Rect> selectionRects; + /// Whether there is any selectable content in the [Selectable] or /// [SelectionHandler]. final bool hasContent; @@ -638,12 +643,14 @@ class SelectionGeometry { SelectionGeometry copyWith({ SelectionPoint? startSelectionPoint, SelectionPoint? endSelectionPoint, + List<Rect>? selectionRects, SelectionStatus? status, bool? hasContent, }) { return SelectionGeometry( startSelectionPoint: startSelectionPoint ?? this.startSelectionPoint, endSelectionPoint: endSelectionPoint ?? this.endSelectionPoint, + selectionRects: selectionRects ?? this.selectionRects, status: status ?? this.status, hasContent: hasContent ?? this.hasContent, ); @@ -660,6 +667,7 @@ class SelectionGeometry { return other is SelectionGeometry && other.startSelectionPoint == startSelectionPoint && other.endSelectionPoint == endSelectionPoint + && other.selectionRects == selectionRects && other.status == status && other.hasContent == hasContent; } @@ -669,6 +677,7 @@ class SelectionGeometry { return Object.hash( startSelectionPoint, endSelectionPoint, + selectionRects, status, hasContent, ); diff --git a/packages/flutter/lib/src/rendering/sliver_group.dart b/packages/flutter/lib/src/rendering/sliver_group.dart index 0ef8402115fbf..b47daff6da70e 100644 --- a/packages/flutter/lib/src/rendering/sliver_group.dart +++ b/packages/flutter/lib/src/rendering/sliver_group.dart @@ -76,7 +76,6 @@ class RenderSliverCrossAxisGroup extends RenderSliver with ContainerRenderObject final double extentPerFlexValue = remainingExtent / totalFlex; child = firstChild; - double offset = 0.0; // At this point, all slivers with constrained cross axis should already be laid out. // Layout the rest and keep track of the child geometry with greatest scrollExtent. @@ -94,22 +93,35 @@ class RenderSliverCrossAxisGroup extends RenderSliver with ContainerRenderObject } else { childExtent = child.geometry!.crossAxisExtent!; } + final SliverGeometry childLayoutGeometry = child.geometry!; + if (geometry!.scrollExtent < childLayoutGeometry.scrollExtent) { + geometry = childLayoutGeometry; + } + child = childAfter(child); + } + + // Go back and correct any slivers using a negative paint offset if it tries + // to paint outside the bounds of the sliver group. + child = firstChild; + double offset = 0.0; + while (child != null) { + final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData; + final SliverGeometry childLayoutGeometry = child.geometry!; + final double remainingExtent = geometry!.scrollExtent - constraints.scrollOffset; + final double paintCorrection = childLayoutGeometry.paintExtent > remainingExtent + ? childLayoutGeometry.paintExtent - remainingExtent + : 0.0; + final double childExtent = child.geometry!.crossAxisExtent ?? extentPerFlexValue * (childParentData.crossAxisFlex ?? 0); // Set child parent data. switch (constraints.axis) { case Axis.vertical: - childParentData.paintOffset = Offset(offset, 0.0); + childParentData.paintOffset = Offset(offset, -paintCorrection); case Axis.horizontal: - childParentData.paintOffset = Offset(0.0, offset); + childParentData.paintOffset = Offset(-paintCorrection, offset); } offset += childExtent; - if (geometry!.scrollExtent < child.geometry!.scrollExtent) { - geometry = child.geometry; - } child = childAfter(child); } - - // Set the geometry with the proper crossAxisExtent. - geometry = geometry!.copyWith(crossAxisExtent: constraints.crossAxisExtent); } @override @@ -168,3 +180,161 @@ bool _assertOutOfExtent(double extent) { } return true; } + +/// A sliver that places multiple sliver children in a linear array along the +/// main axis. +/// +/// The layout algorithm lays out slivers one by one. If the sliver is at the top +/// of the viewport or above the top, then we pass in a nonzero [SliverConstraints.scrollOffset] +/// to inform the sliver at what point along the main axis we should start layout. +/// For the slivers that come after it, we compute the amount of space taken up so +/// far to be used as the [SliverPhysicalParentData.paintOffset] and the +/// [SliverConstraints.remainingPaintExtent] to be passed in as a constraint. +/// +/// Finally, this sliver will also ensure that all child slivers are painted within +/// the total scroll extent of the group by adjusting the child's +/// [SliverPhysicalParentData.paintOffset] as necessary. This can happen for +/// slivers such as [SliverPersistentHeader] which, when pinned, positions itself +/// at the top of the [Viewport] regardless of the scroll offset. +class RenderSliverMainAxisGroup extends RenderSliver with ContainerRenderObjectMixin<RenderSliver, SliverPhysicalContainerParentData> { + @override + void setupParentData(RenderObject child) { + if (child.parentData is! SliverPhysicalContainerParentData) { + child.parentData = SliverPhysicalContainerParentData(); + } + } + + @override + double childMainAxisPosition(RenderSliver child) { + switch (constraints.axisDirection) { + case AxisDirection.up: + case AxisDirection.down: + return (child.parentData! as SliverPhysicalParentData).paintOffset.dy; + case AxisDirection.left: + case AxisDirection.right: + return (child.parentData! as SliverPhysicalParentData).paintOffset.dx; + } + } + + @override + double childCrossAxisPosition(RenderSliver child) => 0.0; + + @override + void performLayout() { + double offset = 0; + double maxPaintExtent = 0; + + RenderSliver? child = firstChild; + + + while (child != null) { + final double beforeOffsetPaintExtent = calculatePaintOffset( + constraints, + from: 0.0, + to: offset, + ); + child.layout( + constraints.copyWith( + scrollOffset: math.max(0.0, constraints.scrollOffset - offset), + cacheOrigin: math.min(0.0, constraints.cacheOrigin + offset), + overlap: math.max(0.0, constraints.overlap - beforeOffsetPaintExtent), + remainingPaintExtent: constraints.remainingPaintExtent - beforeOffsetPaintExtent, + remainingCacheExtent: constraints.remainingCacheExtent - calculateCacheOffset(constraints, from: 0.0, to: offset), + precedingScrollExtent: offset + constraints.precedingScrollExtent, + ), + parentUsesSize: true, + ); + final SliverGeometry childLayoutGeometry = child.geometry!; + final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData; + switch (constraints.axis) { + case Axis.vertical: + childParentData.paintOffset = Offset(0.0, beforeOffsetPaintExtent); + case Axis.horizontal: + childParentData.paintOffset = Offset(beforeOffsetPaintExtent, 0.0); + } + offset += childLayoutGeometry.scrollExtent; + maxPaintExtent += child.geometry!.maxPaintExtent; + child = childAfter(child); + } + + final double totalScrollExtent = offset; + offset = 0.0; + child = firstChild; + // Second pass to correct out of bound paintOffsets. + while (child != null) { + final double beforeOffsetPaintExtent = calculatePaintOffset( + constraints, + from: 0.0, + to: offset, + ); + final SliverGeometry childLayoutGeometry = child.geometry!; + final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData; + final double remainingExtent = totalScrollExtent - constraints.scrollOffset; + if (childLayoutGeometry.paintExtent > remainingExtent) { + final double paintCorrection = childLayoutGeometry.paintExtent - remainingExtent; + switch (constraints.axis) { + case Axis.vertical: + childParentData.paintOffset = Offset(0.0, beforeOffsetPaintExtent - paintCorrection); + case Axis.horizontal: + childParentData.paintOffset = Offset(beforeOffsetPaintExtent - paintCorrection, 0.0); + } + } + offset += child.geometry!.scrollExtent; + child = childAfter(child); + } + geometry = SliverGeometry( + scrollExtent: totalScrollExtent, + paintExtent: calculatePaintOffset(constraints, from: 0, to: totalScrollExtent), + maxPaintExtent: maxPaintExtent, + hasVisualOverflow: totalScrollExtent > constraints.remainingPaintExtent || constraints.scrollOffset > 0.0, + ); + } + + @override + void paint(PaintingContext context, Offset offset) { + RenderSliver? child = lastChild; + + while (child != null) { + final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData; + context.paintChild(child, offset + childParentData.paintOffset); + child = childBefore(child); + } + } + + @override + void applyPaintTransform(RenderSliver child, Matrix4 transform) { + final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData; + childParentData.applyPaintTransform(transform); + } + + @override + bool hitTestChildren(SliverHitTestResult result, {required double mainAxisPosition, required double crossAxisPosition}) { + RenderSliver? child = firstChild; + while (child != null) { + final bool isHit = result.addWithAxisOffset( + mainAxisPosition: mainAxisPosition, + crossAxisPosition: crossAxisPosition, + paintOffset: null, + mainAxisOffset: childMainAxisPosition(child), + crossAxisOffset: childCrossAxisPosition(child), + hitTest: child.hitTest, + ); + if (isHit) { + return true; + } + child = childAfter(child); + } + return false; + } + + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + RenderSliver? child = firstChild; + while (child != null) { + if (child.geometry!.visible) { + visitor(child); + } + child = childAfter(child); + } + } +} diff --git a/packages/flutter/lib/src/rendering/view.dart b/packages/flutter/lib/src/rendering/view.dart index f3dc0fd5547a8..906d237c1da50 100644 --- a/packages/flutter/lib/src/rendering/view.dart +++ b/packages/flutter/lib/src/rendering/view.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:developer'; import 'dart:io' show Platform; import 'dart:ui' as ui show FlutterView, Scene, SceneBuilder, SemanticsUpdate; @@ -33,6 +32,10 @@ class ViewConfiguration { final double devicePixelRatio; /// Creates a transformation matrix that applies the [devicePixelRatio]. + /// + /// The matrix translates points from the local coordinate system of the + /// app (in logical pixels) to the global coordinate system of the + /// [FlutterView] (in physical pixels). Matrix4 toMatrix() { return Matrix4.diagonal3Values(devicePixelRatio, devicePixelRatio, 1.0); } @@ -99,6 +102,8 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> markNeedsLayout(); } + /// The [FlutterView] into which this [RenderView] will render. + ui.FlutterView get flutterView => _view; final ui.FlutterView _view; /// Whether Flutter should automatically compute the desired system UI. @@ -192,18 +197,6 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> return true; } - /// Determines the set of mouse tracker annotations at the given position. - /// - /// See also: - /// - /// * [Layer.findAllAnnotations], which is used by this method to find all - /// [AnnotatedRegionLayer]s annotated for mouse tracking. - HitTestResult hitTestMouseTrackers(Offset position) { - final BoxHitTestResult result = BoxHitTestResult(); - hitTest(result, position: position); - return result; - } - @override bool get isRepaintBoundary => true; @@ -212,6 +205,15 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> if (child != null) { context.paintChild(child!, offset); } + assert(() { + final List<DebugPaintCallback> localCallbacks = _debugPaintCallbacks.toList(); + for (final DebugPaintCallback paintCallback in localCallbacks) { + if (_debugPaintCallbacks.contains(paintCallback)) { + paintCallback(context, offset, this); + } + } + return true; + }()); } @override @@ -226,7 +228,7 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> /// Actually causes the output of the rendering pipeline to appear on screen. void compositeFrame() { if (!kReleaseMode) { - Timeline.startSync('COMPOSITING'); + FlutterTimeline.startSync('COMPOSITING'); } try { final ui.SceneBuilder builder = ui.SceneBuilder(); @@ -244,7 +246,7 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> }()); } finally { if (!kReleaseMode) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } } } @@ -381,4 +383,47 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox> properties.add(DiagnosticsNode.message('semantics enabled')); } } + + static final List<DebugPaintCallback> _debugPaintCallbacks = <DebugPaintCallback>[]; + + /// Registers a [DebugPaintCallback] that is called every time a [RenderView] + /// repaints in debug mode. + /// + /// The callback may paint a debug overlay on top of the content of the + /// [RenderView] provided to the callback. Callbacks are invoked in the + /// order they were registered in. + /// + /// Neither registering a callback nor the continued presence of a callback + /// changes how often [RenderView]s are repainted. It is up to the owner of + /// the callback to call [markNeedsPaint] on any [RenderView] for which it + /// wants to update the painted overlay. + /// + /// Does nothing in release mode. + static void debugAddPaintCallback(DebugPaintCallback callback) { + assert(() { + _debugPaintCallbacks.add(callback); + return true; + }()); + } + + /// Removes a callback registered with [debugAddPaintCallback]. + /// + /// It does not schedule a frame to repaint the [RenderView]s without the + /// overlay painted by the removed callback. It is up to the owner of the + /// callback to call [markNeedsPaint] on the relevant [RenderView]s to + /// repaint them without the overlay. + /// + /// Does nothing in release mode. + static void debugRemovePaintCallback(DebugPaintCallback callback) { + assert(() { + _debugPaintCallbacks.remove(callback); + return true; + }()); + } } + +/// A callback for painting a debug overlay on top of the provided [RenderView]. +/// +/// Used by [RenderView.debugAddPaintCallback] and +/// [RenderView.debugRemovePaintCallback]. +typedef DebugPaintCallback = void Function(PaintingContext context, Offset offset, RenderView renderView); diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index bf07a880263da..44876d8326e54 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -45,7 +45,7 @@ abstract interface class RenderAbstractViewport extends RenderObject { if (object is RenderAbstractViewport) { return object; } - object = object.parent as RenderObject?; + object = object.parent; } return null; } @@ -779,7 +779,7 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix RenderBox? pivot; bool onlySlivers = target is RenderSliver; // ... between viewport and `target` (`target` included). while (child.parent != this) { - final RenderObject parent = child.parent! as RenderObject; + final RenderObject parent = child.parent!; if (child is RenderBox) { pivot = child; } @@ -1205,7 +1205,8 @@ abstract class RenderViewportBase<ParentDataClass extends ContainerParentDataMix } else { // `descendant` is between leading and trailing edge and hence already // fully shown on screen. No action necessary. - final Matrix4 transform = descendant.getTransformTo(viewport.parent! as RenderObject); + assert(viewport.parent != null); + final Matrix4 transform = descendant.getTransformTo(viewport.parent); return MatrixUtils.transformRect(transform, rect ?? descendant.paintBounds); } diff --git a/packages/flutter/lib/src/scheduler/binding.dart b/packages/flutter/lib/src/scheduler/binding.dart index 9d58a91106efb..dc690ab518825 100644 --- a/packages/flutter/lib/src/scheduler/binding.dart +++ b/packages/flutter/lib/src/scheduler/binding.dart @@ -371,11 +371,19 @@ mixin SchedulerBinding on BindingBase { /// This is set by [handleAppLifecycleStateChanged] when the /// [SystemChannels.lifecycle] notification is dispatched. /// - /// The preferred way to watch for changes to this value is using - /// [WidgetsBindingObserver.didChangeAppLifecycleState]. + /// The preferred ways to watch for changes to this value are using + /// [WidgetsBindingObserver.didChangeAppLifecycleState], or through an + /// [AppLifecycleListener] object. AppLifecycleState? get lifecycleState => _lifecycleState; AppLifecycleState? _lifecycleState; + /// Allows the test framework to reset the lifecycle state back to its + /// initial value. + @visibleForTesting + void resetLifecycleState() { + _lifecycleState = null; + } + /// Called when the application lifecycle state changes. /// /// Notifies all the observers using @@ -385,19 +393,18 @@ mixin SchedulerBinding on BindingBase { @protected @mustCallSuper void handleAppLifecycleStateChanged(AppLifecycleState state) { + if (lifecycleState == state) { + return; + } _lifecycleState = state; switch (state) { case AppLifecycleState.resumed: case AppLifecycleState.inactive: _setFramesEnabledState(true); + case AppLifecycleState.hidden: case AppLifecycleState.paused: case AppLifecycleState.detached: _setFramesEnabledState(false); - // ignore: no_default_cases - default: - // TODO(gspencergoog): Remove this and replace with real cases once - // engine change rolls into framework. - break; } } diff --git a/packages/flutter/lib/src/semantics/binding.dart b/packages/flutter/lib/src/semantics/binding.dart index eb2a6d739a370..c5d0a40eb5527 100644 --- a/packages/flutter/lib/src/semantics/binding.dart +++ b/packages/flutter/lib/src/semantics/binding.dart @@ -2,14 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui' as ui show AccessibilityFeatures, SemanticsAction, SemanticsUpdateBuilder; +import 'dart:ui' as ui show AccessibilityFeatures, SemanticsActionEvent, SemanticsUpdateBuilder; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'debug.dart'; -export 'dart:ui' show AccessibilityFeatures, SemanticsUpdateBuilder; +export 'dart:ui' show AccessibilityFeatures, SemanticsActionEvent, SemanticsUpdateBuilder; /// The glue between the semantics layer and the Flutter engine. mixin SemanticsBinding on BindingBase { @@ -20,7 +20,7 @@ mixin SemanticsBinding on BindingBase { _accessibilityFeatures = platformDispatcher.accessibilityFeatures; platformDispatcher ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged - ..onSemanticsAction = _handleSemanticsAction + ..onSemanticsActionEvent = _handleSemanticsActionEvent ..onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged; _handleSemanticsEnabledChanged(); } @@ -111,12 +111,12 @@ mixin SemanticsBinding on BindingBase { } } - void _handleSemanticsAction(int id, ui.SemanticsAction action, ByteData? args) { - performSemanticsAction(SemanticsActionEvent( - nodeId: id, - type: action, - arguments: args != null ? const StandardMessageCodec().decodeMessage(args) : null, - )); + void _handleSemanticsActionEvent(ui.SemanticsActionEvent action) { + final Object? arguments = action.arguments; + final ui.SemanticsActionEvent decodedAction = arguments is ByteData + ? action.copyWith(arguments: const StandardMessageCodec().decodeMessage(arguments)) + : action; + performSemanticsAction(decodedAction); } /// Called whenever the platform requests an action to be performed on a @@ -130,9 +130,9 @@ mixin SemanticsBinding on BindingBase { /// perform the given `action` on the [SemanticsNode] specified by /// [SemanticsActionEvent.nodeId]. /// - /// See [dart:ui.PlatformDispatcher.onSemanticsAction]. + /// See [dart:ui.PlatformDispatcher.onSemanticsActionEvent]. @protected - void performSemanticsAction(SemanticsActionEvent action); + void performSemanticsAction(ui.SemanticsActionEvent action); /// The currently active set of [AccessibilityFeatures]. /// @@ -180,27 +180,6 @@ mixin SemanticsBinding on BindingBase { } } -/// An event to request a [SemanticsAction] of [type] to be performed on the -/// [SemanticsNode] identified by [nodeId]. -/// -/// Used by [SemanticsBinding.performSemanticsAction]. -@immutable -class SemanticsActionEvent { - /// Creates a [SemanticsActionEvent]. - /// - /// The [type] and [nodeId] are required. - const SemanticsActionEvent({required this.type, required this.nodeId, this.arguments}); - - /// The type of action to be performed. - final ui.SemanticsAction type; - - /// The id of the [SemanticsNode] on which the action is to be performed. - final int nodeId; - - /// Optional arguments for the action. - final Object? arguments; -} - /// A reference to the semantics information generated by the framework. /// /// Semantics information are only collected when there are clients interested diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index b3fae67349289..cf39779d17061 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -1367,8 +1367,13 @@ class SemanticsProperties extends DiagnosticableTree { /// the finger without moving it. For example, a button should implement this /// action. /// - /// VoiceOver users on iOS and TalkBack users on Android can trigger this + /// VoiceOver users on iOS and TalkBack users on Android *may* trigger this /// action by double-tapping the screen while an element is focused. + /// + /// Note: different OSes or assistive technologies may decide to interpret + /// user inputs differently. Some may simulate real screen taps, while others + /// may call semantics tap. One way to handle taps properly is to provide the + /// same handler to both gesture tap and semantics tap. final VoidCallback? onTap; /// The handler for [SemanticsAction.longPress]. @@ -1376,9 +1381,15 @@ class SemanticsProperties extends DiagnosticableTree { /// This is the semantic equivalent of a user pressing and holding the screen /// with the finger for a few seconds without moving it. /// - /// VoiceOver users on iOS and TalkBack users on Android can trigger this + /// VoiceOver users on iOS and TalkBack users on Android *may* trigger this /// action by double-tapping the screen without lifting the finger after the /// second tap. + /// + /// Note: different OSes or assistive technologies may decide to interpret + /// user inputs differently. Some may simulate real long presses, while others + /// may call semantics long press. One way to handle long press properly is to + /// provide the same handler to both gesture long press and semantics long + /// press. final VoidCallback? onLongPress; /// The handler for [SemanticsAction.scrollLeft]. @@ -1635,7 +1646,7 @@ void debugResetSemanticsIdCounter() { /// (i.e., during [PipelineOwner.flushSemantics]), which happens after /// compositing. The semantics tree is then uploaded into the engine for use /// by assistive technology. -class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { +class SemanticsNode with DiagnosticableTreeMixin { /// Creates a semantic node. /// /// Each semantic node has a unique identifier that is assigned when the node @@ -1912,7 +1923,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { if (child.parent == this) { // we might have already had our child stolen from us by // another node that is deeper in the tree. - dropChild(child); + _dropChild(child); } sawChange = true; } @@ -1926,10 +1937,10 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { // ancestors. In that case, we drop the child eagerly here. // TODO(ianh): Find a way to assert that the same node didn't // actually appear in the tree in two places. - child.parent?.dropChild(child); + child.parent?._dropChild(child); } assert(!child.attached); - adoptChild(child); + _adoptChild(child); sawChange = true; } } @@ -1987,22 +1998,73 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { return true; } - // AbstractNode OVERRIDES + /// The owner for this node (null if unattached). + /// + /// The entire subtree that this node belongs to will have the same owner. + SemanticsOwner? get owner => _owner; + SemanticsOwner? _owner; - @override - SemanticsOwner? get owner => super.owner as SemanticsOwner?; + /// Whether this node is in a tree whose root is attached to something. + /// + /// This becomes true during the call to [attach]. + /// + /// This becomes false during the call to [detach]. + bool get attached => _owner != null; - @override - SemanticsNode? get parent => super.parent as SemanticsNode?; + /// The parent of this node in the tree. + SemanticsNode? get parent => _parent; + SemanticsNode? _parent; - @override - void redepthChildren() { - _children?.forEach(redepthChild); + /// The depth of this node in the tree. + /// + /// The depth of nodes in a tree monotonically increases as you traverse down + /// the tree. + int get depth => _depth; + int _depth = 0; + + void _redepthChild(SemanticsNode child) { + assert(child.owner == owner); + if (child._depth <= _depth) { + child._depth = _depth + 1; + child._redepthChildren(); + } } - @override + void _redepthChildren() { + _children?.forEach(_redepthChild); + } + + void _adoptChild(SemanticsNode child) { + assert(child._parent == null); + assert(() { + SemanticsNode node = this; + while (node.parent != null) { + node = node.parent!; + } + assert(node != child); // indicates we are about to create a cycle + return true; + }()); + child._parent = this; + if (attached) { + child.attach(_owner!); + } + _redepthChild(child); + } + + void _dropChild(SemanticsNode child) { + assert(child._parent == this); + assert(child.attached == attached); + child._parent = null; + if (attached) { + child.detach(); + } + } + + /// Mark this node as attached to the given owner. + @visibleForTesting void attach(SemanticsOwner owner) { - super.attach(owner); + assert(_owner == null); + _owner = owner; while (owner._nodes.containsKey(id)) { // Ids may repeat if the Flutter has generated > 2^16 ids. We need to keep // regenerating the id until we found an id that is not used. @@ -2021,14 +2083,16 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { } } - @override + /// Mark this node as detached. + @visibleForTesting void detach() { + assert(_owner != null); assert(owner!._nodes.containsKey(id)); assert(!owner!._detachedNodes.contains(this)); owner!._nodes.remove(id); owner!._detachedNodes.add(this); - super.detach(); - assert(owner == null); + _owner = null; + assert(parent == null || attached == parent!.attached); if (_children != null) { for (final SemanticsNode child in _children!) { // The list of children may be stale and may contain nodes that have diff --git a/packages/flutter/lib/src/semantics/semantics_event.dart b/packages/flutter/lib/src/semantics/semantics_event.dart index be391cbadda07..ce2759e774012 100644 --- a/packages/flutter/lib/src/semantics/semantics_event.dart +++ b/packages/flutter/lib/src/semantics/semantics_event.dart @@ -159,3 +159,71 @@ class TapSemanticEvent extends SemanticsEvent { @override Map<String, dynamic> getDataMap() => const <String, dynamic>{}; } + +/// An event to move the accessibility focus. +/// +/// Using this API is generally not recommended, as it may break a users' expectation of +/// how a11y focus works and therefore should be just very carefully. +/// +/// One possibile use case: +/// For example, the currently focused rendering object is replaced by another rendering +/// object. In general, such design should be avoided if possible. If not, one may want +/// to refocus the newly added rendering object. +/// +/// One example that is not recommended: +/// When a new popup or dropdown opens, moving the focus in these cases may confuse users +/// and make it less accessible. +/// +/// {@tool snippet} +/// +/// The following code snippet shows how one can request focus on a +/// certain widget. +/// +/// ```dart +/// class MyWidget extends StatefulWidget { +/// const MyWidget({super.key}); +/// +/// @override +/// State<MyWidget> createState() => _MyWidgetState(); +/// } +/// +/// class _MyWidgetState extends State<MyWidget> { +/// bool noticeAccepted = false; +/// final GlobalKey mykey = GlobalKey(); +/// +/// @override +/// void initState() { +/// super.initState(); +/// // Using addPostFrameCallback because changing focus need to wait for the widget to finish rendering. +/// WidgetsBinding.instance.addPostFrameCallback((_) { +/// mykey.currentContext?.findRenderObject()?.sendSemanticsEvent(const FocusSemanticEvent()); +/// }); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// appBar: AppBar( +/// title: const Text('example'), +/// ), +/// body: Column( +/// children: <Widget>[ +/// const Text('Hello World'), +/// const SizedBox(height: 50), +/// Text('set focus here', key: mykey), +/// ], +/// ), +/// ); +/// } +/// } +/// ``` +/// {@end-tool} +/// +/// This currently only supports Android and iOS. +class FocusSemanticEvent extends SemanticsEvent { + /// Constructs an event that triggers a focus change by the platform. + const FocusSemanticEvent() : super('focus'); + + @override + Map<String, dynamic> getDataMap() => const <String, dynamic>{}; +} diff --git a/packages/flutter/lib/src/services/asset_manifest.dart b/packages/flutter/lib/src/services/asset_manifest.dart index dd62c7d0b8467..3b27490098a8d 100644 --- a/packages/flutter/lib/src/services/asset_manifest.dart +++ b/packages/flutter/lib/src/services/asset_manifest.dart @@ -7,10 +7,15 @@ import 'package:flutter/foundation.dart'; import 'asset_bundle.dart'; import 'message_codecs.dart'; -const String _kAssetManifestFilename = 'AssetManifest.smcbin'; +// We use .bin as the extension since it is well-known to represent +// data in some arbitrary binary format. Using a well-known extension here +// is important for web, because some web servers will not serve files with +// unrecognized file extensions by default. +// See https://github.com/flutter/flutter/issues/128456. +const String _kAssetManifestFilename = 'AssetManifest.bin'; /// Contains details about available assets and their variants. -/// See [Asset variants](https://docs.flutter.dev/development/ui/assets-and-images#asset-variants) +/// See [Resolution-aware image assets](https://docs.flutter.dev/ui/assets-and-images#resolution-aware) /// to learn about asset variants and how to declare them. abstract class AssetManifest { /// Loads asset manifest data from an [AssetBundle] object and creates an @@ -26,15 +31,15 @@ abstract class AssetManifest { /// file at build time. /// /// See [Specifying assets](https://docs.flutter.dev/development/ui/assets-and-images#specifying-assets) - /// and [Loading assets](https://docs.flutter.dev/development/ui/assets-and-images#loading-assets) for more - /// information. + /// and [Loading assets](https://docs.flutter.dev/development/ui/assets-and-images#loading-assets) + /// for more information. List<String> listAssets(); /// Retrieves metadata about an asset and its variants. Returns null if the /// key was not found in the asset manifest. /// - /// This method considers a main asset to be a variant of itself and - /// includes it in the returned list. + /// This method considers a main asset to be a variant of itself. The returned + /// list will include it if it exists. List<AssetMetadata>? getAssetVariants(String key); } @@ -73,22 +78,21 @@ class _AssetManifestBin implements AssetManifest { } _typeCastedData[key] = ((_data[key] ?? <Object?>[]) as Iterable<Object?>) .cast<Map<Object?, Object?>>() - .map((Map<Object?, Object?> data) => AssetMetadata( + .map((Map<Object?, Object?> data) { + final String asset = data['asset']! as String; + final Object? dpr = data['dpr']; + return AssetMetadata( key: data['asset']! as String, - targetDevicePixelRatio: data['dpr']! as double, - main: false, - )) + targetDevicePixelRatio: dpr as double?, + main: key == asset, + ); + }) .toList(); _data.remove(key); } - final AssetMetadata mainAsset = AssetMetadata(key: key, - targetDevicePixelRatio: null, - main: true - ); - - return <AssetMetadata>[mainAsset, ..._typeCastedData[key]!]; + return _typeCastedData[key]!; } @override @@ -115,7 +119,7 @@ class AssetMetadata { /// This will be null if the parent folder name is not a ratio value followed /// by an "x". /// - /// See [Declaring resolution-aware image assets](https://docs.flutter.dev/development/ui/assets-and-images#resolution-aware) + /// See [Resolution-aware image assets](https://docs.flutter.dev/development/ui/assets-and-images#resolution-aware) /// for more information. final double? targetDevicePixelRatio; @@ -125,8 +129,5 @@ class AssetMetadata { /// Whether or not this is a main asset. In other words, this is true if /// this asset is not a variant of another asset. - /// - /// See [Asset variants](https://docs.flutter.dev/development/ui/assets-and-images#asset-variants) - /// for more about asset variants. final bool main; } diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart index 1fb401ea92c35..9969071815984 100644 --- a/packages/flutter/lib/src/services/binding.dart +++ b/packages/flutter/lib/src/services/binding.dart @@ -243,28 +243,104 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { /// /// Once the [lifecycleState] is populated through any means (including this /// method), this method will do nothing. This is because the - /// [dart:ui.PlatformDispatcher.initialLifecycleState] may already be - /// stale and it no longer makes sense to use the initial state at dart vm - /// startup as the current state anymore. + /// [dart:ui.PlatformDispatcher.initialLifecycleState] may already be stale + /// and it no longer makes sense to use the initial state at dart vm startup + /// as the current state anymore. /// /// The latest state should be obtained by subscribing to /// [WidgetsBindingObserver.didChangeAppLifecycleState]. @protected void readInitialLifecycleStateFromNativeWindow() { - if (lifecycleState != null) { + if (lifecycleState != null || platformDispatcher.initialLifecycleState.isEmpty) { return; } - final AppLifecycleState? state = _parseAppLifecycleMessage(platformDispatcher.initialLifecycleState); - if (state != null) { - handleAppLifecycleStateChanged(state); - } + _handleLifecycleMessage(platformDispatcher.initialLifecycleState); } Future<String?> _handleLifecycleMessage(String? message) async { - handleAppLifecycleStateChanged(_parseAppLifecycleMessage(message!)!); + final AppLifecycleState? state = _parseAppLifecycleMessage(message!); + final List<AppLifecycleState> generated = _generateStateTransitions(lifecycleState, state!); + generated.forEach(handleAppLifecycleStateChanged); return null; } + List<AppLifecycleState> _generateStateTransitions(AppLifecycleState? previousState, AppLifecycleState state) { + if (previousState == state) { + return const <AppLifecycleState>[]; + } + if (previousState == AppLifecycleState.paused && state == AppLifecycleState.detached) { + // Handle the wrap-around from paused to detached + return const <AppLifecycleState>[ + AppLifecycleState.detached, + ]; + } + final List<AppLifecycleState> stateChanges = <AppLifecycleState>[]; + if (previousState == null) { + // If there was no previous state, just jump directly to the new state. + stateChanges.add(state); + } else { + final int previousStateIndex = AppLifecycleState.values.indexOf(previousState); + final int stateIndex = AppLifecycleState.values.indexOf(state); + assert(previousStateIndex != -1, 'State $previousState missing in stateOrder array'); + assert(stateIndex != -1, 'State $state missing in stateOrder array'); + if (previousStateIndex > stateIndex) { + for (int i = stateIndex; i < previousStateIndex; ++i) { + stateChanges.insert(0, AppLifecycleState.values[i]); + } + } else { + for (int i = previousStateIndex + 1; i <= stateIndex; ++i) { + stateChanges.add(AppLifecycleState.values[i]); + } + } + } + assert((){ + AppLifecycleState? starting = previousState; + for (final AppLifecycleState ending in stateChanges) { + if (!_debugVerifyLifecycleChange(starting, ending)) { + return false; + } + starting = ending; + } + return true; + }(), 'Invalid lifecycle state transition generated from $previousState to $state (generated $stateChanges)'); + return stateChanges; + } + + static bool _debugVerifyLifecycleChange(AppLifecycleState? starting, AppLifecycleState ending) { + if (starting == null) { + // Any transition from null is fine, since it is initializing the state. + return true; + } + if (starting == ending) { + // Any transition to itself shouldn't happen. + return false; + } + switch (starting) { + case AppLifecycleState.detached: + if (ending == AppLifecycleState.resumed || ending == AppLifecycleState.paused) { + return true; + } + case AppLifecycleState.resumed: + // Can't go from resumed to detached directly (must go through paused). + if (ending == AppLifecycleState.inactive) { + return true; + } + case AppLifecycleState.inactive: + if (ending == AppLifecycleState.resumed || ending == AppLifecycleState.hidden) { + return true; + } + case AppLifecycleState.hidden: + if (ending == AppLifecycleState.inactive || ending == AppLifecycleState.paused) { + return true; + } + case AppLifecycleState.paused: + if (ending == AppLifecycleState.hidden || ending == AppLifecycleState.detached) { + return true; + } + } + return false; + } + Future<dynamic> _handlePlatformMessage(MethodCall methodCall) async { final String method = methodCall.method; assert(method == 'SystemChrome.systemUIChange' || method == 'System.requestAppExit'); @@ -285,6 +361,8 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { return AppLifecycleState.resumed; case 'AppLifecycleState.inactive': return AppLifecycleState.inactive; + case 'AppLifecycleState.hidden': + return AppLifecycleState.hidden; case 'AppLifecycleState.paused': return AppLifecycleState.paused; case 'AppLifecycleState.detached': @@ -357,7 +435,6 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { /// /// * [WidgetsBindingObserver.didRequestAppExit] for a handler you can /// override on a [WidgetsBindingObserver] to receive exit requests. - @mustCallSuper Future<ui.AppExitResponse> exitApplication(ui.AppExitType exitType, [int exitCode = 0]) async { final Map<String, Object?>? result = await SystemChannels.platform.invokeMethod<Map<String, Object?>>( 'System.exitApplication', diff --git a/packages/flutter/lib/src/services/dom.dart b/packages/flutter/lib/src/services/dom.dart index c9181beede563..00d3770949b3d 100644 --- a/packages/flutter/lib/src/services/dom.dart +++ b/packages/flutter/lib/src/services/dom.dart @@ -2,11 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// For now, we're hiding dart:js_interop's `@JS` to avoid a conflict with -// package:js' `@JS`. In the future, we should be able to remove package:js -// altogether and just import dart:js_interop. -import 'dart:js_interop' hide JS; -import 'package:js/js.dart'; +import 'dart:js_interop'; /// This file includes static interop helpers for Flutter Web. // TODO(joshualitt): This file will eventually be removed, diff --git a/packages/flutter/lib/src/services/live_text.dart b/packages/flutter/lib/src/services/live_text.dart new file mode 100644 index 0000000000000..413f7f0698565 --- /dev/null +++ b/packages/flutter/lib/src/services/live_text.dart @@ -0,0 +1,35 @@ +// 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 'system_channels.dart'; + +/// Utility methods for interacting with the system's Live Text. +/// +/// For example, the Live Text input feature of iOS turns the keyboard into a camera view for +/// directly inserting text obtained through OCR into the active field. +/// +/// See also: +/// * <https://developer.apple.com/documentation/uikit/uiresponder/3778577-capturetextfromcamera> +/// * <https://support.apple.com/guide/iphone/use-live-text-iphcf0b71b0e/ios> +class LiveText { + // This class is not meant to be instantiated or extended; this constructor + // prevents instantiation and extension. + LiveText._(); + + /// Returns true if the Live Text input feature is available on the current device. + static Future<bool> isLiveTextInputAvailable() async { + final bool supportLiveTextInput = + await SystemChannels.platform.invokeMethod('LiveText.isLiveTextInputAvailable') ?? false; + return supportLiveTextInput; + } + + /// Start Live Text input. + /// + /// If any [TextInputConnection] is currently active, calling this method will tell the text field + /// to start Live Text input. If the current device doesn't support Live Text input, + /// nothing will happen. + static void startLiveTextInput() { + SystemChannels.textInput.invokeMethod('TextInput.startLiveTextInput'); + } +} diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index efb4afcde359e..f9f475fa1d440 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -1050,6 +1050,13 @@ mixin TextSelectionDelegate { /// Whether select all is enabled, must not be null. bool get selectAllEnabled => true; + /// Whether Live Text input is enabled. + /// + /// See also: + /// * [LiveText], where the availability of Live Text input can be obtained. + /// * [LiveTextInputStatusNotifier], where the status of Live Text can be listened to. + bool get liveTextInputEnabled => false; + /// Cut current selection to [Clipboard]. /// /// If and only if [cause] is [SelectionChangedCause.toolbar], the toolbar diff --git a/packages/flutter/lib/src/widgets/actions.dart b/packages/flutter/lib/src/widgets/actions.dart index ce4183d6a7f67..72137c0d6fba8 100644 --- a/packages/flutter/lib/src/widgets/actions.dart +++ b/packages/flutter/lib/src/widgets/actions.dart @@ -233,8 +233,19 @@ abstract class Action<T extends Intent> with Diagnosticable { /// /// This will be called by the [ActionDispatcher] before attempting to invoke /// the action. + /// + /// If the action's enable state depends on a [BuildContext], subclass + /// [ContextAction] instead of [Action]. bool isEnabled(T intent) => isActionEnabled; + bool _isEnabled(T intent, BuildContext? context) { + final Action<T> self = this; + if (self is ContextAction<T>) { + return self.isEnabled(intent, context); + } + return self.isEnabled(intent); + } + /// Whether this [Action] is inherently enabled. /// /// If [isActionEnabled] is false, then this [Action] is disabled for any @@ -313,9 +324,20 @@ abstract class Action<T extends Intent> with Diagnosticable { /// To receive the result of invoking an action, it must be invoked using /// [Actions.invoke], or by invoking it using an [ActionDispatcher]. An action /// invoked via a [Shortcuts] widget will have its return value ignored. + /// + /// If the action's behavior depends on a [BuildContext], subclass + /// [ContextAction] instead of [Action]. @protected Object? invoke(T intent); + Object? _invoke(T intent, BuildContext? context) { + final Action<T> self = this; + if (self is ContextAction<T>) { + return self.invoke(intent, context); + } + return self.invoke(intent); + } + /// Register a callback to listen for changes to the state of this action. /// /// If you call this, you must call [removeActionListener] a matching number @@ -487,11 +509,22 @@ class _ActionListenerState extends State<ActionListener> { } /// An abstract [Action] subclass that adds an optional [BuildContext] to the -/// [invoke] method to be able to provide context to actions. +/// [isEnabled] and [invoke] methods to be able to provide context to actions. /// /// [ActionDispatcher.invokeAction] checks to see if the action it is invoking /// is a [ContextAction], and if it is, supplies it with a context. abstract class ContextAction<T extends Intent> extends Action<T> { + /// Returns true if the action is enabled and is ready to be invoked. + /// + /// This will be called by the [ActionDispatcher] before attempting to invoke + /// the action. + /// + /// The optional `context` parameter is the context of the invocation of the + /// action, and in the case of an action invoked by a [ShortcutManager], via + /// a [Shortcuts] widget, will be the context of the [Shortcuts] widget. + @override + bool isEnabled(T intent, [BuildContext? context]) => super.isEnabled(intent); + /// Called when the action is to be performed. /// /// This is called by the [ActionDispatcher] when an action is invoked via @@ -598,20 +631,47 @@ class ActionDispatcher with Diagnosticable { /// Returns the object returned from [Action.invoke]. /// /// The caller must receive a `true` result from [Action.isEnabled] before - /// calling this function. This function will assert if the action is not - /// enabled when called. + /// calling this function (or [ContextAction.isEnabled] with the same + /// `context`, if the `action` is a [ContextAction]). This function will + /// assert if the action is not enabled when called. + /// + /// Consider using [invokeActionIfEnabled] to invoke the action conditionally + /// based on whether it is enabled or not, without having to check first. Object? invokeAction( covariant Action<Intent> action, covariant Intent intent, [ BuildContext? context, ]) { - assert(action.isEnabled(intent), 'Action must be enabled when calling invokeAction'); - if (action is ContextAction) { - context ??= primaryFocus?.context; - return action.invoke(intent, context); - } else { - return action.invoke(intent); + final BuildContext? target = context ?? primaryFocus?.context; + assert(action._isEnabled(intent, target), 'Action must be enabled when calling invokeAction'); + return action._invoke(intent, target); + } + + /// Invokes the given `action`, passing it the given `intent`, but only if the + /// action is enabled. + /// + /// The action will be invoked with the given `context`, if given, but only if + /// the action is a [ContextAction] subclass. If no `context` is given, and + /// the action is a [ContextAction], then the context from the [primaryFocus] + /// is used. + /// + /// The return value has two components. The first is a boolean indicating if + /// the action was enabled (as per [Action.isEnabled]). If this is false, the + /// second return value is null. Otherwise, the second return value is the + /// object returned from [Action.invoke]. + /// + /// Consider using [invokeAction] if the enabled state of the action is not in + /// question; this avoids calling [Action.isEnabled] redundantly. + (bool, Object?) invokeActionIfEnabled( + covariant Action<Intent> action, + covariant Intent intent, [ + BuildContext? context, + ]) { + final BuildContext? target = context ?? primaryFocus?.context; + if (action._isEnabled(intent, target)) { + return (true, action._invoke(intent, target)); } + return (false, null); } } @@ -734,11 +794,11 @@ class Actions extends StatefulWidget { /// [Actions.invoke] instead. static VoidCallback? handler<T extends Intent>(BuildContext context, T intent) { final Action<T>? action = Actions.maybeFind<T>(context); - if (action != null && action.isEnabled(intent)) { + if (action != null && action._isEnabled(intent, context)) { return () { // Could be that the action was enabled when the closure was created, // but is now no longer enabled, so check again. - if (action.isEnabled(intent)) { + if (action._isEnabled(intent, context)) { Actions.of(context).invokeAction(action, intent, context); } }; @@ -907,7 +967,7 @@ class Actions extends StatefulWidget { final bool actionFound = _visitActionsAncestors(context, (InheritedElement element) { final _ActionsScope actions = element.widget as _ActionsScope; final Action<T>? result = _castAction(actions, intent: intent); - if (result != null && result.isEnabled(intent)) { + if (result != null && result._isEnabled(intent, context)) { // Invoke the action we found using the relevant dispatcher from the Actions // Element we found. returnValue = _findDispatcher(element).invokeAction(result, intent, context); @@ -954,11 +1014,10 @@ class Actions extends StatefulWidget { T intent, ) { Object? returnValue; - _visitActionsAncestors(context, (InheritedElement element) { final _ActionsScope actions = element.widget as _ActionsScope; final Action<T>? result = _castAction(actions, intent: intent); - if (result != null && result.isEnabled(intent)) { + if (result != null && result._isEnabled(intent, context)) { // Invoke the action we found using the relevant dispatcher from the Actions // element we found. returnValue = _findDispatcher(element).invokeAction(result, intent, context); @@ -1540,12 +1599,17 @@ class PrioritizedIntents extends Intent { /// An [Action] that iterates through a list of [Intent]s, invoking the first /// that is enabled. -class PrioritizedAction extends Action<PrioritizedIntents> { +/// +/// The [isEnabled] method must be called before [invoke]. Calling [isEnabled] +/// configures the object by seeking the first intent with an enabled action. +/// If the actions have an opportunity to change enabled state, [isEnabled] +/// must be called again before calling [invoke]. +class PrioritizedAction extends ContextAction<PrioritizedIntents> { late Action<dynamic> _selectedAction; late Intent _selectedIntent; @override - bool isEnabled(PrioritizedIntents intent) { + bool isEnabled(PrioritizedIntents intent, [ BuildContext? context ]) { final FocusNode? focus = primaryFocus; if (focus == null || focus.context == null) { return false; @@ -1555,7 +1619,7 @@ class PrioritizedAction extends Action<PrioritizedIntents> { focus.context!, intent: candidateIntent, ); - if (candidateAction != null && candidateAction.isEnabled(candidateIntent)) { + if (candidateAction != null && candidateAction._isEnabled(candidateIntent, context)) { _selectedAction = candidateAction; _selectedIntent = candidateIntent; return true; @@ -1565,8 +1629,8 @@ class PrioritizedAction extends Action<PrioritizedIntents> { } @override - void invoke(PrioritizedIntents intent) { - _selectedAction.invoke(_selectedIntent); + void invoke(PrioritizedIntents intent, [ BuildContext? context ]) { + _selectedAction._invoke(_selectedIntent, context); } } @@ -1610,9 +1674,7 @@ mixin _OverridableActionMixin<T extends Intent> on Action<T> { return true; }()); overrideAction._updateCallingAction(defaultAction); - final Object? returnValue = overrideAction is ContextAction<T> - ? overrideAction.invoke(intent, context) - : overrideAction.invoke(intent); + final Object? returnValue = overrideAction._invoke(intent, context); overrideAction._updateCallingAction(null); assert(() { debugAssertMutuallyRecursive = false; @@ -1656,7 +1718,7 @@ mixin _OverridableActionMixin<T extends Intent> on Action<T> { } @override - bool isEnabled(T intent) { + bool isEnabled(T intent, [BuildContext? context]) { assert(!debugAssertIsEnabledMutuallyRecursive); assert(() { debugAssertIsEnabledMutuallyRecursive = true; @@ -1665,7 +1727,7 @@ mixin _OverridableActionMixin<T extends Intent> on Action<T> { final Action<T>? overrideAction = getOverrideAction(); overrideAction?._updateCallingAction(defaultAction); - final bool returnValue = (overrideAction ?? defaultAction).isEnabled(intent); + final bool returnValue = (overrideAction ?? defaultAction)._isEnabled(intent, context); overrideAction?._updateCallingAction(null); assert(() { debugAssertIsEnabledMutuallyRecursive = false; @@ -1747,9 +1809,7 @@ class _OverridableContextAction<T extends Intent> extends ContextAction<T> with // calling BuildContext. final Action<T> wrappedDefault = _ContextActionToActionAdapter<T>(invokeContext: context!, action: defaultAction); overrideAction._updateCallingAction(wrappedDefault); - final Object? returnValue = overrideAction is ContextAction<T> - ? overrideAction.invoke(intent, context) - : overrideAction.invoke(intent); + final Object? returnValue = overrideAction._invoke(intent, context); overrideAction._updateCallingAction(null); assert(() { @@ -1790,7 +1850,7 @@ class _ContextActionToActionAdapter<T extends Intent> extends Action<T> { Action<T>? get callingAction => action.callingAction; @override - bool isEnabled(T intent) => action.isEnabled(intent); + bool isEnabled(T intent) => action.isEnabled(intent, invokeContext); @override bool get isActionEnabled => action.isActionEnabled; diff --git a/packages/flutter/lib/src/widgets/animated_scroll_view.dart b/packages/flutter/lib/src/widgets/animated_scroll_view.dart index a855571903076..856383131583e 100644 --- a/packages/flutter/lib/src/widgets/animated_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/animated_scroll_view.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'basic.dart'; import 'framework.dart'; +import 'media_query.dart'; import 'scroll_controller.dart'; import 'scroll_delegate.dart'; import 'scroll_physics.dart'; @@ -30,6 +31,35 @@ import 'ticker_provider.dart'; /// ** See code in examples/api/lib/widgets/animated_list/animated_list.0.dart ** /// {@end-tool} /// +/// By default, [AnimatedList] will automatically pad the limits of the +/// list's scrollable to avoid partial obstructions indicated by +/// [MediaQuery]'s padding. To avoid this behavior, override with a +/// zero [padding] property. +/// +/// {@tool snippet} +/// The following example demonstrates how to override the default top and +/// bottom padding using [MediaQuery.removePadding]. +/// +/// ```dart +/// Widget myWidget(BuildContext context) { +/// return MediaQuery.removePadding( +/// context: context, +/// removeTop: true, +/// removeBottom: true, +/// child: AnimatedList( +/// initialItemCount: 50, +/// itemBuilder: (BuildContext context, int index, Animation<double> animation) { +/// return Card( +/// color: Colors.amber, +/// child: Center(child: Text('$index')), +/// ); +/// } +/// ), +/// ); +/// } +/// ``` +/// {@end-tool} +/// /// See also: /// /// * [SliverAnimatedList], a sliver that animates items when they are inserted @@ -176,6 +206,7 @@ class AnimatedListState extends _AnimatedScrollViewState<AnimatedList> { itemBuilder: widget.itemBuilder, initialItemCount: widget.initialItemCount, ), + widget.scrollDirection, ); } } @@ -196,6 +227,38 @@ class AnimatedListState extends _AnimatedScrollViewState<AnimatedList> { /// ** See code in examples/api/lib/widgets/animated_grid/animated_grid.0.dart ** /// {@end-tool} /// +/// By default, [AnimatedGrid] will automatically pad the limits of the +/// grid's scrollable to avoid partial obstructions indicated by +/// [MediaQuery]'s padding. To avoid this behavior, override with a +/// zero [padding] property. +/// +/// {@tool snippet} +/// The following example demonstrates how to override the default top and +/// bottom padding using [MediaQuery.removePadding]. +/// +/// ```dart +/// Widget myWidget(BuildContext context) { +/// return MediaQuery.removePadding( +/// context: context, +/// removeTop: true, +/// removeBottom: true, +/// child: AnimatedGrid( +/// gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( +/// crossAxisCount: 3, +/// ), +/// initialItemCount: 50, +/// itemBuilder: (BuildContext context, int index, Animation<double> animation) { +/// return Card( +/// color: Colors.amber, +/// child: Center(child: Text('$index')), +/// ); +/// } +/// ), +/// ); +/// } +/// ``` +/// {@end-tool} +/// /// See also: /// /// * [SliverAnimatedGrid], a sliver which animates items when they are inserted @@ -353,6 +416,7 @@ class AnimatedGridState extends _AnimatedScrollViewState<AnimatedGrid> { itemBuilder: widget.itemBuilder, initialItemCount: widget.initialItemCount, ), + widget.scrollDirection, ); } } @@ -529,7 +593,35 @@ abstract class _AnimatedScrollViewState<T extends _AnimatedScrollView> extends S _sliverAnimatedMultiBoxKey.currentState!.removeAllItems(builder, duration: duration); } - Widget _wrap(Widget sliver) { + Widget _wrap(Widget sliver, Axis direction) { + EdgeInsetsGeometry? effectivePadding = widget.padding; + if (widget.padding == null) { + final MediaQueryData? mediaQuery = MediaQuery.maybeOf(context); + if (mediaQuery != null) { + // Automatically pad sliver with padding from MediaQuery. + final EdgeInsets mediaQueryHorizontalPadding = + mediaQuery.padding.copyWith(top: 0.0, bottom: 0.0); + final EdgeInsets mediaQueryVerticalPadding = + mediaQuery.padding.copyWith(left: 0.0, right: 0.0); + // Consume the main axis padding with SliverPadding. + effectivePadding = direction == Axis.vertical + ? mediaQueryVerticalPadding + : mediaQueryHorizontalPadding; + // Leave behind the cross axis padding. + sliver = MediaQuery( + data: mediaQuery.copyWith( + padding: direction == Axis.vertical + ? mediaQueryHorizontalPadding + : mediaQueryVerticalPadding, + ), + child: sliver, + ); + } + } + + if (effectivePadding != null) { + sliver = SliverPadding(padding: effectivePadding, sliver: sliver); + } return CustomScrollView( scrollDirection: widget.scrollDirection, reverse: widget.reverse, @@ -538,12 +630,7 @@ abstract class _AnimatedScrollViewState<T extends _AnimatedScrollView> extends S physics: widget.physics, clipBehavior: widget.clipBehavior, shrinkWrap: widget.shrinkWrap, - slivers: <Widget>[ - SliverPadding( - padding: widget.padding ?? EdgeInsets.zero, - sliver: sliver, - ), - ], + slivers: <Widget>[ sliver ], ); } } diff --git a/packages/flutter/lib/src/widgets/app_lifecycle_listener.dart b/packages/flutter/lib/src/widgets/app_lifecycle_listener.dart new file mode 100644 index 0000000000000..54b918d62b041 --- /dev/null +++ b/packages/flutter/lib/src/widgets/app_lifecycle_listener.dart @@ -0,0 +1,262 @@ +// 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:ui'; +import 'package:flutter/foundation.dart'; + +import 'binding.dart'; + +/// A callback type that is used by [AppLifecycleListener.onExitRequested] to +/// ask the application if it wants to cancel application termination or not. +typedef AppExitRequestCallback = Future<AppExitResponse> Function(); + +/// A listener that can be used to listen to changes in the application +/// lifecycle. +/// +/// To listen for requests for the application to exit, and to decide whether or +/// not the application should exit when requested, create an +/// [AppLifecycleListener] and set the [onExitRequested] callback. +/// +/// To listen for changes in the application lifecycle state, define an +/// [onStateChange] callback. See the [AppLifecycleState] enum for details on +/// the various states. +/// +/// The [onStateChange] callback is called for each state change, and the +/// individual state transitions ([onResume], [onInactive], etc.) are also +/// called if the state transition they represent occurs. +/// +/// State changes will occur in accordance with the state machine described by +/// this diagram: +/// +/// ![Diagram of the application lifecycle defined by the AppLifecycleState enum]( +/// https://flutter.github.io/assets-for-api-docs/assets/dart-ui/app_lifecycle.png) +/// +/// The initial state of the state machine is the [AppLifecycleState.detached] +/// state, and the arrows describe valid state transitions. Transitions in blue +/// are transitions that only happen on iOS and Android. +/// +/// {@tool dartpad} +/// This example shows how an application can listen to changes in the +/// application state. +/// +/// ** See code in examples/api/lib/widgets/app_lifecycle_listener/app_lifecycle_listener.0.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} +/// This example shows how an application can optionally decide to abort a +/// request for exiting instead of obeying the request. +/// +/// ** See code in examples/api/lib/widgets/app_lifecycle_listener/app_lifecycle_listener.1.dart ** +/// {@end-tool} +/// +/// See also: +/// +/// * [ServicesBinding.exitApplication] for a function to call that will request +/// that the application exits. +/// * [WidgetsBindingObserver.didRequestAppExit] for the handler which this +/// class uses to receive exit requests. +/// * [WidgetsBindingObserver.didChangeAppLifecycleState] for the handler which +/// this class uses to receive lifecycle state changes. +class AppLifecycleListener with WidgetsBindingObserver, Diagnosticable { + /// Creates an [AppLifecycleListener]. + AppLifecycleListener({ + WidgetsBinding? binding, + this.onResume, + this.onInactive, + this.onHide, + this.onShow, + this.onPause, + this.onRestart, + this.onDetach, + this.onExitRequested, + this.onStateChange, + }) : binding = binding ?? WidgetsBinding.instance, + _lifecycleState = (binding ?? WidgetsBinding.instance).lifecycleState { + this.binding.addObserver(this); + } + + AppLifecycleState? _lifecycleState; + + /// The [WidgetsBinding] to listen to for application lifecycle events. + /// + /// Typically, this is set to [WidgetsBinding.instance], but may be + /// substituted for testing or other specialized bindings. + /// + /// Defaults to [WidgetsBinding.instance]. + final WidgetsBinding binding; + + /// Called anytime the state changes, passing the new state. + final ValueChanged<AppLifecycleState>? onStateChange; + + /// A callback that is called when the application loses input focus. + /// + /// On mobile platforms, this can be during a phone call or when a system + /// dialog is visible. + /// + /// On desktop platforms, this is when all views in an application have lost + /// input focus but at least one view of the application is still visible. + /// + /// On the web, this is when the window (or tab) has lost input focus. + final VoidCallback? onInactive; + + /// A callback that is called when a view in the application gains input + /// focus. + /// + /// A call to this callback indicates that the application is entering a state + /// where it is visible, active, and accepting user input. + final VoidCallback? onResume; + + /// A callback that is called when the application is hidden. + /// + /// On mobile platforms, this is usually just before the application is + /// replaced by another application in the foreground. + /// + /// On desktop platforms, this is just before the application is hidden by + /// being minimized or otherwise hiding all views of the application. + /// + /// On the web, this is just before a window (or tab) is hidden. + final VoidCallback? onHide; + + /// A callback that is called when the application is shown. + /// + /// On mobile platforms, this is usually just before the application replaces + /// another application in the foreground. + /// + /// On desktop platforms, this is just before the application is shown after + /// being minimized or otherwise made to show at least one view of the + /// application. + /// + /// On the web, this is just before a window (or tab) is shown. + final VoidCallback? onShow; + + /// A callback that is called when the application is paused. + /// + /// On mobile platforms, this happens right before the application is replaced + /// by another application. + /// + /// On desktop platforms and the web, this function is not called. + final VoidCallback? onPause; + + /// A callback that is called when the application is resumed after being + /// paused. + /// + /// On mobile platforms, this happens just before this application takes over + /// as the active application. + /// + /// On desktop platforms and the web, this function is not called. + final VoidCallback? onRestart; + + /// A callback used to ask the application if it will allow exiting the + /// application for cases where the exit is cancelable. + /// + /// Exiting the application isn't always cancelable, but when it is, this + /// function will be called before exit occurs. + /// + /// Responding [AppExitResponse.exit] will continue termination, and + /// responding [AppExitResponse.cancel] will cancel it. If termination is not + /// canceled, the application will immediately exit. + final AppExitRequestCallback? onExitRequested; + + /// A callback that is called when an application has exited, and detached all + /// host views from the engine. + /// + /// This callback is only called on iOS and Android. + final VoidCallback? onDetach; + + bool _debugDisposed = false; + + /// Call when the listener is no longer in use. + /// + /// Do not use the object after calling [dispose]. + /// + /// Subclasses must call this method in their overridden [dispose], if any. + @mustCallSuper + void dispose() { + assert(_debugAssertNotDisposed()); + binding.removeObserver(this); + assert(() { + _debugDisposed = true; + return true; + }()); + } + + bool _debugAssertNotDisposed() { + assert(() { + if (_debugDisposed) { + throw FlutterError( + 'A $runtimeType was used after being disposed.\n' + 'Once you have called dispose() on a $runtimeType, it ' + 'can no longer be used.', + ); + } + return true; + }()); + return true; + } + + @override + Future<AppExitResponse> didRequestAppExit() async { + assert(_debugAssertNotDisposed()); + if (onExitRequested == null) { + return AppExitResponse.exit; + } + return onExitRequested!(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + assert(_debugAssertNotDisposed()); + final AppLifecycleState? previousState = _lifecycleState; + if (state == previousState) { + // Transitioning to the same state twice doesn't produce any + // notifications (but also won't actually occur). + return; + } + _lifecycleState = state; + switch (state) { + case AppLifecycleState.resumed: + assert(previousState == null || previousState == AppLifecycleState.inactive || previousState == AppLifecycleState.detached, 'Invalid state transition from $previousState to $state'); + onResume?.call(); + case AppLifecycleState.inactive: + assert(previousState == null || previousState == AppLifecycleState.hidden || previousState == AppLifecycleState.resumed, 'Invalid state transition from $previousState to $state'); + if (previousState == AppLifecycleState.hidden) { + onShow?.call(); + } else if (previousState == null || previousState == AppLifecycleState.resumed) { + onInactive?.call(); + } + case AppLifecycleState.hidden: + assert(previousState == null || previousState == AppLifecycleState.paused || previousState == AppLifecycleState.inactive, 'Invalid state transition from $previousState to $state'); + if (previousState == AppLifecycleState.paused) { + onRestart?.call(); + } else if (previousState == null || previousState == AppLifecycleState.inactive) { + onHide?.call(); + } + case AppLifecycleState.paused: + assert(previousState == null || previousState == AppLifecycleState.hidden, 'Invalid state transition from $previousState to $state'); + if (previousState == null || previousState == AppLifecycleState.hidden) { + onPause?.call(); + } + case AppLifecycleState.detached: + assert(previousState == null || previousState == AppLifecycleState.paused, 'Invalid state transition from $previousState to $state'); + onDetach?.call(); + } + // At this point, it can't be null anymore. + onStateChange?.call(_lifecycleState!); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty<WidgetsBinding>('binding', binding)); + properties.add(FlagProperty('onStateChange', value: onStateChange != null, ifTrue: 'onStateChange')); + properties.add(FlagProperty('onInactive', value: onInactive != null, ifTrue: 'onInactive')); + properties.add(FlagProperty('onResume', value: onResume != null, ifTrue: 'onResume')); + properties.add(FlagProperty('onHide', value: onHide != null, ifTrue: 'onHide')); + properties.add(FlagProperty('onShow', value: onShow != null, ifTrue: 'onShow')); + properties.add(FlagProperty('onPause', value: onPause != null, ifTrue: 'onPause')); + properties.add(FlagProperty('onRestart', value: onRestart != null, ifTrue: 'onRestart')); + properties.add(FlagProperty('onExitRequested', value: onExitRequested != null, ifTrue: 'onExitRequested')); + properties.add(FlagProperty('onDetach', value: onDetach != null, ifTrue: 'onDetach')); + } +} diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index d1fc51b42c7bf..cd6c205aa9ccf 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -1371,7 +1371,7 @@ class Transform extends SingleChildRenderObjectWidget { /// Creates a widget that scales its child along the 2D plane. /// - /// The `scaleX` argument provides the scalar by which to multiply the `x` axis, and the `scaleY` argument provides the scalar by which to multiply the `y` axis. Either may be omitted, in which case that axis defaults to 1.0. + /// The `scaleX` argument provides the scalar by which to multiply the `x` axis, and the `scaleY` argument provides the scalar by which to multiply the `y` axis. Either may be omitted, in which case the scaling factor for that axis defaults to 1.0. /// /// For convenience, to scale the child uniformly, instead of providing `scaleX` and `scaleY`, the `scale` parameter may be used. /// @@ -2263,7 +2263,7 @@ class LayoutId extends ParentDataWidget<MultiChildLayoutParentData> { final MultiChildLayoutParentData parentData = renderObject.parentData! as MultiChildLayoutParentData; if (parentData.id != id) { parentData.id = id; - final AbstractNode? targetParent = renderObject.parent; + final RenderObject? targetParent = renderObject.parent; if (targetParent is RenderObject) { targetParent.markNeedsLayout(); } @@ -4348,7 +4348,7 @@ class Positioned extends ParentDataWidget<StackParentData> { } if (needsLayout) { - final AbstractNode? targetParent = renderObject.parent; + final RenderObject? targetParent = renderObject.parent; if (targetParent is RenderObject) { targetParent.markNeedsLayout(); } @@ -5207,7 +5207,7 @@ class Flexible extends ParentDataWidget<FlexParentData> { } if (needsLayout) { - final AbstractNode? targetParent = renderObject.parent; + final RenderObject? targetParent = renderObject.parent; if (targetParent is RenderObject) { targetParent.markNeedsLayout(); } @@ -5732,24 +5732,7 @@ class RichText extends MultiChildRenderObjectWidget { this.selectionColor, }) : assert(maxLines == null || maxLines > 0), assert(selectionRegistrar == null || selectionColor != null), - super(children: _extractChildren(text)); - - // Traverses the InlineSpan tree and depth-first collects the list of - // child widgets that are created in WidgetSpans. - static List<Widget> _extractChildren(InlineSpan span) { - int index = 0; - final List<Widget> result = <Widget>[]; - span.visitChildren((InlineSpan span) { - if (span is WidgetSpan) { - result.add(Semantics( - tagForChildren: PlaceholderSpanIndexSemanticsTag(index++), - child: span.child, - )); - } - return true; - }); - return result; - } + super(children: WidgetSpan.extractFromInlineSpan(text, textScaleFactor)); /// The text to display in this widget. final InlineSpan text; @@ -7000,6 +6983,8 @@ class MetaData extends SingleChildRenderObjectWidget { /// /// See also: /// +/// * [SemanticsProperties], which contains a complete documentation for each +/// of the constructor parameters that belongs to semantics properties. /// * [MergeSemantics], which marks a subtree as being a single node for /// accessibility purposes. /// * [ExcludeSemantics], which excludes a subtree from the semantics tree @@ -7021,6 +7006,8 @@ class Semantics extends SingleChildRenderObjectWidget { /// /// See also: /// + /// * [SemanticsProperties], which contains a complete documentation for each + /// of the constructor parameters that belongs to semantics properties. /// * [SemanticsSortKey] for a class that determines accessibility traversal /// order. Semantics({ diff --git a/packages/flutter/lib/src/widgets/container.dart b/packages/flutter/lib/src/widgets/container.dart index 9202d7f5a07ce..486bce79ce7fd 100644 --- a/packages/flutter/lib/src/widgets/container.dart +++ b/packages/flutter/lib/src/widgets/container.dart @@ -52,6 +52,7 @@ import 'image.dart'; /// * [Decoration], which you can extend to provide other effects with /// [DecoratedBox]. /// * [CustomPaint], another way to draw custom effects from the widget layer. +/// * [DecoratedSliver], which applies a [Decoration] to a sliver. class DecoratedBox extends SingleChildRenderObjectWidget { /// Creates a widget that paints a [Decoration]. /// diff --git a/packages/flutter/lib/src/widgets/context_menu_button_item.dart b/packages/flutter/lib/src/widgets/context_menu_button_item.dart index 8240d9b468c2f..e355ab41e3da0 100644 --- a/packages/flutter/lib/src/widgets/context_menu_button_item.dart +++ b/packages/flutter/lib/src/widgets/context_menu_button_item.dart @@ -26,6 +26,13 @@ enum ContextMenuButtonType { /// A button that deletes the current text selection. delete, + /// A button for starting Live Text input. + /// + /// See also: + /// * [LiveText], where the availability of Live Text input can be obtained. + /// * [LiveTextInputStatusNotifier], where the status of Live Text can be listened to. + liveTextInput, + /// Anything other than the default button types. custom, } diff --git a/packages/flutter/lib/src/widgets/decorated_sliver.dart b/packages/flutter/lib/src/widgets/decorated_sliver.dart new file mode 100644 index 0000000000000..fffe3b4113bee --- /dev/null +++ b/packages/flutter/lib/src/widgets/decorated_sliver.dart @@ -0,0 +1,90 @@ +// 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/rendering.dart'; + +import 'basic.dart'; +import 'framework.dart'; +import 'image.dart'; + +/// A sliver widget that paints a [Decoration] either before or after its child +/// paints. +/// +/// Unlike [DecoratedBox], this widget expects its child to be a sliver, and +/// must be placed in a widget that expects a sliver. +/// +/// If the child sliver has infinite [SliverGeometry.scrollExtent], then we only +/// draw the decoration down to the bottom [SliverGeometry.cacheExtent], and +/// it is necessary to ensure that the bottom border does not creep +/// above the top of the bottom cache. This can happen if the bottom has a +/// border radius larger than the extent of the cache area. +/// +/// Commonly used with [BoxDecoration]. +/// +/// The [child] is not clipped. To clip a child to the shape of a particular +/// [ShapeDecoration], consider using a [ClipPath] widget. +/// +/// {@tool dartpad} +/// This sample shows a radial gradient that draws a moon on a night sky: +/// +/// ** See code in examples/api/lib/widgets/sliver/decorated_sliver.0.dart ** +/// {@end-tool} +/// +/// See also: +/// +/// * [DecoratedBox], the version of this class that works with RenderBox widgets. +/// * [Decoration], which you can extend to provide other effects with +/// [DecoratedSliver]. +/// * [CustomPaint], another way to draw custom effects from the widget layer. +class DecoratedSliver extends SingleChildRenderObjectWidget { + /// Creates a widget that paints a [Decoration]. + /// + /// The [decoration] and [position] arguments must not be null. By default the + /// decoration paints behind the child. + const DecoratedSliver({ + super.key, + required this.decoration, + this.position = DecorationPosition.background, + Widget? sliver, + }) : super(child: sliver); + + /// What decoration to paint. + /// + /// Commonly a [BoxDecoration]. + final Decoration decoration; + + /// Whether to paint the box decoration behind or in front of the child. + final DecorationPosition position; + + @override + RenderDecoratedSliver createRenderObject(BuildContext context) { + return RenderDecoratedSliver( + decoration: decoration, + position: position, + configuration: createLocalImageConfiguration(context), + ); + } + + @override + void updateRenderObject(BuildContext context, RenderDecoratedSliver renderObject) { + renderObject + ..decoration = decoration + ..position = position + ..configuration = createLocalImageConfiguration(context); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + final String label; + switch (position) { + case DecorationPosition.background: + label = 'bg'; + case DecorationPosition.foreground: + label = 'fg'; + } + properties.add(EnumProperty<DecorationPosition>('position', position, level: DiagnosticLevel.hidden)); + properties.add(DiagnosticsProperty<Decoration>(label, decoration)); + } +} diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index 45f7f8b20fc28..f55806353bd5f 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -13,6 +13,7 @@ import 'debug.dart'; import 'framework.dart'; import 'media_query.dart'; import 'overlay.dart'; +import 'view.dart'; /// Signature for determining whether the given data will be accepted by a [DragTarget]. /// @@ -140,7 +141,7 @@ Offset pointerDragAnchorStrategy(Draggable<Object> draggable, BuildContext conte /// [childWhenDragging] when one or more drags are underway. Otherwise, this /// widget always displays [child]. /// -/// {@youtube 560 315 https://www.youtube.com/watch?v=QzA4c4QHZCY} +/// {@youtube 560 315 https://www.youtube.com/watch?v=q4x2G_9-Mu0} /// /// {@tool dartpad} /// The following example has a [Draggable] widget along with a [DragTarget] @@ -497,6 +498,7 @@ class _DraggableState<T extends Object> extends State<Draggable<T>> { feedbackOffset: widget.feedbackOffset, ignoringFeedbackSemantics: widget.ignoringFeedbackSemantics, ignoringFeedbackPointer: widget.ignoringFeedbackPointer, + viewId: View.of(context).viewId, onDragUpdate: (DragUpdateDetails details) { if (mounted && widget.onDragUpdate != null) { widget.onDragUpdate!(details); @@ -756,6 +758,7 @@ class _DragAvatar<T extends Object> extends Drag { this.onDragEnd, required this.ignoringFeedbackSemantics, required this.ignoringFeedbackPointer, + required this.viewId, }) : _position = initialPosition { _entry = OverlayEntry(builder: _build); overlayState.insert(_entry!); @@ -772,6 +775,7 @@ class _DragAvatar<T extends Object> extends Drag { final OverlayState overlayState; final bool ignoringFeedbackSemantics; final bool ignoringFeedbackPointer; + final int viewId; _DragTargetState<Object>? _activeTarget; final List<_DragTargetState<Object>> _enteredTargets = <_DragTargetState<Object>>[]; @@ -804,7 +808,7 @@ class _DragAvatar<T extends Object> extends Drag { _lastOffset = globalPosition - dragStartPoint; _entry!.markNeedsBuild(); final HitTestResult result = HitTestResult(); - WidgetsBinding.instance.hitTest(result, globalPosition + feedbackOffset); + WidgetsBinding.instance.hitTestInView(result, globalPosition + feedbackOffset, viewId); final List<_DragTargetState<Object>> targets = _getDragTargets(result.path).toList(); diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 87bb165979d83..859dfa89bd24f 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -1835,36 +1835,48 @@ class EditableText extends StatefulWidget { required final VoidCallback? onCut, required final VoidCallback? onPaste, required final VoidCallback? onSelectAll, + required final VoidCallback? onLiveTextInput, }) { - // If the paste button is enabled, don't render anything until the state - // of the clipboard is known, since it's used to determine if paste is - // shown. - if (onPaste != null && clipboardStatus == ClipboardStatus.unknown) { - return <ContextMenuButtonItem>[]; + final List<ContextMenuButtonItem> resultButtonItem = <ContextMenuButtonItem>[]; + + // Configure button items with clipboard. + if (onPaste == null || clipboardStatus != ClipboardStatus.unknown) { + // If the paste button is enabled, don't render anything until the state + // of the clipboard is known, since it's used to determine if paste is + // shown. + resultButtonItem.addAll(<ContextMenuButtonItem>[ + if (onCut != null) + ContextMenuButtonItem( + onPressed: onCut, + type: ContextMenuButtonType.cut, + ), + if (onCopy != null) + ContextMenuButtonItem( + onPressed: onCopy, + type: ContextMenuButtonType.copy, + ), + if (onPaste != null) + ContextMenuButtonItem( + onPressed: onPaste, + type: ContextMenuButtonType.paste, + ), + if (onSelectAll != null) + ContextMenuButtonItem( + onPressed: onSelectAll, + type: ContextMenuButtonType.selectAll, + ), + ]); } - return <ContextMenuButtonItem>[ - if (onCut != null) - ContextMenuButtonItem( - onPressed: onCut, - type: ContextMenuButtonType.cut, - ), - if (onCopy != null) - ContextMenuButtonItem( - onPressed: onCopy, - type: ContextMenuButtonType.copy, - ), - if (onPaste != null) - ContextMenuButtonItem( - onPressed: onPaste, - type: ContextMenuButtonType.paste, - ), - if (onSelectAll != null) - ContextMenuButtonItem( - onPressed: onSelectAll, - type: ContextMenuButtonType.selectAll, - ), - ]; + // Config button items with Live Text. + if (onLiveTextInput != null) { + resultButtonItem.add(ContextMenuButtonItem( + onPressed: onLiveTextInput, + type: ContextMenuButtonType.liveTextInput, + )); + } + + return resultButtonItem; } // Infer the keyboard type of an `EditableText` if it's not specified. @@ -2063,6 +2075,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien /// Detects whether the clipboard can paste. final ClipboardStatusNotifier clipboardStatus = ClipboardStatusNotifier(); + /// Detects whether the Live Text input is enabled. + /// + /// See also: + /// * [LiveText], where the availability of Live Text input can be obtained. + final LiveTextInputStatusNotifier? _liveTextInputStatus = + kIsWeb ? null : LiveTextInputStatusNotifier(); + TextInputConnection? _textInputConnection; bool get _hasInputConnection => _textInputConnection?.attached ?? false; @@ -2196,12 +2215,26 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien } } + @override + bool get liveTextInputEnabled { + return _liveTextInputStatus?.value == LiveTextInputStatus.enabled && + !widget.obscureText && + !widget.readOnly && + textEditingValue.selection.isCollapsed; + } + void _onChangedClipboardStatus() { setState(() { // Inform the widget that the value of clipboardStatus has changed. }); } + void _onChangedLiveTextInputStatus() { + setState(() { + // Inform the widget that the value of liveTextInputStatus has changed. + }); + } + TextEditingValue get _textEditingValueforTextLayoutMetrics { final Widget? editableWidget =_editableKey.currentContext?.widget; if (editableWidget is! _Editable) { @@ -2347,6 +2380,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien } } + void _startLiveTextInput(SelectionChangedCause cause) { + if (!liveTextInputEnabled) { + return; + } + if (_hasInputConnection) { + LiveText.startLiveTextInput(); + } + if (cause == SelectionChangedCause.toolbar) { + hideToolbar(); + } + } + /// Finds specified [SuggestionSpan] that matches the provided index using /// binary search. /// @@ -2561,6 +2606,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien onSelectAll: selectAllEnabled ? () => selectAll(SelectionChangedCause.toolbar) : null, + onLiveTextInput: liveTextInputEnabled + ? () => _startLiveTextInput(SelectionChangedCause.toolbar) + : null, ); } @@ -2569,6 +2617,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien @override void initState() { super.initState(); + _liveTextInputStatus?.addListener(_onChangedLiveTextInputStatus); clipboardStatus.addListener(_onChangedClipboardStatus); widget.controller.addListener(_didChangeTextEditingValue); widget.focusNode.addListener(_handleFocusChanged); @@ -2610,12 +2659,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien final bool newTickerEnabled = TickerMode.of(context); if (_tickersEnabled != newTickerEnabled) { _tickersEnabled = newTickerEnabled; - if (_tickersEnabled && _cursorActive) { + if (_showBlinkingCursor) { _startCursorBlink(); } else if (!_tickersEnabled && _cursorTimer != null) { - // Cannot use _stopCursorBlink because it would reset _cursorActive. - _cursorTimer!.cancel(); - _cursorTimer = null; + _stopCursorBlink(); } } @@ -2672,7 +2719,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien if (!_shouldCreateInputConnection) { _closeInputConnectionIfNeeded(); } else if (oldWidget.readOnly && _hasFocus) { - _openInputConnection(); + // _openInputConnection must be called after layout information is available. + // See https://github.com/flutter/flutter/issues/126312 + SchedulerBinding.instance.addPostFrameCallback((Duration _) { + _openInputConnection(); + }); } if (kIsWeb && _hasInputConnection) { @@ -2703,6 +2754,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ); } } + + if (widget.showCursor != oldWidget.showCursor) { + _startOrStopCursorTimerIfNeeded(); + } final bool canPaste = widget.selectionControls is TextSelectionHandleControls ? pasteEnabled : widget.selectionControls?.canPaste(this) ?? false; @@ -2728,6 +2783,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien _selectionOverlay = null; widget.focusNode.removeListener(_handleFocusChanged); WidgetsBinding.instance.removeObserver(this); + _liveTextInputStatus?.removeListener(_onChangedLiveTextInputStatus); + _liveTextInputStatus?.dispose(); clipboardStatus.removeListener(_onChangedClipboardStatus); clipboardStatus.dispose(); _cursorVisibilityNotifier.dispose(); @@ -2788,6 +2845,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien if (_textInputConnection?.scribbleInProgress ?? false) { cause = SelectionChangedCause.scribble; } else if (_pointOffsetOrigin != null) { + // For floating cursor selection when force pressing the space bar. cause = SelectionChangedCause.forcePress; } else { cause = SelectionChangedCause.keyboard; @@ -2812,17 +2870,17 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien _formatAndSetValue(value, SelectionChangedCause.keyboard); } + if (_showBlinkingCursor && _cursorTimer != null) { + // To keep the cursor from blinking while typing, restart the timer here. + _stopCursorBlink(resetCharTicks: false); + _startCursorBlink(); + } + // Wherever the value is changed by the user, schedule a showCaretOnScreen // to make sure the user can see the changes they just made. Programmatic // changes to `textEditingValue` do not trigger the behavior even if the // text field is focused. _scheduleShowCaretOnScreen(withAnimation: true); - if (_hasInputConnection) { - // To keep the cursor from blinking while typing, we want to restart the - // cursor timer every time a new character is typed. - _stopCursorBlink(resetCharTicks: false); - _startCursorBlink(); - } } bool _checkNeedsAdjustAffinity(TextEditingValue value) { @@ -2937,8 +2995,22 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien final Offset finalPosition = renderEditable.getLocalRectForCaret(_lastTextPosition!).centerLeft - _floatingCursorOffset; if (_floatingCursorResetController!.isCompleted) { renderEditable.setFloatingCursor(FloatingCursorDragState.End, finalPosition, _lastTextPosition!); - // Only change if the current selection range is collapsed, to prevent - // overwriting the result of the iOS keyboard selection gesture. + // During a floating cursor's move gesture (1 finger), a cursor is + // animated only visually, without actually updating the selection. + // Only after move gesture is complete, this function will be called + // to actually update the selection to the new cursor location with + // zero selection length. + + // However, During a floating cursor's selection gesture (2 fingers), the + // selection is constantly updated by the engine throughout the gesture. + // Thus when the gesture is complete, we should not update the selection + // to the cursor location with zero selection length, because that would + // overwrite the selection made by floating cursor selection. + + // Here we use `isCollapsed` to distinguish between floating cursor's + // move gesture (1 finger) vs selection gesture (2 fingers), as + // the engine does not provide information other than notifying a + // new selection during with selection gesture (2 fingers). if (renderEditable.selection!.isCollapsed) { // The cause is technically the force cursor, but the cause is listed as tap as the desired functionality is the same. _handleSelectionChanged(TextSelection.fromPosition(_lastTextPosition!), SelectionChangedCause.forcePress); @@ -3133,6 +3205,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien /// default. bool get _needsAutofill => _effectiveAutofillClient.textInputConfiguration.autofillConfiguration.enabled; + // Must be called after layout. + // See https://github.com/flutter/flutter/issues/126312 void _openInputConnection() { if (!_shouldCreateInputConnection) { return; @@ -3306,6 +3380,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien } TextSelectionOverlay _createSelectionOverlay() { + final EditableTextContextMenuBuilder? contextMenuBuilder = widget.contextMenuBuilder; final TextSelectionOverlay selectionOverlay = TextSelectionOverlay( clipboardStatus: clipboardStatus, context: context, @@ -3319,10 +3394,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien selectionDelegate: this, dragStartBehavior: widget.dragStartBehavior, onSelectionHandleTapped: widget.onSelectionHandleTapped, - contextMenuBuilder: widget.contextMenuBuilder == null + contextMenuBuilder: contextMenuBuilder == null ? null : (BuildContext context) { - return widget.contextMenuBuilder!( + return contextMenuBuilder( context, this, ); @@ -3391,18 +3466,12 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien } // To keep the cursor from blinking while it moves, restart the timer here. - if (_cursorTimer != null) { + if (_showBlinkingCursor && _cursorTimer != null) { _stopCursorBlink(resetCharTicks: false); _startCursorBlink(); } } - Rect? _currentCaretRect; - // ignore: use_setters_to_change_properties, (this is used as a callback, can't be a setter) - void _handleCaretChanged(Rect caretRect) { - _currentCaretRect = caretRect; - } - // Animation configuration for scrolling the caret back on screen. static const Duration _caretAnimationDuration = Duration(milliseconds: 100); static const Curve _caretAnimationCurve = Curves.fastOutSlowIn; @@ -3416,7 +3485,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien _showCaretOnScreenScheduled = true; SchedulerBinding.instance.addPostFrameCallback((Duration _) { _showCaretOnScreenScheduled = false; - if (_currentCaretRect == null || !_scrollController.hasClients) { + // Since we are in a post frame callback, check currentContext in case + // RenderEditable has been disposed (in which case it will be null). + final RenderEditable? renderEditable = + _editableKey.currentContext?.findRenderObject() as RenderEditable?; + if (renderEditable == null + || !(renderEditable.selection?.isValid ?? false) + || !_scrollController.hasClients) { return; } @@ -3447,7 +3522,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien final EdgeInsets caretPadding = widget.scrollPadding .copyWith(bottom: bottomSpacing); - final RevealedOffset targetOffset = _getOffsetToRevealCaret(_currentCaretRect!); + final Rect caretRect = renderEditable.getLocalRectForCaret(renderEditable.selection!.extent); + final RevealedOffset targetOffset = _getOffsetToRevealCaret(caretRect); final Rect rectToReveal; final TextSelection selection = textEditingValue.selection; @@ -3633,6 +3709,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien _cursorVisibilityNotifier.value = widget.showCursor && _cursorBlinkOpacityController.value > 0; } + bool get _showBlinkingCursor => _hasFocus && _value.selection.isCollapsed && widget.showCursor && _tickersEnabled; + /// Whether the blinking cursor is actually visible at this precise moment /// (it's hidden half the time, since it blinks). @visibleForTesting @@ -3651,13 +3729,11 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien int _obscureShowCharTicksPending = 0; int? _obscureLatestCharIndex; - // Indicates whether the cursor should be blinking right now (but it may - // actually not blink because it's disabled via TickerMode.of(context)). - bool _cursorActive = false; - void _startCursorBlink() { assert(!(_cursorTimer?.isActive ?? false) || !(_backingCursorBlinkOpacityController?.isAnimating ?? false)); - _cursorActive = true; + if (!widget.showCursor) { + return; + } if (!_tickersEnabled) { return; } @@ -3697,7 +3773,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien } void _stopCursorBlink({ bool resetCharTicks = true }) { - _cursorActive = false; _cursorBlinkOpacityController.value = 0.0; _cursorTimer?.cancel(); _cursorTimer = null; @@ -3707,11 +3782,10 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien } void _startOrStopCursorTimerIfNeeded() { - if (_cursorTimer == null && _hasFocus && _value.selection.isCollapsed) { - _startCursorBlink(); - } - else if (_cursorActive && (!_hasFocus || !_value.selection.isCollapsed)) { + if (!_showBlinkingCursor) { _stopCursorBlink(); + } else if (_cursorTimer == null) { + _startCursorBlink(); } } @@ -3786,6 +3860,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien _updateSizeAndTransform(); } + // Must be called after layout. + // See https://github.com/flutter/flutter/issues/126312 void _updateSizeAndTransform() { final Size size = renderEditable.size; final Matrix4 transform = renderEditable.getTransformTo(null); @@ -3961,6 +4037,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien if (_selectionOverlay == null) { return false; } + _liveTextInputStatus?.update(); clipboardStatus.update(); _selectionOverlay!.showToolbar(); return true; @@ -4620,7 +4697,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien obscuringCharacter: widget.obscuringCharacter, obscureText: widget.obscureText, offset: offset, - onCaretChanged: _handleCaretChanged, rendererIgnoresPointer: widget.rendererIgnoresPointer, cursorWidth: widget.cursorWidth, cursorHeight: widget.cursorHeight, @@ -4745,7 +4821,6 @@ class _Editable extends MultiChildRenderObjectWidget { required this.obscuringCharacter, required this.obscureText, required this.offset, - this.onCaretChanged, this.rendererIgnoresPointer = false, required this.cursorWidth, this.cursorHeight, @@ -4760,20 +4835,7 @@ class _Editable extends MultiChildRenderObjectWidget { this.promptRectRange, this.promptRectColor, required this.clipBehavior, - }) : super(children: _extractChildren(inlineSpan)); - - // Traverses the InlineSpan tree and depth-first collects the list of - // child widgets that are created in WidgetSpans. - static List<Widget> _extractChildren(InlineSpan span) { - final List<Widget> result = <Widget>[]; - span.visitChildren((InlineSpan span) { - if (span is WidgetSpan) { - result.add(span.child); - } - return true; - }); - return result; - } + }) : super(children: WidgetSpan.extractFromInlineSpan(inlineSpan, textScaleFactor)); final InlineSpan inlineSpan; final TextEditingValue value; @@ -4799,7 +4861,6 @@ class _Editable extends MultiChildRenderObjectWidget { final TextHeightBehavior? textHeightBehavior; final TextWidthBasis textWidthBasis; final ViewportOffset offset; - final CaretChangedHandler? onCaretChanged; final bool rendererIgnoresPointer; final double cursorWidth; final double? cursorHeight; @@ -4838,7 +4899,6 @@ class _Editable extends MultiChildRenderObjectWidget { locale: locale ?? Localizations.maybeLocaleOf(context), selection: value.selection, offset: offset, - onCaretChanged: onCaretChanged, ignorePointer: rendererIgnoresPointer, obscuringCharacter: obscuringCharacter, obscureText: obscureText, @@ -4883,7 +4943,6 @@ class _Editable extends MultiChildRenderObjectWidget { ..locale = locale ?? Localizations.maybeLocaleOf(context) ..selection = value.selection ..offset = offset - ..onCaretChanged = onCaretChanged ..ignorePointer = rendererIgnoresPointer ..textHeightBehavior = textHeightBehavior ..textWidthBasis = textWidthBasis @@ -5022,7 +5081,7 @@ class _ScribbleFocusableState extends State<_ScribbleFocusable> implements Scrib } final Rect intersection = calculatedBounds.intersect(rect); final HitTestResult result = HitTestResult(); - WidgetsBinding.instance.hitTest(result, intersection.center); + WidgetsBinding.instance.hitTestInView(result, intersection.center, View.of(context).viewId); return result.path.any((HitTestEntry entry) => entry.target == renderEditable); } @@ -5045,14 +5104,8 @@ class _ScribbleFocusableState extends State<_ScribbleFocusable> implements Scrib class _ScribblePlaceholder extends WidgetSpan { const _ScribblePlaceholder({ required super.child, - super.alignment, - super.baseline, required this.size, - }) : assert(baseline != null || !( - identical(alignment, ui.PlaceholderAlignment.aboveBaseline) || - identical(alignment, ui.PlaceholderAlignment.belowBaseline) || - identical(alignment, ui.PlaceholderAlignment.baseline) - )); + }); /// The size of the span, used in place of adding a placeholder size to the [TextPainter]. final Size size; diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 50eb6ca34aa1e..c6d8f056fd430 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -4,7 +4,6 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:developer'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; @@ -2700,7 +2699,7 @@ class BuildOwner { } return true; }()); - Timeline.startSync( + FlutterTimeline.startSync( 'BUILD', arguments: debugTimelineArguments ); @@ -2771,7 +2770,7 @@ class BuildOwner { } return true; }()); - Timeline.startSync( + FlutterTimeline.startSync( '${element.widget.runtimeType}', arguments: debugTimelineArguments, ); @@ -2794,7 +2793,7 @@ class BuildOwner { ); } if (isTimelineTracked) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } index += 1; if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting!) { @@ -2832,7 +2831,7 @@ class BuildOwner { _scheduledFlushDirtyElements = false; _dirtyElementsNeedsResorting = null; if (!kReleaseMode) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } assert(_debugBuilding); assert(() { @@ -3044,7 +3043,7 @@ class BuildOwner { @pragma('vm:notify-debugger-on-exception') void finalizeTree() { if (!kReleaseMode) { - Timeline.startSync('FINALIZE TREE'); + FlutterTimeline.startSync('FINALIZE TREE'); } try { lockState(_inactiveElements._unmountAll); // this unregisters the GlobalKeys @@ -3140,7 +3139,7 @@ class BuildOwner { _reportException(ErrorSummary('while finalizing the widget tree'), e, stack); } finally { if (!kReleaseMode) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } } } @@ -3153,7 +3152,7 @@ class BuildOwner { /// This is expensive and should not be called except during development. void reassemble(Element root, DebugReassembleConfig? reassembleConfig) { if (!kReleaseMode) { - Timeline.startSync('Preparing Hot Reload (widgets)'); + FlutterTimeline.startSync('Preparing Hot Reload (widgets)'); } try { assert(root._parent == null); @@ -3162,7 +3161,7 @@ class BuildOwner { root.reassemble(); } finally { if (!kReleaseMode) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } } } @@ -3678,14 +3677,14 @@ abstract class Element extends DiagnosticableTree implements BuildContext { } return true; }()); - Timeline.startSync( + FlutterTimeline.startSync( '${newWidget.runtimeType}', arguments: debugTimelineArguments, ); } child.update(newWidget); if (isTimelineTracked) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } assert(child.widget == newWidget); assert(() { @@ -3723,6 +3722,218 @@ abstract class Element extends DiagnosticableTree implements BuildContext { return newChild; } + /// Updates the children of this element to use new widgets. + /// + /// Attempts to update the given old children list using the given new + /// widgets, removing obsolete elements and introducing new ones as necessary, + /// and then returns the new child list. + /// + /// During this function the `oldChildren` list must not be modified. If the + /// caller wishes to remove elements from `oldChildren` reentrantly while + /// this function is on the stack, the caller can supply a `forgottenChildren` + /// argument, which can be modified while this function is on the stack. + /// Whenever this function reads from `oldChildren`, this function first + /// checks whether the child is in `forgottenChildren`. If it is, the function + /// acts as if the child was not in `oldChildren`. + /// + /// This function is a convenience wrapper around [updateChild], which updates + /// each individual child. If `slots` is non-null, the value for the `newSlot` + /// argument of [updateChild] is retrieved from that list using the index that + /// the currently processed `child` corresponds to in the `newWidgets` list + /// (`newWidgets` and `slots` must have the same length). If `slots` is null, + /// an [IndexedSlot<Element>] is used as the value for the `newSlot` argument. + /// In that case, [IndexedSlot.index] is set to the index that the currently + /// processed `child` corresponds to in the `newWidgets` list and + /// [IndexedSlot.value] is set to the [Element] of the previous widget in that + /// list (or null if it is the first child). + /// + /// When the [slot] value of an [Element] changes, its + /// associated [renderObject] needs to move to a new position in the child + /// list of its parents. If that [RenderObject] organizes its children in a + /// linked list (as is done by the [ContainerRenderObjectMixin]) this can + /// be implemented by re-inserting the child [RenderObject] into the + /// list after the [RenderObject] associated with the [Element] provided as + /// [IndexedSlot.value] in the [slot] object. + /// + /// Using the previous sibling as a [slot] is not enough, though, because + /// child [RenderObject]s are only moved around when the [slot] of their + /// associated [RenderObjectElement]s is updated. When the order of child + /// [Element]s is changed, some elements in the list may move to a new index + /// but still have the same previous sibling. For example, when + /// `[e1, e2, e3, e4]` is changed to `[e1, e3, e4, e2]` the element e4 + /// continues to have e3 as a previous sibling even though its index in the list + /// has changed and its [RenderObject] needs to move to come before e2's + /// [RenderObject]. In order to trigger this move, a new [slot] value needs to + /// be assigned to its [Element] whenever its index in its + /// parent's child list changes. Using an [IndexedSlot<Element>] achieves + /// exactly that and also ensures that the underlying parent [RenderObject] + /// knows where a child needs to move to in a linked list by providing its new + /// previous sibling. + @protected + List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element>? forgottenChildren, List<Object?>? slots }) { + assert(slots == null || newWidgets.length == slots.length); + + Element? replaceWithNullIfForgotten(Element child) { + return forgottenChildren != null && forgottenChildren.contains(child) ? null : child; + } + + Object? slotFor(int newChildIndex, Element? previousChild) { + return slots != null + ? slots[newChildIndex] + : IndexedSlot<Element?>(newChildIndex, previousChild); + } + + // This attempts to diff the new child list (newWidgets) with + // the old child list (oldChildren), and produce a new list of elements to + // be the new list of child elements of this element. The called of this + // method is expected to update this render object accordingly. + + // The cases it tries to optimize for are: + // - the old list is empty + // - the lists are identical + // - there is an insertion or removal of one or more widgets in + // only one place in the list + // If a widget with a key is in both lists, it will be synced. + // Widgets without keys might be synced but there is no guarantee. + + // The general approach is to sync the entire new list backwards, as follows: + // 1. Walk the lists from the top, syncing nodes, until you no longer have + // matching nodes. + // 2. Walk the lists from the bottom, without syncing nodes, until you no + // longer have matching nodes. We'll sync these nodes at the end. We + // don't sync them now because we want to sync all the nodes in order + // from beginning to end. + // At this point we narrowed the old and new lists to the point + // where the nodes no longer match. + // 3. Walk the narrowed part of the old list to get the list of + // keys and sync null with non-keyed items. + // 4. Walk the narrowed part of the new list forwards: + // * Sync non-keyed items with null + // * Sync keyed items with the source if it exists, else with null. + // 5. Walk the bottom of the list again, syncing the nodes. + // 6. Sync null with any items in the list of keys that are still + // mounted. + + int newChildrenTop = 0; + int oldChildrenTop = 0; + int newChildrenBottom = newWidgets.length - 1; + int oldChildrenBottom = oldChildren.length - 1; + + final List<Element> newChildren = List<Element>.filled(newWidgets.length, _NullElement.instance); + + Element? previousChild; + + // Update the top of the list. + while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) { + final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]); + final Widget newWidget = newWidgets[newChildrenTop]; + assert(oldChild == null || oldChild._lifecycleState == _ElementLifecycle.active); + if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget)) { + break; + } + final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!; + assert(newChild._lifecycleState == _ElementLifecycle.active); + newChildren[newChildrenTop] = newChild; + previousChild = newChild; + newChildrenTop += 1; + oldChildrenTop += 1; + } + + // Scan the bottom of the list. + while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) { + final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]); + final Widget newWidget = newWidgets[newChildrenBottom]; + assert(oldChild == null || oldChild._lifecycleState == _ElementLifecycle.active); + if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget)) { + break; + } + oldChildrenBottom -= 1; + newChildrenBottom -= 1; + } + + // Scan the old children in the middle of the list. + final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom; + Map<Key, Element>? oldKeyedChildren; + if (haveOldChildren) { + oldKeyedChildren = <Key, Element>{}; + while (oldChildrenTop <= oldChildrenBottom) { + final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]); + assert(oldChild == null || oldChild._lifecycleState == _ElementLifecycle.active); + if (oldChild != null) { + if (oldChild.widget.key != null) { + oldKeyedChildren[oldChild.widget.key!] = oldChild; + } else { + deactivateChild(oldChild); + } + } + oldChildrenTop += 1; + } + } + + // Update the middle of the list. + while (newChildrenTop <= newChildrenBottom) { + Element? oldChild; + final Widget newWidget = newWidgets[newChildrenTop]; + if (haveOldChildren) { + final Key? key = newWidget.key; + if (key != null) { + oldChild = oldKeyedChildren![key]; + if (oldChild != null) { + if (Widget.canUpdate(oldChild.widget, newWidget)) { + // we found a match! + // remove it from oldKeyedChildren so we don't unsync it later + oldKeyedChildren.remove(key); + } else { + // Not a match, let's pretend we didn't see it for now. + oldChild = null; + } + } + } + } + assert(oldChild == null || Widget.canUpdate(oldChild.widget, newWidget)); + final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!; + assert(newChild._lifecycleState == _ElementLifecycle.active); + assert(oldChild == newChild || oldChild == null || oldChild._lifecycleState != _ElementLifecycle.active); + newChildren[newChildrenTop] = newChild; + previousChild = newChild; + newChildrenTop += 1; + } + + // We've scanned the whole list. + assert(oldChildrenTop == oldChildrenBottom + 1); + assert(newChildrenTop == newChildrenBottom + 1); + assert(newWidgets.length - newChildrenTop == oldChildren.length - oldChildrenTop); + newChildrenBottom = newWidgets.length - 1; + oldChildrenBottom = oldChildren.length - 1; + + // Update the bottom of the list. + while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) { + final Element oldChild = oldChildren[oldChildrenTop]; + assert(replaceWithNullIfForgotten(oldChild) != null); + assert(oldChild._lifecycleState == _ElementLifecycle.active); + final Widget newWidget = newWidgets[newChildrenTop]; + assert(Widget.canUpdate(oldChild.widget, newWidget)); + final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!; + assert(newChild._lifecycleState == _ElementLifecycle.active); + assert(oldChild == newChild || oldChild._lifecycleState != _ElementLifecycle.active); + newChildren[newChildrenTop] = newChild; + previousChild = newChild; + newChildrenTop += 1; + oldChildrenTop += 1; + } + + // Clean up any of the remaining middle nodes from the old list. + if (haveOldChildren && oldKeyedChildren!.isNotEmpty) { + for (final Element oldChild in oldKeyedChildren.values) { + if (forgottenChildren == null || !forgottenChildren.contains(oldChild)) { + deactivateChild(oldChild); + } + } + } + assert(newChildren.every((Element element) => element is! _NullElement)); + return newChildren; + } + /// Add this element to the tree in the given slot of the given parent. /// /// The framework calls this function when a newly created element is added to @@ -3941,7 +4152,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext { } return true; }()); - Timeline.startSync( + FlutterTimeline.startSync( '${newWidget.runtimeType}', arguments: debugTimelineArguments, ); @@ -3974,7 +4185,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext { return newChild; } finally { if (isTimelineTracked) { - Timeline.finishSync(); + FlutterTimeline.finishSync(); } } } @@ -4583,7 +4794,7 @@ abstract class Element extends DiagnosticableTree implements BuildContext { final List<DiagnosticsNode> diagnosticsDependencies = sortedDependencies .map((InheritedElement element) => element.widget.toDiagnosticsNode(style: DiagnosticsTreeStyle.sparse)) .toList(); - properties.add(DiagnosticsProperty<List<DiagnosticsNode>>('dependencies', diagnosticsDependencies)); + properties.add(DiagnosticsProperty<Set<InheritedElement>>('dependencies', deps, description: diagnosticsDependencies.toString())); } } @@ -5981,218 +6192,6 @@ abstract class RenderObjectElement extends Element { super.performRebuild(); // clears the "dirty" flag } - /// Updates the children of this element to use new widgets. - /// - /// Attempts to update the given old children list using the given new - /// widgets, removing obsolete elements and introducing new ones as necessary, - /// and then returns the new child list. - /// - /// During this function the `oldChildren` list must not be modified. If the - /// caller wishes to remove elements from `oldChildren` reentrantly while - /// this function is on the stack, the caller can supply a `forgottenChildren` - /// argument, which can be modified while this function is on the stack. - /// Whenever this function reads from `oldChildren`, this function first - /// checks whether the child is in `forgottenChildren`. If it is, the function - /// acts as if the child was not in `oldChildren`. - /// - /// This function is a convenience wrapper around [updateChild], which updates - /// each individual child. If `slots` is non-null, the value for the `newSlot` - /// argument of [updateChild] is retrieved from that list using the index that - /// the currently processed `child` corresponds to in the `newWidgets` list - /// (`newWidgets` and `slots` must have the same length). If `slots` is null, - /// an [IndexedSlot<Element>] is used as the value for the `newSlot` argument. - /// In that case, [IndexedSlot.index] is set to the index that the currently - /// processed `child` corresponds to in the `newWidgets` list and - /// [IndexedSlot.value] is set to the [Element] of the previous widget in that - /// list (or null if it is the first child). - /// - /// When the [slot] value of an [Element] changes, its - /// associated [renderObject] needs to move to a new position in the child - /// list of its parents. If that [RenderObject] organizes its children in a - /// linked list (as is done by the [ContainerRenderObjectMixin]) this can - /// be implemented by re-inserting the child [RenderObject] into the - /// list after the [RenderObject] associated with the [Element] provided as - /// [IndexedSlot.value] in the [slot] object. - /// - /// Using the previous sibling as a [slot] is not enough, though, because - /// child [RenderObject]s are only moved around when the [slot] of their - /// associated [RenderObjectElement]s is updated. When the order of child - /// [Element]s is changed, some elements in the list may move to a new index - /// but still have the same previous sibling. For example, when - /// `[e1, e2, e3, e4]` is changed to `[e1, e3, e4, e2]` the element e4 - /// continues to have e3 as a previous sibling even though its index in the list - /// has changed and its [RenderObject] needs to move to come before e2's - /// [RenderObject]. In order to trigger this move, a new [slot] value needs to - /// be assigned to its [Element] whenever its index in its - /// parent's child list changes. Using an [IndexedSlot<Element>] achieves - /// exactly that and also ensures that the underlying parent [RenderObject] - /// knows where a child needs to move to in a linked list by providing its new - /// previous sibling. - @protected - List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element>? forgottenChildren, List<Object?>? slots }) { - assert(slots == null || newWidgets.length == slots.length); - - Element? replaceWithNullIfForgotten(Element child) { - return forgottenChildren != null && forgottenChildren.contains(child) ? null : child; - } - - Object? slotFor(int newChildIndex, Element? previousChild) { - return slots != null - ? slots[newChildIndex] - : IndexedSlot<Element?>(newChildIndex, previousChild); - } - - // This attempts to diff the new child list (newWidgets) with - // the old child list (oldChildren), and produce a new list of elements to - // be the new list of child elements of this element. The called of this - // method is expected to update this render object accordingly. - - // The cases it tries to optimize for are: - // - the old list is empty - // - the lists are identical - // - there is an insertion or removal of one or more widgets in - // only one place in the list - // If a widget with a key is in both lists, it will be synced. - // Widgets without keys might be synced but there is no guarantee. - - // The general approach is to sync the entire new list backwards, as follows: - // 1. Walk the lists from the top, syncing nodes, until you no longer have - // matching nodes. - // 2. Walk the lists from the bottom, without syncing nodes, until you no - // longer have matching nodes. We'll sync these nodes at the end. We - // don't sync them now because we want to sync all the nodes in order - // from beginning to end. - // At this point we narrowed the old and new lists to the point - // where the nodes no longer match. - // 3. Walk the narrowed part of the old list to get the list of - // keys and sync null with non-keyed items. - // 4. Walk the narrowed part of the new list forwards: - // * Sync non-keyed items with null - // * Sync keyed items with the source if it exists, else with null. - // 5. Walk the bottom of the list again, syncing the nodes. - // 6. Sync null with any items in the list of keys that are still - // mounted. - - int newChildrenTop = 0; - int oldChildrenTop = 0; - int newChildrenBottom = newWidgets.length - 1; - int oldChildrenBottom = oldChildren.length - 1; - - final List<Element> newChildren = List<Element>.filled(newWidgets.length, _NullElement.instance); - - Element? previousChild; - - // Update the top of the list. - while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) { - final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]); - final Widget newWidget = newWidgets[newChildrenTop]; - assert(oldChild == null || oldChild._lifecycleState == _ElementLifecycle.active); - if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget)) { - break; - } - final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!; - assert(newChild._lifecycleState == _ElementLifecycle.active); - newChildren[newChildrenTop] = newChild; - previousChild = newChild; - newChildrenTop += 1; - oldChildrenTop += 1; - } - - // Scan the bottom of the list. - while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) { - final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenBottom]); - final Widget newWidget = newWidgets[newChildrenBottom]; - assert(oldChild == null || oldChild._lifecycleState == _ElementLifecycle.active); - if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget)) { - break; - } - oldChildrenBottom -= 1; - newChildrenBottom -= 1; - } - - // Scan the old children in the middle of the list. - final bool haveOldChildren = oldChildrenTop <= oldChildrenBottom; - Map<Key, Element>? oldKeyedChildren; - if (haveOldChildren) { - oldKeyedChildren = <Key, Element>{}; - while (oldChildrenTop <= oldChildrenBottom) { - final Element? oldChild = replaceWithNullIfForgotten(oldChildren[oldChildrenTop]); - assert(oldChild == null || oldChild._lifecycleState == _ElementLifecycle.active); - if (oldChild != null) { - if (oldChild.widget.key != null) { - oldKeyedChildren[oldChild.widget.key!] = oldChild; - } else { - deactivateChild(oldChild); - } - } - oldChildrenTop += 1; - } - } - - // Update the middle of the list. - while (newChildrenTop <= newChildrenBottom) { - Element? oldChild; - final Widget newWidget = newWidgets[newChildrenTop]; - if (haveOldChildren) { - final Key? key = newWidget.key; - if (key != null) { - oldChild = oldKeyedChildren![key]; - if (oldChild != null) { - if (Widget.canUpdate(oldChild.widget, newWidget)) { - // we found a match! - // remove it from oldKeyedChildren so we don't unsync it later - oldKeyedChildren.remove(key); - } else { - // Not a match, let's pretend we didn't see it for now. - oldChild = null; - } - } - } - } - assert(oldChild == null || Widget.canUpdate(oldChild.widget, newWidget)); - final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!; - assert(newChild._lifecycleState == _ElementLifecycle.active); - assert(oldChild == newChild || oldChild == null || oldChild._lifecycleState != _ElementLifecycle.active); - newChildren[newChildrenTop] = newChild; - previousChild = newChild; - newChildrenTop += 1; - } - - // We've scanned the whole list. - assert(oldChildrenTop == oldChildrenBottom + 1); - assert(newChildrenTop == newChildrenBottom + 1); - assert(newWidgets.length - newChildrenTop == oldChildren.length - oldChildrenTop); - newChildrenBottom = newWidgets.length - 1; - oldChildrenBottom = oldChildren.length - 1; - - // Update the bottom of the list. - while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) { - final Element oldChild = oldChildren[oldChildrenTop]; - assert(replaceWithNullIfForgotten(oldChild) != null); - assert(oldChild._lifecycleState == _ElementLifecycle.active); - final Widget newWidget = newWidgets[newChildrenTop]; - assert(Widget.canUpdate(oldChild.widget, newWidget)); - final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!; - assert(newChild._lifecycleState == _ElementLifecycle.active); - assert(oldChild == newChild || oldChild._lifecycleState != _ElementLifecycle.active); - newChildren[newChildrenTop] = newChild; - previousChild = newChild; - newChildrenTop += 1; - oldChildrenTop += 1; - } - - // Clean up any of the remaining middle nodes from the old list. - if (haveOldChildren && oldKeyedChildren!.isNotEmpty) { - for (final Element oldChild in oldKeyedChildren.values) { - if (forgottenChildren == null || !forgottenChildren.contains(oldChild)) { - deactivateChild(oldChild); - } - } - } - assert(newChildren.every((Element element) => element is! _NullElement)); - return newChildren; - } - @override void deactivate() { super.deactivate(); diff --git a/packages/flutter/lib/src/widgets/icon.dart b/packages/flutter/lib/src/widgets/icon.dart index 8210128f5ff6e..88edcaacb6330 100644 --- a/packages/flutter/lib/src/widgets/icon.dart +++ b/packages/flutter/lib/src/widgets/icon.dart @@ -280,6 +280,7 @@ class Icon extends StatelessWidget { fontSize: iconSize, fontFamily: icon!.fontFamily, package: icon!.fontPackage, + fontFamilyFallback: icon!.fontFamilyFallback, shadows: iconShadows, ), ), diff --git a/packages/flutter/lib/src/widgets/icon_data.dart b/packages/flutter/lib/src/widgets/icon_data.dart index 7cca8d563ffc5..214cadcfc5ef2 100644 --- a/packages/flutter/lib/src/widgets/icon_data.dart +++ b/packages/flutter/lib/src/widgets/icon_data.dart @@ -31,6 +31,7 @@ class IconData { this.fontFamily, this.fontPackage, this.matchTextDirection = false, + this.fontFamilyFallback, }); /// The Unicode code point at which this icon is stored in the icon font. @@ -56,6 +57,11 @@ class IconData { /// [Directionality] is [TextDirection.rtl]. final bool matchTextDirection; + /// The ordered list of font families to fall back on when a glyph cannot be found in a higher priority font family. + /// + /// For more details, refer to the documentation of [TextStyle] + final List<String>? fontFamilyFallback; + @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { @@ -65,11 +71,20 @@ class IconData { && other.codePoint == codePoint && other.fontFamily == fontFamily && other.fontPackage == fontPackage - && other.matchTextDirection == matchTextDirection; + && other.matchTextDirection == matchTextDirection + && listEquals(other.fontFamilyFallback, fontFamilyFallback); } @override - int get hashCode => Object.hash(codePoint, fontFamily, fontPackage, matchTextDirection); + int get hashCode { + return Object.hash( + codePoint, + fontFamily, + fontPackage, + matchTextDirection, + Object.hashAll(fontFamilyFallback ?? const <String?>[]), + ); + } @override String toString() => 'IconData(U+${codePoint.toRadixString(16).toUpperCase().padLeft(5, '0')})'; diff --git a/packages/flutter/lib/src/widgets/interactive_viewer.dart b/packages/flutter/lib/src/widgets/interactive_viewer.dart index ce0ed3111de13..a0ee8fafa6fa7 100644 --- a/packages/flutter/lib/src/widgets/interactive_viewer.dart +++ b/packages/flutter/lib/src/widgets/interactive_viewer.dart @@ -975,7 +975,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid void _receivedPointerSignal(PointerSignalEvent event) { final double scaleChange; if (event is PointerScrollEvent) { - if (event.kind == PointerDeviceKind.trackpad) { + if (event.kind == PointerDeviceKind.trackpad && !widget.trackpadScrollCausesScale) { // Trackpad scroll, so treat it as a pan. widget.onInteractionStart?.call( ScaleStartDetails( diff --git a/packages/flutter/lib/src/widgets/media_query.dart b/packages/flutter/lib/src/widgets/media_query.dart index f7b554ae0855c..8cd8ee3c2a379 100644 --- a/packages/flutter/lib/src/widgets/media_query.dart +++ b/packages/flutter/lib/src/widgets/media_query.dart @@ -756,16 +756,24 @@ class MediaQueryData { /// Establishes a subtree in which media queries resolve to the given data. /// /// For example, to learn the size of the current media (e.g., the window -/// containing your app), you can read the [MediaQueryData.size] property from -/// the [MediaQueryData] returned by [MediaQuery.of]: -/// `MediaQuery.of(context).size`. +/// containing your app), you can use [MediaQuery.sizeOf]: +/// `MediaQuery.sizeOf(context)`. /// -/// Querying the current media using [MediaQuery.of] will cause your widget to -/// rebuild automatically whenever the [MediaQueryData] changes (e.g., if the -/// user rotates their device). +/// Querying the current media using specific methods (for example, +/// [MediaQuery.sizeOf] and [MediaQuery.paddingOf]) will cause your widget to +/// rebuild automatically whenever the property you query changes. /// -/// If no [MediaQuery] is in scope then the [MediaQuery.of] method will throw an -/// exception. Alternatively, [MediaQuery.maybeOf] may be used, which returns +/// On the other hand, querying using [MediaQuery.of] will cause your widget to +/// rebuild automatically whenever any field of the [MediaQueryData] changes +/// (e.g., if the user rotates their device). Therefore, if you are only +/// concerned with one or a few fields of [MediaQueryData], prefer using +/// the specific methods (for example: [MediaQuery.sizeOf] and +/// [MediaQuery.paddingOf]). +/// +/// If no [MediaQuery] is in scope then the series of methods like +/// [MediaQuery.of] and [MediaQuery.sizeOf] will throw an exception. +/// Alternatively, the "maybe-" variant methods (such as [MediaQuery.maybeOf] +/// and [MediaQuery.maybeSizeOf]) can be used, which returns /// null instead of throwing if no [MediaQuery] is in scope. /// /// {@youtube 560 315 https://www.youtube.com/watch?v=A3WrA4zAaPw} diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index 2711e0b624678..020f3dc195a75 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -197,6 +197,13 @@ class NestedScrollView extends StatefulWidget { final ScrollController? controller; /// {@macro flutter.widgets.scroll_view.scrollDirection} + /// + /// This property only applies to the [Axis] of the outer scroll view, + /// composed of the slivers returned from [headerSliverBuilder]. Since the + /// inner scroll view is not directly configured by the [NestedScrollView], + /// for the axes to match, configure the scroll view of the [body] the same + /// way if they are expected to scroll in the same orientation. This allows + /// for flexible configurations of the NestedScrollView. final Axis scrollDirection; /// Whether the scroll view scrolls in the reading direction. @@ -210,6 +217,13 @@ class NestedScrollView extends StatefulWidget { /// scrolls from top to bottom when [reverse] is false and from bottom to top /// when [reverse] is true. /// + /// This property only applies to the outer scroll view, composed of the + /// slivers returned from [headerSliverBuilder]. Since the inner scroll view + /// is not directly configured by the [NestedScrollView]. For both to scroll + /// in reverse, configure the scroll view of the [body] the same way if they + /// are expected to match. This allows for flexible configurations of the + /// NestedScrollView. + /// /// Defaults to false. final bool reverse; @@ -232,6 +246,22 @@ class NestedScrollView extends StatefulWidget { /// [ScrollMetrics.maxScrollExtent] properties passed to that method. If that /// invariant is not maintained, the nested scroll view may respond to user /// scrolling erratically. + /// + /// This property only applies to the outer scroll view, composed of the + /// slivers returned from [headerSliverBuilder]. Since the inner scroll view + /// is not directly configured by the [NestedScrollView]. For both to scroll + /// with the same [ScrollPhysics], configure the scroll view of the [body] + /// the same way if they are expected to match, or use a [ScrollBehavior] as + /// an ancestor so both the inner and outer scroll views inherit the same + /// [ScrollPhysics]. This allows for flexible configurations of the + /// NestedScrollView. + /// + /// The [ScrollPhysics] also determine whether or not the [NestedScrollView] + /// can accept input from the user to change the scroll offset. For example, + /// [NeverScrollableScrollPhysics] typically will not allow the user to drag a + /// scroll view, but in this case, if one of the two scroll views can be + /// dragged, then dragging will be allowed. Configuring both scroll views with + /// [NeverScrollableScrollPhysics] will disallow dragging in this case. final ScrollPhysics? physics; /// A builder for any widgets that are to precede the inner scroll views (as @@ -845,17 +875,18 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont if (!_outerPosition!.haveDimensions) { return; } - double maxInnerExtent = 0.0; + bool innerCanDrag = false; for (final _NestedScrollPosition position in _innerPositions) { if (!position.haveDimensions) { return; } - maxInnerExtent = math.max( - maxInnerExtent, - position.maxScrollExtent - position.minScrollExtent, - ); + innerCanDrag = innerCanDrag + // This refers to the physics of the actual inner scroll position, not + // the whole NestedScrollView, since it is possible to have different + // ScrollPhysics for the inner and outer positions. + || position.physics.shouldAcceptUserOffset(position); } - _outerPosition!.updateCanDrag(maxInnerExtent); + _outerPosition!.updateCanDrag(innerCanDrag); } Future<void> animateTo( @@ -1438,9 +1469,16 @@ class _NestedScrollPosition extends ScrollPosition implements ScrollActivityDele coordinator.updateCanDrag(); } - void updateCanDrag(double totalExtent) { - context.setCanDrag(physics.allowUserScrolling && - (totalExtent > (viewportDimension - maxScrollExtent) || minScrollExtent != maxScrollExtent)); + void updateCanDrag(bool innerCanDrag) { + // This is only called for the outer position + assert(coordinator._outerPosition == this); + context.setCanDrag( + // This refers to the physics of the actual outer scroll position, not + // the whole NestedScrollView, since it is possible to have different + // ScrollPhysics for the inner and outer positions. + physics.shouldAcceptUserOffset(this) + || innerCanDrag, + ); } @override @@ -1756,17 +1794,9 @@ class RenderSliverOverlapAbsorber extends RenderSliver with RenderObjectWithChil } child!.layout(constraints, parentUsesSize: true); final SliverGeometry childLayoutGeometry = child!.geometry!; - geometry = SliverGeometry( + geometry = childLayoutGeometry.copyWith( scrollExtent: childLayoutGeometry.scrollExtent - childLayoutGeometry.maxScrollObstructionExtent, - paintExtent: childLayoutGeometry.paintExtent, - paintOrigin: childLayoutGeometry.paintOrigin, layoutExtent: math.max(0, childLayoutGeometry.paintExtent - childLayoutGeometry.maxScrollObstructionExtent), - maxPaintExtent: childLayoutGeometry.maxPaintExtent, - maxScrollObstructionExtent: childLayoutGeometry.maxScrollObstructionExtent, - hitTestExtent: childLayoutGeometry.hitTestExtent, - visible: childLayoutGeometry.visible, - hasVisualOverflow: childLayoutGeometry.hasVisualOverflow, - scrollOffsetCorrection: childLayoutGeometry.scrollOffsetCorrection, ); handle._setExtents( childLayoutGeometry.maxScrollObstructionExtent, @@ -1914,6 +1944,15 @@ class RenderSliverOverlapInjector extends RenderSliver { void performLayout() { _currentLayoutExtent = handle.layoutExtent; _currentMaxExtent = handle.layoutExtent; + assert( + _currentLayoutExtent != null && _currentMaxExtent != null, + 'SliverOverlapInjector has found no absorbed extent to inject.\n ' + 'The SliverOverlapAbsorber must be an earlier descendant of a common ' + 'ancestor Viewport, so that it will always be laid out before the ' + 'SliverOverlapInjector during a particular frame.\n ' + 'The SliverOverlapAbsorber is typically contained in the list of slivers ' + 'provided by NestedScrollView.headerSliverBuilder.\n' + ); final double clampedLayoutExtent = math.min( _currentLayoutExtent! - constraints.scrollOffset, constraints.remainingPaintExtent, diff --git a/packages/flutter/lib/src/widgets/overlay.dart b/packages/flutter/lib/src/widgets/overlay.dart index aad2536a274de..04c71fab73f0e 100644 --- a/packages/flutter/lib/src/widgets/overlay.dart +++ b/packages/flutter/lib/src/widgets/overlay.dart @@ -1492,47 +1492,35 @@ class _OverlayPortalState extends State<OverlayPortal> { // used as the slot of the overlay child widget. // // The developer must call `show` to reveal the overlay so we can get a unique - // timestamp of the user interaction for sorting. + // timestamp of the user interaction for determining the z-index of the + // overlay child in the overlay. // // Avoid invalidating the cache if possible, since the framework uses `==` to // compare slots, and _OverlayEntryLocation can't override that operator since - // it's mutable. + // it's mutable. Changing slots can be relatively slow. bool _childModelMayHaveChanged = true; _OverlayEntryLocation? _locationCache; + static bool _isTheSameLocation(_OverlayEntryLocation locationCache, _RenderTheaterMarker marker) { + return locationCache._childModel == marker.overlayEntryWidgetState + && locationCache._theater == marker.theater; + } + _OverlayEntryLocation _getLocation(int zOrderIndex, bool targetRootOverlay) { final _OverlayEntryLocation? cachedLocation = _locationCache; - if (cachedLocation != null && !_childModelMayHaveChanged) { + late final _RenderTheaterMarker marker = _RenderTheaterMarker.of(context, targetRootOverlay: targetRootOverlay); + final bool isCacheValid = cachedLocation != null + && (!_childModelMayHaveChanged || _isTheSameLocation(cachedLocation, marker)); + _childModelMayHaveChanged = false; + if (isCacheValid) { assert(cachedLocation._zOrderIndex == zOrderIndex); + assert(cachedLocation._debugIsLocationValid()); return cachedLocation; } - _childModelMayHaveChanged = false; - final _RenderTheaterMarker? marker = _RenderTheaterMarker.maybeOf(context, targetRootOverlay: targetRootOverlay); - if (marker == null) { - throw FlutterError.fromParts(<DiagnosticsNode>[ - ErrorSummary('No Overlay widget found.'), - ErrorDescription( - '${widget.runtimeType} widgets require an Overlay widget ancestor.\n' - 'An overlay lets widgets float on top of other widget children.', - ), - ErrorHint( - 'To introduce an Overlay widget, you can either directly ' - 'include one, or use a widget that contains an Overlay itself, ' - 'such as a Navigator, WidgetApp, MaterialApp, or CupertinoApp.', - ), - ...context.describeMissingAncestor(expectedAncestorType: Overlay), - ]); - } - final _OverlayEntryLocation returnValue; - if (cachedLocation == null) { - returnValue = _OverlayEntryLocation(zOrderIndex, marker.overlayEntryWidgetState, marker.theater); - } else if (cachedLocation._childModel != marker.overlayEntryWidgetState || cachedLocation._theater != marker.theater) { - cachedLocation._dispose(); - returnValue = _OverlayEntryLocation(zOrderIndex, marker.overlayEntryWidgetState, marker.theater); - } else { - returnValue = cachedLocation; - } - assert(returnValue._zOrderIndex == zOrderIndex); - return _locationCache = returnValue; + // Otherwise invalidate the cache and create a new location. + cachedLocation?._debugMarkLocationInvalid(); + final _OverlayEntryLocation newLocation = _OverlayEntryLocation(zOrderIndex, marker.overlayEntryWidgetState, marker.theater); + assert(newLocation._zOrderIndex == zOrderIndex); + return _locationCache = newLocation; } @override @@ -1573,8 +1561,9 @@ class _OverlayPortalState extends State<OverlayPortal> { @override void dispose() { + assert(widget.controller._attachTarget == this); widget.controller._attachTarget = null; - _locationCache?._dispose(); + _locationCache?._debugMarkLocationInvalid(); _locationCache = null; super.dispose(); } @@ -1585,14 +1574,14 @@ class _OverlayPortalState extends State<OverlayPortal> { '${widget.controller.runtimeType}.show() should not be called during build.' ); setState(() { _zOrderIndex = zOrderIndex; }); - _locationCache?._dispose(); + _locationCache?._debugMarkLocationInvalid(); _locationCache = null; } void hide() { assert(SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks); setState(() { _zOrderIndex = null; }); - _locationCache?._dispose(); + _locationCache?._debugMarkLocationInvalid(); _locationCache = null; } @@ -1673,7 +1662,7 @@ final class _OverlayEntryLocation extends LinkedListEntry<_OverlayEntryLocation> } void _addChild(_RenderDeferredLayoutBox child) { - assert(_debugNotDisposed()); + assert(_debugIsLocationValid()); _addToChildModel(child); _theater._addDeferredChild(child); assert(child.parent == _theater); @@ -1688,7 +1677,7 @@ final class _OverlayEntryLocation extends LinkedListEntry<_OverlayEntryLocation> void _moveChild(_RenderDeferredLayoutBox child, _OverlayEntryLocation fromLocation) { assert(fromLocation != this); - assert(_debugNotDisposed()); + assert(_debugIsLocationValid()); final _RenderTheater fromTheater = fromLocation._theater; final _OverlayEntryWidgetState fromModel = fromLocation._childModel; @@ -1704,34 +1693,54 @@ final class _OverlayEntryLocation extends LinkedListEntry<_OverlayEntryLocation> } void _activate(_RenderDeferredLayoutBox child) { - assert(_debugNotDisposed()); + // This call is allowed even when this location is invalidated. + // See _OverlayPortalElement.activate. assert(_overlayChildRenderBox == null, '$_overlayChildRenderBox'); - _theater.adoptChild(child); + _theater._addDeferredChild(child); _overlayChildRenderBox = child; } void _deactivate(_RenderDeferredLayoutBox child) { - assert(_debugNotDisposed()); - _theater.dropChild(child); + // This call is allowed even when this location is invalidated. + _theater._removeDeferredChild(child); _overlayChildRenderBox = null; } - bool _debugNotDisposed() { - if (_debugDisposedStackTrace == null) { + // Throws a StateError if this location is already invalidated and shouldn't + // be used as an OverlayPortal slot. Must be used in asserts. + // + // Generally, `assert(_debugIsLocationValid())` should be used to prevent + // invalid accesses to an invalid `_OverlayEntryLocation` object. Exceptions + // to this rule are _removeChild, _deactive, which will be called when the + // OverlayPortal is being removed from the widget tree and may use the + // location information to perform cleanup tasks. + // + // Another exception is the _activate method which is called by + // _OverlayPortalElement.activate. See the comment in _OverlayPortalElement.activate. + bool _debugIsLocationValid() { + if (_debugMarkLocationInvalidStackTrace == null) { return true; } - throw StateError('$this is already disposed. Stack trace: $_debugDisposedStackTrace'); + throw StateError('$this is already disposed. Stack trace: $_debugMarkLocationInvalidStackTrace'); } - StackTrace? _debugDisposedStackTrace; + // The StackTrace of the first _debugMarkLocationInvalid call. It's only for + // debugging purposes and the StackTrace will only be captured in debug builds. + // + // The effect of this method is not reversible. Once marked invalid, this + // object can't be marked as valid again. + StackTrace? _debugMarkLocationInvalidStackTrace; @mustCallSuper - void _dispose() { - assert(_debugNotDisposed()); + void _debugMarkLocationInvalid() { + assert(_debugIsLocationValid()); assert(() { - _debugDisposedStackTrace = StackTrace.current; + _debugMarkLocationInvalidStackTrace = StackTrace.current; return true; }()); } + + @override + String toString() => '${objectRuntimeType(this, '_OverlayEntryLocation')}[${shortHash(this)}] ${_debugMarkLocationInvalidStackTrace != null ? "(INVALID)":""}'; } class _RenderTheaterMarker extends InheritedWidget { @@ -1750,13 +1759,31 @@ class _RenderTheaterMarker extends InheritedWidget { || oldWidget.overlayEntryWidgetState != overlayEntryWidgetState; } - static _RenderTheaterMarker? maybeOf(BuildContext context, { bool targetRootOverlay = false }) { + static _RenderTheaterMarker of(BuildContext context, { bool targetRootOverlay = false }) { + final _RenderTheaterMarker? marker; if (targetRootOverlay) { final InheritedElement? ancestor = _rootRenderTheaterMarkerOf(context.getElementForInheritedWidgetOfExactType<_RenderTheaterMarker>()); assert(ancestor == null || ancestor.widget is _RenderTheaterMarker); - return ancestor != null ? context.dependOnInheritedElement(ancestor) as _RenderTheaterMarker? : null; + marker = ancestor != null ? context.dependOnInheritedElement(ancestor) as _RenderTheaterMarker? : null; + } else { + marker = context.dependOnInheritedWidgetOfExactType<_RenderTheaterMarker>(); + } + if (marker != null) { + return marker; } - return context.dependOnInheritedWidgetOfExactType<_RenderTheaterMarker>(); + throw FlutterError.fromParts(<DiagnosticsNode>[ + ErrorSummary('No Overlay widget found.'), + ErrorDescription( + '${context.widget.runtimeType} widgets require an Overlay widget ancestor.\n' + 'An overlay lets widgets float on top of other widget children.', + ), + ErrorHint( + 'To introduce an Overlay widget, you can either directly ' + 'include one, or use a widget that contains an Overlay itself, ' + 'such as a Navigator, WidgetApp, MaterialApp, or CupertinoApp.', + ), + ...context.describeMissingAncestor(expectedAncestorType: Overlay), + ]); } static InheritedElement? _rootRenderTheaterMarkerOf(InheritedElement? theaterMarkerElement) { @@ -1784,7 +1811,7 @@ class _OverlayPortal extends RenderObjectWidget { required this.overlayChild, required this.child, }) : assert(overlayChild == null || overlayLocation != null), - assert(overlayLocation == null || overlayLocation._debugNotDisposed()); + assert(overlayLocation == null || overlayLocation._debugIsLocationValid()); final Widget? overlayChild; @@ -1855,6 +1882,9 @@ class _OverlayPortalElement extends RenderObjectElement { if (box != null) { assert(!box.attached); assert(renderObject._deferredLayoutChild == box); + // updateChild has not been called at this point so the RenderTheater in + // the overlay location could be detached. Adding children to a detached + // RenderObject is still allowed however this isn't the most efficient. (overlayChild.slot! as _OverlayEntryLocation)._activate(box); } } @@ -1864,12 +1894,8 @@ class _OverlayPortalElement extends RenderObjectElement { void deactivate() { final Element? overlayChild = _overlayChild; // Instead of just detaching the render objects, removing them from the - // render subtree entirely such that if the widget gets reparented to a - // different overlay entry, the overlay child is inserted in the right - // position in the overlay's child list. - // - // This is also a workaround for the !renderObject.attached assert in the - // `RenderObjectElement.deactive()` method. + // render subtree entirely. This is a workaround for the + // !renderObject.attached assert in the `super.deactive()` method. if (overlayChild != null) { final _RenderDeferredLayoutBox? box = overlayChild.renderObject as _RenderDeferredLayoutBox?; if (box != null) { @@ -1894,7 +1920,7 @@ class _OverlayPortalElement extends RenderObjectElement { // reparenting between _overlayChild and _child, thus the non-null-typed slots. @override void moveRenderObjectChild(_RenderDeferredLayoutBox child, _OverlayEntryLocation oldSlot, _OverlayEntryLocation newSlot) { - assert(newSlot._debugNotDisposed()); + assert(newSlot._debugIsLocationValid()); newSlot._moveChild(child, oldSlot); } @@ -1980,7 +2006,7 @@ final class _RenderDeferredLayoutBox extends RenderProxyBox with _RenderTheaterM @override _RenderTheater get theater { - final AbstractNode? parent = this.parent; + final RenderObject? parent = this.parent; return parent is _RenderTheater ? parent : throw FlutterError('$parent of $this is not a _RenderTheater'); diff --git a/packages/flutter/lib/src/widgets/platform_view.dart b/packages/flutter/lib/src/widgets/platform_view.dart index 55653d2af27df..722a22b983a08 100644 --- a/packages/flutter/lib/src/widgets/platform_view.dart +++ b/packages/flutter/lib/src/widgets/platform_view.dart @@ -345,7 +345,8 @@ class HtmlElementView extends StatelessWidget { super.key, required this.viewType, this.onPlatformViewCreated, - }) : assert(kIsWeb, 'HtmlElementView is only available on Flutter Web.'); + this.creationParams, + }); /// The unique identifier for the HTML view type to be embedded by this widget. /// @@ -357,8 +358,12 @@ class HtmlElementView extends StatelessWidget { /// May be null. final PlatformViewCreatedCallback? onPlatformViewCreated; + /// Passed as the 2nd argument (i.e. `params`) of the registered view factory. + final Object? creationParams; + @override Widget build(BuildContext context) { + assert(kIsWeb, 'HtmlElementView is only available on Flutter Web.'); return PlatformViewLink( viewType: viewType, onCreatePlatformView: _createHtmlElementView, @@ -374,7 +379,11 @@ class HtmlElementView extends StatelessWidget { /// Creates the controller and kicks off its initialization. _HtmlElementViewController _createHtmlElementView(PlatformViewCreationParams params) { - final _HtmlElementViewController controller = _HtmlElementViewController(params.id, viewType); + final _HtmlElementViewController controller = _HtmlElementViewController( + params.id, + viewType, + creationParams, + ); controller._initialize().then((_) { params.onPlatformViewCreated(params.id); onPlatformViewCreated?.call(params.id); @@ -387,6 +396,7 @@ class _HtmlElementViewController extends PlatformViewController { _HtmlElementViewController( this.viewId, this.viewType, + this.creationParams, ); @override @@ -397,12 +407,15 @@ class _HtmlElementViewController extends PlatformViewController { /// A PlatformViewFactory for this type must have been registered. final String viewType; + final dynamic creationParams; + bool _initialized = false; Future<void> _initialize() async { final Map<String, dynamic> args = <String, dynamic>{ 'id': viewId, 'viewType': viewType, + 'params': creationParams, }; await SystemChannels.platform_views.invokeMethod<void>('create', args); _initialized = true; diff --git a/packages/flutter/lib/src/widgets/preferred_size.dart b/packages/flutter/lib/src/widgets/preferred_size.dart index 6714339411a31..899fca2538bf2 100644 --- a/packages/flutter/lib/src/widgets/preferred_size.dart +++ b/packages/flutter/lib/src/widgets/preferred_size.dart @@ -69,8 +69,8 @@ class PreferredSize extends StatelessWidget implements PreferredSizeWidget { /// Creates a widget that has a preferred size that the parent can query. const PreferredSize({ super.key, - required this.child, required this.preferredSize, + required this.child, }); /// The widget below this widget in the tree. diff --git a/packages/flutter/lib/src/widgets/reorderable_list.dart b/packages/flutter/lib/src/widgets/reorderable_list.dart index 24465c819d8f3..cb9de37dec10d 100644 --- a/packages/flutter/lib/src/widgets/reorderable_list.dart +++ b/packages/flutter/lib/src/widgets/reorderable_list.dart @@ -257,6 +257,8 @@ class ReorderableList extends StatefulWidget { final Widget? prototypeItem; /// {@macro flutter.widgets.EdgeDraggingAutoScroller.velocityScalar} + /// + /// {@macro flutter.widgets.SliverReorderableList.autoScrollerVelocityScalar.default} final double? autoScrollerVelocityScalar; /// The state from the closest instance of this class that encloses the given @@ -450,13 +452,17 @@ class SliverReorderableList extends StatefulWidget { this.itemExtent, this.prototypeItem, this.proxyDecorator, - this.autoScrollerVelocityScalar, - }) : assert(itemCount >= 0), + double? autoScrollerVelocityScalar, + }) : autoScrollerVelocityScalar = autoScrollerVelocityScalar ?? _kDefaultAutoScrollVelocityScalar, + assert(itemCount >= 0), assert( itemExtent == null || prototypeItem == null, 'You can only pass itemExtent or prototypeItem, not both', ); + // An eyeballed value for a smooth scrolling experience. + static const double _kDefaultAutoScrollVelocityScalar = 50; + /// {@macro flutter.widgets.reorderable_list.itemBuilder} final IndexedWidgetBuilder itemBuilder; @@ -485,7 +491,11 @@ class SliverReorderableList extends StatefulWidget { final Widget? prototypeItem; /// {@macro flutter.widgets.EdgeDraggingAutoScroller.velocityScalar} - final double? autoScrollerVelocityScalar; + /// + /// {@template flutter.widgets.SliverReorderableList.autoScrollerVelocityScalar.default} + /// Defaults to 50 if not set or set to null. + /// {@endtemplate} + final double autoScrollerVelocityScalar; @override SliverReorderableListState createState() => SliverReorderableListState(); diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index 6c3ddf881c4f8..ad919c96dc697 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -1813,9 +1813,9 @@ abstract class PopupRoute<T> extends ModalRoute<T> { /// /// ## Type arguments /// -/// When using more aggressive -/// [lints](http://dart-lang.github.io/linter/lints/), in particular lints such -/// as `always_specify_types`, the Dart analyzer will require that certain types +/// When using more aggressive [lints](https://dart.dev/lints), +/// in particular lints such as `always_specify_types`, +/// the Dart analyzer will require that certain types /// be given with their type arguments. Since the [Route] class and its /// subclasses have a type argument, this includes the arguments passed to this /// class. Consider using `dynamic` to specify the entire class of routes rather diff --git a/packages/flutter/lib/src/widgets/scroll_delegate.dart b/packages/flutter/lib/src/widgets/scroll_delegate.dart index 4defcc28233f0..ba5e826b9498c 100644 --- a/packages/flutter/lib/src/widgets/scroll_delegate.dart +++ b/packages/flutter/lib/src/widgets/scroll_delegate.dart @@ -9,6 +9,7 @@ import 'automatic_keep_alive.dart'; import 'basic.dart'; import 'framework.dart'; import 'selection_container.dart'; +import 'two_dimensional_viewport.dart'; export 'package:flutter/rendering.dart' show SliverGridDelegate, @@ -630,7 +631,7 @@ class SliverChildListDelegate extends SliverChildDelegate { /// [children] such as `someWidget.children.add(...)` or /// passing a reference of the original list value to the [children] parameter /// will result in incorrect behaviors. Whenever the - /// children list is modified, a new list object should be provided. + /// children list is modified, a new list object must be provided. /// /// The following code corrects the problem mentioned above. /// @@ -861,3 +862,280 @@ Widget _createErrorWidget(Object exception, StackTrace stackTrace) { FlutterError.reportError(details); return ErrorWidget.builder(details); } + +// TODO(Piinks): Come back and add keep alive support, https://github.com/flutter/flutter/issues/126297 +/// A delegate that supplies children for scrolling in two dimensions. +/// +/// A [TwoDimensionalScrollView] lazily constructs its box children to avoid +/// creating more children than are visible through the +/// [TwoDimensionalViewport]. Rather than receiving children as an +/// explicit [List], it receives its children using a +/// [TwoDimensionalChildDelegate]. +/// +/// As a ChangeNotifier, this delegate allows subclasses to notify its listeners +/// (typically as a subclass of [RenderTwoDimensionalViewport]) to rebuild when +/// aspects of the delegate change. When values returned by getters or builders +/// on this delegate change, [notifyListeners] should be called. This signals to +/// the [RenderTwoDimensionalViewport] that the getters and builders need to be +/// re-queried to update the layout of children in the viewport. +/// +/// See also: +/// +/// * [TwoDimensionalChildBuilderDelegate], an concrete subclass of this that +/// lazily builds children on demand. +/// * [TwoDimensionalChildListDelegate], an concrete subclass of this that +/// uses a two dimensional array to layout children. +abstract class TwoDimensionalChildDelegate extends ChangeNotifier { + /// Returns the child with the given [ChildVicinity], which is described in + /// terms of x and y indices. + /// + /// Subclasses must implement this function and will typically wrap their + /// children in [RepaintBoundary] widgets. + /// + /// The values returned by this method are cached. To indicate that the + /// widgets have changed, a new delegate must be provided, and the new + /// delegate's [shouldRebuild] method must return true. Alternatively, + /// calling [notifyListeners] will allow the same delegate to be used. + Widget? build(BuildContext context, ChildVicinity vicinity); + + /// Called whenever a new instance of the child delegate class is + /// provided. + /// + /// If the new instance represents different information than the old + /// instance, then the method should return true, otherwise it should return + /// false. + /// + /// If the method returns false, then the [build] call might be optimized + /// away. + bool shouldRebuild(covariant TwoDimensionalChildDelegate oldDelegate); +} + +/// A delegate that supplies children for a [TwoDimensionalScrollView] using a +/// builder callback. +/// +/// The widgets returned from the builder callback are automatically wrapped in +/// [RepaintBoundary] widgets if [addRepaintBoundaries] is true +/// (also the default). +/// +/// See also: +/// +/// * [TwoDimensionalChildListDelegate], which is a similar delegate that has an +/// explicit two dimensional array of children. +/// * [SliverChildBuilderDelegate], which is a delegate that uses a builder +/// callback to construct the children in one dimension instead of two. +/// * [SliverChildListDelegate], which is a delegate that has an explicit list +/// of children in one dimension instead of two. +class TwoDimensionalChildBuilderDelegate extends TwoDimensionalChildDelegate { + /// Creates a delegate that supplies children for a [TwoDimensionalScrollView] + /// using the given builder callback. + TwoDimensionalChildBuilderDelegate({ + this.addRepaintBoundaries = true, + required this.builder, + int? maxXIndex, + int? maxYIndex, + }) : assert(maxYIndex == null || maxYIndex >= -1), + assert(maxXIndex == null || maxXIndex >= -1), + _maxYIndex = maxYIndex, + _maxXIndex = maxXIndex; + + /// Called to build children on demand. + /// + /// Implementors of [RenderTwoDimensionalViewport.layoutChildSequence] + /// call this builder to create the children of the viewport. For + /// [ChildVicinity] indices greater than [maxXIndex] or [maxYIndex], null will + /// be returned by the default [build] implementation. This default behavior + /// can be changed by overriding the build method. + /// + /// Must return null if asked to build a widget with a [ChildVicinity] that + /// does not exist. + /// + /// The delegate wraps the children returned by this builder in + /// [RepaintBoundary] widgets if [addRepaintBoundaries] is true. + final TwoDimensionalIndexedWidgetBuilder builder; + + /// The maximum [ChildVicinity.xIndex] for children in the x axis. + /// + /// {@template flutter.widgets.twoDimensionalChildBuilderDelegate.maxIndex} + /// For each [ChildVicinity], the child's relative location is described in + /// terms of x and y indices to facilitate a consistent visitor pattern for + /// all children in the viewport. + /// + /// This is fairly straightforward in the context of a table implementation, + /// where there is usually the same number of columns in every row and vice + /// versa, each aligned one after the other. + /// + /// When plotting children more abstractly in two dimensional space, there may + /// be more x indices for a given y index than another y index. An example of + /// this would be a scatter plot where there are more children at the top of + /// the graph than at the bottom. + /// + /// If null, subclasses of [RenderTwoDimensionalViewport] can continue call on + /// the [builder] until null has been returned for each known index of x and + /// y. In some cases, null may not be a terminating result, such as a table + /// with a merged cell spanning multiple indices. Refer to the + /// [TwoDimensionalViewport] subclass to learn how this value is applied in + /// the specific use case. + /// + /// If not null, the value must be greater than or equal to -1, where -1 + /// indicates there will be no children at all provided to the + /// [TwoDimensionalViewport]. + /// + /// If the value changes, the delegate will call [notifyListeners]. This + /// informs the [RenderTwoDimensionalViewport] that any cached information + /// from the delegate is invalid. + /// {@endtemplate} + /// + /// This value represents the greatest x index of all [ChildVicinity]s for the + /// two dimensional scroll view. + /// + /// See also: + /// + /// * [RenderTwoDimensionalViewport.buildOrObtainChildFor], the method that + /// leads to calling on the delegate to build a child of the given + /// [ChildVicinity]. + int? get maxXIndex => _maxXIndex; + int? _maxXIndex; + set maxXIndex(int? value) { + if (value == maxXIndex) { + return; + } + assert(value == null || value >= -1); + _maxXIndex = value; + notifyListeners(); + } + + /// The maximum [ChildVicinity.yIndex] for children in the y axis. + /// + /// {@macro flutter.widgets.twoDimensionalChildBuilderDelegate.maxIndex} + /// + /// This value represents the greatest y index of all [ChildVicinity]s for the + /// two dimensional scroll view. + /// + /// See also: + /// + /// * [RenderTwoDimensionalViewport.buildOrObtainChildFor], the method that + /// leads to calling on the delegate to build a child of the given + /// [ChildVicinity]. + int? get maxYIndex => _maxYIndex; + int? _maxYIndex; + set maxYIndex(int? value) { + if (maxYIndex == value) { + return; + } + assert(value == null || value >= -1); + _maxYIndex = value; + notifyListeners(); + } + + /// {@macro flutter.widgets.SliverChildBuilderDelegate.addRepaintBoundaries} + final bool addRepaintBoundaries; + + @override + Widget? build(BuildContext context, ChildVicinity vicinity) { + // If we have exceeded explicit upper bounds, return null. + if (vicinity.xIndex < 0 || (maxXIndex != null && vicinity.xIndex > maxXIndex!)) { + return null; + } + if (vicinity.yIndex < 0 || (maxYIndex != null && vicinity.yIndex > maxYIndex!)) { + return null; + } + + Widget? child; + try { + child = builder(context, vicinity); + } catch (exception, stackTrace) { + child = _createErrorWidget(exception, stackTrace); + } + if (child == null) { + return null; + } + if (addRepaintBoundaries) { + child = RepaintBoundary(child: child); + } + return child; + } + + @override + bool shouldRebuild(covariant TwoDimensionalChildDelegate oldDelegate) => true; +} + +/// A delegate that supplies children for a [TwoDimensionalViewport] using an +/// explicit two dimensional array. +/// +/// In general, building all the widgets in advance is not efficient. It is +/// better to create a delegate that builds them on demand using +/// [TwoDimensionalChildBuilderDelegate] or by subclassing +/// [TwoDimensionalChildDelegate] directly. +/// +/// This class is provided for the cases where either the list of children is +/// known well in advance (ideally the children are themselves compile-time +/// constants, for example), and therefore will not be built each time the +/// delegate itself is created, or the array is small, such that it's likely +/// always visible (and thus there is nothing to be gained by building it on +/// demand). +/// +/// The widgets in the given [children] list are automatically wrapped in +/// [RepaintBoundary] widgets if [addRepaintBoundaries] is true +/// (also the default). +/// +/// The [children] are accessed for each [ChildVicinity.yIndex] and +/// [ChildVicinity.xIndex] of the [TwoDimensionalViewport] as +/// `children[vicinity.yIndex][vicinity.xIndex]`. +/// +/// See also: +/// +/// * [TwoDimensionalChildBuilderDelegate], which is a delegate that uses a +/// builder callback to construct the children. +/// * [SliverChildBuilderDelegate], which is a delegate that uses a builder +/// callback to construct the children in one dimension instead of two. +/// * [SliverChildListDelegate], which is a delegate that has an explicit list +/// of children in one dimension instead of two. +class TwoDimensionalChildListDelegate extends TwoDimensionalChildDelegate { + /// Creates a delegate that supplies children for a [TwoDimensionalScrollView]. + /// + /// The [children] and [addRepaintBoundaries] must not be + /// null. + TwoDimensionalChildListDelegate({ + this.addRepaintBoundaries = true, + required this.children, + }); + + /// The widgets to display. + /// + /// Also, a [Widget] in Flutter is immutable, so directly modifying the + /// [children] such as `someWidget.children.add(...)` or + /// passing a reference of the original list value to the [children] parameter + /// will result in incorrect behaviors. Whenever the + /// children list is modified, a new list object must be provided. + /// + /// The [children] are accessed for each [ChildVicinity.yIndex] and + /// [ChildVicinity.xIndex] of the [TwoDimensionalViewport] as + /// `children[vicinity.yIndex][vicinity.xIndex]`. + final List<List<Widget>> children; + + /// {@macro flutter.widgets.SliverChildBuilderDelegate.addRepaintBoundaries} + final bool addRepaintBoundaries; + + @override + Widget? build(BuildContext context, ChildVicinity vicinity) { + // If we have exceeded explicit upper bounds, return null. + if (vicinity.yIndex < 0 || vicinity.yIndex >= children.length) { + return null; + } + if (vicinity.xIndex < 0 || vicinity.xIndex >= children[vicinity.yIndex].length) { + return null; + } + + Widget child = children[vicinity.yIndex][vicinity.xIndex]; + if (addRepaintBoundaries) { + child = RepaintBoundary(child: child); + } + + return child; + } + + @override + bool shouldRebuild(covariant TwoDimensionalChildListDelegate oldDelegate) { + return children != oldDelegate.children; + } +} diff --git a/packages/flutter/lib/src/widgets/scroll_position.dart b/packages/flutter/lib/src/widgets/scroll_position.dart index 3712b9b9766eb..e9154a1218afb 100644 --- a/packages/flutter/lib/src/widgets/scroll_position.dart +++ b/packages/flutter/lib/src/widgets/scroll_position.dart @@ -757,6 +757,26 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { context.setSemanticsActions(_semanticActions!); } + ScrollPositionAlignmentPolicy _maybeFlipAlignment(ScrollPositionAlignmentPolicy alignmentPolicy) { + return switch (alignmentPolicy) { + // Don't flip when explicit. + ScrollPositionAlignmentPolicy.explicit => alignmentPolicy, + ScrollPositionAlignmentPolicy.keepVisibleAtEnd => ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ScrollPositionAlignmentPolicy.keepVisibleAtStart => ScrollPositionAlignmentPolicy.keepVisibleAtEnd, + }; + } + + ScrollPositionAlignmentPolicy _applyAxisDirectionToAlignmentPolicy(ScrollPositionAlignmentPolicy alignmentPolicy) { + return switch (axisDirection) { + // Start and end alignments must account for axis direction. + // When focus is requested for example, it knows the directionality of the + // keyboard keys initiating traversal, but not the direction of the + // Scrollable. + AxisDirection.up || AxisDirection.left => _maybeFlipAlignment(alignmentPolicy), + AxisDirection.down || AxisDirection.right => alignmentPolicy, + }; + } + /// Animates the position such that the given object is as visible as possible /// by just scrolling this position. /// @@ -790,7 +810,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { } double target; - switch (alignmentPolicy) { + switch (_applyAxisDirectionToAlignmentPolicy(alignmentPolicy)) { case ScrollPositionAlignmentPolicy.explicit: target = clampDouble(viewport.getOffsetToReveal(object, alignment, rect: targetRect).offset, minScrollExtent, maxScrollExtent); case ScrollPositionAlignmentPolicy.keepVisibleAtEnd: diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart index e95a53153ac70..33708cc11d648 100644 --- a/packages/flutter/lib/src/widgets/scroll_view.dart +++ b/packages/flutter/lib/src/widgets/scroll_view.dart @@ -21,6 +21,7 @@ import 'scroll_delegate.dart'; import 'scroll_notification.dart'; import 'scroll_physics.dart'; import 'scrollable.dart'; +import 'scrollable_helpers.dart'; import 'sliver.dart'; import 'sliver_prototype_extent_list.dart'; import 'viewport.dart'; @@ -39,7 +40,8 @@ enum ScrollViewKeyboardDismissBehavior { onDrag, } -/// A widget that scrolls. +/// A widget that combines a [Scrollable] and a [Viewport] to create an +/// interactive scrolling pane of content in one dimension. /// /// Scrollable widgets consist of three pieces: /// @@ -71,6 +73,8 @@ enum ScrollViewKeyboardDismissBehavior { /// effects using slivers. /// * [ScrollNotification] and [NotificationListener], which can be used to watch /// the scroll position without using a [ScrollController]. +/// * [TwoDimensionalScrollView], which is a similar widget [ScrollView] that +/// scrolls in two dimensions. abstract class ScrollView extends StatelessWidget { /// Creates a widget that scrolls. /// @@ -1769,7 +1773,7 @@ class ListView extends BoxScrollView { /// {@end-tool} /// /// By default, [GridView] will automatically pad the limits of the -/// grids's scrollable to avoid partial obstructions indicated by +/// grid's scrollable to avoid partial obstructions indicated by /// [MediaQuery]'s padding. To avoid this behavior, override with a /// zero [padding] property. /// diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index 994071efcc516..17a90007d42bb 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -40,7 +40,12 @@ export 'package:flutter/physics.dart' show Tolerance; /// scrollable content is displayed. typedef ViewportBuilder = Widget Function(BuildContext context, ViewportOffset position); -/// A widget that scrolls. +/// Signature used by [TwoDimensionalScrollable] to build the viewport through +/// which the scrollable content is displayed. +typedef TwoDimensionalViewportBuilder = Widget Function(BuildContext context, ViewportOffset verticalPosition, ViewportOffset horizontalPosition); + +/// A widget that manages scrolling in one dimension and informs the [Viewport] +/// through which the content is viewed. /// /// [Scrollable] implements the interaction model for a scrollable widget, /// including gesture recognition, but does not have an opinion about how the @@ -177,6 +182,7 @@ class Scrollable extends StatefulWidget { /// slivers and sizes itself based on the size of the slivers. final ViewportBuilder viewportBuilder; + /// {@template flutter.widgets.Scrollable.incrementCalculator} /// An optional function that will be called to calculate the distance to /// scroll when the scrollable is asked to scroll via the keyboard using a /// [ScrollAction]. @@ -188,14 +194,17 @@ class Scrollable extends StatefulWidget { /// If [incrementCalculator] is null, the default for /// [ScrollIncrementType.page] is 80% of the size of the scroll window, and /// for [ScrollIncrementType.line], 50 logical pixels. + /// {@endtemplate} final ScrollIncrementCalculator? incrementCalculator; + /// {@template flutter.widgets.scrollable.excludeFromSemantics} /// Whether the scroll actions introduced by this [Scrollable] are exposed /// in the semantics tree. /// /// Text fields with an overflow are usually scrollable to make sure that the /// user can get to the beginning/end of the entered text. However, these /// scrolling actions are generally not exposed to the semantics layer. + /// {@endtemplate} /// /// See also: /// @@ -311,6 +320,10 @@ class Scrollable extends StatefulWidget { /// the nearest enclosing [ScrollableState] in that [Axis] is returned, or /// null if there is none. /// + /// This finds the nearest _ancestor_ [Scrollable] of the `context`. This + /// means that if the `context` is that of a [Scrollable], it will _not_ find + /// _that_ [Scrollable]. + /// /// See also: /// /// * [Scrollable.of], which is similar to this method, but asserts @@ -350,6 +363,10 @@ class Scrollable extends StatefulWidget { /// target [Scrollable] is not the closest instance. When [axis] is provided, /// the nearest enclosing [ScrollableState] in that [Axis] is returned. /// + /// This finds the nearest _ancestor_ [Scrollable] of the `context`. This + /// means that if the `context` is that of a [Scrollable], it will _not_ find + /// _that_ [Scrollable]. + /// /// If no [Scrollable] ancestor is found, then this method will assert in /// debug mode, and throw an exception in release mode. /// @@ -934,7 +951,6 @@ class ScrollableState extends State<Scrollable> with TickerProviderStateMixin, R Widget result = _ScrollableScope( scrollable: this, position: position, - // TODO(ianh): Having all these global keys is sad. child: Listener( onPointerSignal: _receivedPointerSignal, child: RawGestureDetector( @@ -1611,3 +1627,690 @@ class _RestorableScrollOffset extends RestorableValue<double?> { @override bool get enabled => value != null; } + +// 2D SCROLLING + +/// Specifies how to configure the [DragGestureRecognizer]s of a +/// [TwoDimensionalScrollable]. +// TODO(Piinks): Add sample code, https://github.com/flutter/flutter/issues/126298 +enum DiagonalDragBehavior { + /// This behavior will not allow for any diagonal scrolling. + /// + /// Drag gestures in one direction or the other will lock the input axis until + /// the gesture is released. + none, + + /// This behavior will only allow diagonal scrolling on a weighted + /// scale per gesture event. + /// + /// This means that after initially evaluating the drag gesture, the weighted + /// evaluation (based on [kTouchSlop]) stands until the gesture is released. + weightedEvent, + + /// This behavior will only allow diagonal scrolling on a weighted + /// scale that is evaluated throughout a gesture event. + /// + /// This means that during each update to the drag gesture, the scrolling + /// axis will be allowed to scroll diagonally if it exceeds the + /// [kTouchSlop]. + weightedContinuous, + + /// This behavior allows free movement in any and all directions when + /// dragging. + free, +} + +/// A widget that manages scrolling in both the vertical and horizontal +/// dimensions and informs the [TwoDimensionalViewport] through which the +/// content is viewed. +/// +/// [TwoDimensionalScrollable] implements the interaction model for a scrollable +/// widget in both the vertical and horizontal axes, including gesture +/// recognition, but does not have an opinion about how the +/// [TwoDimensionalViewport], which actually displays the children, is +/// constructed. +/// +/// It's rare to construct a [TwoDimensionalScrollable] directly. Instead, +/// consider subclassing [TwoDimensionalScrollView], which combines scrolling, +/// viewporting, and a layout model in both dimensions. +/// +/// See also: +/// +/// * [TwoDimensionalScrollView], an abstract base class for displaying a +/// scrolling array of children in both directions. +/// * [TwoDimensionalViewport], which can be used to customize the child layout +/// model. +class TwoDimensionalScrollable extends StatefulWidget { + /// Creates a widget that scrolls in two dimensions. + /// + /// The [horizontalDetails], [verticalDetails], and [viewportBuilder] must not + /// be null. + const TwoDimensionalScrollable({ + super.key, + required this.horizontalDetails, + required this.verticalDetails, + required this.viewportBuilder, + this.incrementCalculator, + this.restorationId, + this.excludeFromSemantics = false, + this.diagonalDragBehavior = DiagonalDragBehavior.none, + this.dragStartBehavior = DragStartBehavior.start, + }); + + /// How scrolling gestures should lock to one axis, or allow free movement + /// in both axes. + final DiagonalDragBehavior diagonalDragBehavior; + + /// The configuration of the horizontal [Scrollable]. + /// + /// These [ScrollableDetails] can be used to set the [AxisDirection], + /// [ScrollController], [ScrollPhysics] and more for the horizontal axis. + final ScrollableDetails horizontalDetails; + + /// The configuration of the vertical [Scrollable]. + /// + /// These [ScrollableDetails] can be used to set the [AxisDirection], + /// [ScrollController], [ScrollPhysics] and more for the vertical axis. + final ScrollableDetails verticalDetails; + + /// Builds the viewport through which the scrollable content is displayed. + /// + /// A [TwoDimensionalViewport] uses two given [ViewportOffset]s to determine + /// which part of its content is actually visible through the viewport. + /// + /// See also: + /// + /// * [TwoDimensionalViewport], which is a viewport that displays a span of + /// widgets in both dimensions. + final TwoDimensionalViewportBuilder viewportBuilder; + + /// {@macro flutter.widgets.Scrollable.incrementCalculator} + /// + /// This value applies in both axes. + final ScrollIncrementCalculator? incrementCalculator; + + /// {@macro flutter.widgets.scrollable.restorationId} + /// + /// Internally, the [TwoDimensionalScrollable] will introduce a + /// [RestorationScope] that will be assigned this value. The two [Scrollable]s + /// within will then be given unique IDs within this scope. + final String? restorationId; + + /// {@macro flutter.widgets.scrollable.excludeFromSemantics} + /// + /// This value applies to both axes. + final bool excludeFromSemantics; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + /// + /// This value applies in both axes. + final DragStartBehavior dragStartBehavior; + + @override + State<TwoDimensionalScrollable> createState() => TwoDimensionalScrollableState(); + + /// The state from the closest instance of this class that encloses the given + /// context, or null if none is found. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// TwoDimensionalScrollableState? scrollable = TwoDimensionalScrollable.maybeOf(context); + /// ``` + /// + /// Calling this method will create a dependency on the closest + /// [TwoDimensionalScrollable] in the [context]. The internal [Scrollable]s + /// can be accessed through [TwoDimensionalScrollableState.verticalScrollable] + /// and [TwoDimensionalScrollableState.horizontalScrollable]. + /// + /// Alternatively, [Scrollable.maybeOf] can be used by providing the desired + /// [Axis] to the `axis` parameter. + /// + /// See also: + /// + /// * [TwoDimensionalScrollable.of], which is similar to this method, but + /// asserts if no [Scrollable] ancestor is found. + static TwoDimensionalScrollableState? maybeOf(BuildContext context) { + final _TwoDimensionalScrollableScope? widget = context.dependOnInheritedWidgetOfExactType<_TwoDimensionalScrollableScope>(); + return widget?.twoDimensionalScrollable; + } + + /// The state from the closest instance of this class that encloses the given + /// context. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// TwoDimensionalScrollableState scrollable = TwoDimensionalScrollable.of(context); + /// ``` + /// + /// Calling this method will create a dependency on the closest + /// [TwoDimensionalScrollable] in the [context]. The internal [Scrollable]s + /// can be accessed through [TwoDimensionalScrollableState.verticalScrollable] + /// and [TwoDimensionalScrollableState.horizontalScrollable]. + /// + /// If no [TwoDimensionalScrollable] ancestor is found, then this method will + /// assert in debug mode, and throw an exception in release mode. + /// + /// Alternatively, [Scrollable.of] can be used by providing the desired [Axis] + /// to the `axis` parameter. + /// + /// See also: + /// + /// * [TwoDimensionalScrollable.maybeOf], which is similar to this method, + /// but returns null if no [TwoDimensionalScrollable] ancestor is found. + static TwoDimensionalScrollableState of(BuildContext context) { + final TwoDimensionalScrollableState? scrollableState = maybeOf(context); + assert(() { + if (scrollableState == null) { + throw FlutterError.fromParts(<DiagnosticsNode>[ + ErrorSummary( + 'TwoDimensionalScrollable.of() was called with a context that does ' + 'not contain a TwoDimensionalScrollable widget.\n' + ), + ErrorDescription( + 'No TwoDimensionalScrollable widget ancestor could be found starting ' + 'from the context that was passed to TwoDimensionalScrollable.of(). ' + 'This can happen because you are using a widget that looks for a ' + 'TwoDimensionalScrollable ancestor, but no such ancestor exists.\n' + 'The context used was:\n' + ' $context', + ), + ]); + } + return true; + }()); + return scrollableState!; + } +} + +/// State object for a [TwoDimensionalScrollable] widget. +/// +/// To manipulate one of the internal [Scrollable] widget's scroll position, use +/// the object obtained from the [verticalScrollable] or [horizontalScrollable] +/// property. +/// +/// To be informed of when a [TwoDimensionalScrollable] widget is scrolling, +/// use a [NotificationListener] to listen for [ScrollNotification]s. +/// Both axes will have the same viewport depth since there is only one +/// viewport, and so should be differentiated by the [Axis] of the +/// [ScrollMetrics] provided by the notification. +class TwoDimensionalScrollableState extends State<TwoDimensionalScrollable> { + ScrollController? _verticalFallbackController; + ScrollController? _horizontalFallbackController; + final GlobalKey<ScrollableState> _verticalOuterScrollableKey = GlobalKey<ScrollableState>(); + final GlobalKey<ScrollableState> _horizontalInnerScrollableKey = GlobalKey<ScrollableState>(); + + /// The [ScrollableState] of the vertical axis. + /// + /// Accessible by calling [TwoDimensionalScrollable.of]. + /// + /// Alternatively, [Scrollable.of] can be used by providing [Axis.vertical] + /// to the `axis` parameter. + ScrollableState get verticalScrollable { + assert(_verticalOuterScrollableKey.currentState != null); + return _verticalOuterScrollableKey.currentState!; + } + + /// The [ScrollableState] of the horizontal axis. + /// + /// Accessible by calling [TwoDimensionalScrollable.of]. + /// + /// Alternatively, [Scrollable.of] can be used by providing [Axis.horizontal] + /// to the `axis` parameter. + ScrollableState get horizontalScrollable { + assert(_horizontalInnerScrollableKey.currentState != null); + return _horizontalInnerScrollableKey.currentState!; + } + + @override + void initState() { + if (widget.verticalDetails.controller == null) { + _verticalFallbackController = ScrollController(); + } + if (widget.horizontalDetails.controller == null) { + _horizontalFallbackController = ScrollController(); + } + super.initState(); + } + + @override + void didUpdateWidget(TwoDimensionalScrollable oldWidget) { + super.didUpdateWidget(oldWidget); + // Handle changes in the provided/fallback scroll controllers + + // Vertical + if (oldWidget.verticalDetails.controller != widget.verticalDetails.controller) { + if (oldWidget.verticalDetails.controller == null) { + // The old controller was null, meaning the fallback cannot be null. + // Dispose of the fallback. + assert(_verticalFallbackController != null); + assert(widget.verticalDetails.controller != null); + _verticalFallbackController!.dispose(); + _verticalFallbackController = null; + } else if (widget.verticalDetails.controller == null) { + // If the new controller is null, we need to set up the fallback + // ScrollController. + assert(_verticalFallbackController == null); + _verticalFallbackController = ScrollController(); + } + } + + // Horizontal + if (oldWidget.horizontalDetails.controller != widget.horizontalDetails.controller) { + if (oldWidget.horizontalDetails.controller == null) { + // The old controller was null, meaning the fallback cannot be null. + // Dispose of the fallback. + assert(_horizontalFallbackController != null); + assert(widget.horizontalDetails.controller != null); + _horizontalFallbackController!.dispose(); + _horizontalFallbackController = null; + } else if (widget.horizontalDetails.controller == null) { + // If the new controller is null, we need to set up the fallback + // ScrollController. + assert(_horizontalFallbackController == null); + _horizontalFallbackController = ScrollController(); + } + } + } + + @override + Widget build(BuildContext context) { + assert( + axisDirectionToAxis(widget.verticalDetails.direction) == Axis.vertical, + 'TwoDimensionalScrollable.verticalDetails are not Axis.vertical.' + ); + assert( + axisDirectionToAxis(widget.horizontalDetails.direction) == Axis.horizontal, + 'TwoDimensionalScrollable.horizontalDetails are not Axis.horizontal.' + ); + + final Widget result = RestorationScope( + restorationId: widget.restorationId, + child: _VerticalOuterDimension( + key: _verticalOuterScrollableKey, + axisDirection: widget.verticalDetails.direction, + controller: widget.verticalDetails.controller + ?? _verticalFallbackController!, + physics: widget.verticalDetails.physics, + clipBehavior: widget.verticalDetails.clipBehavior + ?? widget.verticalDetails.decorationClipBehavior + ?? Clip.hardEdge, + incrementCalculator: widget.incrementCalculator, + excludeFromSemantics: widget.excludeFromSemantics, + restorationId: 'OuterVerticalTwoDimensionalScrollable', + dragStartBehavior: widget.dragStartBehavior, + diagonalDragBehavior: widget.diagonalDragBehavior, + viewportBuilder: (BuildContext context, ViewportOffset verticalOffset) { + return _HorizontalInnerDimension( + key: _horizontalInnerScrollableKey, + axisDirection: widget.horizontalDetails.direction, + controller: widget.horizontalDetails.controller + ?? _horizontalFallbackController!, + physics: widget.horizontalDetails.physics, + clipBehavior: widget.horizontalDetails.clipBehavior + ?? widget.horizontalDetails.decorationClipBehavior + ?? Clip.hardEdge, + incrementCalculator: widget.incrementCalculator, + excludeFromSemantics: widget.excludeFromSemantics, + restorationId: 'InnerHorizontalTwoDimensionalScrollable', + dragStartBehavior: widget.dragStartBehavior, + diagonalDragBehavior: widget.diagonalDragBehavior, + viewportBuilder: (BuildContext context, ViewportOffset horizontalOffset) { + return widget.viewportBuilder(context, verticalOffset, horizontalOffset); + }, + ); + } + ) + ); + + // TODO(Piinks): Build scrollbars for 2 dimensions instead of 1, + // https://github.com/flutter/flutter/issues/122348 + + return _TwoDimensionalScrollableScope( + twoDimensionalScrollable: this, + child: result, + ); + } + + @override + void dispose() { + _verticalFallbackController?.dispose(); + _horizontalFallbackController?.dispose(); + super.dispose(); + } +} + +// Enable TwoDimensionalScrollable.of() to work as if +// TwoDimensionalScrollableState was an inherited widget. +// TwoDimensionalScrollableState.build() always rebuilds its +// _TwoDimensionalScrollableScope. +class _TwoDimensionalScrollableScope extends InheritedWidget { + const _TwoDimensionalScrollableScope({ + required this.twoDimensionalScrollable, + required super.child, + }); + + final TwoDimensionalScrollableState twoDimensionalScrollable; + + @override + bool updateShouldNotify(_TwoDimensionalScrollableScope old) => false; +} + +// Vertical outer scrollable of 2D scrolling +class _VerticalOuterDimension extends Scrollable { + const _VerticalOuterDimension({ + super.key, + required super.viewportBuilder, + required super.axisDirection, + super.controller, + super.physics, + super.clipBehavior, + super.incrementCalculator, + super.excludeFromSemantics, + super.dragStartBehavior, + super.restorationId, + this.diagonalDragBehavior = DiagonalDragBehavior.none, + }) : assert(axisDirection == AxisDirection.up || axisDirection == AxisDirection.down); + + final DiagonalDragBehavior diagonalDragBehavior; + + @override + _VerticalOuterDimensionState createState() => _VerticalOuterDimensionState(); +} + +class _VerticalOuterDimensionState extends ScrollableState { + DiagonalDragBehavior get diagonalDragBehavior => (widget as _VerticalOuterDimension).diagonalDragBehavior; + + @override + void setCanDrag(bool value) { + switch (diagonalDragBehavior) { + case DiagonalDragBehavior.none: + // If we aren't scrolling diagonally, the default drag gesture + // recognizer is used. + super.setCanDrag(value); + return; + case DiagonalDragBehavior.weightedEvent: + case DiagonalDragBehavior.weightedContinuous: + case DiagonalDragBehavior.free: + if (value) { + // If a type of diagonal scrolling is enabled, a panning gesture + // recognizer will be created for the _InnerDimension. So in this + // case, the _OuterDimension does not require a gesture recognizer. + _gestureRecognizers = const <Type, GestureRecognizerFactory>{}; + // Cancel the active hold/drag (if any) because the gesture recognizers + // will soon be disposed by our RawGestureDetector, and we won't be + // receiving pointer up events to cancel the hold/drag. + _handleDragCancel(); + _lastCanDrag = value; + _lastAxisDirection = widget.axis; + if (_gestureDetectorKey.currentState != null) { + _gestureDetectorKey.currentState!.replaceGestureRecognizers(_gestureRecognizers); + } + } + return; + } + } + + @override + Widget _buildChrome(BuildContext context, Widget child) { + final ScrollableDetails details = ScrollableDetails( + direction: widget.axisDirection, + controller: _effectiveScrollController, + clipBehavior: widget.clipBehavior, + ); + // Skip building a scrollbar here, the dual scrollbar is added in + // TwoDimensionalScrollableState. + return _configuration.buildOverscrollIndicator(context, child, details); + } +} + +// Horizontal inner scrollable of 2D scrolling +class _HorizontalInnerDimension extends Scrollable { + const _HorizontalInnerDimension({ + super.key, + required super.viewportBuilder, + required super.axisDirection, + super.controller, + super.physics, + super.clipBehavior, + super.incrementCalculator, + super.excludeFromSemantics, + super.dragStartBehavior, + super.restorationId, + this.diagonalDragBehavior = DiagonalDragBehavior.none, + }) : assert(axisDirection == AxisDirection.left || axisDirection == AxisDirection.right); + + final DiagonalDragBehavior diagonalDragBehavior; + + @override + _HorizontalInnerDimensionState createState() => _HorizontalInnerDimensionState(); +} + +class _HorizontalInnerDimensionState extends ScrollableState { + late ScrollableState verticalScrollable; + Axis? lockedAxis; + Offset? lastDragOffset; + + DiagonalDragBehavior get diagonalDragBehavior => (widget as _HorizontalInnerDimension).diagonalDragBehavior; + + @override + void didChangeDependencies() { + verticalScrollable = Scrollable.of(context); + assert(axisDirectionToAxis(verticalScrollable.axisDirection) == Axis.vertical); + super.didChangeDependencies(); + } + + void _evaluateLockedAxis(Offset offset) { + assert(lastDragOffset != null); + final Offset offsetDelta = lastDragOffset! - offset; + final double axisDifferential = offsetDelta.dx.abs() - offsetDelta.dy.abs(); + if (axisDifferential.abs() >= kTouchSlop) { + // We have single axis winner. + lockedAxis = axisDifferential > 0.0 ? Axis.horizontal : Axis.vertical; + } else { + lockedAxis = null; + } + } + + @override + void _handleDragDown(DragDownDetails details) { + switch (diagonalDragBehavior) { + case DiagonalDragBehavior.none: + break; + case DiagonalDragBehavior.weightedEvent: + case DiagonalDragBehavior.weightedContinuous: + case DiagonalDragBehavior.free: + // Initiate hold. If one or the other wins the gesture, cancel the + // opposite axis. + verticalScrollable._handleDragDown(details); + } + super._handleDragDown(details); + } + + @override + void _handleDragStart(DragStartDetails details) { + lastDragOffset = details.globalPosition; + switch (diagonalDragBehavior) { + case DiagonalDragBehavior.none: + break; + case DiagonalDragBehavior.weightedEvent: + case DiagonalDragBehavior.weightedContinuous: + // See if one axis wins the drag. + _evaluateLockedAxis(details.globalPosition); + switch (lockedAxis) { + case null: + // Prepare to scroll diagonally + verticalScrollable._handleDragStart(details); + case Axis.horizontal: + // Prepare to scroll horizontally. + super._handleDragStart(details); + return; + case Axis.vertical: + // Prepare to scroll vertically. + verticalScrollable._handleDragStart(details); + return; + } + case DiagonalDragBehavior.free: + verticalScrollable._handleDragStart(details); + } + super._handleDragStart(details); + } + + @override + void _handleDragUpdate(DragUpdateDetails details) { + final DragUpdateDetails verticalDragDetails = DragUpdateDetails( + sourceTimeStamp: details.sourceTimeStamp, + delta: Offset(0.0, details.delta.dy), + primaryDelta: details.delta.dy, + globalPosition: details.globalPosition, + localPosition: details.localPosition, + ); + + final DragUpdateDetails horizontalDragDetails = DragUpdateDetails( + sourceTimeStamp: details.sourceTimeStamp, + delta: Offset(details.delta.dx, 0.0), + primaryDelta: details.delta.dx, + globalPosition: details.globalPosition, + localPosition: details.localPosition, + ); + switch (diagonalDragBehavior) { + case DiagonalDragBehavior.none: + // Default gesture handling from super class. + super._handleDragUpdate(horizontalDragDetails); + return; + case DiagonalDragBehavior.free: + // Scroll both axes + verticalScrollable._handleDragUpdate(verticalDragDetails); + super._handleDragUpdate(horizontalDragDetails); + return; + case DiagonalDragBehavior.weightedContinuous: + // Re-evaluate locked axis for every update. + _evaluateLockedAxis(details.globalPosition); + lastDragOffset = details.globalPosition; + case DiagonalDragBehavior.weightedEvent: + // Lock axis only once per gesture. + if (lockedAxis == null && lastDragOffset != null) { + // A winner has not been declared yet. + // See if one axis has won the drag. + _evaluateLockedAxis(details.globalPosition); + } + } + switch (lockedAxis) { + case null: + // Scroll diagonally + verticalScrollable._handleDragUpdate(verticalDragDetails); + super._handleDragUpdate(horizontalDragDetails); + case Axis.horizontal: + // Scroll horizontally + super._handleDragUpdate(horizontalDragDetails); + return; + case Axis.vertical: + // Scroll vertically + verticalScrollable._handleDragUpdate(verticalDragDetails); + return; + } + } + + @override + void _handleDragEnd(DragEndDetails details) { + lastDragOffset = null; + lockedAxis = null; + final double dx = details.velocity.pixelsPerSecond.dx; + final double dy = details.velocity.pixelsPerSecond.dy; + final DragEndDetails verticalDragDetails = DragEndDetails( + velocity: Velocity(pixelsPerSecond: Offset(0.0, dy)), + primaryVelocity: details.velocity.pixelsPerSecond.dy, + ); + final DragEndDetails horizontalDragDetails = DragEndDetails( + velocity: Velocity(pixelsPerSecond: Offset(dx, 0.0)), + primaryVelocity: details.velocity.pixelsPerSecond.dx, + ); + switch (diagonalDragBehavior) { + case DiagonalDragBehavior.none: + break; + case DiagonalDragBehavior.weightedEvent: + case DiagonalDragBehavior.weightedContinuous: + case DiagonalDragBehavior.free: + verticalScrollable._handleDragEnd(verticalDragDetails); + } + super._handleDragEnd(horizontalDragDetails); + } + + @override + void _handleDragCancel() { + lastDragOffset = null; + lockedAxis = null; + switch (diagonalDragBehavior) { + case DiagonalDragBehavior.none: + break; + case DiagonalDragBehavior.weightedEvent: + case DiagonalDragBehavior.weightedContinuous: + case DiagonalDragBehavior.free: + verticalScrollable._handleDragCancel(); + } + super._handleDragCancel(); + } + + @override + void setCanDrag(bool value) { + switch (diagonalDragBehavior) { + case DiagonalDragBehavior.none: + // If we aren't scrolling diagonally, the default drag gesture recognizer + // is used. + super.setCanDrag(value); + return; + case DiagonalDragBehavior.weightedEvent: + case DiagonalDragBehavior.weightedContinuous: + case DiagonalDragBehavior.free: + if (value) { + // Replaces the typical vertical/horizontal drag gesture recognizers + // with a pan gesture recognizer to allow bidirectional scrolling. + // Based on the diagonalDragBehavior, valid horizontal deltas are + // applied to this scrollable, while vertical deltas are routed to + // the vertical scrollable. + _gestureRecognizers = <Type, GestureRecognizerFactory>{ + PanGestureRecognizer: GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>( + () => PanGestureRecognizer(supportedDevices: _configuration.dragDevices), + (PanGestureRecognizer instance) { + instance + ..onDown = _handleDragDown + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..onCancel = _handleDragCancel + ..minFlingDistance = _physics?.minFlingDistance + ..minFlingVelocity = _physics?.minFlingVelocity + ..maxFlingVelocity = _physics?.maxFlingVelocity + ..velocityTrackerBuilder = _configuration.velocityTrackerBuilder(context) + ..dragStartBehavior = widget.dragStartBehavior + ..gestureSettings = _mediaQueryGestureSettings; + }, + ), + }; + // Cancel the active hold/drag (if any) because the gesture recognizers + // will soon be disposed by our RawGestureDetector, and we won't be + // receiving pointer up events to cancel the hold/drag. + _handleDragCancel(); + _lastCanDrag = value; + _lastAxisDirection = widget.axis; + if (_gestureDetectorKey.currentState != null) { + _gestureDetectorKey.currentState!.replaceGestureRecognizers(_gestureRecognizers); + } + } + return; + } + } + + @override + Widget _buildChrome(BuildContext context, Widget child) { + final ScrollableDetails details = ScrollableDetails( + direction: widget.axisDirection, + controller: _effectiveScrollController, + clipBehavior: widget.clipBehavior, + ); + // Skip building a scrollbar here, the dual scrollbar is added in + // TwoDimensionalScrollableState. + return _configuration.buildOverscrollIndicator(context, child, details); + } +} diff --git a/packages/flutter/lib/src/widgets/scrollable_helpers.dart b/packages/flutter/lib/src/widgets/scrollable_helpers.dart index e82781bb8d912..20466f96ef84c 100644 --- a/packages/flutter/lib/src/widgets/scrollable_helpers.dart +++ b/packages/flutter/lib/src/widgets/scrollable_helpers.dart @@ -10,7 +10,6 @@ import 'package:flutter/rendering.dart'; import 'actions.dart'; import 'basic.dart'; -import 'focus_manager.dart'; import 'framework.dart'; import 'primary_scroll_controller.dart'; import 'scroll_configuration.dart'; @@ -22,10 +21,8 @@ import 'scrollable.dart'; export 'package:flutter/physics.dart' show Tolerance; /// Describes the aspects of a Scrollable widget to inform inherited widgets -/// like [ScrollBehavior] for decorating. -// TODO(Piinks): Fix doc with 2DScrollable change. -// or enumerate the properties of combined -// Scrollables, such as [TwoDimensionalScrollable]. +/// like [ScrollBehavior] for decorating or enumerate the properties of combined +/// Scrollables, such as [TwoDimensionalScrollable]. /// /// Decorations like [GlowingOverscrollIndicator]s and [Scrollbar]s require /// information about the Scrollable in order to be initialized. @@ -160,11 +157,8 @@ class EdgeDraggingAutoScroller { EdgeDraggingAutoScroller( this.scrollable, { this.onScrollViewScrolled, - double? velocityScalar, - }): velocityScalar = velocityScalar ?? _kDefaultAutoScrollVelocityScalar; - - // An eyeballed value for a smooth scrolling experience. - static const double _kDefaultAutoScrollVelocityScalar = 7; + required this.velocityScalar, + }); /// The [Scrollable] this auto scroller is scrolling. final ScrollableState scrollable; @@ -181,8 +175,6 @@ class EdgeDraggingAutoScroller { /// /// It represents how the velocity scale with the over scroll distance. The /// auto-scroll velocity = <distance of overscroll> * velocityScalar. - /// - /// Defaults to 7 if not set or set to null. /// {@endtemplate} final double velocityScalar; @@ -384,10 +376,10 @@ class ScrollIntent extends Intent { final ScrollIncrementType type; } -/// An [Action] that scrolls the [Scrollable] that encloses the current -/// [primaryFocus] by the amount configured in the [ScrollIntent] given to it. +/// An [Action] that scrolls the relevant [Scrollable] by the amount configured +/// in the [ScrollIntent] given to it. /// -/// If a Scrollable cannot be found above the current [primaryFocus], the +/// If a Scrollable cannot be found above the given [BuildContext], the /// [PrimaryScrollController] will be considered for default handling of /// [ScrollAction]s. /// @@ -395,21 +387,17 @@ class ScrollIntent extends Intent { /// for a [ScrollIntent.type] set to [ScrollIncrementType.page] is 80% of the /// size of the scroll window, and for [ScrollIncrementType.line], 50 logical /// pixels. -class ScrollAction extends Action<ScrollIntent> { +class ScrollAction extends ContextAction<ScrollIntent> { @override - bool isEnabled(ScrollIntent intent) { - final FocusNode? focus = primaryFocus; - final bool contextIsValid = focus != null && focus.context != null; - if (contextIsValid) { - // Check for primary scrollable within the current context - if (Scrollable.maybeOf(focus.context!) != null) { - return true; - } - // Check for fallback scrollable with context from PrimaryScrollController - final ScrollController? primaryScrollController = PrimaryScrollController.maybeOf(focus.context!); - return primaryScrollController != null && primaryScrollController.hasClients; + bool isEnabled(ScrollIntent intent, [BuildContext? context]) { + if (context == null) { + return false; + } + if (Scrollable.maybeOf(context) != null) { + return true; } - return false; + final ScrollController? primaryScrollController = PrimaryScrollController.maybeOf(context); + return (primaryScrollController != null) && (primaryScrollController.hasClients); } /// Returns the scroll increment for a single scroll request, for use when @@ -487,10 +475,11 @@ class ScrollAction extends Action<ScrollIntent> { } @override - void invoke(ScrollIntent intent) { - ScrollableState? state = Scrollable.maybeOf(primaryFocus!.context!); + void invoke(ScrollIntent intent, [BuildContext? context]) { + assert(context != null, 'Cannot scroll without a context.'); + ScrollableState? state = Scrollable.maybeOf(context!); if (state == null) { - final ScrollController primaryScrollController = PrimaryScrollController.of(primaryFocus!.context!); + final ScrollController primaryScrollController = PrimaryScrollController.of(context); assert (() { if (primaryScrollController.positions.length != 1) { throw FlutterError.fromParts(<DiagnosticsNode>[ diff --git a/packages/flutter/lib/src/widgets/scrollbar.dart b/packages/flutter/lib/src/widgets/scrollbar.dart index cf5e3697b160e..bfdc0307fcdf9 100644 --- a/packages/flutter/lib/src/widgets/scrollbar.dart +++ b/packages/flutter/lib/src/widgets/scrollbar.dart @@ -984,19 +984,9 @@ class RawScrollbar extends StatefulWidget { this.mainAxisMargin = 0.0, this.crossAxisMargin = 0.0, this.padding, - @Deprecated( - 'Use thumbVisibility instead. ' - 'This feature was deprecated after v2.9.0-1.0.pre.', - ) - this.isAlwaysShown, }) : assert( - thumbVisibility == null || isAlwaysShown == null, - 'Scrollbar thumb appearance should only be controlled with thumbVisibility, ' - 'isAlwaysShown is deprecated.' - ), - assert( - !((thumbVisibility == false || isAlwaysShown == false) && (trackVisibility ?? false)), - 'A scrollbar track cannot be drawn without a scrollbar thumb.' + !(thumbVisibility == false && (trackVisibility ?? false)), + 'A scrollbar track cannot be drawn without a scrollbar thumb.', ), assert(minThumbLength >= 0), assert(minOverscrollLength == null || minOverscrollLength <= minThumbLength), @@ -1149,100 +1139,12 @@ class RawScrollbar extends StatefulWidget { /// scroll view associated with the parent [PrimaryScrollController]. /// * [PrimaryScrollController], which associates a [ScrollController] with /// a subtree. - /// - /// Replaces deprecated [isAlwaysShown]. /// {@endtemplate} /// /// Subclass [Scrollbar] can hide and show the scrollbar thumb in response to /// [MaterialState]s by using [ScrollbarThemeData.thumbVisibility]. final bool? thumbVisibility; - /// {@template flutter.widgets.Scrollbar.isAlwaysShown} - /// Indicates that the scrollbar thumb should be visible, even when a scroll - /// is not underway. - /// - /// When false, the scrollbar will be shown during scrolling - /// and will fade out otherwise. - /// - /// When true, the scrollbar will always be visible and never fade out. This - /// requires that the Scrollbar can access the [ScrollController] of the - /// associated Scrollable widget. This can either be the provided [controller], - /// or the [PrimaryScrollController] of the current context. - /// - /// * When providing a controller, the same ScrollController must also be - /// provided to the associated Scrollable widget. - /// * The [PrimaryScrollController] is used by default for a [ScrollView] - /// that has not been provided a [ScrollController] and that has a - /// [ScrollView.scrollDirection] of [Axis.vertical]. This automatic - /// behavior does not apply to those with Axis.horizontal. To explicitly - /// use the PrimaryScrollController, set [ScrollView.primary] to true. - /// - /// Defaults to false when null. - /// - /// {@tool snippet} - /// - /// ```dart - /// // (e.g. in a stateful widget) - /// - /// final ScrollController controllerOne = ScrollController(); - /// final ScrollController controllerTwo = ScrollController(); - /// - /// @override - /// Widget build(BuildContext context) { - /// return Column( - /// children: <Widget>[ - /// SizedBox( - /// height: 200, - /// child: Scrollbar( - /// thumbVisibility: true, - /// controller: controllerOne, - /// child: ListView.builder( - /// controller: controllerOne, - /// itemCount: 120, - /// itemBuilder: (BuildContext context, int index) { - /// return Text('item $index'); - /// }, - /// ), - /// ), - /// ), - /// SizedBox( - /// height: 200, - /// child: CupertinoScrollbar( - /// thumbVisibility: true, - /// controller: controllerTwo, - /// child: SingleChildScrollView( - /// controller: controllerTwo, - /// child: const SizedBox( - /// height: 2000, - /// width: 500, - /// child: Placeholder(), - /// ), - /// ), - /// ), - /// ), - /// ], - /// ); - /// } - /// ``` - /// {@end-tool} - /// - /// See also: - /// - /// * [RawScrollbarState.showScrollbar], an overridable getter which uses - /// this value to override the default behavior. - /// * [ScrollView.primary], which indicates whether the ScrollView is the primary - /// scroll view associated with the parent [PrimaryScrollController]. - /// * [PrimaryScrollController], which associates a [ScrollController] with - /// a subtree. - /// - /// This is deprecated, [thumbVisibility] should be used instead. - /// {@endtemplate} - @Deprecated( - 'Use thumbVisibility instead. ' - 'This feature was deprecated after v2.9.0-1.0.pre.', - ) - final bool? isAlwaysShown; - /// The [OutlinedBorder] of the scrollbar's thumb. /// /// Only one of [radius] and [shape] may be specified. For a rounded rectangle, @@ -1453,14 +1355,9 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv /// Subclasses can override this getter to make its value depend on an inherited /// theme. /// - /// Defaults to false when [RawScrollbar.isAlwaysShown] or - /// [RawScrollbar.thumbVisibility] is null. - /// - /// See also: - /// - /// * [RawScrollbar.isAlwaysShown], which overrides the default behavior. + /// Defaults to false when [RawScrollbar.thumbVisibility] is null. @protected - bool get showScrollbar => widget.isAlwaysShown ?? widget.thumbVisibility ?? false; + bool get showScrollbar => widget.thumbVisibility ?? false; bool get _showTrack => showScrollbar && (widget.trackVisibility ?? false); @@ -1546,9 +1443,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv : 'provided ScrollController'; String when = ''; - if (widget.isAlwaysShown ?? false) { - when = 'Scrollbar.isAlwaysShown is true'; - } else if (widget.thumbVisibility ?? false) { + if (widget.thumbVisibility ?? false) { when = 'Scrollbar.thumbVisibility is true'; } else if (enableGestures) { when = 'the scrollbar is interactive'; @@ -1658,9 +1553,8 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv @override void didUpdateWidget(T oldWidget) { super.didUpdateWidget(oldWidget); - if (widget.isAlwaysShown != oldWidget.isAlwaysShown - || widget.thumbVisibility != oldWidget.thumbVisibility) { - if ((widget.isAlwaysShown ?? false) || (widget.thumbVisibility ?? false)) { + if (widget.thumbVisibility != oldWidget.thumbVisibility) { + if (widget.thumbVisibility ?? false) { assert(_debugScheduleCheckHasValidScrollPosition()); _fadeoutTimer?.cancel(); _fadeoutAnimationController.animateTo(1.0); diff --git a/packages/flutter/lib/src/widgets/selectable_region.dart b/packages/flutter/lib/src/widgets/selectable_region.dart index 5aea500ec09ac..1786b00ff7af6 100644 --- a/packages/flutter/lib/src/widgets/selectable_region.dart +++ b/packages/flutter/lib/src/widgets/selectable_region.dart @@ -263,7 +263,7 @@ class SelectableRegion extends StatefulWidget { required final VoidCallback onCopy, required final VoidCallback onSelectAll, }) { - final bool canCopy = selectionGeometry.hasSelection; + final bool canCopy = selectionGeometry.status == SelectionStatus.uncollapsed; final bool canSelectAll = selectionGeometry.hasContent; // Determine which buttons will appear so that the order and total number is @@ -439,8 +439,7 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe instance ..onLongPressStart = _handleTouchLongPressStart ..onLongPressMoveUpdate = _handleTouchLongPressMoveUpdate - ..onLongPressEnd = _handleTouchLongPressEnd - ..onLongPressCancel = _clearSelection; + ..onLongPressEnd = _handleTouchLongPressEnd; }, ); } @@ -489,12 +488,62 @@ class SelectableRegionState extends State<SelectableRegion> with TextSelectionDe _updateSelectedContentIfNeeded(); } + bool _positionIsOnActiveSelection({required Offset globalPosition}) { + for (final Rect selectionRect in _selectionDelegate.value.selectionRects) { + final Matrix4 transform = _selectable!.getTransformTo(null); + final Rect globalRect = MatrixUtils.transformRect(transform, selectionRect); + if (globalRect.contains(globalPosition)) { + return true; + } + } + return false; + } + void _handleRightClickDown(TapDownDetails details) { + final Offset? previousSecondaryTapDownPosition = lastSecondaryTapDownPosition; + final bool toolbarIsVisible = _selectionOverlay?.toolbarIsVisible ?? false; lastSecondaryTapDownPosition = details.globalPosition; widget.focusNode.requestFocus(); - _selectWordAt(offset: details.globalPosition); - _showHandles(); - _showToolbar(location: details.globalPosition); + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.windows: + // If lastSecondaryTapDownPosition is within the current selection then + // keep the current selection, if not then collapse it. + final bool lastSecondaryTapDownPositionWasOnActiveSelection = _positionIsOnActiveSelection(globalPosition: details.globalPosition); + if (!lastSecondaryTapDownPositionWasOnActiveSelection) { + _selectStartTo(offset: lastSecondaryTapDownPosition!); + _selectEndTo(offset: lastSecondaryTapDownPosition!); + } + _showHandles(); + _showToolbar(location: lastSecondaryTapDownPosition); + case TargetPlatform.iOS: + _selectWordAt(offset: lastSecondaryTapDownPosition!); + _showHandles(); + _showToolbar(location: lastSecondaryTapDownPosition); + case TargetPlatform.macOS: + if (previousSecondaryTapDownPosition == lastSecondaryTapDownPosition && toolbarIsVisible) { + hideToolbar(); + return; + } + _selectWordAt(offset: lastSecondaryTapDownPosition!); + _showHandles(); + _showToolbar(location: lastSecondaryTapDownPosition); + case TargetPlatform.linux: + if (toolbarIsVisible) { + hideToolbar(); + return; + } + // If lastSecondaryTapDownPosition is within the current selection then + // keep the current selection, if not then collapse it. + final bool lastSecondaryTapDownPositionWasOnActiveSelection = _positionIsOnActiveSelection(globalPosition: details.globalPosition); + if (!lastSecondaryTapDownPositionWasOnActiveSelection) { + _selectStartTo(offset: lastSecondaryTapDownPosition!); + _selectEndTo(offset: lastSecondaryTapDownPosition!); + } + _showHandles(); + _showToolbar(location: lastSecondaryTapDownPosition); + } _updateSelectedContentIfNeeded(); } @@ -1669,12 +1718,21 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai /// /// Returns positive if a is lower, negative if a is higher. static int _compareHorizontally(Rect a, Rect b) { + // a encloses b. if (a.left - b.left < precisionErrorTolerance && a.right - b.right > - precisionErrorTolerance) { - // a encloses b. + // b ends before a. + if (a.right - b.right > precisionErrorTolerance) { + return 1; + } return -1; } + + // b encloses a. if (b.left - a.left < precisionErrorTolerance && b.right - a.right > - precisionErrorTolerance) { - // b encloses a. + // a ends before b. + if (b.right - a.right > precisionErrorTolerance) { + return -1; + } return 1; } if ((a.left - b.left).abs() > precisionErrorTolerance) { @@ -1761,9 +1819,30 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai } } + // Need to collect selection rects from selectables ranging from the + // currentSelectionStartIndex to the currentSelectionEndIndex. + final List<Rect> selectionRects = <Rect>[]; + final Rect? drawableArea = hasSize ? Rect + .fromLTWH(0, 0, containerSize.width, containerSize.height) : null; + for (int index = currentSelectionStartIndex; index <= currentSelectionEndIndex; index++) { + final List<Rect> currSelectableSelectionRects = selectables[index].value.selectionRects; + final List<Rect> selectionRectsWithinDrawableArea = currSelectableSelectionRects.map((Rect selectionRect) { + final Matrix4 transform = getTransformFrom(selectables[index]); + final Rect localRect = MatrixUtils.transformRect(transform, selectionRect); + if (drawableArea != null) { + return drawableArea.intersect(localRect); + } + return localRect; + }).where((Rect selectionRect) { + return selectionRect.isFinite && !selectionRect.isEmpty; + }).toList(); + selectionRects.addAll(selectionRectsWithinDrawableArea); + } + return SelectionGeometry( startSelectionPoint: startPoint, endSelectionPoint: endPoint, + selectionRects: selectionRects, status: startGeometry != endGeometry ? SelectionStatus.uncollapsed : startGeometry.status, @@ -1888,13 +1967,23 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai /// [SelectWordSelectionEvent.globalPosition]. @protected SelectionResult handleSelectWord(SelectWordSelectionEvent event) { + SelectionResult? lastSelectionResult; for (int index = 0; index < selectables.length; index += 1) { final Rect localRect = Rect.fromLTWH(0, 0, selectables[index].size.width, selectables[index].size.height); final Matrix4 transform = selectables[index].getTransformTo(null); final Rect globalRect = MatrixUtils.transformRect(transform, localRect); if (globalRect.contains(event.globalPosition)) { final SelectionGeometry existingGeometry = selectables[index].value; - dispatchSelectionEventToChild(selectables[index], event); + lastSelectionResult = dispatchSelectionEventToChild(selectables[index], event); + if (lastSelectionResult == SelectionResult.next) { + continue; + } + if (index == 0 && lastSelectionResult == SelectionResult.previous) { + return SelectionResult.previous; + } + if (index == selectables.length - 1 && lastSelectionResult == SelectionResult.next) { + return SelectionResult.next; + } if (selectables[index].value != existingGeometry) { // Geometry has changed as a result of select word, need to clear the // selection of other selectables to keep selection in sync. @@ -1904,9 +1993,15 @@ abstract class MultiSelectableSelectionContainerDelegate extends SelectionContai currentSelectionStartIndex = currentSelectionEndIndex = index; } return SelectionResult.end; + } else { + if (lastSelectionResult == SelectionResult.next) { + currentSelectionStartIndex = currentSelectionEndIndex = index - 1; + return SelectionResult.end; + } } } - return SelectionResult.none; + assert(lastSelectionResult == null); + return SelectionResult.end; } /// Removes the selection of all selectables this delegate manages. diff --git a/packages/flutter/lib/src/widgets/shortcuts.dart b/packages/flutter/lib/src/widgets/shortcuts.dart index 6ec98f304988b..5bc3879de8dd2 100644 --- a/packages/flutter/lib/src/widgets/shortcuts.dart +++ b/packages/flutter/lib/src/widgets/shortcuts.dart @@ -843,9 +843,13 @@ class ShortcutManager with Diagnosticable, ChangeNotifier { primaryContext, intent: matchedIntent, ); - if (action != null && action.isEnabled(matchedIntent)) { - final Object? invokeResult = Actions.of(primaryContext).invokeAction(action, matchedIntent, primaryContext); - return action.toKeyEventResult(matchedIntent, invokeResult); + if (action != null) { + final (bool enabled, Object? invokeResult) = Actions.of(primaryContext).invokeActionIfEnabled( + action, matchedIntent, primaryContext, + ); + if (enabled) { + return action.toKeyEventResult(matchedIntent, invokeResult); + } } } } @@ -1047,6 +1051,8 @@ class _ShortcutsState extends State<Shortcuts> { /// A widget that binds key combinations to specific callbacks. /// +/// {@youtube 560 315 https://www.youtube.com/watch?v=VcQQ1ns_qNY} +/// /// This is similar to but simpler than the [Shortcuts] widget as it doesn't /// require [Intent]s and [Actions] widgets. Instead, it accepts a map /// of [ShortcutActivator]s to [VoidCallback]s. diff --git a/packages/flutter/lib/src/widgets/sliver.dart b/packages/flutter/lib/src/widgets/sliver.dart index 28d60ced820d8..0090198cb3893 100644 --- a/packages/flutter/lib/src/widgets/sliver.dart +++ b/packages/flutter/lib/src/widgets/sliver.dart @@ -1342,7 +1342,7 @@ class KeepAlive extends ParentDataWidget<KeepAliveParentDataMixin> { if (parentData.keepAlive != keepAlive) { // No need to redo layout if it became true. parentData.keepAlive = keepAlive; - final AbstractNode? targetParent = renderObject.parent; + final RenderObject? targetParent = renderObject.parent; if (targetParent is RenderObject && !keepAlive) { targetParent.markNeedsLayout(); } @@ -1436,7 +1436,7 @@ class _SliverZeroFlexParentDataWidget extends ParentDataWidget<SliverPhysicalPar } if (needsLayout) { - final AbstractNode? targetParent = renderObject.parent; + final RenderObject? targetParent = renderObject.parent; if (targetParent is RenderObject) { targetParent.markNeedsLayout(); } @@ -1508,7 +1508,7 @@ class SliverCrossAxisExpanded extends ParentDataWidget<SliverPhysicalContainerPa } if (needsLayout) { - final AbstractNode? targetParent = renderObject.parent; + final RenderObject? targetParent = renderObject.parent; if (targetParent is RenderObject) { targetParent.markNeedsLayout(); } @@ -1561,6 +1561,8 @@ class SliverCrossAxisExpanded extends ParentDataWidget<SliverPhysicalContainerPa /// value to a widget. /// * [SliverConstrainedCrossAxis], which is a [RenderObjectWidget] for setting /// an extent to constrain the widget to. +/// * [SliverMainAxisGroup], which is the [RenderObjectWidget] for laying out +/// multiple slivers along the main axis. class SliverCrossAxisGroup extends MultiChildRenderObjectWidget { /// Creates a sliver that places sliver children in a linear array along /// the cross axis. @@ -1574,3 +1576,61 @@ class SliverCrossAxisGroup extends MultiChildRenderObjectWidget { return RenderSliverCrossAxisGroup(); } } + +/// A sliver that places multiple sliver children in a linear array along +/// the main axis, one after another. +/// +/// ## Layout algorithm +/// +/// _This section describes how the framework causes [RenderSliverMainAxisGroup] +/// to position its children._ +/// +/// Layout for a [RenderSliverMainAxisGroup] has four steps: +/// +/// 1. Keep track of an offset variable which is the total [SliverGeometry.scrollExtent] +/// of the slivers laid out so far. +/// 2. To determine the constraints for the next sliver child to layout, calculate the +/// amount of paint extent occupied from 0.0 to the offset variable and subtract this from +/// [SliverConstraints.remainingPaintExtent] minus to use as the child's +/// [SliverConstraints.remainingPaintExtent]. For the [SliverConstraints.scrollOffset], +/// take the provided constraint's value and subtract out the offset variable, using +/// 0.0 if negative. +/// 3. Once we finish laying out all the slivers, this offset variable represents +/// the total [SliverGeometry.scrollExtent] of the sliver group. Since it is possible +/// for specialized slivers to try to paint itself outside of the bounds of the +/// sliver group's scroll extent (see [SliverPersistentHeader]), we must do a +/// second pass to set a [SliverPhysicalParentData.paintOffset] to make sure it +/// is within the bounds of the sliver group. +/// 4. Finally, set the [RenderSliverMainAxisGroup.geometry] with the total +/// [SliverGeometry.scrollExtent], [SliverGeometry.paintExtent] calculated from +/// the constraints and [SliverGeometry.scrollExtent], and [SliverGeometry.maxPaintExtent]. +/// +/// {@tool dartpad} +/// In this sample the [CustomScrollView] renders a [SliverMainAxisGroup] and a +/// [SliverToBoxAdapter] with some content. The [SliverMainAxisGroup] renders a +/// [SliverAppBar], [SliverList], and [SliverToBoxAdapter]. Notice that when the +/// [SliverMainAxisGroup] goes out of view, so does the pinned [SliverAppBar]. +/// +/// ** See code in examples/api/lib/widgets/sliver/sliver_main_axis_group.0.dart ** +/// {@end-tool} +/// +/// See also: +/// +/// * [SliverPersistentHeader], which is a [RenderObjectWidget] which may require +/// adjustment to its [SliverPhysicalParentData.paintOffset] to make it fit +/// within the computed [SliverGeometry.scrollExtent] of the [SliverMainAxisGroup]. +/// * [SliverCrossAxisGroup], which is the [RenderObjectWidget] for laying out +/// multiple slivers along the cross axis. +class SliverMainAxisGroup extends MultiChildRenderObjectWidget { + /// Creates a sliver that places sliver children in a linear array along + /// the main axis. + const SliverMainAxisGroup({ + super.key, + required List<Widget> slivers, + }) : super(children: slivers); + + @override + RenderSliverMainAxisGroup createRenderObject(BuildContext context) { + return RenderSliverMainAxisGroup(); + } +} diff --git a/packages/flutter/lib/src/widgets/table.dart b/packages/flutter/lib/src/widgets/table.dart index 83d54be54f84a..a3aba1c95fe25 100644 --- a/packages/flutter/lib/src/widgets/table.dart +++ b/packages/flutter/lib/src/widgets/table.dart @@ -417,7 +417,7 @@ class TableCell extends ParentDataWidget<TableCellParentData> { final TableCellParentData parentData = renderObject.parentData! as TableCellParentData; if (parentData.verticalAlignment != verticalAlignment) { parentData.verticalAlignment = verticalAlignment; - final AbstractNode? targetParent = renderObject.parent; + final RenderObject? targetParent = renderObject.parent; if (targetParent is RenderObject) { targetParent.markNeedsLayout(); } diff --git a/packages/flutter/lib/src/widgets/tap_and_drag_gestures.dart b/packages/flutter/lib/src/widgets/tap_and_drag_gestures.dart index 4bf60f95efb10..51a8435b2f759 100644 --- a/packages/flutter/lib/src/widgets/tap_and_drag_gestures.dart +++ b/packages/flutter/lib/src/widgets/tap_and_drag_gestures.dart @@ -539,6 +539,9 @@ mixin _TapStatusTrackerMixin on OneSequenceGestureRecognizer { @override void addAllowedPointer(PointerDownEvent event) { super.addAllowedPointer(event); + if (_consecutiveTapTimer != null && !_consecutiveTapTimer!.isActive) { + _tapTrackerReset(); + } if (maxConsecutiveTap == _consecutiveTapCount) { _tapTrackerReset(); } @@ -623,7 +626,7 @@ mixin _TapStatusTrackerMixin on OneSequenceGestureRecognizer { } void _consecutiveTapTimerStart() { - _consecutiveTapTimer ??= Timer(kDoubleTapTimeout, _tapTrackerReset); + _consecutiveTapTimer ??= Timer(kDoubleTapTimeout, _consecutiveTapTimerTimeout); } void _consecutiveTapTimerStop() { @@ -633,6 +636,13 @@ mixin _TapStatusTrackerMixin on OneSequenceGestureRecognizer { } } + void _consecutiveTapTimerTimeout() { + // The consecutive tap timer may time out before a tap down/tap up event is + // fired. In this case we should not reset the tap tracker state immediately. + // Instead we should reset the tap tracker on the next call to [addAllowedPointer], + // if the timer is no longer active. + } + 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 diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index d27b282a37a59..34e3279551925 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -562,15 +562,13 @@ class TextSelectionOverlay { /// Whether the handles are currently visible. bool get handlesAreVisible => _selectionOverlay._handles != null && handlesVisible; - /// Whether the toolbar is currently visible. - /// - /// Includes both the text selection toolbar and the spell check menu. + /// {@macro flutter.widgets.SelectionOverlay.toolbarIsVisible} /// /// See also: /// /// * [spellCheckToolbarIsVisible], which is only whether the spell check menu /// specifically is visible. - bool get toolbarIsVisible => _selectionOverlay._toolbarIsVisible; + bool get toolbarIsVisible => _selectionOverlay.toolbarIsVisible; /// Whether the magnifier is currently visible. bool get magnifierIsVisible => _selectionOverlay._magnifierController.shown; @@ -984,7 +982,12 @@ class SelectionOverlay { /// {@macro flutter.widgets.magnifier.TextMagnifierConfiguration.details} final TextMagnifierConfiguration magnifierConfiguration; - bool get _toolbarIsVisible { + /// {@template flutter.widgets.SelectionOverlay.toolbarIsVisible} + /// Whether the toolbar is currently visible. + /// + /// Includes both the text selection toolbar and the spell check menu. + /// {@endtemplate} + bool get toolbarIsVisible { return selectionControls is TextSelectionHandleControls ? _contextMenuController.isShown || _spellCheckToolbarController.isShown : _toolbar != null || _spellCheckToolbarController.isShown; @@ -1001,7 +1004,7 @@ class SelectionOverlay { /// [MagnifierController.shown]. /// {@endtemplate} void showMagnifier(MagnifierInfo initialMagnifierInfo) { - if (_toolbarIsVisible) { + if (toolbarIsVisible) { hideToolbar(); } @@ -1088,10 +1091,26 @@ class SelectionOverlay { void _handleStartHandleDragStart(DragStartDetails details) { assert(!_isDraggingStartHandle); + // Calling OverlayEntry.remove may not happen until the following frame, so + // it's possible for the handles to receive a gesture after calling remove. + if (_handles == null) { + _isDraggingStartHandle = false; + return; + } _isDraggingStartHandle = details.kind == PointerDeviceKind.touch; onStartHandleDragStart?.call(details); } + void _handleStartHandleDragUpdate(DragUpdateDetails details) { + // Calling OverlayEntry.remove may not happen until the following frame, so + // it's possible for the handles to receive a gesture after calling remove. + if (_handles == null) { + _isDraggingStartHandle = false; + return; + } + onStartHandleDragUpdate?.call(details); + } + /// Called when the users drag the start selection handles to new locations. final ValueChanged<DragUpdateDetails>? onStartHandleDragUpdate; @@ -1101,6 +1120,11 @@ class SelectionOverlay { void _handleStartHandleDragEnd(DragEndDetails details) { _isDraggingStartHandle = false; + // Calling OverlayEntry.remove may not happen until the following frame, so + // it's possible for the handles to receive a gesture after calling remove. + if (_handles == null) { + return; + } onStartHandleDragEnd?.call(details); } @@ -1147,10 +1171,26 @@ class SelectionOverlay { void _handleEndHandleDragStart(DragStartDetails details) { assert(!_isDraggingEndHandle); + // Calling OverlayEntry.remove may not happen until the following frame, so + // it's possible for the handles to receive a gesture after calling remove. + if (_handles == null) { + _isDraggingEndHandle = false; + return; + } _isDraggingEndHandle = details.kind == PointerDeviceKind.touch; onEndHandleDragStart?.call(details); } + void _handleEndHandleDragUpdate(DragUpdateDetails details) { + // Calling OverlayEntry.remove may not happen until the following frame, so + // it's possible for the handles to receive a gesture after calling remove. + if (_handles == null) { + _isDraggingEndHandle = false; + return; + } + onEndHandleDragUpdate?.call(details); + } + /// Called when the users drag the end selection handles to new locations. final ValueChanged<DragUpdateDetails>? onEndHandleDragUpdate; @@ -1160,6 +1200,11 @@ class SelectionOverlay { void _handleEndHandleDragEnd(DragEndDetails details) { _isDraggingEndHandle = false; + // Calling OverlayEntry.remove may not happen until the following frame, so + // it's possible for the handles to receive a gesture after calling remove. + if (_handles == null) { + return; + } onEndHandleDragEnd?.call(details); } @@ -1472,7 +1517,7 @@ class SelectionOverlay { handleLayerLink: startHandleLayerLink, onSelectionHandleTapped: onSelectionHandleTapped, onSelectionHandleDragStart: _handleStartHandleDragStart, - onSelectionHandleDragUpdate: onStartHandleDragUpdate, + onSelectionHandleDragUpdate: _handleStartHandleDragUpdate, onSelectionHandleDragEnd: _handleStartHandleDragEnd, selectionControls: selectionControls, visibility: startHandlesVisible, @@ -1499,7 +1544,7 @@ class SelectionOverlay { handleLayerLink: endHandleLayerLink, onSelectionHandleTapped: onSelectionHandleTapped, onSelectionHandleDragStart: _handleEndHandleDragStart, - onSelectionHandleDragUpdate: onEndHandleDragUpdate, + onSelectionHandleDragUpdate: _handleEndHandleDragUpdate, onSelectionHandleDragEnd: _handleEndHandleDragEnd, selectionControls: selectionControls, visibility: endHandlesVisible, @@ -1752,7 +1797,7 @@ class _SelectionHandleOverlayState extends State<_SelectionHandleOverlay> with S // Make sure the GestureDetector is big enough to be easily interactive. final Rect interactiveRect = handleRect.expandToInclude( - Rect.fromCircle(center: handleRect.center, radius: kMinInteractiveDimension/ 2), + Rect.fromCircle(center: handleRect.center, radius: kMinInteractiveDimension / 2), ); final RelativeRect padding = RelativeRect.fromLTRB( math.max((interactiveRect.width - handleRect.width) / 2, 0), @@ -3080,11 +3125,6 @@ class _TextSelectionGestureDetectorState extends State<TextSelectionGestureDetec } } - @override - void dispose() { - super.dispose(); - } - // The down handler is force-run on success of a single tap and optimistically // run before a long press success. void _handleTapDown(TapDragDownDetails details) { @@ -3314,14 +3354,10 @@ class ClipboardStatusNotifier extends ValueNotifier<ClipboardStatus> with Widget update(); case AppLifecycleState.detached: case AppLifecycleState.inactive: + case AppLifecycleState.hidden: case AppLifecycleState.paused: // Nothing to do. break; - // ignore: no_default_cases - default: - // TODO(gspencergoog): Remove this and replace with real cases once - // engine change rolls into framework. - break; } } @@ -3347,6 +3383,115 @@ enum ClipboardStatus { notPasteable, } +/// A [ValueNotifier] whose [value] indicates whether the current device supports the Live Text +/// (OCR) function. +/// +/// See also: +/// * [LiveText], where the availability of Live Text input can be obtained. +/// * [LiveTextInputStatus], an enumeration that indicates whether the current device is available +/// for Live Text input. +/// +/// Call [update] to asynchronously update [value] if needed. +class LiveTextInputStatusNotifier extends ValueNotifier<LiveTextInputStatus> with WidgetsBindingObserver { + /// Create a new LiveTextStatusNotifier. + LiveTextInputStatusNotifier({ + LiveTextInputStatus value = LiveTextInputStatus.unknown, + }) : super(value); + + bool _disposed = false; + + /// Check the [LiveTextInputStatus] and update [value] if needed. + Future<void> update() async { + if (_disposed) { + return; + } + + final bool isLiveTextInputEnabled; + try { + isLiveTextInputEnabled = await LiveText.isLiveTextInputAvailable(); + } catch (exception, stack) { + FlutterError.reportError(FlutterErrorDetails( + exception: exception, + stack: stack, + library: 'widget library', + context: ErrorDescription('while checking the availability of Live Text input'), + )); + // In the case of an error from the Live Text API, set the value to + // unknown so that it will try to update again later. + if (_disposed || value == LiveTextInputStatus.unknown) { + return; + } + value = LiveTextInputStatus.unknown; + return; + } + + final LiveTextInputStatus nextStatus = isLiveTextInputEnabled + ? LiveTextInputStatus.enabled + : LiveTextInputStatus.disabled; + + if (_disposed || nextStatus == value) { + return; + } + value = nextStatus; + } + + @override + void addListener(VoidCallback listener) { + if (!hasListeners) { + WidgetsBinding.instance.addObserver(this); + } + if (value == LiveTextInputStatus.unknown) { + update(); + } + super.addListener(listener); + } + + @override + void removeListener(VoidCallback listener) { + super.removeListener(listener); + if (!_disposed && !hasListeners) { + WidgetsBinding.instance.removeObserver(this); + } + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + switch (state) { + case AppLifecycleState.resumed: + update(); + case AppLifecycleState.detached: + case AppLifecycleState.inactive: + case AppLifecycleState.paused: + case AppLifecycleState.hidden: + // Nothing to do. + } + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + _disposed = true; + super.dispose(); + } +} + +/// An enumeration that indicates whether the current device is available for Live Text input. +/// +/// See also: +/// * [LiveText], where the availability of Live Text input can be obtained. +enum LiveTextInputStatus { + /// This device supports Live Text input currently. + enabled, + + /// The status of the Live Text input is unknown. Since getting the Live Text input availability + /// is asynchronous (see [LiveText.isLiveTextInputAvailable]), this status often exists while + /// waiting to receive the status value for the first time. + unknown, + + /// The current device doesn't support Live Text input. + disabled, +} + // TODO(justinmc): Deprecate this after TextSelectionControls.buildToolbar is // deleted, when users should migrate back to TextSelectionControls.buildHandle. // See https://github.com/flutter/flutter/pull/124262 diff --git a/packages/flutter/lib/src/widgets/two_dimensional_scroll_view.dart b/packages/flutter/lib/src/widgets/two_dimensional_scroll_view.dart new file mode 100644 index 0000000000000..b5a38bec6c6e9 --- /dev/null +++ b/packages/flutter/lib/src/widgets/two_dimensional_scroll_view.dart @@ -0,0 +1,206 @@ +// 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/rendering.dart'; + +import 'focus_manager.dart'; +import 'focus_scope.dart'; +import 'framework.dart'; +import 'notification_listener.dart'; +import 'primary_scroll_controller.dart'; +import 'scroll_controller.dart'; +import 'scroll_delegate.dart'; +import 'scroll_notification.dart'; +import 'scroll_physics.dart'; +import 'scroll_view.dart'; +import 'scrollable.dart'; +import 'scrollable_helpers.dart'; +import 'two_dimensional_viewport.dart'; + +/// A widget that combines a [TwoDimensionalScrollable] and a +/// [TwoDimensionalViewport] to create an interactive scrolling pane of content +/// in both vertical and horizontal dimensions. +/// +/// A two-way scrollable widget consist of three pieces: +/// +/// 1. A [TwoDimensionalScrollable] widget, which listens for various user +/// gestures and implements the interaction design for scrolling. +/// 2. A [TwoDimensionalViewport] widget, which implements the visual design +/// for scrolling by displaying only a portion +/// of the widgets inside the scroll view. +/// 3. A [TwoDimensionalChildDelegate], which provides the children visible in +/// the scroll view. +/// +/// [TwoDimensionalScrollView] helps orchestrate these pieces by creating the +/// [TwoDimensionalScrollable] and deferring to its subclass to implement +/// [buildViewport], which builds a subclass of [TwoDimensionalViewport]. The +/// [TwoDimensionalChildDelegate] is provided by the [delegate] parameter. +/// +/// A [TwoDimensionalScrollView] has two different [ScrollPosition]s, one for +/// each [Axis]. This means that there are also two unique [ScrollController]s +/// for these positions. To provide a ScrollController to access the +/// ScrollPosition, use the [ScrollableDetails.controller] property of the +/// associated axis that is provided to this scroll view. +abstract class TwoDimensionalScrollView extends StatelessWidget { + /// Creates a widget that scrolls in both dimensions. + /// + /// The [primary] argument is associated with the [mainAxis]. The main axis + /// [ScrollableDetails.controller] must be null if [primary] is configured for + /// that axis. If [primary] is true, the nearest [PrimaryScrollController] + /// surrounding the widget is attached to the scroll position of that axis. + const TwoDimensionalScrollView({ + super.key, + this.primary, + this.mainAxis = Axis.vertical, + this.verticalDetails = const ScrollableDetails.vertical(), + this.horizontalDetails = const ScrollableDetails.horizontal(), + required this.delegate, + this.cacheExtent, + this.diagonalDragBehavior = DiagonalDragBehavior.none, + this.dragStartBehavior = DragStartBehavior.start, + this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, + this.clipBehavior = Clip.hardEdge, + }); + + /// A delegate that provides the children for the [TwoDimensionalScrollView]. + final TwoDimensionalChildDelegate delegate; + + /// {@macro flutter.rendering.RenderViewportBase.cacheExtent} + final double? cacheExtent; + + /// Whether scrolling gestures should lock to one axes, allow free movement + /// in both axes, or be evaluated on a weighted scale. + /// + /// Defaults to [DiagonalDragBehavior.none], locking axes to receive input one + /// at a time. + final DiagonalDragBehavior diagonalDragBehavior; + + /// {@macro flutter.widgets.scroll_view.primary} + final bool? primary; + + /// The main axis of the two. + /// + /// Used to determine how to apply [primary] when true. + /// + /// This value should also be provided to the subclass of + /// [TwoDimensionalViewport], where it is used to determine paint order of + /// children. + final Axis mainAxis; + + /// The configuration of the vertical Scrollable. + /// + /// These [ScrollableDetails] can be used to set the [AxisDirection], + /// [ScrollController], [ScrollPhysics] and more for the vertical axis. + final ScrollableDetails verticalDetails; + + /// The configuration of the horizontal Scrollable. + /// + /// These [ScrollableDetails] can be used to set the [AxisDirection], + /// [ScrollController], [ScrollPhysics] and more for the horizontal axis. + final ScrollableDetails horizontalDetails; + + /// {@macro flutter.widgets.scrollable.dragStartBehavior} + final DragStartBehavior dragStartBehavior; + + /// {@macro flutter.widgets.scroll_view.keyboardDismissBehavior} + final ScrollViewKeyboardDismissBehavior keyboardDismissBehavior; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + + /// Build the two dimensional viewport. + /// + /// Subclasses may override this method to change how the viewport is built, + /// likely a subclass of [TwoDimensionalViewport]. + /// + /// The `verticalOffset` and `horizontalOffset` arguments are the values + /// obtained from [TwoDimensionalScrollable.viewportBuilder]. + Widget buildViewport( + BuildContext context, + ViewportOffset verticalOffset, + ViewportOffset horizontalOffset, + ); + + @override + Widget build(BuildContext context) { + assert( + axisDirectionToAxis(verticalDetails.direction) == Axis.vertical, + 'TwoDimensionalScrollView.verticalDetails are not Axis.vertical.' + ); + assert( + axisDirectionToAxis(horizontalDetails.direction) == Axis.horizontal, + 'TwoDimensionalScrollView.horizontalDetails are not Axis.horizontal.' + ); + + ScrollableDetails mainAxisDetails = switch (mainAxis) { + Axis.vertical => verticalDetails, + Axis.horizontal => horizontalDetails, + }; + + final bool effectivePrimary = primary + ?? mainAxisDetails.controller == null && PrimaryScrollController.shouldInherit( + context, + mainAxis, + ); + + if (effectivePrimary) { + // Using PrimaryScrollController for mainAxis. + assert( + mainAxisDetails.controller == null, + 'TwoDimensionalScrollView.primary was explicitly set to true, but a ' + 'ScrollController was provided in the ScrollableDetails of the ' + 'TwoDimensionalScrollView.mainAxis.' + ); + mainAxisDetails = mainAxisDetails.copyWith( + controller: PrimaryScrollController.of(context), + ); + } + + final TwoDimensionalScrollable scrollable = TwoDimensionalScrollable( + horizontalDetails : switch (mainAxis) { + Axis.horizontal => mainAxisDetails, + Axis.vertical => horizontalDetails, + }, + verticalDetails: switch (mainAxis) { + Axis.vertical => mainAxisDetails, + Axis.horizontal => verticalDetails, + }, + diagonalDragBehavior: diagonalDragBehavior, + viewportBuilder: buildViewport, + dragStartBehavior: dragStartBehavior, + ); + + final Widget scrollableResult = effectivePrimary + // Further descendant ScrollViews will not inherit the same PrimaryScrollController + ? PrimaryScrollController.none(child: scrollable) + : scrollable; + + if (keyboardDismissBehavior == ScrollViewKeyboardDismissBehavior.onDrag) { + return NotificationListener<ScrollUpdateNotification>( + child: scrollableResult, + onNotification: (ScrollUpdateNotification notification) { + final FocusScopeNode focusScope = FocusScope.of(context); + if (notification.dragDetails != null && focusScope.hasFocus) { + focusScope.unfocus(); + } + return false; + }, + ); + } + return scrollableResult; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(EnumProperty<Axis>('mainAxis', mainAxis)); + properties.add(EnumProperty<DiagonalDragBehavior>('diagonalDragBehavior', diagonalDragBehavior)); + properties.add(FlagProperty('primary', value: primary, ifTrue: 'using primary controller', showName: true)); + properties.add(DiagnosticsProperty<ScrollableDetails>('verticalDetails', verticalDetails, showName: false)); + properties.add(DiagnosticsProperty<ScrollableDetails>('horizontalDetails', horizontalDetails, showName: false)); + } +} diff --git a/packages/flutter/lib/src/widgets/two_dimensional_viewport.dart b/packages/flutter/lib/src/widgets/two_dimensional_viewport.dart new file mode 100644 index 0000000000000..82befbce08432 --- /dev/null +++ b/packages/flutter/lib/src/widgets/two_dimensional_viewport.dart @@ -0,0 +1,1481 @@ +// 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:math' as math; + +import 'package:flutter/rendering.dart'; + +import 'framework.dart'; +import 'scroll_delegate.dart'; +import 'scroll_notification.dart'; +import 'scroll_position.dart'; + +export 'package:flutter/rendering.dart' show AxisDirection; + +// Examples can assume: +// late final RenderBox child; +// late final BoxConstraints constraints; +// class RenderSimpleTwoDimensionalViewport extends RenderTwoDimensionalViewport { +// RenderSimpleTwoDimensionalViewport({ +// required super.horizontalOffset, +// required super.horizontalAxisDirection, +// required super.verticalOffset, +// required super.verticalAxisDirection, +// required super.delegate, +// required super.mainAxis, +// required super.childManager, +// super.cacheExtent, +// super.clipBehavior = Clip.hardEdge, +// }); +// @override +// void layoutChildSequence() { } +// } + +/// Signature for a function that creates a widget for a given [ChildVicinity], +/// e.g., in a [TwoDimensionalScrollView], but may return null. +/// +/// Used by [TwoDimensionalChildBuilderDelegate.builder] and other APIs that +/// use lazily-generated widgets where the child count may not be known +/// ahead of time. +/// +/// Unlike most builders, this callback can return null, indicating the +/// [ChildVicinity.xIndex] or [ChildVicinity.yIndex] is out of range. Whether +/// and when this is valid depends on the semantics of the builder. For example, +/// [TwoDimensionalChildBuilderDelegate.builder] returns +/// null when one or both of the indices is out of range, where the range is +/// defined by the [TwoDimensionalChildBuilderDelegate.maxXIndex] or +/// [TwoDimensionalChildBuilderDelegate.maxYIndex]; so in that case the +/// vicinity values may determine whether returning null is valid or not. +/// +/// See also: +/// +/// * [WidgetBuilder], which is similar but only takes a [BuildContext]. +/// * [NullableIndexedWidgetBuilder], which is similar but may return null. +/// * [IndexedWidgetBuilder], which is similar but not nullable. +typedef TwoDimensionalIndexedWidgetBuilder = Widget? Function(BuildContext, ChildVicinity vicinity); + +/// A widget through which a portion of larger content can be viewed, typically +/// in combination with a [TwoDimensionalScrollable]. +/// +/// [TwoDimensionalViewport] is the visual workhorse of the two dimensional +/// scrolling machinery. It displays a subset of its children according to its +/// own dimensions and the given [horizontalOffset] an [verticalOffset]. As the +/// offsets vary, different children are visible through the viewport. +/// +/// Subclasses must implement [createRenderObject] and [updateRenderObject]. +/// Both of these methods require the render object to be a subclass of +/// [RenderTwoDimensionalViewport]. This class will create its own +/// [RenderObjectElement] which already implements the +/// [TwoDimensionalChildManager], which means subclasses should cast the +/// [BuildContext] to provide as the child manager to the +/// [RenderTwoDimensionalViewport]. +/// +/// {@tool snippet} +/// This is an example of a subclass implementation of [TwoDimensionalViewport], +/// `SimpleTwoDimensionalViewport`. The `RenderSimpleTwoDimensionalViewport` is +/// a subclass of [RenderTwoDimensionalViewport]. +/// +/// ```dart +/// class SimpleTwoDimensionalViewport extends TwoDimensionalViewport { +/// const SimpleTwoDimensionalViewport({ +/// super.key, +/// required super.verticalOffset, +/// required super.verticalAxisDirection, +/// required super.horizontalOffset, +/// required super.horizontalAxisDirection, +/// required super.delegate, +/// required super.mainAxis, +/// super.cacheExtent, +/// super.clipBehavior = Clip.hardEdge, +/// }); +/// +/// @override +/// RenderSimpleTwoDimensionalViewport createRenderObject(BuildContext context) { +/// return RenderSimpleTwoDimensionalViewport( +/// horizontalOffset: horizontalOffset, +/// horizontalAxisDirection: horizontalAxisDirection, +/// verticalOffset: verticalOffset, +/// verticalAxisDirection: verticalAxisDirection, +/// mainAxis: mainAxis, +/// delegate: delegate, +/// childManager: context as TwoDimensionalChildManager, +/// cacheExtent: cacheExtent, +/// clipBehavior: clipBehavior, +/// ); +/// } +/// +/// @override +/// void updateRenderObject(BuildContext context, RenderSimpleTwoDimensionalViewport renderObject) { +/// renderObject +/// ..horizontalOffset = horizontalOffset +/// ..horizontalAxisDirection = horizontalAxisDirection +/// ..verticalOffset = verticalOffset +/// ..verticalAxisDirection = verticalAxisDirection +/// ..mainAxis = mainAxis +/// ..delegate = delegate +/// ..cacheExtent = cacheExtent +/// ..clipBehavior = clipBehavior; +/// } +/// } +/// ``` +/// {@end-tool} +/// +/// See also: +/// +/// * [Viewport], the equivalent of this widget that scrolls in only one +/// dimension. +abstract class TwoDimensionalViewport extends RenderObjectWidget { + /// Creates a viewport for [RenderBox] objects that extend and scroll in both + /// horizontal and vertical dimensions. + /// + /// The viewport listens to the [horizontalOffset] and [verticalOffset], which + /// means this widget does not need to be rebuilt when the offsets change. + const TwoDimensionalViewport({ + super.key, + required this.verticalOffset, + required this.verticalAxisDirection, + required this.horizontalOffset, + required this.horizontalAxisDirection, + required this.delegate, + required this.mainAxis, + this.cacheExtent, + this.clipBehavior = Clip.hardEdge, + }) : assert( + verticalAxisDirection == AxisDirection.down || verticalAxisDirection == AxisDirection.up, + 'TwoDimensionalViewport.verticalAxisDirection is not Axis.vertical.' + ), + assert( + horizontalAxisDirection == AxisDirection.left || horizontalAxisDirection == AxisDirection.right, + 'TwoDimensionalViewport.horizontalAxisDirection is not Axis.horizontal.' + ); + + /// Which part of the content inside the viewport should be visible in the + /// vertical axis. + /// + /// The [ViewportOffset.pixels] value determines the scroll offset that the + /// viewport uses to select which part of its content to display. As the user + /// scrolls the viewport vertically, this value changes, which changes the + /// content that is displayed. + /// + /// Typically a [ScrollPosition]. + final ViewportOffset verticalOffset; + + /// The direction in which the [verticalOffset]'s [ViewportOffset.pixels] + /// increases. + /// + /// For example, if the axis direction is [AxisDirection.down], a scroll + /// offset of zero is at the top of the viewport and increases towards the + /// bottom of the viewport. + /// + /// Must be either [AxisDirection.down] or [AxisDirection.up] in correlation + /// with an [Axis.vertical]. + final AxisDirection verticalAxisDirection; + + /// Which part of the content inside the viewport should be visible in the + /// horizontal axis. + /// + /// The [ViewportOffset.pixels] value determines the scroll offset that the + /// viewport uses to select which part of its content to display. As the user + /// scrolls the viewport horizontally, this value changes, which changes the + /// content that is displayed. + /// + /// Typically a [ScrollPosition]. + final ViewportOffset horizontalOffset; + + /// The direction in which the [horizontalOffset]'s [ViewportOffset.pixels] + /// increases. + /// + /// For example, if the axis direction is [AxisDirection.right], a scroll + /// offset of zero is at the left of the viewport and increases towards the + /// right of the viewport. + /// + /// Must be either [AxisDirection.left] or [AxisDirection.right] in correlation + /// with an [Axis.horizontal]. + final AxisDirection horizontalAxisDirection; + + /// The main axis of the two. + /// + /// Used to determine the paint order of the children of the viewport. When + /// the main axis is [Axis.vertical], children will be painted in row major + /// order, according to their associated [ChildVicinity]. When the main axis + /// is [Axis.horizontal], the children will be painted in column major order. + final Axis mainAxis; + + /// {@macro flutter.rendering.RenderViewportBase.cacheExtent} + final double? cacheExtent; + + /// {@macro flutter.material.Material.clipBehavior} + final Clip clipBehavior; + + /// A delegate that provides the children for the [TwoDimensionalViewport]. + final TwoDimensionalChildDelegate delegate; + + @override + RenderObjectElement createElement() => _TwoDimensionalViewportElement(this); + + @override + RenderTwoDimensionalViewport createRenderObject(BuildContext context); + + @override + void updateRenderObject(BuildContext context, RenderTwoDimensionalViewport renderObject); +} + +class _TwoDimensionalViewportElement extends RenderObjectElement + with NotifiableElementMixin, ViewportElementMixin implements TwoDimensionalChildManager { + _TwoDimensionalViewportElement(super.widget); + + @override + RenderTwoDimensionalViewport get renderObject => super.renderObject as RenderTwoDimensionalViewport; + + // Contains all children, including those that are keyed. + Map<ChildVicinity, Element> _vicinityToChild = <ChildVicinity, Element>{}; + Map<Key, Element> _keyToChild = <Key, Element>{}; + // Used between _startLayout() & _endLayout() to compute the new values for + // _vicinityToChild and _keyToChild. + Map<ChildVicinity, Element>? _newVicinityToChild; + Map<Key, Element>? _newKeyToChild; + + @override + void performRebuild() { + super.performRebuild(); + // Children list is updated during layout since we only know during layout + // which children will be visible. + renderObject.markNeedsLayout(withDelegateRebuild: true); + } + + @override + void forgetChild(Element child) { + assert(!_debugIsDoingLayout); + super.forgetChild(child); + _vicinityToChild.remove(child.slot); + if (child.widget.key != null) { + _keyToChild.remove(child.widget.key); + } + } + + @override + void insertRenderObjectChild(RenderBox child, ChildVicinity slot) { + renderObject._insertChild(child, slot); + } + + @override + void moveRenderObjectChild(RenderBox child, ChildVicinity oldSlot, ChildVicinity newSlot) { + renderObject._moveChild(child, from: oldSlot, to: newSlot); + } + + @override + void removeRenderObjectChild(RenderBox child, ChildVicinity slot) { + renderObject._removeChild(child, slot); + } + + @override + void visitChildren(ElementVisitor visitor) { + _vicinityToChild.values.forEach(visitor); + } + + @override + List<DiagnosticsNode> debugDescribeChildren() { + final List<Element> children = _vicinityToChild.values.toList()..sort(_compareChildren); + return <DiagnosticsNode>[ + for (final Element child in children) + child.toDiagnosticsNode(name: child.slot.toString()) + ]; + } + + static int _compareChildren(Element a, Element b) { + final ChildVicinity aSlot = a.slot! as ChildVicinity; + final ChildVicinity bSlot = b.slot! as ChildVicinity; + return aSlot.compareTo(bSlot); + } + + // ---- ChildManager implementation ---- + + bool get _debugIsDoingLayout => _newKeyToChild != null && _newVicinityToChild != null; + + @override + void _startLayout() { + assert(!_debugIsDoingLayout); + _newVicinityToChild = <ChildVicinity, Element>{}; + _newKeyToChild = <Key, Element>{}; + } + + @override + void _buildChild(ChildVicinity vicinity) { + assert(_debugIsDoingLayout); + owner!.buildScope(this, () { + final Widget? newWidget = (widget as TwoDimensionalViewport).delegate.build(this, vicinity); + if (newWidget == null) { + return; + } + final Element? oldElement = _retrieveOldElement(newWidget, vicinity); + final Element? newChild = updateChild(oldElement, newWidget, vicinity); + assert(newChild != null); + // Ensure we are not overwriting an existing child. + assert(_newVicinityToChild![vicinity] == null); + _newVicinityToChild![vicinity] = newChild!; + if (newWidget.key != null) { + // Ensure we are not overwriting an existing key + assert(_newKeyToChild![newWidget.key!] == null); + _newKeyToChild![newWidget.key!] = newChild; + } + }); + } + + Element? _retrieveOldElement(Widget newWidget, ChildVicinity vicinity) { + if (newWidget.key != null) { + final Element? result = _keyToChild.remove(newWidget.key); + if (result != null) { + _vicinityToChild.remove(result.slot); + } + return result; + } + final Element? potentialOldElement = _vicinityToChild[vicinity]; + if (potentialOldElement != null && potentialOldElement.widget.key == null) { + return _vicinityToChild.remove(vicinity); + } + return null; + } + + @override + void _reuseChild(ChildVicinity vicinity) { + assert(_debugIsDoingLayout); + final Element? elementToReuse = _vicinityToChild.remove(vicinity); + assert( + elementToReuse != null, + 'Expected to re-use an element at $vicinity, but none was found.' + ); + _newVicinityToChild![vicinity] = elementToReuse!; + if (elementToReuse.widget.key != null) { + assert(_keyToChild.containsKey(elementToReuse.widget.key)); + assert(_keyToChild[elementToReuse.widget.key] == elementToReuse); + _newKeyToChild![elementToReuse.widget.key!] = _keyToChild.remove(elementToReuse.widget.key)!; + } + } + + @override + void _endLayout() { + assert(_debugIsDoingLayout); + + // Unmount all elements that have not been reused in the layout cycle. + for (final Element element in _vicinityToChild.values) { + if (element.widget.key == null) { + // If it has a key, we handle it below. + updateChild(element, null, null); + } else { + assert(_keyToChild.containsValue(element)); + } + } + for (final Element element in _keyToChild.values) { + assert(element.widget.key != null); + updateChild(element, null, null); + } + + _vicinityToChild = _newVicinityToChild!; + _keyToChild = _newKeyToChild!; + _newVicinityToChild = null; + _newKeyToChild = null; + assert(!_debugIsDoingLayout); + } +} + +/// Parent data structure used by [RenderTwoDimensionalViewport]. +/// +/// The parent data primarily describes where a child is in the viewport. The +/// [layoutOffset] must be set by subclasses of [RenderTwoDimensionalViewport], +/// during [RenderTwoDimensionalViewport.layoutChildSequence] which represents +/// the position of the child in the viewport. +/// +/// The [paintOffset] is computed by [RenderTwoDimensionalViewport] after +/// [RenderTwoDimensionalViewport.layoutChildSequence]. If subclasses of +/// RenderTwoDimensionalViewport override the paint method, the [paintOffset] +/// should be used to position the child in the viewport in order to account for +/// a reversed [AxisDirection] in one or both dimensions. +class TwoDimensionalViewportParentData extends ParentData { + /// The offset at which to paint the child in the parent's coordinate system. + /// + /// This [Offset] represents the top left corner of the child of the + /// [TwoDimensionalViewport]. + /// + /// This value must be set by implementors during + /// [RenderTwoDimensionalViewport.layoutChildSequence]. After the method is + /// complete, the [RenderTwoDimensionalViewport] will compute the + /// [paintOffset] based on this value to account for the [AxisDirection]. + Offset? layoutOffset; + + /// The logical positioning of children in two dimensions. + /// + /// While children may not be strictly laid out in rows and columns, the + /// relative positioning determines traversal of + /// children in row or column major format. + /// + /// This is set in the [RenderTwoDimensionalViewport.buildOrObtainChildFor]. + ChildVicinity vicinity = ChildVicinity.invalid; + + /// Whether or not the child is actually visible within the viewport. + /// + /// For example, if a child is contained within the + /// [RenderTwoDimensionalViewport.cacheExtent] and out of view. + /// + /// This is used during [RenderTwoDimensionalViewport.paint] in order to skip + /// painting children that cannot be seen. + bool get isVisible { + assert(() { + if (_paintExtent == null) { + throw FlutterError.fromParts(<DiagnosticsNode>[ + ErrorSummary('The paint extent of the child has not been determined yet.'), + ErrorDescription( + 'The paint extent, and therefore the visibility, of a child of a ' + 'RenderTwoDimensionalViewport is computed after ' + 'RenderTwoDimensionalViewport.layoutChildSequence.' + ), + ]); + } + return true; + }()); + return _paintExtent != Size.zero || _paintExtent!.height != 0.0 || _paintExtent!.width != 0.0; + } + + /// Represents the extent in both dimensions of the child that is actually + /// visible. + /// + /// For example, if a child [RenderBox] had a height of 100 pixels, and a + /// width of 100 pixels, but was scrolled to positions such that only 50 + /// pixels of both width and height were visible, the paintExtent would be + /// represented as `Size(50.0, 50.0)`. + /// + /// This is set in [RenderTwoDimensionalViewport.updateChildPaintData]. + Size? _paintExtent; + + /// The previous sibling in the parent's child list according to the traversal + /// order specified by [RenderTwoDimensionalViewport.mainAxis]. + RenderBox? _previousSibling; + + /// The next sibling in the parent's child list according to the traversal + /// order specified by [RenderTwoDimensionalViewport.mainAxis]. + RenderBox? _nextSibling; + + /// The position of the child relative to the bounds and [AxisDirection] of + /// the viewport. + /// + /// This is the distance from the top left visible corner of the parent to the + /// top left visible corner of the child. When the [AxisDirection]s are + /// [AxisDirection.down] or [AxisDirection.right], this value is the same as + /// the [layoutOffset]. This value deviates when scrolling in the reverse + /// directions of [AxisDirection.up] and [AxisDirection.left] to reposition + /// the children correctly. + /// + /// This is set in [RenderTwoDimensionalViewport.updateChildPaintData], after + /// [RenderTwoDimensionalViewport.layoutChildSequence]. + /// + /// If overriding [RenderTwoDimensionalViewport.paint], use this value to + /// position the children instead of [layoutOffset]. + Offset? paintOffset; + + @override + String toString() { + return 'vicinity=$vicinity; ' + 'layoutOffset=$layoutOffset; ' + 'paintOffset=$paintOffset; ' + '${_paintExtent == null + ? 'not visible ' + : '${!isVisible ? 'not ' : ''}visible - paintExtent=$_paintExtent'}'; + } +} + +/// A base class for viewing render objects that scroll in two dimensions. +/// +/// The viewport listens to two [ViewportOffset]s, which determines the +/// visible content. +/// +/// Subclasses must implement [layoutChildSequence], calling on +/// [buildOrObtainChildFor] to manage the children of the viewport. +/// +/// Subclasses should not override [performLayout], as it handles housekeeping +/// on either side of the call to [layoutChildSequence]. +// TODO(Piinks): Two follow up changes: +// - Keep alive https://github.com/flutter/flutter/issues/126297 +// - ensureVisible https://github.com/flutter/flutter/issues/126299 +abstract class RenderTwoDimensionalViewport extends RenderBox implements RenderAbstractViewport { + /// Initializes fields for subclasses. + /// + /// The [cacheExtent], if null, defaults to + /// [RenderAbstractViewport.defaultCacheExtent]. + RenderTwoDimensionalViewport({ + required ViewportOffset horizontalOffset, + required AxisDirection horizontalAxisDirection, + required ViewportOffset verticalOffset, + required AxisDirection verticalAxisDirection, + required TwoDimensionalChildDelegate delegate, + required Axis mainAxis, + required TwoDimensionalChildManager childManager, + double? cacheExtent, + Clip clipBehavior = Clip.hardEdge, + }) : assert( + verticalAxisDirection == AxisDirection.down || verticalAxisDirection == AxisDirection.up, + 'TwoDimensionalViewport.verticalAxisDirection is not Axis.vertical.' + ), + assert( + horizontalAxisDirection == AxisDirection.left || horizontalAxisDirection == AxisDirection.right, + 'TwoDimensionalViewport.horizontalAxisDirection is not Axis.horizontal.' + ), + _childManager = childManager, + _horizontalOffset = horizontalOffset, + _horizontalAxisDirection = horizontalAxisDirection, + _verticalOffset = verticalOffset, + _verticalAxisDirection = verticalAxisDirection, + _delegate = delegate, + _mainAxis = mainAxis, + _cacheExtent = cacheExtent ?? RenderAbstractViewport.defaultCacheExtent, + _clipBehavior = clipBehavior; + + /// Which part of the content inside the viewport should be visible in the + /// horizontal axis. + /// + /// The [ViewportOffset.pixels] value determines the scroll offset that the + /// viewport uses to select which part of its content to display. As the user + /// scrolls the viewport horizontally, this value changes, which changes the + /// content that is displayed. + /// + /// Typically a [ScrollPosition]. + ViewportOffset get horizontalOffset => _horizontalOffset; + ViewportOffset _horizontalOffset; + set horizontalOffset(ViewportOffset value) { + if (_horizontalOffset == value) { + return; + } + if (attached) { + _horizontalOffset.removeListener(markNeedsLayout); + } + _horizontalOffset = value; + if (attached) { + _horizontalOffset.addListener(markNeedsLayout); + } + markNeedsLayout(); + } + + /// The direction in which the [horizontalOffset] increases. + /// + /// For example, if the axis direction is [AxisDirection.right], a scroll + /// offset of zero is at the left of the viewport and increases towards the + /// right of the viewport. + AxisDirection get horizontalAxisDirection => _horizontalAxisDirection; + AxisDirection _horizontalAxisDirection; + set horizontalAxisDirection(AxisDirection value) { + if (_horizontalAxisDirection == value) { + return; + } + _horizontalAxisDirection = value; + markNeedsLayout(); + } + + /// Which part of the content inside the viewport should be visible in the + /// vertical axis. + /// + /// The [ViewportOffset.pixels] value determines the scroll offset that the + /// viewport uses to select which part of its content to display. As the user + /// scrolls the viewport vertically, this value changes, which changes the + /// content that is displayed. + /// + /// Typically a [ScrollPosition]. + ViewportOffset get verticalOffset => _verticalOffset; + ViewportOffset _verticalOffset; + set verticalOffset(ViewportOffset value) { + if (_verticalOffset == value) { + return; + } + if (attached) { + _verticalOffset.removeListener(markNeedsLayout); + } + _verticalOffset = value; + if (attached) { + _verticalOffset.addListener(markNeedsLayout); + } + markNeedsLayout(); + } + + /// The direction in which the [verticalOffset] increases. + /// + /// For example, if the axis direction is [AxisDirection.down], a scroll + /// offset of zero is at the top the viewport and increases towards the + /// bottom of the viewport. + AxisDirection get verticalAxisDirection => _verticalAxisDirection; + AxisDirection _verticalAxisDirection; + set verticalAxisDirection(AxisDirection value) { + if (_verticalAxisDirection == value) { + return; + } + _verticalAxisDirection = value; + markNeedsLayout(); + } + + /// Supplies children for layout in the viewport. + TwoDimensionalChildDelegate get delegate => _delegate; + TwoDimensionalChildDelegate _delegate; + set delegate(covariant TwoDimensionalChildDelegate value) { + if (_delegate == value) { + return; + } + if (attached) { + _delegate.removeListener(_handleDelegateNotification); + } + final TwoDimensionalChildDelegate oldDelegate = _delegate; + _delegate = value; + if (attached) { + _delegate.addListener(_handleDelegateNotification); + } + if (_delegate.runtimeType != oldDelegate.runtimeType || _delegate.shouldRebuild(oldDelegate)) { + _handleDelegateNotification(); + } + } + + /// The major axis of the two dimensions. + /// + /// This is can be used by subclasses to determine paint order, + /// visitor patterns like row and column major ordering, or hit test + /// precedence. + /// + /// See also: + /// + /// * [TwoDimensionalScrollView], which assigns the [PrimaryScrollController] + /// to the [TwoDimensionalScrollView.mainAxis] and shares this value. + Axis get mainAxis => _mainAxis; + Axis _mainAxis; + set mainAxis(Axis value) { + if (_mainAxis == value) { + return; + } + _mainAxis = value; + // Child order needs to be resorted, which happens in performLayout. + markNeedsLayout(); + } + + /// {@macro flutter.rendering.RenderViewportBase.cacheExtent} + double get cacheExtent => _cacheExtent ?? RenderAbstractViewport.defaultCacheExtent; + double? _cacheExtent; + set cacheExtent(double? value) { + if (_cacheExtent == value) { + return; + } + _cacheExtent = value; + markNeedsLayout(); + } + + /// {@macro flutter.material.Material.clipBehavior} + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior; + set clipBehavior(Clip value) { + if (_clipBehavior == value) { + return; + } + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + + final TwoDimensionalChildManager _childManager; + bool _hasVisualOverflow = false; + final LayerHandle<ClipRectLayer> _clipRectLayer = LayerHandle<ClipRectLayer>(); + + @override + bool get isRepaintBoundary => true; + + @override + bool get sizedByParent => true; + + final Map<ChildVicinity, RenderBox> _children = <ChildVicinity, RenderBox>{}; + // Keeps track of the upper and lower bounds of ChildVicinity indices when + // subclasses call buildOrObtainChildFor during layoutChildSequence. These + // values are used to sort children in accordance with the mainAxis for + // paint order. + int? _leadingXIndex; + int? _trailingXIndex; + int? _leadingYIndex; + int? _trailingYIndex; + + /// The first child of the viewport according to the traversal order of the + /// [mainAxis]. + /// + /// {@template flutter.rendering.twoDimensionalViewport.paintOrder} + /// The [mainAxis] correlates with the [ChildVicinity] of each child to paint + /// the children in a row or column major order. + /// + /// By default, the [mainAxis] is [Axis.vertical], which would result in a + /// row major paint order, visiting children in the horizontal indices before + /// advancing to the next vertical index. + /// {@endtemplate} + /// + /// This value is null during [layoutChildSequence] as children are reified + /// into the correct order after layout is completed. This can be used when + /// overriding [paint] in order to paint the children in the correct order. + RenderBox? get firstChild => _firstChild; + RenderBox? _firstChild; + + /// The last child in the viewport according to the traversal order of the + /// [mainAxis]. + /// + /// {@macro flutter.rendering.twoDimensionalViewport.paintOrder} + /// + /// This value is null during [layoutChildSequence] as children are reified + /// into the correct order after layout is completed. This can be used when + /// overriding [paint] in order to paint the children in the correct order. + RenderBox? get lastChild => _lastChild; + RenderBox? _lastChild; + + /// The previous child before the given child in the child list according to + /// the traversal order of the [mainAxis]. + /// + /// {@macro flutter.rendering.twoDimensionalViewport.paintOrder} + /// + /// This method is useful when overriding [paint] in order to paint children + /// in the correct order. + RenderBox? childBefore(RenderBox child) { + assert(child.parent == this); + return parentDataOf(child)._previousSibling; + } + + /// The next child after the given child in the child list according to + /// the traversal order of the [mainAxis]. + /// + /// {@macro flutter.rendering.twoDimensionalViewport.paintOrder} + /// + /// This method is useful when overriding [paint] in order to paint children + /// in the correct order. + RenderBox? childAfter(RenderBox child) { + assert(child.parent == this); + return parentDataOf(child)._nextSibling; + } + + void _handleDelegateNotification() { + return markNeedsLayout(withDelegateRebuild: true); + } + + @override + void setupParentData(RenderBox child) { + if (child.parentData is! TwoDimensionalViewportParentData) { + child.parentData = TwoDimensionalViewportParentData(); + } + } + + /// Convenience method for retrieving and casting the [ParentData] of the + /// viewport's children. + /// + /// Children must have a [ParentData] of type + /// [TwoDimensionalViewportParentData], or a subclass thereof. + @protected + TwoDimensionalViewportParentData parentDataOf(RenderBox child) { + assert(_children.containsValue(child)); + return child.parentData! as TwoDimensionalViewportParentData; + } + + /// Returns the active child located at the provided [ChildVicinity], if there + /// is one. + /// + /// This can be used by subclasses to access currently active children to make + /// use of their size or [TwoDimensionalViewportParentData], such as when + /// overriding the [paint] method. + /// + /// Returns null if there is no active child for the given [ChildVicinity]. + @protected + RenderBox? getChildFor(ChildVicinity vicinity) => _children[vicinity]; + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _horizontalOffset.addListener(markNeedsLayout); + _verticalOffset.addListener(markNeedsLayout); + _delegate.addListener(_handleDelegateNotification); + for (final RenderBox child in _children.values) { + child.attach(owner); + } + } + + @override + void detach() { + super.detach(); + _horizontalOffset.removeListener(markNeedsLayout); + _verticalOffset.removeListener(markNeedsLayout); + _delegate.removeListener(_handleDelegateNotification); + for (final RenderBox child in _children.values) { + child.detach(); + } + } + + @override + void redepthChildren() { + for (final RenderBox child in _children.values) { + child.redepthChildren(); + } + } + + @override + void visitChildren(RenderObjectVisitor visitor) { + RenderBox? child = _firstChild; + while (child != null) { + visitor(child); + child = parentDataOf(child)._nextSibling; + } + } + + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + // Only children that are visible should be visited, and they must be in + // paint order. + RenderBox? child = _firstChild; + while (child != null) { + final TwoDimensionalViewportParentData childParentData = parentDataOf(child); + if (childParentData.isVisible) { + visitor(child); + } + child = childParentData._nextSibling; + } + } + + @override + List<DiagnosticsNode> debugDescribeChildren() { + final List<DiagnosticsNode> debugChildren = <DiagnosticsNode>[ + ..._children.keys.map<DiagnosticsNode>((ChildVicinity vicinity) { + return _children[vicinity]!.toDiagnosticsNode(name: vicinity.toString()); + }) + ]; + return debugChildren; + } + + @override + Size computeDryLayout(BoxConstraints constraints) { + assert(debugCheckHasBoundedAxis(Axis.vertical, constraints)); + assert(debugCheckHasBoundedAxis(Axis.horizontal, constraints)); + return constraints.biggest; + } + + @override + bool hitTestChildren(BoxHitTestResult result, { required Offset position }) { + for (final RenderBox child in _children.values) { + final TwoDimensionalViewportParentData childParentData = parentDataOf(child); + if (!childParentData.isVisible) { + // Can't hit a child that is not visible. + continue; + } + final bool isHit = result.addWithPaintOffset( + offset: childParentData.paintOffset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + assert(transformed == position - childParentData.paintOffset!); + return child.hitTest(result, position: transformed); + }, + ); + if (isHit) { + return true; + } + } + return false; + } + + /// The dimensions of the viewport. + /// + /// This [Size] represents the width and height of the visible area. + Size get viewportDimension { + assert(hasSize); + return size; + } + + @override + void performResize() { + final Size? oldSize = hasSize ? size : null; + super.performResize(); + // Ignoring return value since we are doing a layout either way + // (performLayout will be invoked next). + horizontalOffset.applyViewportDimension(size.width); + verticalOffset.applyViewportDimension(size.height); + if (oldSize != size) { + // Specs can depend on viewport size. + _didResize = true; + } + } + + @override + RevealedOffset getOffsetToReveal(RenderObject target, double alignment, { Rect? rect }) { + // TODO(Piinks): Add this back in follow up change (ensureVisible), https://github.com/flutter/flutter/issues/126299 + return const RevealedOffset(offset: 0.0, rect: Rect.zero); + } + + /// Should be used by subclasses to invalidate any cached metrics for the + /// viewport. + /// + /// This is set to true when the viewport has been resized, indicating that + /// any cached dimensions are invalid. + /// + /// After performLayout, the value is set to false until the viewport + /// dimensions are changed again in [performResize]. + /// + /// Subclasses are not required to use this value, but it can be used to + /// safely cache layout information in between layout calls. + bool get didResize => _didResize; + bool _didResize = true; + + /// Should be used by subclasses to invalidate any cached data from the + /// [delegate]. + /// + /// This value is set to false after [layoutChildSequence]. If + /// [markNeedsLayout] is called `withDelegateRebuild` set to true, then this + /// value will be updated to true, signifying any cached delegate information + /// needs to be updated in the next call to [layoutChildSequence]. + /// + /// Subclasses are not required to use this value, but it can be used to + /// safely cache layout information in between layout calls. + @protected + bool get needsDelegateRebuild => _needsDelegateRebuild; + bool _needsDelegateRebuild = true; + + @override + void markNeedsLayout({ bool withDelegateRebuild = false }) { + _needsDelegateRebuild = _needsDelegateRebuild || withDelegateRebuild; + super.markNeedsLayout(); + } + + /// Primary work horse of [performLayout]. + /// + /// Subclasses must implement this method to layout the children of the + /// viewport. The [TwoDimensionalViewportParentData.layoutOffset] must be set + /// during this method in order for the children to be positioned during paint. + /// Further, children of the viewport must be laid out with the expectation + /// that the parent (this viewport) will use their size. + /// + /// ```dart + /// child.layout(constraints, parentUsesSize: true); + /// ``` + /// + /// The primary methods used for creating and obtaining children is + /// [buildOrObtainChildFor], which takes a [ChildVicinity] that is used by the + /// [TwoDimensionalChildDelegate]. If a child is not provided by the delegate + /// for the provided vicinity, the method will return null, otherwise, it will + /// return the [RenderBox] of the child. + /// + /// After [layoutChildSequence] is completed, any remaining children that were + /// not obtained will be disposed. + void layoutChildSequence(); + + @override + void performLayout() { + _firstChild = null; + _lastChild = null; + _childManager._startLayout(); + + // Subclass lays out children. + layoutChildSequence(); + + assert(_debugCheckContentDimensions()); + _didResize = false; + _needsDelegateRebuild = false; + invokeLayoutCallback<BoxConstraints>((BoxConstraints _) { + _childManager._endLayout(); + assert(_debugOrphans?.isEmpty ?? true); + // Organize children in paint order and complete parent data after + // un-used children are disposed of by the childManager. + _reifyChildren(); + }); + } + + // Ensures all children have a layoutOffset, sets paintExtent & paintOffset, + // and arranges children in paint order. + void _reifyChildren() { + assert(_leadingXIndex != null); + assert(_trailingXIndex != null); + assert(_leadingYIndex != null); + assert(_trailingYIndex != null); + assert(_firstChild == null); + assert(_lastChild == null); + RenderBox? previousChild; + switch (mainAxis) { + case Axis.vertical: + // Row major traversal. + // This seems backwards, but the vertical axis is the typical default + // axis for scrolling in Flutter, while Row-major ordering is the + // typical default for matrices, which is why the inverse follows + // through in the horizontal case below. + // Minor + for (int minorIndex = _leadingYIndex!; minorIndex <= _trailingYIndex!; minorIndex++) { + // Major + for (int majorIndex = _leadingXIndex!; majorIndex <= _trailingXIndex!; majorIndex++) { + final ChildVicinity vicinity = ChildVicinity(xIndex: majorIndex, yIndex: minorIndex); + previousChild = _completeChildParentData( + vicinity, + previousChild: previousChild, + ) ?? previousChild; + } + } + case Axis.horizontal: + // Column major traversal + // Minor + for (int minorIndex = _leadingXIndex!; minorIndex <= _trailingXIndex!; minorIndex++) { + // Major + for (int majorIndex = _leadingYIndex!; majorIndex <= _trailingYIndex!; majorIndex++) { + final ChildVicinity vicinity = ChildVicinity(xIndex: minorIndex, yIndex: majorIndex); + previousChild = _completeChildParentData( + vicinity, + previousChild: previousChild, + ) ?? previousChild; + } + } + } + _lastChild = previousChild; + parentDataOf(_lastChild!)._nextSibling = null; + // Reset for next layout pass. + _leadingXIndex = null; + _trailingXIndex = null; + _leadingYIndex = null; + _trailingYIndex = null; + } + + RenderBox? _completeChildParentData(ChildVicinity vicinity, { RenderBox? previousChild }) { + assert(vicinity != ChildVicinity.invalid); + // It is possible and valid for a vicinity to be skipped. + // For example, a table can have merged cells, spanning multiple + // indices, but only represented by one RenderBox and ChildVicinity. + if (_children.containsKey(vicinity)) { + final RenderBox child = _children[vicinity]!; + assert(parentDataOf(child).vicinity == vicinity); + updateChildPaintData(child); + if (previousChild == null) { + // _firstChild is only set once. + assert(_firstChild == null); + _firstChild = child; + } else { + parentDataOf(previousChild)._nextSibling = child; + parentDataOf(child)._previousSibling = previousChild; + } + return child; + } + return null; + } + + bool _debugCheckContentDimensions() { + const String hint = 'Subclasses should call applyContentDimensions on the ' + 'verticalOffset and horizontalOffset to set the min and max scroll offset. ' + 'If the contents exceed one or both sides of the viewportDimension, ' + 'ensure the viewportDimension height or width is subtracted in that axis ' + 'for the correct extent.'; + assert(() { + if (!(verticalOffset as ScrollPosition).hasContentDimensions) { + throw FlutterError.fromParts(<DiagnosticsNode>[ + ErrorSummary( + 'The verticalOffset was not given content dimensions during ' + 'layoutChildSequence.' + ), + ErrorHint(hint), + ]); + } + return true; + }()); + assert(() { + if (!(horizontalOffset as ScrollPosition).hasContentDimensions) { + throw FlutterError.fromParts(<DiagnosticsNode>[ + ErrorSummary( + 'The horizontalOffset was not given content dimensions during ' + 'layoutChildSequence.' + ), + ErrorHint(hint), + ]); + } + return true; + }()); + return true; + } + + /// Returns the child for a given [ChildVicinity]. + /// + /// This method will build the child if it has not been already, or will reuse + /// it if it already exists. + RenderBox? buildOrObtainChildFor(ChildVicinity vicinity) { + assert(vicinity != ChildVicinity.invalid); + if (_leadingXIndex == null || _trailingXIndex == null || _leadingXIndex == null || _trailingYIndex == null) { + // First child of this layout pass. Set leading and trailing trackers. + _leadingXIndex = vicinity.xIndex; + _trailingXIndex = vicinity.xIndex; + _leadingYIndex = vicinity.yIndex; + _trailingYIndex = vicinity.yIndex; + } else { + // If any of these are still null, we missed a child. + assert(_leadingXIndex != null); + assert(_trailingXIndex != null); + assert(_leadingYIndex != null); + assert(_trailingYIndex != null); + + // Update as we go. + _leadingXIndex = math.min(vicinity.xIndex, _leadingXIndex!); + _trailingXIndex = math.max(vicinity.xIndex, _trailingXIndex!); + _leadingYIndex = math.min(vicinity.yIndex, _leadingYIndex!); + _trailingYIndex = math.max(vicinity.yIndex, _trailingYIndex!); + } + if (_needsDelegateRebuild || !_children.containsKey(vicinity)) { + invokeLayoutCallback<BoxConstraints>((BoxConstraints _) { + _childManager._buildChild(vicinity); + }); + } else { + _childManager._reuseChild(vicinity); + } + if (!_children.containsKey(vicinity)) { + // There is no child for this vicinity, we may have reached the end of the + // children in one or both of the x/y indices. + return null; + } + + assert(_children.containsKey(vicinity)); + final RenderBox child = _children[vicinity]!; + parentDataOf(child).vicinity = vicinity; + return child; + } + + /// Called after [layoutChildSequence] to compute the + /// [TwoDimensionalViewportParentData.paintOffset] and + /// [TwoDimensionalViewportParentData._paintExtent] of the child. + void updateChildPaintData(RenderBox child) { + final TwoDimensionalViewportParentData childParentData = parentDataOf(child); + assert( + childParentData.layoutOffset != null, + 'The child with ChildVicinity(xIndex: ${childParentData.vicinity.xIndex}, ' + 'yIndex: ${childParentData.vicinity.yIndex}) was not provided a ' + 'layoutOffset. This should be set during layoutChildSequence, ' + 'representing the position of the child.' + ); + assert(child.hasSize); // Child must have been laid out by now. + + // Set paintExtent (and visibility) + childParentData._paintExtent = computeChildPaintExtent( + childParentData.layoutOffset!, + child.size, + ); + // Set paintOffset + childParentData.paintOffset = computeAbsolutePaintOffsetFor( + child, + layoutOffset: childParentData.layoutOffset!, + ); + // If the child is partially visible, or not visible at all, there is + // visual overflow. + _hasVisualOverflow = _hasVisualOverflow + || childParentData.layoutOffset != childParentData._paintExtent + || !childParentData.isVisible; + } + + /// Computes the portion of the child that is visible, assuming that only the + /// region from the [ViewportOffset.pixels] of both dimensions to the + /// [cacheExtent] is visible, and that the relationship between scroll offsets + /// and paint offsets is linear. + /// + /// For example, if the [ViewportOffset]s each have a scroll offset of 100 and + /// the arguments to this method describe a child with [layoutOffset] of + /// `Offset(50.0, 50.0)`, with a size of `Size(200.0, 200.0)`, then the + /// returned value would be `Size(150.0, 150.0)`, representing the visible + /// extent of the child. + Size computeChildPaintExtent(Offset layoutOffset, Size childSize) { + if (childSize == Size.zero || childSize.height == 0.0 || childSize.width == 0.0) { + return Size.zero; + } + // Horizontal extent + final double width; + if (layoutOffset.dx < 0.0) { + // The child is positioned beyond the leading edge of the viewport. + if (layoutOffset.dx + childSize.width <= 0.0) { + // The child does not extend into the viewable area, it is not visible. + return Size.zero; + } + // If the child is positioned starting at -50, then the paint extent is + // the width + (-50). + width = layoutOffset.dx + childSize.width; + } else if (layoutOffset.dx >= viewportDimension.width) { + // The child is positioned after the trailing edge of the viewport, also + // not visible. + return Size.zero; + } else { + // The child is positioned within the viewport bounds, but may extend + // beyond it. + assert(layoutOffset.dx >= 0 && layoutOffset.dx < viewportDimension.width); + if (layoutOffset.dx + childSize.width > viewportDimension.width) { + width = viewportDimension.width - layoutOffset.dx; + } else { + assert(layoutOffset.dx + childSize.width <= viewportDimension.width); + width = childSize.width; + } + } + + // Vertical extent + final double height; + if (layoutOffset.dy < 0.0) { + // The child is positioned beyond the leading edge of the viewport. + if (layoutOffset.dy + childSize.height <= 0.0) { + // The child does not extend into the viewable area, it is not visible. + return Size.zero; + } + // If the child is positioned starting at -50, then the paint extent is + // the width + (-50). + height = layoutOffset.dy + childSize.height; + } else if (layoutOffset.dy >= viewportDimension.height) { + // The child is positioned after the trailing edge of the viewport, also + // not visible. + return Size.zero; + } else { + // The child is positioned within the viewport bounds, but may extend + // beyond it. + assert(layoutOffset.dy >= 0 && layoutOffset.dy < viewportDimension.height); + if (layoutOffset.dy + childSize.height > viewportDimension.height) { + height = viewportDimension.height - layoutOffset.dy; + } else { + assert(layoutOffset.dy + childSize.height <= viewportDimension.height); + height = childSize.height; + } + } + + return Size(width, height); + } + + /// The offset at which the given `child` should be painted. + /// + /// The returned offset is from the top left corner of the inside of the + /// viewport to the top left corner of the paint coordinate system of the + /// `child`. + /// + /// This is useful when the one or both of the axes of the viewport are + /// reversed. The normalized layout offset of the child is used to compute + /// the paint offset in relation to the [verticalAxisDirection] and + /// [horizontalAxisDirection]. + @protected + Offset computeAbsolutePaintOffsetFor( + RenderBox child, { + required Offset layoutOffset, + }) { + // This is only usable once we have sizes. + assert(hasSize); + assert(child.hasSize); + final double xOffset; + final double yOffset; + switch (verticalAxisDirection) { + case AxisDirection.up: + yOffset = viewportDimension.height - (layoutOffset.dy + child.size.height); + case AxisDirection.down: + yOffset = layoutOffset.dy; + case AxisDirection.right: + case AxisDirection.left: + throw Exception('This should not happen'); + } + switch (horizontalAxisDirection) { + case AxisDirection.right: + xOffset = layoutOffset.dx; + case AxisDirection.left: + xOffset = viewportDimension.width - (layoutOffset.dx + child.size.width); + case AxisDirection.up: + case AxisDirection.down: + throw Exception('This should not happen'); + } + return Offset(xOffset, yOffset); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (_children.isEmpty) { + return; + } + if (_hasVisualOverflow && clipBehavior != Clip.none) { + _clipRectLayer.layer = context.pushClipRect( + needsCompositing, + offset, + Offset.zero & viewportDimension, + _paintChildren, + clipBehavior: clipBehavior, + oldLayer: _clipRectLayer.layer, + ); + } else { + _clipRectLayer.layer = null; + _paintChildren(context, offset); + } + } + + void _paintChildren(PaintingContext context, Offset offset) { + RenderBox? child = _firstChild; + while (child != null) { + final TwoDimensionalViewportParentData childParentData = parentDataOf(child); + if (childParentData.isVisible) { + context.paintChild(child, offset + childParentData.paintOffset!); + } + child = childParentData._nextSibling; + } + } + + // ---- Called from _TwoDimensionalViewportElement ---- + + void _insertChild(RenderBox child, ChildVicinity slot) { + assert(_debugTrackOrphans(newOrphan: _children[slot])); + _children[slot] = child; + adoptChild(child); + } + + void _moveChild(RenderBox child, {required ChildVicinity from, required ChildVicinity to}) { + if (_children[from] == child) { + _children.remove(from); + } + assert(_debugTrackOrphans(newOrphan: _children[to], noLongerOrphan: child)); + _children[to] = child; + } + + void _removeChild(RenderBox child, ChildVicinity slot) { + if (_children[slot] == child) { + _children.remove(slot); + } + assert(_debugTrackOrphans(noLongerOrphan: child)); + dropChild(child); + } + + List<RenderBox>? _debugOrphans; + + // When a child is inserted into a slot currently occupied by another child, + // it becomes an orphan until it is either moved to another slot or removed. + bool _debugTrackOrphans({RenderBox? newOrphan, RenderBox? noLongerOrphan}) { + assert(() { + _debugOrphans ??= <RenderBox>[]; + if (newOrphan != null) { + _debugOrphans!.add(newOrphan); + } + if (noLongerOrphan != null) { + _debugOrphans!.remove(noLongerOrphan); + } + return true; + }()); + return true; + } + + /// Throws an exception saying that the object does not support returning + /// intrinsic dimensions if, in debug mode, we are not in the + /// [RenderObject.debugCheckingIntrinsics] mode. + /// + /// This is used by [computeMinIntrinsicWidth] et al because viewports do not + /// generally support returning intrinsic dimensions. See the discussion at + /// [computeMinIntrinsicWidth]. + @protected + bool debugThrowIfNotCheckingIntrinsics() { + assert(() { + if (!RenderObject.debugCheckingIntrinsics) { + throw FlutterError.fromParts(<DiagnosticsNode>[ + ErrorSummary('$runtimeType does not support returning intrinsic dimensions.'), + ErrorDescription( + 'Calculating the intrinsic dimensions would require instantiating every child of ' + 'the viewport, which defeats the point of viewports being lazy.', + ), + ]); + } + return true; + }()); + return true; + } + + @override + double computeMinIntrinsicWidth(double height) { + assert(debugThrowIfNotCheckingIntrinsics()); + return 0.0; + } + + @override + double computeMaxIntrinsicWidth(double height) { + assert(debugThrowIfNotCheckingIntrinsics()); + return 0.0; + } + + @override + double computeMinIntrinsicHeight(double width) { + assert(debugThrowIfNotCheckingIntrinsics()); + return 0.0; + } + + @override + double computeMaxIntrinsicHeight(double width) { + assert(debugThrowIfNotCheckingIntrinsics()); + return 0.0; + } + + @override + void applyPaintTransform(RenderBox child, Matrix4 transform) { + final Offset paintOffset = parentDataOf(child).paintOffset!; + transform.translate(paintOffset.dx, paintOffset.dy); + } + + @override + void dispose() { + _clipRectLayer.layer = null; + super.dispose(); + } +} + +/// A delegate used by [RenderTwoDimensionalViewport] to manage its children. +/// +/// [RenderTwoDimensionalViewport] objects reify their children lazily to avoid +/// spending resources on children that are not visible in the viewport. This +/// delegate lets these objects create, reuse and remove children. +abstract class TwoDimensionalChildManager { + void _startLayout(); + void _buildChild(ChildVicinity vicinity); + void _reuseChild(ChildVicinity vicinity); + void _endLayout(); +} + +/// The relative position of a child in a [TwoDimensionalViewport] in relation +/// to other children of the viewport. +/// +/// While children can be plotted arbitrarily in two dimensional space, the +/// [ChildVicinity] is used to disambiguate their positions, determining how to +/// traverse the children of the space. +/// +/// Combined with the [RenderTwoDimensionalViewport.mainAxis], each child's +/// vicinity determines its paint order among all of the children. +@immutable +class ChildVicinity implements Comparable<ChildVicinity> { + /// Creates a reference to a child in a two dimensional plane, with the + /// [xIndex] and [yIndex] being relative to other children in the viewport. + const ChildVicinity({ + required this.xIndex, + required this.yIndex, + }) : assert(xIndex >= -1), + assert(yIndex >= -1); + + /// Represents an unassigned child position. The given child may be in the + /// process of moving from one position to another. + static const ChildVicinity invalid = ChildVicinity(xIndex: -1, yIndex: -1); + + /// The index of the child in the horizontal axis, relative to neighboring + /// children. + /// + /// While children's offset and positioning may not be strictly defined in + /// terms of rows and columns, like a table, [ChildVicinity.xIndex] and + /// [ChildVicinity.yIndex] represents order of traversal in row or column + /// major format. + final int xIndex; + + /// The index of the child in the vertical axis, relative to neighboring + /// children. + /// + /// While children's offset and positioning may not be strictly defined in + /// terms of rows and columns, like a table, [ChildVicinity.xIndex] and + /// [ChildVicinity.yIndex] represents order of traversal in row or column + /// major format. + final int yIndex; + + @override + bool operator ==(Object other) { + return other is ChildVicinity + && other.xIndex == xIndex + && other.yIndex == yIndex; + } + + @override + int get hashCode => Object.hash(xIndex, yIndex); + + @override + int compareTo(ChildVicinity other) { + if (xIndex == other.xIndex) { + return yIndex - other.yIndex; + } + return xIndex - other.xIndex; + } + + @override + String toString() { + return '(xIndex: $xIndex, yIndex: $yIndex)'; + } +} diff --git a/packages/flutter/lib/src/widgets/viewport.dart b/packages/flutter/lib/src/widgets/viewport.dart index 92aa83211a4b4..ad4a67facc204 100644 --- a/packages/flutter/lib/src/widgets/viewport.dart +++ b/packages/flutter/lib/src/widgets/viewport.dart @@ -13,7 +13,8 @@ export 'package:flutter/rendering.dart' show AxisDirection, GrowthDirection; -/// A widget that is bigger on the inside. +/// A widget through which a portion of larger content can be viewed, typically +/// in combination with a [Scrollable]. /// /// [Viewport] is the visual workhorse of the scrolling machinery. It displays a /// subset of its children according to its own dimensions and the given diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index 34ad78bc35a88..7a88e928e81de 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -553,7 +553,7 @@ class _ScreenshotPaintingContext extends PaintingContext { }) { RenderObject repaintBoundary = renderObject; while (!repaintBoundary.isRepaintBoundary) { - repaintBoundary = repaintBoundary.parent! as RenderObject; + repaintBoundary = repaintBoundary.parent!; } final _ScreenshotData data = _ScreenshotData(target: renderObject); final _ScreenshotPaintingContext context = _ScreenshotPaintingContext( @@ -680,11 +680,39 @@ typedef InspectorSelectionChangedCallback = void Function(); /// Structure to help reference count Dart objects referenced by a GUI tool /// using [WidgetInspectorService]. -class _InspectorReferenceData { - _InspectorReferenceData(this.object); +/// +/// Does not hold the object from garbage collection. +@visibleForTesting +class InspectorReferenceData { + /// Creates an instance of [InspectorReferenceData]. + InspectorReferenceData(Object object, this.id) { + // These types are not supported by [WeakReference]. + // See https://api.dart.dev/stable/3.0.2/dart-core/WeakReference-class.html + if (object is String || object is num || object is bool) { + _value = object; + return; + } + + _ref = WeakReference<Object>(object); + } + + WeakReference<Object>? _ref; - final Object object; + Object? _value; + + /// The id of the object in the widget inspector records. + final String id; + + /// The number of times the object has been referenced. int count = 1; + + /// The value. + Object? get value { + if (_ref != null) { + return _ref!.target; + } + return _value; + } } // Production implementation of [WidgetInspectorService]. @@ -742,9 +770,9 @@ mixin WidgetInspectorService { /// The VM service protocol does not keep alive object references so this /// class needs to manually manage groups of objects that should be kept /// alive. - final Map<String, Set<_InspectorReferenceData>> _groups = <String, Set<_InspectorReferenceData>>{}; - final Map<String, _InspectorReferenceData> _idToReferenceData = <String, _InspectorReferenceData>{}; - final Map<Object, String> _objectToId = Map<Object, String>.identity(); + final Map<String, Set<InspectorReferenceData>> _groups = <String, Set<InspectorReferenceData>>{}; + final Map<String, InspectorReferenceData> _idToReferenceData = <String, InspectorReferenceData>{}; + final WeakMap<Object, String> _objectToId = WeakMap<Object, String>(); int _nextId = 0; /// The pubRootDirectories that are currently configured for the widget inspector. @@ -1270,20 +1298,22 @@ mixin WidgetInspectorService { /// references from a different group. @protected void disposeGroup(String name) { - final Set<_InspectorReferenceData>? references = _groups.remove(name); + final Set<InspectorReferenceData>? references = _groups.remove(name); if (references == null) { return; } references.forEach(_decrementReferenceCount); } - void _decrementReferenceCount(_InspectorReferenceData reference) { + void _decrementReferenceCount(InspectorReferenceData reference) { reference.count -= 1; assert(reference.count >= 0); if (reference.count == 0) { - final String? id = _objectToId.remove(reference.object); - assert(id != null); - _idToReferenceData.remove(id); + final Object? value = reference.value; + if (value != null) { + _objectToId.remove(value); + } + _idToReferenceData.remove(reference.id); } } @@ -1295,14 +1325,16 @@ mixin WidgetInspectorService { return null; } - final Set<_InspectorReferenceData> group = _groups.putIfAbsent(groupName, () => Set<_InspectorReferenceData>.identity()); + final Set<InspectorReferenceData> group = _groups.putIfAbsent(groupName, () => Set<InspectorReferenceData>.identity()); String? id = _objectToId[object]; - _InspectorReferenceData referenceData; + InspectorReferenceData referenceData; if (id == null) { + // TODO(polina-c): comment here why we increase memory footprint by the prefix 'inspector-'. + // https://github.com/flutter/devtools/issues/5995 id = 'inspector-$_nextId'; _nextId += 1; _objectToId[object] = id; - referenceData = _InspectorReferenceData(object); + referenceData = InspectorReferenceData(object, id); _idToReferenceData[id] = referenceData; group.add(referenceData); } else { @@ -1332,11 +1364,11 @@ mixin WidgetInspectorService { return null; } - final _InspectorReferenceData? data = _idToReferenceData[id]; + final InspectorReferenceData? data = _idToReferenceData[id]; if (data == null) { throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Id does not exist.')]); } - return data.object; + return data.value; } /// Returns the object to introspect to determine the source location of an @@ -1368,7 +1400,7 @@ mixin WidgetInspectorService { return; } - final _InspectorReferenceData? referenceData = _idToReferenceData[id]; + final InspectorReferenceData? referenceData = _idToReferenceData[id]; if (referenceData == null) { throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Id does not exist')]); } @@ -1632,7 +1664,7 @@ mixin WidgetInspectorService { final List<RenderObject> chain = <RenderObject>[]; while (renderObject != null) { chain.add(renderObject); - renderObject = renderObject.parent as RenderObject?; + renderObject = renderObject.parent; } return _followDiagnosticableChain(chain.reversed.toList()); } @@ -1722,9 +1754,12 @@ mixin WidgetInspectorService { return _safeJsonEncode(_getProperties(diagnosticsNodeId, groupName)); } - List<Object> _getProperties(String? diagnosticsNodeId, String groupName) { - final DiagnosticsNode? node = toObject(diagnosticsNodeId) as DiagnosticsNode?; - return _nodesToJson(node == null ? const <DiagnosticsNode>[] : node.getProperties(), InspectorSerializationDelegate(groupName: groupName, service: this), parent: node); + List<Object> _getProperties(String? diagnosticableId, String groupName) { + final DiagnosticsNode? node = _idToDiagnosticsNode(diagnosticableId); + if (node == null) { + return const <Object>[]; + } + return _nodesToJson(node.getProperties(), InspectorSerializationDelegate(groupName: groupName, service: this), parent: node); } /// Returns a JSON representation of the children of the [DiagnosticsNode] @@ -1757,24 +1792,42 @@ mixin WidgetInspectorService { return _safeJsonEncode(_getChildrenSummaryTree(diagnosticsNodeId, groupName)); } - List<Object> _getChildrenSummaryTree(String? diagnosticsNodeId, String groupName) { - final DiagnosticsNode? node = toObject(diagnosticsNodeId) as DiagnosticsNode?; + DiagnosticsNode? _idToDiagnosticsNode(String? diagnosticableId) { + final Object? object = toObject(diagnosticableId); + return objectToDiagnosticsNode(object); + } + + /// If possible, returns [DiagnosticsNode] for the object. + @visibleForTesting + static DiagnosticsNode? objectToDiagnosticsNode(Object? object) { + if (object is Diagnosticable) { + return object.toDiagnosticsNode(); + } + return null; + } + + List<Object> _getChildrenSummaryTree(String? diagnosticableId, String groupName) { + final DiagnosticsNode? node = _idToDiagnosticsNode(diagnosticableId); + if (node == null) { + return <Object>[]; + } + final InspectorSerializationDelegate delegate = InspectorSerializationDelegate(groupName: groupName, summaryTree: true, service: this); - return _nodesToJson(node == null ? const <DiagnosticsNode>[] : _getChildrenFiltered(node, delegate), delegate, parent: node); + return _nodesToJson(_getChildrenFiltered(node, delegate), delegate, parent: node); } /// Returns a JSON representation of the children of the [DiagnosticsNode] - /// object that `diagnosticsNodeId` references providing information needed + /// object that [diagnosticableId] references providing information needed /// for the details subtree view. /// /// The details subtree shows properties inline and includes all children /// rather than a filtered set of important children. - String getChildrenDetailsSubtree(String diagnosticsNodeId, String groupName) { - return _safeJsonEncode(_getChildrenDetailsSubtree(diagnosticsNodeId, groupName)); + String getChildrenDetailsSubtree(String diagnosticableId, String groupName) { + return _safeJsonEncode(_getChildrenDetailsSubtree(diagnosticableId, groupName)); } - List<Object> _getChildrenDetailsSubtree(String? diagnosticsNodeId, String groupName) { - final DiagnosticsNode? node = toObject(diagnosticsNodeId) as DiagnosticsNode?; + List<Object> _getChildrenDetailsSubtree(String? diagnosticableId, String groupName) { + final DiagnosticsNode? node = _idToDiagnosticsNode(diagnosticableId); // With this value of minDepth we only expand one extra level of important nodes. final InspectorSerializationDelegate delegate = InspectorSerializationDelegate(groupName: groupName, includeProperties: true, service: this); return _nodesToJson(node == null ? const <DiagnosticsNode>[] : _getChildrenFiltered(node, delegate), delegate, parent: node); @@ -1886,19 +1939,19 @@ mixin WidgetInspectorService { /// * [getChildrenDetailsSubtree], a method to get children of a node /// in the details subtree. String getDetailsSubtree( - String id, + String diagnosticableId, String groupName, { int subtreeDepth = 2, }) { - return _safeJsonEncode(_getDetailsSubtree( id, groupName, subtreeDepth)); + return _safeJsonEncode(_getDetailsSubtree(diagnosticableId, groupName, subtreeDepth)); } Map<String, Object?>? _getDetailsSubtree( - String? id, + String? diagnosticableId, String? groupName, int subtreeDepth, ) { - final DiagnosticsNode? root = toObject(id) as DiagnosticsNode?; + final DiagnosticsNode? root = _idToDiagnosticsNode(diagnosticableId); if (root == null) { return null; } @@ -1914,13 +1967,12 @@ mixin WidgetInspectorService { } /// Returns a [DiagnosticsNode] representing the currently selected [Element]. - /// - /// If the currently selected [Element] is identical to the [Element] - /// referenced by `previousSelectionId` then the previous [DiagnosticsNode] is - /// reused. @protected String getSelectedWidget(String? previousSelectionId, String groupName) { - return _safeJsonEncode(_getSelectedWidget(previousSelectionId, groupName)); + if (previousSelectionId != null) { + debugPrint('previousSelectionId is deprecated in API'); + } + return _safeJsonEncode(_getSelectedWidget(null, groupName)); } /// Captures an image of the current state of an [object] that is a @@ -1996,18 +2048,18 @@ mixin WidgetInspectorService { Future<Map<String, Object?>> _getLayoutExplorerNode( Map<String, String> parameters, ) { - final String? id = parameters['id']; + final String? diagnosticableId = parameters['id']; final int subtreeDepth = int.parse(parameters['subtreeDepth']!); final String? groupName = parameters['groupName']; Map<String, dynamic>? result = <String, dynamic>{}; - final Object? root = toObject(id); + final DiagnosticsNode? root = _idToDiagnosticsNode(diagnosticableId); if (root == null) { return Future<Map<String, dynamic>>.value(<String, dynamic>{ 'result': result, }); } result = _nodeToJson( - root as DiagnosticsNode, + root, InspectorSerializationDelegate( groupName: groupName, summaryTree: true, @@ -2029,7 +2081,7 @@ mixin WidgetInspectorService { 'renderObject': renderObject.toDiagnosticsNode().toJsonMap(renderObjectSerializationDelegate), }; - final AbstractNode? renderParent = renderObject.parent; + final RenderObject? renderParent = renderObject.parent; if (renderParent is RenderObject && subtreeDepth > 0) { final Object? parentCreator = renderParent.debugCreator; if (parentCreator is DebugCreator) { @@ -2204,12 +2256,11 @@ mixin WidgetInspectorService { /// if the selected [Element] should be shown in the summary tree otherwise /// returns the first ancestor of the selected [Element] shown in the summary /// tree. - /// - /// If the currently selected [Element] is identical to the [Element] - /// referenced by `previousSelectionId` then the previous [DiagnosticsNode] is - /// reused. - String getSelectedSummaryWidget(String previousSelectionId, String groupName) { - return _safeJsonEncode(_getSelectedSummaryWidget(previousSelectionId, groupName)); + String getSelectedSummaryWidget(String? previousSelectionId, String groupName) { + if (previousSelectionId != null) { + debugPrint('previousSelectionId is deprecated in API'); + } + return _safeJsonEncode(_getSelectedSummaryWidget(null, groupName)); } _Location? _getSelectedSummaryWidgetLocation(String? previousSelectionId) { @@ -2924,7 +2975,7 @@ class _RenderInspectorOverlay extends RenderBox { context.addLayer(_InspectorOverlayLayer( overlayRect: Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height), selection: selection, - rootRenderObject: parent is RenderObject ? parent! as RenderObject : null, + rootRenderObject: parent is RenderObject ? parent! : null, )); } } @@ -3213,14 +3264,14 @@ class _InspectorOverlayLayer extends Layer { /// overlays in the same app (i.e. an storyboard), a selected or candidate /// render object may not belong to this tree. bool _isInInspectorRenderObjectTree(RenderObject child) { - RenderObject? current = child.parent as RenderObject?; + RenderObject? current = child.parent; while (current != null) { // We found the widget inspector render object. if (current is RenderStack && current.lastChild is _RenderInspectorOverlay) { return rootRenderObject == current; } - current = current.parent as RenderObject?; + current = current.parent; } return false; } @@ -3578,7 +3629,6 @@ class InspectorSerializationDelegate implements DiagnosticsSerializationDelegate final Map<String, Object?> result = <String, Object?>{}; final Object? value = node.value; if (_interactive) { - result['objectId'] = service.toId(node, groupName!); result['valueId'] = service.toId(value, groupName!); } if (summaryTree) { @@ -3713,3 +3763,54 @@ class _WidgetFactory { // recognize the annotation. // ignore: library_private_types_in_public_api const _WidgetFactory widgetFactory = _WidgetFactory(); + +/// Does not hold keys from garbage collection. +@visibleForTesting +class WeakMap<K, V> { + Expando<Object> _objects = Expando<Object>(); + + /// Strings, numbers, booleans. + final Map<K, V> _primitives = <K, V>{}; + + bool _isPrimitive(Object? key) { + return key == null || key is String || key is num || key is bool; + } + + /// Returns the value for the given [key] or null if [key] is not in the map + /// or garbage collected. + /// + /// Does not support records to act as keys. + V? operator [](K key){ + if (_isPrimitive(key)) { + return _primitives[key]; + } else { + return _objects[key!] as V?; + } + } + + /// Associates the [key] with the given [value]. + void operator []=(K key, V value){ + if (_isPrimitive(key)) { + _primitives[key] = value; + } else { + _objects[key!] = value; + } + } + + /// Removes the value for the given [key] from the map. + V? remove(K key) { + if (_isPrimitive(key)) { + return _primitives.remove(key); + } else { + final V? result = _objects[key!] as V?; + _objects[key] = null; + return result; + } + } + + /// Removes all pairs from the map. + void clear() { + _objects = Expando<Object>(); + _primitives.clear(); + } +} diff --git a/packages/flutter/lib/src/widgets/widget_span.dart b/packages/flutter/lib/src/widgets/widget_span.dart index e175ee74b67a0..e15ace6655a42 100644 --- a/packages/flutter/lib/src/widgets/widget_span.dart +++ b/packages/flutter/lib/src/widgets/widget_span.dart @@ -4,8 +4,10 @@ import 'dart:ui' as ui show ParagraphBuilder, PlaceholderAlignment; -import 'package:flutter/painting.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; +import 'basic.dart'; import 'framework.dart'; // Examples can assume: @@ -85,6 +87,39 @@ class WidgetSpan extends PlaceholderSpan { ), ); + /// Helper function for extracting [WidgetSpan]s in preorder, from the given + /// [InlineSpan] as a list of widgets. + /// + /// The `textScaleFactor` is the the number of font pixels for each logical + /// pixel. + /// + /// This function is used by [EditableText] and [RichText] so calling it + /// directly is rarely necessary. + static List<Widget> extractFromInlineSpan(InlineSpan span, double textScaleFactor) { + final List<Widget> widgets = <Widget>[]; + int index = 0; + // This assumes an InlineSpan tree's logical order is equivalent to preorder. + span.visitChildren((InlineSpan span) { + if (span is WidgetSpan) { + widgets.add( + _WidgetSpanParentData( + span: span, + child: Semantics( + tagForChildren: PlaceholderSpanIndexSemanticsTag(index++), + child: _AutoScaleInlineWidget(span: span, textScaleFactor: textScaleFactor, child: span.child), + ), + ), + ); + } + assert( + span is WidgetSpan || span is! PlaceholderSpan, + '$span is a PlaceholderSpan but not a WidgetSpan subclass. This is currently not supported.', + ); + return true; + }); + return widgets; + } + /// The widget to embed inline within text. final Widget child; @@ -110,7 +145,6 @@ class WidgetSpan extends PlaceholderSpan { currentDimensions.size.width, currentDimensions.size.height, alignment, - scale: textScaleFactor, baseline: currentDimensions.baseline, baselineOffset: currentDimensions.baselineOffset, ); @@ -212,4 +246,174 @@ class WidgetSpan extends PlaceholderSpan { // from being constructed. return true; } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty<Widget>('widget', child)); + } +} + +// A ParentDataWidget that sets TextParentData.span. +class _WidgetSpanParentData extends ParentDataWidget<TextParentData> { + const _WidgetSpanParentData({ required this.span, required super.child }); + + final WidgetSpan span; + + @override + void applyParentData(RenderObject renderObject) { + final TextParentData parentData = renderObject.parentData! as TextParentData; + parentData.span = span; + } + + @override + Type get debugTypicalAncestorWidgetClass => RenderInlineChildrenContainerDefaults; +} + +// A RenderObjectWidget that automatically applies text scaling on inline +// widgets. +// +// TODO(LongCatIsLooong): this shouldn't happen automatically, at least there +// should be a way to opt out: https://github.com/flutter/flutter/issues/126962 +class _AutoScaleInlineWidget extends SingleChildRenderObjectWidget { + const _AutoScaleInlineWidget({ required this.span, required this.textScaleFactor, required super.child }); + + final WidgetSpan span; + final double textScaleFactor; + + @override + _RenderScaledInlineWidget createRenderObject(BuildContext context) { + return _RenderScaledInlineWidget(span.alignment, span.baseline, textScaleFactor); + } + + @override + void updateRenderObject(BuildContext context, _RenderScaledInlineWidget renderObject) { + renderObject + ..alignment = span.alignment + ..baseline = span.baseline + ..scale = textScaleFactor; + } +} + +class _RenderScaledInlineWidget extends RenderBox with RenderObjectWithChildMixin<RenderBox> { + _RenderScaledInlineWidget(this._alignment, this._baseline, this._scale); + + double get scale => _scale; + double _scale; + set scale(double value) { + if (value == _scale) { + return; + } + assert(value > 0); + assert(value.isFinite); + _scale = value; + markNeedsLayout(); + } + + ui.PlaceholderAlignment get alignment => _alignment; + ui.PlaceholderAlignment _alignment; + set alignment(ui.PlaceholderAlignment value) { + if (_alignment == value) { + return; + } + _alignment = value; + markNeedsLayout(); + } + + TextBaseline? get baseline => _baseline; + TextBaseline? _baseline; + set baseline(TextBaseline? value) { + if (value == _baseline) { + return; + } + _baseline = value; + markNeedsLayout(); + } + + @override + double computeMaxIntrinsicHeight(double width) { + return (child?.computeMaxIntrinsicHeight(width / scale) ?? 0.0) * scale; + } + + @override + double computeMaxIntrinsicWidth(double height) { + return (child?.computeMaxIntrinsicWidth(height / scale) ?? 0.0) * scale; + } + + @override + double computeMinIntrinsicHeight(double width) { + return (child?.computeMinIntrinsicHeight(width / scale) ?? 0.0) * scale; + } + + @override + double computeMinIntrinsicWidth(double height) { + return (child?.computeMinIntrinsicWidth(height / scale) ?? 0.0) * scale; + } + + @override + double? computeDistanceToActualBaseline(TextBaseline baseline) { + return switch (child?.getDistanceToActualBaseline(baseline)) { + null => super.computeDistanceToActualBaseline(baseline), + final double childBaseline => scale * childBaseline, + }; + } + + @override + Size computeDryLayout(BoxConstraints constraints) { + assert(!constraints.hasBoundedHeight); + final Size unscaledSize = child?.computeDryLayout(BoxConstraints(maxWidth: constraints.maxWidth / scale)) ?? Size.zero; + return constraints.constrain(unscaledSize * scale); + } + + @override + void performLayout() { + final RenderBox? child = this.child; + if (child == null) { + return; + } + assert(!constraints.hasBoundedHeight); + // Only constrain the width to the maximum width of the paragraph. + // Leave height unconstrained, which will overflow if expanded past. + child.layout(BoxConstraints(maxWidth: constraints.maxWidth / scale), parentUsesSize: true); + size = constraints.constrain(child.size * scale); + } + + @override + void applyPaintTransform(RenderBox child, Matrix4 transform) { + transform.scale(scale, scale); + } + + @override + void paint(PaintingContext context, Offset offset) { + final RenderBox? child = this.child; + if (child == null) { + layer = null; + return; + } + if (scale == 1.0) { + context.paintChild(child, offset); + layer = null; + return; + } + layer = context.pushTransform( + needsCompositing, + offset, + Matrix4.diagonal3Values(scale, scale, 1.0), + (PaintingContext context, Offset offset) => context.paintChild(child, offset), + oldLayer: layer as TransformLayer? + ); + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + final RenderBox? child = this.child; + if (child == null) { + return false; + } + return result.addWithPaintTransform( + transform: Matrix4.diagonal3Values(scale, scale, 1.0), + position: position, + hitTest: (BoxHitTestResult result, Offset transformedOffset) => child.hitTest(result, position: transformedOffset), + ); + } } diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart index 376f0c6df34f4..fd2cf457c4110 100644 --- a/packages/flutter/lib/widgets.dart +++ b/packages/flutter/lib/widgets.dart @@ -24,6 +24,7 @@ export 'src/widgets/animated_size.dart'; export 'src/widgets/animated_switcher.dart'; export 'src/widgets/annotated_region.dart'; export 'src/widgets/app.dart'; +export 'src/widgets/app_lifecycle_listener.dart'; export 'src/widgets/async.dart'; export 'src/widgets/autocomplete.dart'; export 'src/widgets/autofill.dart'; @@ -37,6 +38,7 @@ export 'src/widgets/container.dart'; export 'src/widgets/context_menu_button_item.dart'; export 'src/widgets/context_menu_controller.dart'; export 'src/widgets/debug.dart'; +export 'src/widgets/decorated_sliver.dart'; export 'src/widgets/default_selection_style.dart'; export 'src/widgets/default_text_editing_shortcuts.dart'; export 'src/widgets/desktop_text_selection_toolbar_layout_delegate.dart'; @@ -149,6 +151,8 @@ export 'src/widgets/ticker_provider.dart'; export 'src/widgets/title.dart'; export 'src/widgets/transitions.dart'; export 'src/widgets/tween_animation_builder.dart'; +export 'src/widgets/two_dimensional_scroll_view.dart'; +export 'src/widgets/two_dimensional_viewport.dart'; export 'src/widgets/undo_history.dart'; export 'src/widgets/unique_widget.dart'; export 'src/widgets/value_listenable_builder.dart'; diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml index 0b39bd5516545..eb0f026887878 100644 --- a/packages/flutter/pubspec.yaml +++ b/packages/flutter/pubspec.yaml @@ -9,10 +9,10 @@ dependencies: # To update these, use "flutter update-packages --force-upgrade". characters: 1.3.0 collection: 1.17.2 - js: 0.6.7 material_color_utilities: 0.5.0 meta: 1.9.1 vector_math: 2.1.4 + web: 0.1.4-beta sky_engine: sdk: flutter @@ -22,11 +22,12 @@ dev_dependencies: flutter_goldens: sdk: flutter fake_async: 1.3.1 - leak_tracker: 6.0.0 + leak_tracker: 7.0.4 + leak_tracker_testing: 1.0.0 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -35,13 +36,14 @@ dev_dependencies: crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" intl: 0.18.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -61,14 +63,14 @@ dev_dependencies: stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test: 1.24.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test: 1.24.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 7066 +# PUBSPEC CHECKSUM: 155f diff --git a/packages/flutter/test/animation/animation_sheet_test.dart b/packages/flutter/test/animation/animation_sheet_test.dart index 0c14e3f82d4bd..4c26669abec4f 100644 --- a/packages/flutter/test/animation/animation_sheet_test.dart +++ b/packages/flutter/test/animation/animation_sheet_test.dart @@ -20,60 +20,6 @@ void main() { * because [matchesGoldenFile] does not use Skia Gold in its native package. */ - testWidgetsWithLeakTracking('correctly records frames using display', (WidgetTester tester) async { - final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size); - - await tester.pumpFrames( - builder.record( - const _DecuplePixels(Duration(seconds: 1)), - ), - const Duration(milliseconds: 200), - const Duration(milliseconds: 100), - ); - - await tester.pumpFrames( - builder.record( - const _DecuplePixels(Duration(seconds: 1)), - recording: false, - ), - const Duration(milliseconds: 200), - const Duration(milliseconds: 100), - ); - - await tester.pumpFrames( - builder.record( - const _DecuplePixels(Duration(seconds: 1)), - ), - const Duration(milliseconds: 400), - const Duration(milliseconds: 100), - ); - - // This test verifies deprecated methods. - final Widget display = await builder.display(); // ignore: deprecated_member_use - await tester.binding.setSurfaceSize(builder.sheetSize()); // ignore: deprecated_member_use - await tester.pumpWidget(display); - - await expectLater(find.byWidget(display), matchesGoldenFile('test.animation_sheet_builder.records.png')); - }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 - - testWidgetsWithLeakTracking('correctly wraps a row', (WidgetTester tester) async { - final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size); - - const Duration duration = Duration(seconds: 2); - await tester.pumpFrames( - builder.record(const _DecuplePixels(duration)), - duration, - const Duration(milliseconds: 200), - ); - - // This test verifies deprecated methods. - final Widget display = await builder.display(); // ignore: deprecated_member_use - await tester.binding.setSurfaceSize(builder.sheetSize(maxWidth: 80)); // ignore: deprecated_member_use - await tester.pumpWidget(display); - - await expectLater(find.byWidget(display), matchesGoldenFile('test.animation_sheet_builder.wraps.png')); - }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56001 - testWidgetsWithLeakTracking('correctly records frames using collate', (WidgetTester tester) async { final AnimationSheetBuilder builder = AnimationSheetBuilder(frameSize: _DecuplePixels.size); diff --git a/packages/flutter/test/cupertino/adaptive_text_selection_toolbar_test.dart b/packages/flutter/test/cupertino/adaptive_text_selection_toolbar_test.dart index 0813e90811002..d0c6c1a57855f 100644 --- a/packages/flutter/test/cupertino/adaptive_text_selection_toolbar_test.dart +++ b/packages/flutter/test/cupertino/adaptive_text_selection_toolbar_test.dart @@ -8,6 +8,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../widgets/clipboard_utils.dart'; +import '../widgets/live_text_utils.dart'; void main() { final MockClipboard mockClipboard = MockClipboard(); @@ -166,6 +167,7 @@ void main() { onCut: () {}, onPaste: () {}, onSelectAll: () {}, + onLiveTextInput: () {}, ), ), )); @@ -178,13 +180,16 @@ void main() { switch (defaultTargetPlatform) { case TargetPlatform.android: + expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(5)); case TargetPlatform.fuchsia: + expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(5)); case TargetPlatform.iOS: - expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(4)); + expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(5)); + expect(findLiveTextButton(), findsOneWidget); case TargetPlatform.macOS: case TargetPlatform.linux: case TargetPlatform.windows: - expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNWidgets(4)); + expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNWidgets(5)); } }, skip: kIsWeb, // [intended] on web the browser handles the context menu. diff --git a/packages/flutter/test/cupertino/dialog_test.dart b/packages/flutter/test/cupertino/dialog_test.dart index 7f19ba217c06f..64b15d38ca4ed 100644 --- a/packages/flutter/test/cupertino/dialog_test.dart +++ b/packages/flutter/test/cupertino/dialog_test.dart @@ -1180,9 +1180,10 @@ void main() { expect(tester.getRect(find.byType(Placeholder)), placeholderRectWithoutInsets.translate(10, 10)); }); - testWidgets('Default cupertino dialog golden', (WidgetTester tester) async { + testWidgets('Material2 - Default cupertino dialog golden', (WidgetTester tester) async { await tester.pumpWidget( createAppWithButtonThatLaunchesDialog( + useMaterial3: false, dialogBuilder: (BuildContext context) { return MediaQuery( data: MediaQuery.of(context).copyWith(textScaleFactor: 3.0), @@ -1206,7 +1207,38 @@ void main() { await expectLater( find.byType(CupertinoAlertDialog), - matchesGoldenFile('dialog_test.cupertino.default.png'), + matchesGoldenFile('m2_dialog_test.cupertino.default.png'), + ); + }); + + testWidgets('Material3 - Default cupertino dialog golden', (WidgetTester tester) async { + await tester.pumpWidget( + createAppWithButtonThatLaunchesDialog( + useMaterial3: true, + dialogBuilder: (BuildContext context) { + return MediaQuery( + data: MediaQuery.of(context).copyWith(textScaleFactor: 3.0), + child: const RepaintBoundary( + child: CupertinoAlertDialog( + title: Text('Title'), + content: Text('text'), + actions: <Widget>[ + CupertinoDialogAction(child: Text('No')), + CupertinoDialogAction(child: Text('OK')), + ], + ), + ), + ); + }, + ), + ); + + await tester.tap(find.text('Go')); + await tester.pumpAndSettle(); + + await expectLater( + find.byType(CupertinoAlertDialog), + matchesGoldenFile('m3_dialog_test.cupertino.default.png'), ); }); @@ -1523,8 +1555,10 @@ RenderBox findScrollableActionsSectionRenderBox(WidgetTester tester) { Widget createAppWithButtonThatLaunchesDialog({ required WidgetBuilder dialogBuilder, + bool? useMaterial3, }) { return MaterialApp( + theme: ThemeData(useMaterial3: useMaterial3), home: Material( child: Center( child: Builder(builder: (BuildContext context) { diff --git a/packages/flutter/test/cupertino/nav_bar_transition_test.dart b/packages/flutter/test/cupertino/nav_bar_transition_test.dart index 1f844ddcacd09..cdefa99dc59b5 100644 --- a/packages/flutter/test/cupertino/nav_bar_transition_test.dart +++ b/packages/flutter/test/cupertino/nav_bar_transition_test.dart @@ -145,11 +145,17 @@ void main() { // place. expect( tester.getTopLeft(flying(tester, find.text('Page 1')).first), - const Offset(342.33420100808144, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 342.547737105096302912 : 342.33420100808144, + 13.5, + ), ); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).last), - const Offset(342.33420100808144, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 342.547737105096302912 : 342.33420100808144, + 13.5, + ), ); }); @@ -166,11 +172,17 @@ void main() { // Same as LTR but more to the right now. expect( tester.getTopLeft(flying(tester, find.text('Page 1')).first), - const Offset(357.66579899191856, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 357.912261979376353338 : 357.66579899191856, + 13.5, + ), ); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).last), - const Offset(357.66579899191856, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 357.912261979376353338 : 357.66579899191856, + 13.5, + ), ); }); @@ -356,9 +368,13 @@ void main() { final RenderParagraph bottomMiddle = tester.renderObject(flying(tester, find.text('Page 1')).first); expect(bottomMiddle.text.style!.color, const Color(0xff000306)); + expect( tester.getTopLeft(flying(tester, find.text('Page 1')).first), - const Offset(342.33420100808144, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 342.547737105096302912 : 342.33420100808144, + 13.5, + ), ); // The top back label is styled exactly the same way. But the opacity tweens @@ -368,7 +384,10 @@ void main() { expect(topBackLabel.text.style!.color, const Color(0xff000306)); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).last), - const Offset(342.33420100808144, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 342.547737105096302912 : 342.33420100808144, + 13.5, + ), ); } @@ -403,7 +422,10 @@ void main() { expect(bottomMiddle.text.style!.color, const Color(0xff000306)); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).first), - const Offset(357.66579899191856, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 357.912261979376353338 : 357.66579899191856, + 13.5, + ), ); // The top back label is styled exactly the same way. But the opacity tweens @@ -413,7 +435,10 @@ void main() { expect(topBackLabel.text.style!.color, const Color(0xff000306)); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).last), - const Offset(357.66579899191856, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 357.912261979376353338 : 357.66579899191856, + 13.5, + ), ); } @@ -711,11 +736,17 @@ void main() { ); // Come in from the right and fade in. checkOpacity(tester, backChevron, 0.0); - expect(tester.getTopLeft(backChevron), const Offset(88.04496401548386, 7.0)); + expect(tester.getTopLeft(backChevron), const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 87.2460581221158690823 : 88.04496401548386, + 7.0, + )); await tester.pump(const Duration(milliseconds: 200)); checkOpacity(tester, backChevron, 0.09497911669313908); - expect(tester.getTopLeft(backChevron), const Offset(31.055883467197418, 7.0)); + expect(tester.getTopLeft(backChevron), const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 30.8718595298545324113 : 31.055883467197418, + 7.0, + )); }); testWidgets('First appearance of back chevron fades in from the left in RTL', (WidgetTester tester) async { @@ -753,14 +784,20 @@ void main() { checkOpacity(tester, backChevron, 0.0); expect( tester.getTopRight(backChevron), - const Offset(685.9550359845161, 7.0), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 687.163941725296126606 : 685.9550359845161, + 7.0, + ), ); await tester.pump(const Duration(milliseconds: 200)); checkOpacity(tester, backChevron, 0.09497911669313908); expect( tester.getTopRight(backChevron), - const Offset(742.9441165328026, 7.0), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 743.538140317557690651 : 742.9441165328026, + 7.0, + ), ); }); @@ -862,14 +899,20 @@ void main() { checkOpacity(tester, flying(tester, find.text('custom')), 0.9280824661254883); expect( tester.getTopLeft(flying(tester, find.text('custom'))), - const Offset(684.0, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 684.459999084472656250 : 684.0, + 13.5, + ), ); await tester.pump(const Duration(milliseconds: 150)); checkOpacity(tester, flying(tester, find.text('custom')), 0.0); expect( tester.getTopLeft(flying(tester, find.text('custom'))), - const Offset(684.0, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 684.459999084472656250 : 684.0, + 13.5, + ), ); }); @@ -898,14 +941,20 @@ void main() { checkOpacity(tester, flying(tester, find.text('Page 1')), 0.7952219992876053); expect( tester.getTopLeft(flying(tester, find.text('Page 1'))), - const Offset(41.71033692359924, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 41.3003370761871337891 : 41.71033692359924, + 13.5, + ), ); await tester.pump(const Duration(milliseconds: 200)); checkOpacity(tester, flying(tester, find.text('Page 1')), 0.0); expect( tester.getTopLeft(flying(tester, find.text('Page 1'))), - const Offset(-258.2321922779083, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? -258.642192125320434570 : -258.2321922779083, + 13.5, + ), ); }); @@ -935,7 +984,10 @@ void main() { checkOpacity(tester, flying(tester, find.text('Page 1')), 0.7952219992876053); expect( tester.getTopRight(flying(tester, find.text('Page 1'))), - const Offset(758.2896630764008, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 758.699662923812866211 : 758.2896630764008, + 13.5, + ), ); await tester.pump(const Duration(milliseconds: 200)); @@ -943,7 +995,10 @@ void main() { expect( tester.getTopRight(flying(tester, find.text('Page 1'))), // >1000. It's now off the screen. - const Offset(1058.2321922779083, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 1058.64219212532043457 : 1058.2321922779083, + 13.5, + ), ); }); @@ -963,25 +1018,39 @@ void main() { checkOpacity(tester, flying(tester, find.text('Page 1')).first, 0.9280824661254883); checkOpacity(tester, flying(tester, find.text('Page 1')).last, 0.0); + expect( tester.getTopLeft(flying(tester, find.text('Page 1')).first), - const Offset(16.926069676876068, 52.73951627314091), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 16.9155227761479522997 : 16.926069676876068, + 52.73951627314091, + ), ); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).last), - const Offset(16.926069676876068, 52.73951627314091), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 16.9155227761479522997 : 16.926069676876068, + 52.73951627314091, + ), ); await tester.pump(const Duration(milliseconds: 200)); checkOpacity(tester, flying(tester, find.text('Page 1')).first, 0.0); checkOpacity(tester, flying(tester, find.text('Page 1')).last, 0.4604858811944723); + expect( tester.getTopLeft(flying(tester, find.text('Page 1')).first), - const Offset(43.92089730501175, 22.49655644595623), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 43.6029094262710827934 : 43.92089730501175, + 22.49655644595623, + ), ); expect( tester.getTopLeft(flying(tester, find.text('Page 1')).last), - const Offset(43.92089730501175, 22.49655644595623), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 43.6029094262710827934 : 43.92089730501175, + 22.49655644595623, + ), ); }); @@ -1003,11 +1072,17 @@ void main() { checkOpacity(tester, flying(tester, find.text('Back')), 0.0); expect( tester.getTopLeft(flying(tester, find.text('A title too long to fit'))), - const Offset(16.926069676876068, 52.73951627314091), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 16.9155227761479522997 : 16.926069676876068, + 52.73951627314091, + ), ); expect( tester.getTopLeft(flying(tester, find.text('Back'))), - const Offset(16.926069676876068, 52.73951627314091), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 16.9155227761479522997 : 16.926069676876068, + 52.73951627314091, + ), ); await tester.pump(const Duration(milliseconds: 200)); @@ -1015,11 +1090,17 @@ void main() { checkOpacity(tester, flying(tester, find.text('Back')), 0.4604858811944723); expect( tester.getTopLeft(flying(tester, find.text('A title too long to fit'))), - const Offset(43.92089730501175, 22.49655644595623), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 43.6029094262710827934 : 43.92089730501175, + 22.49655644595623, + ), ); expect( tester.getTopLeft(flying(tester, find.text('Back'))), - const Offset(43.92089730501175, 22.49655644595623), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 43.6029094262710827934 : 43.92089730501175, + 22.49655644595623, + ), ); }); @@ -1075,7 +1156,10 @@ void main() { checkOpacity(tester, flying(tester, find.text('Page 2')), 0.0); expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(739.7103369235992, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 739.940336465835571289 : 739.7103369235992, + 13.5, + ), ); await tester.pump(const Duration(milliseconds: 150)); @@ -1083,7 +1167,10 @@ void main() { checkOpacity(tester, flying(tester, find.text('Page 2')), 0.29867843724787235); expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(504.65044379234314, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 504.880443334579467773 : 504.65044379234314, + 13.5, + ), ); }); @@ -1125,7 +1212,10 @@ void main() { checkOpacity(tester, flying(tester, find.text('Page 2')), 0.0); expect( tester.getTopRight(flying(tester, find.text('Page 2'))), - const Offset(60.28966307640076, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 60.0596635341644287109 : 60.28966307640076, + 13.5, + ), ); await tester.pump(const Duration(milliseconds: 150)); @@ -1133,7 +1223,10 @@ void main() { checkOpacity(tester, flying(tester, find.text('Page 2')), 0.29867843724787235); expect( tester.getTopRight(flying(tester, find.text('Page 2'))), - const Offset(295.34955620765686, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 295.119556665420532227 : 295.34955620765686, + 13.5, + ), ); }); @@ -1257,7 +1350,10 @@ void main() { // Page 2, which is the middle of the top route, start to fly back to the right. expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(353.5802058875561, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 353.810205429792404175 : 353.5802058875561, + 13.5, + ), ); // Page 1 is in transition in 2 places. Once as the top back label and once @@ -1272,12 +1368,18 @@ void main() { // Transition continues. expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(655.2055835723877, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 655.435583114624023438 : 655.2055835723877, + 13.5, + ), ); await tester.pump(const Duration(milliseconds: 50)); expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(749.6335566043854, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 749.863556146621704102 : 749.6335566043854, + 13.5, + ), ); await tester.pump(const Duration(milliseconds: 500)); @@ -1319,7 +1421,10 @@ void main() { // Page 2, which is the middle of the top route, start to fly back to the right. expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(353.5802058875561, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 353.810205429792404175 : 353.5802058875561, + 13.5, + ), ); await gesture.up(); @@ -1328,12 +1433,18 @@ void main() { // Transition continues from the point we let off. expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(353.5802058875561, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 353.810205429792404175 : 353.5802058875561, + 13.5, + ), ); await tester.pump(const Duration(milliseconds: 50)); expect( tester.getTopLeft(flying(tester, find.text('Page 2'))), - const Offset(350.0011436641216, 13.5), + const Offset( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 350.231143206357955933 : 350.0011436641216, + 13.5, + ), ); // Finish the snap back animation. diff --git a/packages/flutter/test/cupertino/route_test.dart b/packages/flutter/test/cupertino/route_test.dart index 804ef677a570c..f3b66c8a72206 100644 --- a/packages/flutter/test/cupertino/route_test.dart +++ b/packages/flutter/test/cupertino/route_test.dart @@ -161,7 +161,8 @@ void main() { // Also shows the previous page's title next to the back button. expect(find.widgetWithText(CupertinoButton, 'An iPod'), findsOneWidget); // 3 paddings + 1 test font character at font size 34.0. - expect(tester.getTopLeft(find.text('An iPod')).dx, 8.0 + 4.0 + 34.0 + 6.0); + // The epsilon is needed since the text theme has a negative letter spacing thus. + expect(tester.getTopLeft(find.text('An iPod')).dx, moreOrLessEquals(8.0 + 4.0 + 34.0 + 6.0, epsilon: 0.5)); }); testWidgets('Previous title is correct on first transition frame', (WidgetTester tester) async { @@ -267,7 +268,8 @@ void main() { // from An iPod to Back (since An Internet communicator is too long to // fit in the back button). expect(find.widgetWithText(CupertinoButton, 'Back'), findsOneWidget); - expect(tester.getTopLeft(find.text('Back')).dx, 8.0 + 4.0 + 34.0 + 6.0); + // The epsilon is needed since the text theme has a negative letter spacing thus. + expect(tester.getTopLeft(find.text('Back')).dx, moreOrLessEquals(8.0 + 4.0 + 34.0 + 6.0, epsilon: 0.5)); }); testWidgets('Back swipe dismiss interrupted by route push', (WidgetTester tester) async { diff --git a/packages/flutter/test/cupertino/scrollbar_test.dart b/packages/flutter/test/cupertino/scrollbar_test.dart index 6d47d2d87acd4..84ff11748ce3a 100644 --- a/packages/flutter/test/cupertino/scrollbar_test.dart +++ b/packages/flutter/test/cupertino/scrollbar_test.dart @@ -400,14 +400,14 @@ void main() { }, ); - testWidgets('When isAlwaysShown is true, must pass a controller or find PrimaryScrollController', (WidgetTester tester) async { + testWidgets('When thumbVisibility is true, must pass a controller or find PrimaryScrollController', (WidgetTester tester) async { Widget viewWithScroll() { return const Directionality( textDirection: TextDirection.ltr, child: MediaQuery( data: MediaQueryData(), child: CupertinoScrollbar( - isAlwaysShown: true, + thumbVisibility: true, child: SingleChildScrollView( child: SizedBox( width: 4000.0, @@ -425,7 +425,7 @@ void main() { }, ); - testWidgets('When isAlwaysShown is true, must pass a controller or find PrimaryScrollController that is attached to a scroll view', (WidgetTester tester) async { + testWidgets('When thumbVisibility is true, must pass a controller or find PrimaryScrollController that is attached to a scroll view', (WidgetTester tester) async { final ScrollController controller = ScrollController(); Widget viewWithScroll() { return Directionality( @@ -434,7 +434,7 @@ void main() { data: const MediaQueryData(), child: CupertinoScrollbar( controller: controller, - isAlwaysShown: true, + thumbVisibility: true, child: const SingleChildScrollView( child: SizedBox( width: 4000.0, @@ -526,7 +526,7 @@ void main() { expect(find.byType(CupertinoScrollbar), paints..rrect()); }); - testWidgets('On first render with isAlwaysShown: true, the thumb shows with PrimaryScrollController', (WidgetTester tester) async { + testWidgets('On first render with thumbVisibility: true, the thumb shows with PrimaryScrollController', (WidgetTester tester) async { final ScrollController controller = ScrollController(); Widget viewWithScroll() { return Directionality( @@ -538,7 +538,7 @@ void main() { child: Builder( builder: (BuildContext context) { return const CupertinoScrollbar( - isAlwaysShown: true, + thumbVisibility: true, child: SingleChildScrollView( primary: true, child: SizedBox( @@ -560,7 +560,7 @@ void main() { }, ); - testWidgets('On first render with isAlwaysShown: true, the thumb shows', (WidgetTester tester) async { + testWidgets('On first render with thumbVisibility: true, the thumb shows', (WidgetTester tester) async { final ScrollController controller = ScrollController(); Widget viewWithScroll() { return Directionality( @@ -570,7 +570,7 @@ void main() { child: PrimaryScrollController( controller: controller, child: CupertinoScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: controller, child: const SingleChildScrollView( child: SizedBox( @@ -593,7 +593,7 @@ void main() { expect(find.byType(CupertinoScrollbar), paints..rrect()); }); - testWidgets('On first render with isAlwaysShown: false, the thumb is hidden', (WidgetTester tester) async { + testWidgets('On first render with thumbVisibility: false, the thumb is hidden', (WidgetTester tester) async { final ScrollController controller = ScrollController(); Widget viewWithScroll() { return Directionality( @@ -621,9 +621,9 @@ void main() { expect(find.byType(CupertinoScrollbar), isNot(paints..rect())); }); - testWidgets('With isAlwaysShown: true, fling a scroll. While it is still scrolling, set isAlwaysShown: false. The thumb should not fade out until the scrolling stops.', (WidgetTester tester) async { + testWidgets('With thumbVisibility: true, fling a scroll. While it is still scrolling, set thumbVisibility: false. The thumb should not fade out until the scrolling stops.', (WidgetTester tester) async { final ScrollController controller = ScrollController(); - bool isAlwaysShown = true; + bool thumbVisibility = true; Widget viewWithScroll() { return StatefulBuilder( builder: (BuildContext context, StateSetter setState) { @@ -634,7 +634,7 @@ void main() { child: Stack( children: <Widget>[ CupertinoScrollbar( - isAlwaysShown: isAlwaysShown, + thumbVisibility: thumbVisibility, controller: controller, child: SingleChildScrollView( controller: controller, @@ -649,10 +649,10 @@ void main() { child: CupertinoButton( onPressed: () { setState(() { - isAlwaysShown = !isAlwaysShown; + thumbVisibility = !thumbVisibility; }); }, - child: const Text('change isAlwaysShown'), + child: const Text('change thumbVisibility'), ), ), ], @@ -679,10 +679,10 @@ void main() { ); testWidgets( - 'With isAlwaysShown: false, set isAlwaysShown: true. The thumb should be always shown directly', + 'With thumbVisibility: false, set thumbVisibility: true. The thumb should be always shown directly', (WidgetTester tester) async { final ScrollController controller = ScrollController(); - bool isAlwaysShown = false; + bool thumbVisibility = false; Widget viewWithScroll() { return StatefulBuilder( builder: (BuildContext context, StateSetter setState) { @@ -693,7 +693,7 @@ void main() { child: Stack( children: <Widget>[ CupertinoScrollbar( - isAlwaysShown: isAlwaysShown, + thumbVisibility: thumbVisibility, controller: controller, child: SingleChildScrollView( controller: controller, @@ -708,10 +708,10 @@ void main() { child: CupertinoButton( onPressed: () { setState(() { - isAlwaysShown = !isAlwaysShown; + thumbVisibility = !thumbVisibility; }); }, - child: const Text('change isAlwaysShown'), + child: const Text('change thumbVisibility'), ), ), ], @@ -733,11 +733,11 @@ void main() { ); testWidgets( - 'With isAlwaysShown: false, fling a scroll. While it is still scrolling, set isAlwaysShown: true. ' + 'With thumbVisibility: false, fling a scroll. While it is still scrolling, set thumbVisibility: true. ' 'The thumb should not fade even after the scrolling stops', (WidgetTester tester) async { final ScrollController controller = ScrollController(); - bool isAlwaysShown = false; + bool thumbVisibility = false; Widget viewWithScroll() { return StatefulBuilder( builder: (BuildContext context, StateSetter setState) { @@ -748,7 +748,7 @@ void main() { child: Stack( children: <Widget>[ CupertinoScrollbar( - isAlwaysShown: isAlwaysShown, + thumbVisibility: thumbVisibility, controller: controller, child: SingleChildScrollView( controller: controller, @@ -763,10 +763,10 @@ void main() { child: CupertinoButton( onPressed: () { setState(() { - isAlwaysShown = !isAlwaysShown; + thumbVisibility = !thumbVisibility; }); }, - child: const Text('change isAlwaysShown'), + child: const Text('change thumbVisibility'), ), ), ], @@ -800,11 +800,11 @@ void main() { ); testWidgets( - 'Toggling isAlwaysShown while not scrolling fades the thumb in/out. ' + 'Toggling thumbVisibility while not scrolling fades the thumb in/out. ' 'This works even when you have never scrolled at all yet', (WidgetTester tester) async { final ScrollController controller = ScrollController(); - bool isAlwaysShown = true; + bool thumbVisibility = true; Widget viewWithScroll() { return StatefulBuilder( builder: (BuildContext context, StateSetter setState) { @@ -815,7 +815,7 @@ void main() { child: Stack( children: <Widget>[ CupertinoScrollbar( - isAlwaysShown: isAlwaysShown, + thumbVisibility: thumbVisibility, controller: controller, child: SingleChildScrollView( controller: controller, @@ -830,10 +830,10 @@ void main() { child: CupertinoButton( onPressed: () { setState(() { - isAlwaysShown = !isAlwaysShown; + thumbVisibility = !thumbVisibility; }); }, - child: const Text('change isAlwaysShown'), + child: const Text('change thumbVisibility'), ), ), ], @@ -1011,7 +1011,7 @@ void main() { child: MediaQuery( data: const MediaQueryData(), child: CupertinoScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: SingleChildScrollView( controller: scrollController, @@ -1155,7 +1155,7 @@ void main() { home: PrimaryScrollController( controller: scrollController, child: CupertinoScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), @@ -1266,7 +1266,7 @@ void main() { home: PrimaryScrollController( controller: scrollController, child: CupertinoScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, scrollbarOrientation: ScrollbarOrientation.left, child: const SingleChildScrollView( diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index 5e869ca25e56d..ab0b3fbf86dad 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -21,6 +21,7 @@ import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; import '../widgets/clipboard_utils.dart'; import '../widgets/editable_text_utils.dart' show OverflowWidgetTextEditingController; +import '../widgets/live_text_utils.dart'; import '../widgets/semantics_tester.dart'; // On web, the context menu (aka toolbar) is provided by the browser. @@ -199,12 +200,56 @@ void main() { Offset textOffsetToPosition(WidgetTester tester, int offset) => textOffsetToBottomLeftPosition(tester, offset) + const Offset(kIsWeb ? 1 : 0, -2); setUp(() async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, mockClipboard.handleMethodCall); EditableText.debugDeterministicCursor = false; // Fill the clipboard so that the Paste option is available in the text // selection menu. await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); }); + + testWidgets( + 'Live Text button shows and hides correctly when LiveTextStatus changes', + (WidgetTester tester) async { + final LiveTextInputTester liveTextInputTester = LiveTextInputTester(); + addTearDown(liveTextInputTester.dispose); + + final TextEditingController controller = TextEditingController(text: ''); + const Key key = ValueKey<String>('TextField'); + final FocusNode focusNode = FocusNode(); + final Widget app = MaterialApp( + theme: ThemeData(platform: TargetPlatform.iOS), + home: Scaffold( + body: Center( + child: CupertinoTextField( + key: key, + controller: controller, + focusNode: focusNode, + ), + ), + ), + ); + + liveTextInputTester.mockLiveTextInputEnabled = true; + await tester.pumpWidget(app); + focusNode.requestFocus(); + await tester.pumpAndSettle(); + + final Finder textFinder = find.byType(EditableText); + await tester.longPress(textFinder); + await tester.pumpAndSettle(); + expect( + findLiveTextButton(), + kIsWeb ? findsNothing : findsOneWidget, + ); + + liveTextInputTester.mockLiveTextInputEnabled = false; + await tester.longPress(textFinder); + await tester.pumpAndSettle(); + expect(findLiveTextButton(), findsNothing); + }, + ); + testWidgets('can use the desktop cut/copy/paste buttons on Mac', (WidgetTester tester) async { final TextEditingController controller = TextEditingController( text: 'blah1 blah2', @@ -1545,7 +1590,7 @@ void main() { Text text = tester.widget<Text>(find.text('Paste')); expect(text.style!.color!.value, CupertinoColors.black.value); - expect(text.style!.fontSize, 14); + expect(text.style!.fontSize, 15); expect(text.style!.letterSpacing, -0.15); expect(text.style!.fontWeight, FontWeight.w400); @@ -1577,7 +1622,7 @@ void main() { text = tester.widget<Text>(find.text('Paste')); // The toolbar buttons' text are still the same style. expect(text.style!.color!.value, CupertinoColors.white.value); - expect(text.style!.fontSize, 14); + expect(text.style!.fontSize, 15); expect(text.style!.letterSpacing, -0.15); expect(text.style!.fontWeight, FontWeight.w400); }, skip: isContextMenuProvidedByPlatform); // [intended] only applies to platforms where we supply the context menu. @@ -6537,7 +6582,7 @@ void main() { topMatcher: moreOrLessEquals(bottomLeftSelectionPosition.dy + 8, epsilon: 0.01), leftMatcher: moreOrLessEquals(8), rightMatcher: lessThanOrEqualTo(400 - 8), - bottomMatcher: moreOrLessEquals(bottomLeftSelectionPosition.dy + 8 + 43, epsilon: 0.01), + bottomMatcher: moreOrLessEquals(bottomLeftSelectionPosition.dy + 8 + 45, epsilon: 0.01), ), ), ); @@ -6597,7 +6642,7 @@ void main() { pathMatcher: PathBoundsMatcher( topMatcher: moreOrLessEquals(bottomLeftSelectionPosition.dy + 8, epsilon: 0.01), rightMatcher: moreOrLessEquals(400.0 - 8), - bottomMatcher: moreOrLessEquals(bottomLeftSelectionPosition.dy + 8 + 43, epsilon: 0.01), + bottomMatcher: moreOrLessEquals(bottomLeftSelectionPosition.dy + 8 + 45, epsilon: 0.01), leftMatcher: greaterThanOrEqualTo(8), ), ), @@ -6650,7 +6695,7 @@ void main() { paints..clipPath( pathMatcher: PathBoundsMatcher( bottomMatcher: moreOrLessEquals(bottomLeftSelectionPosition.dy - 8 - lineHeight, epsilon: 0.01), - topMatcher: moreOrLessEquals(bottomLeftSelectionPosition.dy - 8 - lineHeight - 43, epsilon: 0.01), + topMatcher: moreOrLessEquals(bottomLeftSelectionPosition.dy - 8 - lineHeight - 45, epsilon: 0.01), rightMatcher: lessThanOrEqualTo(400 - 8), leftMatcher: greaterThanOrEqualTo(8), ), @@ -6719,7 +6764,7 @@ void main() { paints..clipPath( pathMatcher: PathBoundsMatcher( bottomMatcher: moreOrLessEquals(selectionPosition.dy - 8 - lineHeight, epsilon: 0.01), - topMatcher: moreOrLessEquals(selectionPosition.dy - 8 - lineHeight - 43, epsilon: 0.01), + topMatcher: moreOrLessEquals(selectionPosition.dy - 8 - lineHeight - 45, epsilon: 0.01), rightMatcher: lessThanOrEqualTo(400 - 8), leftMatcher: greaterThanOrEqualTo(8), ), @@ -6792,7 +6837,7 @@ void main() { paints..clipPath( pathMatcher: PathBoundsMatcher( bottomMatcher: moreOrLessEquals(selectionPosition.dy - 8 - lineHeight, epsilon: 0.01), - topMatcher: moreOrLessEquals(selectionPosition.dy - 8 - lineHeight - 43, epsilon: 0.01), + topMatcher: moreOrLessEquals(selectionPosition.dy - 8 - lineHeight - 45, epsilon: 0.01), rightMatcher: lessThanOrEqualTo(400 - 8), leftMatcher: greaterThanOrEqualTo(8), ), diff --git a/packages/flutter/test/cupertino/text_selection_test.dart b/packages/flutter/test/cupertino/text_selection_test.dart index ed65377857175..b679eb6d46f00 100644 --- a/packages/flutter/test/cupertino/text_selection_test.dart +++ b/packages/flutter/test/cupertino/text_selection_test.dart @@ -60,18 +60,6 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); final MockClipboard mockClipboard = MockClipboard(); - // Returns true iff the button is visually enabled. - bool appearsEnabled(WidgetTester tester, String text) { - final CupertinoButton button = tester.widget<CupertinoButton>( - find.ancestor( - of: find.text(text), - matching: find.byType(CupertinoButton), - ), - ); - // Disabled buttons have no opacity change when pressed. - return button.pressedOpacity! < 1.0; - } - List<TextSelectionPoint> globalize(Iterable<TextSelectionPoint> points, RenderBox box) { return points.map<TextSelectionPoint>((TextSelectionPoint point) { return TextSelectionPoint( @@ -191,6 +179,15 @@ void main() { }); group('Text selection menu overflow (iOS)', () { + Finder findOverflowNextButton() => find.byWidgetPredicate((Widget widget) => + widget is CustomPaint && + '${widget.painter?.runtimeType}' == '_RightCupertinoChevronPainter', + ); + Finder findOverflowBackButton() => find.byWidgetPredicate((Widget widget) => + widget is CustomPaint && + '${widget.painter?.runtimeType}' == '_LeftCupertinoChevronPainter', + ); + testWidgets('All menu items show when they fit.', (WidgetTester tester) async { final TextEditingController controller = TextEditingController(text: 'abc def ghi'); await tester.pumpWidget(CupertinoApp( @@ -216,8 +213,8 @@ void main() { expect(find.text('Copy'), findsNothing); expect(find.text('Paste'), findsNothing); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsNothing); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsNothing); // Long press on an empty space to show the selection menu. await tester.longPressAt(textOffsetToPosition(tester, 4)); @@ -226,8 +223,8 @@ void main() { expect(find.text('Copy'), findsNothing); expect(find.text('Paste'), findsOneWidget); expect(find.text('Select All'), findsOneWidget); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsNothing); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsNothing); // Double tap to select a word and show the full selection menu. final Offset textOffset = textOffsetToPosition(tester, 1); @@ -241,8 +238,8 @@ void main() { expect(find.text('Copy'), findsOneWidget); expect(find.text('Paste'), findsOneWidget); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsNothing); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsNothing); }, skip: isBrowser, // [intended] We do not use Flutter-rendered context menu on the Web. variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }), @@ -273,8 +270,8 @@ void main() { expect(find.text('Copy'), findsNothing); expect(find.text('Paste'), findsNothing); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsNothing); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsNothing); // Double tap to select a word and show the selection menu. final Offset textOffset = textOffsetToPosition(tester, 1); @@ -288,32 +285,29 @@ void main() { expect(find.text('Copy'), findsOneWidget); expect(find.text('Paste'), findsNothing); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '▶'), true); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsOneWidget); - // Tapping the next button shows the overflowing button. - await tester.tap(find.text('▶')); + // Tapping the next button shows the overflowing button and the next + // button is hidden as the last page is shown. + await tester.tapAt(tester.getCenter(findOverflowNextButton())); await tester.pumpAndSettle(); expect(find.text('Cut'), findsNothing); expect(find.text('Copy'), findsNothing); expect(find.text('Paste'), findsOneWidget); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsOneWidget); - expect(appearsEnabled(tester, '◀'), true); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '▶'), false); + expect(findOverflowBackButton(), findsOneWidget); + expect(findOverflowNextButton(), findsNothing); - // Tapping the back button shows the first page again. - await tester.tap(find.text('◀')); + // Tapping the back button shows the first page again with the next button. + await tester.tapAt(tester.getCenter(findOverflowBackButton())); await tester.pumpAndSettle(); expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); expect(find.text('Paste'), findsNothing); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '▶'), true); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsOneWidget); }, skip: isBrowser, // [intended] We do not use Flutter-rendered context menu on the Web. variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }), @@ -341,13 +335,13 @@ void main() { )); // Initially, the menu isn't shown at all. - expect(find.byType(CupertinoButton), findsNothing); + expect(find.byType(CupertinoTextSelectionToolbarButton), findsNothing); expect(find.text('Cut'), findsNothing); expect(find.text('Copy'), findsNothing); expect(find.text('Paste'), findsNothing); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsNothing); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsNothing); // Double tap to select a word and show the selection menu. final Offset textOffset = textOffsetToPosition(tester, 1); @@ -357,65 +351,58 @@ void main() { await tester.pumpAndSettle(); // Only the first button fits, and a next button is shown. - expect(find.byType(CupertinoButton), findsNWidgets(2)); + expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(2)); expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsNothing); expect(find.text('Paste'), findsNothing); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '▶'), true); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsOneWidget); // Tapping the next button shows Copy. - await tester.tap(find.text('▶')); + await tester.tapAt(tester.getCenter(findOverflowNextButton())); await tester.pumpAndSettle(); - expect(find.byType(CupertinoButton), findsNWidgets(3)); + expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(3)); expect(find.text('Cut'), findsNothing); expect(find.text('Copy'), findsOneWidget); expect(find.text('Paste'), findsNothing); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsOneWidget); - expect(appearsEnabled(tester, '◀'), true); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '▶'), true); + expect(findOverflowBackButton(), findsOneWidget); + expect(findOverflowNextButton(), findsOneWidget); - // Tapping the next button again shows Paste. - await tester.tap(find.text('▶')); + // Tapping the next button again shows Paste and hides the next button as + // the last page is shown. + await tester.tapAt(tester.getCenter(findOverflowNextButton())); await tester.pumpAndSettle(); - expect(find.byType(CupertinoButton), findsNWidgets(3)); + expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(2)); expect(find.text('Cut'), findsNothing); expect(find.text('Copy'), findsNothing); expect(find.text('Paste'), findsOneWidget); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsOneWidget); - expect(appearsEnabled(tester, '◀'), true); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '▶'), false); + expect(findOverflowBackButton(), findsOneWidget); + expect(findOverflowNextButton(), findsNothing); - // Tapping the back button shows the second page again. - await tester.tap(find.text('◀')); + // Tapping the back button shows the second page again with the next button. + await tester.tapAt(tester.getCenter(findOverflowBackButton())); await tester.pumpAndSettle(); - expect(find.byType(CupertinoButton), findsNWidgets(3)); + expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(3)); expect(find.text('Cut'), findsNothing); expect(find.text('Copy'), findsOneWidget); expect(find.text('Paste'), findsNothing); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsOneWidget); - expect(appearsEnabled(tester, '◀'), true); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '▶'), true); + expect(findOverflowBackButton(), findsOneWidget); + expect(findOverflowNextButton(), findsOneWidget); // Tapping the back button again shows the first page again. - await tester.tap(find.text('◀')); + await tester.tapAt(tester.getCenter(findOverflowBackButton())); await tester.pumpAndSettle(); - expect(find.byType(CupertinoButton), findsNWidgets(2)); + expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(2)); expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsNothing); expect(find.text('Paste'), findsNothing); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '▶'), true); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsOneWidget); }, skip: isBrowser, // [intended] We do not use Flutter-rendered context menu on the Web. variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }), @@ -452,8 +439,8 @@ void main() { expect(find.text(_longLocalizations.copyButtonLabel), findsNothing); expect(find.text(_longLocalizations.pasteButtonLabel), findsNothing); expect(find.text(_longLocalizations.selectAllButtonLabel), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsNothing); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsNothing); // Long press on an empty space to show the selection menu, with only the // paste button visible. @@ -463,21 +450,18 @@ void main() { expect(find.text(_longLocalizations.copyButtonLabel), findsNothing); expect(find.text(_longLocalizations.pasteButtonLabel), findsOneWidget); expect(find.text(_longLocalizations.selectAllButtonLabel), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '▶'), true); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsOneWidget); // Tap next to go to the second and final page. - await tester.tap(find.text('▶')); + await tester.tapAt(tester.getCenter(findOverflowNextButton())); await tester.pumpAndSettle(); expect(find.text(_longLocalizations.cutButtonLabel), findsNothing); expect(find.text(_longLocalizations.copyButtonLabel), findsNothing); expect(find.text(_longLocalizations.pasteButtonLabel), findsNothing); expect(find.text(_longLocalizations.selectAllButtonLabel), findsOneWidget); - expect(find.text('◀'), findsOneWidget); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '◀'), true); - expect(appearsEnabled(tester, '▶'), false); + expect(findOverflowBackButton(), findsOneWidget); + expect(findOverflowNextButton(), findsNothing); // Tap select all to show the full selection menu. await tester.tap(find.text(_longLocalizations.selectAllButtonLabel)); @@ -488,56 +472,48 @@ void main() { expect(find.text(_longLocalizations.copyButtonLabel), findsNothing); expect(find.text(_longLocalizations.pasteButtonLabel), findsNothing); expect(find.text(_longLocalizations.selectAllButtonLabel), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '▶'), true); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsOneWidget); // Tap next to go to the second page. - await tester.tap(find.text('▶')); + await tester.tapAt(tester.getCenter(findOverflowNextButton())); await tester.pumpAndSettle(); expect(find.text(_longLocalizations.cutButtonLabel), findsNothing); expect(find.text(_longLocalizations.copyButtonLabel), findsOneWidget); expect(find.text(_longLocalizations.pasteButtonLabel), findsNothing); expect(find.text(_longLocalizations.selectAllButtonLabel), findsNothing); - expect(find.text('◀'), findsOneWidget); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '◀'), true); - expect(appearsEnabled(tester, '▶'), true); + expect(findOverflowBackButton(), findsOneWidget); + expect(findOverflowNextButton(), findsOneWidget); // Tap next to go to the third and final page. - await tester.tap(find.text('▶')); + await tester.tapAt(tester.getCenter(findOverflowNextButton())); await tester.pumpAndSettle(); expect(find.text(_longLocalizations.cutButtonLabel), findsNothing); expect(find.text(_longLocalizations.copyButtonLabel), findsNothing); expect(find.text(_longLocalizations.pasteButtonLabel), findsOneWidget); expect(find.text(_longLocalizations.selectAllButtonLabel), findsNothing); - expect(find.text('◀'), findsOneWidget); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '◀'), true); - expect(appearsEnabled(tester, '▶'), false); + expect(findOverflowBackButton(), findsOneWidget); + expect(findOverflowNextButton(), findsNothing); // Tap back to go to the second page again. - await tester.tap(find.text('◀')); + await tester.tapAt(tester.getCenter(findOverflowBackButton())); await tester.pumpAndSettle(); expect(find.text(_longLocalizations.cutButtonLabel), findsNothing); expect(find.text(_longLocalizations.copyButtonLabel), findsOneWidget); expect(find.text(_longLocalizations.pasteButtonLabel), findsNothing); expect(find.text(_longLocalizations.selectAllButtonLabel), findsNothing); - expect(find.text('◀'), findsOneWidget); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '◀'), true); - expect(appearsEnabled(tester, '▶'), true); + expect(findOverflowBackButton(), findsOneWidget); + expect(findOverflowNextButton(), findsOneWidget); // Tap back to go to the first page again. - await tester.tap(find.text('◀')); + await tester.tapAt(tester.getCenter(findOverflowBackButton())); await tester.pumpAndSettle(); expect(find.text(_longLocalizations.cutButtonLabel), findsOneWidget); expect(find.text(_longLocalizations.copyButtonLabel), findsNothing); expect(find.text(_longLocalizations.pasteButtonLabel), findsNothing); expect(find.text(_longLocalizations.selectAllButtonLabel), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsOneWidget); - expect(appearsEnabled(tester, '▶'), true); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsOneWidget); }, skip: isBrowser, // [intended] We do not use Flutter-rendered context menu on the Web. variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }), @@ -572,8 +548,8 @@ void main() { expect(find.text('Copy'), findsNothing); expect(find.text('Paste'), findsNothing); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsNothing); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsNothing); // Long press on an space to show the selection menu. await tester.longPressAt(textOffsetToPosition(tester, 1)); @@ -582,8 +558,8 @@ void main() { expect(find.text('Copy'), findsNothing); expect(find.text('Paste'), findsOneWidget); expect(find.text('Select All'), findsOneWidget); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsNothing); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsNothing); // Tap to select all. await tester.tap(find.text('Select All')); @@ -594,8 +570,8 @@ void main() { expect(find.text('Copy'), findsOneWidget); expect(find.text('Paste'), findsOneWidget); expect(find.text('Select All'), findsNothing); - expect(find.text('◀'), findsNothing); - expect(find.text('▶'), findsNothing); + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsNothing); // The menu appears at the top of the visible selection. final Offset selectionOffset = tester @@ -603,8 +579,8 @@ void main() { final Offset textFieldOffset = tester.getTopLeft(find.byType(CupertinoTextField)); - // 7.0 + 43.0 + 8.0 - 8.0 = _kToolbarArrowSize + _kToolbarHeight + _kToolbarContentDistance - padding - expect(selectionOffset.dy + 7.0 + 43.0 + 8.0 - 8.0, equals(textFieldOffset.dy)); + // 7.0 + 45.0 + 8.0 - 8.0 = _kToolbarArrowSize + _kToolbarHeight + _kToolbarContentDistance - padding + expect(selectionOffset.dy + 7.0 + 45.0 + 8.0 - 8.0, equals(textFieldOffset.dy)); }, skip: isBrowser, // [intended] the selection menu isn't required by web variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }), diff --git a/packages/flutter/test/cupertino/text_selection_toolbar_button_test.dart b/packages/flutter/test/cupertino/text_selection_toolbar_button_test.dart index 708183bac8e7a..76eee86317a7f 100644 --- a/packages/flutter/test/cupertino/text_selection_toolbar_button_test.dart +++ b/packages/flutter/test/cupertino/text_selection_toolbar_button_test.dart @@ -29,7 +29,7 @@ void main() { expect(pressed, true); }); - testWidgets('pressedOpacity defaults to 0.1', (WidgetTester tester) async { + testWidgets('background darkens when pressed', (WidgetTester tester) async { await tester.pumpWidget( CupertinoApp( home: Center( @@ -41,35 +41,38 @@ void main() { ), ); - // Original at full opacity. - FadeTransition opacity = tester.widget(find.descendant( - of: find.byType(CupertinoTextSelectionToolbarButton), - matching: find.byType(FadeTransition), + // Original with transparent background. + DecoratedBox decoratedBox = tester.widget(find.descendant( + of: find.byType(CupertinoButton), + matching: find.byType(DecoratedBox), )); - expect(opacity.opacity.value, 1.0); + BoxDecoration boxDecoration = decoratedBox.decoration as BoxDecoration; + expect(boxDecoration.color, const Color(0x00000000)); // Make a "down" gesture on the button. final Offset center = tester.getCenter(find.byType(CupertinoTextSelectionToolbarButton)); final TestGesture gesture = await tester.startGesture(center); await tester.pumpAndSettle(); - // Opacity reduces during the down gesture. - opacity = tester.widget(find.descendant( + // When pressed, the background darkens. + decoratedBox = tester.widget(find.descendant( of: find.byType(CupertinoTextSelectionToolbarButton), - matching: find.byType(FadeTransition), + matching: find.byType(DecoratedBox), )); - expect(opacity.opacity.value, 0.7); + boxDecoration = decoratedBox.decoration as BoxDecoration; + expect(boxDecoration.color!.value, const Color(0x10000000).value); // Release the down gesture. await gesture.up(); await tester.pumpAndSettle(); - // Opacity is back to normal. - opacity = tester.widget(find.descendant( + // Color is back to transparent. + decoratedBox = tester.widget(find.descendant( of: find.byType(CupertinoTextSelectionToolbarButton), - matching: find.byType(FadeTransition), + matching: find.byType(DecoratedBox), )); - expect(opacity.opacity.value, 1.0); + boxDecoration = decoratedBox.decoration as BoxDecoration; + expect(boxDecoration.color, const Color(0x00000000)); }); testWidgets('passing null to onPressed disables the button', (WidgetTester tester) async { diff --git a/packages/flutter/test/cupertino/text_selection_toolbar_test.dart b/packages/flutter/test/cupertino/text_selection_toolbar_test.dart index 98a56018ed1e4..5c110b22e9620 100644 --- a/packages/flutter/test/cupertino/text_selection_toolbar_test.dart +++ b/packages/flutter/test/cupertino/text_selection_toolbar_test.dart @@ -6,12 +6,13 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../rendering/mock_canvas.dart'; import '../widgets/editable_text_utils.dart' show textOffsetToPosition; // These constants are copied from cupertino/text_selection_toolbar.dart. const double _kArrowScreenPadding = 26.0; const double _kToolbarContentDistance = 8.0; -const double _kToolbarHeight = 43.0; +const double _kToolbarHeight = 45.0; // A custom text selection menu that just displays a single custom button. class _CustomCupertinoTextSelectionControls extends CupertinoTextSelectionControls { @@ -60,9 +61,9 @@ class TestBox extends SizedBox { static const double itemWidth = 100.0; } -const CupertinoDynamicColor _kToolbarBackgroundColor = CupertinoDynamicColor.withBrightness( - color: Color(0xEBF7F7F7), - darkColor: Color(0xEB202020), +const CupertinoDynamicColor _kToolbarTextColor = CupertinoDynamicColor.withBrightness( + color: CupertinoColors.black, + darkColor: CupertinoColors.white, ); void main() { @@ -81,8 +82,65 @@ void main() { // visible part of the toolbar for use in measurements. Finder findToolbar() => findPrivate('_CupertinoTextSelectionToolbarContent'); - Finder findOverflowNextButton() => find.text('▶'); - Finder findOverflowBackButton() => find.text('◀'); + // Check if the middle point of the chevron is pointing left or right. + // + // Offset.dx: a right or left margin (_kToolbarChevronSize / 4 => 2.5) to center the icon horizontally + // Offset.dy: always in the exact vertical center (_kToolbarChevronSize / 2 => 5) + PaintPattern overflowNextPaintPattern() => paints + ..line(p1: const Offset(2.5, 0), p2: const Offset(7.5, 5)) + ..line(p1: const Offset(7.5, 5), p2: const Offset(2.5, 10)); + PaintPattern overflowBackPaintPattern() => paints + ..line(p1: const Offset(7.5, 0), p2: const Offset(2.5, 5)) + ..line(p1: const Offset(2.5, 5), p2: const Offset(7.5, 10)); + + Finder findOverflowNextButton() => find.byWidgetPredicate((Widget widget) => + widget is CustomPaint && + '${widget.painter?.runtimeType}' == '_RightCupertinoChevronPainter', + ); + Finder findOverflowBackButton() => find.byWidgetPredicate((Widget widget) => + widget is CustomPaint && + '${widget.painter?.runtimeType}' == '_LeftCupertinoChevronPainter', + ); + + testWidgets('chevrons point to the correct side', (WidgetTester tester) async { + // Add enough TestBoxes to need 3 pages. + final List<Widget> children = List<Widget>.generate(15, (int i) => const TestBox()); + await tester.pumpWidget( + CupertinoApp( + home: Center( + child: CupertinoTextSelectionToolbar( + anchorAbove: const Offset(50.0, 100.0), + anchorBelow: const Offset(50.0, 200.0), + children: children, + ), + ), + ), + ); + + expect(findOverflowBackButton(), findsNothing); + expect(findOverflowNextButton(), findsOneWidget); + + expect(findOverflowNextButton(), overflowNextPaintPattern()); + + // Tap the overflow next button to show the next page of children. + await tester.tapAt(tester.getCenter(findOverflowNextButton())); + await tester.pumpAndSettle(); + + expect(findOverflowBackButton(), findsOneWidget); + expect(findOverflowNextButton(), findsOneWidget); + + expect(findOverflowBackButton(), overflowBackPaintPattern()); + expect(findOverflowNextButton(), overflowNextPaintPattern()); + + // Tap the overflow next button to show the last page of children. + await tester.tapAt(tester.getCenter(findOverflowNextButton())); + await tester.pumpAndSettle(); + + expect(findOverflowBackButton(), findsOneWidget); + expect(findOverflowNextButton(), findsNothing); + + expect(findOverflowBackButton(), overflowBackPaintPattern()); + }, skip: kIsWeb); // Path.combine is not implemented in the HTML backend https://github.com/flutter/flutter/issues/44572 testWidgets('paginates children if they overflow', (WidgetTester tester) async { late StateSetter setState; @@ -121,22 +179,15 @@ void main() { expect(findOverflowBackButton(), findsNothing); // Tap the overflow next button to show the next page of children. - await tester.tap(findOverflowNextButton()); - await tester.pumpAndSettle(); - expect(find.byType(TestBox), findsNWidgets(1)); - expect(findOverflowNextButton(), findsOneWidget); - expect(findOverflowBackButton(), findsOneWidget); - - // Tapping the overflow next button again does nothing because it is - // disabled and there are no more children to display. - await tester.tap(findOverflowNextButton()); + // The next button is hidden as there's no next page. + await tester.tapAt(tester.getCenter(findOverflowNextButton())); await tester.pumpAndSettle(); expect(find.byType(TestBox), findsNWidgets(1)); - expect(findOverflowNextButton(), findsOneWidget); + expect(findOverflowNextButton(), findsNothing); expect(findOverflowBackButton(), findsOneWidget); // Tap the overflow back button to go back to the first page. - await tester.tap(findOverflowBackButton()); + await tester.tapAt(tester.getCenter(findOverflowBackButton())); await tester.pumpAndSettle(); expect(find.byType(TestBox), findsNWidgets(7)); expect(findOverflowNextButton(), findsOneWidget); @@ -157,7 +208,7 @@ void main() { expect(findOverflowBackButton(), findsNothing); // Tap the overflow next button to show the second page of children. - await tester.tap(findOverflowNextButton()); + await tester.tapAt(tester.getCenter(findOverflowNextButton())); await tester.pumpAndSettle(); // With the back button, only six children fit on this page. expect(find.byType(TestBox), findsNWidgets(6)); @@ -165,21 +216,21 @@ void main() { expect(findOverflowBackButton(), findsOneWidget); // Tap the overflow next button again to show the third page of children. - await tester.tap(findOverflowNextButton()); + await tester.tapAt(tester.getCenter(findOverflowNextButton())); await tester.pumpAndSettle(); expect(find.byType(TestBox), findsNWidgets(1)); - expect(findOverflowNextButton(), findsOneWidget); + expect(findOverflowNextButton(), findsNothing); expect(findOverflowBackButton(), findsOneWidget); // Tap the overflow back button to go back to the second page. - await tester.tap(findOverflowBackButton()); + await tester.tapAt(tester.getCenter(findOverflowBackButton())); await tester.pumpAndSettle(); expect(find.byType(TestBox), findsNWidgets(6)); expect(findOverflowNextButton(), findsOneWidget); expect(findOverflowBackButton(), findsOneWidget); // Tap the overflow back button to go back to the first page. - await tester.tap(findOverflowBackButton()); + await tester.tapAt(tester.getCenter(findOverflowBackButton())); await tester.pumpAndSettle(); expect(find.byType(TestBox), findsNWidgets(7)); expect(findOverflowNextButton(), findsOneWidget); @@ -345,13 +396,12 @@ void main() { final Finder buttonFinder = find.byType(CupertinoButton); expect(buttonFinder, findsOneWidget); - final Finder decorationFinder = find.descendant( + final Finder textFinder = find.descendant( of: find.byType(CupertinoButton), - matching: find.byType(DecoratedBox) + matching: find.byType(Text) ); - expect(decorationFinder, findsOneWidget); - final DecoratedBox decoratedBox = tester.widget(decorationFinder); - final BoxDecoration boxDecoration = decoratedBox.decoration as BoxDecoration; + expect(textFinder, findsOneWidget); + final Text text = tester.widget(textFinder); // Theme brightness is preferred, otherwise MediaQuery brightness is // used. If both are null, defaults to light. @@ -363,10 +413,10 @@ void main() { } expect( - boxDecoration.color!.value, + text.style!.color!.value, effectiveBrightness == Brightness.dark - ? _kToolbarBackgroundColor.darkColor.value - : _kToolbarBackgroundColor.color.value, + ? _kToolbarTextColor.darkColor.value + : _kToolbarTextColor.color.value, ); }, skip: kIsWeb); // [intended] We do not use Flutter-rendered context menu on the Web. } @@ -419,7 +469,7 @@ void main() { of: find.byType(CupertinoTextSelectionToolbar), matching: find.byType(DecoratedBox), ); - expect(finder, findsNWidgets(2)); + expect(finder, findsOneWidget); DecoratedBox decoratedBox = tester.widget(finder.first); BoxDecoration boxDecoration = decoratedBox.decoration as BoxDecoration; List<BoxShadow>? shadows = boxDecoration.boxShadow; diff --git a/packages/flutter/test/foundation/annotations_test.dart b/packages/flutter/test/foundation/annotations_test.dart new file mode 100644 index 0000000000000..9ac272ebccc58 --- /dev/null +++ b/packages/flutter/test/foundation/annotations_test.dart @@ -0,0 +1,27 @@ +// 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/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('test Category constructor', () { + const List<String> sections = <String>[ + 'First section', + 'Second section', + 'Third section' + ]; + const Category category = Category(sections); + expect(category.sections, sections); + }); + test('test DocumentationIcon constructor', () { + const DocumentationIcon docIcon = DocumentationIcon('Test String'); + expect(docIcon.url, contains('Test String')); + }); + + test('test Summary constructor', () { + const Summary summary = Summary('Test String'); + expect(summary.text, contains('Test String')); + }); +} diff --git a/packages/flutter/test/foundation/isolates_test.dart b/packages/flutter/test/foundation/isolates_test.dart index 571d9a1e6b826..24b834bc0c5f0 100644 --- a/packages/flutter/test/foundation/isolates_test.dart +++ b/packages/flutter/test/foundation/isolates_test.dart @@ -139,6 +139,7 @@ Future<int> computeInstanceMethod(int square) { Future<int> computeInvalidInstanceMethod(int square) { final ComputeTestSubject subject = ComputeTestSubject(square, ReceivePort()); + expect(subject.additional, isA<ReceivePort>()); return compute(subject.method, square); } diff --git a/packages/flutter/test/foundation/leak_tracking.dart b/packages/flutter/test/foundation/leak_tracking.dart index 96c6c9018cbc6..26a3dbd7a0bac 100644 --- a/packages/flutter/test/foundation/leak_tracking.dart +++ b/packages/flutter/test/foundation/leak_tracking.dart @@ -2,12 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:core'; + import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:leak_tracker/leak_tracker.dart'; +import 'package:leak_tracker_testing/leak_tracker_testing.dart'; import 'package:meta/meta.dart'; -export 'package:leak_tracker/leak_tracker.dart' show LeakTrackingTestConfig, StackTraceCollectionConfig; +export 'package:leak_tracker/leak_tracker.dart' show LeakDiagnosticConfig, LeakTrackingTestConfig; /// Set of objects, that does not hold the objects from garbage collection. /// @@ -81,7 +84,7 @@ bool _webWarningPrinted = false; /// The method will fail if wrapped code contains memory leaks. /// /// See details in documentation for `withLeakTracking` at -/// https://github.com/dart-lang/leak_tracker/blob/main/lib/src/orchestration.dart#withLeakTracking +/// https://github.com/dart-lang/leak_tracker/blob/main/lib/src/leak_tracking/orchestration.dart /// /// The Flutter related enhancements are: /// 1. Listens to [MemoryAllocations] events. @@ -117,7 +120,7 @@ Future<void> _withFlutterLeakTracking( Leaks leaks = await withLeakTracking( callback, asyncCodeRunner: asyncCodeRunner, - stackTraceCollectionConfig: config.stackTraceCollectionConfig, + leakDiagnosticConfig: config.leakDiagnosticConfig, shouldThrowOnLeaks: false, ); @@ -142,28 +145,56 @@ class LeakCleaner { final LeakTrackingTestConfig config; + static Map<(String, LeakType), int> _countByClassAndType(Leaks leaks) { + final Map<(String, LeakType), int> result = <(String, LeakType), int>{}; + + for (final MapEntry<LeakType, List<LeakReport>> entry in leaks.byType.entries) { + for (final LeakReport leak in entry.value) { + final (String, LeakType) classAndType = (leak.type, entry.key); + result[classAndType] = (result[classAndType] ?? 0) + 1; + } + } + return result; + } + Leaks clean(Leaks leaks) { + final Map<(String, LeakType), int> countByClassAndType = _countByClassAndType(leaks); + final Leaks result = Leaks(<LeakType, List<LeakReport>>{ - for (LeakType leakType in leaks.byType.keys) - leakType: leaks.byType[leakType]!.where((LeakReport leak) => _shouldReportLeak(leakType, leak)).toList() + for (final LeakType leakType in leaks.byType.keys) + leakType: leaks.byType[leakType]!.where((LeakReport leak) => _shouldReportLeak(leakType, leak, countByClassAndType)).toList() }); return result; } /// Returns true if [leak] should be reported as failure. - bool _shouldReportLeak(LeakType leakType, LeakReport leak) { + bool _shouldReportLeak(LeakType leakType, LeakReport leak, Map<(String, LeakType), int> countByClassAndType) { // Tracking for non-GCed is temporarily disabled. // TODO(polina-c): turn on tracking for non-GCed after investigating existing leaks. if (leakType != LeakType.notDisposed) { return false; } + final String leakingClass = leak.type; + final (String, LeakType) classAndType = (leakingClass, leakType); + + bool isAllowedForClass(Map<String, int?> allowList) { + if (!allowList.containsKey(leakingClass)) { + return false; + } + final int? allowedCount = allowList[leakingClass]; + if (allowedCount == null) { + return true; + } + return allowedCount >= countByClassAndType[classAndType]!; + } + switch (leakType) { case LeakType.notDisposed: - return !config.notDisposedAllowList.containsKey(leak.type); + return !isAllowedForClass(config.notDisposedAllowList); case LeakType.notGCed: case LeakType.gcedLate: - return !config.notGCedAllowList.containsKey(leak.type); + return !isAllowedForClass(config.notGCedAllowList); } } } diff --git a/packages/flutter/test/foundation/leak_tracking_test.dart b/packages/flutter/test/foundation/leak_tracking_test.dart index 0c1b761465d36..fc71814d260ff 100644 --- a/packages/flutter/test/foundation/leak_tracking_test.dart +++ b/packages/flutter/test/foundation/leak_tracking_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:leak_tracker/leak_tracker.dart'; +import 'package:leak_tracker_testing/leak_tracker_testing.dart'; import 'leak_tracking.dart'; @@ -28,9 +29,22 @@ Future<void> main() async { expect(cleanedLeaks.total, 1); }); - group('Leak tracking works for non-web', () { + test('$LeakCleaner catches extra leaks', () { + Leaks leaks = _leaksOfAllTypes(); + final LeakReport leak = leaks.notDisposed.first; + leaks.notDisposed.add(leak); + + final LeakTrackingTestConfig config = LeakTrackingTestConfig( + notDisposedAllowList: <String, int?>{leak.type: 1}, + ); + leaks = LeakCleaner(config).clean(leaks); + + expect(leaks.notDisposed, hasLength(2)); + }); + + group('Leak tracking works for non-web, and', () { testWidgetsWithLeakTracking( - 'Leak tracker respects all allow lists', + 'respects all allow lists', (WidgetTester tester) async { await tester.pumpWidget(_StatelessLeakingWidget()); }, @@ -40,7 +54,41 @@ Future<void> main() async { ), ); - group('Leak tracker respects notGCed allow lists', () { + testWidgetsWithLeakTracking( + 'respects count in allow lists', + (WidgetTester tester) async { + await tester.pumpWidget(_StatelessLeakingWidget()); + }, + leakTrackingConfig: LeakTrackingTestConfig( + notDisposedAllowList: <String, int?>{_leakTrackedClassName: 1}, + notGCedAllowList: <String, int?>{_leakTrackedClassName: 1}, + ), + ); + + group('fails if number or leaks is more than allowed', () { + // This test cannot run inside other tests because test nesting is forbidden. + // So, `expect` happens outside the tests, in `tearDown`. + late Leaks leaks; + + testWidgetsWithLeakTracking( + 'for $_StatelessLeakingWidget', + (WidgetTester tester) async { + await tester.pumpWidget(_StatelessLeakingWidget()); + await tester.pumpWidget(_StatelessLeakingWidget()); + }, + leakTrackingConfig: LeakTrackingTestConfig( + onLeaks: (Leaks theLeaks) { + leaks = theLeaks; + }, + failTestOnLeaks: false, + notDisposedAllowList: <String, int?>{_leakTrackedClassName: 1}, + ), + ); + + tearDown(() => _verifyLeaks(leaks, expectedNotDisposed: 2)); + }); + + group('respects notGCed allow lists', () { // These tests cannot run inside other tests because test nesting is forbidden. // So, `expect` happens outside the tests, in `tearDown`. late Leaks leaks; @@ -62,8 +110,8 @@ Future<void> main() async { tearDown(() => _verifyLeaks(leaks, expectedNotDisposed: 1)); }); - group('Leak tracker catches that', () { - // These tests cannot run inside other tests because test nesting is forbidden. + group('catches that', () { + // These test cannot run inside other tests because test nesting is forbidden. // So, `expect` happens outside the tests, in `tearDown`. late Leaks leaks; diff --git a/packages/flutter/test/foundation/timeline_test.dart b/packages/flutter/test/foundation/timeline_test.dart new file mode 100644 index 0000000000000..80c95d1a8b0ad --- /dev/null +++ b/packages/flutter/test/foundation/timeline_test.dart @@ -0,0 +1,143 @@ +// 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/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +// IMPORTANT: keep this in sync with the same constant defined +// in foundation/timeline.dart +const int kSliceSize = 500; + +void main() { + setUp(() { + FlutterTimeline.debugReset(); + FlutterTimeline.debugCollectionEnabled = false; + }); + + test('Does not collect when collection not enabled', () { + FlutterTimeline.startSync('TEST'); + FlutterTimeline.finishSync(); + expect( + () => FlutterTimeline.debugCollect(), + throwsStateError, + ); + }); + + test('Collects when collection is enabled', () { + FlutterTimeline.debugCollectionEnabled = true; + FlutterTimeline.startSync('TEST'); + FlutterTimeline.finishSync(); + final AggregatedTimings data = FlutterTimeline.debugCollect(); + expect(data.timedBlocks, hasLength(1)); + expect(data.aggregatedBlocks, hasLength(1)); + + final AggregatedTimedBlock block = data.getAggregated('TEST'); + expect(block.name, 'TEST'); + expect(block.count, 1); + + // After collection the timeline is reset back to empty. + final AggregatedTimings data2 = FlutterTimeline.debugCollect(); + expect(data2.timedBlocks, isEmpty); + expect(data2.aggregatedBlocks, isEmpty); + }); + + test('Deletes old data when reset', () { + FlutterTimeline.debugCollectionEnabled = true; + FlutterTimeline.startSync('TEST'); + FlutterTimeline.finishSync(); + FlutterTimeline.debugReset(); + + final AggregatedTimings data = FlutterTimeline.debugCollect(); + expect(data.timedBlocks, isEmpty); + expect(data.aggregatedBlocks, isEmpty); + }); + + test('Reports zero aggregation when requested missing block', () { + FlutterTimeline.debugCollectionEnabled = true; + + final AggregatedTimings data = FlutterTimeline.debugCollect(); + final AggregatedTimedBlock block = data.getAggregated('MISSING'); + expect(block.name, 'MISSING'); + expect(block.count, 0); + expect(block.duration, 0); + }); + + test('Measures the runtime of a function', () { + FlutterTimeline.debugCollectionEnabled = true; + + // The off-by-one values for `start` and `end` are for web's sake where + // timer values are reported as float64 and toInt/toDouble conversions + // are noops, so there's no value truncation happening, which makes it + // a bit inconsistent with Stopwatch. + final int start = FlutterTimeline.now - 1; + FlutterTimeline.timeSync('TEST', () { + final Stopwatch watch = Stopwatch()..start(); + while (watch.elapsedMilliseconds < 5) {} + watch.stop(); + }); + final int end = FlutterTimeline.now + 1; + + final AggregatedTimings data = FlutterTimeline.debugCollect(); + expect(data.timedBlocks, hasLength(1)); + expect(data.aggregatedBlocks, hasLength(1)); + + final TimedBlock block = data.timedBlocks.single; + expect(block.name, 'TEST'); + expect(block.start, greaterThanOrEqualTo(start)); + expect(block.end, lessThanOrEqualTo(end)); + expect(block.duration, greaterThan(0)); + + final AggregatedTimedBlock aggregated = data.getAggregated('TEST'); + expect(aggregated.name, 'TEST'); + expect(aggregated.count, 1); + expect(aggregated.duration, block.duration); + }); + + test('FlutterTimeline.instanceSync does not collect anything', () { + FlutterTimeline.debugCollectionEnabled = true; + FlutterTimeline.instantSync('TEST'); + + final AggregatedTimings data = FlutterTimeline.debugCollect(); + expect(data.timedBlocks, isEmpty); + expect(data.aggregatedBlocks, isEmpty); + }); + + test('FlutterTimeline.now returns a value', () { + FlutterTimeline.debugCollectionEnabled = true; + expect(FlutterTimeline.now, isNotNull); + }); + + test('Can collect more than one slice of data', () { + FlutterTimeline.debugCollectionEnabled = true; + + for (int i = 0; i < 10 * kSliceSize; i++) { + FlutterTimeline.startSync('TEST'); + FlutterTimeline.finishSync(); + } + final AggregatedTimings data = FlutterTimeline.debugCollect(); + expect(data.timedBlocks, hasLength(10 * kSliceSize)); + expect(data.aggregatedBlocks, hasLength(1)); + + final AggregatedTimedBlock block = data.getAggregated('TEST'); + expect(block.name, 'TEST'); + expect(block.count, 10 * kSliceSize); + }); + + test('Collects blocks in a correct order', () { + FlutterTimeline.debugCollectionEnabled = true; + const int testCount = 7 * kSliceSize ~/ 2; + + for (int i = 0; i < testCount; i++) { + FlutterTimeline.startSync('TEST$i'); + FlutterTimeline.finishSync(); + } + + final AggregatedTimings data = FlutterTimeline.debugCollect(); + expect(data.timedBlocks, hasLength(testCount)); + expect( + data.timedBlocks.map<String>((TimedBlock block) => block.name).toList(), + List<String>.generate(testCount, (int i) => 'TEST$i'), + ); + }); +} diff --git a/packages/flutter/test/gestures/gesture_binding_resample_event_on_widget_test.dart b/packages/flutter/test/gestures/gesture_binding_resample_event_on_widget_test.dart index 45050ce4c2125..26c2f8ffe3bd0 100644 --- a/packages/flutter/test/gestures/gesture_binding_resample_event_on_widget_test.dart +++ b/packages/flutter/test/gestures/gesture_binding_resample_event_on_widget_test.dart @@ -39,42 +39,50 @@ void main() { final ui.PointerDataPacket packet = ui.PointerDataPacket( data: <ui.PointerData>[ ui.PointerData( - change: ui.PointerChange.add, - timeStamp: epoch, + viewId: tester.view.viewId, + change: ui.PointerChange.add, + timeStamp: epoch, ), ui.PointerData( - change: ui.PointerChange.down, - timeStamp: epoch, + viewId: tester.view.viewId, + change: ui.PointerChange.down, + timeStamp: epoch, ), ui.PointerData( - change: ui.PointerChange.move, - physicalX: 15.0, - timeStamp: epoch + const Duration(milliseconds: 10), + viewId: tester.view.viewId, + change: ui.PointerChange.move, + physicalX: 15.0, + timeStamp: epoch + const Duration(milliseconds: 10), ), ui.PointerData( - change: ui.PointerChange.move, - physicalX: 30.0, - timeStamp: epoch + const Duration(milliseconds: 20), + viewId: tester.view.viewId, + change: ui.PointerChange.move, + physicalX: 30.0, + timeStamp: epoch + const Duration(milliseconds: 20), ), ui.PointerData( - change: ui.PointerChange.move, - physicalX: 45.0, - timeStamp: epoch + const Duration(milliseconds: 30), + viewId: tester.view.viewId, + change: ui.PointerChange.move, + physicalX: 45.0, + timeStamp: epoch + const Duration(milliseconds: 30), ), ui.PointerData( - change: ui.PointerChange.move, - physicalX: 50.0, - timeStamp: epoch + const Duration(milliseconds: 40), + viewId: tester.view.viewId, + change: ui.PointerChange.move, + physicalX: 50.0, + timeStamp: epoch + const Duration(milliseconds: 40), ), ui.PointerData( - change: ui.PointerChange.up, - physicalX: 60.0, - timeStamp: epoch + const Duration(milliseconds: 40), + viewId: tester.view.viewId, + change: ui.PointerChange.up, + physicalX: 60.0, + timeStamp: epoch + const Duration(milliseconds: 40), ), ui.PointerData( - change: ui.PointerChange.remove, - physicalX: 60.0, - timeStamp: epoch + const Duration(milliseconds: 40), + viewId: tester.view.viewId, + change: ui.PointerChange.remove, + physicalX: 60.0, + timeStamp: epoch + const Duration(milliseconds: 40), ), ], ); diff --git a/packages/flutter/test/gestures/gesture_binding_test.dart b/packages/flutter/test/gestures/gesture_binding_test.dart index d4df7b403f74b..6663726713e6e 100644 --- a/packages/flutter/test/gestures/gesture_binding_test.dart +++ b/packages/flutter/test/gestures/gesture_binding_test.dart @@ -175,7 +175,7 @@ void main() { ], ); - final List<PointerEvent> events = PointerEventConverter.expand(packet.data, devicePixelRatio).toList(); + final List<PointerEvent> events = PointerEventConverter.expand(packet.data, (int viewId) => devicePixelRatio).toList(); expect(events.length, 5); expect(events[0], isA<PointerAddedEvent>()); @@ -191,7 +191,7 @@ void main() { ui.PointerData(change: ui.PointerChange.add, device: 24), ], ); - List<PointerEvent> events = PointerEventConverter.expand(packet.data, devicePixelRatio).toList(); + List<PointerEvent> events = PointerEventConverter.expand(packet.data, (int viewId) => devicePixelRatio).toList(); expect(events.length, 1); expect(events[0], isA<PointerAddedEvent>()); @@ -207,7 +207,7 @@ void main() { ui.PointerData(signalKind: ui.PointerSignalKind.scroll, device: 24, scrollDeltaY: double.negativeInfinity, scrollDeltaX: 10), ], ); - events = PointerEventConverter.expand(packet.data, devicePixelRatio).toList(); + events = PointerEventConverter.expand(packet.data, (int viewId) => devicePixelRatio).toList(); expect(events.length, 0); // Send packet with a valid scroll event. @@ -217,12 +217,12 @@ void main() { ], ); // Make sure PointerEventConverter can expand when device pixel ratio is valid. - events = PointerEventConverter.expand(packet.data, devicePixelRatio).toList(); + events = PointerEventConverter.expand(packet.data, (int viewId) => devicePixelRatio).toList(); expect(events.length, 1); expect(events[0], isA<PointerScrollEvent>()); // Make sure PointerEventConverter returns none when device pixel ratio is invalid. - events = PointerEventConverter.expand(packet.data, 0).toList(); + events = PointerEventConverter.expand(packet.data, (int viewId) => 0).toList(); expect(events.length, 0); }); @@ -234,7 +234,7 @@ void main() { ], ); - final List<PointerEvent> events = PointerEventConverter.expand(packet.data, devicePixelRatio).toList(); + final List<PointerEvent> events = PointerEventConverter.expand(packet.data, (int viewId) => devicePixelRatio).toList(); expect(events.length, 2); expect(events[0], isA<PointerAddedEvent>()); @@ -253,7 +253,7 @@ void main() { ], ); - final List<PointerEvent> events = PointerEventConverter.expand(packet.data, devicePixelRatio).toList(); + final List<PointerEvent> events = PointerEventConverter.expand(packet.data, (int viewId) => devicePixelRatio).toList(); expect(events.length, 5); expect(events[0], isA<PointerAddedEvent>()); @@ -280,7 +280,7 @@ void main() { ], ); - final List<PointerEvent> events = PointerEventConverter.expand(packet.data, devicePixelRatio).toList(); + final List<PointerEvent> events = PointerEventConverter.expand(packet.data, (int viewId) => devicePixelRatio).toList(); expect(events.length, 5); expect(events[0], isA<PointerAddedEvent>()); @@ -312,7 +312,7 @@ void main() { ], ); - final List<PointerEvent> events = PointerEventConverter.expand(packet.data, devicePixelRatio).toList(); + final List<PointerEvent> events = PointerEventConverter.expand(packet.data, (int viewId) => devicePixelRatio).toList(); expect(events.length, 5); expect(events[0], isA<PointerAddedEvent>()); @@ -341,7 +341,7 @@ void main() { ], ); - final List<PointerEvent> events = PointerEventConverter.expand(packet.data, devicePixelRatio).toList(); + final List<PointerEvent> events = PointerEventConverter.expand(packet.data, (int viewId) => devicePixelRatio).toList(); expect(events.length, 5); expect(events[0], isA<PointerAddedEvent>()); @@ -371,7 +371,7 @@ void main() { ], ); - final List<PointerEvent> events = PointerEventConverter.expand(packet.data, devicePixelRatio).toList(); + final List<PointerEvent> events = PointerEventConverter.expand(packet.data, (int viewId) => devicePixelRatio).toList(); expect(events.length, 5); expect(events[0], isA<PointerAddedEvent>()); @@ -429,4 +429,33 @@ void main() { FlutterError.onError = FlutterError.presentError; } }); + + test('PointerEventConverter processes view IDs', () { + const int startID = 987654; + const List<ui.PointerData> data = <ui.PointerData>[ + ui.PointerData(viewId: startID + 0, change: ui.PointerChange.cancel), // ignore: avoid_redundant_argument_values + ui.PointerData(viewId: startID + 1, change: ui.PointerChange.add), + ui.PointerData(viewId: startID + 2, change: ui.PointerChange.remove), + ui.PointerData(viewId: startID + 3, change: ui.PointerChange.hover), + ui.PointerData(viewId: startID + 4, change: ui.PointerChange.down), + ui.PointerData(viewId: startID + 5, change: ui.PointerChange.move), + ui.PointerData(viewId: startID + 6, change: ui.PointerChange.up), + ui.PointerData(viewId: startID + 7, change: ui.PointerChange.panZoomStart), + ui.PointerData(viewId: startID + 8, change: ui.PointerChange.panZoomUpdate), + ui.PointerData(viewId: startID + 9, change: ui.PointerChange.panZoomEnd), + ]; + + final List<int> viewIds = <int>[]; + double devicePixelRatioGetter(int viewId) { + viewIds.add(viewId); + return viewId / 10.0; + } + + final List<PointerEvent> events = PointerEventConverter.expand(data, devicePixelRatioGetter).toList(); + + final List<int> expectedViewIds = List<int>.generate(10, (int index) => startID + index); + expect(viewIds, expectedViewIds); + expect(events, hasLength(10)); + expect(events.map((PointerEvent event) => event.viewId), expectedViewIds); + }); } diff --git a/packages/flutter/test/gestures/long_press_test.dart b/packages/flutter/test/gestures/long_press_test.dart index 4de0cb465278d..c967956a8b6c7 100644 --- a/packages/flutter/test/gestures/long_press_test.dart +++ b/packages/flutter/test/gestures/long_press_test.dart @@ -458,7 +458,7 @@ void main() { expect(recognized, <String>['end']); }); - testGesture('Should cancel long press when buttons change after acceptance', (GestureTester tester) { + testGesture('Should not cancel long press when buttons change after acceptance', (GestureTester tester) { // First press gesture.addPointer(down); tester.closeArena(down.pointer); @@ -470,7 +470,7 @@ void main() { tester.route(moveR); expect(recognized, <String>[]); tester.route(up); - expect(recognized, <String>[]); + expect(recognized, <String>['end']); }); testGesture('Buttons change after acceptance should not prevent the next long press', (GestureTester tester) { @@ -701,4 +701,95 @@ void main() { longPress.dispose(); recognized.clear(); }); + + testGesture('Switching buttons mid-stream does not fail to send "end" event', (GestureTester tester) { + final List<String> recognized = <String>[]; + final LongPressGestureRecognizer longPress = LongPressGestureRecognizer() + ..onLongPressStart = (LongPressStartDetails details) { + recognized.add('primaryStart'); + } + ..onLongPressEnd = (LongPressEndDetails details) { + recognized.add('primaryEnd'); + }; + + const PointerDownEvent down4 = PointerDownEvent( + pointer: 8, + position: Offset(10, 10), + ); + + const PointerMoveEvent move4 = PointerMoveEvent( + pointer: 8, + position: Offset(100, 200), + buttons: kPrimaryButton | kSecondaryButton, + ); + + const PointerUpEvent up4 = PointerUpEvent( + pointer: 8, + position: Offset(100, 200), + buttons: kSecondaryButton, + ); + + longPress.addPointer(down4); + tester.closeArena(4); + tester.route(down4); + tester.async.elapse(const Duration(milliseconds: 1000)); + recognized.add('two seconds later...'); + tester.route(move4); + tester.async.elapse(const Duration(milliseconds: 1000)); + recognized.add('two more seconds later...'); + tester.route(up4); + tester.async.elapse(const Duration(milliseconds: 1000)); + expect(recognized, <String>['primaryStart', 'two seconds later...', 'two more seconds later...', 'primaryEnd']); + longPress.dispose(); + }); + + testGesture('Switching buttons mid-stream does not fail to send "end" event (alternative sequence)', (GestureTester tester) { + // This reproduces sequences seen on macOS. + final List<String> recognized = <String>[]; + final LongPressGestureRecognizer longPress = LongPressGestureRecognizer() + ..onLongPressStart = (LongPressStartDetails details) { + recognized.add('primaryStart'); + } + ..onLongPressEnd = (LongPressEndDetails details) { + recognized.add('primaryEnd'); + }; + + const PointerDownEvent down5 = PointerDownEvent( + pointer: 9, + position: Offset(10, 10), + ); + + const PointerMoveEvent move5a = PointerMoveEvent( + pointer: 9, + position: Offset(100, 200), + buttons: 3, // add 2 + ); + + const PointerMoveEvent move5b = PointerMoveEvent( + pointer: 9, + position: Offset(100, 200), + buttons: 2, // remove 1 + ); + + const PointerUpEvent up5 = PointerUpEvent( + pointer: 9, + position: Offset(100, 200), + ); + + longPress.addPointer(down5); + tester.closeArena(4); + tester.route(down5); + tester.async.elapse(const Duration(milliseconds: 1000)); + recognized.add('two seconds later...'); + tester.route(move5a); + tester.async.elapse(const Duration(milliseconds: 1000)); + recognized.add('two more seconds later...'); + tester.route(move5b); + tester.async.elapse(const Duration(milliseconds: 1000)); + recognized.add('two more seconds later still...'); + tester.route(up5); + tester.async.elapse(const Duration(milliseconds: 1000)); + expect(recognized, <String>['primaryStart', 'two seconds later...', 'two more seconds later...', 'two more seconds later still...', 'primaryEnd']); + longPress.dispose(); + }); } diff --git a/packages/flutter/test/gestures/monodrag_test.dart b/packages/flutter/test/gestures/monodrag_test.dart index 584af6f88bed6..b681dc62782b8 100644 --- a/packages/flutter/test/gestures/monodrag_test.dart +++ b/packages/flutter/test/gestures/monodrag_test.dart @@ -74,6 +74,75 @@ void main() { GestureBinding.instance.handleEvent(up91, HitTestEntry(MockHitTestTarget())); }); + testGesture('DragGestureRecognizer should not dispatch drag callbacks when it wins the arena if onlyAcceptDragOnThreshold is true and the threshold has not been met', (GestureTester tester) { + final VerticalDragGestureRecognizer verticalDrag = VerticalDragGestureRecognizer(); + final List<String> dragCallbacks = <String>[]; + verticalDrag + ..onlyAcceptDragOnThreshold = true + ..onStart = (DragStartDetails details) { + dragCallbacks.add('onStart'); + } + ..onUpdate = (DragUpdateDetails details) { + dragCallbacks.add('onUpdate'); + } + ..onEnd = (DragEndDetails details) { + dragCallbacks.add('onEnd'); + }; + + const PointerDownEvent down1 = PointerDownEvent( + pointer: 6, + position: Offset(10.0, 10.0), + ); + + const PointerUpEvent up1 = PointerUpEvent( + pointer: 6, + position: Offset(10.0, 10.0), + ); + + verticalDrag.addPointer(down1); + tester.closeArena(down1.pointer); + tester.route(down1); + tester.route(up1); + expect(dragCallbacks.isEmpty, true); + verticalDrag.dispose(); + dragCallbacks.clear(); + }); + + testGesture('DragGestureRecognizer should dispatch drag callbacks when it wins the arena if onlyAcceptDragOnThreshold is false and the threshold has not been met', (GestureTester tester) { + final VerticalDragGestureRecognizer verticalDrag = VerticalDragGestureRecognizer(); + final List<String> dragCallbacks = <String>[]; + verticalDrag + ..onlyAcceptDragOnThreshold = false + ..onStart = (DragStartDetails details) { + dragCallbacks.add('onStart'); + } + ..onUpdate = (DragUpdateDetails details) { + dragCallbacks.add('onUpdate'); + } + ..onEnd = (DragEndDetails details) { + dragCallbacks.add('onEnd'); + }; + + const PointerDownEvent down1 = PointerDownEvent( + pointer: 6, + position: Offset(10.0, 10.0), + ); + + const PointerUpEvent up1 = PointerUpEvent( + pointer: 6, + position: Offset(10.0, 10.0), + ); + + verticalDrag.addPointer(down1); + tester.closeArena(down1.pointer); + tester.route(down1); + tester.route(up1); + expect(dragCallbacks.isEmpty, false); + expect(dragCallbacks, <String>['onStart', 'onEnd']); + verticalDrag.dispose(); + dragCallbacks.clear(); + }); + group('Recognizers on different button filters:', () { final List<String> recognized = <String>[]; late HorizontalDragGestureRecognizer primaryRecognizer; diff --git a/packages/flutter/test/material/about_test.dart b/packages/flutter/test/material/about_test.dart index 191fa8eaf4b81..29cfdf5a372b4 100644 --- a/packages/flutter/test/material/about_test.dart +++ b/packages/flutter/test/material/about_test.dart @@ -63,6 +63,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), title: 'Pirate app', home: Scaffold( appBar: AppBar( @@ -167,8 +168,9 @@ void main() { }); await tester.pumpWidget( - const MaterialApp( - home: Center( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Center( child: LicensePage(), ), ), @@ -220,9 +222,10 @@ void main() { }); await tester.pumpWidget( - const MaterialApp( + MaterialApp( + theme: ThemeData(useMaterial3: false), title: 'Pirate app', - home: Center( + home: const Center( child: LicensePage( applicationName: 'LicensePage test app', applicationVersion: '0.1.2', @@ -298,6 +301,7 @@ void main() { await tester.pumpWidget( MaterialApp( theme: ThemeData( + useMaterial3: false, primaryTextTheme: const TextTheme( titleLarge: titleTextStyle, titleSmall: subtitleTextStyle, @@ -384,8 +388,9 @@ void main() { }); await tester.pumpWidget( - const MaterialApp( - home: MediaQuery( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const MediaQuery( data: MediaQueryData( padding: EdgeInsets.all(safeareaPadding), ), @@ -830,7 +835,7 @@ void main() { const Color cardColor = Color(0xFF654321); await tester.pumpWidget(MaterialApp( - theme: ThemeData.light().copyWith( + theme: ThemeData.light(useMaterial3: false).copyWith( scaffoldBackgroundColor: scaffoldColor, cardColor: cardColor, ), @@ -1051,7 +1056,12 @@ void main() { testWidgetsWithLeakTracking('Error handling test', (WidgetTester tester) async { LicenseRegistry.addLicense(() => Stream<LicenseEntry>.error(Exception('Injected failure'))); - await tester.pumpWidget(const MaterialApp(home: Material(child: AboutListTile()))); + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Material(child: AboutListTile()) + ) + ); await tester.tap(find.byType(ListTile)); await tester.pump(); await tester.pump(const Duration(seconds: 2)); @@ -1082,9 +1092,10 @@ void main() { await tester.binding.setSurfaceSize(defaultSize); await tester.pumpWidget( - const MaterialApp( + MaterialApp( + theme: ThemeData(useMaterial3: false), title: title, - home: Scaffold( + home: const Scaffold( body: Directionality( textDirection: textDirection, child: LicensePage(), @@ -1145,9 +1156,10 @@ void main() { await tester.binding.setSurfaceSize(defaultSize); await tester.pumpWidget( - const MaterialApp( + MaterialApp( + theme: ThemeData(useMaterial3: false), title: title, - home: Scaffold( + home: const Scaffold( body: Directionality( textDirection: textDirection, child: LicensePage(), @@ -1270,8 +1282,9 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgetsWithLeakTracking('License page default title text color in the nested UI', (WidgetTester tester) async { // This is a regression test for https://github.com/flutter/flutter/issues/108991 diff --git a/packages/flutter/test/material/action_chip_test.dart b/packages/flutter/test/material/action_chip_test.dart index 79e33c730663d..f3f4d67fc1d23 100644 --- a/packages/flutter/test/material/action_chip_test.dart +++ b/packages/flutter/test/material/action_chip_test.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../foundation/leak_tracking.dart'; +import '../rendering/mock_canvas.dart'; /// Adds the basic requirements for a Chip. Widget wrapForChip({ @@ -13,9 +14,10 @@ Widget wrapForChip({ TextDirection textDirection = TextDirection.ltr, double textScaleFactor = 1.0, Brightness brightness = Brightness.light, + bool? useMaterial3, }) { return MaterialApp( - theme: ThemeData(brightness: brightness), + theme: ThemeData(brightness: brightness, useMaterial3: useMaterial3), home: Directionality( textDirection: textDirection, child: MediaQuery( @@ -26,6 +28,33 @@ Widget wrapForChip({ ); } +RenderBox getMaterialBox(WidgetTester tester, Finder type) { + return tester.firstRenderObject<RenderBox>( + find.descendant( + of: type, + matching: find.byType(CustomPaint), + ), + ); +} + +Material getMaterial(WidgetTester tester) { + return tester.widget<Material>( + find.descendant( + of: find.byType(ActionChip), + matching: find.byType(Material), + ), + ); +} + +DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) { + return tester.widget( + find.ancestor( + of: find.text(labelText), + matching: find.byType(DefaultTextStyle), + ).first, + ); +} + void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { final Iterable<Material> materials = tester.widgetList<Material>(find.byType(Material)); // There should be two Material widgets, first Material is from the "_wrapForChip" and @@ -36,6 +65,262 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { } void main() { + testWidgets('ActionChip defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + const String label = 'action chip'; + + // Test enabled ActionChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: Center( + child: ActionChip( + onPressed: () {}, + label: const Text(label), + ), + ), + ), + ), + ); + + // Test default chip size. + expect(tester.getSize(find.byType(ActionChip)), const Size(190.0, 48.0)); + // Test default label style. + expect( + getLabelStyle(tester, label).style.color!.value, + theme.textTheme.labelLarge!.color!.value, + ); + + Material chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, Colors.transparent); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: theme.colorScheme.outline), + ), + ); + + ShapeDecoration decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test disabled ActionChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: ActionChip( + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, Colors.transparent); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: theme.colorScheme.onSurface.withOpacity(0.12)), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + }); + + testWidgets('ActionChip.elevated defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + const String label = 'action chip'; + + // Test enabled ActionChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: Center( + child: ActionChip.elevated( + onPressed: () {}, + label: const Text(label), + ), + ), + ), + ), + ); + + // Test default chip size. + expect(tester.getSize(find.byType(ActionChip)), const Size(190.0, 48.0)); + // Test default label style. + expect( + getLabelStyle(tester, label).style.color!.value, + theme.textTheme.labelLarge!.color!.value, + ); + + Material chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 1); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + ShapeDecoration decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test disabled ActionChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: ActionChip.elevated( + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); + }); + + testWidgets('ActionChip.color resolves material states', (WidgetTester tester) async { + const Color disabledColor = Color(0xff00ff00); + const Color backgroundColor = Color(0xff0000ff); + final MaterialStateProperty<Color?> color = MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.disabled)) { + return disabledColor; + } + return backgroundColor; + }); + Widget buildApp({ required bool enabled, required bool selected }) { + return wrapForChip( + useMaterial3: true, + child: Column( + children: <Widget>[ + ActionChip( + onPressed: enabled ? () { } : null, + color: color, + label: const Text('ActionChip'), + ), + ActionChip.elevated( + onPressed: enabled ? () { } : null, + color: color, + label: const Text('ActionChip.elevated'), + ), + ], + ), + ); + } + + // Test enabled state. + await tester.pumpWidget(buildApp(enabled: true, selected: false)); + + // Enabled ActionChip should have the provided backgroundColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: backgroundColor), + ); + // Enabled elevated ActionChip should have the provided backgroundColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: backgroundColor), + ); + + // Test disabled state. + await tester.pumpWidget(buildApp(enabled: false, selected: false)); + await tester.pumpAndSettle(); + + // Disabled ActionChip should have the provided disabledColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: disabledColor), + ); + // Disabled elevated ActionChip should have the provided disabledColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: disabledColor), + ); + }); + + testWidgets('ActionChip uses provided state color properties', (WidgetTester tester) async { + const Color disabledColor = Color(0xff00ff00); + const Color backgroundColor = Color(0xff0000ff); + Widget buildApp({ required bool enabled, required bool selected }) { + return wrapForChip( + useMaterial3: true, + child: Column( + children: <Widget>[ + ActionChip( + onPressed: enabled ? () { } : null, + disabledColor: disabledColor, + backgroundColor: backgroundColor, + label: const Text('ActionChip'), + ), + ActionChip.elevated( + onPressed: enabled ? () { } : null, + disabledColor: disabledColor, + backgroundColor: backgroundColor, + label: const Text('ActionChip.elevated'), + ), + ], + ), + ); + } + + // Test enabled state. + await tester.pumpWidget(buildApp(enabled: true, selected: false)); + + // Enabled ActionChip should have the provided backgroundColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: backgroundColor), + ); + // Enabled elevated ActionChip should have the provided backgroundColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: backgroundColor), + ); + + // Test disabled state. + await tester.pumpWidget(buildApp(enabled: false, selected: false)); + await tester.pumpAndSettle(); + + // Disabled ActionChip should have the provided disabledColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: disabledColor), + ); + // Disabled elevated ActionChip should have the provided disabledColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: disabledColor), + ); + }); + testWidgetsWithLeakTracking('ActionChip can be tapped', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( diff --git a/packages/flutter/test/material/adaptive_text_selection_toolbar_test.dart b/packages/flutter/test/material/adaptive_text_selection_toolbar_test.dart index 8238e057cc356..3d85f3ea76c09 100644 --- a/packages/flutter/test/material/adaptive_text_selection_toolbar_test.dart +++ b/packages/flutter/test/material/adaptive_text_selection_toolbar_test.dart @@ -11,6 +11,7 @@ import 'package:flutter_test/flutter_test.dart'; import '../foundation/leak_tracking.dart'; import '../widgets/clipboard_utils.dart'; import '../widgets/editable_text_utils.dart'; +import '../widgets/live_text_utils.dart'; void main() { final MockClipboard mockClipboard = MockClipboard(); @@ -184,6 +185,7 @@ void main() { onCut: () {}, onPaste: () {}, onSelectAll: () {}, + onLiveTextInput: () {}, ), ), ), @@ -199,17 +201,18 @@ void main() { case TargetPlatform.android: case TargetPlatform.fuchsia: expect(find.text('Select all'), findsOneWidget); - expect(find.byType(TextSelectionToolbarTextButton), findsNWidgets(4)); + expect(find.byType(TextSelectionToolbarTextButton), findsNWidgets(5)); case TargetPlatform.iOS: expect(find.text('Select All'), findsOneWidget); - expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(4)); + expect(findLiveTextButton(), findsOneWidget); + expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(5)); case TargetPlatform.linux: case TargetPlatform.windows: expect(find.text('Select all'), findsOneWidget); - expect(find.byType(DesktopTextSelectionToolbarButton), findsNWidgets(4)); + expect(find.byType(DesktopTextSelectionToolbarButton), findsNWidgets(5)); case TargetPlatform.macOS: expect(find.text('Select All'), findsOneWidget); - expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNWidgets(4)); + expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNWidgets(5)); } }, skip: kIsWeb, // [intended] on web the browser handles the context menu. diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart index d59b46e0a89bd..b9234fbe602dd 100644 --- a/packages/flutter/test/material/app_bar_test.dart +++ b/packages/flutter/test/material/app_bar_test.dart @@ -62,6 +62,15 @@ TextStyle? iconStyle(WidgetTester tester, IconData icon) { return iconRichText.text.style; } +void _verifyTextNotClipped(Finder textFinder, WidgetTester tester) { + final Rect clipRect = tester.getRect(find.ancestor(of: textFinder, matching: find.byType(ClipRect)).first); + final Rect textRect = tester.getRect(textFinder); + expect(textRect.top, inInclusiveRange(clipRect.top, clipRect.bottom)); + expect(textRect.bottom, inInclusiveRange(clipRect.top, clipRect.bottom)); + expect(textRect.left, inInclusiveRange(clipRect.left, clipRect.right)); + expect(textRect.right, inInclusiveRange(clipRect.left, clipRect.right)); +} + double appBarHeight(WidgetTester tester) => tester.getSize(find.byType(AppBar, skipOffstage: false)).height; double appBarTop(WidgetTester tester) => tester.getTopLeft(find.byType(AppBar, skipOffstage: false)).dy; double appBarBottom(WidgetTester tester) => tester.getBottomLeft(find.byType(AppBar, skipOffstage: false)).dy; @@ -700,6 +709,7 @@ void main() { theme: themeData, home: Scaffold( appBar: AppBar( + leading: IconButton(icon: const Icon(Icons.menu), onPressed: () {}), title: const Text('X'), ), drawer: const Column(), // Doesn't really matter. Triggers a hamburger regardless. @@ -1138,7 +1148,9 @@ void main() { // Test the expanded title is positioned correctly. final Offset titleOffset = tester.getBottomLeft(expandedTitle); expect(titleOffset.dx, 16.0); - expect(titleOffset.dy, closeTo(96.0, 0.1)); + expect(titleOffset.dy, 96.0); + + _verifyTextNotClipped(expandedTitle, tester); // Test the expanded title default color. expect( @@ -1223,8 +1235,14 @@ void main() { // Test the expanded title is positioned correctly. final Offset titleOffset = tester.getBottomLeft(expandedTitle); expect(titleOffset.dx, 16.0); - expect(titleOffset.dy, closeTo(128.0, 0.1)); - + final RenderSliver renderSliverAppBar = tester.renderObject(find.byType(SliverAppBar)); + // The expanded title and the bottom padding fits in the flexible space. + expect( + titleOffset.dy, + renderSliverAppBar.geometry!.scrollExtent - 28.0, + reason: 'bottom padding of a large expanded title should be 28.', + ); + _verifyTextNotClipped(expandedTitle, tester); // Test the expanded title default color. expect( @@ -1366,13 +1384,19 @@ void main() { group('SliverAppBar elevation', () { Widget buildSliverAppBar(bool forceElevated, {double? elevation, double? themeElevation}) { return MaterialApp( - theme: ThemeData(appBarTheme: AppBarTheme(elevation: themeElevation)), + theme: ThemeData( + appBarTheme: AppBarTheme( + elevation: themeElevation, + scrolledUnderElevation: themeElevation, + ), + ), home: CustomScrollView( slivers: <Widget>[ SliverAppBar( title: const Text('Title'), forceElevated: forceElevated, elevation: elevation, + scrolledUnderElevation: elevation, ), ], ), @@ -1391,8 +1415,10 @@ void main() { // Default elevation should be used by the material, but // the AppBar's elevation should not be specified by SliverAppBar. + // When useMaterial3 is true, and forceElevated is true, the default elevation + // should be the value of `scrolledUnderElevation` which is 3.0 await tester.pumpWidget(buildSliverAppBar(true)); - expect(getMaterial().elevation, useMaterial3 ? 0.0 : 4.0); + expect(getMaterial().elevation, useMaterial3 ? 3.0 : 4.0); expect(getAppBar().elevation, null); // SliverAppBar should use the specified elevation. @@ -1790,7 +1816,10 @@ void main() { await tester.pumpWidget( MaterialApp( // Test was designed against InkSplash so need to make sure that is used. - theme: ThemeData(splashFactory: InkSplash.splashFactory), + theme: ThemeData( + useMaterial3: false, + splashFactory: InkSplash.splashFactory + ), home: Center( child: AppBar( title: const Text('Abc'), @@ -2445,7 +2474,7 @@ void main() { }); testWidgets('AppBar draws a light system bar for a dark background', (WidgetTester tester) async { - final ThemeData darkTheme = ThemeData.dark(); + final ThemeData darkTheme = ThemeData.dark(useMaterial3: false); await tester.pumpWidget(MaterialApp( theme: darkTheme, home: Scaffold( @@ -2455,7 +2484,6 @@ void main() { ), )); - expect(darkTheme.primaryColorBrightness, Brightness.dark); expect(darkTheme.colorScheme.brightness, Brightness.dark); expect(SystemChrome.latestStyle, const SystemUiOverlayStyle( statusBarBrightness: Brightness.dark, @@ -2464,7 +2492,7 @@ void main() { }); testWidgets('AppBar draws a dark system bar for a light background', (WidgetTester tester) async { - final ThemeData lightTheme = ThemeData(primarySwatch: Colors.lightBlue); + final ThemeData lightTheme = ThemeData(primarySwatch: Colors.lightBlue, useMaterial3: false); await tester.pumpWidget( MaterialApp( theme: lightTheme, @@ -2476,7 +2504,6 @@ void main() { ), ); - expect(lightTheme.primaryColorBrightness, Brightness.light); expect(lightTheme.colorScheme.brightness, Brightness.light); expect(SystemChrome.latestStyle, const SystemUiOverlayStyle( statusBarBrightness: Brightness.light, @@ -2496,7 +2523,7 @@ void main() { // Using a light theme. { - await tester.pumpWidget(buildAppBar(ThemeData.from(colorScheme: const ColorScheme.light()))); + await tester.pumpWidget(buildAppBar(ThemeData(useMaterial3: false))); final Material appBarMaterial = tester.widget<Material>( find.descendant( of: find.byType(AppBar), @@ -2516,7 +2543,7 @@ void main() { // Using a dark theme. { - await tester.pumpWidget(buildAppBar(ThemeData.from(colorScheme: const ColorScheme.dark()))); + await tester.pumpWidget(buildAppBar(ThemeData.dark(useMaterial3: false))); final Material appBarMaterial = tester.widget<Material>( find.descendant( of: find.byType(AppBar), @@ -3803,7 +3830,7 @@ void main() { title: const Text('AppBar'), ), body: Scrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: controller, child: ListView( controller: controller, @@ -4240,7 +4267,7 @@ void main() { // By default, title widget should be to the right of the // leading widget and title spacing should be respected. Offset titleOffset = tester.getTopLeft(collapsedTitle); - Offset iconButtonOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu)); + Offset iconButtonOffset = tester.getTopRight(find.ancestor(of: find.widgetWithIcon(IconButton, Icons.menu), matching: find.byType(ConstrainedBox))); expect(titleOffset.dx, iconButtonOffset.dx + titleSpacing); await tester.pumpWidget(buildWidget(centerTitle: true)); @@ -4265,7 +4292,7 @@ void main() { // The title widget should be to the right of the leading // widget with no spacing. titleOffset = tester.getTopLeft(collapsedTitle); - iconButtonOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu)); + iconButtonOffset = tester.getTopRight(find.ancestor(of: find.widgetWithIcon(IconButton, Icons.menu), matching: find.byType(ConstrainedBox))); expect(titleOffset.dx, iconButtonOffset.dx); // Set centerTitle to true so the end of the title can reach @@ -4333,7 +4360,7 @@ void main() { // By default, title widget should be to the right of the leading // widget and title spacing should be respected. Offset titleOffset = tester.getTopLeft(collapsedTitle); - Offset iconButtonOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu)); + Offset iconButtonOffset = tester.getTopRight(find.ancestor(of: find.widgetWithIcon(IconButton, Icons.menu), matching: find.byType(ConstrainedBox))); expect(titleOffset.dx, iconButtonOffset.dx + titleSpacing); await tester.pumpWidget(buildWidget(centerTitle: true)); @@ -4357,7 +4384,7 @@ void main() { // The title widget should be to the right of the leading // widget with no spacing. titleOffset = tester.getTopLeft(collapsedTitle); - iconButtonOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu)); + iconButtonOffset = tester.getTopRight(find.ancestor(of: find.widgetWithIcon(IconButton, Icons.menu), matching: find.byType(ConstrainedBox))); expect(titleOffset.dx, iconButtonOffset.dx); // Set centerTitle to true so the end of the title can reach @@ -4706,13 +4733,16 @@ void main() { await tester.pumpWidget(buildAppBar()); final Finder expandedTitle = find.text(title).first; - expect(tester.getRect(expandedTitle).height, closeTo(31.9, 0.1)); + expect(tester.getRect(expandedTitle).height, 32.0); + _verifyTextNotClipped(expandedTitle, tester); await tester.pumpWidget(buildAppBar(textScaleFactor: 2.0)); - expect(tester.getRect(expandedTitle).height, closeTo(43.0, 0.1)); + expect(tester.getRect(expandedTitle).height, 43.0); + _verifyTextNotClipped(expandedTitle, tester); await tester.pumpWidget(buildAppBar(textScaleFactor: 3.0)); - expect(tester.getRect(expandedTitle).height, closeTo(43.0, 0.1)); + expect(tester.getRect(expandedTitle).height, 43.0); + _verifyTextNotClipped(expandedTitle, tester); }); testWidgets('SliverAppBar.large expanded title has upper limit on text scaling', (WidgetTester tester) async { @@ -4726,7 +4756,7 @@ void main() { child: CustomScrollView( slivers: <Widget>[ const SliverAppBar.large( - title: Text(title), + title: Text(title, maxLines: 1), ), SliverToBoxAdapter( child: Container( @@ -4771,7 +4801,7 @@ void main() { child: CustomScrollView( slivers: <Widget>[ const SliverAppBar.medium( - title: Text(title), + title: Text(title, maxLines: 1), ), SliverToBoxAdapter( child: Container( @@ -4789,24 +4819,16 @@ void main() { await tester.pumpWidget(buildAppBar()); final Finder expandedTitle = find.text(title).first; - Offset titleTop = tester.getTopLeft(expandedTitle); - expect(titleTop.dy, 64.0); - Offset titleBottom = tester.getBottomLeft(expandedTitle); - expect(titleBottom.dy, closeTo(96.0, 0.1)); + expect(tester.getBottomLeft(expandedTitle).dy, 96.0); + _verifyTextNotClipped(expandedTitle, tester); await tester.pumpWidget(buildAppBar(textScaleFactor: 2.0)); - - titleTop = tester.getTopLeft(expandedTitle); - expect(titleTop.dy, closeTo(57.0, 0.1)); - titleBottom = tester.getBottomLeft(expandedTitle); - expect(titleBottom.dy, closeTo(100.0, 0.1)); + expect(tester.getBottomLeft(expandedTitle).dy, 107.0); + _verifyTextNotClipped(expandedTitle, tester); await tester.pumpWidget(buildAppBar(textScaleFactor: 3.0)); - - titleTop = tester.getTopLeft(expandedTitle); - expect(titleTop.dy, closeTo(57.0, 0.1)); - titleBottom = tester.getBottomLeft(expandedTitle); - expect(titleBottom.dy, closeTo(100.0, 0.1)); + expect(tester.getBottomLeft(expandedTitle).dy, 107.0); + _verifyTextNotClipped(expandedTitle, tester); }); testWidgets('SliverAppBar.large expanded title position is adjusted with textScaleFactor', (WidgetTester tester) async { @@ -4820,7 +4842,7 @@ void main() { child: CustomScrollView( slivers: <Widget>[ const SliverAppBar.large( - title: Text(title), + title: Text(title, maxLines: 1), ), SliverToBoxAdapter( child: Container( @@ -4836,29 +4858,28 @@ void main() { } await tester.pumpWidget(buildAppBar()); - // TODO(tahatesser): https://github.com/flutter/flutter/issues/99933 - // A bug in the HTML renderer and/or Chrome 96+ causes a - // discrepancy in the paragraph height. - const bool hasIssue99933 = kIsWeb && !bool.fromEnvironment('FLUTTER_WEB_USE_SKIA'); final Finder expandedTitle = find.text(title).first; - Offset titleTop = tester.getTopLeft(expandedTitle); - expect(titleTop.dy, closeTo(hasIssue99933 ? 91.0 : 92.0, 0.1)); - Offset titleBottom = tester.getBottomLeft(expandedTitle); - expect(titleBottom.dy, closeTo(128.0, 0.1)); + final RenderSliver renderSliverAppBar = tester.renderObject(find.byType(SliverAppBar)); + expect( + tester.getBottomLeft(expandedTitle).dy, + renderSliverAppBar.geometry!.scrollExtent - 28.0, + reason: 'bottom padding of a large expanded title should be 28.', + ); + _verifyTextNotClipped(expandedTitle, tester); await tester.pumpWidget(buildAppBar(textScaleFactor: 2.0)); + expect( + tester.getBottomLeft(expandedTitle).dy, + renderSliverAppBar.geometry!.scrollExtent - 28.0, + reason: 'bottom padding of a large expanded title should be 28.', + ); + _verifyTextNotClipped(expandedTitle, tester); - titleTop = tester.getTopLeft(expandedTitle); - expect(titleTop.dy, closeTo(86.1, 0.1)); - titleBottom = tester.getBottomLeft(expandedTitle); - expect(titleBottom.dy, closeTo(134.1, 0.1)); - + // The bottom padding of the expanded title needs to be reduced for it to be + // fully visible. await tester.pumpWidget(buildAppBar(textScaleFactor: 3.0)); - - titleTop = tester.getTopLeft(expandedTitle); - expect(titleTop.dy, closeTo(86.1, 0.1)); - titleBottom = tester.getBottomLeft(expandedTitle); - expect(titleBottom.dy, closeTo(134.1, 0.1)); + expect(tester.getBottomLeft(expandedTitle).dy, 124.0); + _verifyTextNotClipped(expandedTitle, tester); }); group('AppBar.forceMaterialTransparency', () { @@ -4937,8 +4958,9 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('SliverAppBar.medium defaults', (WidgetTester tester) async { final ThemeData theme = ThemeData(useMaterial3: false); diff --git a/packages/flutter/test/material/app_bar_theme_test.dart b/packages/flutter/test/material/app_bar_theme_test.dart index 4866c8c753f93..e520391d1a3cb 100644 --- a/packages/flutter/test/material/app_bar_theme_test.dart +++ b/packages/flutter/test/material/app_bar_theme_test.dart @@ -44,6 +44,7 @@ void main() { testWidgets('Passing no AppBarTheme returns defaults', (WidgetTester tester) async { final ThemeData theme = ThemeData(); + final bool material3 = theme.useMaterial3; await tester.pumpWidget( MaterialApp( theme: theme, @@ -67,13 +68,15 @@ void main() { expect(SystemChrome.latestStyle!.statusBarBrightness, Brightness.light); expect(widget.color, theme.colorScheme.surface); expect(widget.elevation, 0); - expect(widget.shadowColor, null); + expect(widget.shadowColor, material3 ? Colors.transparent : null); expect(widget.surfaceTintColor, theme.colorScheme.surfaceTint); expect(widget.shape, null); expect(iconTheme.data, IconThemeData(color: theme.colorScheme.onSurface, size: 24)); expect(actionsIconTheme.data, IconThemeData(color: theme.colorScheme.onSurfaceVariant, size: 24)); - expect(actionIconText.text.style!.color, Colors.black); - expect(text.style, Typography.material2021().englishLike.bodyMedium!.merge(Typography.material2021().black.bodyMedium).copyWith(color: theme.colorScheme.onSurface)); + expect(actionIconText.text.style!.color, material3 ? theme.colorScheme.onSurfaceVariant : Colors.black); + expect(text.style, material3 + ? Typography.material2021().englishLike.bodyMedium!.merge(Typography.material2021().black.bodyMedium).copyWith(color: theme.colorScheme.onSurface, decorationColor: theme.colorScheme.onSurface) + : Typography.material2021().englishLike.bodyMedium!.merge(Typography.material2021().black.bodyMedium).copyWith(color: theme.colorScheme.onSurface)); expect(tester.getSize(find.byType(AppBar)).height, kToolbarHeight); expect(tester.getSize(find.byType(AppBar)).width, 800); } else { @@ -262,7 +265,7 @@ void main() { if (lightTheme.useMaterial3) { // M3 AppBar defaults for light themes: // - elevation: 0 - // - shadow color: null + // - shadow color: Colors.transparent // - surface tint color: ColorScheme.surfaceTint // - background color: ColorScheme.surface // - foreground color: ColorScheme.onSurface @@ -280,7 +283,7 @@ void main() { expect(SystemChrome.latestStyle!.statusBarBrightness, Brightness.light); expect(widget.color, lightTheme.colorScheme.surface); expect(widget.elevation, 0); - expect(widget.shadowColor, null); + expect(widget.shadowColor, Colors.transparent); expect(widget.surfaceTintColor, lightTheme.colorScheme.surfaceTint); expect(iconTheme.data.color, lightTheme.colorScheme.onSurface); expect(actionsIconTheme.data.color, lightTheme.colorScheme.onSurface); @@ -290,14 +293,14 @@ void main() { // M3 AppBar defaults for dark themes: // - elevation: 0 - // - shadow color: null + // - shadow color: Colors.transparent // - surface tint color: ColorScheme.surfaceTint // - background color: ColorScheme.surface // - foreground color: ColorScheme.onSurface // - actions text: style bodyMedium, foreground color // - status bar brightness: dark (based on background color) { - await tester.pumpWidget(buildFrame(ThemeData.from(colorScheme: const ColorScheme.dark()))); + await tester.pumpWidget(buildFrame(darkTheme)); await tester.pumpAndSettle(); // Theme change animation final Material widget = _getAppBarMaterial(tester); @@ -309,15 +312,15 @@ void main() { expect(SystemChrome.latestStyle!.statusBarBrightness, Brightness.dark); expect(widget.color, darkTheme.colorScheme.surface); expect(widget.elevation, 0); - expect(widget.shadowColor, null); + expect(widget.shadowColor, Colors.transparent); expect(widget.surfaceTintColor, darkTheme.colorScheme.surfaceTint); expect(iconTheme.data.color, darkTheme.colorScheme.onSurface); expect(actionsIconTheme.data.color, darkTheme.colorScheme.onSurface); expect(actionIconText.text.style!.color, darkTheme.colorScheme.onSurface); - expect(text.style, Typography.material2021().englishLike.bodyMedium!.merge(Typography.material2021().black.bodyMedium).copyWith(color: darkTheme.colorScheme.onSurface)); + expect(text.style, Typography.material2021().englishLike.bodyMedium!.merge(Typography.material2021().black.bodyMedium).copyWith(color: darkTheme.colorScheme.onSurface, decorationColor: darkTheme.colorScheme.onSurface)); } } else { - // AppBar defaults for light themes: + // AppBar M2 defaults for light themes: // - elevation: 4 // - shadow color: black // - surface tint color: null @@ -345,7 +348,7 @@ void main() { expect(text.style, Typography.material2014().englishLike.bodyMedium!.merge(Typography.material2014().black.bodyMedium).copyWith(color: lightTheme.colorScheme.onPrimary)); } - // AppBar defaults for dark themes: + // AppBar M2 defaults for dark themes: // - elevation: 4 // - shadow color: black // - surface tint color: null @@ -751,7 +754,7 @@ void main() { // Test title spacing. final Finder collapsedTitle = find.text(title).last; final Offset titleOffset = tester.getTopLeft(collapsedTitle); - final Offset iconOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu)); + final Offset iconOffset = tester.getTopRight(find.ancestor(of: find.widgetWithIcon(IconButton, Icons.menu), matching: find.byType(ConstrainedBox))); expect(titleOffset.dx, iconOffset.dx + appBarTheme.titleSpacing!); }); @@ -828,7 +831,7 @@ void main() { // Test title spacing. final Finder collapsedTitle = find.text(title).last; final Offset titleOffset = tester.getTopLeft(collapsedTitle); - final Offset iconOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu)); + final Offset iconOffset = tester.getTopRight(find.ancestor(of: find.widgetWithIcon(IconButton, Icons.menu), matching: find.byType(ConstrainedBox))); expect(titleOffset.dx, iconOffset.dx + titleSpacing); }); @@ -884,7 +887,7 @@ void main() { // Test title spacing. final Finder collapsedTitle = find.text(title).last; final Offset titleOffset = tester.getTopLeft(collapsedTitle); - final Offset iconOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu)); + final Offset iconOffset = tester.getTopRight(find.ancestor(of: find.widgetWithIcon(IconButton, Icons.menu), matching: find.byType(ConstrainedBox))); expect(titleOffset.dx, iconOffset.dx + appBarTheme.titleSpacing!); }); @@ -961,7 +964,7 @@ void main() { // Test title spacing. final Finder collapsedTitle = find.text(title).last; final Offset titleOffset = tester.getTopLeft(collapsedTitle); - final Offset iconOffset = tester.getTopRight(find.widgetWithIcon(IconButton, Icons.menu)); + final Offset iconOffset = tester.getTopRight(find.ancestor(of: find.widgetWithIcon(IconButton, Icons.menu), matching: find.byType(ConstrainedBox))); expect(titleOffset.dx, iconOffset.dx + titleSpacing); }); @@ -1082,7 +1085,7 @@ Material _getAppBarMaterial(WidgetTester tester) { find.descendant( of: find.byType(AppBar), matching: find.byType(Material), - ), + ).first, ); } diff --git a/packages/flutter/test/material/app_builder_test.dart b/packages/flutter/test/material/app_builder_test.dart index c63b84e16b200..851a88edcf82d 100644 --- a/packages/flutter/test/material/app_builder_test.dart +++ b/packages/flutter/test/material/app_builder_test.dart @@ -10,6 +10,7 @@ void main() { final List<String> log = <String>[]; final Widget app = MaterialApp( theme: ThemeData( + useMaterial3: false, primarySwatch: Colors.green, ), home: const Placeholder(), @@ -42,6 +43,7 @@ void main() { await tester.pumpWidget( MaterialApp( theme: ThemeData( + useMaterial3: false, primarySwatch: Colors.yellow, ), home: Builder( diff --git a/packages/flutter/test/material/app_test.dart b/packages/flutter/test/material/app_test.dart index 59ba965cd7498..734aa878be7a8 100644 --- a/packages/flutter/test/material/app_test.dart +++ b/packages/flutter/test/material/app_test.dart @@ -995,6 +995,7 @@ void main() { const Color secondaryColor = Color(0xff008800); final Color glowSecondaryColor = secondaryColor.withOpacity(0.05); final ThemeData theme = ThemeData.from( + useMaterial3: false, colorScheme: const ColorScheme.light().copyWith(secondary: secondaryColor), ); await tester.pumpWidget( @@ -1264,6 +1265,7 @@ void main() { testWidgets('ScrollBehavior default android overscroll indicator', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), scrollBehavior: const MaterialScrollBehavior(), home: ListView( children: const <Widget>[ diff --git a/packages/flutter/test/material/autocomplete_test.dart b/packages/flutter/test/material/autocomplete_test.dart index ab77b522ec67e..ad33efd68c667 100644 --- a/packages/flutter/test/material/autocomplete_test.dart +++ b/packages/flutter/test/material/autocomplete_test.dart @@ -459,7 +459,7 @@ void main() { const Color highlightColor = Color(0xFF112233); await tester.pumpWidget( MaterialApp( - theme: ThemeData.light().copyWith( + theme: ThemeData.light(useMaterial3: false).copyWith( focusColor: highlightColor, ), home: Scaffold( diff --git a/packages/flutter/test/material/badge_test.dart b/packages/flutter/test/material/badge_test.dart index 8f853bf2f3b44..4b97861418da9 100644 --- a/packages/flutter/test/material/badge_test.dart +++ b/packages/flutter/test/material/badge_test.dart @@ -50,7 +50,10 @@ void main() { expect(tester.getTopLeft(find.text('0')), const Offset(16, -4)); final RenderBox box = tester.renderObject(find.byType(Badge)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(12, -4, 32, 12, const Radius.circular(8)), color: theme.colorScheme.error)); + final RRect rrect = const bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') + ? RRect.fromLTRBR(12, -4, 31.5, 12, const Radius.circular(8)) + : RRect.fromLTRBR(12, -4, 32, 12, const Radius.circular(8)); + expect(box, paints..rrect(rrect: rrect, color: theme.colorScheme.error)); }); testWidgets('Large Badge defaults with RTL', (WidgetTester tester) async { @@ -89,7 +92,10 @@ void main() { expect(tester.getTopLeft(find.text('0')), const Offset(0, -4)); final RenderBox box = tester.renderObject(find.byType(Badge)); - expect(box, paints..rrect(rrect: RRect.fromLTRBR(-4, -4, 16, 12, const Radius.circular(8)), color: theme.colorScheme.error)); + final RRect rrect = const bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') + ? RRect.fromLTRBR(-4, -4, 15.5, 12, const Radius.circular(8)) + : RRect.fromLTRBR(-4, -4, 16, 12, const Radius.circular(8)); + expect(box, paints..rrect(rrect: rrect, color: theme.colorScheme.error)); }); // Essentially the same as 'Large Badge defaults' @@ -143,7 +149,10 @@ void main() { // T = alignment.top // R = L + '0'.width + padding.width // B = T + largeSize, R = largeSize/2 - expect(box, paints..rrect(rrect: RRect.fromLTRBR(12, -4, 32, 12, const Radius.circular(8)), color: theme.colorScheme.error)); + final RRect rrect = const bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') + ? RRect.fromLTRBR(12, -4, 31.5, 12, const Radius.circular(8)) + : RRect.fromLTRBR(12, -4, 32, 12, const Radius.circular(8)); + expect(box, paints..rrect(rrect: rrect, color: theme.colorScheme.error)); await tester.pumpWidget(buildFrame(1000)); expect(find.text('999+'), findsOneWidget); diff --git a/packages/flutter/test/material/banner_theme_test.dart b/packages/flutter/test/material/banner_theme_test.dart index 0c12b92964938..4fd5f3a05c319 100644 --- a/packages/flutter/test/material/banner_theme_test.dart +++ b/packages/flutter/test/material/banner_theme_test.dart @@ -449,13 +449,15 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('Passing no MaterialBannerThemeData returns defaults', (WidgetTester tester) async { const String contentText = 'Content'; await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: MaterialBanner( content: const Text(contentText), @@ -502,6 +504,7 @@ void main() { const Key tapTarget = Key('tap-target'); await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Builder( builder: (BuildContext context) { diff --git a/packages/flutter/test/material/bottom_app_bar_test.dart b/packages/flutter/test/material/bottom_app_bar_test.dart index 6e5075b535716..5e2c9c82a6f36 100644 --- a/packages/flutter/test/material/bottom_app_bar_test.dart +++ b/packages/flutter/test/material/bottom_app_bar_test.dart @@ -120,6 +120,7 @@ void main() { child: RepaintBoundary( key: key, child: MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( floatingActionButton: FloatingActionButton( onPressed: () { }, @@ -217,9 +218,10 @@ void main() { expect(babRect, const Rect.fromLTRB(240, 520, 560, 600)); }); - testWidgets('color defaults to Theme.bottomAppBarColor', (WidgetTester tester) async { + testWidgets('color defaults to Theme.bottomAppBarColor in M2', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { return Theme( @@ -245,6 +247,7 @@ void main() { testWidgets('color overrides theme color', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { return Theme( @@ -324,7 +327,7 @@ void main() { testWidgets('dark theme applies an elevation overlay color', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: const ColorScheme.dark()), + theme: ThemeData.from(useMaterial3: false, colorScheme: const ColorScheme.dark()), home: Scaffold( bottomNavigationBar: BottomAppBar( color: const ColorScheme.dark().surface, @@ -508,8 +511,9 @@ void main() { testWidgets('observes safe area', (WidgetTester tester) async { await tester.pumpWidget( - const MaterialApp( - home: MediaQuery( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const MediaQuery( data: MediaQueryData( padding: EdgeInsets.all(50.0), ), @@ -566,8 +570,10 @@ void main() { testWidgets('BottomAppBar with shape when Scaffold.bottomNavigationBar == null', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/80878 + final ThemeData theme = ThemeData(); await tester.pumpWidget( MaterialApp( + theme: theme, home: Scaffold( floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButton: FloatingActionButton( @@ -595,7 +601,7 @@ void main() { ); expect(tester.getRect(find.byType(FloatingActionButton)), const Rect.fromLTRB(372, 528, 428, 584)); - expect(tester.getSize(find.byType(BottomAppBar)), const Size(800, 50)); + expect(tester.getSize(find.byType(BottomAppBar)), theme.useMaterial3 ? const Size(800, 80) : const Size(800, 50)); }); testWidgets('notch with margin and top padding, home safe area', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/bottom_app_bar_theme_test.dart b/packages/flutter/test/material/bottom_app_bar_theme_test.dart index e3a811f5a56bc..bd08943f4250d 100644 --- a/packages/flutter/test/material/bottom_app_bar_theme_test.dart +++ b/packages/flutter/test/material/bottom_app_bar_theme_test.dart @@ -36,6 +36,7 @@ void main() { await tester.pumpWidget(MaterialApp( theme: ThemeData( + useMaterial3: false, bottomAppBarTheme: theme, bottomAppBarColor: themeColor ), @@ -53,6 +54,7 @@ void main() { await tester.pumpWidget(MaterialApp( theme: ThemeData( + useMaterial3: false, bottomAppBarTheme: theme, bottomAppBarColor: themeColor ), @@ -67,7 +69,7 @@ void main() { const Color themeColor = Colors.white10; await tester.pumpWidget(MaterialApp( - theme: ThemeData(bottomAppBarColor: themeColor), + theme: ThemeData(useMaterial3: false, bottomAppBarColor: themeColor), home: const Scaffold(body: BottomAppBar()), )); @@ -77,7 +79,7 @@ void main() { testWidgets('BAB color - Default', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( - theme: ThemeData(), + theme: ThemeData(useMaterial3: false), home: const Scaffold(body: BottomAppBar()), )); @@ -102,8 +104,9 @@ void main() { }); testWidgets('BAB theme does not affect defaults', (WidgetTester tester) async { - await tester.pumpWidget(const MaterialApp( - home: Scaffold(body: BottomAppBar()), + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold(body: BottomAppBar()), )); final PhysicalShape widget = _getBabRenderObject(tester); diff --git a/packages/flutter/test/material/bottom_navigation_bar_test.dart b/packages/flutter/test/material/bottom_navigation_bar_test.dart index 5bb0592ee209f..a73239ad9c4c1 100644 --- a/packages/flutter/test/material/bottom_navigation_bar_test.dart +++ b/packages/flutter/test/material/bottom_navigation_bar_test.dart @@ -52,6 +52,7 @@ void main() { testWidgets('BottomNavigationBar content test', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( bottomNavigationBar: BottomNavigationBar( items: const <BottomNavigationBarItem>[ @@ -81,7 +82,7 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: ThemeData.light().copyWith( + theme: ThemeData.light(useMaterial3: false).copyWith( colorScheme: const ColorScheme.light().copyWith(primary: primaryColor), unselectedWidgetColor: unselectedWidgetColor, ), @@ -413,6 +414,7 @@ void main() { testWidgets('Shifting BottomNavigationBar defaults', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.shifting, @@ -991,6 +993,7 @@ void main() { testWidgets('BottomNavigationBar adds bottom padding to height', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: MediaQuery( data: const MediaQueryData(viewPadding: EdgeInsets.only(bottom: 40.0)), child: Scaffold( @@ -1323,6 +1326,7 @@ void main() { testWidgets('BottomNavigationBar responds to textScaleFactor', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, @@ -1396,6 +1400,7 @@ void main() { testWidgets('BottomNavigationBar does not grow with textScaleFactor when labels are provided', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, @@ -1484,19 +1489,22 @@ void main() { onGenerateRoute: (RouteSettings settings) { return MaterialPageRoute<void>( builder: (BuildContext context) { - return Scaffold( - bottomNavigationBar: BottomNavigationBar( - items: const <BottomNavigationBarItem>[ - BottomNavigationBarItem( - label: label, - icon: Icon(Icons.ac_unit), - tooltip: label, - ), - BottomNavigationBarItem( - label: 'B', - icon: Icon(Icons.battery_alert), - ), - ], + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Scaffold( + bottomNavigationBar: BottomNavigationBar( + items: const <BottomNavigationBarItem>[ + BottomNavigationBarItem( + label: label, + icon: Icon(Icons.ac_unit), + tooltip: label, + ), + BottomNavigationBarItem( + label: 'B', + icon: Icon(Icons.battery_alert), + ), + ], + ), ), ); }, @@ -1566,6 +1574,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( bottomNavigationBar: BottomNavigationBar( items: <BottomNavigationBarItem>[ @@ -1595,6 +1604,7 @@ void main() { testWidgets('BottomNavigationBar paints circles', (WidgetTester tester) async { await tester.pumpWidget( boilerplate( + useMaterial3: false, textDirection: TextDirection.ltr, bottomNavigationBar: BottomNavigationBar( items: const <BottomNavigationBarItem>[ @@ -1924,6 +1934,7 @@ void main() { Widget runTest() { int currentIndex = 0; return MaterialApp( + theme: ThemeData(useMaterial3: false), home: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Scaffold( @@ -2363,6 +2374,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { return Scaffold( @@ -2410,6 +2422,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { return Scaffold( @@ -2455,6 +2468,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { return Scaffold( @@ -2490,8 +2504,9 @@ void main() { }); } -Widget boilerplate({ Widget? bottomNavigationBar, required TextDirection textDirection }) { +Widget boilerplate({ Widget? bottomNavigationBar, required TextDirection textDirection, bool? useMaterial3 }) { return MaterialApp( + theme: ThemeData(useMaterial3: useMaterial3), home: Localizations( locale: const Locale('en', 'US'), delegates: const <LocalizationsDelegate<dynamic>>[ diff --git a/packages/flutter/test/material/bottom_sheet_test.dart b/packages/flutter/test/material/bottom_sheet_test.dart index 3789255283a37..b6e5db84011c8 100644 --- a/packages/flutter/test/material/bottom_sheet_test.dart +++ b/packages/flutter/test/material/bottom_sheet_test.dart @@ -199,6 +199,43 @@ void main() { expect(find.text('BottomSheet'), findsNothing); }); + testWidgets('Tapping on a BottomSheet should not trigger a rebuild when enableDrag is true', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/126833. + final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); + int buildCount = 0; + + await tester.pumpWidget(MaterialApp( + home: Scaffold( + key: scaffoldKey, + body: const Center(child: Text('body')), + ), + )); + + await tester.pump(); + expect(buildCount, 0); + expect(find.text('BottomSheet'), findsNothing); + + scaffoldKey.currentState!.showBottomSheet<void>((BuildContext context) { + buildCount++; + return const SizedBox( + height: 200.0, + child: Text('BottomSheet'), + ); + }, + enableDrag: true, + ); + + await tester.pumpAndSettle(); + expect(buildCount, 1); + expect(find.text('BottomSheet'), findsOneWidget); + + // Tap on bottom sheet should not trigger a rebuild. + await tester.tap(find.text('BottomSheet')); + await tester.pumpAndSettle(); + expect(buildCount, 1); + expect(find.text('BottomSheet'), findsOneWidget); + }); + testWidgets('Modal BottomSheet builder should only be called once', (WidgetTester tester) async { late BuildContext savedContext; @@ -942,6 +979,7 @@ void main() { final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( key: scaffoldKey, body: const Center(child: Text('body')), @@ -1143,6 +1181,7 @@ void main() { testWidgets('showModalBottomSheet does not use root Navigator by default', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Navigator(onGenerateRoute: (RouteSettings settings) => MaterialPageRoute<void>(builder: (_) { return const _TestPage(); @@ -1365,9 +1404,9 @@ void main() { reverseDuration: const Duration(seconds: 2), ), builder: (BuildContext context) { - return MaterialButton( - onPressed: () => Navigator.pop(context), + return ElevatedButton( key: tapTargetToClose, + onPressed: () => Navigator.pop(context), child: const Text('BottomSheet'), ); }, @@ -1514,9 +1553,9 @@ void main() { context: context, transitionAnimationController: controller, builder: (BuildContext context) { - return MaterialButton( - onPressed: () => Navigator.pop(context), + return ElevatedButton( key: tapTargetToClose, + onPressed: () => Navigator.pop(context), child: const Text('BottomSheet'), ); }, @@ -1767,8 +1806,9 @@ void main() { }); testWidgets('No constraints by default for bottomSheet property', (WidgetTester tester) async { - await tester.pumpWidget(const MaterialApp( - home: Scaffold( + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( body: Center(child: Text('body')), bottomSheet: Text('BottomSheet'), ), @@ -1782,6 +1822,7 @@ void main() { testWidgets('No constraints by default for showBottomSheet', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Builder(builder: (BuildContext context) { return Center( @@ -1809,6 +1850,7 @@ void main() { testWidgets('No constraints by default for showModalBottomSheet', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Builder(builder: (BuildContext context) { return Center( @@ -1838,6 +1880,7 @@ void main() { testWidgets('Theme constraints used for bottomSheet property', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData( + useMaterial3: false, bottomSheetTheme: const BottomSheetThemeData( constraints: BoxConstraints(maxWidth: 80), ), @@ -1865,6 +1908,7 @@ void main() { testWidgets('Theme constraints used for showBottomSheet', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData( + useMaterial3: false, bottomSheetTheme: const BottomSheetThemeData( constraints: BoxConstraints(maxWidth: 80), ), @@ -1898,6 +1942,7 @@ void main() { testWidgets('Theme constraints used for showModalBottomSheet', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData( + useMaterial3: false, bottomSheetTheme: const BottomSheetThemeData( constraints: BoxConstraints(maxWidth: 80), ), @@ -1932,6 +1977,7 @@ void main() { testWidgets('constraints param overrides theme for showBottomSheet', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData( + useMaterial3: false, bottomSheetTheme: const BottomSheetThemeData( constraints: BoxConstraints(maxWidth: 80), ), @@ -1966,6 +2012,7 @@ void main() { testWidgets('constraints param overrides theme for showModalBottomSheet', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData( + useMaterial3: false, bottomSheetTheme: const BottomSheetThemeData( constraints: BoxConstraints(maxWidth: 80), ), diff --git a/packages/flutter/test/material/bottom_sheet_theme_test.dart b/packages/flutter/test/material/bottom_sheet_theme_test.dart index 44a740584a9ee..7a965b59711b4 100644 --- a/packages/flutter/test/material/bottom_sheet_theme_test.dart +++ b/packages/flutter/test/material/bottom_sheet_theme_test.dart @@ -80,6 +80,7 @@ void main() { testWidgets('Passing no BottomSheetThemeData returns defaults', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: BottomSheet( onClosing: () {}, @@ -256,14 +257,14 @@ void main() { const Color darkShadowColor = Colors.purple; await tester.pumpWidget(MaterialApp( - theme: ThemeData.light().copyWith( + theme: ThemeData.light(useMaterial3: false).copyWith( bottomSheetTheme: const BottomSheetThemeData( elevation: lightElevation, backgroundColor: lightBackgroundColor, shadowColor: lightShadowColor, ), ), - darkTheme: ThemeData.dark().copyWith( + darkTheme: ThemeData.dark(useMaterial3: false).copyWith( bottomSheetTheme: const BottomSheetThemeData( elevation: darkElevation, backgroundColor: darkBackgroundColor, @@ -323,7 +324,7 @@ void main() { Widget bottomSheetWithElevations(BottomSheetThemeData bottomSheetTheme) { return MaterialApp( - theme: ThemeData(bottomSheetTheme: bottomSheetTheme), + theme: ThemeData(bottomSheetTheme: bottomSheetTheme, useMaterial3: false), home: Scaffold( body: Builder( builder: (BuildContext context) { diff --git a/packages/flutter/test/material/calendar_date_picker_test.dart b/packages/flutter/test/material/calendar_date_picker_test.dart index acc4c54815927..038302dfd7afc 100644 --- a/packages/flutter/test/material/calendar_date_picker_test.dart +++ b/packages/flutter/test/material/calendar_date_picker_test.dart @@ -28,6 +28,7 @@ void main() { TextDirection textDirection = TextDirection.ltr, }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Directionality( textDirection: textDirection, @@ -687,152 +688,182 @@ void main() { // Day grid. expect(tester.getSemantics(find.text('1')), matchesSemantics( label: '1, Friday, January 1, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('2')), matchesSemantics( label: '2, Saturday, January 2, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('3')), matchesSemantics( label: '3, Sunday, January 3, 2016, Today', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('4')), matchesSemantics( label: '4, Monday, January 4, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('5')), matchesSemantics( label: '5, Tuesday, January 5, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('6')), matchesSemantics( label: '6, Wednesday, January 6, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('7')), matchesSemantics( label: '7, Thursday, January 7, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('8')), matchesSemantics( label: '8, Friday, January 8, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('9')), matchesSemantics( label: '9, Saturday, January 9, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('10')), matchesSemantics( label: '10, Sunday, January 10, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('11')), matchesSemantics( label: '11, Monday, January 11, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('12')), matchesSemantics( label: '12, Tuesday, January 12, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('13')), matchesSemantics( label: '13, Wednesday, January 13, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('14')), matchesSemantics( label: '14, Thursday, January 14, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('15')), matchesSemantics( label: '15, Friday, January 15, 2016', + isButton: true, hasTapAction: true, isSelected: true, isFocusable: true, )); expect(tester.getSemantics(find.text('16')), matchesSemantics( label: '16, Saturday, January 16, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('17')), matchesSemantics( label: '17, Sunday, January 17, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('18')), matchesSemantics( label: '18, Monday, January 18, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('19')), matchesSemantics( label: '19, Tuesday, January 19, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('20')), matchesSemantics( label: '20, Wednesday, January 20, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('21')), matchesSemantics( label: '21, Thursday, January 21, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('22')), matchesSemantics( label: '22, Friday, January 22, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('23')), matchesSemantics( label: '23, Saturday, January 23, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('24')), matchesSemantics( label: '24, Sunday, January 24, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('25')), matchesSemantics( label: '25, Monday, January 25, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('26')), matchesSemantics( label: '26, Tuesday, January 26, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('27')), matchesSemantics( label: '27, Wednesday, January 27, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('28')), matchesSemantics( label: '28, Thursday, January 28, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('29')), matchesSemantics( label: '29, Friday, January 29, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); expect(tester.getSemantics(find.text('30')), matchesSemantics( label: '30, Saturday, January 30, 2016', + isButton: true, hasTapAction: true, isFocusable: true, )); diff --git a/packages/flutter/test/material/card_test.dart b/packages/flutter/test/material/card_test.dart index ae549b39d4894..738104686adf9 100644 --- a/packages/flutter/test/material/card_test.dart +++ b/packages/flutter/test/material/card_test.dart @@ -22,9 +22,9 @@ void main() { children: <Widget>[ const Text('I am text!'), const Text('Moar text!!1'), - MaterialButton( - child: const Text('Button'), + ElevatedButton( onPressed: () { }, + child: const Text('Button'), ), ], ), diff --git a/packages/flutter/test/material/card_theme_test.dart b/packages/flutter/test/material/card_theme_test.dart index f448c361ad88b..992fbf937b222 100644 --- a/packages/flutter/test/material/card_theme_test.dart +++ b/packages/flutter/test/material/card_theme_test.dart @@ -119,6 +119,7 @@ void main() { testWidgets('ThemeData properties are used when no CardTheme is set', (WidgetTester tester) async { final ThemeData themeData = _themeData(); + final bool material3 = themeData.useMaterial3; await tester.pumpWidget(MaterialApp( theme: themeData, @@ -128,7 +129,7 @@ void main() { )); final Material material = _getCardMaterial(tester); - expect(material.color, themeData.cardColor); + expect(material.color, material3 ? themeData.colorScheme.surface: themeData.cardColor); }); testWidgets('CardTheme customizes shape', (WidgetTester tester) async { @@ -161,8 +162,9 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('Passing no CardTheme returns defaults - M2', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( diff --git a/packages/flutter/test/material/checkbox_list_tile_test.dart b/packages/flutter/test/material/checkbox_list_tile_test.dart index 247b01f4ece46..5a7754e68847d 100644 --- a/packages/flutter/test/material/checkbox_list_tile_test.dart +++ b/packages/flutter/test/material/checkbox_list_tile_test.dart @@ -42,11 +42,14 @@ void main() { Color checkBoxCheckColor = const Color(0xffFFFFFF); Widget buildFrame(Color? color) { - return wrap( - child: CheckboxListTile( - value: true, - checkColor: color, - onChanged: (bool? value) {}, + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: CheckboxListTile( + value: true, + checkColor: color, + onChanged: (bool? value) {}, + ), ), ); } @@ -730,14 +733,17 @@ void main() { const double splashRadius = 24.0; Widget buildCheckbox({bool active = false, bool useOverlay = true}) { - return wrap( - child: CheckboxListTile( - value: active, - onChanged: (_) { }, - fillColor: const MaterialStatePropertyAll<Color>(fillColor), - overlayColor: useOverlay ? MaterialStateProperty.resolveWith(getOverlayColor) : null, - hoverColor: hoverColor, - splashRadius: splashRadius, + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: CheckboxListTile( + value: active, + onChanged: (_) { }, + fillColor: const MaterialStatePropertyAll<Color>(fillColor), + overlayColor: useOverlay ? MaterialStateProperty.resolveWith(getOverlayColor) : null, + hoverColor: hoverColor, + splashRadius: splashRadius, + ), ), ); } diff --git a/packages/flutter/test/material/checkbox_test.dart b/packages/flutter/test/material/checkbox_test.dart index 9bdf0bb3a3c06..8a7a592f24e12 100644 --- a/packages/flutter/test/material/checkbox_test.dart +++ b/packages/flutter/test/material/checkbox_test.dart @@ -474,14 +474,16 @@ void main() { }); testWidgets('Checkbox color rendering', (WidgetTester tester) async { + final ThemeData theme = ThemeData(); const Color borderColor = Color(0xff2196f3); + const Color m3BorderColor = Color(0xFF6750A4); Color checkColor = const Color(0xffFFFFFF); Color activeColor; Widget buildFrame({Color? activeColor, Color? checkColor, ThemeData? themeData}) { return Material( child: Theme( - data: themeData ?? ThemeData(), + data: themeData ?? theme, child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Checkbox( @@ -502,13 +504,13 @@ void main() { await tester.pumpWidget(buildFrame(checkColor: checkColor)); await tester.pumpAndSettle(); - expect(getCheckboxRenderer(), paints..path(color: borderColor)..path(color: checkColor)); // paints's color is 0xFFFFFFFF (default color) + expect(getCheckboxRenderer(), paints..path(color: theme.useMaterial3 ? m3BorderColor : borderColor)..path(color: checkColor)); // paints's color is 0xFFFFFFFF (default color) checkColor = const Color(0xFF000000); await tester.pumpWidget(buildFrame(checkColor: checkColor)); await tester.pumpAndSettle(); - expect(getCheckboxRenderer(), paints..path(color: borderColor)..path(color: checkColor)); // paints's color is 0xFF000000 (params) + expect(getCheckboxRenderer(), paints..path(color: theme.useMaterial3 ? m3BorderColor : borderColor)..path(color: checkColor)); // paints's color is 0xFF000000 (params) activeColor = const Color(0xFF00FF00); @@ -520,7 +522,7 @@ void main() { themeData = themeData.copyWith(colorScheme: colorScheme); await tester.pumpWidget(buildFrame( themeData: themeData), - ); + ); await tester.pumpAndSettle(); expect(getCheckboxRenderer(), paints..path(color: activeColor)); // paints's color is 0xFF00FF00 (theme) @@ -567,7 +569,7 @@ void main() { material3 ? (paints ..circle(color: Colors.orange[500]) - ..path(color: const Color(0xff2196f3)) + ..path(color: theme.colorScheme.primary) ..path(color: theme.colorScheme.onPrimary)) : (paints ..circle(color: Colors.orange[500]) @@ -586,7 +588,7 @@ void main() { ..circle(color: Colors.orange[500]) ..drrect( color: material3 ? theme.colorScheme.onSurface : const Color(0x8a000000), - outer: RRect.fromLTRBR(15.0, 15.0, 33.0, 33.0, const Radius.circular(1.0)), + outer: RRect.fromLTRBR(15.0, 15.0, 33.0, 33.0, material3 ? const Radius.circular(2.0) : const Radius.circular(1.0)), inner: RRect.fromLTRBR(17.0, 17.0, 31.0, 31.0, Radius.zero), ), ); @@ -601,7 +603,7 @@ void main() { paints ..drrect( color: material3 ? theme.colorScheme.onSurface.withOpacity(0.38) : const Color(0x61000000), - outer: RRect.fromLTRBR(15.0, 15.0, 33.0, 33.0, const Radius.circular(1.0)), + outer: RRect.fromLTRBR(15.0, 15.0, 33.0, 33.0, material3 ? const Radius.circular(2.0) : const Radius.circular(1.0)), inner: RRect.fromLTRBR(17.0, 17.0, 31.0, 31.0, Radius.zero), ), ); @@ -697,7 +699,7 @@ void main() { expect( Material.of(tester.element(find.byType(Checkbox))), paints - ..path(color: const Color(0xff2196f3)) + ..path(color: material3 ? const Color(0xff6750a4) : const Color(0xff2196f3)) ..path(color: material3 ? theme.colorScheme.onPrimary : const Color(0xffffffff), style: PaintingStyle.stroke, strokeWidth: 2.0), ); @@ -710,7 +712,7 @@ void main() { expect( Material.of(tester.element(find.byType(Checkbox))), paints - ..path(color: const Color(0xff2196f3)) + ..path(color: material3 ? const Color(0xff6750a4) : const Color(0xff2196f3)) ..path(color: material3 ? theme.colorScheme.onPrimary : const Color(0xffffffff), style: PaintingStyle.stroke, strokeWidth: 2.0), ); @@ -1450,7 +1452,7 @@ void main() { await gesture.up(); }); - testWidgets('Checkbox BorderSide side only applies when unselected', (WidgetTester tester) async { + testWidgets('Checkbox BorderSide side only applies when unselected in M2', (WidgetTester tester) async { const Color borderColor = Color(0xfff44336); const Color activeColor = Color(0xff123456); const BorderSide side = BorderSide( @@ -1460,7 +1462,7 @@ void main() { Widget buildApp({ bool? value, bool enabled = true }) { return MaterialApp( - theme: theme, + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: Checkbox( @@ -1520,6 +1522,7 @@ void main() { width: 4, color: borderColor, ); + final bool material3 = theme.useMaterial3; Widget buildApp({ bool? value, bool enabled = true }) { return MaterialApp( @@ -1543,7 +1546,7 @@ void main() { paints ..drrect( color: borderColor, - outer: RRect.fromLTRBR(15, 15, 33, 33, const Radius.circular(1)), + outer: material3 ? RRect.fromLTRBR(15, 15, 33, 33, const Radius.circular(2)) : RRect.fromLTRBR(15, 15, 33, 33, const Radius.circular(1)), inner: RRect.fromLTRBR(19, 19, 29, 29, Radius.zero), ), ); diff --git a/packages/flutter/test/material/chip_test.dart b/packages/flutter/test/material/chip_test.dart index 46aa1ecf39d77..baa306a29404a 100644 --- a/packages/flutter/test/material/chip_test.dart +++ b/packages/flutter/test/material/chip_test.dart @@ -74,9 +74,10 @@ Widget wrapForChip({ TextDirection textDirection = TextDirection.ltr, double textScaleFactor = 1.0, Brightness brightness = Brightness.light, + bool? useMaterial3, }) { return MaterialApp( - theme: ThemeData(brightness: brightness), + theme: ThemeData(brightness: brightness, useMaterial3: useMaterial3), home: Directionality( textDirection: textDirection, child: MediaQuery( @@ -142,9 +143,11 @@ Widget chipWithOptionalDeleteButton({ String? chipTooltip, String? deleteButtonTooltipMessage, VoidCallback? onPressed = doNothing, + bool? useMaterial3, }) { return wrapForChip( textDirection: textDirection, + useMaterial3: useMaterial3, child: Wrap( children: <Widget>[ RawChip( @@ -220,7 +223,7 @@ void main() { Widget buildFrame(Brightness brightness) { return MaterialApp( - theme: ThemeData(brightness: brightness), + theme: ThemeData(brightness: brightness, useMaterial3: false), home: Scaffold( body: Center( child: Builder( @@ -579,6 +582,7 @@ void main() { const TextStyle style = TextStyle(fontSize: 10.0); await tester.pumpWidget( wrapForChip( + useMaterial3: false, child: const Row( children: <Widget>[ Chip(label: Text('Test'), labelStyle: style), @@ -615,6 +619,7 @@ void main() { testWidgets('Chip responds to materialTapTargetSize', (WidgetTester tester) async { await tester.pumpWidget( wrapForChip( + useMaterial3: false, child: const Column( children: <Widget>[ Chip( @@ -731,6 +736,7 @@ void main() { testWidgets('Chip responds to textScaleFactor', (WidgetTester tester) async { await tester.pumpWidget( wrapForChip( + useMaterial3: false, child: const Column( children: <Widget>[ Chip( @@ -809,6 +815,7 @@ void main() { final Key keyB = GlobalKey(); await tester.pumpWidget( wrapForChip( + useMaterial3: false, child: Column( children: <Widget>[ Chip( @@ -946,6 +953,7 @@ void main() { Future<void> pushChip({ Widget? avatar }) async { return tester.pumpWidget( wrapForChip( + useMaterial3: false, child: Wrap( children: <Widget>[ RawChip( @@ -1060,6 +1068,7 @@ void main() { Future<void> pushChip({ bool deletable = false }) async { return tester.pumpWidget( wrapForChip( + useMaterial3: false, child: Wrap( children: <Widget>[ StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -1211,6 +1220,7 @@ void main() { await tester.pumpWidget( chipWithOptionalDeleteButton( + useMaterial3: false, labelKey: labelKey, deleteButtonKey: deleteButtonKey, deletable: true, @@ -1293,6 +1303,7 @@ void main() { await tester.pumpWidget( chipWithOptionalDeleteButton( + useMaterial3: false, labelKey: labelKey, deleteButtonKey: deleteButtonKey, deletable: true, @@ -1346,6 +1357,7 @@ void main() { await tester.pumpWidget( chipWithOptionalDeleteButton( + useMaterial3: false, labelKey: labelKey, onPressed: null, deleteButtonKey: deleteButtonKey, @@ -1430,6 +1442,7 @@ void main() { await tester.pumpWidget( chipWithOptionalDeleteButton( + useMaterial3: false, labelKey: labelKey, deletable: false, ), @@ -1484,6 +1497,7 @@ void main() { Future<void> pushChip({ Widget? avatar, bool selectable = false }) async { return tester.pumpWidget( wrapForChip( + useMaterial3: false, child: Wrap( children: <Widget>[ StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -1564,6 +1578,7 @@ void main() { Future<void> pushChip({ bool selectable = false }) async { return tester.pumpWidget( wrapForChip( + useMaterial3: false, child: Wrap( children: <Widget>[ StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -1637,6 +1652,7 @@ void main() { Future<void> pushChip({ Widget? avatar, bool selectable = false }) async { return tester.pumpWidget( wrapForChip( + useMaterial3: false, child: Wrap( children: <Widget>[ StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -1689,6 +1705,7 @@ void main() { testWidgets('Chip uses ThemeData chip theme if present', (WidgetTester tester) async { final ThemeData theme = ThemeData( + useMaterial3: false, platform: TargetPlatform.android, primarySwatch: Colors.red, ); @@ -1792,7 +1809,7 @@ void main() { await tester.pumpWidget( wrapForChip( child: Theme( - data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded), + data: ThemeData(useMaterial3: false, materialTapTargetSize: MaterialTapTargetSize.padded), child: Center( child: RawChip( key: key1, @@ -1809,7 +1826,7 @@ void main() { await tester.pumpWidget( wrapForChip( child: Theme( - data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap), + data: ThemeData(useMaterial3: false, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap), child: Center( child: RawChip( key: key2, @@ -2466,6 +2483,7 @@ void main() { testWidgets('Chip elevation and shadow color work correctly', (WidgetTester tester) async { final ThemeData theme = ThemeData( + useMaterial3: false, platform: TargetPlatform.android, primarySwatch: Colors.red, ); @@ -2570,12 +2588,17 @@ void main() { }); testWidgets('selected chip and avatar draw darkened layer within avatar circle', (WidgetTester tester) async { - await tester.pumpWidget(wrapForChip(child: const FilterChip( - avatar: CircleAvatar(child: Text('t')), - label: Text('test'), - selected: true, - onSelected: null, - ))); + await tester.pumpWidget( + wrapForChip( + useMaterial3: false, + child: const FilterChip( + avatar: CircleAvatar(child: Text('t')), + label: Text('test'), + selected: true, + onSelected: null, + ), + ), + ); final RenderBox rawChip = tester.firstRenderObject<RenderBox>( find.descendant( of: find.byType(RawChip), @@ -2725,6 +2748,7 @@ void main() { Widget chipWidget({ bool enabled = true, bool selected = false }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Focus( focusNode: focusNode, @@ -2804,6 +2828,7 @@ void main() { Widget chipWidget({ bool enabled = true, bool selected = false }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Focus( focusNode: focusNode, @@ -2886,6 +2911,7 @@ void main() { Widget chipWidget({ bool enabled = true, bool selected = false }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Focus( focusNode: focusNode, @@ -2963,6 +2989,7 @@ void main() { Widget chipWidget({ bool enabled = true, bool selected = false }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Focus( focusNode: focusNode, @@ -3035,6 +3062,7 @@ void main() { Widget chipWidget({ bool enabled = true, bool selected = false }) { return MaterialApp( theme: ThemeData( + useMaterial3: false, chipTheme: ThemeData.light().chipTheme.copyWith( shape: themeShape, side: themeBorderSide, @@ -3071,6 +3099,7 @@ void main() { Future<void> buildTest(VisualDensity visualDensity) async { return tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: Column( @@ -3290,6 +3319,7 @@ void main() { const OutlinedBorder shape = ContinuousRectangleBorder(); await tester.pumpWidget(wrapForChip( + useMaterial3: false, child: const RawChip( label: Text('text'), backgroundColor: backgroundColor, @@ -3333,6 +3363,338 @@ void main() { ..rect(color: const Color(0x1f000000)), ); }); + + testWidgets('RawChip.color resolves material states', (WidgetTester tester) async { + const Color disabledSelectedColor = Color(0xffffff00); + const Color disabledColor = Color(0xff00ff00); + const Color backgroundColor = Color(0xff0000ff); + const Color selectedColor = Color(0xffff0000); + Widget buildApp({ required bool enabled, required bool selected }) { + return wrapForChip( + useMaterial3: true, + child: RawChip( + isEnabled: enabled, + selected: selected, + color: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.disabled) && states.contains(MaterialState.selected)) { + return disabledSelectedColor; + } + if (states.contains(MaterialState.disabled)) { + return disabledColor; + } + if (states.contains(MaterialState.selected)) { + return selectedColor; + } + return backgroundColor; + }), + label: const Text('RawChip'), + ), + ); + } + + // Test enabled chip. + await tester.pumpWidget(buildApp(enabled: true, selected: false)); + + // Enabled chip should have the provided backgroundColor. + expect(getMaterialBox(tester), paints..rrect(color: backgroundColor)); + + // Test disabled chip. + await tester.pumpWidget(buildApp(enabled: false, selected: false)); + await tester.pumpAndSettle(); + + // Disabled chip should have the provided disabledColor. + expect(getMaterialBox(tester), paints..rrect(color: disabledColor)); + + // Test enabled & selected chip. + await tester.pumpWidget(buildApp(enabled: true, selected: true)); + await tester.pumpAndSettle(); + + // Enabled & selected chip should have the provided selectedColor. + expect(getMaterialBox(tester), paints..rrect(color: selectedColor)); + + // Test disabled & selected chip. + await tester.pumpWidget(buildApp(enabled: false, selected: true)); + await tester.pumpAndSettle(); + + // Disabled & selected chip should have the provided disabledSelectedColor. + expect(getMaterialBox(tester), paints..rrect(color: disabledSelectedColor)); + }); + + testWidgets('RawChip uses provided state color properties', (WidgetTester tester) async { + const Color disabledColor = Color(0xff00ff00); + const Color backgroundColor = Color(0xff0000ff); + const Color selectedColor = Color(0xffff0000); + Widget buildApp({ required bool enabled, required bool selected }) { + return wrapForChip( + useMaterial3: true, + child: RawChip( + isEnabled: enabled, + selected: selected, + disabledColor: disabledColor, + backgroundColor: backgroundColor, + selectedColor: selectedColor, + label: const Text('RawChip'), + ), + ); + } + + // Test enabled chip. + await tester.pumpWidget(buildApp(enabled: true, selected: false)); + + // Enabled chip should have the provided backgroundColor. + expect(getMaterialBox(tester), paints..rrect(color: backgroundColor)); + + // Test disabled chip. + await tester.pumpWidget(buildApp(enabled: false, selected: false)); + await tester.pumpAndSettle(); + + // Disabled chip should have the provided disabledColor. + expect(getMaterialBox(tester), paints..rrect(color: disabledColor)); + + // Test enabled & selected chip. + await tester.pumpWidget(buildApp(enabled: true, selected: true)); + await tester.pumpAndSettle(); + + // Enabled & selected chip should have the provided selectedColor. + expect(getMaterialBox(tester), paints..rrect(color: selectedColor)); + }); + + group('Material 2', () { + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. + + testWidgets('M2 Chip defaults', (WidgetTester tester) async { + late TextTheme textTheme; + + Widget buildFrame(Brightness brightness) { + return MaterialApp( + theme: ThemeData(brightness: brightness, useMaterial3: false), + home: Scaffold( + body: Center( + child: Builder( + builder: (BuildContext context) { + textTheme = Theme.of(context).textTheme; + return Chip( + avatar: const CircleAvatar(child: Text('A')), + label: const Text('Chip A'), + onDeleted: () { }, + ); + }, + ), + ), + ), + ); + } + + await tester.pumpWidget(buildFrame(Brightness.light)); + expect(getMaterialBox(tester), paints..rrect()..circle(color: const Color(0xff1976d2))); + expect(tester.getSize(find.byType(Chip)), const Size(156.0, 48.0)); + expect(getMaterial(tester).color, null); + expect(getMaterial(tester).elevation, 0); + expect(getMaterial(tester).shape, const StadiumBorder()); + expect(getIconData(tester).color?.value, 0xffffffff); + expect(getIconData(tester).opacity, null); + expect(getIconData(tester).size, null); + + TextStyle labelStyle = getLabelStyle(tester, 'Chip A').style; + expect(labelStyle.color?.value, 0xde000000); + expect(labelStyle.fontFamily, textTheme.bodyLarge?.fontFamily); + expect(labelStyle.fontFamilyFallback, textTheme.bodyLarge?.fontFamilyFallback); + expect(labelStyle.fontFeatures, textTheme.bodyLarge?.fontFeatures); + expect(labelStyle.fontSize, textTheme.bodyLarge?.fontSize); + expect(labelStyle.fontStyle, textTheme.bodyLarge?.fontStyle); + expect(labelStyle.fontWeight, textTheme.bodyLarge?.fontWeight); + expect(labelStyle.height, textTheme.bodyLarge?.height); + expect(labelStyle.inherit, textTheme.bodyLarge?.inherit); + expect(labelStyle.leadingDistribution, textTheme.bodyLarge?.leadingDistribution); + expect(labelStyle.letterSpacing, textTheme.bodyLarge?.letterSpacing); + expect(labelStyle.overflow, textTheme.bodyLarge?.overflow); + expect(labelStyle.textBaseline, textTheme.bodyLarge?.textBaseline); + expect(labelStyle.wordSpacing, textTheme.bodyLarge?.wordSpacing); + + await tester.pumpWidget(buildFrame(Brightness.dark)); + await tester.pumpAndSettle(); // Theme transition animation + expect(getMaterialBox(tester), paints..rrect(color: const Color(0x1fffffff))); + expect(tester.getSize(find.byType(Chip)), const Size(156.0, 48.0)); + expect(getMaterial(tester).color, null); + expect(getMaterial(tester).elevation, 0); + expect(getMaterial(tester).shape, const StadiumBorder()); + expect(getIconData(tester).color?.value, 0xffffffff); + expect(getIconData(tester).opacity, null); + expect(getIconData(tester).size, null); + + labelStyle = getLabelStyle(tester, 'Chip A').style; + expect(labelStyle.color?.value, 0xdeffffff); + expect(labelStyle.fontFamily, textTheme.bodyLarge?.fontFamily); + expect(labelStyle.fontFamilyFallback, textTheme.bodyLarge?.fontFamilyFallback); + expect(labelStyle.fontFeatures, textTheme.bodyLarge?.fontFeatures); + expect(labelStyle.fontSize, textTheme.bodyLarge?.fontSize); + expect(labelStyle.fontStyle, textTheme.bodyLarge?.fontStyle); + expect(labelStyle.fontWeight, textTheme.bodyLarge?.fontWeight); + expect(labelStyle.height, textTheme.bodyLarge?.height); + expect(labelStyle.inherit, textTheme.bodyLarge?.inherit); + expect(labelStyle.leadingDistribution, textTheme.bodyLarge?.leadingDistribution); + expect(labelStyle.letterSpacing, textTheme.bodyLarge?.letterSpacing); + expect(labelStyle.overflow, textTheme.bodyLarge?.overflow); + expect(labelStyle.textBaseline, textTheme.bodyLarge?.textBaseline); + expect(labelStyle.wordSpacing, textTheme.bodyLarge?.wordSpacing); + }); + + testWidgets('Chip uses the right theme colors for the right components', (WidgetTester tester) async { + final ThemeData themeData = ThemeData( + platform: TargetPlatform.android, + primarySwatch: Colors.blue, + useMaterial3: false, + ); + final ChipThemeData defaultChipTheme = ChipThemeData.fromDefaults( + brightness: themeData.brightness, + secondaryColor: Colors.blue, + labelStyle: themeData.textTheme.bodyLarge!, + ); + bool value = false; + Widget buildApp({ + ChipThemeData? chipTheme, + Widget? avatar, + Widget? deleteIcon, + bool isSelectable = true, + bool isPressable = false, + bool isDeletable = true, + bool showCheckmark = true, + }) { + chipTheme ??= defaultChipTheme; + return wrapForChip( + child: Theme( + data: themeData, + child: ChipTheme( + data: chipTheme, + child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { + return RawChip( + showCheckmark: showCheckmark, + onDeleted: isDeletable ? () { } : null, + avatar: avatar, + deleteIcon: deleteIcon, + isEnabled: isSelectable || isPressable, + shape: chipTheme?.shape, + selected: isSelectable && value, + label: Text('$value'), + onSelected: isSelectable + ? (bool newValue) { + setState(() { + value = newValue; + }); + } + : null, + onPressed: isPressable + ? () { + setState(() { + value = true; + }); + } + : null, + ); + }), + ), + ), + ); + } + + await tester.pumpWidget(buildApp()); + + RenderBox materialBox = getMaterialBox(tester); + IconThemeData iconData = getIconData(tester); + DefaultTextStyle labelStyle = getLabelStyle(tester, 'false'); + + // Check default theme for enabled chip. + expect(materialBox, paints..rrect(color: defaultChipTheme.backgroundColor)); + expect(iconData.color, equals(const Color(0xde000000))); + expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); + + // Check default theme for disabled chip. + await tester.pumpWidget(buildApp(isSelectable: false)); + await tester.pumpAndSettle(); + materialBox = getMaterialBox(tester); + labelStyle = getLabelStyle(tester, 'false'); + expect(materialBox, paints..rrect(color: defaultChipTheme.disabledColor)); + expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); + + // Check default theme for enabled and selected chip. + await tester.pumpWidget(buildApp()); + await tester.pumpAndSettle(); + await tester.tap(find.byType(RawChip)); + await tester.pumpAndSettle(); + materialBox = getMaterialBox(tester); + expect(materialBox, paints..rrect(color: defaultChipTheme.selectedColor)); + + // Check default theme for disabled and selected chip. + await tester.pumpWidget(buildApp(isSelectable: false)); + await tester.pumpAndSettle(); + materialBox = getMaterialBox(tester); + labelStyle = getLabelStyle(tester, 'true'); + expect(materialBox, paints..rrect(color: defaultChipTheme.disabledColor)); + expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); + + // Enable the chip again. + await tester.pumpWidget(buildApp()); + await tester.pumpAndSettle(); + // Tap to unselect the chip. + await tester.tap(find.byType(RawChip)); + await tester.pumpAndSettle(); + + // Apply a custom theme. + const Color customColor1 = Color(0xcafefeed); + const Color customColor2 = Color(0xdeadbeef); + const Color customColor3 = Color(0xbeefcafe); + const Color customColor4 = Color(0xaddedabe); + final ChipThemeData customTheme = defaultChipTheme.copyWith( + brightness: Brightness.dark, + backgroundColor: customColor1, + disabledColor: customColor2, + selectedColor: customColor3, + deleteIconColor: customColor4, + ); + await tester.pumpWidget(buildApp(chipTheme: customTheme)); + await tester.pumpAndSettle(); + materialBox = getMaterialBox(tester); + iconData = getIconData(tester); + labelStyle = getLabelStyle(tester, 'false'); + + // Check custom theme for enabled chip. + expect(materialBox, paints..rrect(color: customTheme.backgroundColor)); + expect(iconData.color, equals(customTheme.deleteIconColor)); + expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); + + // Check custom theme with disabled widget. + await tester.pumpWidget(buildApp( + chipTheme: customTheme, + isSelectable: false, + )); + await tester.pumpAndSettle(); + materialBox = getMaterialBox(tester); + labelStyle = getLabelStyle(tester, 'false'); + expect(materialBox, paints..rrect(color: customTheme.disabledColor)); + expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); + + // Check custom theme for enabled and selected chip. + await tester.pumpWidget(buildApp(chipTheme: customTheme)); + await tester.pumpAndSettle(); + await tester.tap(find.byType(RawChip)); + await tester.pumpAndSettle(); + materialBox = getMaterialBox(tester); + expect(materialBox, paints..rrect(color: customTheme.selectedColor)); + + // Check custom theme for disabled and selected chip. + await tester.pumpWidget(buildApp( + chipTheme: customTheme, + isSelectable: false, + )); + await tester.pumpAndSettle(); + materialBox = getMaterialBox(tester); + labelStyle = getLabelStyle(tester, 'true'); + expect(materialBox, paints..rrect(color: customTheme.disabledColor)); + expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde))); + }); + }); } class _MaterialStateOutlinedBorder extends StadiumBorder implements MaterialStateOutlinedBorder { diff --git a/packages/flutter/test/material/chip_theme_test.dart b/packages/flutter/test/material/chip_theme_test.dart index 2ea2eb3f719ff..85e901b2a1064 100644 --- a/packages/flutter/test/material/chip_theme_test.dart +++ b/packages/flutter/test/material/chip_theme_test.dart @@ -50,6 +50,7 @@ void main() { test('ChipThemeData defaults', () { const ChipThemeData themeData = ChipThemeData(); + expect(themeData.color, null); expect(themeData.backgroundColor, null); expect(themeData.deleteIconColor, null); expect(themeData.disabledColor, null); @@ -86,16 +87,17 @@ void main() { testWidgets('ChipThemeData implements debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); const ChipThemeData( - backgroundColor: Color(0xfffffff0), - deleteIconColor: Color(0xfffffff1), - disabledColor: Color(0xfffffff2), - selectedColor: Color(0xfffffff3), - secondarySelectedColor: Color(0xfffffff4), - shadowColor: Color(0xfffffff5), - surfaceTintColor: Color(0xfffffff8), - selectedShadowColor: Color(0xfffffff6), + color: MaterialStatePropertyAll<Color>(Color(0xfffffff0)), + backgroundColor: Color(0xfffffff1), + deleteIconColor: Color(0xfffffff2), + disabledColor: Color(0xfffffff3), + selectedColor: Color(0xfffffff4), + secondarySelectedColor: Color(0xfffffff5), + shadowColor: Color(0xfffffff6), + surfaceTintColor: Color(0xfffffff7), + selectedShadowColor: Color(0xfffffff8), showCheckmark: true, - checkmarkColor: Color(0xfffffff7), + checkmarkColor: Color(0xfffffff9), labelPadding: EdgeInsets.all(1), padding: EdgeInsets.all(2), side: BorderSide(width: 10), @@ -113,16 +115,17 @@ void main() { .toList(); expect(description, <String>[ - 'backgroundColor: Color(0xfffffff0)', - 'deleteIconColor: Color(0xfffffff1)', - 'disabledColor: Color(0xfffffff2)', - 'selectedColor: Color(0xfffffff3)', - 'secondarySelectedColor: Color(0xfffffff4)', - 'shadowColor: Color(0xfffffff5)', - 'surfaceTintColor: Color(0xfffffff8)', - 'selectedShadowColor: Color(0xfffffff6)', + 'color: MaterialStatePropertyAll(Color(0xfffffff0))', + 'backgroundColor: Color(0xfffffff1)', + 'deleteIconColor: Color(0xfffffff2)', + 'disabledColor: Color(0xfffffff3)', + 'selectedColor: Color(0xfffffff4)', + 'secondarySelectedColor: Color(0xfffffff5)', + 'shadowColor: Color(0xfffffff6)', + 'surfaceTintColor: Color(0xfffffff7)', + 'selectedShadowColor: Color(0xfffffff8)', 'showCheckmark: true', - 'checkMarkColor: Color(0xfffffff7)', + 'checkMarkColor: Color(0xfffffff9)', 'labelPadding: EdgeInsets.all(1.0)', 'padding: EdgeInsets.all(2.0)', 'side: BorderSide(width: 10.0)', @@ -147,7 +150,7 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: ThemeData.light().copyWith( + theme: ThemeData.light(useMaterial3: false).copyWith( chipTheme: chipTheme, ), home: Directionality( @@ -193,7 +196,7 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: ThemeData.light().copyWith( + theme: ThemeData.light(useMaterial3: false).copyWith( chipTheme: shadowedChipTheme, ), home: ChipTheme( @@ -242,6 +245,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: ChipTheme( data: shadowedChipTheme, child: Builder( @@ -329,7 +333,6 @@ void main() { expect(chipTheme.pressElevation, 8.0); }); - testWidgets('ChipThemeData generates correct opacities for defaults', (WidgetTester tester) async { const Color customColor1 = Color(0xcafefeed); const Color customColor2 = Color(0xdeadbeef); @@ -654,6 +657,7 @@ void main() { Widget chipWidget({ bool selected = false }) { return MaterialApp( theme: ThemeData( + useMaterial3: false, chipTheme: ThemeData.light().chipTheme.copyWith( side: MaterialStateBorderSide.resolveWith(getBorderSide), ), @@ -699,7 +703,7 @@ void main() { Widget chipWidget({ bool selected = false }) { return MaterialApp( - theme: ThemeData(chipTheme: chipTheme), + theme: ThemeData(useMaterial3: false, chipTheme: chipTheme), home: Scaffold( body: ChoiceChip( label: const Text('Chip'), @@ -739,7 +743,7 @@ void main() { Widget chipWidget({ bool selected = false }) { return MaterialApp( - theme: ThemeData(chipTheme: chipTheme), + theme: ThemeData(useMaterial3: false, chipTheme: chipTheme), home: Scaffold( body: ChoiceChip( label: const Text('Chip'), @@ -758,6 +762,111 @@ void main() { await tester.pumpWidget(chipWidget(selected: true)); expect(getMaterial(tester).shape, isA<RoundedRectangleBorder>()); }); + + testWidgets('RawChip uses material state color from ChipTheme', (WidgetTester tester) async { + const Color disabledSelectedColor = Color(0xffffff00); + const Color disabledColor = Color(0xff00ff00); + const Color backgroundColor = Color(0xff0000ff); + const Color selectedColor = Color(0xffff0000); + Widget buildApp({ required bool enabled, required bool selected }) { + return MaterialApp( + theme: ThemeData( + chipTheme: ChipThemeData( + color: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.disabled) + && states.contains(MaterialState.selected)) { + return disabledSelectedColor; + } + if (states.contains(MaterialState.disabled)) { + return disabledColor; + } + if (states.contains(MaterialState.selected)) { + return selectedColor; + } + return backgroundColor; + }), + ), + useMaterial3: true, + ), + home: Material( + child: RawChip( + isEnabled: enabled, + selected: selected, + label: const Text('RawChip'), + ), + ), + ); + } + + // Check theme color for enabled chip. + await tester.pumpWidget(buildApp(enabled: true, selected: false)); + await tester.pumpAndSettle(); + + // Enabled chip should have the provided backgroundColor. + expect(getMaterialBox(tester), paints..rrect(color: backgroundColor)); + + // Check theme color for disabled chip. + await tester.pumpWidget(buildApp(enabled: false, selected: false)); + await tester.pumpAndSettle(); + + // Disabled chip should have the provided disabledColor. + expect(getMaterialBox(tester),paints..rrect(color: disabledColor)); + + // Check theme color for enabled and selected chip. + await tester.pumpWidget(buildApp(enabled: true, selected: true)); + await tester.pumpAndSettle(); + + // Enabled & selected chip should have the provided selectedColor. + expect(getMaterialBox(tester), paints..rrect(color: selectedColor)); + + // Check theme color for disabled & selected chip. + await tester.pumpWidget(buildApp(enabled: false, selected: true)); + await tester.pumpAndSettle(); + + // Disabled & selected chip should have the provided disabledSelectedColor. + expect(getMaterialBox(tester), paints..rrect(color: disabledSelectedColor)); + }); + + testWidgets('RawChip uses state colors from ChipTheme', (WidgetTester tester) async { + const ChipThemeData chipTheme = ChipThemeData( + disabledColor: Color(0xadfefafe), + backgroundColor: Color(0xcafefeed), + selectedColor: Color(0xbeefcafe), + ); + Widget buildApp({ required bool enabled, required bool selected }) { + return MaterialApp( + theme: ThemeData(chipTheme: chipTheme, useMaterial3: true), + home: Material( + child: RawChip( + isEnabled: enabled, + selected: selected, + label: const Text('RawChip'), + ), + ), + ); + } + + // Check theme color for enabled chip. + await tester.pumpWidget(buildApp(enabled: true, selected: false)); + await tester.pumpAndSettle(); + + // Enabled chip should have the provided backgroundColor. + expect(getMaterialBox(tester), paints..rrect(color: chipTheme.backgroundColor)); + + // Check theme color for disabled chip. + await tester.pumpWidget(buildApp(enabled: false, selected: false)); + await tester.pumpAndSettle(); + + // Disabled chip should have the provided disabledColor. + expect(getMaterialBox(tester),paints..rrect(color: chipTheme.disabledColor)); + + // Check theme color for enabled and selected chip. + await tester.pumpWidget(buildApp(enabled: true, selected: true)); + await tester.pumpAndSettle(); + + // Enabled & selected chip should have the provided selectedColor. + expect(getMaterialBox(tester), paints..rrect(color: chipTheme.selectedColor)); + }); } class _MaterialStateOutlinedBorder extends StadiumBorder implements MaterialStateOutlinedBorder { diff --git a/packages/flutter/test/material/choice_chip_test.dart b/packages/flutter/test/material/choice_chip_test.dart index 5ec884ceb74a3..187ec8fd2cef9 100644 --- a/packages/flutter/test/material/choice_chip_test.dart +++ b/packages/flutter/test/material/choice_chip_test.dart @@ -7,10 +7,10 @@ import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; -RenderBox getMaterialBox(WidgetTester tester) { +RenderBox getMaterialBox(WidgetTester tester, Finder type) { return tester.firstRenderObject<RenderBox>( find.descendant( - of: find.byType(RawChip), + of: type, matching: find.byType(CustomPaint), ), ); @@ -19,7 +19,7 @@ RenderBox getMaterialBox(WidgetTester tester) { Material getMaterial(WidgetTester tester) { return tester.widget<Material>( find.descendant( - of: find.byType(RawChip), + of: find.byType(ChoiceChip), matching: find.byType(Material), ), ); @@ -40,9 +40,10 @@ Widget wrapForChip({ TextDirection textDirection = TextDirection.ltr, double textScaleFactor = 1.0, Brightness brightness = Brightness.light, + bool? useMaterial3, }) { return MaterialApp( - theme: ThemeData(brightness: brightness), + theme: ThemeData(brightness: brightness, useMaterial3: useMaterial3), home: Directionality( textDirection: textDirection, child: MediaQuery( @@ -64,36 +65,441 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { void main() { testWidgets('ChoiceChip defaults', (WidgetTester tester) async { - Widget buildFrame(Brightness brightness) { - return MaterialApp( - theme: ThemeData(brightness: brightness), - home: const Scaffold( - body: Center( + final ThemeData theme = ThemeData(useMaterial3: true); + const String label = 'choice chip'; + + // Test enabled ChoiceChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: Center( child: ChoiceChip( - label: Text('Chip A'), - selected: true, + selected: false, + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ), + ); + + // Test default chip size. + expect(tester.getSize(find.byType(ChoiceChip)), const Size(190.0, 48.0)); + // Test default label style. + expect( + getLabelStyle(tester, label).style.color!.value, + theme.textTheme.labelLarge!.color!.value, + ); + + Material chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, Colors.transparent); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: theme.colorScheme.outline), + ), + ); + + ShapeDecoration decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test disabled ChoiceChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: ChoiceChip( + selected: false, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, Colors.transparent); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: theme.colorScheme.onSurface.withOpacity(0.12)), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test selected enabled ChoiceChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: ChoiceChip( + selected: true, + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.secondaryContainer); + + // Test selected disabled ChoiceChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: ChoiceChip( + selected: true, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); + }); + + testWidgets('ChoiceChip.elevated defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + const String label = 'choice chip'; + + // Test enabled ChoiceChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: Center( + child: ChoiceChip.elevated( + selected: false, + onSelected: (bool valueChanged) { }, + label: const Text(label), ), ), ), + ), + ); + + // Test default chip size. + expect(tester.getSize(find.byType(ChoiceChip)), const Size(190.0, 48.0)); + // Test default label style. + expect( + getLabelStyle(tester, label).style.color!.value, + theme.textTheme.labelLarge!.color!.value, + ); + + Material chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 1); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + ShapeDecoration decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test disabled ChoiceChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: ChoiceChip.elevated( + selected: false, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); + + // Test selected enabled ChoiceChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: ChoiceChip.elevated( + selected: true, + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 1); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.secondaryContainer); + + // Test selected disabled ChoiceChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: ChoiceChip.elevated( + selected: false, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); + }); + + testWidgets('ChoiceChip.color resolves material states', (WidgetTester tester) async { + const Color disabledSelectedColor = Color(0xffffff00); + const Color disabledColor = Color(0xff00ff00); + const Color backgroundColor = Color(0xff0000ff); + const Color selectedColor = Color(0xffff0000); + final MaterialStateProperty<Color?> color = MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.disabled) && states.contains(MaterialState.selected)) { + return disabledSelectedColor; + } + if (states.contains(MaterialState.disabled)) { + return disabledColor; + } + if (states.contains(MaterialState.selected)) { + return selectedColor; + } + return backgroundColor; + }); + Widget buildApp({ required bool enabled, required bool selected }) { + return wrapForChip( + useMaterial3: true, + child: Column( + children: <Widget>[ + ChoiceChip( + onSelected: enabled ? (bool value) { } : null, + selected: selected, + color: color, + label: const Text('ChoiceChip'), + ), + ChoiceChip.elevated( + onSelected: enabled ? (bool value) { } : null, + selected: selected, + color: color, + label: const Text('ChoiceChip.elevated'), + ), + ], + ), + ); + } + + // Test enabled state. + await tester.pumpWidget(buildApp(enabled: true, selected: false)); + + // Enabled ChoiceChip should have the provided backgroundColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: backgroundColor), + ); + // Enabled elevated ChoiceChip should have the provided backgroundColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: backgroundColor), + ); + + // Test disabled state. + await tester.pumpWidget(buildApp(enabled: false, selected: false)); + await tester.pumpAndSettle(); + + // Disabled ChoiceChip should have the provided disabledColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: disabledColor), + ); + // Disabled elevated ChoiceChip should have the provided disabledColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: disabledColor), + ); + + // Test enabled & selected state. + await tester.pumpWidget(buildApp(enabled: true, selected: true)); + await tester.pumpAndSettle(); + + // Enabled & selected ChoiceChip should have the provided selectedColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: selectedColor), + ); + // Enabled & selected elevated ChoiceChip should have the provided selectedColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: selectedColor), + ); + + // Test disabled & selected state. + await tester.pumpWidget(buildApp(enabled: false, selected: true)); + await tester.pumpAndSettle(); + + // Disabled & selected ChoiceChip should have the provided disabledSelectedColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: disabledSelectedColor), + ); + // Disabled & selected elevated ChoiceChip should have the provided disabledSelectedColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: disabledSelectedColor), + ); + }); + + testWidgets('ChoiceChip uses provided state color properties', (WidgetTester tester) async { + const Color disabledColor = Color(0xff00ff00); + const Color backgroundColor = Color(0xff0000ff); + const Color selectedColor = Color(0xffff0000); + Widget buildApp({ required bool enabled, required bool selected }) { + return wrapForChip( + useMaterial3: true, + child: Column( + children: <Widget>[ + ChoiceChip( + onSelected: enabled ? (bool value) { } : null, + selected: selected, + disabledColor: disabledColor, + backgroundColor: backgroundColor, + selectedColor: selectedColor, + label: const Text('ChoiceChip'), + ), + ChoiceChip.elevated( + onSelected: enabled ? (bool value) { } : null, + selected: selected, + disabledColor: disabledColor, + backgroundColor: backgroundColor, + selectedColor: selectedColor, + label: const Text('ChoiceChip.elevated'), + ), + ], + ), ); } - await tester.pumpWidget(buildFrame(Brightness.light)); - expect(getMaterialBox(tester), paints..rrect(color: const Color(0x3d000000))); - expect(tester.getSize(find.byType(ChoiceChip)), const Size(108.0, 48.0)); - expect(getMaterial(tester).color, null); - expect(getMaterial(tester).elevation, 0); - expect(getMaterial(tester).shape, const StadiumBorder()); - expect(getLabelStyle(tester, 'Chip A').style.color?.value, 0xde000000); - - await tester.pumpWidget(buildFrame(Brightness.dark)); - await tester.pumpAndSettle(); // Theme transition animation - expect(getMaterialBox(tester), paints..rrect(color: const Color(0x3dffffff))); - expect(tester.getSize(find.byType(ChoiceChip)), const Size(108.0, 48.0)); - expect(getMaterial(tester).color, null); - expect(getMaterial(tester).elevation, 0); - expect(getMaterial(tester).shape, const StadiumBorder()); - expect(getLabelStyle(tester, 'Chip A').style.color?.value, 0xdeffffff); + // Test enabled chips. + await tester.pumpWidget(buildApp(enabled: true, selected: false)); + + // Enabled ChoiceChip should have the provided backgroundColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: backgroundColor), + ); + // Enabled elevated ChoiceChip should have the provided backgroundColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: backgroundColor), + ); + + // Test disabled chips. + await tester.pumpWidget(buildApp(enabled: false, selected: false)); + await tester.pumpAndSettle(); + + // Disabled ChoiceChip should have the provided disabledColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: disabledColor), + ); + // Disabled elevated ChoiceChip should have the provided disabledColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: disabledColor), + ); + + // Test enabled & selected chips. + await tester.pumpWidget(buildApp(enabled: true, selected: true)); + await tester.pumpAndSettle(); + + // Enabled & selected ChoiceChip should have the provided selectedColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: selectedColor), + ); + // Enabled & selected elevated ChoiceChip should have the provided selectedColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: selectedColor), + ); }); testWidgets('ChoiceChip can be tapped', (WidgetTester tester) async { @@ -163,4 +569,43 @@ void main() { expect(rawChip.showCheckmark, showCheckmark); expect(rawChip.checkmarkColor, checkmarkColor); }); + + group('Material 2', () { + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. + + testWidgets('ChoiceChip defaults', (WidgetTester tester) async { + Widget buildFrame(Brightness brightness) { + return MaterialApp( + theme: ThemeData(useMaterial3: false, brightness: brightness), + home: const Scaffold( + body: Center( + child: ChoiceChip( + label: Text('Chip A'), + selected: true, + ), + ), + ), + ); + } + + await tester.pumpWidget(buildFrame(Brightness.light)); + expect(getMaterialBox(tester, find.byType(RawChip)), paints..rrect(color: const Color(0x3d000000))); + expect(tester.getSize(find.byType(ChoiceChip)), const Size(108.0, 48.0)); + expect(getMaterial(tester).color, null); + expect(getMaterial(tester).elevation, 0); + expect(getMaterial(tester).shape, const StadiumBorder()); + expect(getLabelStyle(tester, 'Chip A').style.color?.value, 0xde000000); + + await tester.pumpWidget(buildFrame(Brightness.dark)); + await tester.pumpAndSettle(); // Theme transition animation + expect(getMaterialBox(tester, find.byType(RawChip)), paints..rrect(color: const Color(0x3dffffff))); + expect(tester.getSize(find.byType(ChoiceChip)), const Size(108.0, 48.0)); + expect(getMaterial(tester).color, null); + expect(getMaterial(tester).elevation, 0); + expect(getMaterial(tester).shape, const StadiumBorder()); + expect(getLabelStyle(tester, 'Chip A').style.color?.value, 0xdeffffff); + }); + }); } diff --git a/packages/flutter/test/material/circle_avatar_test.dart b/packages/flutter/test/material/circle_avatar_test.dart index fb737b305388e..bf4ffe919d00b 100644 --- a/packages/flutter/test/material/circle_avatar_test.dart +++ b/packages/flutter/test/material/circle_avatar_test.dart @@ -282,14 +282,12 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('CircleAvatar default colors with light theme', (WidgetTester tester) async { - final ThemeData theme = ThemeData( - primaryColor: Colors.grey.shade100, - primaryColorBrightness: Brightness.light, - ); + final ThemeData theme = ThemeData(useMaterial3: false, primaryColor: Colors.grey.shade100); await tester.pumpWidget( wrap( child: Theme( @@ -311,10 +309,7 @@ void main() { }); testWidgets('CircleAvatar default colors with dark theme', (WidgetTester tester) async { - final ThemeData theme = ThemeData( - primaryColor: Colors.grey.shade800, - primaryColorBrightness: Brightness.dark, - ); + final ThemeData theme = ThemeData(useMaterial3: false, primaryColor: Colors.grey.shade800); await tester.pumpWidget( wrap( child: Theme( @@ -342,7 +337,9 @@ Widget wrap({ required Widget child }) { textDirection: TextDirection.ltr, child: MediaQuery( data: const MediaQueryData(), - child: Center(child: child), + child: MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Center(child: child)), ), ); } diff --git a/packages/flutter/test/material/data_table_test.dart b/packages/flutter/test/material/data_table_test.dart index 6918f5c3cc87e..4e9d0af6e9f12 100644 --- a/packages/flutter/test/material/data_table_test.dart +++ b/packages/flutter/test/material/data_table_test.dart @@ -1680,6 +1680,7 @@ void main() { } await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material(child: buildTable()), )); diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart index 483c1ec4cba1b..514a5f4c28b43 100644 --- a/packages/flutter/test/material/date_picker_test.dart +++ b/packages/flutter/test/material/date_picker_test.dart @@ -382,6 +382,7 @@ void main() { ); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Center( child: Builder( builder: (BuildContext context) { @@ -417,7 +418,7 @@ void main() { ); await tester.pumpWidget( MaterialApp( - theme: ThemeData.fallback().copyWith(dialogTheme: customDialogTheme), + theme: ThemeData.fallback(useMaterial3: false).copyWith(dialogTheme: customDialogTheme), home: Center( child: Builder( builder: (BuildContext context) { @@ -447,6 +448,7 @@ void main() { testWidgets('OK Cancel button layout', (WidgetTester tester) async { Widget buildFrame(TextDirection textDirection) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: Builder( @@ -1266,6 +1268,7 @@ void main() { expect(tester.getSemantics(find.text('3')), matchesSemantics( label: '3, Sunday, January 3, 2016, Today', + isButton: true, hasTapAction: true, isFocusable: true, )); @@ -1857,8 +1860,9 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. group('showDatePicker Dialog', () { testWidgets('Default dialog size', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/date_picker_theme_test.dart b/packages/flutter/test/material/date_picker_theme_test.dart index 8ef5a51ffe216..191c6faa3aa37 100644 --- a/packages/flutter/test/material/date_picker_theme_test.dart +++ b/packages/flutter/test/material/date_picker_theme_test.dart @@ -40,7 +40,11 @@ void main() { rangePickerHeaderHelpStyle: TextStyle(fontSize: 15), rangeSelectionBackgroundColor: Color(0xffffff2f), rangeSelectionOverlayColor: MaterialStatePropertyAll<Color>(Color(0xffffff3f)), - dividerColor: Color(0xffffff3f), + dividerColor: Color(0xffffff4f), + inputDecorationTheme: InputDecorationTheme( + fillColor: Color(0xffffff5f), + border: UnderlineInputBorder(), + ) ); Material findDialogMaterial(WidgetTester tester) { @@ -119,6 +123,7 @@ void main() { expect(theme.rangeSelectionBackgroundColor, null); expect(theme.rangeSelectionOverlayColor, null); expect(theme.dividerColor, null); + expect(theme.inputDecorationTheme, null); }); testWidgets('DatePickerTheme.defaults M3 defaults', (WidgetTester tester) async { @@ -129,7 +134,7 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: ThemeData.light(useMaterial3: true), + theme: ThemeData(useMaterial3: true), home: Builder( builder: (BuildContext context) { m3 = DatePickerTheme.defaults(context); @@ -193,6 +198,7 @@ void main() { expect(m3.rangePickerHeaderHeadlineStyle, textTheme.titleLarge); expect(m3.rangePickerHeaderHelpStyle, textTheme.titleSmall); expect(m3.dividerColor, null); + expect(m3.inputDecorationTheme, null); }); testWidgets('DatePickerTheme.defaults M2 defaults', (WidgetTester tester) async { @@ -203,7 +209,7 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: ThemeData.light(useMaterial3: false), + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { m2 = DatePickerTheme.defaults(context); @@ -258,6 +264,8 @@ void main() { expect(m2.rangePickerHeaderForegroundColor, colorScheme.onPrimary); expect(m2.rangePickerHeaderHeadlineStyle, textTheme.headlineSmall); expect(m2.rangePickerHeaderHelpStyle, textTheme.labelSmall); + expect(m2.dividerColor, null); + expect(m2.inputDecorationTheme, null); }); testWidgets('Default DatePickerThemeData debugFillProperties', (WidgetTester tester) async { @@ -282,7 +290,9 @@ void main() { .map((DiagnosticsNode node) => node.toString()) .toList(); - expect(description, <String>[ + expect( + description, + equalsIgnoringHashCodes(<String>[ 'backgroundColor: Color(0xfffffff0)', 'elevation: 6.0', 'shadowColor: Color(0xfffffff1)', @@ -315,15 +325,18 @@ void main() { 'rangePickerHeaderHelpStyle: TextStyle(inherit: true, size: 15.0)', 'rangeSelectionBackgroundColor: Color(0xffffff2f)', 'rangeSelectionOverlayColor: MaterialStatePropertyAll(Color(0xffffff3f))', - 'dividerColor: Color(0xffffff3f)', - ]); + 'dividerColor: Color(0xffffff4f)', + 'inputDecorationTheme: InputDecorationTheme#00000(fillColor: Color(0xffffff5f), border: UnderlineInputBorder())' + ]), + ); }); - testWidgets('DatePickerDialog uses ThemeData datePicker theme', (WidgetTester tester) async { + testWidgets('DatePickerDialog uses ThemeData datePicker theme (calendar mode)', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData.light(useMaterial3: true).copyWith( + theme: ThemeData( datePickerTheme: datePickerTheme, + useMaterial3: true, ), home: Directionality( textDirection: TextDirection.ltr, @@ -398,11 +411,53 @@ void main() { expect(year2023Decoration.border?.bottom.color, datePickerTheme.todayForegroundColor?.resolve(<MaterialState>{})); }); + testWidgets('DatePickerDialog uses ThemeData datePicker theme (input mode)', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + datePickerTheme: datePickerTheme, + useMaterial3: true, + ), + home: Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: Center( + child: DatePickerDialog( + initialEntryMode: DatePickerEntryMode.input, + initialDate: DateTime(2023, DateTime.january, 25), + firstDate: DateTime(2022), + lastDate: DateTime(2024, DateTime.december, 31), + currentDate: DateTime(2023, DateTime.january, 24), + ), + ), + ), + ), + ), + ); + + final Material material = findDialogMaterial(tester); + expect(material.color, datePickerTheme.backgroundColor); + expect(material.elevation, datePickerTheme.elevation); + expect(material.shadowColor, datePickerTheme.shadowColor); + expect(material.surfaceTintColor, datePickerTheme.surfaceTintColor); + expect(material.shape, datePickerTheme.shape); + + final Text selectDate = tester.widget<Text>(find.text('Select date')); + final Material headerMaterial = findHeaderMaterial(tester, 'Select date'); + expect(selectDate.style?.color, datePickerTheme.headerForegroundColor); + expect(selectDate.style?.fontSize, datePickerTheme.headerHelpStyle?.fontSize); + expect(headerMaterial.color, datePickerTheme.headerBackgroundColor); + + final InputDecoration inputDecoration = tester.widget<TextField>(find.byType(TextField)).decoration!; + expect(inputDecoration.fillColor, datePickerTheme.inputDecorationTheme?.fillColor); + }); + testWidgets('DateRangePickerDialog uses ThemeData datePicker theme', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData.light(useMaterial3: true).copyWith( + theme: ThemeData( datePickerTheme: datePickerTheme, + useMaterial3: true, ), home: Directionality( textDirection: TextDirection.ltr, @@ -431,6 +486,9 @@ void main() { expect(material.surfaceTintColor, datePickerTheme.rangePickerSurfaceTintColor); expect(material.shape, datePickerTheme.rangePickerShape); + final AppBar appBar = tester.widget<AppBar>(find.byType(AppBar)); + expect(appBar.backgroundColor, datePickerTheme.rangePickerHeaderBackgroundColor); + final Text selectRange = tester.widget<Text>(find.text('Select range')); expect(selectRange.style?.color, datePickerTheme.rangePickerHeaderForegroundColor); expect(selectRange.style?.fontSize, datePickerTheme.rangePickerHeaderHelpStyle?.fontSize); @@ -447,8 +505,9 @@ void main() { addTearDown(tester.view.reset); await tester.pumpWidget( MaterialApp( - theme: ThemeData.light(useMaterial3: true).copyWith( + theme: ThemeData( datePickerTheme: datePickerTheme, + useMaterial3: true, ), home: Directionality( textDirection: TextDirection.ltr, @@ -479,4 +538,60 @@ void main() { final Divider horizontalDivider = tester.widget(find.byType(Divider)); expect(horizontalDivider.color, datePickerTheme.dividerColor); }); + + testWidgets( + 'DatePicker uses ThemeData.inputDecorationTheme properties ' + 'which are null in DatePickerThemeData.inputDecorationTheme', + (WidgetTester tester) async { + + Widget buildWidget({ + InputDecorationTheme? inputDecorationTheme, + DatePickerThemeData? datePickerTheme, + }) { + return MaterialApp( + theme: ThemeData( + useMaterial3: true, + inputDecorationTheme: inputDecorationTheme, + datePickerTheme: datePickerTheme, + ), + home: Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: Center( + child: DatePickerDialog( + initialEntryMode: DatePickerEntryMode.input, + initialDate: DateTime(2023, DateTime.january, 25), + firstDate: DateTime(2022), + lastDate: DateTime(2024, DateTime.december, 31), + currentDate: DateTime(2023, DateTime.january, 24), + ), + ), + ), + ), + ); + } + + // Test DatePicker with DatePickerThemeData.inputDecorationTheme. + await tester.pumpWidget(buildWidget( + inputDecorationTheme: const InputDecorationTheme(filled: true), + datePickerTheme: datePickerTheme, + )); + InputDecoration inputDecoration = tester.widget<TextField>(find.byType(TextField)).decoration!; + expect(inputDecoration.fillColor, datePickerTheme.inputDecorationTheme!.fillColor); + expect(inputDecoration.border , datePickerTheme.inputDecorationTheme!.border); + + // Test DatePicker with ThemeData.inputDecorationTheme. + await tester.pumpWidget(buildWidget( + inputDecorationTheme: const InputDecorationTheme( + filled: true, + fillColor: Color(0xFF00FF00), + border: OutlineInputBorder(), + ), + )); + await tester.pumpAndSettle(); + + inputDecoration = tester.widget<TextField>(find.byType(TextField)).decoration!; + expect(inputDecoration.fillColor, const Color(0xFF00FF00)); + expect(inputDecoration.border , const OutlineInputBorder()); + }); } diff --git a/packages/flutter/test/material/date_range_picker_test.dart b/packages/flutter/test/material/date_range_picker_test.dart index 7f5d67c24fb07..6feafa939d78b 100644 --- a/packages/flutter/test/material/date_range_picker_test.dart +++ b/packages/flutter/test/material/date_range_picker_test.dart @@ -4,6 +4,7 @@ import 'dart:ui'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -108,6 +109,125 @@ void main() { await callback(range); } + testWidgets('Default layout (calendar mode)', (WidgetTester tester) async { + await preparePicker(tester, (Future<DateTimeRange?> range) async { + final Finder helpText = find.text('Select range'); + final Finder firstDateHeaderText = find.text('Jan 15'); + final Finder lastDateHeaderText = find.text('Jan 25, 2016'); + final Finder saveText = find.text('Save'); + + expect(helpText, findsOneWidget); + expect(firstDateHeaderText, findsOneWidget); + expect(lastDateHeaderText, findsOneWidget); + expect(saveText, findsOneWidget); + + // Test the close button position. + final Offset closeButtonBottomRight = tester.getBottomRight(find.byType(CloseButton)); + final Offset helpTextTopLeft = tester.getTopLeft(helpText); + expect(closeButtonBottomRight.dx, 56.0); + expect(closeButtonBottomRight.dy, helpTextTopLeft.dy); + + // Test the save and entry buttons position. + final Offset saveButtonBottomLeft = tester.getBottomLeft(find.byType(TextButton)); + final Offset entryButtonBottomLeft = tester.getBottomLeft( + find.widgetWithIcon(IconButton, Icons.edit_outlined), + ); + expect( + saveButtonBottomLeft.dx, + const bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? moreOrLessEquals(711.6, epsilon: 1e-5) : (800 - 89.0), + ); + expect(saveButtonBottomLeft.dy, helpTextTopLeft.dy); + expect(entryButtonBottomLeft.dx, saveButtonBottomLeft.dx - 48.0); + expect(entryButtonBottomLeft.dy, helpTextTopLeft.dy); + + // Test help text position. + final Offset helpTextBottomLeft = tester.getBottomLeft(helpText); + expect(helpTextBottomLeft.dx, 72.0); + // TODO(tahatesser): https://github.com/flutter/flutter/issues/99933 + // A bug in the HTML renderer and/or Chrome 96+ causes a + // discrepancy in the paragraph height. + const bool hasIssue99933 = kIsWeb && !bool.fromEnvironment('FLUTTER_WEB_USE_SKIA'); + expect(helpTextBottomLeft.dy, closeButtonBottomRight.dy + 20.0 + (hasIssue99933 ? 1.0 : 0.0)); + + // Test the header position. + final Offset firstDateHeaderTopLeft = tester.getTopLeft(firstDateHeaderText); + final Offset lastDateHeaderTopLeft = tester.getTopLeft(lastDateHeaderText); + expect(firstDateHeaderTopLeft.dx, 72.0); + expect(firstDateHeaderTopLeft.dy, helpTextBottomLeft.dy + 8.0); + final Offset firstDateHeaderTopRight = tester.getTopRight(firstDateHeaderText); + expect(lastDateHeaderTopLeft.dx, firstDateHeaderTopRight.dx + 66.0); + expect(lastDateHeaderTopLeft.dy, helpTextBottomLeft.dy + 8.0); + + // Test the day headers position. + final Offset dayHeadersGridTopLeft = tester.getTopLeft(find.byType(GridView).first); + final Offset firstDateHeaderBottomLeft = tester.getBottomLeft(firstDateHeaderText); + expect(dayHeadersGridTopLeft.dx, (800 - 384) / 2); + expect(dayHeadersGridTopLeft.dy, firstDateHeaderBottomLeft.dy + 16.0); + + // Test the calendar custom scroll view position. + final Offset calendarScrollViewTopLeft = tester.getTopLeft(find.byType(CustomScrollView)); + final Offset dayHeadersGridBottomLeft = tester.getBottomLeft(find.byType(GridView).first); + expect(calendarScrollViewTopLeft.dx, 0.0); + expect(calendarScrollViewTopLeft.dy, dayHeadersGridBottomLeft.dy); + }, useMaterial3: true); + }); + + testWidgets('Default Dialog properties (calendar mode)', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + await preparePicker(tester, (Future<DateTimeRange?> range) async { + final Material dialogMaterial = tester.widget<Material>( + find.descendant(of: find.byType(Dialog), + matching: find.byType(Material), + ).first); + + expect(dialogMaterial.color, theme.colorScheme.surface); + expect(dialogMaterial.shadowColor, Colors.transparent); + expect(dialogMaterial.surfaceTintColor, Colors.transparent); + expect(dialogMaterial.elevation, 0.0); + expect(dialogMaterial.shape, const RoundedRectangleBorder()); + expect(dialogMaterial.clipBehavior, Clip.antiAlias); + + final Dialog dialog = tester.widget<Dialog>(find.byType(Dialog)); + expect(dialog.insetPadding, EdgeInsets.zero); + }, useMaterial3: theme.useMaterial3); + }); + + testWidgets('Default Dialog properties (input mode)', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + await preparePicker(tester, (Future<DateTimeRange?> range) async { + final Material dialogMaterial = tester.widget<Material>( + find.descendant(of: find.byType(Dialog), + matching: find.byType(Material), + ).first); + + expect(dialogMaterial.color, theme.colorScheme.surface); + expect(dialogMaterial.shadowColor, Colors.transparent); + expect(dialogMaterial.surfaceTintColor, Colors.transparent); + expect(dialogMaterial.elevation, 0.0); + expect(dialogMaterial.shape, const RoundedRectangleBorder()); + expect(dialogMaterial.clipBehavior, Clip.antiAlias); + + final Dialog dialog = tester.widget<Dialog>(find.byType(Dialog)); + expect(dialog.insetPadding, EdgeInsets.zero); + }, useMaterial3: theme.useMaterial3); + }); + + testWidgets('Scaffold and AppBar defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + await preparePicker(tester, (Future<DateTimeRange?> range) async { + final Scaffold scaffold = tester.widget<Scaffold>(find.byType(Scaffold)); + expect(scaffold.backgroundColor, null); + + final AppBar appBar = tester.widget<AppBar>(find.byType(AppBar)); + final IconThemeData iconTheme = IconThemeData(color: theme.colorScheme.onSurfaceVariant); + expect(appBar.iconTheme, iconTheme); + expect(appBar.actionsIconTheme, iconTheme); + expect(appBar.elevation, 0); + expect(appBar.scrolledUnderElevation, 0); + expect(appBar.backgroundColor, Colors.transparent); + }, useMaterial3: theme.useMaterial3); + }); + group('Landscape input-only date picker headers use headlineSmall', () { // Regression test for https://github.com/flutter/flutter/issues/122056 @@ -384,6 +504,7 @@ void main() { testWidgets('OK Cancel button layout', (WidgetTester tester) async { Widget buildFrame(TextDirection textDirection) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: Builder( @@ -668,6 +789,49 @@ void main() { initialEntryMode = DatePickerEntryMode.input; }); + testWidgets('Default Dialog properties (input mode)', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + await preparePicker(tester, (Future<DateTimeRange?> range) async { + final Material dialogMaterial = tester.widget<Material>( + find.descendant(of: find.byType(Dialog), + matching: find.byType(Material), + ).first); + + expect(dialogMaterial.color, theme.colorScheme.surface); + expect(dialogMaterial.shadowColor, Colors.transparent); + expect(dialogMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect(dialogMaterial.elevation, 6.0); + expect( + dialogMaterial.shape, + const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(28.0))), + ); + expect(dialogMaterial.clipBehavior, Clip.antiAlias); + + final Dialog dialog = tester.widget<Dialog>(find.byType(Dialog)); + expect(dialog.insetPadding, const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0)); + }, useMaterial3: theme.useMaterial3); + }); + + testWidgets('Default InputDecoration', (WidgetTester tester) async { + await preparePicker(tester, (Future<DateTimeRange?> range) async { + final InputDecoration startDateDecoration = tester.widget<TextField>( + find.byType(TextField).first).decoration!; + expect(startDateDecoration.border, const OutlineInputBorder()); + expect(startDateDecoration.filled, false); + expect(startDateDecoration.hintText, 'mm/dd/yyyy'); + expect(startDateDecoration.labelText, 'Start Date'); + expect(startDateDecoration.errorText, null); + + final InputDecoration endDateDecoration = tester.widget<TextField>( + find.byType(TextField).last).decoration!; + expect(endDateDecoration.border, const OutlineInputBorder()); + expect(endDateDecoration.filled, false); + expect(endDateDecoration.hintText, 'mm/dd/yyyy'); + expect(endDateDecoration.labelText, 'End Date'); + expect(endDateDecoration.errorText, null); + }, useMaterial3: true); + }); + testWidgets('Initial entry mode is used', (WidgetTester tester) async { await preparePicker(tester, (Future<DateTimeRange?> range) async { expect(find.byType(TextField), findsNWidgets(2)); @@ -899,9 +1063,10 @@ void main() { testWidgets('DatePickerDialog is state restorable', (WidgetTester tester) async { await tester.pumpWidget( - const MaterialApp( + MaterialApp( + theme: ThemeData(useMaterial3: false), restorationScopeId: 'app', - home: _RestorableDateRangePickerDialogTestWidget(), + home: const _RestorableDateRangePickerDialogTestWidget(), ), ); @@ -1299,6 +1464,160 @@ void main() { await tester.tap(find.text('CANCEL')); await tester.pumpAndSettle(); }); + + + group('Material 2', () { + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. + + testWidgets('Default layout (calendar mode)', (WidgetTester tester) async { + await preparePicker(tester, (Future<DateTimeRange?> range) async { + final Finder helpText = find.text('SELECT RANGE'); + final Finder firstDateHeaderText = find.text('Jan 15'); + final Finder lastDateHeaderText = find.text('Jan 25, 2016'); + final Finder saveText = find.text('SAVE'); + + expect(helpText, findsOneWidget); + expect(firstDateHeaderText, findsOneWidget); + expect(lastDateHeaderText, findsOneWidget); + expect(saveText, findsOneWidget); + + // Test the close button position. + final Offset closeButtonBottomRight = tester.getBottomRight(find.byType(CloseButton)); + final Offset helpTextTopLeft = tester.getTopLeft(helpText); + expect(closeButtonBottomRight.dx, 56.0); + expect(closeButtonBottomRight.dy, helpTextTopLeft.dy - 6.0); + + // Test the save and entry buttons position. + final Offset saveButtonBottomLeft = tester.getBottomLeft(find.byType(TextButton)); + final Offset entryButtonBottomLeft = tester.getBottomLeft( + find.widgetWithIcon(IconButton, Icons.edit), + ); + expect(saveButtonBottomLeft.dx, 800 - 80.0); + expect(saveButtonBottomLeft.dy, helpTextTopLeft.dy - 6.0); + expect(entryButtonBottomLeft.dx, saveButtonBottomLeft.dx - 48.0); + expect(entryButtonBottomLeft.dy, helpTextTopLeft.dy - 6.0); + + // Test help text position. + final Offset helpTextBottomLeft = tester.getBottomLeft(helpText); + expect(helpTextBottomLeft.dx, 72.0); + expect(helpTextBottomLeft.dy, closeButtonBottomRight.dy + 16.0); + + // Test the header position. + final Offset firstDateHeaderTopLeft = tester.getTopLeft(firstDateHeaderText); + final Offset lastDateHeaderTopLeft = tester.getTopLeft(lastDateHeaderText); + expect(firstDateHeaderTopLeft.dx, 72.0); + expect(firstDateHeaderTopLeft.dy, helpTextBottomLeft.dy + 8.0); + final Offset firstDateHeaderTopRight = tester.getTopRight(firstDateHeaderText); + expect(lastDateHeaderTopLeft.dx, firstDateHeaderTopRight.dx + 72.0); + expect(lastDateHeaderTopLeft.dy, helpTextBottomLeft.dy + 8.0); + + // Test the day headers position. + final Offset dayHeadersGridTopLeft = tester.getTopLeft(find.byType(GridView).first); + final Offset firstDateHeaderBottomLeft = tester.getBottomLeft(firstDateHeaderText); + expect(dayHeadersGridTopLeft.dx, (800 - 384) / 2); + expect(dayHeadersGridTopLeft.dy, firstDateHeaderBottomLeft.dy + 16.0); + + // Test the calendar custom scroll view position. + final Offset calendarScrollViewTopLeft = tester.getTopLeft(find.byType(CustomScrollView)); + final Offset dayHeadersGridBottomLeft = tester.getBottomLeft(find.byType(GridView).first); + expect(calendarScrollViewTopLeft.dx, 0.0); + expect(calendarScrollViewTopLeft.dy, dayHeadersGridBottomLeft.dy); + }); + }); + + testWidgets('Default Dialog properties (calendar mode)', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: false); + await preparePicker(tester, (Future<DateTimeRange?> range) async { + final Material dialogMaterial = tester.widget<Material>( + find.descendant(of: find.byType(Dialog), + matching: find.byType(Material), + ).first); + + expect(dialogMaterial.color, theme.colorScheme.surface); + expect(dialogMaterial.shadowColor, Colors.transparent); + expect(dialogMaterial.surfaceTintColor, Colors.transparent); + expect(dialogMaterial.elevation, 0.0); + expect(dialogMaterial.shape, const RoundedRectangleBorder()); + expect(dialogMaterial.clipBehavior, Clip.antiAlias); + + final Dialog dialog = tester.widget<Dialog>(find.byType(Dialog)); + expect(dialog.insetPadding, EdgeInsets.zero); + }); + }); + + testWidgets('Scaffold and AppBar defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: false); + await preparePicker(tester, (Future<DateTimeRange?> range) async { + final Scaffold scaffold = tester.widget<Scaffold>(find.byType(Scaffold)); + expect(scaffold.backgroundColor, theme.colorScheme.surface); + + final AppBar appBar = tester.widget<AppBar>(find.byType(AppBar)); + final IconThemeData iconTheme = IconThemeData(color: theme.colorScheme.onPrimary); + expect(appBar.iconTheme, iconTheme); + expect(appBar.actionsIconTheme, iconTheme); + expect(appBar.elevation, null); + expect(appBar.scrolledUnderElevation, null); + expect(appBar.backgroundColor, null); + }); + }); + + group('Input mode', () { + setUp(() { + firstDate = DateTime(2015); + lastDate = DateTime(2017, DateTime.december, 31); + initialDateRange = DateTimeRange( + start: DateTime(2017, DateTime.january, 15), + end: DateTime(2017, DateTime.january, 17), + ); + initialEntryMode = DatePickerEntryMode.input; + }); + + testWidgets('Default Dialog properties (input mode)', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: false); + await preparePicker(tester, (Future<DateTimeRange?> range) async { + final Material dialogMaterial = tester.widget<Material>( + find.descendant(of: find.byType(Dialog), + matching: find.byType(Material), + ).first); + + expect(dialogMaterial.color, theme.colorScheme.surface); + expect(dialogMaterial.shadowColor, theme.shadowColor); + expect(dialogMaterial.surfaceTintColor, null); + expect(dialogMaterial.elevation, 24.0); + expect( + dialogMaterial.shape, + const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))), + ); + expect(dialogMaterial.clipBehavior, Clip.antiAlias); + + final Dialog dialog = tester.widget<Dialog>(find.byType(Dialog)); + expect(dialog.insetPadding, const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0)); + }); + }); + + testWidgets('Default InputDecoration', (WidgetTester tester) async { + await preparePicker(tester, (Future<DateTimeRange?> range) async { + final InputDecoration startDateDecoration = tester.widget<TextField>( + find.byType(TextField).first).decoration!; + expect(startDateDecoration.border, const UnderlineInputBorder()); + expect(startDateDecoration.filled, false); + expect(startDateDecoration.hintText, 'mm/dd/yyyy'); + expect(startDateDecoration.labelText, 'Start Date'); + expect(startDateDecoration.errorText, null); + + final InputDecoration endDateDecoration = tester.widget<TextField>( + find.byType(TextField).last).decoration!; + expect(endDateDecoration.border, const UnderlineInputBorder()); + expect(endDateDecoration.filled, false); + expect(endDateDecoration.hintText, 'mm/dd/yyyy'); + expect(endDateDecoration.labelText, 'End Date'); + expect(endDateDecoration.errorText, null); + }); + }); + }); + }); } class _RestorableDateRangePickerDialogTestWidget extends StatefulWidget { diff --git a/packages/flutter/test/material/debug_test.dart b/packages/flutter/test/material/debug_test.dart index 00882c49015a3..5b36a71c2bad3 100644 --- a/packages/flutter/test/material/debug_test.dart +++ b/packages/flutter/test/material/debug_test.dart @@ -119,7 +119,7 @@ void main() { 'or WidgetsApp widget at the top of your application widget tree.\n', ), ); - expect(error.toStringDeep(), equalsIgnoringHashCodes( + expect(error.toStringDeep(), startsWith( 'FlutterError\n' ' No Scaffold widget found.\n' ' Builder widgets require a Scaffold widget ancestor.\n' @@ -128,114 +128,8 @@ void main() { ' The ancestors of this widget were:\n' ' Semantics\n' ' Builder\n' - ' RepaintBoundary-[GlobalKey#00000]\n' - ' IgnorePointer\n' - ' AnimatedBuilder\n' - ' FadeTransition\n' - ' FractionalTranslation\n' - ' SlideTransition\n' - ' _FadeUpwardsPageTransition\n' - ' AnimatedBuilder\n' - ' RepaintBoundary\n' - ' _FocusInheritedScope\n' - ' Semantics\n' - ' FocusScope\n' - ' PrimaryScrollController\n' - ' _ActionsScope\n' - ' Actions\n' - ' Builder\n' - ' PageStorage\n' - ' Offstage\n' - ' _ModalScopeStatus\n' - ' UnmanagedRestorationScope\n' - ' RestorationScope\n' - ' AnimatedBuilder\n' - ' _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#00000]\n' - ' Semantics\n' - ' _RenderTheaterMarker\n' - ' _EffectiveTickerMode\n' - ' TickerMode\n' - ' _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#00000]\n' - ' _Theater\n' - ' Overlay-[LabeledGlobalKey<OverlayState>#00000]\n' - ' UnmanagedRestorationScope\n' - ' _FocusInheritedScope\n' - ' Focus\n' - ' _FocusInheritedScope\n' - ' Focus\n' - ' FocusTraversalGroup\n' - ' AbsorbPointer\n' - ' Listener\n' - ' HeroControllerScope\n' - ' Navigator-[GlobalObjectKey<NavigatorState> _WidgetsAppState#00000]\n' - ' _FocusInheritedScope\n' - ' Semantics\n' - ' FocusScope\n' - ' DefaultSelectionStyle\n' - ' IconTheme\n' - ' IconTheme\n' - ' _InheritedCupertinoTheme\n' - ' CupertinoTheme\n' - ' _InheritedTheme\n' - ' Theme\n' - ' AnimatedTheme\n' - ' DefaultSelectionStyle\n' - ' _ScaffoldMessengerScope\n' - ' ScaffoldMessenger\n' - ' Builder\n' - ' DefaultTextStyle\n' - ' CustomPaint\n' - ' Banner\n' - ' CheckedModeBanner\n' - ' Title\n' - ' Directionality\n' - ' _LocalizationsScope-[GlobalKey#00000]\n' - ' Semantics\n' - ' Localizations\n' - ' Semantics\n' - ' _FocusInheritedScope\n' - ' Focus\n' - ' Shortcuts\n' - ' _ShortcutRegistrarScope\n' - ' ShortcutRegistrar\n' - ' TapRegionSurface\n' - ' _FocusInheritedScope\n' - ' Focus\n' - ' FocusTraversalGroup\n' - ' _ActionsScope\n' - ' Actions\n' - '${kIsWeb - ? ' Semantics\n' - ' _FocusInheritedScope\n' - ' Focus\n' - ' Shortcuts\n' - : ''}' - ' Semantics\n' - ' _FocusInheritedScope\n' - ' Focus\n' - ' Shortcuts\n' - ' DefaultTextEditingShortcuts\n' - ' Semantics\n' - ' _FocusInheritedScope\n' - ' Focus\n' - ' Shortcuts\n' - ' _SharedAppModel\n' - ' SharedAppData\n' - ' UnmanagedRestorationScope\n' - ' RestorationScope\n' - ' UnmanagedRestorationScope\n' - ' RootRestorationScope\n' - ' WidgetsApp-[GlobalObjectKey _MaterialAppState#00000]\n' - ' Semantics\n' - ' _FocusInheritedScope\n' - ' Focus\n' - ' HeroControllerScope\n' - ' ScrollConfiguration\n' - ' MaterialApp\n' - ' MediaQuery\n' - ' _MediaQueryFromView\n' - ' _ViewScope\n' - ' View-[GlobalObjectKey TestFlutterView#00000]\n' + )); + expect(error.toStringDeep(), endsWith( ' [root]\n' ' Typically, the Scaffold widget is introduced by the MaterialApp\n' ' or WidgetsApp widget at the top of your application widget tree.\n' @@ -302,7 +196,7 @@ void main() { 'MaterialApp at the top of your application widget tree.\n', ), ); - expect(error.toStringDeep(), equalsIgnoringHashCodes( + expect(error.toStringDeep(), startsWith( 'FlutterError\n' ' No ScaffoldMessenger widget found.\n' ' SnackBarAction widgets require a ScaffoldMessenger widget\n' @@ -314,69 +208,8 @@ void main() { ' TextButtonTheme\n' ' Padding\n' ' Row\n' - ' Wrap\n' - ' Padding\n' - ' MediaQuery\n' - ' Padding\n' - ' SafeArea\n' - ' FadeTransition\n' - ' DefaultSelectionStyle\n' - ' IconTheme\n' - ' IconTheme\n' - ' _InheritedCupertinoTheme\n' - ' CupertinoTheme\n' - ' _InheritedTheme\n' - ' Theme\n' - ' DefaultTextStyle\n' - ' AnimatedDefaultTextStyle\n' - ' _InkFeatures-[GlobalKey#00000 ink renderer]\n' - ' NotificationListener<LayoutChangedNotification>\n' - ' PhysicalModel\n' - ' AnimatedPhysicalModel\n' - ' Material\n' - ' KeyedSubtree-[GlobalKey#00000]\n' - ' FractionalTranslation\n' - ' SlideTransition\n' - ' Listener\n' - ' _GestureSemantics\n' - ' RawGestureDetector\n' - ' GestureDetector\n' - " Dismissible-[<'dismissible'>]\n" - ' Semantics\n' - ' Align\n' - ' AnimatedBuilder\n' - ' ClipRect\n' - ' KeyedSubtree-[GlobalKey#00000]\n' - ' _EffectiveTickerMode\n' - ' TickerMode\n' - ' Offstage\n' - ' SizedBox\n' - ' Hero\n' - ' SnackBar-[#00000]\n' - ' MediaQuery\n' - ' LayoutId-[<_ScaffoldSlot.snackBar>]\n' - ' CustomMultiChildLayout\n' - ' _ActionsScope\n' - ' Actions\n' - ' AnimatedBuilder\n' - ' DefaultTextStyle\n' - ' AnimatedDefaultTextStyle\n' - ' _InkFeatures-[GlobalKey#00000 ink renderer]\n' - ' NotificationListener<LayoutChangedNotification>\n' - ' PhysicalModel\n' - ' AnimatedPhysicalModel\n' - ' Material\n' - ' _ScrollNotificationObserverScope\n' - ' NotificationListener<ScrollNotification>\n' - ' NotificationListener<ScrollMetricsNotification>\n' - ' ScrollNotificationObserver\n' - ' _ScaffoldScope\n' - ' Scaffold-[LabeledGlobalKey<ScaffoldState>#00000]\n' - ' Directionality\n' - ' MediaQuery\n' - ' _MediaQueryFromView\n' - ' _ViewScope\n' - ' View-[GlobalObjectKey TestFlutterView#00000]\n' + )); + expect(error.toStringDeep(), endsWith( ' [root]\n' ' Typically, the ScaffoldMessenger widget is introduced by the\n' ' MaterialApp at the top of your application widget tree.\n' diff --git a/packages/flutter/test/material/dialog_test.dart b/packages/flutter/test/material/dialog_test.dart index 9b2aebceea781..7e467635a6eb5 100644 --- a/packages/flutter/test/material/dialog_test.dart +++ b/packages/flutter/test/material/dialog_test.dart @@ -293,16 +293,17 @@ void main() { }); testWidgets('Null dialog shape', (WidgetTester tester) async { + final ThemeData theme = ThemeData(); const AlertDialog dialog = AlertDialog( actions: <Widget>[ ], ); - await tester.pumpWidget(_buildAppWithDialog(dialog)); + await tester.pumpWidget(_buildAppWithDialog(dialog, theme: theme)); await tester.tap(find.text('X')); await tester.pumpAndSettle(); final Material materialWidget = _getMaterialFromDialog(tester); - expect(materialWidget.shape, _defaultM2DialogShape); + expect(materialWidget.shape, theme.useMaterial3 ? _defaultM3DialogShape : _defaultM2DialogShape); }); testWidgets('Rectangular dialog shape', (WidgetTester tester) async { @@ -767,7 +768,7 @@ void main() { ); await tester.pumpWidget( - _buildAppWithDialog(dialog), + _buildAppWithDialog(dialog, theme: ThemeData(useMaterial3: false)), ); await tester.tap(find.text('X')); @@ -2544,6 +2545,7 @@ void main() { Widget buildFrame(MainAxisAlignment? alignment) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: AlertDialog( content: const SizedBox(width: 800), diff --git a/packages/flutter/test/material/dialog_theme_test.dart b/packages/flutter/test/material/dialog_theme_test.dart index f02bd471e9e04..f215649d4826c 100644 --- a/packages/flutter/test/material/dialog_theme_test.dart +++ b/packages/flutter/test/material/dialog_theme_test.dart @@ -171,7 +171,7 @@ void main() { actions: <Widget>[ ], alignment: Alignment.topRight, ); - final ThemeData theme = ThemeData(dialogTheme: const DialogTheme(alignment: Alignment.bottomLeft)); + final ThemeData theme = ThemeData(useMaterial3: false, dialogTheme: const DialogTheme(alignment: Alignment.bottomLeft)); await tester.pumpWidget( _appWithDialog(tester, dialog, theme: theme), @@ -193,7 +193,7 @@ void main() { title: Text('Title'), actions: <Widget>[ ], ); - final ThemeData theme = ThemeData(dialogTheme: const DialogTheme(shape: customBorder)); + final ThemeData theme = ThemeData(useMaterial3: false, dialogTheme: const DialogTheme(shape: customBorder)); await tester.pumpWidget(_appWithDialog(tester, dialog, theme: theme)); await tester.tap(find.text('X')); @@ -248,7 +248,7 @@ void main() { testWidgets('Custom Icon Color - Theme - lowest preference', (WidgetTester tester) async { const Color iconThemeColor = Colors.yellow; - final ThemeData theme = ThemeData(iconTheme: const IconThemeData(color: iconThemeColor)); + final ThemeData theme = ThemeData(useMaterial3: false, iconTheme: const IconThemeData(color: iconThemeColor)); const AlertDialog dialog = AlertDialog( icon: Icon(Icons.ac_unit), actions: <Widget>[ ], @@ -320,7 +320,7 @@ void main() { title: Text(titleText), actions: <Widget>[ ], ); - final ThemeData theme = ThemeData(textTheme: const TextTheme(titleLarge: titleTextStyle)); + final ThemeData theme = ThemeData(useMaterial3: false, textTheme: const TextTheme(titleLarge: titleTextStyle)); await tester.pumpWidget(_appWithDialog(tester, dialog, theme: theme)); await tester.tap(find.text('X')); @@ -419,7 +419,7 @@ void main() { content: Text(contentText), actions: <Widget>[ ], ); - final ThemeData theme = ThemeData(textTheme: const TextTheme(titleMedium: contentTextStyle)); + final ThemeData theme = ThemeData(useMaterial3: false, textTheme: const TextTheme(titleMedium: contentTextStyle)); await tester.pumpWidget(_appWithDialog(tester, dialog, theme: theme)); await tester.tap(find.text('X')); diff --git a/packages/flutter/test/material/divider_test.dart b/packages/flutter/test/material/divider_test.dart index 10681474cc164..7136767808305 100644 --- a/packages/flutter/test/material/divider_test.dart +++ b/packages/flutter/test/material/divider_test.dart @@ -9,9 +9,9 @@ import '../rendering/mock_canvas.dart'; void main() { testWidgets('Divider control test', (WidgetTester tester) async { await tester.pumpWidget( - const Directionality( - textDirection: TextDirection.ltr, - child: Center( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Center( child: Divider(), ), ), @@ -94,9 +94,9 @@ void main() { testWidgets('Vertical Divider Test', (WidgetTester tester) async { await tester.pumpWidget( - const Directionality( - textDirection: TextDirection.ltr, - child: Center( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Center( child: VerticalDivider(), ), ), diff --git a/packages/flutter/test/material/divider_theme_test.dart b/packages/flutter/test/material/divider_theme_test.dart index 5f18f3b655b8e..49afe07313a0d 100644 --- a/packages/flutter/test/material/divider_theme_test.dart +++ b/packages/flutter/test/material/divider_theme_test.dart @@ -266,13 +266,15 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. group('Horizontal Divider', () { testWidgets('Passing no DividerThemeData returns defaults', (WidgetTester tester) async { - await tester.pumpWidget(const MaterialApp( - home: Scaffold( + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( body: Divider(), ), )); @@ -284,7 +286,7 @@ void main() { final BoxDecoration decoration = container.decoration! as BoxDecoration; expect(decoration.border!.bottom.width, 0.0); - final ThemeData theme = ThemeData(); + final ThemeData theme = ThemeData(useMaterial3: false); expect(decoration.border!.bottom.color, theme.dividerColor); final Rect dividerRect = tester.getRect(find.byType(Divider)); @@ -313,8 +315,9 @@ void main() { group('Vertical Divider', () { testWidgets('Passing no DividerThemeData returns defaults', (WidgetTester tester) async { - await tester.pumpWidget(const MaterialApp( - home: Scaffold( + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( body: VerticalDivider(), ), )); @@ -327,7 +330,7 @@ void main() { final Border border = decoration.border! as Border; expect(border.left.width, 0.0); - final ThemeData theme = ThemeData(); + final ThemeData theme = ThemeData(useMaterial3: false); expect(border.left.color, theme.dividerColor); final Rect dividerRect = tester.getRect(find.byType(VerticalDivider)); diff --git a/packages/flutter/test/material/drawer_test.dart b/packages/flutter/test/material/drawer_test.dart index 63d46003f2294..faf3c2522bb09 100644 --- a/packages/flutter/test/material/drawer_test.dart +++ b/packages/flutter/test/material/drawer_test.dart @@ -14,6 +14,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( drawer: Drawer( child: ListView( @@ -740,8 +741,9 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('Drawer default shape', (WidgetTester tester) async { await tester.pumpWidget( diff --git a/packages/flutter/test/material/drawer_theme_test.dart b/packages/flutter/test/material/drawer_theme_test.dart index 9894c59e5a0fe..1a25f288e7f64 100644 --- a/packages/flutter/test/material/drawer_theme_test.dart +++ b/packages/flutter/test/material/drawer_theme_test.dart @@ -60,9 +60,11 @@ void main() { testWidgets('Default values are used when no Drawer or DrawerThemeData properties are specified', (WidgetTester tester) async { final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); - final bool useMaterial3 = ThemeData().useMaterial3; + final ThemeData theme = ThemeData(); + final bool useMaterial3 = theme.useMaterial3; await tester.pumpWidget( MaterialApp( + theme: theme, home: Scaffold( key: scaffoldKey, drawer: const Drawer(), @@ -72,10 +74,10 @@ void main() { scaffoldKey.currentState!.openDrawer(); await tester.pumpAndSettle(); - expect(_drawerMaterial(tester).color, null); - expect(_drawerMaterial(tester).elevation, 16.0); + expect(_drawerMaterial(tester).color, useMaterial3 ? theme.colorScheme.surface : null); + expect(_drawerMaterial(tester).elevation, useMaterial3 ? 1.0 : 16.0); expect(_drawerMaterial(tester).shadowColor, useMaterial3 ? Colors.transparent : ThemeData().shadowColor); - expect(_drawerMaterial(tester).surfaceTintColor, useMaterial3 ? ThemeData().colorScheme.surfaceTint : null); + expect(_drawerMaterial(tester).surfaceTintColor, useMaterial3 ? theme.colorScheme.surfaceTint : null); expect( _drawerMaterial(tester).shape, useMaterial3 @@ -88,9 +90,11 @@ void main() { testWidgets('Default values are used when no Drawer or DrawerThemeData properties are specified in end drawer', (WidgetTester tester) async { final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); - final bool useMaterial3 = ThemeData().useMaterial3; + final ThemeData theme = ThemeData(); + final bool useMaterial3 = theme.useMaterial3; await tester.pumpWidget( MaterialApp( + theme: theme, home: Scaffold( key: scaffoldKey, endDrawer: const Drawer(), @@ -100,8 +104,8 @@ void main() { scaffoldKey.currentState!.openEndDrawer(); await tester.pumpAndSettle(); - expect(_drawerMaterial(tester).color, null); - expect(_drawerMaterial(tester).elevation, 16.0); + expect(_drawerMaterial(tester).color, useMaterial3 ? theme.colorScheme.surface : null); + expect(_drawerMaterial(tester).elevation, useMaterial3 ? 1.0 : 16.0); expect(_drawerMaterial(tester).shadowColor, useMaterial3 ? Colors.transparent : ThemeData().shadowColor); expect(_drawerMaterial(tester).surfaceTintColor, useMaterial3 ? ThemeData().colorScheme.surfaceTint : null); expect( diff --git a/packages/flutter/test/material/dropdown_menu_test.dart b/packages/flutter/test/material/dropdown_menu_test.dart index e326411e4f686..71f05e87ae638 100644 --- a/packages/flutter/test/material/dropdown_menu_test.dart +++ b/packages/flutter/test/material/dropdown_menu_test.dart @@ -112,7 +112,8 @@ void main() { testWidgets('The width of the text field should always be the same as the menu view', (WidgetTester tester) async { - final ThemeData themeData = ThemeData(); + final ThemeData themeData = ThemeData(useMaterial3: false); + final bool useMaterial3 = themeData.useMaterial3; await tester.pumpWidget( MaterialApp( theme: themeData, @@ -128,7 +129,7 @@ void main() { final Finder textField = find.byType(TextField); final Size anchorSize = tester.getSize(textField); - expect(anchorSize, const Size(180.0, 56.0)); + expect(anchorSize, useMaterial3 ? const Size(195.0, 60.0) : const Size(180.0, 56.0)); await tester.tap(find.byType(DropdownMenu<TestMenu>)); await tester.pumpAndSettle(); @@ -138,7 +139,7 @@ void main() { matching: find.byType(Material), ); final Size menuSize = tester.getSize(menuMaterial); - expect(menuSize, const Size(180.0, 304.0)); + expect(menuSize, useMaterial3 ? const Size(195.0, 304.0) : const Size(180.0, 304.0)); // The text field should have same width as the menu // when the width property is not null. @@ -146,7 +147,7 @@ void main() { final Finder anchor = find.byType(TextField); final Size size = tester.getSize(anchor); - expect(size, const Size(200.0, 56.0)); + expect(size, useMaterial3 ? const Size(200.0, 60.0) : const Size(200.0, 56.0)); await tester.tap(anchor); await tester.pumpAndSettle(); @@ -218,6 +219,7 @@ void main() { testWidgets('The menuHeight property can be used to show a shorter scrollable menu list instead of the complete list', (WidgetTester tester) async { final ThemeData themeData = ThemeData(); + final bool material3 = themeData.useMaterial3; await tester.pumpWidget(buildTest(themeData, menuChildren)); await tester.tap(find.byType(DropdownMenu<TestMenu>)); @@ -237,7 +239,7 @@ void main() { matching: find.byType(Padding), ).first; final Size menuViewSize = tester.getSize(menuView); - expect(menuViewSize, const Size(180.0, 304.0)); // 304 = 288 + vertical padding(2 * 8) + expect(menuViewSize, material3 ? const Size(195.0, 304.0) : const Size(180.0, 304.0)); // 304 = 288 + vertical padding(2 * 8) // Constrains the menu height. await tester.pumpWidget(Container()); @@ -253,7 +255,7 @@ void main() { ).first; final Size updatedMenuSize = tester.getSize(updatedMenu); - expect(updatedMenuSize, const Size(180.0, 100.0)); + expect(updatedMenuSize, material3 ? const Size(195.0, 100.0) : const Size(180.0, 100.0)); }); testWidgets('The text in the menu button should be aligned with the text of ' @@ -1313,6 +1315,29 @@ void main() { await tester.pumpWidget(buildFrame()); expect(find.text(errorText), findsOneWidget); }); + + testWidgets('Can scroll to the highlighted item', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: DropdownMenu<TestMenu>( + requestFocusOnTap: true, + menuHeight: 100, // Give a small number so the list can only show 2 or 3 items. + dropdownMenuEntries: menuChildren, + ), + ), + )); + await tester.pumpAndSettle(); + + await tester.tap(find.byType(DropdownMenu<TestMenu>)); + await tester.pumpAndSettle(); + + expect(find.text('Item 5').hitTestable(), findsNothing); + await tester.enterText(find.byType(TextField), '5'); + await tester.pumpAndSettle(); + // Item 5 should show up. + expect(find.text('Item 5').hitTestable(), findsOneWidget); + }); + } enum TestMenu { diff --git a/packages/flutter/test/material/dropdown_test.dart b/packages/flutter/test/material/dropdown_test.dart index cb53f4a17961d..d2a3c9d14f86e 100644 --- a/packages/flutter/test/material/dropdown_test.dart +++ b/packages/flutter/test/material/dropdown_test.dart @@ -16,6 +16,7 @@ library; import 'dart:math' as math; import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -161,39 +162,43 @@ Widget buildFrame({ double? menuMaxHeight, EdgeInsetsGeometry? padding, Alignment dropdownAlignment = Alignment.center, + bool? useMaterial3, }) { - return TestApp( - textDirection: textDirection, - mediaSize: mediaSize, - child: Material( - child: Align( - alignment: dropdownAlignment, - child: RepaintBoundary( - child: buildDropdown( - isFormField: isFormField, - buttonKey: buttonKey, - value: value, - hint: hint, - disabledHint: disabledHint, - onChanged: onChanged, - onTap: onTap, - icon: icon, - iconSize: iconSize, - iconDisabledColor: iconDisabledColor, - iconEnabledColor: iconEnabledColor, - isDense: isDense, - isExpanded: isExpanded, - underline: underline, - focusNode: focusNode, - autofocus: autofocus, - focusColor: focusColor, - dropdownColor: dropdownColor, - items: items, - selectedItemBuilder: selectedItemBuilder, - itemHeight: itemHeight, - alignment: alignment, - menuMaxHeight: menuMaxHeight, - padding: padding, + return Theme( + data: ThemeData(useMaterial3: useMaterial3), + child: TestApp( + textDirection: textDirection, + mediaSize: mediaSize, + child: Material( + child: Align( + alignment: dropdownAlignment, + child: RepaintBoundary( + child: buildDropdown( + isFormField: isFormField, + buttonKey: buttonKey, + value: value, + hint: hint, + disabledHint: disabledHint, + onChanged: onChanged, + onTap: onTap, + icon: icon, + iconSize: iconSize, + iconDisabledColor: iconDisabledColor, + iconEnabledColor: iconEnabledColor, + isDense: isDense, + isExpanded: isExpanded, + underline: underline, + focusNode: focusNode, + autofocus: autofocus, + focusColor: focusColor, + dropdownColor: dropdownColor, + items: items, + selectedItemBuilder: selectedItemBuilder, + itemHeight: itemHeight, + alignment: alignment, + menuMaxHeight: menuMaxHeight, + padding: padding, + ), ), ), ), @@ -207,6 +212,7 @@ Widget buildDropdownWithHint({ bool enableSelectedItemBuilder = false, }){ return buildFrame( + useMaterial3: false, mediaSize: const Size(800, 600), itemHeight: 100.0, alignment: alignment, @@ -286,6 +292,7 @@ Future<void> checkDropdownColor(WidgetTester tester, {Color? color, bool isFormF const String text = 'foo'; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: isFormField ? Form( @@ -335,7 +342,7 @@ Future<void> checkDropdownColor(WidgetTester tester, {Color? color, bool isFormF void main() { testWidgets('Default dropdown golden', (WidgetTester tester) async { final Key buttonKey = UniqueKey(); - Widget build() => buildFrame(buttonKey: buttonKey, onChanged: onChanged); + Widget build() => buildFrame(buttonKey: buttonKey, onChanged: onChanged, useMaterial3: false); await tester.pumpWidget(build()); final Finder buttonFinder = find.byKey(buttonKey); assert(tester.renderObject(buttonFinder).attached); @@ -347,7 +354,7 @@ void main() { testWidgets('Expanded dropdown golden', (WidgetTester tester) async { final Key buttonKey = UniqueKey(); - Widget build() => buildFrame(buttonKey: buttonKey, isExpanded: true, onChanged: onChanged); + Widget build() => buildFrame(buttonKey: buttonKey, isExpanded: true, onChanged: onChanged, useMaterial3: false); await tester.pumpWidget(build()); final Finder buttonFinder = find.byKey(buttonKey); assert(tester.renderObject(buttonFinder).attached); @@ -609,6 +616,7 @@ void main() { // Regression test for https://github.com/flutter/flutter/issues/66870 await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( appBar: AppBar(), body: Column( @@ -697,7 +705,7 @@ void main() { testWidgets('Dropdown button aligns selected menu item ($textDirection)', (WidgetTester tester) async { final Key buttonKey = UniqueKey(); - Widget build() => buildFrame(buttonKey: buttonKey, textDirection: textDirection, onChanged: onChanged); + Widget build() => buildFrame(buttonKey: buttonKey, textDirection: textDirection, onChanged: onChanged, useMaterial3: false); await tester.pumpWidget(build()); final RenderBox buttonBox = tester.renderObject<RenderBox>(find.byKey(buttonKey)); @@ -2382,12 +2390,12 @@ void main() { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; final UniqueKey buttonKey = UniqueKey(); final FocusNode focusNode = FocusNode(debugLabel: 'DropdownButton'); - await tester.pumpWidget(buildFrame(buttonKey: buttonKey, onChanged: onChanged, focusNode: focusNode, autofocus: true)); + await tester.pumpWidget(buildFrame(buttonKey: buttonKey, onChanged: onChanged, focusNode: focusNode, autofocus: true, useMaterial3: false)); await tester.pumpAndSettle(); // Pump a frame for autofocus to take effect. expect(focusNode.hasPrimaryFocus, isTrue); expect(find.byType(Material), paints..rect(rect: const Rect.fromLTRB(348.0, 276.0, 452.0, 324.0), color: const Color(0x1f000000))); - await tester.pumpWidget(buildFrame(buttonKey: buttonKey, onChanged: onChanged, focusNode: focusNode, focusColor: const Color(0xff00ff00))); + await tester.pumpWidget(buildFrame(buttonKey: buttonKey, onChanged: onChanged, focusNode: focusNode, focusColor: const Color(0xff00ff00), useMaterial3: false)); await tester.pumpAndSettle(); // Pump a frame for autofocus to take effect. expect(find.byType(Material), paints..rect(rect: const Rect.fromLTRB(348.0, 276.0, 452.0, 324.0), color: const Color(0x1f00ff00))); }); @@ -2696,6 +2704,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( child: StatefulBuilder( @@ -3169,6 +3178,7 @@ void main() { final UniqueKey itemKey = UniqueKey(); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( child: DropdownButton<String>( @@ -3504,8 +3514,9 @@ void main() { await tester.pumpAndSettle(); // finish the menu animation // The inherited ScrollBehavior should not apply Scrollbars since they are - // already built in to the widget. - expect(find.byType(CupertinoScrollbar), findsNothing); + // already built in to the widget. For iOS platform, ScrollBar directly returns + // CupertinoScrollbar + expect(find.byType(CupertinoScrollbar), debugDefaultTargetPlatformOverride == TargetPlatform.iOS ? findsOneWidget : findsNothing); expect(find.byType(Scrollbar), findsOneWidget); expect(find.byType(RawScrollbar), findsNothing); @@ -3516,6 +3527,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( child: DropdownButton<String>( diff --git a/packages/flutter/test/material/elevated_button_test.dart b/packages/flutter/test/material/elevated_button_test.dart index 9bfe47ed93434..6c9cc205a54fa 100644 --- a/packages/flutter/test/material/elevated_button_test.dart +++ b/packages/flutter/test/material/elevated_button_test.dart @@ -738,18 +738,21 @@ void main() { testWidgets('Does ElevatedButton contribute semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: ElevatedButton( - style: const ButtonStyle( - // Specifying minimumSize to mimic the original minimumSize for - // RaisedButton so that the semantics tree's rect and transform - // match the original version of this test. - minimumSize: MaterialStatePropertyAll<Size>(Size(88, 36)), + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: ElevatedButton( + style: const ButtonStyle( + // Specifying minimumSize to mimic the original minimumSize for + // RaisedButton so that the semantics tree's rect and transform + // match the original version of this test. + minimumSize: MaterialStatePropertyAll<Size>(Size(88, 36)), + ), + onPressed: () { }, + child: const Text('ABC'), ), - onPressed: () { }, - child: const Text('ABC'), ), ), ), @@ -790,7 +793,7 @@ void main() { Widget buildFrame(MaterialTapTargetSize tapTargetSize, Key key) { return Theme( - data: ThemeData(materialTapTargetSize: tapTargetSize), + data: ThemeData(useMaterial3: false, materialTapTargetSize: tapTargetSize), child: Directionality( textDirection: TextDirection.ltr, child: Center( @@ -838,9 +841,7 @@ void main() { Future<void> buildTest(VisualDensity visualDensity, {bool useText = false}) async { return tester.pumpWidget( MaterialApp( - // Test was setup using fonts from Material 2, so make sure we always - // test against englishLike2014. - theme: ThemeData(textTheme: Typography.englishLike2014), + theme: ThemeData(useMaterial3: false), home: Directionality( textDirection: TextDirection.rtl, child: Center( @@ -1033,10 +1034,8 @@ void main() { await tester.pumpWidget( MaterialApp( theme: ThemeData( + useMaterial3: false, colorScheme: const ColorScheme.light(), - // Force Material 2 defaults for the typography and size - // default values as the test was designed against these settings. - textTheme: Typography.englishLike2014, elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom(minimumSize: const Size(64, 36)), ), diff --git a/packages/flutter/test/material/elevated_button_theme_test.dart b/packages/flutter/test/material/elevated_button_theme_test.dart index 7c6dfa30359e5..1957b7463874e 100644 --- a/packages/flutter/test/material/elevated_button_theme_test.dart +++ b/packages/flutter/test/material/elevated_button_theme_test.dart @@ -12,11 +12,48 @@ void main() { expect(identical(ElevatedButtonThemeData.lerp(data, data, 0.5), data), true); }); - testWidgets('Passing no ElevatedButtonTheme returns defaults', (WidgetTester tester) async { + testWidgets('Material3: Passing no ElevatedButtonTheme returns defaults', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme), + theme: ThemeData.from(colorScheme: colorScheme, useMaterial3: true), + home: Scaffold( + body: Center( + child: ElevatedButton( + onPressed: () { }, + child: const Text('button'), + ), + ), + ), + ), + ); + + final Finder buttonMaterial = find.descendant( + of: find.byType(ElevatedButton), + matching: find.byType(Material), + ); + + final Material material = tester.widget<Material>(buttonMaterial); + expect(material.animationDuration, const Duration(milliseconds: 200)); + expect(material.borderRadius, null); + expect(material.color, colorScheme.surface); + expect(material.elevation, 1); + expect(material.shadowColor, colorScheme.shadow); + expect(material.shape, const StadiumBorder()); + expect(material.textStyle!.color, colorScheme.primary); + expect(material.textStyle!.fontFamily, 'Roboto'); + expect(material.textStyle!.fontSize, 14); + expect(material.textStyle!.fontWeight, FontWeight.w500); + + final Align align = tester.firstWidget<Align>(find.ancestor(of: find.text('button'), matching: find.byType(Align))); + expect(align.alignment, Alignment.center); + }); + + testWidgets('Material2: Passing no ElevatedButtonTheme returns defaults', (WidgetTester tester) async { + const ColorScheme colorScheme = ColorScheme.light(); + await tester.pumpWidget( + MaterialApp( + theme: ThemeData.from(colorScheme: colorScheme, useMaterial3: false), home: Scaffold( body: Center( child: ElevatedButton( @@ -98,7 +135,7 @@ void main() { }, ); return MaterialApp( - theme: ThemeData.from(colorScheme: const ColorScheme.light()).copyWith( + theme: ThemeData.from(useMaterial3: false, colorScheme: const ColorScheme.light()).copyWith( elevatedButtonTheme: ElevatedButtonThemeData(style: overallStyle), ), home: Scaffold( @@ -191,14 +228,85 @@ void main() { }); }); - testWidgets('Theme shadowColor', (WidgetTester tester) async { + testWidgets('Material 3: Theme shadowColor', (WidgetTester tester) async { + const ColorScheme colorScheme = ColorScheme.light(); + const Color shadowColor = Color(0xff000001); + const Color overriddenColor = Color(0xff000002); + + Widget buildFrame({ Color? overallShadowColor, Color? themeShadowColor, Color? shadowColor }) { + return MaterialApp( + theme: ThemeData.from( + useMaterial3: true, + colorScheme: colorScheme.copyWith(shadow: overallShadowColor), + ), + home: Scaffold( + body: Center( + child: ElevatedButtonTheme( + data: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + shadowColor: themeShadowColor, + ), + ), + child: Builder( + builder: (BuildContext context) { + return ElevatedButton( + style: ElevatedButton.styleFrom( + shadowColor: shadowColor, + ), + onPressed: () { }, + child: const Text('button'), + ); + }, + ), + ), + ), + ), + ); + } + + final Finder buttonMaterialFinder = find.descendant( + of: find.byType(ElevatedButton), + matching: find.byType(Material), + ); + + await tester.pumpWidget(buildFrame()); + Material material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, Colors.black); //default + + await tester.pumpWidget(buildFrame(overallShadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, shadowColor); + + await tester.pumpWidget(buildFrame(themeShadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, shadowColor); + + await tester.pumpWidget(buildFrame(shadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, shadowColor); + + await tester.pumpWidget(buildFrame(overallShadowColor: overriddenColor, themeShadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, shadowColor); + + await tester.pumpWidget(buildFrame(themeShadowColor: overriddenColor, shadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, shadowColor); + }); + + testWidgets('Material 2: Theme shadowColor', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); const Color shadowColor = Color(0xff000001); const Color overriddenColor = Color(0xff000002); Widget buildFrame({ Color? overallShadowColor, Color? themeShadowColor, Color? shadowColor }) { return MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme).copyWith( + theme: ThemeData.from(useMaterial3: false, colorScheme: colorScheme).copyWith( shadowColor: overallShadowColor, ), home: Scaffold( diff --git a/packages/flutter/test/material/expand_icon_test.dart b/packages/flutter/test/material/expand_icon_test.dart index 8acf7071c169a..4d795da9adb66 100644 --- a/packages/flutter/test/material/expand_icon_test.dart +++ b/packages/flutter/test/material/expand_icon_test.dart @@ -77,6 +77,7 @@ void main() { IconTheme iconTheme; // Light mode test await tester.pumpWidget(wrap( + theme: ThemeData(useMaterial3: false), child: const ExpandIcon(onPressed: null), )); await tester.pumpAndSettle(); @@ -87,7 +88,7 @@ void main() { // Dark mode test await tester.pumpWidget(wrap( child: const ExpandIcon(onPressed: null), - theme: ThemeData(brightness: Brightness.dark), + theme: ThemeData(useMaterial3: false, brightness: Brightness.dark), )); await tester.pumpAndSettle(); @@ -176,6 +177,7 @@ void main() { final SemanticsHandle handle = tester.ensureSemantics(); const DefaultMaterialLocalizations localizations = DefaultMaterialLocalizations(); await tester.pumpWidget(wrap( + theme: ThemeData(useMaterial3: false), child: ExpandIcon( isExpanded: true, onPressed: (bool _) { }, diff --git a/packages/flutter/test/material/expansion_panel_test.dart b/packages/flutter/test/material/expansion_panel_test.dart index 1adbae5b3a957..7c2fc32a49bd9 100644 --- a/packages/flutter/test/material/expansion_panel_test.dart +++ b/packages/flutter/test/material/expansion_panel_test.dart @@ -144,7 +144,7 @@ void main() { expect(find.byType(ExpandIcon), findsOneWidget); await tester.tap(find.byType(ExpandIcon)); expect(capturedIndex, 0); - expect(capturedIsExpanded, isFalse); + expect(capturedIsExpanded, isTrue); box = tester.renderObject(find.byType(ExpansionPanelList)); expect(box.size.height, equals(oldHeight)); @@ -183,6 +183,7 @@ void main() { final Key headerKey = UniqueKey(); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: ExpansionPanelListSemanticsTest(headerKey: headerKey), ), ); @@ -556,7 +557,7 @@ void main() { // Callback is invoked once with appropriate arguments expect(callbackHistory.length, equals(1)); expect(callbackHistory.last['index'], equals(1)); - expect(callbackHistory.last['isExpanded'], equals(false)); + expect(callbackHistory.last['isExpanded'], equals(true)); // Close the same panel await tester.tap(find.byType(ExpandIcon).at(1)); @@ -565,7 +566,7 @@ void main() { // Callback is invoked once with appropriate arguments expect(callbackHistory.length, equals(2)); expect(callbackHistory.last['index'], equals(1)); - expect(callbackHistory.last['isExpanded'], equals(true)); + expect(callbackHistory.last['isExpanded'], equals(false)); }); testWidgets('Radio mode calls expansionCallback twice if other panel open prior', (WidgetTester tester) async { @@ -630,7 +631,7 @@ void main() { expect(callbackHistory.length, equals(1)); callbackResults = callbackHistory[callbackHistory.length - 1]; expect(callbackResults['index'], equals(1)); - expect(callbackResults['isExpanded'], equals(false)); + expect(callbackResults['isExpanded'], equals(true)); // Close a different panel await tester.tap(find.byType(ExpandIcon).at(2)); @@ -639,13 +640,108 @@ void main() { // Callback is invoked the first time with correct arguments expect(callbackHistory.length, equals(3)); callbackResults = callbackHistory[callbackHistory.length - 2]; - expect(callbackResults['index'], equals(2)); + expect(callbackResults['index'], equals(1)); expect(callbackResults['isExpanded'], equals(false)); // Callback is invoked the second time with correct arguments callbackResults = callbackHistory[callbackHistory.length - 1]; - expect(callbackResults['index'], equals(1)); - expect(callbackResults['isExpanded'], equals(false)); + expect(callbackResults['index'], equals(2)); + expect(callbackResults['isExpanded'], equals(true)); + }); + + testWidgets('ExpansionPanelList.radio callback displays true or false based on the visibility of a list item', (WidgetTester tester) async { + late int lastExpanded; + bool topElementExpanded = false; + bool bottomElementExpanded = false; + + final List<ExpansionPanel> demoItemsRadio = <ExpansionPanelRadio>[ + // topElement + ExpansionPanelRadio( + headerBuilder: (BuildContext context, bool isExpanded) { + return Text(isExpanded ? 'B' : 'A'); + }, + body: const SizedBox(height: 100.0), + value: 0, + ), + // bottomElement + ExpansionPanelRadio( + headerBuilder: (BuildContext context, bool isExpanded) { + return Text(isExpanded ? 'D' : 'C'); + }, + body: const SizedBox(height: 100.0), + value: 1, + ), + ]; + + final ExpansionPanelList expansionListRadio = ExpansionPanelList.radio( + children: demoItemsRadio, + expansionCallback: (int index, bool isExpanded) + { + lastExpanded = index; + if (index == 0) + { + topElementExpanded = isExpanded; + bottomElementExpanded = false; + } + else + { + topElementExpanded = false; + bottomElementExpanded = isExpanded; + } + } + ); + + await tester.pumpWidget( + MaterialApp( + home: SingleChildScrollView( + child: expansionListRadio, + ), + ), + ); + + // Initializes with all panels closed. + expect(find.text('A'), findsOneWidget); + expect(find.text('B'), findsNothing); + expect(find.text('C'), findsOneWidget); + expect(find.text('D'), findsNothing); + + await tester.tap(find.byType(ExpandIcon).at(0)); + await tester.pump(const Duration(milliseconds: 200)); + await tester.pumpAndSettle(); + + // Now the first panel is open. + expect(find.text('A'), findsNothing); + expect(find.text('B'), findsOneWidget); + expect(find.text('C'), findsOneWidget); + expect(find.text('D'), findsNothing); + + expect(lastExpanded,0); + expect(topElementExpanded,true); + + await tester.tap(find.byType(ExpandIcon).at(1)); + await tester.pump(const Duration(milliseconds: 200)); + await tester.pumpAndSettle(); + + // Open the other panel and ensure the first is now closed. + expect(lastExpanded,1); + expect(bottomElementExpanded,true); + expect(topElementExpanded,false); + expect(find.text('D'), findsOneWidget); + expect(find.text('A'), findsOneWidget); + + await tester.tap(find.byType(ExpandIcon).at(1)); + await tester.pump(const Duration(milliseconds: 200)); + await tester.pumpAndSettle(); + + // Close the item that was expanded should now be false. + expect(lastExpanded,1); + expect(bottomElementExpanded,false); + + // All panels should be closed. + expect(find.text('A'), findsOneWidget); + expect(find.text('B'), findsNothing); + expect(find.text('C'), findsOneWidget); + expect(find.text('D'), findsNothing); }); testWidgets( @@ -929,6 +1025,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: SingleChildScrollView( child: expansionList, ), diff --git a/packages/flutter/test/material/expansion_tile_test.dart b/packages/flutter/test/material/expansion_tile_test.dart index d7a741ca2232b..9d6fba1a42058 100644 --- a/packages/flutter/test/material/expansion_tile_test.dart +++ b/packages/flutter/test/material/expansion_tile_test.dart @@ -167,6 +167,7 @@ void main() { await tester.pumpWidget( MaterialApp( theme: ThemeData( + useMaterial3: false, colorScheme: ColorScheme.fromSwatch().copyWith(primary: foregroundColor), unselectedWidgetColor: unselectedWidgetColor, textTheme: const TextTheme(titleMedium: TextStyle(color: headerColor)), @@ -892,8 +893,9 @@ void main() { }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('ExpansionTile default iconColor, textColor', (WidgetTester tester) async { final ThemeData theme = ThemeData(useMaterial3: false); diff --git a/packages/flutter/test/material/filled_button_test.dart b/packages/flutter/test/material/filled_button_test.dart index 0c7b904dc70f6..5469933ba0358 100644 --- a/packages/flutter/test/material/filled_button_test.dart +++ b/packages/flutter/test/material/filled_button_test.dart @@ -14,7 +14,7 @@ import '../widgets/semantics_tester.dart'; void main() { testWidgets('FilledButton, FilledButton.icon defaults', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); - final ThemeData theme = ThemeData.from(colorScheme: colorScheme); + final ThemeData theme = ThemeData.from(useMaterial3: false, colorScheme: colorScheme); // Enabled FilledButton await tester.pumpWidget( @@ -884,18 +884,21 @@ void main() { testWidgets('Does FilledButton contribute semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: FilledButton( - style: const ButtonStyle( - // Specifying minimumSize to mimic the original minimumSize for - // RaisedButton so that the semantics tree's rect and transform - // match the original version of this test. - minimumSize: MaterialStatePropertyAll<Size>(Size(88, 36)), + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: FilledButton( + style: const ButtonStyle( + // Specifying minimumSize to mimic the original minimumSize for + // RaisedButton so that the semantics tree's rect and transform + // match the original version of this test. + minimumSize: MaterialStatePropertyAll<Size>(Size(88, 36)), + ), + onPressed: () { }, + child: const Text('ABC'), ), - onPressed: () { }, - child: const Text('ABC'), ), ), ), @@ -936,7 +939,7 @@ void main() { Widget buildFrame(MaterialTapTargetSize tapTargetSize, Key key) { return Theme( - data: ThemeData(materialTapTargetSize: tapTargetSize), + data: ThemeData(useMaterial3: false, materialTapTargetSize: tapTargetSize), child: Directionality( textDirection: TextDirection.ltr, child: Center( @@ -984,9 +987,7 @@ void main() { Future<void> buildTest(VisualDensity visualDensity, {bool useText = false}) async { return tester.pumpWidget( MaterialApp( - // Test was setup using fonts from Material 2, so make sure we always - // test against englishLike2014. - theme: ThemeData(textTheme: Typography.englishLike2014), + theme: ThemeData(useMaterial3: false), home: Directionality( textDirection: TextDirection.rtl, child: Center( @@ -1179,10 +1180,7 @@ void main() { await tester.pumpWidget( MaterialApp( theme: ThemeData( - colorScheme: const ColorScheme.light(), - // Force Material 2 defaults for the typography and size - // default values as the test was designed against these settings. - textTheme: Typography.englishLike2014, + useMaterial3: false, filledButtonTheme: FilledButtonThemeData( style: FilledButton.styleFrom(minimumSize: const Size(64, 36)), ), @@ -1410,7 +1408,7 @@ void main() { const Color borderColor = Color(0xff4caf50); await tester.pumpWidget( MaterialApp( - theme: ThemeData(colorScheme: const ColorScheme.light(), textTheme: Typography.englishLike2014), + theme: ThemeData(useMaterial3: false), home: Center( child: FilledButton( style: FilledButton.styleFrom( @@ -1599,7 +1597,7 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: ThemeData(textTheme: Typography.englishLike2014), + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( child: Column( diff --git a/packages/flutter/test/material/filter_chip_test.dart b/packages/flutter/test/material/filter_chip_test.dart index 7369a9e5018ae..c4aaa40a06f28 100644 --- a/packages/flutter/test/material/filter_chip_test.dart +++ b/packages/flutter/test/material/filter_chip_test.dart @@ -13,9 +13,10 @@ Widget wrapForChip({ TextDirection textDirection = TextDirection.ltr, double textScaleFactor = 1.0, Brightness brightness = Brightness.light, + bool? useMaterial3, }) { return MaterialApp( - theme: ThemeData(brightness: brightness), + theme: ThemeData(brightness: brightness, useMaterial3: useMaterial3), home: Directionality( textDirection: textDirection, child: MediaQuery( @@ -31,9 +32,11 @@ Future<void> pumpCheckmarkChip( required Widget chip, Color? themeColor, Brightness brightness = Brightness.light, + ThemeData? theme, }) async { await tester.pumpWidget( wrapForChip( + useMaterial3: false, brightness: brightness, child: Builder( builder: (BuildContext context) { @@ -75,6 +78,15 @@ void expectCheckmarkColor(Finder finder, Color color) { ); } +RenderBox getMaterialBox(WidgetTester tester, Finder type) { + return tester.firstRenderObject<RenderBox>( + find.descendant( + of: type, + matching: find.byType(CustomPaint), + ), + ); +} + void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { final Iterable<Material> materials = tester.widgetList<Material>(find.byType(Material)); // There should be two Material widgets, first Material is from the "_wrapForChip" and @@ -84,7 +96,464 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { expect(materials.last.clipBehavior, clipBehavior); } +Material getMaterial(WidgetTester tester) { + return tester.widget<Material>( + find.descendant( + of: find.byType(FilterChip), + matching: find.byType(Material), + ), + ); +} + +DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) { + return tester.widget( + find.ancestor( + of: find.text(labelText), + matching: find.byType(DefaultTextStyle), + ).first, + ); +} + void main() { + testWidgets('FilterChip defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + const String label = 'filter chip'; + + // Test enabled FilterChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: Center( + child: FilterChip( + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ), + ); + + // Test default chip size. + expect(tester.getSize(find.byType(FilterChip)), const Size(190.0, 48.0)); + // Test default label style. + expect( + getLabelStyle(tester, label).style.color!.value, + theme.textTheme.labelLarge!.color!.value, + ); + + Material chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, Colors.transparent); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: theme.colorScheme.outline), + ), + ); + + ShapeDecoration decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test disabled FilterChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: FilterChip( + onSelected: null, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, Colors.transparent); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: theme.colorScheme.onSurface.withOpacity(0.12)), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test selected enabled FilterChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: FilterChip( + selected: true, + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.secondaryContainer); + + // Test selected disabled FilterChip defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: FilterChip( + selected: true, + onSelected: null, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); + }); + + testWidgets('FilterChip.elevated defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + const String label = 'filter chip'; + + // Test enabled FilterChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: Center( + child: FilterChip.elevated( + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ), + ); + + // Test default chip size. + expect(tester.getSize(find.byType(FilterChip)), const Size(190.0, 48.0)); + // Test default label style. + expect( + getLabelStyle(tester, 'filter chip').style.color!.value, + theme.textTheme.labelLarge!.color!.value, + ); + + Material chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 1); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + ShapeDecoration decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, null); + + // Test disabled FilterChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: FilterChip.elevated( + onSelected: null, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, theme.colorScheme.shadow); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); + + // Test selected enabled FilterChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Material( + child: FilterChip.elevated( + selected: true, + onSelected: (bool valueChanged) { }, + label: const Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 1); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.secondaryContainer); + + // Test selected disabled FilterChip.elevated defaults. + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: const Material( + child: FilterChip.elevated( + selected: true, + onSelected: null, + label: Text(label), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + chipMaterial = getMaterial(tester); + expect(chipMaterial.elevation, 0); + expect(chipMaterial.shadowColor, null); + expect(chipMaterial.surfaceTintColor, theme.colorScheme.surfaceTint); + expect( + chipMaterial.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: Colors.transparent), + ), + ); + + decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; + expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); + }); + + testWidgets('FilterChip.color resolves material states', (WidgetTester tester) async { + const Color disabledSelectedColor = Color(0xffffff00); + const Color disabledColor = Color(0xff00ff00); + const Color backgroundColor = Color(0xff0000ff); + const Color selectedColor = Color(0xffff0000); + final MaterialStateProperty<Color?> color = MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.disabled) && states.contains(MaterialState.selected)) { + return disabledSelectedColor; + } + if (states.contains(MaterialState.disabled)) { + return disabledColor; + } + if (states.contains(MaterialState.selected)) { + return selectedColor; + } + return backgroundColor; + }); + Widget buildApp({ required bool enabled, required bool selected }) { + return wrapForChip( + useMaterial3: true, + child: Column( + children: <Widget>[ + FilterChip( + onSelected: enabled ? (bool value) { } : null, + selected: selected, + color: color, + label: const Text('FilterChip'), + ), + FilterChip.elevated( + onSelected: enabled ? (bool value) { } : null, + selected: selected, + color: color, + label: const Text('FilterChip.elevated'), + ), + ], + ), + ); + } + + // Test enabled state. + await tester.pumpWidget(buildApp(enabled: true, selected: false)); + + // Enabled FilterChip should have the provided backgroundColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: backgroundColor), + ); + // Enabled elevated FilterChip should have the provided backgroundColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: backgroundColor), + ); + + // Test disabled state. + await tester.pumpWidget(buildApp(enabled: false, selected: false)); + await tester.pumpAndSettle(); + + // Disabled FilterChip should have the provided disabledColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: disabledColor), + ); + // Disabled elevated FilterChip should have the provided disabledColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: disabledColor), + ); + + // Test enabled & selected state. + await tester.pumpWidget(buildApp(enabled: true, selected: true)); + await tester.pumpAndSettle(); + + // Enabled & selected FilterChip should have the provided selectedColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: selectedColor), + ); + // Enabled & selected elevated FilterChip should have the provided selectedColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: selectedColor), + ); + + // Test disabled & selected state. + await tester.pumpWidget(buildApp(enabled: false, selected: true)); + await tester.pumpAndSettle(); + + // Disabled & selected FilterChip should have the provided disabledSelectedColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: disabledSelectedColor), + ); + // Disabled & selected elevated FilterChip should have the + // provided disabledSelectedColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: disabledSelectedColor), + ); + }); + + testWidgets('FilterChip uses provided state color properties', (WidgetTester tester) async { + const Color disabledColor = Color(0xff00ff00); + const Color backgroundColor = Color(0xff0000ff); + const Color selectedColor = Color(0xffff0000); + Widget buildApp({ required bool enabled, required bool selected }) { + return wrapForChip( + useMaterial3: true, + child: Column( + children: <Widget>[ + FilterChip( + onSelected: enabled ? (bool value) { } : null, + selected: selected, + disabledColor: disabledColor, + backgroundColor: backgroundColor, + selectedColor: selectedColor, + label: const Text('FilterChip'), + ), + FilterChip.elevated( + onSelected: enabled ? (bool value) { } : null, + selected: selected, + disabledColor: disabledColor, + backgroundColor: backgroundColor, + selectedColor: selectedColor, + label: const Text('FilterChip.elevated'), + ), + ], + ), + ); + } + + // Test enabled state. + await tester.pumpWidget(buildApp(enabled: true, selected: false)); + + // Enabled FilterChip should have the provided backgroundColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: backgroundColor), + ); + // Enabled elevated FilterChip should have the provided backgroundColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: backgroundColor), + ); + + // Test disabled state. + await tester.pumpWidget(buildApp(enabled: false, selected: false)); + await tester.pumpAndSettle(); + + // Disabled FilterChip should have the provided disabledColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: disabledColor), + ); + // Disabled elevated FilterChip should have the provided disabledColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: disabledColor), + ); + + // Test enabled & selected state. + await tester.pumpWidget(buildApp(enabled: true, selected: true)); + await tester.pumpAndSettle(); + + // Enabled & selected FilterChip should have the provided selectedColor. + expect( + getMaterialBox(tester, find.byType(RawChip).first), + paints..rrect(color: selectedColor), + ); + // Enabled & selected elevated FilterChip should have the provided selectedColor. + expect( + getMaterialBox(tester, find.byType(RawChip).last), + paints..rrect(color: selectedColor), + ); + }); + testWidgets('FilterChip can be tapped', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( @@ -103,6 +572,7 @@ void main() { testWidgets('Filter chip check mark color is determined by platform brightness when light', (WidgetTester tester) async { await pumpCheckmarkChip( + theme: ThemeData(useMaterial3: false), tester, chip: selectedFilterChip(), ); @@ -118,6 +588,7 @@ void main() { tester, chip: selectedFilterChip(), brightness: Brightness.dark, + theme: ThemeData(useMaterial3: false), ); expectCheckmarkColor( diff --git a/packages/flutter/test/material/flexible_space_bar_stretch_mode_test.dart b/packages/flutter/test/material/flexible_space_bar_stretch_mode_test.dart index d43165dd4e5fc..86efe7056f7bc 100644 --- a/packages/flutter/test/material/flexible_space_bar_stretch_mode_test.dart +++ b/packages/flutter/test/material/flexible_space_bar_stretch_mode_test.dart @@ -53,6 +53,7 @@ void main() { testWidgets('FlexibleSpaceBar stretch mode blurBackground', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: CustomScrollView( physics: const BouncingScrollPhysics(), diff --git a/packages/flutter/test/material/flexible_space_bar_test.dart b/packages/flutter/test/material/flexible_space_bar_test.dart index f869f23a6411e..7477480f9f969 100644 --- a/packages/flutter/test/material/flexible_space_bar_test.dart +++ b/packages/flutter/test/material/flexible_space_bar_test.dart @@ -8,6 +8,7 @@ library; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; @@ -80,6 +81,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: CustomScrollView( key: dragTarget, @@ -159,7 +161,10 @@ void main() { ), ); - final Opacity backgroundOpacity = tester.firstWidget(find.byType(Opacity)); + final dynamic backgroundOpacity = tester.firstWidget( + find.byWidgetPredicate((Widget widget) => widget.runtimeType.toString() == '_FlexibleSpaceHeaderOpacity')); + // accessing private type member. + // ignore: avoid_dynamic_calls expect(backgroundOpacity.opacity, 1.0); }); @@ -168,6 +173,7 @@ void main() { const double expandedHeight = 200; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: CustomScrollView( slivers: <Widget>[ @@ -409,8 +415,6 @@ void main() { label: 'Item 6', textDirection: TextDirection.ltr, ), - - ], ), ], @@ -436,6 +440,7 @@ void main() { late double width; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Builder( builder: (BuildContext context) { @@ -465,18 +470,16 @@ void main() { ), ); + final double textWidth = const bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') + ? width + : (width / 1.5).floorToDouble() * 1.5; // The title is scaled and transformed to be 1.5 times bigger, when the // FlexibleSpaceBar is fully expanded, thus we expect the width to be // 1.5 times smaller than the full width. The height of the text is the same // as the font size, with 10 dps bottom margin. expect( tester.getRect(find.byType(Text)), - Rect.fromLTRB( - 0, - height - titleFontSize - 10, - (width / 1.5).floorToDouble() * 1.5, - height, - ), + rectMoreOrLessEquals(Rect.fromLTRB(0, height - titleFontSize - 10, textWidth, height), epsilon: 0.0001), ); }); @@ -486,6 +489,7 @@ void main() { const double expandedTitleScale = 3.0; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: CustomScrollView( slivers: <Widget>[ @@ -537,18 +541,16 @@ void main() { // bottom edge. const double bottomMargin = titleFontSize * (expandedTitleScale - 1); + final double textWidth = const bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') + ? collapsedWidth + : (collapsedWidth / 3).floorToDouble() * 3; // The title is scaled and transformed to be 3 times bigger, when the // FlexibleSpaceBar is fully expanded, thus we expect the width to be // 3 times smaller than the full width. The height of the text is the same // as the font size, with 40 dps bottom margin to maintain its bottom position. expect( tester.getRect(title), - Rect.fromLTRB( - 0, - height - titleFontSize - bottomMargin, - (collapsedWidth / 3).floorToDouble() * 3, - height, - ), + rectMoreOrLessEquals(Rect.fromLTRB(0, height - titleFontSize - bottomMargin, textWidth, height), epsilon: 0.0001), ); }); @@ -557,6 +559,7 @@ void main() { const double height = 300.0; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: CustomScrollView( slivers: <Widget>[ @@ -617,6 +620,7 @@ void main() { const double expandedTitleScale = 3.0; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: CustomScrollView( slivers: <Widget>[ @@ -676,7 +680,7 @@ void main() { testWidgets('FlexibleSpaceBar test titlePadding defaults', (WidgetTester tester) async { Widget buildFrame(TargetPlatform platform, bool? centerTitle) { return MaterialApp( - theme: ThemeData(platform: platform), + theme: ThemeData(platform: platform, useMaterial3: false), home: Scaffold( appBar: AppBar( flexibleSpace: FlexibleSpaceBar( @@ -726,7 +730,7 @@ void main() { testWidgets('FlexibleSpaceBar test titlePadding override', (WidgetTester tester) async { Widget buildFrame(TargetPlatform platform, bool? centerTitle) { return MaterialApp( - theme: ThemeData(platform: platform), + theme: ThemeData(platform: platform, useMaterial3: false), home: Scaffold( appBar: AppBar( flexibleSpace: FlexibleSpaceBar( @@ -790,6 +794,37 @@ void main() { await tester.pumpWidget(buildFrame(TargetPlatform.linux, true)); expect(getTitleBottomLeft(), const Offset(390.0, 0.0)); }); + + testWidgets('FlexibleSpaceBar rebuilds when scrolling.', (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp( + home: SubCategoryScreenView(), + )); + + expect(RenderRebuildTracker.count, 1); + expect( + tester.layers.lastWhere((Layer element) => element is OpacityLayer), + isA<OpacityLayer>().having((OpacityLayer p0) => p0.alpha, 'alpha', 255), + ); + + // We drag up to fully collapse the space bar. + for (int i = 0; i < 9; i++) { + await tester.drag(find.byKey(SubCategoryScreenView.scrollKey), const Offset(0, -50.0)); + await tester.pumpAndSettle(); + } + + expect( + tester.layers.lastWhere((Layer element) => element is OpacityLayer), + isA<OpacityLayer>().having((OpacityLayer p0) => p0.alpha, 'alpha', lessThan(255)), + ); + + for (int i = 0; i < 11; i++) { + await tester.drag(find.byKey(SubCategoryScreenView.scrollKey), const Offset(0, -50.0)); + await tester.pumpAndSettle(); + } + + expect(RenderRebuildTracker.count, greaterThan(1)); + expect(tester.layers.whereType<OpacityLayer>(), isEmpty); + }); } class TestDelegate extends SliverPersistentHeaderDelegate { @@ -814,3 +849,83 @@ class TestDelegate extends SliverPersistentHeaderDelegate { @override bool shouldRebuild(TestDelegate oldDelegate) => false; } + +class RebuildTracker extends SingleChildRenderObjectWidget { + const RebuildTracker({super.key}); + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderRebuildTracker(); + } +} + +class RenderRebuildTracker extends RenderProxyBox { + static int count = 0; + + @override + void paint(PaintingContext context, Offset offset) { + count++; + super.paint(context, offset); + } +} + +class SubCategoryScreenView extends StatefulWidget { + const SubCategoryScreenView({ + super.key, + }); + + static const Key scrollKey = Key('orange box'); + + @override + State<SubCategoryScreenView> createState() => _SubCategoryScreenViewState(); +} + +class _SubCategoryScreenViewState extends State<SubCategoryScreenView> + with TickerProviderStateMixin { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: const Text('Test'), + ), + body: CustomScrollView( + key: SubCategoryScreenView.scrollKey, + slivers: <Widget>[ + SliverAppBar( + leading: const SizedBox(), + expandedHeight: MediaQuery.of(context).size.width / 1.7, + collapsedHeight: 0, + toolbarHeight: 0, + titleSpacing: 0, + leadingWidth: 0, + flexibleSpace: const FlexibleSpaceBar( + background: AspectRatio( + aspectRatio: 1.7, + child: RebuildTracker(), + ), + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: 12)), + SliverToBoxAdapter( + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + ), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: 300, + itemBuilder: (BuildContext context, int index) { + return Card( + color: Colors.amber, + child: Center(child: Text('$index')), + ); + }, + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: 12)), + ], + ), + ); + } +} diff --git a/packages/flutter/test/material/floating_action_button_location_test.dart b/packages/flutter/test/material/floating_action_button_location_test.dart index d24366dd35467..faefd63c90cfa 100644 --- a/packages/flutter/test/material/floating_action_button_location_test.dart +++ b/packages/flutter/test/material/floating_action_button_location_test.dart @@ -693,6 +693,7 @@ void main() { bool resizeToAvoidBottomInset = true, }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: MediaQuery( data: data, child: Scaffold( @@ -1640,15 +1641,14 @@ const double _dockedOffsetY = 544.0; const double _containedOffsetY = 544.0 + 56.0 / 2; const double _miniFloatOffsetY = _floatOffsetY + kMiniButtonOffsetAdjustment; -Widget _singleFabScaffold( - FloatingActionButtonLocation location, - { - FloatingActionButtonAnimator? animator, - bool mini = false, - TextDirection textDirection = TextDirection.ltr, - } -) { +Widget _singleFabScaffold(FloatingActionButtonLocation location, { + bool useMaterial3 = false, + FloatingActionButtonAnimator? animator, + bool mini = false, + TextDirection textDirection = TextDirection.ltr, +}) { return MaterialApp( + theme: ThemeData(useMaterial3: useMaterial3), home: Directionality( textDirection: textDirection, child: Scaffold( diff --git a/packages/flutter/test/material/floating_action_button_test.dart b/packages/flutter/test/material/floating_action_button_test.dart index 3a1827045a045..2511fade5f177 100644 --- a/packages/flutter/test/material/floating_action_button_test.dart +++ b/packages/flutter/test/material/floating_action_button_test.dart @@ -19,9 +19,8 @@ import '../widgets/semantics_tester.dart'; import 'feedback_tester.dart'; void main() { - - final ThemeData material3Theme = ThemeData.light().copyWith(useMaterial3: true); - final ThemeData material2Theme = ThemeData.light().copyWith(useMaterial3: false); + final ThemeData material3Theme = ThemeData(useMaterial3: true); + final ThemeData material2Theme = ThemeData(useMaterial3: false); testWidgets('Floating Action Button control test', (WidgetTester tester) async { bool didPressButton = false; @@ -950,6 +949,7 @@ void main() { const Color splashColor = Color(0xcafefeed); await tester.pumpWidget(MaterialApp( + theme: material2Theme, home: FloatingActionButton( onPressed: () {}, splashColor: splashColor, @@ -1051,6 +1051,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: material2Theme, home: Scaffold( floatingActionButton: FloatingActionButton.extended( label: const Text('', key: labelKey), @@ -1073,8 +1074,9 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('Floating Action Button elevation when highlighted - effect', (WidgetTester tester) async { await tester.pumpWidget( diff --git a/packages/flutter/test/material/floating_action_button_theme_test.dart b/packages/flutter/test/material/floating_action_button_theme_test.dart index e88e67e6ee0df..f3d54f69957ea 100644 --- a/packages/flutter/test/material/floating_action_button_theme_test.dart +++ b/packages/flutter/test/material/floating_action_button_theme_test.dart @@ -19,8 +19,10 @@ void main() { expect(identical(FloatingActionButtonThemeData.lerp(data, data, 0.5), data), true); }); - testWidgets('Default values are used when no FloatingActionButton or FloatingActionButtonThemeData properties are specified', (WidgetTester tester) async { + testWidgets('Material3: Default values are used when no FloatingActionButton or FloatingActionButtonThemeData properties are specified', (WidgetTester tester) async { + const ColorScheme colorScheme = ColorScheme.light(); await tester.pumpWidget(MaterialApp( + theme: ThemeData.from(useMaterial3: true, colorScheme: colorScheme), home: Scaffold( floatingActionButton: FloatingActionButton( onPressed: () { }, @@ -29,10 +31,33 @@ void main() { ), )); - // The color scheme values are guaranteed to be non null since the default - // [ThemeData] creates it with [ColorScheme.fromSwatch]. - expect(_getRawMaterialButton(tester).fillColor, ThemeData().colorScheme.secondary); - expect(_getRichText(tester).text.style!.color, ThemeData().colorScheme.onSecondary); + expect(_getRawMaterialButton(tester).fillColor, colorScheme.primaryContainer); + expect(_getRichText(tester).text.style!.color, colorScheme.onPrimaryContainer); + + // These defaults come directly from the [FloatingActionButton]. + expect(_getRawMaterialButton(tester).elevation, 6); + expect(_getRawMaterialButton(tester).highlightElevation, 6); + expect(_getRawMaterialButton(tester).shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0)))); + expect(_getRawMaterialButton(tester).splashColor, colorScheme.onPrimaryContainer.withOpacity(0.12)); + expect(_getRawMaterialButton(tester).constraints, const BoxConstraints.tightFor(width: 56.0, height: 56.0)); + expect(_getIconSize(tester).width, 24.0); + expect(_getIconSize(tester).height, 24.0); + }); + + testWidgets('Material2: Default values are used when no FloatingActionButton or FloatingActionButtonThemeData properties are specified', (WidgetTester tester) async { + const ColorScheme colorScheme = ColorScheme.light(); + await tester.pumpWidget(MaterialApp( + theme: ThemeData.from(useMaterial3: false, colorScheme: colorScheme), + home: Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () { }, + child: const Icon(Icons.add), + ), + ), + )); + + expect(_getRawMaterialButton(tester).fillColor, colorScheme.secondary); + expect(_getRichText(tester).text.style!.color, colorScheme.onSecondary); // These defaults come directly from the [FloatingActionButton]. expect(_getRawMaterialButton(tester).elevation, 6); @@ -191,7 +216,8 @@ void main() { expect(_getIconSize(tester).height, iconSize); }); - testWidgets('FloatingActionButton.extended uses custom properties when specified in the theme', (WidgetTester tester) async { + testWidgets('Material3: FloatingActionButton.extended uses custom properties when specified in the theme', (WidgetTester tester) async { + const ColorScheme colorScheme = ColorScheme.light(); const Key iconKey = Key('icon'); const Key labelKey = Key('label'); const BoxConstraints constraints = BoxConstraints.tightFor(height: 100.0); @@ -200,7 +226,43 @@ void main() { const TextStyle textStyle = TextStyle(letterSpacing: 2.0); await tester.pumpWidget(MaterialApp( - theme: ThemeData().copyWith( + theme: ThemeData( + useMaterial3: true, + colorScheme: colorScheme, + ).copyWith( + floatingActionButtonTheme: const FloatingActionButtonThemeData( + extendedSizeConstraints: constraints, + extendedIconLabelSpacing: iconLabelSpacing, + extendedPadding: padding, + extendedTextStyle: textStyle, + ), + ), + home: Scaffold( + floatingActionButton: FloatingActionButton.extended( + onPressed: () { }, + label: const Text('Extended', key: labelKey), + icon: const Icon(Icons.add, key: iconKey), + ), + ), + )); + + expect(_getRawMaterialButton(tester).constraints, constraints); + expect(tester.getTopLeft(find.byKey(labelKey)).dx - tester.getTopRight(find.byKey(iconKey)).dx, iconLabelSpacing); + expect(tester.getTopLeft(find.byKey(iconKey)).dx - tester.getTopLeft(find.byType(FloatingActionButton)).dx, padding.start); + expect(tester.getTopRight(find.byType(FloatingActionButton)).dx - tester.getTopRight(find.byKey(labelKey)).dx, padding.end); + expect(_getRawMaterialButton(tester).textStyle, textStyle.copyWith(color: colorScheme.onPrimaryContainer)); + }); + + testWidgets('Material2: FloatingActionButton.extended uses custom properties when specified in the theme', (WidgetTester tester) async { + const Key iconKey = Key('icon'); + const Key labelKey = Key('label'); + const BoxConstraints constraints = BoxConstraints.tightFor(height: 100.0); + const double iconLabelSpacing = 33.0; + const EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(start: 5.0, end: 6.0); + const TextStyle textStyle = TextStyle(letterSpacing: 2.0); + + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false).copyWith( floatingActionButtonTheme: const FloatingActionButtonThemeData( extendedSizeConstraints: constraints, extendedIconLabelSpacing: iconLabelSpacing, @@ -225,7 +287,8 @@ void main() { expect(_getRawMaterialButton(tester).textStyle, textStyle.copyWith(color: const Color(0xffffffff))); }); - testWidgets('FloatingActionButton.extended custom properties takes priority over FloatingActionButtonThemeData spacing', (WidgetTester tester) async { + testWidgets('Material3: FloatingActionButton.extended custom properties takes priority over FloatingActionButtonThemeData spacing', (WidgetTester tester) async { + const ColorScheme colorScheme = ColorScheme.light(); const Key iconKey = Key('icon'); const Key labelKey = Key('label'); const double iconLabelSpacing = 33.0; @@ -233,7 +296,43 @@ void main() { const TextStyle textStyle = TextStyle(letterSpacing: 2.0); await tester.pumpWidget(MaterialApp( - theme: ThemeData().copyWith( + theme: ThemeData( + useMaterial3: true, + colorScheme: colorScheme, + ).copyWith( + floatingActionButtonTheme: const FloatingActionButtonThemeData( + extendedIconLabelSpacing: 25.0, + extendedPadding: EdgeInsetsDirectional.only(start: 7.0, end: 8.0), + extendedTextStyle: TextStyle(letterSpacing: 3.0), + ), + ), + home: Scaffold( + floatingActionButton: FloatingActionButton.extended( + onPressed: () { }, + label: const Text('Extended', key: labelKey), + icon: const Icon(Icons.add, key: iconKey), + extendedIconLabelSpacing: iconLabelSpacing, + extendedPadding: padding, + extendedTextStyle: textStyle, + ), + ), + )); + + expect(tester.getTopLeft(find.byKey(labelKey)).dx - tester.getTopRight(find.byKey(iconKey)).dx, iconLabelSpacing); + expect(tester.getTopLeft(find.byKey(iconKey)).dx - tester.getTopLeft(find.byType(FloatingActionButton)).dx, padding.start); + expect(tester.getTopRight(find.byType(FloatingActionButton)).dx - tester.getTopRight(find.byKey(labelKey)).dx, padding.end); + expect(_getRawMaterialButton(tester).textStyle, textStyle.copyWith(color: colorScheme.onPrimaryContainer)); + }); + + testWidgets('Material2: FloatingActionButton.extended custom properties takes priority over FloatingActionButtonThemeData spacing', (WidgetTester tester) async { + const Key iconKey = Key('icon'); + const Key labelKey = Key('label'); + const double iconLabelSpacing = 33.0; + const EdgeInsetsDirectional padding = EdgeInsetsDirectional.only(start: 5.0, end: 6.0); + const TextStyle textStyle = TextStyle(letterSpacing: 2.0); + + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false).copyWith( floatingActionButtonTheme: const FloatingActionButtonThemeData( extendedIconLabelSpacing: 25.0, extendedPadding: EdgeInsetsDirectional.only(start: 7.0, end: 8.0), diff --git a/packages/flutter/test/material/icon_button_test.dart b/packages/flutter/test/material/icon_button_test.dart index 3ff46df1e7207..949a467b1be26 100644 --- a/packages/flutter/test/material/icon_button_test.dart +++ b/packages/flutter/test/material/icon_button_test.dart @@ -653,6 +653,62 @@ void main() { semantics.dispose(); }); + testWidgets('IconButton Semantics (selected) - M3', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + + await tester.pumpWidget( + wrap( + useMaterial3: true, + child: IconButton( + onPressed: mockOnPressedFunction.handler, + isSelected: true, + icon: const Icon(Icons.link, semanticLabel: 'link'), + ), + ), + ); + + expect( + semantics, + hasSemantics( + TestSemantics.root( + children: <TestSemantics>[ + TestSemantics( + textDirection: TextDirection.ltr, + children: <TestSemantics>[ + TestSemantics( + children: <TestSemantics>[ + TestSemantics( + flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], + children: <TestSemantics>[ + TestSemantics( + actions: <SemanticsAction>[ + SemanticsAction.tap, + ], + flags: <SemanticsFlag>[ + SemanticsFlag.hasEnabledState, + SemanticsFlag.isButton, + SemanticsFlag.isEnabled, + SemanticsFlag.isFocusable, + SemanticsFlag.isSelected, + ], + label: 'link', + ), + ], + ), + ], + ), + ], + ), + ], + ), + ignoreId: true, + ignoreRect: true, + ignoreTransform: true, + ), + ); + semantics.dispose(); + }); + testWidgets('IconButton loses focus when disabled.', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'IconButton'); await tester.pumpWidget( @@ -733,7 +789,7 @@ void main() { await tester.pumpWidget( wrap( - useMaterial3: theme.useMaterial3, + useMaterial3: false, child: Column( children: <Widget>[ IconButton( diff --git a/packages/flutter/test/material/inherited_theme_test.dart b/packages/flutter/test/material/inherited_theme_test.dart index ee8f3635e86b6..9cffa4706e76d 100644 --- a/packages/flutter/test/material/inherited_theme_test.dart +++ b/packages/flutter/test/material/inherited_theme_test.dart @@ -99,6 +99,7 @@ void main() { Widget buildFrame() { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: PopupMenuTheme( data: const PopupMenuThemeData( diff --git a/packages/flutter/test/material/ink_paint_test.dart b/packages/flutter/test/material/ink_paint_test.dart index f6cba22839f6e..e62cc7b7107c7 100644 --- a/packages/flutter/test/material/ink_paint_test.dart +++ b/packages/flutter/test/material/ink_paint_test.dart @@ -59,9 +59,9 @@ void main() { const BorderRadius borderRadius = BorderRadius.all(Radius.circular(6.0)); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Material( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( child: Center( child: SizedBox( width: 200.0, @@ -190,9 +190,9 @@ void main() { testWidgets('Does the Ink widget render anything', (WidgetTester tester) async { await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Material( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( child: Center( child: Ink( color: Colors.blue, @@ -222,9 +222,9 @@ void main() { ); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Material( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( child: Center( child: Ink( color: Colors.red, @@ -250,9 +250,9 @@ void main() { ); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Material( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( child: Center( child: InkWell( // this is at a different depth in the tree so it's now a new InkWell splashColor: Colors.green, @@ -518,9 +518,9 @@ void main() { const Color splashColor = Color(0xff00ff00); Widget buildWidget({InteractiveInkFeatureFactory? splashFactory}) { - return Directionality( - textDirection: TextDirection.ltr, - child: Material( + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( child: Center( child: SizedBox( width: 100.0, diff --git a/packages/flutter/test/material/ink_sparkle_test.dart b/packages/flutter/test/material/ink_sparkle_test.dart index 6a76bb9a53617..6fea3242e3d3d 100644 --- a/packages/flutter/test/material/ink_sparkle_test.dart +++ b/packages/flutter/test/material/ink_sparkle_test.dart @@ -116,6 +116,7 @@ Future<void> _runTest(WidgetTester tester, String positionName, double distanceF final Key buttonKey = UniqueKey(); await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( child: RepaintBoundary( diff --git a/packages/flutter/test/material/ink_splash_test.dart b/packages/flutter/test/material/ink_splash_test.dart index c0886c5d51c54..f178004ebceea 100644 --- a/packages/flutter/test/material/ink_splash_test.dart +++ b/packages/flutter/test/material/ink_splash_test.dart @@ -30,6 +30,7 @@ void main() { testWidgets('InkWell with NoSplash splashFactory paints nothing', (WidgetTester tester) async { Widget buildFrame({ InteractiveInkFeatureFactory? splashFactory }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( child: Material( diff --git a/packages/flutter/test/material/ink_well_test.dart b/packages/flutter/test/material/ink_well_test.dart index 26110554c7c87..60d64ed420d8e 100644 --- a/packages/flutter/test/material/ink_well_test.dart +++ b/packages/flutter/test/material/ink_well_test.dart @@ -338,23 +338,26 @@ void main() { FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch; final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus'); const Color splashColor = Color(0xffff0000); - await tester.pumpWidget(Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: Focus( - focusNode: focusNode, - child: SizedBox( - width: 100, - height: 100, - child: InkWell( - hoverColor: const Color(0xff00ff00), - splashColor: splashColor, - focusColor: const Color(0xff0000ff), - highlightColor: const Color(0xf00fffff), - onTap: () { }, - onLongPress: () { }, - onHover: (bool hover) { }, + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: Focus( + focusNode: focusNode, + child: SizedBox( + width: 100, + height: 100, + child: InkWell( + hoverColor: const Color(0xff00ff00), + splashColor: splashColor, + focusColor: const Color(0xff0000ff), + highlightColor: const Color(0xf00fffff), + onTap: () { }, + onLongPress: () { }, + onHover: (bool hover) { }, + ), ), ), ), @@ -376,31 +379,34 @@ void main() { FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch; final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus'); const Color splashColor = Color(0xffff0000); - await tester.pumpWidget(Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: Focus( - focusNode: focusNode, - child: SizedBox( - width: 100, - height: 100, - child: InkWell( - overlayColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) { - if (states.contains(MaterialState.hovered)) { - return const Color(0xff00ff00); - } - if (states.contains(MaterialState.focused)) { - return const Color(0xff0000ff); - } - if (states.contains(MaterialState.pressed)) { - return splashColor; - } - return const Color(0xffbadbad); // Shouldn't happen. - }), - onTap: () { }, - onLongPress: () { }, - onHover: (bool hover) { }, + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: Focus( + focusNode: focusNode, + child: SizedBox( + width: 100, + height: 100, + child: InkWell( + overlayColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) { + if (states.contains(MaterialState.hovered)) { + return const Color(0xff00ff00); + } + if (states.contains(MaterialState.focused)) { + return const Color(0xff0000ff); + } + if (states.contains(MaterialState.pressed)) { + return splashColor; + } + return const Color(0xffbadbad); // Shouldn't happen. + }), + onTap: () { }, + onLongPress: () { }, + onHover: (bool hover) { }, + ), ), ), ), @@ -820,19 +826,22 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/121626. final FocusNode focusNode = FocusNode(debugLabel: 'Ink Focus'); Widget boilerplate(BorderRadius borderRadius) { - return Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Align( - alignment: Alignment.topLeft, - child: SizedBox( - width: 100, - height: 100, - child: MouseRegion( - child: InkWell( - focusNode: focusNode, - customBorder: RoundedRectangleBorder(borderRadius: borderRadius), - onTap: () { }, + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 100, + height: 100, + child: MouseRegion( + child: InkWell( + focusNode: focusNode, + customBorder: RoundedRectangleBorder(borderRadius: borderRadius), + onTap: () { }, + ), ), ), ), @@ -1108,9 +1117,9 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async { testWidgets('splashing survives scrolling when keep-alive is enabled', (WidgetTester tester) async { Future<void> runTest(bool keepAlive) async { await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Material( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( child: CompositedTransformFollower( // forces a layer, which makes the paints easier to separate out link: LayerLink(), @@ -1325,16 +1334,19 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async { } await tester.pumpWidget( - Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: paddedInkWell( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( child: paddedInkWell( - key: middleKey, child: paddedInkWell( - key: innerKey, - child: const SizedBox(width: 50, height: 50), + key: middleKey, + child: paddedInkWell( + key: innerKey, + child: const SizedBox(width: 50, height: 50), + ), ), ), ), @@ -1391,24 +1403,27 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async { } await tester.pumpWidget( - Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Align( - alignment: Alignment.topLeft, - child: SizedBox( - width: 200, - height: 100, - child: Row( - children: <Widget>[ - paddedInkWell( - key: middleKey, - child: paddedInkWell( - key: innerKey, + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 200, + height: 100, + child: Row( + children: <Widget>[ + paddedInkWell( + key: middleKey, + child: paddedInkWell( + key: innerKey, + ), ), - ), - const SizedBox(), - ], + const SizedBox(), + ], + ), ), ), ), @@ -1424,23 +1439,26 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async { // Reparent parent await tester.pumpWidget( - Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Align( - alignment: Alignment.topLeft, - child: SizedBox( - width: 200, - height: 100, - child: Row( - children: <Widget>[ - paddedInkWell( - key: innerKey, - ), - paddedInkWell( - key: middleKey, - ), - ], + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 200, + height: 100, + child: Row( + children: <Widget>[ + paddedInkWell( + key: innerKey, + ), + paddedInkWell( + key: middleKey, + ), + ], + ), ), ), ), @@ -1479,16 +1497,19 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async { } await tester.pumpWidget( - Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: paddedInkWell( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( child: paddedInkWell( - key: middleKey, child: paddedInkWell( - key: innerKey, - child: const SizedBox(width: 50, height: 50), + key: middleKey, + child: paddedInkWell( + key: innerKey, + child: const SizedBox(width: 50, height: 50), + ), ), ), ), @@ -1514,39 +1535,42 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async { final GlobalKey leftKey = GlobalKey(); final GlobalKey rightKey = GlobalKey(); await tester.pumpWidget( - Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: SizedBox( - width: 100, - height: 100, - child: InkWell( - key: parentKey, - onTap: () {}, - child: Center( - child: SizedBox( - width: 100, - height: 50, - child: Row( - children: <Widget>[ - SizedBox( - width: 50, - height: 50, - child: InkWell( - key: leftKey, - onTap: () {}, + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: SizedBox( + width: 100, + height: 100, + child: InkWell( + key: parentKey, + onTap: () {}, + child: Center( + child: SizedBox( + width: 100, + height: 50, + child: Row( + children: <Widget>[ + SizedBox( + width: 50, + height: 50, + child: InkWell( + key: leftKey, + onTap: () {}, + ), ), - ), - SizedBox( - width: 50, - height: 50, - child: InkWell( - key: rightKey, - onTap: () {}, + SizedBox( + width: 50, + height: 50, + child: InkWell( + key: rightKey, + onTap: () {}, + ), ), - ), - ], + ], + ), ), ), ), @@ -1610,47 +1634,50 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async { Widget? leftChild, Widget? rightChild, }) { - return Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Align( - alignment: Alignment.topLeft, - child: SizedBox( - width: leftWidth+rightWidth, - height: 100, - child: Row( - children: <Widget>[ - SizedBox( - width: leftWidth, - height: 100, - child: InkWell( - key: leftKey, - onTap: () {}, - child: Center( - child: SizedBox( - width: leftWidth, - height: 50, - child: leftChild, + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: leftWidth+rightWidth, + height: 100, + child: Row( + children: <Widget>[ + SizedBox( + width: leftWidth, + height: 100, + child: InkWell( + key: leftKey, + onTap: () {}, + child: Center( + child: SizedBox( + width: leftWidth, + height: 50, + child: leftChild, + ), ), ), ), - ), - SizedBox( - width: rightWidth, - height: 100, - child: InkWell( - key: rightKey, - onTap: () {}, - child: Center( - child: SizedBox( - width: leftWidth, - height: 50, - child: rightChild, + SizedBox( + width: rightWidth, + height: 100, + child: InkWell( + key: rightKey, + onTap: () {}, + child: Center( + child: SizedBox( + width: leftWidth, + height: 50, + child: rightChild, + ), ), ), ), - ), - ], + ], + ), ), ), ), @@ -1714,24 +1741,27 @@ testWidgets('InkResponse radius can be updated', (WidgetTester tester) async { testWidgets("Ink wells's splash starts before tap is confirmed and disappear after tap is canceled", (WidgetTester tester) async { final GlobalKey innerKey = GlobalKey(); await tester.pumpWidget( - Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: GestureDetector( - onHorizontalDragStart: (_) {}, - child: Center( - child: SizedBox( - width: 100, - height: 100, - child: InkWell( - onTap: () {}, - child: Center( - child: SizedBox( - width: 50, - height: 50, - child: InkWell( - key: innerKey, - onTap: () {}, + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: GestureDetector( + onHorizontalDragStart: (_) {}, + child: Center( + child: SizedBox( + width: 100, + height: 100, + child: InkWell( + onTap: () {}, + child: Center( + child: SizedBox( + width: 50, + height: 50, + child: InkWell( + key: innerKey, + onTap: () {}, + ), ), ), ), diff --git a/packages/flutter/test/material/input_chip_test.dart b/packages/flutter/test/material/input_chip_test.dart index a42ae2f0b02ec..827ded808496e 100644 --- a/packages/flutter/test/material/input_chip_test.dart +++ b/packages/flutter/test/material/input_chip_test.dart @@ -13,7 +13,7 @@ Widget wrapForChip({ TextDirection textDirection = TextDirection.ltr, double textScaleFactor = 1.0, Brightness brightness = Brightness.light, - bool useMaterial3 = false, + bool? useMaterial3, }) { return MaterialApp( theme: ThemeData(brightness: brightness, useMaterial3: useMaterial3), @@ -101,6 +101,101 @@ void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { } void main() { + testWidgets('InputChip.color resolves material states', (WidgetTester tester) async { + const Color disabledSelectedColor = Color(0xffffff00); + const Color disabledColor = Color(0xff00ff00); + const Color backgroundColor = Color(0xff0000ff); + const Color selectedColor = Color(0xffff0000); + Widget buildApp({ required bool enabled, required bool selected }) { + return wrapForChip( + useMaterial3: true, + child: InputChip( + onSelected: enabled ? (bool value) { } : null, + selected: selected, + color: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.disabled) && states.contains(MaterialState.selected)) { + return disabledSelectedColor; + } + if (states.contains(MaterialState.disabled)) { + return disabledColor; + } + if (states.contains(MaterialState.selected)) { + return selectedColor; + } + return backgroundColor; + }), + label: const Text('InputChip'), + ), + ); + } + + // Test enabled chip. + await tester.pumpWidget(buildApp(enabled: true, selected: false)); + + // Enabled chip should have the provided backgroundColor. + expect(getMaterialBox(tester), paints..rrect(color: backgroundColor)); + + // Test disabled chip. + await tester.pumpWidget(buildApp(enabled: false, selected: false)); + await tester.pumpAndSettle(); + + // Disabled chip should have the provided disabledColor. + expect(getMaterialBox(tester), paints..rrect(color: disabledColor)); + + // Test enabled & selected chip. + await tester.pumpWidget(buildApp(enabled: true, selected: true)); + await tester.pumpAndSettle(); + + // Enabled & selected chip should have the provided selectedColor. + expect(getMaterialBox(tester), paints..rrect(color: selectedColor)); + + // Test disabled & selected chip. + await tester.pumpWidget(buildApp(enabled: false, selected: true)); + await tester.pumpAndSettle(); + + // Disabled & selected chip should have the provided disabledSelectedColor. + expect(getMaterialBox(tester), paints..rrect(color: disabledSelectedColor)); + }); + + testWidgets('InputChip uses provided state color properties', (WidgetTester tester) async { + const Color disabledColor = Color(0xff00ff00); + const Color backgroundColor = Color(0xff0000ff); + const Color selectedColor = Color(0xffff0000); + Widget buildApp({ required bool enabled, required bool selected }) { + return wrapForChip( + useMaterial3: true, + child: InputChip( + onSelected: enabled ? (bool value) { } : null, + selected: selected, + disabledColor: disabledColor, + backgroundColor: backgroundColor, + selectedColor: selectedColor, + label: const Text('InputChip'), + ), + ); + } + + // Test enabled chip. + await tester.pumpWidget(buildApp(enabled: true, selected: false)); + + // Enabled chip should have the provided backgroundColor. + expect(getMaterialBox(tester), paints..rrect(color: backgroundColor)); + + // Test disabled chip. + await tester.pumpWidget(buildApp(enabled: false, selected: false)); + await tester.pumpAndSettle(); + + // Disabled chip should have the provided disabledColor. + expect(getMaterialBox(tester), paints..rrect(color: disabledColor)); + + // Test enabled & selected chip. + await tester.pumpWidget(buildApp(enabled: true, selected: true)); + await tester.pumpAndSettle(); + + // Enabled & selected chip should have the provided selectedColor. + expect(getMaterialBox(tester), paints..rrect(color: selectedColor)); + }); + testWidgets('InputChip can be tapped', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index eade9fac9978b..775505396767e 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -36,6 +36,7 @@ Widget buildInputDecorator({ ), }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Builder( builder: (BuildContext context) { @@ -787,6 +788,7 @@ void main() { final TextEditingController controller = TextEditingController(); Widget buildFrame(bool alignLabelWithHint) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Directionality( textDirection: TextDirection.ltr, @@ -837,6 +839,7 @@ void main() { final TextEditingController controller = TextEditingController(); Widget buildFrame(bool alignLabelWithHint) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Directionality( textDirection: TextDirection.ltr, @@ -888,6 +891,7 @@ void main() { final TextEditingController controller = TextEditingController(); Widget buildFrame(bool alignLabelWithHint) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Directionality( textDirection: TextDirection.ltr, @@ -939,6 +943,7 @@ void main() { final TextEditingController controller = TextEditingController(); Widget buildFrame(bool alignLabelWithHint) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Directionality( textDirection: TextDirection.ltr, @@ -1638,6 +1643,47 @@ void main() { expect(tester.getBottomLeft(find.text(kHelper1)), const Offset(12.0, 76.0)); }); + testWidgets('InputDecorator shows error text', (WidgetTester tester) async { + await tester.pumpWidget( + buildInputDecorator( + useMaterial3: useMaterial3, + decoration: const InputDecoration( + errorText: 'errorText', + ), + ), + ); + + expect(find.text('errorText'), findsOneWidget); + }); + + testWidgets('InputDecorator shows error widget', (WidgetTester tester) async { + await tester.pumpWidget( + buildInputDecorator( + useMaterial3: useMaterial3, + decoration: const InputDecoration( + error: Text('error', style: TextStyle(fontSize: 20.0)), + ), + ), + ); + + expect(find.text('error'), findsOneWidget); + }); + + testWidgets('InputDecorator throws when error text and error widget are provided', (WidgetTester tester) async { + expect( + () { + buildInputDecorator( + useMaterial3: useMaterial3, + decoration: InputDecoration( + errorText: 'errorText', + error: const Text('error', style: TextStyle(fontSize: 20.0)), + ), + ); + }, + throwsAssertionError, + ); + }); + testWidgets('InputDecorator prefix/suffix texts', (WidgetTester tester) async { await tester.pumpWidget( buildInputDecorator( @@ -3919,6 +3965,108 @@ void main() { expect(copy.fillColor, Colors.blue); }); + test('InputDecorationTheme merge', () { + const InputDecorationTheme overrideTheme = InputDecorationTheme( + labelStyle: TextStyle(color: Color(0x000000f0)), + floatingLabelStyle: TextStyle(color: Color(0x000000f1)), + helperStyle: TextStyle(color: Color(0x000000f2)), + helperMaxLines: 1, + hintStyle: TextStyle(color: Color(0x000000f3)), + errorStyle: TextStyle(color: Color(0x000000f4)), + errorMaxLines: 1, + floatingLabelBehavior: FloatingLabelBehavior.never, + floatingLabelAlignment: FloatingLabelAlignment.center, + isDense: true, + contentPadding: EdgeInsets.all(1.0), + isCollapsed: true, + iconColor: Color(0x000000f5), + prefixStyle: TextStyle(color: Color(0x000000f6)), + prefixIconColor: Color(0x000000f7), + suffixStyle: TextStyle(color: Color(0x000000f8)), + suffixIconColor: Color(0x000000f9), + counterStyle: TextStyle(color: Color(0x00000f10)), + filled: true, + fillColor: Color(0x00000f11), + activeIndicatorBorder: BorderSide( + color: Color(0x00000f12), + ), + outlineBorder: BorderSide( + color: Color(0x00000f13), + ), + focusColor: Color(0x00000f14), + hoverColor: Color(0x00000f15), + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(2.0)), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000f16), + ), + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000f17), + ), + ), + disabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000f18), + ), + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000f19), + ), + ), + border: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000f20), + ), + ), + alignLabelWithHint: true, + constraints: BoxConstraints( + minHeight: 1.0, + minWidth: 1.0, + ), + ); + + final InputDecorationTheme inputDecorationTheme = ThemeData().inputDecorationTheme; + final InputDecorationTheme merged = inputDecorationTheme.merge(overrideTheme); + + expect(merged.labelStyle, overrideTheme.labelStyle); + expect(merged.floatingLabelStyle, overrideTheme.floatingLabelStyle); + expect(merged.helperStyle, overrideTheme.helperStyle); + expect(merged.helperMaxLines, overrideTheme.helperMaxLines); + expect(merged.hintStyle, overrideTheme.hintStyle); + expect(merged.errorStyle, overrideTheme.errorStyle); + expect(merged.errorMaxLines, overrideTheme.errorMaxLines); + expect(merged.floatingLabelBehavior, isNot(overrideTheme.floatingLabelBehavior)); + expect(merged.floatingLabelAlignment, isNot(overrideTheme.floatingLabelAlignment)); + expect(merged.isDense, isNot(overrideTheme.isDense)); + expect(merged.contentPadding, overrideTheme.contentPadding); + expect(merged.isCollapsed, isNot(overrideTheme.isCollapsed)); + expect(merged.iconColor, overrideTheme.iconColor); + expect(merged.prefixStyle, overrideTheme.prefixStyle); + expect(merged.prefixIconColor, overrideTheme.prefixIconColor); + expect(merged.suffixStyle, overrideTheme.suffixStyle); + expect(merged.suffixIconColor, overrideTheme.suffixIconColor); + expect(merged.counterStyle, overrideTheme.counterStyle); + expect(merged.filled, isNot(overrideTheme.filled)); + expect(merged.fillColor, overrideTheme.fillColor); + expect(merged.activeIndicatorBorder, overrideTheme.activeIndicatorBorder); + expect(merged.outlineBorder, overrideTheme.outlineBorder); + expect(merged.focusColor, overrideTheme.focusColor); + expect(merged.hoverColor, overrideTheme.hoverColor); + expect(merged.errorBorder, overrideTheme.errorBorder); + expect(merged.focusedBorder, overrideTheme.focusedBorder); + expect(merged.focusedErrorBorder, overrideTheme.focusedErrorBorder); + expect(merged.disabledBorder, overrideTheme.disabledBorder); + expect(merged.enabledBorder, overrideTheme.enabledBorder); + expect(merged.border, overrideTheme.border); + expect(merged.alignLabelWithHint, isNot(overrideTheme.alignLabelWithHint)); + expect(merged.constraints, overrideTheme.constraints); + }); + testWidgets('InputDecorationTheme outline border', (WidgetTester tester) async { await tester.pumpWidget( buildInputDecorator( @@ -4184,24 +4332,50 @@ void main() { }); testWidgets('InputDecorationTheme.inputDecoration', (WidgetTester tester) async { - const TextStyle themeStyle = TextStyle(color: Colors.green); - const TextStyle decorationStyle = TextStyle(color: Colors.blue); + const TextStyle themeStyle = TextStyle(color: Color(0xFF00FFFF)); + const Color themeColor = Color(0xFF00FF00); + const InputBorder themeInputBorder = OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFF0000FF), + ), + ); + const TextStyle decorationStyle = TextStyle(color: Color(0xFFFFFF00)); + const Color decorationColor = Color(0xFF0000FF); + const InputBorder decorationInputBorder = OutlineInputBorder( + borderSide: BorderSide( + color: Color(0xFFFF00FF), + ), + ); // InputDecorationTheme arguments define InputDecoration properties. InputDecoration decoration = const InputDecoration().applyDefaults( const InputDecorationTheme( labelStyle: themeStyle, + floatingLabelStyle: themeStyle, helperStyle: themeStyle, + helperMaxLines: 2, hintStyle: themeStyle, errorStyle: themeStyle, + errorMaxLines: 2, + floatingLabelBehavior: FloatingLabelBehavior.never, + floatingLabelAlignment: FloatingLabelAlignment.center, isDense: true, contentPadding: EdgeInsets.all(1.0), + iconColor: themeColor, prefixStyle: themeStyle, + prefixIconColor: themeColor, suffixStyle: themeStyle, + suffixIconColor: themeColor, counterStyle: themeStyle, filled: true, - fillColor: Colors.red, - focusColor: Colors.blue, + fillColor: themeColor, + focusColor: themeColor, + hoverColor: themeColor, + errorBorder: themeInputBorder, + focusedBorder: themeInputBorder, + focusedErrorBorder: themeInputBorder, + disabledBorder: themeInputBorder, + enabledBorder: themeInputBorder, border: InputBorder.none, alignLabelWithHint: true, constraints: BoxConstraints(minWidth: 10, maxWidth: 20, minHeight: 30, maxHeight: 40), @@ -4209,16 +4383,31 @@ void main() { ); expect(decoration.labelStyle, themeStyle); + expect(decoration.floatingLabelStyle, themeStyle); expect(decoration.helperStyle, themeStyle); + expect(decoration.helperMaxLines, 2); expect(decoration.hintStyle, themeStyle); expect(decoration.errorStyle, themeStyle); + expect(decoration.errorMaxLines, 2); + expect(decoration.floatingLabelBehavior, FloatingLabelBehavior.never); + expect(decoration.floatingLabelAlignment, FloatingLabelAlignment.center); expect(decoration.isDense, true); expect(decoration.contentPadding, const EdgeInsets.all(1.0)); + expect(decoration.iconColor, themeColor); expect(decoration.prefixStyle, themeStyle); + expect(decoration.prefixIconColor, themeColor); expect(decoration.suffixStyle, themeStyle); + expect(decoration.suffixIconColor, themeColor); expect(decoration.counterStyle, themeStyle); expect(decoration.filled, true); - expect(decoration.fillColor, Colors.red); + expect(decoration.fillColor, themeColor); + expect(decoration.focusColor, themeColor); + expect(decoration.hoverColor, themeColor); + expect(decoration.errorBorder, themeInputBorder); + expect(decoration.focusedBorder, themeInputBorder); + expect(decoration.focusedErrorBorder, themeInputBorder); + expect(decoration.disabledBorder, themeInputBorder); + expect(decoration.enabledBorder, themeInputBorder); expect(decoration.border, InputBorder.none); expect(decoration.alignLabelWithHint, true); expect(decoration.constraints, const BoxConstraints(minWidth: 10, maxWidth: 20, minHeight: 30, maxHeight: 40)); @@ -4226,57 +4415,97 @@ void main() { // InputDecoration (baseDecoration) defines InputDecoration properties decoration = const InputDecoration( labelStyle: decorationStyle, + floatingLabelStyle: decorationStyle, helperStyle: decorationStyle, + helperMaxLines: 3, hintStyle: decorationStyle, errorStyle: decorationStyle, + errorMaxLines: 3, + floatingLabelBehavior: FloatingLabelBehavior.always, + floatingLabelAlignment: FloatingLabelAlignment.start, isDense: false, contentPadding: EdgeInsets.all(4.0), + iconColor: decorationColor, prefixStyle: decorationStyle, + prefixIconColor: decorationColor, suffixStyle: decorationStyle, + suffixIconColor: decorationColor, counterStyle: decorationStyle, filled: false, - fillColor: Colors.blue, + fillColor: decorationColor, + focusColor: decorationColor, + hoverColor: decorationColor, + errorBorder: decorationInputBorder, + focusedBorder: decorationInputBorder, + focusedErrorBorder: decorationInputBorder, + disabledBorder: decorationInputBorder, + enabledBorder: decorationInputBorder, border: OutlineInputBorder(), alignLabelWithHint: false, - constraints: BoxConstraints(minWidth: 10, maxWidth: 20, minHeight: 30, maxHeight: 40), + constraints: BoxConstraints(minWidth: 40, maxWidth: 50, minHeight: 60, maxHeight: 70), ).applyDefaults( const InputDecorationTheme( labelStyle: themeStyle, + floatingLabelStyle: themeStyle, helperStyle: themeStyle, - helperMaxLines: 5, + helperMaxLines: 2, hintStyle: themeStyle, errorStyle: themeStyle, - errorMaxLines: 4, + errorMaxLines: 2, + floatingLabelBehavior: FloatingLabelBehavior.never, + floatingLabelAlignment: FloatingLabelAlignment.center, isDense: true, contentPadding: EdgeInsets.all(1.0), + iconColor: themeColor, prefixStyle: themeStyle, + prefixIconColor: themeColor, suffixStyle: themeStyle, + suffixIconColor: themeColor, counterStyle: themeStyle, filled: true, - fillColor: Colors.red, - focusColor: Colors.blue, + fillColor: themeColor, + focusColor: themeColor, + hoverColor: themeColor, + errorBorder: themeInputBorder, + focusedBorder: themeInputBorder, + focusedErrorBorder: themeInputBorder, + disabledBorder: themeInputBorder, + enabledBorder: themeInputBorder, border: InputBorder.none, alignLabelWithHint: true, - constraints: BoxConstraints(minWidth: 40, maxWidth: 30, minHeight: 20, maxHeight: 10), + constraints: BoxConstraints(minWidth: 10, maxWidth: 20, minHeight: 30, maxHeight: 40), ), ); expect(decoration.labelStyle, decorationStyle); + expect(decoration.floatingLabelStyle, decorationStyle); expect(decoration.helperStyle, decorationStyle); - expect(decoration.helperMaxLines, 5); + expect(decoration.helperMaxLines, 3); expect(decoration.hintStyle, decorationStyle); expect(decoration.errorStyle, decorationStyle); - expect(decoration.errorMaxLines, 4); + expect(decoration.errorMaxLines, 3); + expect(decoration.floatingLabelBehavior, FloatingLabelBehavior.always); + expect(decoration.floatingLabelAlignment, FloatingLabelAlignment.start); expect(decoration.isDense, false); expect(decoration.contentPadding, const EdgeInsets.all(4.0)); + expect(decoration.iconColor, decorationColor); expect(decoration.prefixStyle, decorationStyle); + expect(decoration.prefixIconColor, decorationColor); expect(decoration.suffixStyle, decorationStyle); + expect(decoration.suffixIconColor, decorationColor); expect(decoration.counterStyle, decorationStyle); expect(decoration.filled, false); - expect(decoration.fillColor, Colors.blue); + expect(decoration.fillColor, decorationColor); + expect(decoration.focusColor, decorationColor); + expect(decoration.hoverColor, decorationColor); + expect(decoration.errorBorder, decorationInputBorder); + expect(decoration.focusedBorder, decorationInputBorder); + expect(decoration.focusedErrorBorder, decorationInputBorder); + expect(decoration.disabledBorder, decorationInputBorder); + expect(decoration.enabledBorder, decorationInputBorder); expect(decoration.border, const OutlineInputBorder()); expect(decoration.alignLabelWithHint, false); - expect(decoration.constraints, const BoxConstraints(minWidth: 10, maxWidth: 20, minHeight: 30, maxHeight: 40)); + expect(decoration.constraints, const BoxConstraints(minWidth: 40, maxWidth: 50, minHeight: 60, maxHeight: 70)); }); testWidgets('InputDecorationTheme.inputDecoration with MaterialState', (WidgetTester tester) async { @@ -4483,6 +4712,7 @@ void main() { // This is a regression test for https://github.com/flutter/flutter/issues/82321 Widget buildFrame(TextDirection textDirection) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Container( padding: const EdgeInsets.all(16.0), @@ -4531,6 +4761,7 @@ void main() { Widget buildFrame(TextDirection textDirection) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Container( padding: const EdgeInsets.all(16.0), @@ -5027,7 +5258,6 @@ void main() { useMaterial3: useMaterial3, // isFocused: false (default) decoration: const InputDecoration( - // errorText: false (default) enabled: false, errorBorder: errorBorder, focusedBorder: focusedBorder, @@ -5697,8 +5927,11 @@ void main() { return false; } final Rect clipRect = arguments[0] as Rect; - // 133.3 is approximately 100 / 0.75 (_kFinalLabelScale) - expect(clipRect, rectMoreOrLessEquals(const Rect.fromLTWH(0, 0, 133.0, 16.0))); + // _kFinalLabelScale = 0.75 + const double width = bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') + ? 100 / 0.75 + : 133.0; + expect(clipRect, rectMoreOrLessEquals(const Rect.fromLTWH(0, 0, width, 16.0), epsilon: 1e-5)); return true; }), ); @@ -5711,6 +5944,7 @@ void main() { late StateSetter setState; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: StatefulBuilder( builder: (BuildContext context, StateSetter setter) { setState = setter; @@ -6282,5 +6516,35 @@ void main() { final Text hintTextWidget = tester.widget(hintTextFinder); expect(hintTextWidget.style!.overflow, decoration.hintStyle!.overflow); }); + + testWidgets('prefixIcon in RTL with asymmetric padding', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/129591 + const InputDecoration decoration = InputDecoration( + contentPadding: EdgeInsetsDirectional.only(end: 24), + prefixIcon: Focus(child: Icon(Icons.search)), + ); + + await tester.pumpWidget( + buildInputDecorator( + useMaterial3: useMaterial3, + // isEmpty: false (default) + // isFocused: false (default) + decoration: decoration, + textDirection: TextDirection.rtl, + ), + ); + await tester.pumpAndSettle(); + + expect(find.byType(InputDecorator), findsOneWidget); + expect(find.byType(Icon), findsOneWidget); + + final Offset(dx: double decoratorRight) = + tester.getTopRight(find.byType(InputDecorator)); + final Offset(dx: double prefixRight) = + tester.getTopRight(find.byType(Icon)); + + // The prefix is inside the decorator. + expect(decoratorRight, lessThanOrEqualTo(prefixRight)); + }); } } diff --git a/packages/flutter/test/material/list_tile_test.dart b/packages/flutter/test/material/list_tile_test.dart index 5d0204ff88b8b..d18beb02620fb 100644 --- a/packages/flutter/test/material/list_tile_test.dart +++ b/packages/flutter/test/material/list_tile_test.dart @@ -991,6 +991,7 @@ void main() { testWidgets('ListTile can be splashed and has correct splash color', (WidgetTester tester) async { final Widget buildApp = MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: SizedBox( @@ -1279,6 +1280,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: ListTile( @@ -1807,6 +1809,7 @@ void main() { bool selected = false, }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: Builder( @@ -1947,13 +1950,13 @@ void main() { // ListTile default text colors. await tester.pumpWidget(buildFrame()); final RenderParagraph leading = _getTextRenderObject(tester, 'leading'); - expect(leading.text.style!.color, theme.textTheme.labelSmall!.color); + expect(leading.text.style!.color, theme.colorScheme.onSurfaceVariant); final RenderParagraph title = _getTextRenderObject(tester, 'title'); - expect(title.text.style!.color, theme.textTheme.bodyLarge!.color); + expect(title.text.style!.color, theme.colorScheme.onSurface); final RenderParagraph subtitle = _getTextRenderObject(tester, 'subtitle'); - expect(subtitle.text.style!.color, theme.textTheme.bodyMedium!.color); + expect(subtitle.text.style!.color, theme.colorScheme.onSurfaceVariant); final RenderParagraph trailing = _getTextRenderObject(tester, 'trailing'); - expect(trailing.text.style!.color, theme.textTheme.labelSmall!.color); + expect(trailing.text.style!.color, theme.colorScheme.onSurfaceVariant); }); testWidgets('Default ListTile debugFillProperties', (WidgetTester tester) async { @@ -2479,8 +2482,9 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('ListTile geometry (LTR)', (WidgetTester tester) async { // See https://material.io/go/design-lists @@ -3570,6 +3574,7 @@ void main() { }); testWidgets('ListTile text color', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: false); Widget buildFrame({ bool dense = false, bool enabled = true, @@ -3577,7 +3582,7 @@ void main() { ListTileStyle? style, }) { return MaterialApp( - theme: ThemeData(useMaterial3: false), + theme: theme, home: Material( child: Center( child: Builder( @@ -3599,8 +3604,6 @@ void main() { ); } - final ThemeData theme = ThemeData(); - // ListTile - ListTileStyle.list (default). await tester.pumpWidget(buildFrame()); RenderParagraph leading = _getTextRenderObject(tester, 'leading'); diff --git a/packages/flutter/test/material/list_tile_theme_test.dart b/packages/flutter/test/material/list_tile_theme_test.dart index 53273095f1001..c18921ba8f932 100644 --- a/packages/flutter/test/material/list_tile_theme_test.dart +++ b/packages/flutter/test/material/list_tile_theme_test.dart @@ -215,6 +215,7 @@ void main() { Color? textColor, }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: ListTileTheme( diff --git a/packages/flutter/test/material/localizations_test.dart b/packages/flutter/test/material/localizations_test.dart index d528cc73da3ea..67df019b002e9 100644 --- a/packages/flutter/test/material/localizations_test.dart +++ b/packages/flutter/test/material/localizations_test.dart @@ -28,6 +28,7 @@ void main() { expect(localizations.continueButtonLabel, isNotNull); expect(localizations.copyButtonLabel, isNotNull); expect(localizations.cutButtonLabel, isNotNull); + expect(localizations.scanTextButtonLabel, isNotNull); expect(localizations.okButtonLabel, isNotNull); expect(localizations.pasteButtonLabel, isNotNull); expect(localizations.selectAllButtonLabel, isNotNull); diff --git a/packages/flutter/test/material/material_button_test.dart b/packages/flutter/test/material/material_button_test.dart index 40ba12980d863..1b329a19485c5 100644 --- a/packages/flutter/test/material/material_button_test.dart +++ b/packages/flutter/test/material/material_button_test.dart @@ -23,11 +23,14 @@ void main() { // Enabled MaterialButton await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: MaterialButton( - onPressed: () { }, - child: const Text('button'), + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: MaterialButton( + onPressed: () { }, + child: const Text('button'), + ), ), ), ); @@ -68,11 +71,14 @@ void main() { // Disabled MaterialButton await tester.pumpWidget( - const Directionality( - textDirection: TextDirection.ltr, - child: MaterialButton( - onPressed: null, - child: Text('button'), + Theme( + data: ThemeData(useMaterial3: false), + child: const Directionality( + textDirection: TextDirection.ltr, + child: MaterialButton( + onPressed: null, + child: Text('button'), + ), ), ), ); @@ -442,6 +448,7 @@ void main() { textDirection: TextDirection.ltr, child: Theme( data: ThemeData( + useMaterial3: false, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), child: buttonWidget, @@ -488,6 +495,7 @@ void main() { textDirection: TextDirection.ltr, child: Theme( data: ThemeData( + useMaterial3: false, highlightColor: themeHighlightColor1, splashColor: themeSplashColor1, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, @@ -516,6 +524,7 @@ void main() { textDirection: TextDirection.ltr, child: Theme( data: ThemeData( + useMaterial3: false, highlightColor: themeHighlightColor2, splashColor: themeSplashColor2, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, @@ -574,15 +583,20 @@ void main() { ); // enabled button - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: MaterialButton( - child: const Text('Button'), - onPressed: () { /* to make sure the button is enabled */ }, + await tester.pumpWidget( + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: MaterialButton( + child: const Text('Button'), + onPressed: () { /* to make sure the button is enabled */ }, + ), + ), ), ), - )); + ); expect(semantics, hasSemantics( TestSemantics.root( @@ -607,15 +621,20 @@ void main() { )); // disabled button - await tester.pumpWidget(const Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: MaterialButton( - onPressed: null, // button is disabled - child: Text('Button'), + await tester.pumpWidget( + Theme( + data: ThemeData(useMaterial3: false), + child: const Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: MaterialButton( + onPressed: null, // button is disabled + child: Text('Button'), + ), + ), ), ), - )); + ); expect(semantics, hasSemantics( TestSemantics.root( @@ -773,6 +792,7 @@ void main() { Future<void> buildTest(VisualDensity visualDensity, {bool useText = false}) async { return tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Directionality( textDirection: TextDirection.rtl, child: Center( diff --git a/packages/flutter/test/material/material_test.dart b/packages/flutter/test/material/material_test.dart index ba8a399d934f6..f08733a29e578 100644 --- a/packages/flutter/test/material/material_test.dart +++ b/packages/flutter/test/material/material_test.dart @@ -924,6 +924,7 @@ void main() { final Key painterKey = UniqueKey(); await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: RepaintBoundary( key: painterKey, @@ -962,6 +963,7 @@ void main() { final Key painterKey = UniqueKey(); await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: RepaintBoundary( key: painterKey, diff --git a/packages/flutter/test/material/menu_anchor_test.dart b/packages/flutter/test/material/menu_anchor_test.dart index 0703202aae54d..e48e855ff1d97 100644 --- a/packages/flutter/test/material/menu_anchor_test.dart +++ b/packages/flutter/test/material/menu_anchor_test.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; +import '../widgets/semantics_tester.dart'; void main() { late MenuController controller; @@ -81,6 +82,7 @@ void main() { }) { final FocusNode focusNode = FocusNode(); return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Directionality( textDirection: textDirection, @@ -144,7 +146,7 @@ void main() { testWidgets('Menu responds to density changes', (WidgetTester tester) async { Widget buildMenu({VisualDensity? visualDensity = VisualDensity.standard}) { return MaterialApp( - theme: ThemeData(visualDensity: visualDensity), + theme: ThemeData(visualDensity: visualDensity, useMaterial3: false), home: Material( child: Column( children: <Widget>[ @@ -541,6 +543,7 @@ void main() { testWidgets('geometry', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Column( children: <Widget>[ @@ -605,6 +608,7 @@ void main() { testWidgets('geometry with RTL direction', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Directionality( textDirection: TextDirection.rtl, @@ -814,6 +818,7 @@ void main() { Padding( padding: const EdgeInsets.all(10.0), child: MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Column( children: <Widget>[ @@ -866,6 +871,7 @@ void main() { Padding( padding: const EdgeInsets.all(10.0), child: MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Directionality( textDirection: TextDirection.rtl, @@ -2294,6 +2300,7 @@ void main() { await changeSurfaceSize(tester, const Size(800, 600)); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Column( children: <Widget>[ @@ -2337,6 +2344,7 @@ void main() { await changeSurfaceSize(tester, const Size(800, 600)); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Directionality( textDirection: TextDirection.rtl, child: Material( @@ -2383,6 +2391,7 @@ void main() { await changeSurfaceSize(tester, const Size(300, 300)); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { return Directionality( @@ -2427,6 +2436,7 @@ void main() { await changeSurfaceSize(tester, const Size(300, 300)); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { return Directionality( @@ -2471,6 +2481,7 @@ void main() { await changeSurfaceSize(tester, const Size(800, 600)); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { return Directionality( @@ -2547,6 +2558,7 @@ void main() { await changeSurfaceSize(tester, const Size(800, 600)); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { return Directionality( @@ -2623,6 +2635,7 @@ void main() { await changeSurfaceSize(tester, const Size(800, 600)); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { return Directionality( @@ -2674,6 +2687,7 @@ void main() { await changeSurfaceSize(tester, const Size(800, 600)); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { return Directionality( @@ -2729,7 +2743,7 @@ void main() { }) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData.light().copyWith(visualDensity: visualDensity), + theme: ThemeData.light(useMaterial3: false).copyWith(visualDensity: visualDensity), home: Directionality( textDirection: textDirection, child: Material( @@ -3040,6 +3054,86 @@ void main() { expect(radioValue, 1); }); }); + + group('Semantics', () { + testWidgets('MenuItemButton is not a semantic button', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: MenuItemButton( + style: MenuItemButton.styleFrom(fixedSize: const Size(88.0, 36.0)), + onPressed: () { }, + child: const Text('ABC'), + ), + ), + ), + ); + + // The flags should not have SemanticsFlag.isButton + expect(semantics, hasSemantics( + TestSemantics.root( + children: <TestSemantics>[ + TestSemantics.rootChild( + actions: <SemanticsAction>[ + SemanticsAction.tap, + ], + label: 'ABC', + rect: const Rect.fromLTRB(0.0, 0.0, 88.0, 48.0), + transform: Matrix4.translationValues(356.0, 276.0, 0.0), + flags: <SemanticsFlag>[ + SemanticsFlag.hasEnabledState, + SemanticsFlag.isEnabled, + SemanticsFlag.isFocusable, + ], + textDirection: TextDirection.ltr, + ), + ], + ), + ignoreId: true, + )); + + semantics.dispose(); + }); + + testWidgets('SubMenuButton is not a semantic button', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: SubmenuButton( + onHover: (bool value) {}, + style: SubmenuButton.styleFrom(fixedSize: const Size(88.0, 36.0)), + menuChildren: const <Widget>[], + child: const Text('ABC'), + ), + ), + ), + ); + + // The flags should not have SemanticsFlag.isButton + expect(semantics, hasSemantics( + TestSemantics.root( + children: <TestSemantics>[ + TestSemantics.rootChild( + label: 'ABC', + rect: const Rect.fromLTRB(0.0, 0.0, 88.0, 48.0), + transform: Matrix4.translationValues(356.0, 276.0, 0.0), + flags: <SemanticsFlag>[ + SemanticsFlag.hasEnabledState, + ], + textDirection: TextDirection.ltr, + ), + ], + ), + ignoreId: true, + )); + + semantics.dispose(); + }); + }); } List<Widget> createTestMenus({ @@ -3162,9 +3256,4 @@ enum TestMenu { final String acceleratorLabel; // Strip the accelerator markers. String get label => MenuAcceleratorLabel.stripAcceleratorMarkers(acceleratorLabel); - int get acceleratorIndex { - int index = -1; - MenuAcceleratorLabel.stripAcceleratorMarkers(acceleratorLabel, setIndex: (int i) => index = i); - return index; - } } diff --git a/packages/flutter/test/material/menu_bar_theme_test.dart b/packages/flutter/test/material/menu_bar_theme_test.dart index c8d4aa6c2d32a..003ff987ed27c 100644 --- a/packages/flutter/test/material/menu_bar_theme_test.dart +++ b/packages/flutter/test/material/menu_bar_theme_test.dart @@ -55,6 +55,7 @@ void main() { testWidgets('theme is honored', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Builder(builder: (BuildContext context) { return MenuTheme( @@ -107,6 +108,7 @@ void main() { testWidgets('Constructor parameters override theme parameters', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Builder( builder: (BuildContext context) { diff --git a/packages/flutter/test/material/menu_style_test.dart b/packages/flutter/test/material/menu_style_test.dart index 482dbe39b1d8e..9f1de88855344 100644 --- a/packages/flutter/test/material/menu_style_test.dart +++ b/packages/flutter/test/material/menu_style_test.dart @@ -239,6 +239,7 @@ void main() { testWidgets('visual density', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Column( children: <Widget>[ diff --git a/packages/flutter/test/material/menu_theme_test.dart b/packages/flutter/test/material/menu_theme_test.dart index 52fa532db6053..f6fb5d324e3e4 100644 --- a/packages/flutter/test/material/menu_theme_test.dart +++ b/packages/flutter/test/material/menu_theme_test.dart @@ -55,6 +55,7 @@ void main() { testWidgets('theme is honored', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Builder(builder: (BuildContext context) { return MenuBarTheme( @@ -107,6 +108,7 @@ void main() { testWidgets('Constructor parameters override theme parameters', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Builder( builder: (BuildContext context) { diff --git a/packages/flutter/test/material/mergeable_material_test.dart b/packages/flutter/test/material/mergeable_material_test.dart index 6ae2d3b466176..08b3fc735da94 100644 --- a/packages/flutter/test/material/mergeable_material_test.dart +++ b/packages/flutter/test/material/mergeable_material_test.dart @@ -202,8 +202,9 @@ void main() { testWidgets('MergeableMaterial paints shadows', (WidgetTester tester) async { debugDisableShadows = false; await tester.pumpWidget( - const MaterialApp( - home: Scaffold( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( body: SingleChildScrollView( child: MergeableMaterial( children: <MergeableMaterialItem>[ @@ -1087,6 +1088,75 @@ void main() { matches(getBorderRadius(tester, 1), RadiusType.Round, RadiusType.Round); }); + testWidgets('MergeableMaterial insert and separate slice', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: SingleChildScrollView( + child: MergeableMaterial( + children: <MergeableMaterialItem>[ + MaterialSlice( + key: ValueKey<String>('A'), + child: SizedBox( + width: 100.0, + height: 100.0, + ), + ), + ], + ), + ), + ), + ), + ); + + final RenderBox box = tester.renderObject(find.byType(MergeableMaterial)); + expect(box.size.height, equals(100)); + + matches(getBorderRadius(tester, 0), RadiusType.Round, RadiusType.Round); + + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: SingleChildScrollView( + child: MergeableMaterial( + children: <MergeableMaterialItem>[ + MaterialSlice( + key: ValueKey<String>('A'), + child: SizedBox( + width: 100.0, + height: 100.0, + ), + ), + MaterialGap( + key: ValueKey<String>('x'), + ), + MaterialSlice( + key: ValueKey<String>('B'), + child: SizedBox( + width: 100.0, + height: 100.0, + ), + ), + ], + ), + ), + ), + ), + ); + + await tester.pump(const Duration(milliseconds: 100)); + expect(box.size.height, lessThan(216)); + + matches(getBorderRadius(tester, 0), RadiusType.Round, RadiusType.Shifting); + matches(getBorderRadius(tester, 1), RadiusType.Shifting, RadiusType.Round); + + await tester.pump(const Duration(milliseconds: 100)); + expect(box.size.height, equals(216)); + + matches(getBorderRadius(tester, 0), RadiusType.Round, RadiusType.Round); + matches(getBorderRadius(tester, 1), RadiusType.Round, RadiusType.Round); + }); + bool isDivider(BoxDecoration decoration, bool top, bool bottom) { const BorderSide side = BorderSide(color: Color(0x1F000000), width: 0.5); @@ -1100,8 +1170,9 @@ void main() { testWidgets('MergeableMaterial dividers', (WidgetTester tester) async { await tester.pumpWidget( - const MaterialApp( - home: Scaffold( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( body: SingleChildScrollView( child: MergeableMaterial( hasDividers: true, @@ -1157,8 +1228,9 @@ void main() { expect(isDivider(boxes[offset + 3], true, false), isTrue); await tester.pumpWidget( - const MaterialApp( - home: Scaffold( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( body: SingleChildScrollView( child: MergeableMaterial( hasDividers: true, diff --git a/packages/flutter/test/material/navigation_bar_test.dart b/packages/flutter/test/material/navigation_bar_test.dart index cf3f210ec0909..77e6ecc0d76df 100644 --- a/packages/flutter/test/material/navigation_bar_test.dart +++ b/packages/flutter/test/material/navigation_bar_test.dart @@ -246,8 +246,8 @@ void main() { ); }); - testWidgets('NavigationBar uses proper defaults when no parameters are given', (WidgetTester tester) async { - // Pre-M3 settings that were hand coded. + testWidgets('NavigationBar uses proper defaults when no parameters are given - M2', (WidgetTester tester) async { + // M2 settings that were hand coded. await tester.pumpWidget( _buildWidget( NavigationBar( @@ -263,6 +263,7 @@ void main() { ], onDestinationSelected: (int i) {}, ), + useMaterial3: false, ), ); @@ -272,13 +273,14 @@ void main() { expect(tester.getSize(find.byType(NavigationBar)).height, 80); expect(_getIndicatorDecoration(tester)?.color, const Color(0x3d2196f3)); expect(_getIndicatorDecoration(tester)?.shape, RoundedRectangleBorder(borderRadius: BorderRadius.circular(16))); + }); + testWidgets('NavigationBar uses proper defaults when no parameters are given - M3', (WidgetTester tester) async { // M3 settings from the token database. + final ThemeData theme = ThemeData(useMaterial3: true); await tester.pumpWidget( _buildWidget( - Theme( - data: ThemeData.light().copyWith(useMaterial3: true), - child: NavigationBar( + NavigationBar( destinations: const <Widget>[ NavigationDestination( icon: Icon(Icons.ac_unit), @@ -291,15 +293,15 @@ void main() { ], onDestinationSelected: (int i) {}, ), - ), + useMaterial3: theme.useMaterial3 ), ); - expect(_getMaterial(tester).color, ThemeData().colorScheme.surface); - expect(_getMaterial(tester).surfaceTintColor, ThemeData().colorScheme.surfaceTint); + expect(_getMaterial(tester).color, theme.colorScheme.surface); + expect(_getMaterial(tester).surfaceTintColor, theme.colorScheme.surfaceTint); expect(_getMaterial(tester).elevation, 3); expect(tester.getSize(find.byType(NavigationBar)).height, 80); - expect(_getIndicatorDecoration(tester)?.color, const Color(0xff2196f3)); + expect(_getIndicatorDecoration(tester)?.color, theme.colorScheme.secondaryContainer); expect(_getIndicatorDecoration(tester)?.shape, const StadiumBorder()); }); @@ -315,9 +317,9 @@ void main() { DefaultMaterialLocalizations.delegate, DefaultWidgetsLocalizations.delegate, ], - child: Directionality( - textDirection: TextDirection.ltr, - child: Navigator( + child: MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Navigator( onGenerateRoute: (RouteSettings settings) { return MaterialPageRoute<void>( builder: (BuildContext context) { @@ -915,8 +917,9 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('Navigation destination updates indicator color and shape', (WidgetTester tester) async { final ThemeData theme = ThemeData(useMaterial3: false); @@ -1268,9 +1271,9 @@ void main() { }); } -Widget _buildWidget(Widget child) { +Widget _buildWidget(Widget child, { bool? useMaterial3 }) { return MaterialApp( - theme: ThemeData.light(), + theme: ThemeData(useMaterial3: useMaterial3), home: Scaffold( bottomNavigationBar: Center( child: child, diff --git a/packages/flutter/test/material/navigation_drawer_test.dart b/packages/flutter/test/material/navigation_drawer_test.dart index fd82e7bac846e..cde4e4eb0eace 100644 --- a/packages/flutter/test/material/navigation_drawer_test.dart +++ b/packages/flutter/test/material/navigation_drawer_test.dart @@ -115,41 +115,52 @@ void main() { }); testWidgets( - 'NavigationDrawer uses proper defaults when no parameters are given', + 'NavigationDrawer uses proper defaults when no parameters are given', (WidgetTester tester) async { final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); - final ThemeData theme= ThemeData.from(colorScheme: const ColorScheme.light()); - // M3 settings from the token database. + final ThemeData theme = ThemeData(useMaterial3: true); await tester.pumpWidget( _buildWidget( scaffoldKey, - Theme( - data: ThemeData.light().copyWith(useMaterial3: true), - child: NavigationDrawer( - children: <Widget>[ - Text('Headline', style: theme.textTheme.bodyLarge), - NavigationDrawerDestination( - icon: Icon(Icons.ac_unit, color: theme.iconTheme.color), - label: Text('AC', style: theme.textTheme.bodySmall), - ), - NavigationDrawerDestination( - icon: Icon(Icons.access_alarm, color: theme.iconTheme.color), - label: Text('Alarm', style: theme.textTheme.bodySmall), - ), - ], - onDestinationSelected: (int i) {}, - ), + NavigationDrawer( + children: <Widget>[ + Text('Headline', style: theme.textTheme.bodyLarge), + const NavigationDrawerDestination( + icon: Icon(Icons.ac_unit), + label: Text('AC'), + ), + const NavigationDrawerDestination( + icon: Icon(Icons.access_alarm), + label: Text('Alarm'), + ), + ], + onDestinationSelected: (int i) {}, ), + useMaterial3: theme.useMaterial3, ), ); scaffoldKey.currentState!.openDrawer(); await tester.pump(const Duration(seconds: 1)); - expect(_getMaterial(tester).color, ThemeData().colorScheme.surface); - expect(_getMaterial(tester).surfaceTintColor, ThemeData().colorScheme.surfaceTint); + // Test drawer Material. + expect(_getMaterial(tester).color, theme.colorScheme.surface); + expect(_getMaterial(tester).surfaceTintColor, theme.colorScheme.surfaceTint); + expect(_getMaterial(tester).shadowColor, Colors.transparent); expect(_getMaterial(tester).elevation, 1); - expect(_getIndicatorDecoration(tester)?.color, const Color(0xff2196f3)); + // Test indicator decoration. + expect(_getIndicatorDecoration(tester)?.color, theme.colorScheme.secondaryContainer); expect(_getIndicatorDecoration(tester)?.shape, const StadiumBorder()); + // Test selected and unselected icon colors. + expect(_iconStyle(tester, Icons.ac_unit)?.color, theme.colorScheme.onSecondaryContainer); + expect(_iconStyle(tester, Icons.access_alarm)?.color, theme.colorScheme.onSurfaceVariant); + // Test selected and unselected label colors. + expect(_labelStyle(tester, 'AC')?.color, theme.colorScheme.onSecondaryContainer); + expect(_labelStyle(tester, 'Alarm')?.color, theme.colorScheme.onSurfaceVariant); + // Test that the icon and label are the correct size. + RenderBox iconBox = tester.renderObject(find.byIcon(Icons.ac_unit)); + expect(iconBox.size, const Size(24.0, 24.0)); + iconBox = tester.renderObject(find.byIcon(Icons.access_alarm)); + expect(iconBox.size, const Size(24.0, 24.0)); }); testWidgets('Navigation drawer is scrollable', (WidgetTester tester) async { @@ -385,9 +396,9 @@ void main() { }); } -Widget _buildWidget(GlobalKey<ScaffoldState> scaffoldKey, Widget child) { +Widget _buildWidget(GlobalKey<ScaffoldState> scaffoldKey, Widget child, { bool? useMaterial3 }) { return MaterialApp( - theme: ThemeData.light(), + theme: ThemeData(useMaterial3: useMaterial3), home: Scaffold( key: scaffoldKey, drawer: child, @@ -421,6 +432,20 @@ ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) { .decoration as ShapeDecoration?; } +TextStyle? _iconStyle(WidgetTester tester, IconData icon) { + final RichText iconRichText = tester.widget<RichText>( + find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)), + ); + return iconRichText.text.style; +} + +TextStyle? _labelStyle(WidgetTester tester, String label) { + final RichText labelRichText = tester.widget<RichText>( + find.descendant(of: find.text(label), matching: find.byType(RichText)), + ); + return labelRichText.text.style; +} + void widgetSetup(WidgetTester tester, double viewWidth, {double viewHeight = 1000}) { tester.view.devicePixelRatio = 2; final double dpi = tester.view.devicePixelRatio; diff --git a/packages/flutter/test/material/navigation_rail_test.dart b/packages/flutter/test/material/navigation_rail_test.dart index f92e67ca8f627..fb0086b9d2446 100644 --- a/packages/flutter/test/material/navigation_rail_test.dart +++ b/packages/flutter/test/material/navigation_rail_test.dart @@ -583,7 +583,7 @@ void main() { // Padding at the top of the rail. const double topPadding = 8.0; // Width of a destination. - const double destinationWidth = 126.0; + const double destinationWidth = bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 125.5 : 126.0; // Height of a destination indicator with icon. const double destinationHeight = 32.0; // Space between the indicator and label. @@ -858,7 +858,7 @@ void main() { // Padding at the top of the rail. const double topPadding = 8.0; // Width of a destination. - const double destinationWidth = 126.0; + const double destinationWidth = bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 125.5 : 126.0; // Height of a destination indicator with icon. const double destinationHeight = 32.0; // Space between the indicator and label. @@ -3048,6 +3048,81 @@ void main() { ); }); + testWidgets('NavigationRail indicator renders properly with long labels', (WidgetTester tester) async { + // This is a regression test for https://github.com/flutter/flutter/issues/128005. + await _pumpNavigationRail( + tester, + navigationRail: NavigationRail( + selectedIndex: 1, + destinations: const <NavigationRailDestination>[ + NavigationRailDestination( + icon: Icon(Icons.favorite_border), + selectedIcon: Icon(Icons.favorite), + label: Text('ABCDEFGHIJKLMNOPQRSTUVWXYZ'), + ), + NavigationRailDestination( + icon: Icon(Icons.bookmark_border), + selectedIcon: Icon(Icons.bookmark), + label: Text('ABC'), + ), + ], + labelType: NavigationRailLabelType.all, + ), + ); + + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(); + await gesture.moveTo(tester.getCenter(find.byIcon(Icons.favorite_border))); + await tester.pumpAndSettle(); + + final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); + + // Default values from M3 specification. + const double indicatorHeight = 32.0; + const double destinationWidth = 72.0; + const double destinationHorizontalPadding = 8.0; + const double indicatorWidth = destinationWidth - 2 * destinationHorizontalPadding; // 56.0 + + // The navigation rail width is larger than default because of the first destination long label. + final double railWidth = tester.getSize(find.byType(NavigationRail)).width; + + // Expected indicator position. + final double indicatorLeft = (railWidth - indicatorWidth) / 2; + final double indicatorRight = (railWidth + indicatorWidth) / 2; + final Rect indicatorRect = Rect.fromLTRB(indicatorLeft, 0.0, indicatorRight, indicatorHeight); + final Rect includedRect = indicatorRect; + final Rect excludedRect = includedRect.inflate(10); + + expect( + inkFeatures, + paints + ..clipPath( + pathMatcher: isPathThat( + includes: <Offset>[ + includedRect.centerLeft, + includedRect.topCenter, + includedRect.centerRight, + includedRect.bottomCenter, + ], + excludes: <Offset>[ + excludedRect.centerLeft, + excludedRect.topCenter, + excludedRect.centerRight, + excludedRect.bottomCenter, + ], + ), + ) + ..rect( + rect: indicatorRect, + color: const Color(0x0a6750a4), + ) + ..rrect( + rrect: RRect.fromLTRBR(indicatorLeft, 72.0, indicatorRight, 104.0, const Radius.circular(16)), + color: const Color(0xffe8def8), + ), + ); + }); + testWidgets('NavigationRail indicator scale transform', (WidgetTester tester) async { int selectedIndex = 0; Future<void> buildWidget() async { @@ -3136,8 +3211,55 @@ void main() { expect(_getIndicatorDecoration(tester)?.shape, shape); }); + testWidgets("Destination's respect their disabled state", (WidgetTester tester) async { + late int selectedIndex; + await _pumpNavigationRail( + tester, + navigationRail: NavigationRail( + selectedIndex: 0, + destinations: const <NavigationRailDestination>[ + NavigationRailDestination( + icon: Icon(Icons.favorite_border), + selectedIcon: Icon(Icons.favorite), + label: Text('Abc'), + ), + NavigationRailDestination( + icon: Icon(Icons.star_border), + selectedIcon: Icon(Icons.star), + label: Text('Bcd'), + ), + NavigationRailDestination( + icon: Icon(Icons.bookmark_border), + selectedIcon: Icon(Icons.bookmark), + label: Text('Cde'), + disabled: true, + ), + ], + onDestinationSelected: (int index) { + selectedIndex = index; + }, + labelType: NavigationRailLabelType.all, + ), + ); + + await tester.tap(find.text('Abc')); + expect(selectedIndex, 0); + + await tester.tap(find.text('Bcd')); + expect(selectedIndex, 1); + + await tester.tap(find.text('Cde')); + expect(selectedIndex, 1); + + // Wait for any pending shader compilation. + tester.pumpAndSettle(); + }); + group('Material 2', () { - // Original Material 2 tests. Remove this group after `useMaterial3` has been deprecated. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. + testWidgets('Renders at the correct default width - [labelType]=none (default)', (WidgetTester tester) async { await _pumpNavigationRail( tester, diff --git a/packages/flutter/test/material/navigation_rail_theme_test.dart b/packages/flutter/test/material/navigation_rail_theme_test.dart index b791bf8fea58b..bf77080fd61f9 100644 --- a/packages/flutter/test/material/navigation_rail_theme_test.dart +++ b/packages/flutter/test/material/navigation_rail_theme_test.dart @@ -13,10 +13,11 @@ void main() { }); testWidgets('Default values are used when no NavigationRail or NavigationRailThemeData properties are specified', (WidgetTester tester) async { + final ThemeData theme = ThemeData.light(useMaterial3: true); // Material 3 defaults await tester.pumpWidget( MaterialApp( - theme: ThemeData.light().copyWith(useMaterial3: true), + theme: theme, home: Scaffold( body: NavigationRail( selectedIndex: 0, @@ -26,21 +27,21 @@ void main() { ), ); - expect(_railMaterial(tester).color, ThemeData().colorScheme.surface); + expect(_railMaterial(tester).color, theme.colorScheme.surface); expect(_railMaterial(tester).elevation, 0); expect(_destinationSize(tester).width, 80.0); expect(_selectedIconTheme(tester).size, 24.0); - expect(_selectedIconTheme(tester).color, ThemeData().colorScheme.onSecondaryContainer); + expect(_selectedIconTheme(tester).color, theme.colorScheme.onSecondaryContainer); expect(_selectedIconTheme(tester).opacity, null); expect(_unselectedIconTheme(tester).size, 24.0); - expect(_unselectedIconTheme(tester).color, ThemeData().colorScheme.onSurface); + expect(_unselectedIconTheme(tester).color, theme.colorScheme.onSurfaceVariant); expect(_unselectedIconTheme(tester).opacity, null); expect(_selectedLabelStyle(tester).fontSize, 14.0); expect(_unselectedLabelStyle(tester).fontSize, 14.0); expect(_destinationsAlign(tester).alignment, Alignment.topCenter); expect(_labelType(tester), NavigationRailLabelType.none); expect(find.byType(NavigationIndicator), findsWidgets); - expect(_indicatorDecoration(tester)?.color, ThemeData().colorScheme.secondaryContainer); + expect(_indicatorDecoration(tester)?.color, theme.colorScheme.secondaryContainer); expect(_indicatorDecoration(tester)?.shape, const StadiumBorder()); final InkResponse inkResponse = tester.allWidgets.firstWhere((Widget object) => object.runtimeType.toString() == '_IndicatorInkWell') as InkResponse; expect(inkResponse.customBorder, const StadiumBorder()); diff --git a/packages/flutter/test/material/outlined_button_test.dart b/packages/flutter/test/material/outlined_button_test.dart index f5e5cd70f18e7..eb25c9401993e 100644 --- a/packages/flutter/test/material/outlined_button_test.dart +++ b/packages/flutter/test/material/outlined_button_test.dart @@ -42,7 +42,7 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, material3 ? null : const Color(0xff000000)); + expect(material.shadowColor, material3 ? Colors.transparent : const Color(0xff000000)); expect(material.shape, material3 ? StadiumBorder(side: BorderSide(color: colorScheme.outline)) @@ -82,7 +82,7 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, material3 ? null : const Color(0xff000000)); + expect(material.shadowColor, material3 ? Colors.transparent : const Color(0xff000000)); expect(material.shape, material3 ? StadiumBorder(side: BorderSide(color: colorScheme.outline)) @@ -125,7 +125,7 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, material3 ? null : const Color(0xff000000)); + expect(material.shadowColor, material3 ? Colors.transparent : const Color(0xff000000)); expect(material.shape, material3 ? StadiumBorder(side: BorderSide(color: colorScheme.outline)) @@ -160,7 +160,7 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, material3 ? null : const Color(0xff000000)); + expect(material.shadowColor, material3 ? Colors.transparent : const Color(0xff000000)); expect(material.shape, material3 ? StadiumBorder(side: BorderSide(color: colorScheme.onSurface.withOpacity(0.12))) @@ -966,18 +966,21 @@ void main() { testWidgets('OutlinedButton contributes semantics', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: OutlinedButton( - style: const ButtonStyle( - // Specifying minimumSize to mimic the original minimumSize for - // RaisedButton so that the corresponding button size matches - // the original version of this test. - minimumSize: MaterialStatePropertyAll<Size>(Size(88, 36)), + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: OutlinedButton( + style: const ButtonStyle( + // Specifying minimumSize to mimic the original minimumSize for + // RaisedButton so that the corresponding button size matches + // the original version of this test. + minimumSize: MaterialStatePropertyAll<Size>(Size(88, 36)), + ), + onPressed: () {}, + child: const Text('ABC'), ), - onPressed: () {}, - child: const Text('ABC'), ), ), ), @@ -1011,8 +1014,7 @@ void main() { testWidgets('OutlinedButton scales textScaleFactor', (WidgetTester tester) async { await tester.pumpWidget( Theme( - // Force Material 2 typography. - data: ThemeData(textTheme: Typography.englishLike2014), + data: ThemeData(useMaterial3: false), child: Directionality( textDirection: TextDirection.ltr, child: MediaQuery( @@ -1041,11 +1043,11 @@ void main() { await tester.pumpWidget( Theme( // Force Material 2 typography. - data: ThemeData(textTheme: Typography.englishLike2014), + data: ThemeData(useMaterial3: false), child: Directionality( textDirection: TextDirection.ltr, child: MediaQuery( - data: const MediaQueryData(textScaleFactor: 1.3), + data: const MediaQueryData(textScaleFactor: 1.25), child: Center( child: OutlinedButton( style: const ButtonStyle( @@ -1064,13 +1066,15 @@ void main() { ); expect(tester.getSize(find.byType(OutlinedButton)), equals(const Size(88.0, 48.0))); - expect(tester.getSize(find.byType(Text)), const Size(55.0, 18.0)); + expect(tester.getSize(find.byType(Text)), const Size( + bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 52.5 : 53.0, + 18.0, + )); // Set text scale large enough to expand text and button. await tester.pumpWidget( Theme( - // Force Material 2 typography. - data: ThemeData(textTheme: Typography.englishLike2014), + data: ThemeData(useMaterial3: false), child: Directionality( textDirection: TextDirection.ltr, child: MediaQuery( @@ -1128,7 +1132,7 @@ void main() { Future<void> buildTest(VisualDensity visualDensity, {bool useText = false}) async { return tester.pumpWidget( MaterialApp( - theme: ThemeData(textTheme: Typography.englishLike2014), + theme: ThemeData(useMaterial3: false), home: Directionality( textDirection: TextDirection.rtl, child: Center( @@ -1258,10 +1262,7 @@ void main() { await tester.pumpWidget( MaterialApp( theme: ThemeData( - colorScheme: const ColorScheme.light(), - // Force Material 2 defaults for the typography and size - // default values as the test was designed against these settings. - textTheme: Typography.englishLike2014, + useMaterial3: false, outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom(minimumSize: const Size(64, 36)), ), @@ -1404,7 +1405,7 @@ void main() { testWidgets('Override OutlinedButton default padding', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: const ColorScheme.light()), + theme: ThemeData(useMaterial3: false), home: Builder( builder: (BuildContext context) { return MediaQuery( @@ -1674,7 +1675,7 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: ThemeData(textTheme: Typography.englishLike2014), + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( child: Column( diff --git a/packages/flutter/test/material/outlined_button_theme_test.dart b/packages/flutter/test/material/outlined_button_theme_test.dart index fb06b126b9032..93e10033f7549 100644 --- a/packages/flutter/test/material/outlined_button_theme_test.dart +++ b/packages/flutter/test/material/outlined_button_theme_test.dart @@ -12,11 +12,52 @@ void main() { expect(identical(OutlinedButtonThemeData.lerp(data, data, 0.5), data), true); }); - testWidgets('Passing no OutlinedButtonTheme returns defaults', (WidgetTester tester) async { + testWidgets('Material3: Passing no OutlinedButtonTheme returns defaults', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme), + theme: ThemeData.from(useMaterial3: true, colorScheme: colorScheme), + home: Scaffold( + body: Center( + child: OutlinedButton( + onPressed: () { }, + child: const Text('button'), + ), + ), + ), + ), + ); + + final Finder buttonMaterial = find.descendant( + of: find.byType(OutlinedButton), + matching: find.byType(Material), + ); + + final Material material = tester.widget<Material>(buttonMaterial); + expect(material.animationDuration, const Duration(milliseconds: 200)); + expect(material.borderRadius, null); + expect(material.color, Colors.transparent); + expect(material.elevation, 0.0); + expect(material.shadowColor, Colors.transparent); + + expect(material.shape, isInstanceOf<StadiumBorder>()); + final StadiumBorder materialShape = material.shape! as StadiumBorder; + expect(materialShape.side, BorderSide(color: colorScheme.outline)); + + expect(material.textStyle!.color, colorScheme.primary); + expect(material.textStyle!.fontFamily, 'Roboto'); + expect(material.textStyle!.fontSize, 14); + expect(material.textStyle!.fontWeight, FontWeight.w500); + + final Align align = tester.firstWidget<Align>(find.ancestor(of: find.text('button'), matching: find.byType(Align))); + expect(align.alignment, Alignment.center); + }); + + testWidgets('Material2: Passing no OutlinedButtonTheme returns defaults', (WidgetTester tester) async { + const ColorScheme colorScheme = ColorScheme.light(); + await tester.pumpWidget( + MaterialApp( + theme: ThemeData.from(useMaterial3: false, colorScheme: colorScheme), home: Scaffold( body: Center( child: OutlinedButton( @@ -194,14 +235,85 @@ void main() { }); }); - testWidgets('Theme shadowColor', (WidgetTester tester) async { + testWidgets('Material3: Theme shadowColor', (WidgetTester tester) async { + const ColorScheme colorScheme = ColorScheme.light(); + const Color shadowColor = Color(0xff000001); + const Color overriddenColor = Color(0xff000002); + + Widget buildFrame({ Color? overallShadowColor, Color? themeShadowColor, Color? shadowColor }) { + return MaterialApp( + theme: ThemeData.from( + useMaterial3: true, + colorScheme: colorScheme.copyWith(shadow: overallShadowColor), + ), + home: Scaffold( + body: Center( + child: OutlinedButtonTheme( + data: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + shadowColor: themeShadowColor, + ), + ), + child: Builder( + builder: (BuildContext context) { + return OutlinedButton( + style: OutlinedButton.styleFrom( + shadowColor: shadowColor, + ), + onPressed: () { }, + child: const Text('button'), + ); + }, + ), + ), + ), + ), + ); + } + + final Finder buttonMaterialFinder = find.descendant( + of: find.byType(OutlinedButton), + matching: find.byType(Material), + ); + + await tester.pumpWidget(buildFrame()); + Material material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, Colors.transparent); + + await tester.pumpWidget(buildFrame(overallShadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, Colors.transparent); + + await tester.pumpWidget(buildFrame(themeShadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, shadowColor); + + await tester.pumpWidget(buildFrame(shadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, shadowColor); + + await tester.pumpWidget(buildFrame(overallShadowColor: overriddenColor, themeShadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, shadowColor); + + await tester.pumpWidget(buildFrame(themeShadowColor: overriddenColor, shadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, shadowColor); + }); + + testWidgets('Material2: Theme shadowColor', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); const Color shadowColor = Color(0xff000001); const Color overriddenColor = Color(0xff000002); Widget buildFrame({ Color? overallShadowColor, Color? themeShadowColor, Color? shadowColor }) { return MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme).copyWith( + theme: ThemeData.from(useMaterial3: false, colorScheme: colorScheme).copyWith( shadowColor: overallShadowColor, ), home: Scaffold( diff --git a/packages/flutter/test/material/page_test.dart b/packages/flutter/test/material/page_test.dart index 531025084eafa..b0486a9cfac82 100644 --- a/packages/flutter/test/material/page_test.dart +++ b/packages/flutter/test/material/page_test.dart @@ -252,6 +252,7 @@ void main() { RepaintBoundary( key: key, child: MaterialApp( + theme: ThemeData(useMaterial3: false), onGenerateRoute: (RouteSettings settings) { return MaterialPageRoute<void>( builder: (BuildContext context) { diff --git a/packages/flutter/test/material/persistent_bottom_sheet_test.dart b/packages/flutter/test/material/persistent_bottom_sheet_test.dart index 670a3d11050dc..8c51e01ef030a 100644 --- a/packages/flutter/test/material/persistent_bottom_sheet_test.dart +++ b/packages/flutter/test/material/persistent_bottom_sheet_test.dart @@ -534,6 +534,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: const Placeholder(), bottomSheet: Container( diff --git a/packages/flutter/test/material/popup_menu_test.dart b/packages/flutter/test/material/popup_menu_test.dart index a03728524272b..13c24d0b8f028 100644 --- a/packages/flutter/test/material/popup_menu_test.dart +++ b/packages/flutter/test/material/popup_menu_test.dart @@ -1560,6 +1560,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( child: PopupMenuButton<String>( @@ -1774,6 +1775,7 @@ void main() { double fontSize = 24, }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), builder: (BuildContext context, Widget? child) { return Directionality( textDirection: textDirection, @@ -2363,6 +2365,7 @@ void main() { Widget buildFrame(double width, double height) { return MaterialApp( + theme: ThemeData(useMaterial3: false), builder: (BuildContext context, Widget? child) { return MediaQuery( data: const MediaQueryData( @@ -2422,6 +2425,7 @@ void main() { Widget buildFrame(double width, double height) { return MaterialApp( + theme: ThemeData(useMaterial3: false), builder: (BuildContext context, Widget? child) { return MediaQuery( data: const MediaQueryData( @@ -2710,6 +2714,7 @@ void main() { Future<void> buildFrameWithoutChild({double? splashRadius}) { return tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( child: PopupMenuButton<String>( @@ -3260,6 +3265,48 @@ void main() { final Offset menuTopLeft = tester.getTopLeft(find.bySemanticsLabel('Popup menu')); expect(childBottomLeft, menuTopLeft); }); + + testWidgets('PopupmenuItem onTap should be calling after Navigator.pop', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: AppBar( + actions: <Widget>[ + PopupMenuButton<int>( + itemBuilder: (BuildContext context) => <PopupMenuItem<int>>[ + PopupMenuItem<int>( + onTap: () { + showModalBottomSheet( + context: context, + builder: (BuildContext context) { + return const SizedBox( + height: 200.0, + child: Center(child: Text('ModalBottomSheet')), + ); + }, + ); + }, + value: 10, + child: const Text('ACTION'), + ), + ], + ), + ], + ), + ), + ), + ); + + await tester.tap(find.byType(PopupMenuButton<int>)); + await tester.pumpAndSettle(); + + await tester.tap(find.text('ACTION')); + await tester.pumpAndSettle(); + + // Verify that the ModalBottomSheet is displayed + final Finder modalBottomSheet = find.text('ModalBottomSheet'); + expect(modalBottomSheet, findsOneWidget); + }); } class TestApp extends StatelessWidget { diff --git a/packages/flutter/test/material/popup_menu_theme_test.dart b/packages/flutter/test/material/popup_menu_theme_test.dart index f625ad5d152ee..4b37dedb96a3c 100644 --- a/packages/flutter/test/material/popup_menu_theme_test.dart +++ b/packages/flutter/test/material/popup_menu_theme_test.dart @@ -400,15 +400,16 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('Passing no PopupMenuThemeData returns defaults', (WidgetTester tester) async { final Key popupButtonKey = UniqueKey(); final Key popupButtonApp = UniqueKey(); final Key enabledPopupItemKey = UniqueKey(); final Key disabledPopupItemKey = UniqueKey(); - final ThemeData theme = ThemeData(); + final ThemeData theme = ThemeData(useMaterial3: false); await tester.pumpWidget(MaterialApp( theme: theme, @@ -510,7 +511,7 @@ void main() { final Key disabledPopupItemKey = UniqueKey(); await tester.pumpWidget(MaterialApp( - theme: ThemeData(popupMenuTheme: popupMenuTheme), + theme: ThemeData(popupMenuTheme: popupMenuTheme, useMaterial3: false), key: popupButtonApp, home: Material( child: Column( diff --git a/packages/flutter/test/material/progress_indicator_test.dart b/packages/flutter/test/material/progress_indicator_test.dart index fdf268f4aa956..c68e3507a8975 100644 --- a/packages/flutter/test/material/progress_indicator_test.dart +++ b/packages/flutter/test/material/progress_indicator_test.dart @@ -723,7 +723,10 @@ void main() { final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(50, 50)); await tester.pumpFrames(animationSheet.record( - const _RefreshProgressIndicatorGolden(), + Theme( + data: ThemeData(useMaterial3: false), + child: const _RefreshProgressIndicatorGolden() + ), ), const Duration(seconds: 3)); await expectLater( @@ -1001,11 +1004,14 @@ void main() { final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(40, 40)); await tester.pumpFrames(animationSheet.record( - const Directionality( - textDirection: TextDirection.ltr, - child: Padding( - padding: EdgeInsets.all(4), - child: CircularProgressIndicator(), + Theme( + data: ThemeData(useMaterial3: false), + child: const Directionality( + textDirection: TextDirection.ltr, + child: Padding( + padding: EdgeInsets.all(4), + child: CircularProgressIndicator(), + ), ), ), ), const Duration(seconds: 2)); diff --git a/packages/flutter/test/material/radio_list_tile_test.dart b/packages/flutter/test/material/radio_list_tile_test.dart index c852c9fd78108..9e0cba869a568 100644 --- a/packages/flutter/test/material/radio_list_tile_test.dart +++ b/packages/flutter/test/material/radio_list_tile_test.dart @@ -1016,20 +1016,24 @@ void main() { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; int? groupValue = 0; final Color? hoverColor = Colors.orange[500]; + final ThemeData theme = ThemeData(useMaterial3: true); Widget buildApp({bool enabled = true}) { return wrap( - child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { - return RadioListTile<int>( - value: 0, - onChanged: enabled ? (int? newValue) { - setState(() { - groupValue = newValue; - }); - } : null, - hoverColor: hoverColor, - groupValue: groupValue, - ); - }), + child: MaterialApp( + theme: theme, + home: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { + return RadioListTile<int>( + value: 0, + onChanged: enabled ? (int? newValue) { + setState(() { + groupValue = newValue; + }); + } : null, + hoverColor: hoverColor, + groupValue: groupValue, + ); + }), + ), ); } await tester.pumpWidget(buildApp()); @@ -1040,8 +1044,8 @@ void main() { Material.of(tester.element(find.byType(Radio<int>))), paints ..rect() - ..circle(color: const Color(0xff2196f3)) - ..circle(color: const Color(0xff2196f3)), + ..circle(color: theme.colorScheme.primary) + ..circle(color: theme.colorScheme.primary), ); // Start hovering @@ -1069,8 +1073,8 @@ void main() { Material.of(tester.element(find.byType(Radio<int>))), paints ..rect() - ..circle(color: const Color(0x61000000)) - ..circle(color: const Color(0x61000000)), + ..circle(color: theme.colorScheme.onSurface.withOpacity(0.38)) + ..circle(color: theme.colorScheme.onSurface.withOpacity(0.38)), ); }); @@ -1097,14 +1101,17 @@ void main() { } Widget buildRadio({bool active = false, bool useOverlay = true}) { - return wrap( - child: RadioListTile<bool>( - value: active, - groupValue: true, - onChanged: (_) { }, - fillColor: const MaterialStatePropertyAll<Color>(fillColor), - overlayColor: useOverlay ? MaterialStateProperty.resolveWith(getOverlayColor) : null, - hoverColor: hoverColor, + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: Material( + child: RadioListTile<bool>( + value: active, + groupValue: true, + onChanged: (_) { }, + fillColor: const MaterialStatePropertyAll<Color>(fillColor), + overlayColor: useOverlay ? MaterialStateProperty.resolveWith(getOverlayColor) : null, + hoverColor: hoverColor, + ), ), ); } @@ -1115,10 +1122,12 @@ void main() { expect( Material.of(tester.element(find.byType(Radio<bool>))), - paints..circle() + paints + ..rect(color: const Color(0x00000000)) + ..rect(color: const Color(0x66bcbcbc)) ..circle( color: fillColor.withAlpha(kRadialReactionAlpha), - radius: 20, + radius: 20.0, ), reason: 'Default inactive pressed Radio should have overlay color from fillColor', ); @@ -1129,10 +1138,12 @@ void main() { expect( Material.of(tester.element(find.byType(Radio<bool>))), - paints..circle() + paints + ..rect(color: const Color(0x00000000)) + ..rect(color: const Color(0x66bcbcbc)) ..circle( color: fillColor.withAlpha(kRadialReactionAlpha), - radius: 20, + radius: 20.0, ), reason: 'Default active pressed Radio should have overlay color from fillColor', ); @@ -1143,10 +1154,12 @@ void main() { expect( Material.of(tester.element(find.byType(Radio<bool>))), - paints..circle() + paints + ..rect(color: const Color(0x00000000)) + ..rect(color: const Color(0x66bcbcbc)) ..circle( color: inactivePressedOverlayColor, - radius: 20, + radius: 20.0, ), reason: 'Inactive pressed Radio should have overlay color: $inactivePressedOverlayColor', ); @@ -1157,15 +1170,17 @@ void main() { expect( Material.of(tester.element(find.byType(Radio<bool>))), - paints..circle() + paints + ..rect(color: const Color(0x00000000)) + ..rect(color: const Color(0x66bcbcbc)) ..circle( color: activePressedOverlayColor, - radius: 20, + radius: 20.0, ), reason: 'Active pressed Radio should have overlay color: $activePressedOverlayColor', ); - // Start hovering + // Start hovering. final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); await gesture.addPointer(); await gesture.moveTo(tester.getCenter(find.byType(Radio<bool>))); @@ -1178,9 +1193,11 @@ void main() { expect( Material.of(tester.element(find.byType(Radio<bool>))), paints + ..rect(color: const Color(0x00000000)) + ..rect(color: const Color(0x0a000000)) ..circle( color: hoverOverlayColor, - radius: 20, + radius: 20.0, ), reason: 'Hovered Radio should use overlay color $hoverOverlayColor over $hoverColor', ); @@ -1316,4 +1333,179 @@ void main() { expect(feedback.hapticCount, 0); }); }); + + group('Material 2', () { + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. + + testWidgets('RadioListTile respects overlayColor in active/pressed/hovered states', (WidgetTester tester) async { + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + + const Color fillColor = Color(0xFF000000); + const Color activePressedOverlayColor = Color(0xFF000001); + const Color inactivePressedOverlayColor = Color(0xFF000002); + const Color hoverOverlayColor = Color(0xFF000003); + const Color hoverColor = Color(0xFF000005); + + Color? getOverlayColor(Set<MaterialState> states) { + if (states.contains(MaterialState.pressed)) { + if (states.contains(MaterialState.selected)) { + return activePressedOverlayColor; + } + return inactivePressedOverlayColor; + } + if (states.contains(MaterialState.hovered)) { + return hoverOverlayColor; + } + return null; + } + + Widget buildRadio({bool active = false, bool useOverlay = true}) { + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: RadioListTile<bool>( + value: active, + groupValue: true, + onChanged: (_) { }, + fillColor: const MaterialStatePropertyAll<Color>(fillColor), + overlayColor: useOverlay ? MaterialStateProperty.resolveWith(getOverlayColor) : null, + hoverColor: hoverColor, + ), + ), + ); + } + + await tester.pumpWidget(buildRadio(useOverlay: false)); + await tester.press(find.byType(Radio<bool>)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Radio<bool>))), + paints + ..circle() + ..circle( + color: fillColor.withAlpha(kRadialReactionAlpha), + radius: 20, + ), + reason: 'Default inactive pressed Radio should have overlay color from fillColor', + ); + + await tester.pumpWidget(buildRadio(active: true, useOverlay: false)); + await tester.press(find.byType(Radio<bool>)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Radio<bool>))), + paints + ..circle() + ..circle( + color: fillColor.withAlpha(kRadialReactionAlpha), + radius: 20, + ), + reason: 'Default active pressed Radio should have overlay color from fillColor', + ); + + await tester.pumpWidget(buildRadio()); + await tester.press(find.byType(Radio<bool>)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Radio<bool>))), + paints + ..circle() + ..circle( + color: inactivePressedOverlayColor, + radius: 20, + ), + reason: 'Inactive pressed Radio should have overlay color: $inactivePressedOverlayColor', + ); + + // Start hovering. + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(); + await gesture.moveTo(tester.getCenter(find.byType(Radio<bool>))); + await tester.pumpAndSettle(); + + await tester.pumpWidget(Container()); + await tester.pumpWidget(buildRadio()); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Radio<bool>))), + paints + ..circle( + color: hoverOverlayColor, + radius: 20, + ), + reason: 'Hovered Radio should use overlay color $hoverOverlayColor over $hoverColor', + ); + }); + + testWidgets('RadioListTile respects hoverColor', (WidgetTester tester) async { + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + int? groupValue = 0; + final Color? hoverColor = Colors.orange[500]; + Widget buildApp({bool enabled = true}) { + return wrap( + child: MaterialApp( + theme: ThemeData(useMaterial3: false), + home: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { + return RadioListTile<int>( + value: 0, + onChanged: enabled ? (int? newValue) { + setState(() { + groupValue = newValue; + }); + } : null, + hoverColor: hoverColor, + groupValue: groupValue, + ); + }), + ), + ); + } + await tester.pumpWidget(buildApp()); + + await tester.pump(); + await tester.pumpAndSettle(); + expect( + Material.of(tester.element(find.byType(Radio<int>))), + paints + ..rect() + ..circle(color:const Color(0xff2196f3)) + ..circle(color:const Color(0xff2196f3)), + ); + + // Start hovering + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.moveTo(tester.getCenter(find.byType(Radio<int>))); + + // Check when the radio isn't selected. + groupValue = 1; + await tester.pumpWidget(buildApp()); + await tester.pump(); + await tester.pumpAndSettle(); + expect( + Material.of(tester.element(find.byType(Radio<int>))), + paints + ..rect() + ..circle(color: hoverColor) + ); + + // Check when the radio is selected, but disabled. + groupValue = 0; + await tester.pumpWidget(buildApp(enabled: false)); + await tester.pump(); + await tester.pumpAndSettle(); + expect( + Material.of(tester.element(find.byType(Radio<int>))), + paints + ..rect() + ..circle(color:const Color(0x61000000)) + ..circle(color:const Color(0x61000000)), + ); + }); + }); } diff --git a/packages/flutter/test/material/radio_test.dart b/packages/flutter/test/material/radio_test.dart index 1c07c837da909..9f4610c12f9b3 100644 --- a/packages/flutter/test/material/radio_test.dart +++ b/packages/flutter/test/material/radio_test.dart @@ -398,7 +398,7 @@ void main() { tester.binding.defaultBinaryMessenger.setMockDecodedMessageHandler<dynamic>(SystemChannels.accessibility, null); }); - testWidgets('Radio ink ripple is displayed correctly - M2', (WidgetTester tester) async { + testWidgets('Material2 - Radio ink ripple is displayed correctly', (WidgetTester tester) async { final Key painterKey = UniqueKey(); const Key radioKey = Key('radio'); @@ -428,7 +428,41 @@ void main() { await tester.pumpAndSettle(); await expectLater( find.byKey(painterKey), - matchesGoldenFile('radio.ink_ripple.png'), + matchesGoldenFile('m2_radio.ink_ripple.png'), + ); + }); + + testWidgets('Material3 - Radio ink ripple is displayed correctly', (WidgetTester tester) async { + final Key painterKey = UniqueKey(); + const Key radioKey = Key('radio'); + + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: true), + home: Scaffold( + body: RepaintBoundary( + key: painterKey, + child: Center( + child: Container( + width: 100, + height: 100, + color: Colors.white, + child: Radio<int>( + key: radioKey, + value: 1, + groupValue: 1, + onChanged: (int? value) { }, + ), + ), + ), + ), + ), + )); + + await tester.press(find.byKey(radioKey)); + await tester.pumpAndSettle(); + await expectLater( + find.byKey(painterKey), + matchesGoldenFile('m3_radio.ink_ripple.png'), ); }); @@ -469,14 +503,14 @@ void main() { ); }); - testWidgets('Radio is focusable and has correct focus color', (WidgetTester tester) async { + testWidgets('Material2 - Radio is focusable and has correct focus color', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'Radio'); tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; int? groupValue = 0; const Key radioKey = Key('radio'); Widget buildApp({bool enabled = true}) { return MaterialApp( - theme: theme, + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -511,9 +545,9 @@ void main() { Material.of(tester.element(find.byKey(radioKey))), paints ..rect( - color: const Color(0xffffffff), - rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), - ) + color: const Color(0xffffffff), + rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), + ) ..circle(color: Colors.orange[500]) ..circle(color: const Color(0xff2196f3)) ..circle(color: const Color(0xff2196f3)), @@ -526,15 +560,13 @@ void main() { expect(focusNode.hasPrimaryFocus, isTrue); expect( Material.of(tester.element(find.byKey(radioKey))), - theme.useMaterial3 - ? (paints..rect()..circle(color: Colors.orange[500])..circle(color: theme.colorScheme.onSurface)) - : (paints - ..rect( - color: const Color(0xffffffff), - rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), - ) - ..circle(color: Colors.orange[500]) - ..circle(color: const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0)), + paints + ..rect( + color: const Color(0xffffffff), + rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), + ) + ..circle(color: Colors.orange[500]) + ..circle(color: const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0), ); // Check when the radio is selected, but disabled. @@ -546,21 +578,99 @@ void main() { Material.of(tester.element(find.byKey(radioKey))), paints ..rect( - color: const Color(0xffffffff), - rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), - ) + color: const Color(0xffffffff), + rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), + ) ..circle(color: const Color(0x61000000)) ..circle(color: const Color(0x61000000)), ); }); - testWidgets('Radio can be hovered and has correct hover color', (WidgetTester tester) async { + testWidgets('Material3 - Radio is focusable and has correct focus color', (WidgetTester tester) async { + final FocusNode focusNode = FocusNode(debugLabel: 'Radio'); tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; int? groupValue = 0; const Key radioKey = Key('radio'); + final ThemeData theme = ThemeData(useMaterial3: true); Widget buildApp({bool enabled = true}) { return MaterialApp( theme: theme, + home: Material( + child: Center( + child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { + return Container( + width: 100, + height: 100, + color: Colors.white, + child: Radio<int>( + key: radioKey, + value: 0, + onChanged: enabled ? (int? newValue) { + setState(() { + groupValue = newValue; + }); + } : null, + focusColor: Colors.orange[500], + autofocus: true, + focusNode: focusNode, + groupValue: groupValue, + ), + ); + }), + ), + ), + ); + } + await tester.pumpWidget(buildApp()); + + await tester.pumpAndSettle(); + expect(focusNode.hasPrimaryFocus, isTrue); + expect( + Material.of(tester.element(find.byKey(radioKey))), + paints + ..rect( + color: const Color(0xffffffff), + rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), + ) + ..circle(color: Colors.orange[500]) + ..circle(color: theme.colorScheme.primary) + ..circle(color: theme.colorScheme.primary), + ); + + // Check when the radio isn't selected. + groupValue = 1; + await tester.pumpWidget(buildApp()); + await tester.pumpAndSettle(); + expect(focusNode.hasPrimaryFocus, isTrue); + expect( + Material.of(tester.element(find.byKey(radioKey))), + paints..rect()..circle(color: Colors.orange[500])..circle(color: theme.colorScheme.onSurface), + ); + + // Check when the radio is selected, but disabled. + groupValue = 0; + await tester.pumpWidget(buildApp(enabled: false)); + await tester.pumpAndSettle(); + expect(focusNode.hasPrimaryFocus, isFalse); + expect( + Material.of(tester.element(find.byKey(radioKey))), + paints + ..rect( + color: const Color(0xffffffff), + rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), + ) + ..circle(color: theme.colorScheme.onSurface.withOpacity(0.38)) + ..circle(color: theme.colorScheme.onSurface.withOpacity(0.38)), + ); + }); + + testWidgets('Material2 - Radio can be hovered and has correct hover color', (WidgetTester tester) async { + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + int? groupValue = 0; + const Key radioKey = Key('radio'); + Widget buildApp({bool enabled = true}) { + return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -617,7 +727,7 @@ void main() { rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), ) ..circle(color: Colors.orange[500]) - ..circle(color: theme.useMaterial3 ? theme.colorScheme.onSurface : const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0), + ..circle(color: const Color(0x8a000000), style: PaintingStyle.stroke, strokeWidth: 2.0), ); // Check when the radio is selected, but disabled. @@ -637,6 +747,90 @@ void main() { ); }); + testWidgets('Material3 - Radio can be hovered and has correct hover color', (WidgetTester tester) async { + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + int? groupValue = 0; + const Key radioKey = Key('radio'); + final ThemeData theme = ThemeData(useMaterial3: true); + Widget buildApp({bool enabled = true}) { + return MaterialApp( + theme: theme, + home: Material( + child: Center( + child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { + return Container( + width: 100, + height: 100, + color: Colors.white, + child: Radio<int>( + key: radioKey, + value: 0, + onChanged: enabled ? (int? newValue) { + setState(() { + groupValue = newValue; + }); + } : null, + hoverColor: Colors.orange[500], + groupValue: groupValue, + ), + ); + }), + ), + ), + ); + } + await tester.pumpWidget(buildApp()); + + await tester.pump(); + await tester.pumpAndSettle(); + expect( + Material.of(tester.element(find.byKey(radioKey))), + paints + ..rect( + color: const Color(0xffffffff), + rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), + ) + ..circle(color: theme.colorScheme.primary) + ..circle(color: theme.colorScheme.primary), + ); + + // Start hovering + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.moveTo(tester.getCenter(find.byKey(radioKey))); + + // Check when the radio isn't selected. + groupValue = 1; + await tester.pumpWidget(buildApp()); + await tester.pump(); + await tester.pumpAndSettle(); + expect( + Material.of(tester.element(find.byKey(radioKey))), + paints + ..rect( + color: const Color(0xffffffff), + rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), + ) + ..circle(color: Colors.orange[500]) + ..circle(color: theme.colorScheme.onSurface, style: PaintingStyle.stroke, strokeWidth: 2.0), + ); + + // Check when the radio is selected, but disabled. + groupValue = 0; + await tester.pumpWidget(buildApp(enabled: false)); + await tester.pump(); + await tester.pumpAndSettle(); + expect( + Material.of(tester.element(find.byKey(radioKey))), + paints + ..rect( + color: const Color(0xffffffff), + rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), + ) + ..circle(color: theme.colorScheme.onSurface.withOpacity(0.38)) + ..circle(color: theme.colorScheme.onSurface.withOpacity(0.38)), + ); + }); + testWidgets('Radio can be controlled by keyboard shortcuts', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; int? groupValue = 1; @@ -949,7 +1143,90 @@ void main() { ); }); - testWidgets('Radio fill color resolves in hovered/focused states', (WidgetTester tester) async { + testWidgets('Material2 - Radio fill color resolves in hovered/focused states', (WidgetTester tester) async { + final FocusNode focusNode = FocusNode(debugLabel: 'radio'); + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + const Color hoveredFillColor = Color(0xFF000001); + const Color focusedFillColor = Color(0xFF000002); + + Color getFillColor(Set<MaterialState> states) { + if (states.contains(MaterialState.hovered)) { + return hoveredFillColor; + } + if (states.contains(MaterialState.focused)) { + return focusedFillColor; + } + return Colors.transparent; + } + + final MaterialStateProperty<Color> fillColor = MaterialStateColor.resolveWith(getFillColor); + + int? groupValue = 0; + const Key radioKey = Key('radio'); + final ThemeData theme = ThemeData(useMaterial3: false); + Widget buildApp() { + return MaterialApp( + theme: theme, + home: Material( + child: Center( + child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { + return Container( + width: 100, + height: 100, + color: Colors.white, + child: Radio<int>( + autofocus: true, + focusNode: focusNode, + key: radioKey, + value: 0, + fillColor: fillColor, + onChanged: (int? newValue) { + setState(() { + groupValue = newValue; + }); + }, + groupValue: groupValue, + ), + ); + }), + ), + ), + ); + } + + await tester.pumpWidget(buildApp()); + await tester.pumpAndSettle(); + expect(focusNode.hasPrimaryFocus, isTrue); + expect( + Material.of(tester.element(find.byKey(radioKey))), + paints + ..rect( + color: const Color(0xffffffff), + rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), + ) + ..circle(color: Colors.black12) + ..circle(color: focusedFillColor), + ); + + // Start hovering + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(); + await gesture.moveTo(tester.getCenter(find.byKey(radioKey))); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byKey(radioKey))), + paints + ..rect( + color: const Color(0xffffffff), + rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), + ) + ..circle(color: theme.hoverColor) + ..circle(color: hoveredFillColor), + ); + }); + + testWidgets('Material3 - Radio fill color resolves in hovered/focused states', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'radio'); tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const Color hoveredFillColor = Color(0xFF000001); @@ -966,10 +1243,11 @@ void main() { } final MaterialStateProperty<Color> fillColor = - MaterialStateColor.resolveWith(getFillColor); + MaterialStateColor.resolveWith(getFillColor); int? groupValue = 0; const Key radioKey = Key('radio'); + final ThemeData theme = ThemeData(useMaterial3: true); Widget buildApp() { return MaterialApp( theme: theme, @@ -1005,15 +1283,7 @@ void main() { expect(focusNode.hasPrimaryFocus, isTrue); expect( Material.of(tester.element(find.byKey(radioKey))), - theme.useMaterial3 - ? (paints..rect()..circle(color: theme.colorScheme.primary.withOpacity(0.12))..circle(color: focusedFillColor)) - : (paints - ..rect( - color: const Color(0xffffffff), - rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), - ) - ..circle(color: Colors.black12) - ..circle(color: focusedFillColor)), + paints..rect()..circle(color: theme.colorScheme.primary.withOpacity(0.12))..circle(color: focusedFillColor), ); // Start hovering @@ -1026,10 +1296,10 @@ void main() { Material.of(tester.element(find.byKey(radioKey))), paints ..rect( - color: const Color(0xffffffff), - rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), - ) - ..circle(color: theme.useMaterial3 ? theme.colorScheme.primary.withOpacity(0.08) : theme.hoverColor) + color: const Color(0xffffffff), + rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), + ) + ..circle(color: theme.colorScheme.primary.withOpacity(0.08)) ..circle(color: hoveredFillColor), ); }); @@ -1261,10 +1531,10 @@ void main() { expect(find.text(tapTooltip), findsOneWidget); }); - testWidgets('Radio button default colors', (WidgetTester tester) async { + testWidgets('Material2 - Radio button default colors', (WidgetTester tester) async { Widget buildRadio({bool enabled = true, bool selected = true}) { return MaterialApp( - theme: theme, + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Radio<bool>( value: true, @@ -1281,8 +1551,8 @@ void main() { expect( Material.of(tester.element(find.byType(Radio<bool>))), paints - ..circle(color: const Color(0xFF2196F3)) // Outer circle - blue primary value - ..circle(color: const Color(0xFF2196F3))..restore(), // Inner circle - blue primary value + ..circle(color: const Color(0xFF2196F3)) // Outer circle - primary value + ..circle(color: const Color(0xFF2196F3))..restore(), // Inner circle - primary value ); await tester.pumpWidget(Container()); @@ -1303,19 +1573,64 @@ void main() { expect( Material.of(tester.element(find.byType(Radio<bool>))), - theme.useMaterial3 - ? (paints - ..circle(color: theme.colorScheme.onSurface.withOpacity(0.38))) - : (paints..circle(color: Colors.black38)) + paints..circle(color: Colors.black38) ); }); - testWidgets('Radio button default overlay colors in hover/focus/press states', (WidgetTester tester) async { + testWidgets('Material3 - Radio button default colors', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + Widget buildRadio({bool enabled = true, bool selected = true}) { + return MaterialApp( + theme: theme, + home: Scaffold( + body: Radio<bool>( + value: true, + groupValue: true, + onChanged: enabled ? (_) {} : null, + ), + ) + ); + } + + await tester.pumpWidget(buildRadio()); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Radio<bool>))), + paints + ..circle(color: theme.colorScheme.primary) // Outer circle - primary value + ..circle(color: theme.colorScheme.primary)..restore(), // Inner circle - primary value + ); + + await tester.pumpWidget(Container()); + await tester.pumpWidget(buildRadio(selected: false)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Radio<bool>))), + paints + ..save() + ..circle(color: theme.colorScheme.primary) + ..restore(), + ); + + await tester.pumpWidget(Container()); + await tester.pumpWidget(buildRadio(enabled: false)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Radio<bool>))), + paints + ..circle(color: theme.colorScheme.onSurface.withOpacity(0.38)) + ); + }); + + testWidgets('Material2 - Radio button default overlay colors in hover/focus/press states', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'Radio'); tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + final ThemeData theme = ThemeData(useMaterial3: false); final ColorScheme colors = theme.colorScheme; - final bool material3 = theme.useMaterial3; Widget buildRadio({bool enabled = true, bool focused = false, bool selected = true}) { return MaterialApp( theme: theme, @@ -1336,9 +1651,7 @@ void main() { await tester.pumpAndSettle(); expect( Material.of(tester.element(find.byType(Radio<bool>))), - material3 - ? (paints..circle(color: colors.primary.withOpacity(1))) - : (paints..circle(color: colors.secondary)) + paints..circle(color: colors.secondary) ); // selected radio in pressed state @@ -1348,12 +1661,8 @@ void main() { expect( Material.of(tester.element(find.byType(Radio<bool>))), - paints..circle(color: material3 - ? colors.onSurface.withOpacity(0.12) - : colors.secondary.withAlpha(0x1F)) - ..circle(color: material3 - ? colors.primary.withOpacity(1) - : colors.secondary + paints..circle(color: colors.secondary.withAlpha(0x1F)) + ..circle(color: colors.secondary ) ); @@ -1364,9 +1673,93 @@ void main() { expect( Material.of(tester.element(find.byType(Radio<bool>))), - material3 - ? (paints..circle(color: colors.primary.withOpacity(0.12))..circle(color: colors.onSurface.withOpacity(1))) - : (paints..circle(color: theme.unselectedWidgetColor.withAlpha(0x1F))..circle(color: theme.unselectedWidgetColor)) + paints..circle(color: theme.unselectedWidgetColor.withAlpha(0x1F))..circle(color: theme.unselectedWidgetColor) + ); + + // selected radio in focused state + await tester.pumpWidget(Container()); // reset test + await tester.pumpWidget(buildRadio(focused: true)); + await tester.pumpAndSettle(); + expect(focusNode.hasPrimaryFocus, isTrue); + + expect( + Material.of(tester.element(find.byType(Radio<bool>))), + paints..circle(color: theme.focusColor)..circle(color: colors.secondary) + ); + + // unselected radio in focused state + await tester.pumpWidget(Container()); // reset test + await tester.pumpWidget(buildRadio(focused: true, selected: false)); + await tester.pumpAndSettle(); + expect(focusNode.hasPrimaryFocus, isTrue); + + expect( + Material.of(tester.element(find.byType(Radio<bool>))), + paints..circle(color: theme.focusColor)..circle(color: theme.unselectedWidgetColor) + ); + + // selected radio in hovered state + await tester.pumpWidget(Container()); // reset test + await tester.pumpWidget(buildRadio()); + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(); + await gesture.moveTo(tester.getCenter(find.byType(Radio<bool>))); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Radio<bool>))), + paints..circle(color: theme.hoverColor)..circle(color: colors.secondary) + ); + }); + + testWidgets('Material3 - Radio button default overlay colors in hover/focus/press states', (WidgetTester tester) async { + final FocusNode focusNode = FocusNode(debugLabel: 'Radio'); + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + + final ThemeData theme = ThemeData(useMaterial3: true); + final ColorScheme colors = theme.colorScheme; + Widget buildRadio({bool enabled = true, bool focused = false, bool selected = true}) { + return MaterialApp( + theme: theme, + home: Scaffold( + body: Radio<bool>( + focusNode: focusNode, + autofocus: focused, + value: true, + groupValue: selected, + onChanged: enabled ? (_) {} : null, + ), + ), + ); + } + + // default selected radio + await tester.pumpWidget(buildRadio()); + await tester.pumpAndSettle(); + expect( + Material.of(tester.element(find.byType(Radio<bool>))), + paints..circle(color: colors.primary.withOpacity(1)) + ); + + // selected radio in pressed state + await tester.pumpWidget(buildRadio()); + await tester.startGesture(tester.getCenter(find.byType(Radio<bool>))); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Radio<bool>))), + paints..circle(color: colors.onSurface.withOpacity(0.12)) + ..circle(color: colors.primary.withOpacity(1)) + ); + + // unselected radio in pressed state + await tester.pumpWidget(buildRadio(selected: false)); + await tester.startGesture(tester.getCenter(find.byType(Radio<bool>))); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Radio<bool>))), + paints..circle(color: colors.primary.withOpacity(0.12))..circle(color: colors.onSurfaceVariant.withOpacity(1)) ); // selected radio in focused state @@ -1377,9 +1770,7 @@ void main() { expect( Material.of(tester.element(find.byType(Radio<bool>))), - material3 - ? (paints..circle(color: colors.primary.withOpacity(0.12))..circle(color: colors.primary.withOpacity(1))) - : (paints..circle(color: theme.focusColor)..circle(color: colors.secondary)) + paints..circle(color: colors.primary.withOpacity(0.12))..circle(color: colors.primary.withOpacity(1)) ); // unselected radio in focused state @@ -1390,9 +1781,7 @@ void main() { expect( Material.of(tester.element(find.byType(Radio<bool>))), - material3 - ? (paints..circle(color: colors.onSurface.withOpacity(0.12))..circle(color: colors.onSurface.withOpacity(1))) - : (paints..circle(color: theme.focusColor)..circle(color: theme.unselectedWidgetColor)) + paints..circle(color: colors.onSurface.withOpacity(0.12))..circle(color: colors.onSurface.withOpacity(1)) ); // selected radio in hovered state @@ -1405,9 +1794,7 @@ void main() { expect( Material.of(tester.element(find.byType(Radio<bool>))), - material3 - ? (paints..circle(color: colors.primary.withOpacity(0.08))..circle(color: colors.primary.withOpacity(1))) - : (paints..circle(color: theme.hoverColor)..circle(color: colors.secondary)) + paints..circle(color: colors.primary.withOpacity(0.08))..circle(color: colors.primary.withOpacity(1)) ); }); @@ -1442,10 +1829,75 @@ void main() { } }); - testWidgets('Radio default overlayColor and fillColor resolves pressed state', (WidgetTester tester) async { + testWidgets('Material2 - Radio default overlayColor and fillColor resolves pressed state', (WidgetTester tester) async { + final FocusNode focusNode = FocusNode(debugLabel: 'Radio'); + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + final ThemeData theme = ThemeData(useMaterial3: false); + + Finder findRadio() { + return find.byWidgetPredicate((Widget widget) => widget is Radio<bool>); + } + + MaterialInkController? getRadioMaterial(WidgetTester tester) { + return Material.of(tester.element(findRadio())); + } + await tester.pumpWidget(MaterialApp( + theme: theme, + home: Scaffold( + body: Radio<bool>( + focusNode: focusNode, + value: true, + groupValue: true, + onChanged: (_) { }, + ), + ), + )); + + // Hover + final Offset center = tester.getCenter(find.byType(Radio<bool>)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + + expect(getRadioMaterial(tester), + paints + ..circle(color: theme.hoverColor) + ..circle(color: theme.colorScheme.secondary) + ); + + // Highlighted (pressed). + await gesture.down(center); + await tester.pumpAndSettle(); + + expect(getRadioMaterial(tester), + paints + ..circle(color: theme.colorScheme.secondary.withAlpha(kRadialReactionAlpha)) + ..circle(color: theme.colorScheme.secondary) + ); + // Remove pressed and hovered states + await gesture.up(); + await tester.pumpAndSettle(); + await gesture.moveTo(const Offset(0, 50)); + await tester.pumpAndSettle(); + + // Focused. + focusNode.requestFocus(); + await tester.pumpAndSettle(); + + expect(getRadioMaterial(tester), + paints + ..circle(color: theme.focusColor) + ..circle(color: theme.colorScheme.secondary) + ); + }); + + testWidgets('Material3 - Radio default overlayColor and fillColor resolves pressed state', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'Radio'); tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; - final bool material3 = theme.useMaterial3; + final ThemeData theme = ThemeData(useMaterial3: true); Finder findRadio() { return find.byWidgetPredicate((Widget widget) => widget is Radio<bool>); @@ -1477,8 +1929,8 @@ void main() { expect(getRadioMaterial(tester), paints - ..circle(color: material3 ? theme.colorScheme.primary.withOpacity(0.08) : theme.hoverColor) - ..circle(color: material3 ? theme.colorScheme.primary : theme.colorScheme.secondary) + ..circle(color: theme.colorScheme.primary.withOpacity(0.08)) + ..circle(color: theme.colorScheme.primary) ); // Highlighted (pressed). @@ -1487,8 +1939,8 @@ void main() { expect(getRadioMaterial(tester), paints - ..circle(color: material3 ? theme.colorScheme.onSurface.withOpacity(0.12) : theme.colorScheme.secondary.withAlpha(kRadialReactionAlpha)) - ..circle(color: material3 ? theme.colorScheme.primary : theme.colorScheme.secondary) + ..circle(color: theme.colorScheme.onSurface.withOpacity(0.12)) + ..circle(color: theme.colorScheme.primary) ); // Remove pressed and hovered states await gesture.up(); @@ -1502,8 +1954,8 @@ void main() { expect(getRadioMaterial(tester), paints - ..circle(color: material3 ? theme.colorScheme.primary.withOpacity(0.12) : theme.focusColor) - ..circle(color: material3 ? theme.colorScheme.primary : theme.colorScheme.secondary) + ..circle(color: theme.colorScheme.primary.withOpacity(0.12)) + ..circle(color: theme.colorScheme.primary) ); }); } diff --git a/packages/flutter/test/material/range_slider_test.dart b/packages/flutter/test/material/range_slider_test.dart index 02a5cab358f88..12c5eabdbb667 100644 --- a/packages/flutter/test/material/range_slider_test.dart +++ b/packages/flutter/test/material/range_slider_test.dart @@ -1407,6 +1407,7 @@ void main() { values = newValues; } return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( // The builder is used to pass the context from the MaterialApp widget // to the [Navigator]. This context is required in order for the @@ -2540,4 +2541,45 @@ void main() { isNot(paints..circle(color: draggedColor)), ); }); + + testWidgets('RangeSlider onChangeStart and onChangeEnd fire once', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/128433 + + int startFired = 0; + int endFired = 0; + await tester.pumpWidget( + MaterialApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: Center( + child: GestureDetector( + onHorizontalDragUpdate: (_) { }, + child: RangeSlider( + values: const RangeValues(40, 80), + max: 100, + onChanged: (RangeValues newValue) { }, + onChangeStart: (RangeValues value) { + startFired += 1; + }, + onChangeEnd: (RangeValues value) { + endFired += 1; + }, + ), + ), + ), + ), + ), + ), + ); + + await tester.timedDragFrom( + tester.getTopLeft(find.byType(RangeSlider)), + const Offset(100.0, 0.0), + const Duration(milliseconds: 500), + ); + + expect(startFired, equals(1)); + expect(endFired, equals(1)); + }); } diff --git a/packages/flutter/test/material/raw_material_button_test.dart b/packages/flutter/test/material/raw_material_button_test.dart index 9cf12c8592b20..da69556af2132 100644 --- a/packages/flutter/test/material/raw_material_button_test.dart +++ b/packages/flutter/test/material/raw_material_button_test.dart @@ -17,13 +17,16 @@ void main() { bool pressed = false; const Color splashColor = Color(0xff00ff00); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: RawMaterialButton( - splashColor: splashColor, - onPressed: () { pressed = true; }, - child: const Text('BUTTON'), + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: RawMaterialButton( + splashColor: splashColor, + onPressed: () { pressed = true; }, + child: const Text('BUTTON'), + ), ), ), ), @@ -45,19 +48,22 @@ void main() { final FocusNode focusNode = FocusNode(debugLabel: 'Test Button'); const Color splashColor = Color(0xff00ff00); await tester.pumpWidget( - Shortcuts( - shortcuts: const <ShortcutActivator, Intent>{ - SingleActivator(LogicalKeyboardKey.enter): ActivateIntent(), - SingleActivator(LogicalKeyboardKey.space): ActivateIntent(), - }, - child: Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: RawMaterialButton( - splashColor: splashColor, - focusNode: focusNode, - onPressed: () { pressed = true; }, - child: const Text('BUTTON'), + Theme( + data: ThemeData(useMaterial3: false), + child: Shortcuts( + shortcuts: const <ShortcutActivator, Intent>{ + SingleActivator(LogicalKeyboardKey.enter): ActivateIntent(), + SingleActivator(LogicalKeyboardKey.space): ActivateIntent(), + }, + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: RawMaterialButton( + splashColor: splashColor, + focusNode: focusNode, + onPressed: () { pressed = true; }, + child: const Text('BUTTON'), + ), ), ), ), @@ -175,16 +181,19 @@ void main() { const Color fillColor = Color(0xFFEF5350); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: RawMaterialButton( - materialTapTargetSize: MaterialTapTargetSize.padded, - onPressed: () { }, - fillColor: fillColor, - highlightColor: highlightColor, - splashColor: splashColor, - child: const SizedBox(), + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: RawMaterialButton( + materialTapTargetSize: MaterialTapTargetSize.padded, + onPressed: () { }, + fillColor: fillColor, + highlightColor: highlightColor, + splashColor: splashColor, + child: const SizedBox(), + ), ), ), ), @@ -207,16 +216,19 @@ void main() { const Color fillColor = Color(0xFFEF5350); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Center( - child: RawMaterialButton( - materialTapTargetSize: MaterialTapTargetSize.padded, - onPressed: () { }, - fillColor: fillColor, - highlightColor: highlightColor, - splashColor: splashColor, - child: const SizedBox(), + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: RawMaterialButton( + materialTapTargetSize: MaterialTapTargetSize.padded, + onPressed: () { }, + fillColor: fillColor, + highlightColor: highlightColor, + splashColor: splashColor, + child: const SizedBox(), + ), ), ), ), @@ -520,6 +532,7 @@ void main() { Future<void> buildTest(VisualDensity visualDensity, {bool useText = false}) async { return tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Directionality( textDirection: TextDirection.rtl, child: Center( diff --git a/packages/flutter/test/material/refresh_indicator_test.dart b/packages/flutter/test/material/refresh_indicator_test.dart index d1cd626be1856..9a2248204d94f 100644 --- a/packages/flutter/test/material/refresh_indicator_test.dart +++ b/packages/flutter/test/material/refresh_indicator_test.dart @@ -959,6 +959,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: RefreshIndicator( onRefresh: refresh, child: Builder( diff --git a/packages/flutter/test/material/reorderable_list_test.dart b/packages/flutter/test/material/reorderable_list_test.dart index d159176a1ac8a..6bcf56fe5f378 100644 --- a/packages/flutter/test/material/reorderable_list_test.dart +++ b/packages/flutter/test/material/reorderable_list_test.dart @@ -474,6 +474,45 @@ void main() { expect(customController.offset, 120.0); }); + testWidgets('ReorderableList auto scrolling is fast enough', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/121603. + final ScrollController controller = ScrollController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ReorderableListView.builder( + scrollController: controller, + itemCount: 100, + itemBuilder: (BuildContext context, int index) { + return Text('data', key: ValueKey<int>(index)); + }, + onReorder: (int oldIndex, int newIndex) {}, + ), + ), + ), + ); + + // Start gesture on first item. + final TestGesture drag = await tester.startGesture(tester.getCenter(find.byKey(const ValueKey<int>(0)))); + await tester.pump(kLongPressTimeout + kPressTimeout); + final Offset bottomRight = tester.getBottomRight(find.byType(ReorderableListView)); + // Drag enough for move to start. + await drag.moveTo(Offset(bottomRight.dx / 2, bottomRight.dy)); + await tester.pump(); + // Use a fixed value to make sure the default velocity scalar is bigger + // than a certain amount. + const double kMinimumAllowedAutoScrollDistancePer5ms = 1.7; + + await tester.pump(const Duration(milliseconds: 5)); + expect(controller.offset, greaterThan(kMinimumAllowedAutoScrollDistancePer5ms)); + await tester.pump(const Duration(milliseconds: 5)); + expect(controller.offset, greaterThan(kMinimumAllowedAutoScrollDistancePer5ms * 2)); + await tester.pump(const Duration(milliseconds: 5)); + expect(controller.offset, greaterThan(kMinimumAllowedAutoScrollDistancePer5ms * 3)); + await tester.pump(const Duration(milliseconds: 5)); + expect(controller.offset, greaterThan(kMinimumAllowedAutoScrollDistancePer5ms * 4)); + }); + testWidgets('Still builds when no PrimaryScrollController is available', (WidgetTester tester) async { final Widget reorderableList = ReorderableListView( children: const <Widget>[ diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart index d26772c999612..55b38c18c86b2 100644 --- a/packages/flutter/test/material/scaffold_test.dart +++ b/packages/flutter/test/material/scaffold_test.dart @@ -581,7 +581,7 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: Scaffold( appBar: AppBar( title: const Text('Title'), @@ -746,6 +746,7 @@ void main() { // Regression test for https://github.com/flutter/flutter/pull/92039 await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: MediaQuery( data: const MediaQueryData( // Representing a navigational notch at the bottom of the screen @@ -991,9 +992,9 @@ void main() { late double mediaQueryBottom; Widget buildFrame({ required bool extendBody, bool? resizeToAvoidBottomInset, double viewInsetBottom = 0.0 }) { - return Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: MediaQuery( data: MediaQueryData( viewInsets: EdgeInsets.only(bottom: viewInsetBottom), ), @@ -2501,7 +2502,7 @@ void main() { 'MaterialApp at the top of your application widget tree.\n', ), ); - expect(error.toStringDeep(), equalsIgnoringHashCodes( + expect(error.toStringDeep(), startsWith( 'FlutterError\n' ' No ScaffoldMessenger widget found.\n' ' Builder widgets require a ScaffoldMessenger widget ancestor.\n' @@ -2509,32 +2510,8 @@ void main() { ' ancestor was:\n' ' Builder\n' ' The ancestors of this widget were:\n' - ' KeyedSubtree-[GlobalKey#00000]\n' - ' _BodyBuilder\n' - ' MediaQuery\n' - ' LayoutId-[<_ScaffoldSlot.body>]\n' - ' CustomMultiChildLayout\n' - ' _ActionsScope\n' - ' Actions\n' - ' AnimatedBuilder\n' - ' DefaultTextStyle\n' - ' AnimatedDefaultTextStyle\n' - ' _InkFeatures-[GlobalKey#00000 ink renderer]\n' - ' NotificationListener<LayoutChangedNotification>\n' - ' PhysicalModel\n' - ' AnimatedPhysicalModel\n' - ' Material\n' - ' _ScrollNotificationObserverScope\n' - ' NotificationListener<ScrollNotification>\n' - ' NotificationListener<ScrollMetricsNotification>\n' - ' ScrollNotificationObserver\n' - ' _ScaffoldScope\n' - ' Scaffold\n' - ' Directionality\n' - ' MediaQuery\n' - ' _MediaQueryFromView\n' - ' _ViewScope\n' - ' View-[GlobalObjectKey TestFlutterView#e6136]\n' + )); + expect(error.toStringDeep(), endsWith( ' [root]\n' ' Typically, the ScaffoldMessenger widget is introduced by the\n' ' MaterialApp at the top of your application widget tree.\n', @@ -2778,6 +2755,111 @@ void main() { expect(tester.takeException(), isNull); }); + + testWidgets('showBottomSheet removes scrim when draggable sheet is dismissed', (WidgetTester tester) async { + final DraggableScrollableController draggableController = DraggableScrollableController(); + final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey(); + PersistentBottomSheetController<void>? sheetController; + + await tester.pumpWidget(MaterialApp( + home: Scaffold( + key: scaffoldKey, + body: const Center(child: Text('body')), + ), + )); + + sheetController = scaffoldKey.currentState!.showBottomSheet<void>((_) { + return DraggableScrollableSheet( + expand: false, + controller: draggableController, + builder: (BuildContext context, ScrollController scrollController) { + return SingleChildScrollView( + controller: scrollController, + child: const Placeholder(), + ); + }, + ); + }); + + Finder findModalBarrier() => find.descendant(of: find.byType(Scaffold), matching: find.byType(ModalBarrier)); + + await tester.pump(); + expect(find.byType(BottomSheet), findsOneWidget); + + // The scrim is not present yet. + expect(findModalBarrier(), findsNothing); + + // Expand the sheet to 80% of parent height to show the scrim. + draggableController.jumpTo(0.8); + await tester.pump(); + expect(findModalBarrier(), findsOneWidget); + + // Dismiss the sheet. + sheetController.close(); + await tester.pumpAndSettle(); + + // The scrim should be gone. + expect(findModalBarrier(), findsNothing); + }); + + testWidgets("Closing bottom sheet & removing FAB at the same time doesn't throw assertion", (WidgetTester tester) async { + final Key bottomSheetKey = UniqueKey(); + PersistentBottomSheetController<void>? controller; + bool show = true; + + await tester.pumpWidget(StatefulBuilder( + builder: (_, StateSetter setState) => MaterialApp( + home: Scaffold( + body: Center( + child: Builder( + builder: (BuildContext context) => ElevatedButton( + onPressed: () { + if (controller == null) { + controller = showBottomSheet( + context: context, + builder: (_) => Container( + key: bottomSheetKey, + height: 200, + ), + ); + } else { + controller!.close(); + controller = null; + } + }, + child: const Text('BottomSheet'), + )), + ), + floatingActionButton: show + ? FloatingActionButton(onPressed: () => setState(() => show = false)) + : null, + ), + ), + )); + + // Show bottom sheet. + await tester.tap(find.byType(ElevatedButton)); + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Bottom sheet and FAB are visible. + expect(find.byType(FloatingActionButton), findsOneWidget); + expect(find.byKey(bottomSheetKey), findsOneWidget); + + // Close bottom sheet while removing FAB. + await tester.tap(find.byType(FloatingActionButton)); + await tester.pump(); // start animation + await tester.tap(find.byType(ElevatedButton)); + // Let the animation finish. + await tester.pumpAndSettle(const Duration(seconds: 1)); + + // Bottom sheet and FAB are gone. + expect(find.byType(FloatingActionButton), findsNothing); + expect(find.byKey(bottomSheetKey), findsNothing); + + // No exception is thrown. + expect(tester.takeException(), isNull); + }); + } class _GeometryListener extends StatefulWidget { diff --git a/packages/flutter/test/material/scrollbar_test.dart b/packages/flutter/test/material/scrollbar_test.dart index c4e8808501563..44addd4fc5c47 100644 --- a/packages/flutter/test/material/scrollbar_test.dart +++ b/packages/flutter/test/material/scrollbar_test.dart @@ -265,14 +265,14 @@ void main() { }); testWidgets( - 'When isAlwaysShown is true, must pass a controller or find PrimaryScrollController', + 'When thumbVisibility is true, must pass a controller or find PrimaryScrollController', (WidgetTester tester) async { Widget viewWithScroll() { return _buildBoilerplate( child: Theme( data: ThemeData(), child: const Scrollbar( - isAlwaysShown: true, + thumbVisibility: true, child: SingleChildScrollView( child: SizedBox( width: 4000.0, @@ -291,7 +291,7 @@ void main() { ); testWidgets( - 'When isAlwaysShown is true, must pass a controller that is attached to a scroll view or find PrimaryScrollController', + 'When thumbVisibility is true, must pass a controller that is attached to a scroll view or find PrimaryScrollController', (WidgetTester tester) async { final ScrollController controller = ScrollController(); Widget viewWithScroll() { @@ -299,7 +299,7 @@ void main() { child: Theme( data: ThemeData(), child: Scrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: controller, child: const SingleChildScrollView( child: SizedBox( @@ -318,14 +318,14 @@ void main() { }, ); - testWidgets('On first render with isAlwaysShown: true, the thumb shows', (WidgetTester tester) async { + testWidgets('On first render with thumbVisibility: true, the thumb shows', (WidgetTester tester) async { final ScrollController controller = ScrollController(); Widget viewWithScroll() { return _buildBoilerplate( child: Theme( data: ThemeData(), child: Scrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: controller, child: SingleChildScrollView( controller: controller, @@ -344,7 +344,7 @@ void main() { expect(find.byType(Scrollbar), paints..rect()); }); - testWidgets('On first render with isAlwaysShown: true, the thumb shows with PrimaryScrollController', (WidgetTester tester) async { + testWidgets('On first render with thumbVisibility: true, the thumb shows with PrimaryScrollController', (WidgetTester tester) async { final ScrollController controller = ScrollController(); Widget viewWithScroll() { return _buildBoilerplate( @@ -355,7 +355,7 @@ void main() { child: Builder( builder: (BuildContext context) { return const Scrollbar( - isAlwaysShown: true, + thumbVisibility: true, child: SingleChildScrollView( primary: true, child: SizedBox( @@ -376,14 +376,14 @@ void main() { expect(find.byType(Scrollbar), paints..rect()); }); - testWidgets('On first render with isAlwaysShown: false, the thumb is hidden', (WidgetTester tester) async { + testWidgets('On first render with thumbVisibility: false, the thumb is hidden', (WidgetTester tester) async { final ScrollController controller = ScrollController(); Widget viewWithScroll() { return _buildBoilerplate( child: Theme( data: ThemeData(), child: Scrollbar( - isAlwaysShown: false, + thumbVisibility: false, controller: controller, child: SingleChildScrollView( controller: controller, @@ -403,10 +403,10 @@ void main() { }); testWidgets( - 'With isAlwaysShown: true, fling a scroll. While it is still scrolling, set isAlwaysShown: false. The thumb should not fade out until the scrolling stops.', + 'With thumbVisibility: true, fling a scroll. While it is still scrolling, set thumbVisibility: false. The thumb should not fade out until the scrolling stops.', (WidgetTester tester) async { final ScrollController controller = ScrollController(); - bool isAlwaysShown = true; + bool thumbVisibility = true; Widget viewWithScroll() { return _buildBoilerplate( child: StatefulBuilder( @@ -418,12 +418,12 @@ void main() { child: const Icon(Icons.threed_rotation), onPressed: () { setState(() { - isAlwaysShown = !isAlwaysShown; + thumbVisibility = !thumbVisibility; }); }, ), body: Scrollbar( - isAlwaysShown: isAlwaysShown, + thumbVisibility: thumbVisibility, controller: controller, child: SingleChildScrollView( controller: controller, @@ -457,10 +457,10 @@ void main() { ); testWidgets( - 'With isAlwaysShown: false, set isAlwaysShown: true. The thumb should be always shown directly', + 'With thumbVisibility: false, set thumbVisibility: true. The thumb should be always shown directly', (WidgetTester tester) async { final ScrollController controller = ScrollController(); - bool isAlwaysShown = false; + bool thumbVisibility = false; Widget viewWithScroll() { return _buildBoilerplate( child: StatefulBuilder( @@ -472,12 +472,12 @@ void main() { child: const Icon(Icons.threed_rotation), onPressed: () { setState(() { - isAlwaysShown = !isAlwaysShown; + thumbVisibility = !thumbVisibility; }); }, ), body: Scrollbar( - isAlwaysShown: isAlwaysShown, + thumbVisibility: thumbVisibility, controller: controller, child: SingleChildScrollView( controller: controller, @@ -506,10 +506,10 @@ void main() { ); testWidgets( - 'With isAlwaysShown: false, fling a scroll. While it is still scrolling, set isAlwaysShown: true. The thumb should not fade even after the scrolling stops', + 'With thumbVisibility: false, fling a scroll. While it is still scrolling, set thumbVisibility: true. The thumb should not fade even after the scrolling stops', (WidgetTester tester) async { final ScrollController controller = ScrollController(); - bool isAlwaysShown = false; + bool thumbVisibility = false; Widget viewWithScroll() { return _buildBoilerplate( child: StatefulBuilder( @@ -521,12 +521,12 @@ void main() { child: const Icon(Icons.threed_rotation), onPressed: () { setState(() { - isAlwaysShown = !isAlwaysShown; + thumbVisibility = !thumbVisibility; }); }, ), body: Scrollbar( - isAlwaysShown: isAlwaysShown, + thumbVisibility: thumbVisibility, controller: controller, child: SingleChildScrollView( controller: controller, @@ -566,10 +566,10 @@ void main() { ); testWidgets( - 'Toggling isAlwaysShown while not scrolling fades the thumb in/out. This works even when you have never scrolled at all yet', + 'Toggling thumbVisibility while not scrolling fades the thumb in/out. This works even when you have never scrolled at all yet', (WidgetTester tester) async { final ScrollController controller = ScrollController(); - bool isAlwaysShown = true; + bool thumbVisibility = true; Widget viewWithScroll() { return _buildBoilerplate( child: StatefulBuilder( @@ -581,12 +581,12 @@ void main() { child: const Icon(Icons.threed_rotation), onPressed: () { setState(() { - isAlwaysShown = !isAlwaysShown; + thumbVisibility = !thumbVisibility; }); }, ), body: Scrollbar( - isAlwaysShown: isAlwaysShown, + thumbVisibility: thumbVisibility, controller: controller, child: SingleChildScrollView( controller: controller, @@ -685,7 +685,7 @@ void main() { data: const MediaQueryData(), child: Scrollbar( interactive: true, - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: SingleChildScrollView( controller: scrollController, @@ -851,11 +851,12 @@ void main() { final ScrollController scrollController = ScrollController(); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: PrimaryScrollController( controller: scrollController, child: Scrollbar( interactive: true, - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), @@ -941,7 +942,10 @@ void main() { testWidgets('Scrollbar thumb color completes a hover animation', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(scrollbarTheme: const ScrollbarThemeData(isAlwaysShown: true)), + theme: ThemeData( + useMaterial3: false, + scrollbarTheme: ScrollbarThemeData(thumbVisibility: MaterialStateProperty.all(true)), + ), home: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), ), @@ -986,10 +990,13 @@ void main() { testWidgets('Hover animation is not triggered by tap gestures', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(scrollbarTheme: const ScrollbarThemeData( - isAlwaysShown: true, - showTrackOnHover: true, - )), + theme: ThemeData( + useMaterial3: false, + scrollbarTheme: ScrollbarThemeData( + thumbVisibility: MaterialStateProperty.all(true), + showTrackOnHover: true, + ), + ), home: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), ), @@ -1058,19 +1065,22 @@ void main() { testWidgets('ScrollbarThemeData.thickness replaces hoverThickness', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(scrollbarTheme: ScrollbarThemeData( - thumbVisibility: MaterialStateProperty.resolveWith((Set<MaterialState> states) => true), - trackVisibility: MaterialStateProperty.resolveWith((Set<MaterialState> states) { - return states.contains(MaterialState.hovered); - }), - thickness: MaterialStateProperty.resolveWith((Set<MaterialState> states) { - if (states.contains(MaterialState.hovered)) { - return 40.0; - } - // Default thickness - return 8.0; - }), - )), + theme: ThemeData( + useMaterial3: false, + scrollbarTheme: ScrollbarThemeData( + thumbVisibility: MaterialStateProperty.resolveWith((Set<MaterialState> states) => true), + trackVisibility: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + return states.contains(MaterialState.hovered); + }), + thickness: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.hovered)) { + return 40.0; + } + // Default thickness + return 8.0; + }), + ), + ), home: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), ), @@ -1127,15 +1137,18 @@ void main() { testWidgets('ScrollbarThemeData.trackVisibility replaces showTrackOnHover', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(scrollbarTheme: ScrollbarThemeData( - isAlwaysShown: true, - trackVisibility: MaterialStateProperty.resolveWith((Set<MaterialState> states) { - if (states.contains(MaterialState.hovered)) { - return true; - } - return false; - }), - )), + theme: ThemeData( + useMaterial3: false, + scrollbarTheme: ScrollbarThemeData( + thumbVisibility: MaterialStateProperty.all(true), + trackVisibility: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.hovered)) { + return true; + } + return false; + }), + ), + ), home: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), ), @@ -1192,10 +1205,13 @@ void main() { testWidgets('Scrollbar showTrackOnHover', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(scrollbarTheme: const ScrollbarThemeData( - isAlwaysShown: true, - showTrackOnHover: true, - )), + theme: ThemeData( + useMaterial3: false, + scrollbarTheme: ScrollbarThemeData( + thumbVisibility: MaterialStateProperty.all(true), + showTrackOnHover: true, + ), + ), home: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), ), @@ -1385,11 +1401,12 @@ void main() { final ScrollController scrollController = ScrollController(); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: PrimaryScrollController( controller: scrollController, child: Scrollbar( interactive: false, - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), @@ -1457,7 +1474,7 @@ void main() { home: PrimaryScrollController( controller: scrollController, child: Scrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: SingleChildScrollView( dragStartBehavior: DragStartBehavior.down, @@ -1548,11 +1565,12 @@ void main() { final ScrollController scrollController = ScrollController(); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: PrimaryScrollController( controller: scrollController, child: Scrollbar( interactive: true, - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), @@ -1714,10 +1732,10 @@ void main() { ); }); - testWidgets('Scrollbar.isAlwaysShown triggers assertion when multiple ScrollPositions are attached.', (WidgetTester tester) async { + testWidgets('Scrollbar.thumbVisibility triggers assertion when multiple ScrollPositions are attached.', (WidgetTester tester) async { Widget getTabContent({ ScrollController? scrollController }) { return Scrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: ListView.builder( controller: scrollController, @@ -1794,7 +1812,7 @@ void main() { controller: scrollController, child: Scrollbar( interactive: true, - isAlwaysShown: true, + thumbVisibility: true, scrollbarOrientation: orientation, controller: scrollController, child: const SingleChildScrollView( diff --git a/packages/flutter/test/material/scrollbar_theme_test.dart b/packages/flutter/test/material/scrollbar_theme_test.dart index bd98448fb8b45..50c6ba74f6ad7 100644 --- a/packages/flutter/test/material/scrollbar_theme_test.dart +++ b/packages/flutter/test/material/scrollbar_theme_test.dart @@ -35,10 +35,11 @@ void main() { final ScrollController scrollController = ScrollController(); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: ScrollConfiguration( behavior: const NoScrollbarBehavior(), child: Scrollbar( - isAlwaysShown: true, + thumbVisibility: true, showTrackOnHover: true, controller: scrollController, child: SingleChildScrollView( @@ -131,7 +132,7 @@ void main() { home: ScrollConfiguration( behavior: const NoScrollbarBehavior(), child: Scrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: SingleChildScrollView( controller: scrollController, @@ -259,9 +260,9 @@ void main() { testWidgets('ScrollbarTheme can disable gestures', (WidgetTester tester) async { final ScrollController scrollController = ScrollController(); await tester.pumpWidget(MaterialApp( - theme: ThemeData(scrollbarTheme: const ScrollbarThemeData(interactive: false)), + theme: ThemeData(useMaterial3: false, scrollbarTheme: const ScrollbarThemeData(interactive: false)), home: Scrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: SingleChildScrollView( controller: scrollController, @@ -306,10 +307,10 @@ void main() { testWidgets('Scrollbar.interactive takes priority over ScrollbarTheme', (WidgetTester tester) async { final ScrollController scrollController = ScrollController(); await tester.pumpWidget(MaterialApp( - theme: ThemeData(scrollbarTheme: const ScrollbarThemeData(interactive: false)), + theme: ThemeData(useMaterial3: false, scrollbarTheme: const ScrollbarThemeData(interactive: false)), home: Scrollbar( interactive: true, - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: SingleChildScrollView( controller: scrollController, @@ -353,7 +354,6 @@ void main() { testWidgets('Scrollbar widget properties take priority over theme', (WidgetTester tester) async { const double thickness = 4.0; - const double hoverThickness = 4.0; const bool showTrackOnHover = true; const Radius radius = Radius.circular(3.0); final ScrollController scrollController = ScrollController(); @@ -367,7 +367,6 @@ void main() { behavior: const NoScrollbarBehavior(), child: Scrollbar( thickness: thickness, - hoverThickness: hoverThickness, thumbVisibility: true, showTrackOnHover: showTrackOnHover, radius: radius, @@ -426,19 +425,18 @@ void main() { find.byType(Scrollbar), paints ..rect( - rect: const Rect.fromLTRB(792.0, 0.0, 800.0, 600.0), + rect: const Rect.fromLTRB(784.0, 0.0, 800.0, 600.0), color: const Color(0x08000000), ) ..line( - p1: const Offset(792.0, 0.0), - p2: const Offset(792.0, 600.0), + p1: const Offset(784.0, 0.0), + p2: const Offset(784.0, 600.0), strokeWidth: 1.0, color: const Color(0x1a000000), ) ..rrect( rrect: RRect.fromRectAndRadius( - // Scrollbar thumb is larger - const Rect.fromLTRB(794.0, 10.0, 798.0, 100.0), + const Rect.fromLTRB(786.0, 10.0, 798.0, 100.0), const Radius.circular(3.0), ), // Hover color @@ -461,7 +459,7 @@ void main() { home: ScrollConfiguration( behavior: const NoScrollbarBehavior(), child: Scrollbar( - isAlwaysShown: true, + thumbVisibility: true, showTrackOnHover: true, controller: scrollController, child: SingleChildScrollView( @@ -628,7 +626,7 @@ void main() { } await tester.pumpWidget( MaterialApp( - theme: ThemeData().copyWith( + theme: ThemeData(useMaterial3: false).copyWith( scrollbarTheme: _scrollbarTheme( trackVisibility: MaterialStateProperty.resolveWith(getTrackVisibility), ), @@ -636,7 +634,7 @@ void main() { home: ScrollConfiguration( behavior: const NoScrollbarBehavior(), child: Scrollbar( - isAlwaysShown: true, + thumbVisibility: true, showTrackOnHover: true, controller: scrollController, child: SingleChildScrollView( diff --git a/packages/flutter/test/material/search_anchor_test.dart b/packages/flutter/test/material/search_anchor_test.dart index 1a05c55f4080c..d30cd8e0a67d6 100644 --- a/packages/flutter/test/material/search_anchor_test.dart +++ b/packages/flutter/test/material/search_anchor_test.dart @@ -270,6 +270,26 @@ void main() { expect(changeCount, 2); }); + testWidgets('SearchBar respects onSubmitted property', (WidgetTester tester) async { + String submittedQuery = ''; + await tester.pumpWidget( + MaterialApp( + home: Material( + child: SearchBar( + onSubmitted: (String text) { + submittedQuery = text; + }, + ), + ), + ), + ); + + await tester.enterText(find.byType(SearchBar), 'query'); + await tester.testTextInput.receiveAction(TextInputAction.done); + + expect(submittedQuery, equals('query')); + }); + testWidgets('SearchBar respects constraints property', (WidgetTester tester) async { const BoxConstraints constraints = BoxConstraints(maxWidth: 350.0, minHeight: 80); await tester.pumpWidget( @@ -708,6 +728,29 @@ void main() { expect(helperText.style?.color, focusedColor); }); + // Regression test for https://github.com/flutter/flutter/issues/127092. + testWidgets('The text is still centered when SearchBar text field is smaller than 48', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Center( + child: Material( + child: SearchBar( + constraints: BoxConstraints.tightFor(height: 35.0), + ), + ), + ), + ), + ); + + await tester.enterText(find.byType(TextField), 'input text'); + final Finder textContent = find.text('input text'); + final double textCenterY = tester.getCenter(textContent).dy; + final Finder searchBar = find.byType(SearchBar); + final double searchBarCenterY = tester.getCenter(searchBar).dy; + expect(textCenterY, searchBarCenterY); + }); + testWidgets('The search view defaults', (WidgetTester tester) async { final ThemeData theme = ThemeData(useMaterial3: true); final ColorScheme colorScheme = theme.colorScheme; @@ -1670,7 +1713,7 @@ void main() { }); testWidgets('Search view route does not throw exception during pop animation', (WidgetTester tester) async { - // regression test for https://github.com/flutter/flutter/issues/126590. + // Regression test for https://github.com/flutter/flutter/issues/126590. await tester.pumpWidget(MaterialApp( home: Material( child: Center( @@ -1746,8 +1789,52 @@ void main() { expect(searchViewRect.topLeft, equals(const Offset(rootSpacing, rootSpacing))); }); + testWidgets('Docked search view with nested navigator does not go off the screen', (WidgetTester tester) async { + addTearDown(tester.view.reset); + tester.view.physicalSize = const Size(400.0, 400.0); + tester.view.devicePixelRatio = 1.0; + + const double rootSpacing = 100.0; + + await tester.pumpWidget(MaterialApp( + builder: (BuildContext context, Widget? child) { + return Scaffold( + body: Padding( + padding: const EdgeInsets.all(rootSpacing), + child: child, + ), + ); + }, + home: Material( + child: Align( + alignment: Alignment.bottomRight, + child: SearchAnchor( + isFullScreen: false, + builder: (BuildContext context, SearchController controller) { + return IconButton( + icon: const Icon(Icons.search), + onPressed: () { + controller.openView(); + }, + ); + }, + suggestionsBuilder: (BuildContext context, SearchController controller) { + return <Widget>[]; + }, + ), + ), + ), + )); + + await tester.tap(find.byIcon(Icons.search)); + await tester.pumpAndSettle(); + + final Rect searchViewRect = tester.getRect(find.descendant(of: findViewContent(), matching: find.byType(SizedBox)).first); + expect(searchViewRect.bottomRight, equals(const Offset(300.0, 300.0))); + }); + - // regression tests for https://github.com/flutter/flutter/issues/126623 + // Regression tests for https://github.com/flutter/flutter/issues/126623 group('Overall InputDecorationTheme does not impact SearchBar and SearchView', () { const InputDecorationTheme inputDecorationTheme = InputDecorationTheme( @@ -1789,7 +1876,7 @@ void main() { expect(decoration?.fillColor, null); expect(decoration?.focusColor, null); expect(decoration?.hoverColor, null); - expect(decoration?.contentPadding, const EdgeInsets.fromLTRB(0.0, 12.0, 0.0, 12.0)); + expect(decoration?.contentPadding, EdgeInsets.zero); expect(decoration?.hintStyle?.color, theme.colorScheme.onSurfaceVariant); } diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart index 8c6e8073a1258..be9a1d6a2498e 100644 --- a/packages/flutter/test/material/search_test.dart +++ b/packages/flutter/test/material/search_test.dart @@ -589,6 +589,163 @@ void main() { expect(tester.testTextInput.setClientArgs!['inputAction'], TextInputAction.done.toString()); }); + testWidgets('Custom flexibleSpace value', (WidgetTester tester) async { + const Widget flexibleSpace = Text('custom flexibleSpace'); + final _TestSearchDelegate delegate = _TestSearchDelegate(flexibleSpace: flexibleSpace); + + await tester.pumpWidget(TestHomePage(delegate: delegate)); + await tester.tap(find.byTooltip('Search')); + await tester.pumpAndSettle(); + + expect(find.byWidget(flexibleSpace), findsOneWidget); + }); + + + group('contributes semantics with custom flexibleSpace', () { + const Widget flexibleSpace = Text('FlexibleSpace'); + + TestSemantics buildExpected({ required String routeName }) { + return TestSemantics.root( + children: <TestSemantics>[ + TestSemantics( + id: 1, + textDirection: TextDirection.ltr, + children: <TestSemantics>[ + TestSemantics( + id: 2, + children: <TestSemantics>[ + TestSemantics( + id: 3, + flags: <SemanticsFlag>[ + SemanticsFlag.scopesRoute, + SemanticsFlag.namesRoute, + ], + label: routeName, + textDirection: TextDirection.ltr, + children: <TestSemantics>[ + TestSemantics( + id: 4, + children: <TestSemantics>[ + TestSemantics( + id: 6, + children: <TestSemantics>[ + TestSemantics( + id: 8, + flags: <SemanticsFlag>[ + SemanticsFlag.hasEnabledState, + SemanticsFlag.isButton, + SemanticsFlag.isEnabled, + SemanticsFlag.isFocusable, + ], + actions: <SemanticsAction>[SemanticsAction.tap], + tooltip: 'Back', + textDirection: TextDirection.ltr, + ), + TestSemantics( + id: 9, + flags: <SemanticsFlag>[ + SemanticsFlag.isTextField, + SemanticsFlag.isFocused, + SemanticsFlag.isHeader, + if (debugDefaultTargetPlatformOverride != TargetPlatform.iOS && + debugDefaultTargetPlatformOverride != TargetPlatform.macOS) SemanticsFlag.namesRoute, + ], + actions: <SemanticsAction>[ + if (debugDefaultTargetPlatformOverride == TargetPlatform.macOS || + debugDefaultTargetPlatformOverride == TargetPlatform.windows) + SemanticsAction.didGainAccessibilityFocus, + SemanticsAction.tap, + SemanticsAction.setSelection, + SemanticsAction.setText, + SemanticsAction.paste, + ], + label: 'Search', + textDirection: TextDirection.ltr, + textSelection: const TextSelection(baseOffset: 0, extentOffset: 0), + ), + TestSemantics( + id: 10, + label: 'Bottom', + textDirection: TextDirection.ltr, + ), + ], + ), + TestSemantics( + id: 7, + children: <TestSemantics>[ + TestSemantics( + id: 11, + label: 'FlexibleSpace', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + TestSemantics( + id: 5, + flags: <SemanticsFlag>[ + SemanticsFlag.hasEnabledState, + SemanticsFlag.isButton, + SemanticsFlag.isEnabled, + SemanticsFlag.isFocusable, + ], + actions: <SemanticsAction>[SemanticsAction.tap], + label: 'Suggestions', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ), + ], + ); + } + + testWidgets('includes routeName on Android', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + final _TestSearchDelegate delegate = _TestSearchDelegate(flexibleSpace: flexibleSpace); + await tester.pumpWidget(TestHomePage( + delegate: delegate, + )); + + await tester.tap(find.byTooltip('Search')); + await tester.pumpAndSettle(); + + expect(semantics, hasSemantics( + buildExpected(routeName: 'Search'), + ignoreId: true, + ignoreRect: true, + ignoreTransform: true, + )); + + semantics.dispose(); + }); + + testWidgets('does not include routeName', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + final _TestSearchDelegate delegate = _TestSearchDelegate(flexibleSpace: flexibleSpace); + await tester.pumpWidget(TestHomePage( + delegate: delegate, + )); + + await tester.tap(find.byTooltip('Search')); + await tester.pumpAndSettle(); + + expect(semantics, hasSemantics( + buildExpected(routeName: ''), + ignoreId: true, + ignoreRect: true, + ignoreTransform: true, + )); + + semantics.dispose(); + }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); + }); + + group('contributes semantics', () { TestSemantics buildExpected({ required String routeName }) { return TestSemantics.root( @@ -734,7 +891,7 @@ void main() { // Regression test for: https://github.com/flutter/flutter/issues/66781 testWidgets('text in search bar contrasts background (light mode)', (WidgetTester tester) async { - final ThemeData themeData = ThemeData.light(); + final ThemeData themeData = ThemeData(useMaterial3: false); final _TestSearchDelegate delegate = _TestSearchDelegate( defaultAppBarTheme: true, ); @@ -749,10 +906,10 @@ void main() { await tester.tap(find.byTooltip('Search')); await tester.pumpAndSettle(); - final Material appBarBackground = tester.widget<Material>(find.descendant( + final Material appBarBackground = tester.widgetList<Material>(find.descendant( of: find.byType(AppBar), matching: find.byType(Material), - )); + )).first; expect(appBarBackground.color, Colors.white); final TextField textField = tester.widget<TextField>(find.byType(TextField)); @@ -762,7 +919,7 @@ void main() { // Regression test for: https://github.com/flutter/flutter/issues/66781 testWidgets('text in search bar contrasts background (dark mode)', (WidgetTester tester) async { - final ThemeData themeData = ThemeData.dark(); + final ThemeData themeData = ThemeData.dark(useMaterial3: false); final _TestSearchDelegate delegate = _TestSearchDelegate( defaultAppBarTheme: true, ); @@ -777,10 +934,10 @@ void main() { await tester.tap(find.byTooltip('Search')); await tester.pumpAndSettle(); - final Material appBarBackground = tester.widget<Material>(find.descendant( + final Material appBarBackground = tester.widgetList<Material>(find.descendant( of: find.byType(AppBar), matching: find.byType(Material), - )); + )).first; expect(appBarBackground.color, themeData.primaryColor); final TextField textField = tester.widget<TextField>(find.byType(TextField)); @@ -789,9 +946,9 @@ void main() { }); // Regression test for: https://github.com/flutter/flutter/issues/78144 - testWidgets('`Leading` and `Actions` nullable test', (WidgetTester tester) async { + testWidgets('`Leading`, `Actions` and `FlexibleSpace` nullable test', (WidgetTester tester) async { // The search delegate page is displayed with no issues - // even with a null return values for [buildLeading] and [buildActions]. + // even with a null return values for [buildLeading], [buildActions] and [flexibleSpace]. final _TestEmptySearchDelegate delegate = _TestEmptySearchDelegate(); final List<String> selectedResults = <String>[]; @@ -980,6 +1137,7 @@ class _TestSearchDelegate extends SearchDelegate<String> { this.suggestions = 'Suggestions', this.result = 'Result', this.actions = const <Widget>[], + this.flexibleSpace , this.defaultAppBarTheme = false, super.searchFieldDecorationTheme, super.searchFieldStyle, @@ -993,6 +1151,7 @@ class _TestSearchDelegate extends SearchDelegate<String> { final String suggestions; final String result; final List<Widget> actions; + final Widget? flexibleSpace; static const Color hintTextColor = Colors.green; @override @@ -1029,7 +1188,7 @@ class _TestSearchDelegate extends SearchDelegate<String> { @override Widget buildSuggestions(BuildContext context) { queriesForSuggestions.add(query); - return MaterialButton( + return ElevatedButton( onPressed: () { showResults(context); }, @@ -1048,6 +1207,11 @@ class _TestSearchDelegate extends SearchDelegate<String> { return actions; } + @override + Widget? buildFlexibleSpace(BuildContext context) { + return flexibleSpace; + } + @override PreferredSizeWidget buildBottom(BuildContext context) { return const PreferredSize( @@ -1066,7 +1230,7 @@ class _TestEmptySearchDelegate extends SearchDelegate<String> { @override Widget buildSuggestions(BuildContext context) { - return MaterialButton( + return ElevatedButton( onPressed: () { showResults(context); }, diff --git a/packages/flutter/test/material/segmented_button_test.dart b/packages/flutter/test/material/segmented_button_test.dart index ff863a860ced4..cb785b3217e85 100644 --- a/packages/flutter/test/material/segmented_button_test.dart +++ b/packages/flutter/test/material/segmented_button_test.dart @@ -533,4 +533,59 @@ testWidgets('SegmentedButton shows checkboxes for selected segments', (WidgetTes expect(overlayColor(), paints..rect()..rect(color: theme.colorScheme.onSurface.withOpacity(0.12))); expect(material.textStyle?.color, theme.colorScheme.onSurface); }); + + testWidgets('SegmentedButton has no tooltips by default', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Scaffold( + body: Center( + child: SegmentedButton<int>( + segments: const <ButtonSegment<int>>[ + ButtonSegment<int>(value: 1, label: Text('1')), + ButtonSegment<int>(value: 2, label: Text('2')), + ButtonSegment<int>(value: 3, label: Text('3'), enabled: false), + ], + selected: const <int>{2}, + onSelectionChanged: (Set<int> selected) { }, + ), + ), + ), + ), + ); + + expect(find.byType(Tooltip), findsNothing); + }); + + testWidgets('SegmentedButton has correct tooltips', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Scaffold( + body: Center( + child: SegmentedButton<int>( + segments: const <ButtonSegment<int>>[ + ButtonSegment<int>(value: 1, label: Text('1')), + ButtonSegment<int>(value: 2, label: Text('2'), tooltip: 't2'), + ButtonSegment<int>( + value: 3, + label: Text('3'), + tooltip: 't3', + enabled: false, + ), + ], + selected: const <int>{2}, + onSelectionChanged: (Set<int> selected) { }, + ), + ), + ), + ), + ); + + expect(find.byType(Tooltip), findsNWidgets(2)); + expect(find.byTooltip('t2'), findsOneWidget); + expect(find.byTooltip('t3'), findsOneWidget); + }); } diff --git a/packages/flutter/test/material/selection_area_test.dart b/packages/flutter/test/material/selection_area_test.dart index 0ab44528e6906..b9fb37c582d6e 100644 --- a/packages/flutter/test/material/selection_area_test.dart +++ b/packages/flutter/test/material/selection_area_test.dart @@ -167,6 +167,7 @@ void main() { // Regression test for https://github.com/flutter/flutter/issues/119314 await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Padding( padding: const EdgeInsets.only(top: 64), diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart index 6430b0d314dbb..a8bf69325ccea 100644 --- a/packages/flutter/test/material/slider_test.dart +++ b/packages/flutter/test/material/slider_test.dart @@ -829,6 +829,7 @@ void main() { ShowValueIndicator show = ShowValueIndicator.onlyForDiscrete, }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Directionality( textDirection: TextDirection.ltr, child: StatefulBuilder( @@ -1944,6 +1945,7 @@ void main() { double value = 0.5; final ThemeData theme = ThemeData(useMaterial3: true); final Key sliderKey = UniqueKey(); + final FocusNode focusNode = FocusNode(); Widget buildApp({bool enabled = true}) { return MaterialApp( @@ -1952,8 +1954,9 @@ void main() { child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { return Slider( - value: value, key: sliderKey, + value: value, + focusNode: focusNode, onChanged: enabled ? (double newValue) { setState(() { @@ -1993,7 +1996,18 @@ void main() { await drag.up(); await tester.pumpAndSettle(); - // Slider still has overlay when stopped dragging. + // Slider without focus doesn't have overlay when enabled and dragged. + expect(focusNode.hasFocus, false); + expect( + Material.of(tester.element(find.byType(Slider))), + isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))), + ); + + // Slider has overlay when enabled, dragged and focused. + focusNode.requestFocus(); + await tester.pumpAndSettle(); + + expect(focusNode.hasFocus, true); expect( Material.of(tester.element(find.byType(Slider))), paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)), @@ -2004,6 +2018,7 @@ void main() { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; double value = 0.5; final Key sliderKey = UniqueKey(); + final FocusNode focusNode = FocusNode(); Widget buildApp({bool enabled = true}) { return MaterialApp( @@ -2011,8 +2026,9 @@ void main() { child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { return Slider( - value: value, key: sliderKey, + value: value, + focusNode: focusNode, overlayColor: MaterialStateColor.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.dragged)) { return Colors.lime[500]!; @@ -2059,10 +2075,11 @@ void main() { await drag.up(); await tester.pumpAndSettle(); - // Slider still has overlay when stopped dragging. + // Slider without focus doesn't have overlay when enabled and dragged. + expect(focusNode.hasFocus, false); expect( Material.of(tester.element(find.byType(Slider))), - paints..circle(color: Colors.lime[500]), + isNot(paints..circle(color: Colors.lime[500])), ); }); @@ -2670,6 +2687,7 @@ void main() { bool enabled = true, }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Builder( // The builder is used to pass the context from the MaterialApp widget @@ -3493,19 +3511,62 @@ void main() { await gesture.up(); await tester.pumpAndSettle(); expect(focusNode.hasFocus, true); + // Overlay is removed when adjusted with a tap. expect( Material.of(tester.element(find.byType(Slider))), - paints..circle(color: overlayColor), + isNot(paints..circle(color: overlayColor)), ); + }, variant: TargetPlatformVariant.desktop()); - focusNode.unfocus(); + testWidgets('Value indicator disappears after adjusting the slider', (WidgetTester tester) async { + // This is a regression test for https://github.com/flutter/flutter/issues/123313. + final ThemeData theme = ThemeData(useMaterial3: true); + const double currentValue = 0.5; + await tester.pumpWidget(MaterialApp( + theme: theme, + home: Material( + child: Center( + child: Slider( + value: currentValue, + divisions: 5, + label: currentValue.toStringAsFixed(1), + onChanged: (double value) {}, + ), + ), + ), + )); + + // Slider does not show value indicator initially. await tester.pumpAndSettle(); - expect(focusNode.hasFocus, false); + RenderBox valueIndicatorBox = tester.renderObject(find.byType(Overlay)); expect( - Material.of(tester.element(find.byType(Slider))), - isNot(paints..circle(color: overlayColor)), + valueIndicatorBox, + isNot(paints..scale()..path(color: theme.colorScheme.primary)), ); - }, variant: TargetPlatformVariant.desktop()); + + final Offset sliderCenter = tester.getCenter(find.byType(Slider)); + final Offset tapLocation = Offset(sliderCenter.dx + 50, sliderCenter.dy); + + // Tap the slider to bring up the value indicator. + await tester.tapAt(tapLocation); + await tester.pumpAndSettle(); + + // Value indicator is visible. + valueIndicatorBox = tester.renderObject(find.byType(Overlay)); + expect( + valueIndicatorBox, + paints..scale()..path(color: theme.colorScheme.primary), + ); + + // Wait for the value indicator to disappear. + await tester.pumpAndSettle(const Duration(seconds: 2)); + + // Value indicator is no longer visible. + expect( + valueIndicatorBox, + isNot(paints..scale()..path(color: theme.colorScheme.primary)), + ); + }); testWidgets('Value indicator remains when Slider is in focus on desktop', (WidgetTester tester) async { double value = 0.5; @@ -3632,15 +3693,17 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('Slider can be hovered and has correct hover color', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; - final ThemeData theme = ThemeData(); + final ThemeData theme = ThemeData(useMaterial3: false); double value = 0.5; Widget buildApp({bool enabled = true}) { return MaterialApp( + theme: theme, home: Material( child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -3742,6 +3805,7 @@ void main() { double value = 0.5; final ThemeData theme = ThemeData(); final Key sliderKey = UniqueKey(); + final FocusNode focusNode = FocusNode(); Widget buildApp({bool enabled = true}) { return MaterialApp( @@ -3749,8 +3813,9 @@ void main() { child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { return Slider( - value: value, key: sliderKey, + value: value, + focusNode: focusNode, onChanged: enabled ? (double newValue) { setState(() { @@ -3790,10 +3855,11 @@ void main() { await drag.up(); await tester.pumpAndSettle(); - // Slider still has overlay when stopped dragging. + // Slider without focus doesn't have overlay when enabled and dragged. + expect(focusNode.hasFocus, false); expect( Material.of(tester.element(find.byType(Slider))), - paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)), + isNot(paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))), ); }); }); diff --git a/packages/flutter/test/material/slider_theme_test.dart b/packages/flutter/test/material/slider_theme_test.dart index 7920abe89a68f..4f3a41e1f9fce 100644 --- a/packages/flutter/test/material/slider_theme_test.dart +++ b/packages/flutter/test/material/slider_theme_test.dart @@ -242,6 +242,7 @@ void main() { const Color customColor2 = Color(0xdeadbeef); const Color customColor3 = Color(0xdecaface); final ThemeData theme = ThemeData( + useMaterial3: false, platform: TargetPlatform.android, primarySwatch: Colors.blue, sliderTheme: const SliderThemeData( @@ -276,6 +277,7 @@ void main() { value = d; }; return MaterialApp( + theme: theme, home: Directionality( textDirection: TextDirection.ltr, child: Material( @@ -894,6 +896,7 @@ void main() { debugDisableShadows = false; try { final ThemeData theme = ThemeData( + useMaterial3: false, platform: TargetPlatform.android, primarySwatch: Colors.blue, ); @@ -904,6 +907,7 @@ void main() { ); Widget buildApp(String value, { double sliderValue = 0.5, double textScale = 1.0 }) { return MaterialApp( + theme: theme, home: Directionality( textDirection: TextDirection.ltr, child: MediaQuery( @@ -1076,6 +1080,7 @@ void main() { debugDisableShadows = false; try { final ThemeData theme = ThemeData( + useMaterial3: false, platform: TargetPlatform.android, primarySwatch: Colors.blue, ); @@ -1086,6 +1091,7 @@ void main() { ); Widget buildApp(String value, { double sliderValue = 0.5, double textScale = 1.0 }) { return MaterialApp( + theme: theme, home: Directionality( textDirection: TextDirection.ltr, child: MediaQuery( @@ -2075,12 +2081,13 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('Slider defaults', (WidgetTester tester) async { debugDisableShadows = false; - final ThemeData theme = ThemeData(); + final ThemeData theme = ThemeData(useMaterial3: false); const double trackHeight = 4.0; final ColorScheme colorScheme = theme.colorScheme; final Color activeTrackColor = Color(colorScheme.primary.value); @@ -2110,6 +2117,7 @@ void main() { value = d; }; return MaterialApp( + theme: theme, home: Directionality( textDirection: TextDirection.ltr, child: Material( @@ -2231,6 +2239,7 @@ void main() { debugDisableShadows = false; try { final ThemeData theme = ThemeData( + useMaterial3: false, platform: TargetPlatform.android, ); Widget buildApp(String value, { double sliderValue = 0.5, double textScale = 1.0 }) { diff --git a/packages/flutter/test/material/snack_bar_test.dart b/packages/flutter/test/material/snack_bar_test.dart index d67c91f1ecf54..a31753412f845 100644 --- a/packages/flutter/test/material/snack_bar_test.dart +++ b/packages/flutter/test/material/snack_bar_test.dart @@ -302,7 +302,7 @@ void main() { }); testWidgets('Light theme SnackBar has dark background', (WidgetTester tester) async { - final ThemeData lightTheme = ThemeData.light(); + final ThemeData lightTheme = ThemeData.light(useMaterial3: false); await tester.pumpWidget( MaterialApp( theme: lightTheme, @@ -383,7 +383,7 @@ void main() { }); testWidgets('Dark theme SnackBar has primary text buttons', (WidgetTester tester) async { - final ThemeData darkTheme = ThemeData.dark(); + final ThemeData darkTheme = ThemeData.dark(useMaterial3: false); await tester.pumpWidget( MaterialApp( theme: darkTheme, @@ -445,7 +445,6 @@ void main() { final ThemeData theme = ThemeData.light().copyWith( visualDensity: VisualDensity.standard, primaryColor: Colors.black, - primaryColorBrightness: Brightness.dark, primaryColorLight: Colors.black, primaryColorDark: Colors.black, canvasColor: Colors.black, @@ -962,6 +961,7 @@ void main() { testWidgets('SnackBar button text alignment', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: MediaQuery( data: const MediaQueryData( padding: EdgeInsets.only( @@ -1011,6 +1011,7 @@ void main() { 'Custom padding between SnackBar and its contents when set to SnackBarBehavior.fixed', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: MediaQuery( data: const MediaQueryData( padding: EdgeInsets.only( @@ -1121,6 +1122,7 @@ void main() { testWidgets('Floating SnackBar button text alignment', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData( + useMaterial3: false, snackBarTheme: const SnackBarThemeData(behavior: SnackBarBehavior.floating), ), home: MediaQuery( @@ -1173,6 +1175,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData( + useMaterial3: false, snackBarTheme: const SnackBarThemeData(behavior: SnackBarBehavior.floating), ), home: MediaQuery( @@ -1903,8 +1906,9 @@ void main() { testWidgets('Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.persistentFooterButtons', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/84263 await tester.pumpWidget( - const MaterialApp( - home: Scaffold( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( persistentFooterButtons: <Widget>[SizedBox(height: 1000)], ), ), @@ -1918,8 +1922,9 @@ void main() { testWidgets('Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.bottomNavigationBar', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/84263 await tester.pumpWidget( - const MaterialApp( - home: Scaffold( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( bottomNavigationBar: SizedBox(height: 1000), ), ), @@ -2150,8 +2155,9 @@ void main() { }); testWidgets('SnackBars should be shown above the bottomSheet', (WidgetTester tester) async { - await tester.pumpWidget(const MaterialApp( - home: Scaffold( + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( bottomSheet: SizedBox( width: 200, height: 50, @@ -2233,6 +2239,7 @@ void main() { testWidgets('ScaffoldMessenger presents SnackBars to only the root Scaffold when Scaffolds are nested.', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: const Scaffold(), floatingActionButton: FloatingActionButton(onPressed: () {}), @@ -2434,6 +2441,7 @@ void main() { testWidgets('Snackbar by default clips BackdropFilter', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/98205 await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: const Scaffold(), floatingActionButton: FloatingActionButton(onPressed: () {}), @@ -2464,8 +2472,9 @@ void main() { }); testWidgets('Floating snackbar can display optional icon', (WidgetTester tester) async { - await tester.pumpWidget(const MaterialApp( - home: Scaffold( + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( bottomSheet: SizedBox( width: 200, height: 50, @@ -2495,8 +2504,9 @@ void main() { }); testWidgets('Fixed width snackbar can display optional icon', (WidgetTester tester) async { - await tester.pumpWidget(const MaterialApp( - home: Scaffold( + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( bottomSheet: SizedBox( width: 200, height: 50, @@ -2521,8 +2531,9 @@ void main() { }); testWidgets('Fixed snackbar can display optional icon without action', (WidgetTester tester) async { - await tester.pumpWidget(const MaterialApp( - home: Scaffold( + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( bottomSheet: SizedBox( width: 200, height: 50, @@ -2549,8 +2560,9 @@ void main() { testWidgets( 'Floating width snackbar can display optional icon without action', (WidgetTester tester) async { - await tester.pumpWidget(const MaterialApp( - home: Scaffold( + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( bottomSheet: SizedBox( width: 200, height: 50, @@ -2575,8 +2587,9 @@ void main() { }); testWidgets('Floating multi-line snackbar with icon is aligned correctly', (WidgetTester tester) async { - await tester.pumpWidget(const MaterialApp( - home: Scaffold( + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( bottomSheet: SizedBox( width: 200, height: 50, @@ -2602,8 +2615,9 @@ void main() { }); testWidgets('Floating multi-line snackbar with icon and actionOverflowThreshold=1 is aligned correctly', (WidgetTester tester) async { - await tester.pumpWidget(const MaterialApp( - home: Scaffold( + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( bottomSheet: SizedBox( width: 200, height: 50, @@ -2622,10 +2636,10 @@ void main() { behavior: SnackBarBehavior.floating, actionOverflowThreshold: 1, )); - await tester.pumpAndSettle(); // Have the SnackBar fully animate out. + await tester.pumpAndSettle(); // Have the SnackBar fully animate in. await expectLater(find.byType(MaterialApp), - matchesGoldenFile('snack_bar.goldenTest.multiLineWithIconWithZeroActionOverflowThreshold.png')); + matchesGoldenFile('snack_bar.goldenTest.multiLineWithIconWithZeroActionOverflowThreshold.png')); }); testWidgets( diff --git a/packages/flutter/test/material/snack_bar_theme_test.dart b/packages/flutter/test/material/snack_bar_theme_test.dart index d7a266c919a17..be25edd2d5a95 100644 --- a/packages/flutter/test/material/snack_bar_theme_test.dart +++ b/packages/flutter/test/material/snack_bar_theme_test.dart @@ -98,7 +98,10 @@ void main() { testWidgets('Passing no SnackBarThemeData returns defaults', (WidgetTester tester) async { const String text = 'I am a snack bar.'; + final ThemeData theme = ThemeData(); + final bool material3 = theme.useMaterial3; await tester.pumpWidget(MaterialApp( + theme: theme, home: Scaffold( body: Builder( builder: (BuildContext context) { @@ -123,8 +126,10 @@ void main() { final Material material = _getSnackBarMaterial(tester); final RenderParagraph content = _getSnackBarTextRenderObject(tester, text); - expect(content.text.style, Typography.material2018().white.titleMedium); - expect(material.color, const Color(0xFF333333)); + expect(content.text.style, material3 + ? Typography.material2021().englishLike.bodyMedium?.merge(Typography.material2021().black.bodyMedium).copyWith(color: theme.colorScheme.onInverseSurface, decorationColor: theme.colorScheme.onSurface) + : Typography.material2018().white.titleMedium); + expect(material.color, material3 ? theme.colorScheme.inverseSurface : const Color(0xFF333333)); expect(material.elevation, 6.0); expect(material.shape, null); }); diff --git a/packages/flutter/test/material/stepper_test.dart b/packages/flutter/test/material/stepper_test.dart index 3fd1a817b8556..d85070b607b4f 100644 --- a/packages/flutter/test/material/stepper_test.dart +++ b/packages/flutter/test/material/stepper_test.dart @@ -217,9 +217,12 @@ void main() { testWidgets('Stepper button test', (WidgetTester tester) async { bool continuePressed = false; bool cancelPressed = false; + final ThemeData theme = ThemeData(); + final bool material3 = theme.useMaterial3; await tester.pumpWidget( MaterialApp( + theme: theme, home: Material( child: Stepper( type: StepperType.horizontal, @@ -250,8 +253,8 @@ void main() { ), ); - await tester.tap(find.text('CONTINUE')); - await tester.tap(find.text('CANCEL')); + await tester.tap(find.text(material3 ? 'Continue' : 'CONTINUE')); + await tester.tap(find.text(material3 ? 'Cancel' : 'CANCEL')); expect(continuePressed, isTrue); expect(cancelPressed, isTrue); @@ -857,33 +860,39 @@ testWidgets('Stepper custom indexed controls test', (WidgetTester tester) async } const OutlinedBorder buttonShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2))); - const Rect continueButtonRect = Rect.fromLTRB(24.0, 212.0, 168.0, 260.0); - const Rect cancelButtonRect = Rect.fromLTRB(176.0, 212.0, 292.0, 260.0); - - await tester.pumpWidget(buildFrame(ThemeData.light())); - - expect(buttonMaterial('CONTINUE').color!.value, 0xff2196f3); - expect(buttonMaterial('CONTINUE').textStyle!.color!.value, 0xffffffff); - expect(buttonMaterial('CONTINUE').shape, buttonShape); - expect(tester.getRect(find.widgetWithText(TextButton, 'CONTINUE')), continueButtonRect); - - expect(buttonMaterial('CANCEL').color!.value, 0); - expect(buttonMaterial('CANCEL').textStyle!.color!.value, 0x8a000000); - expect(buttonMaterial('CANCEL').shape, buttonShape); - expect(tester.getRect(find.widgetWithText(TextButton, 'CANCEL')), cancelButtonRect); - await tester.pumpWidget(buildFrame(ThemeData.dark())); + final ThemeData themeLight = ThemeData.light(); + final bool material3Light = themeLight.useMaterial3; + await tester.pumpWidget(buildFrame(themeLight)); + + final String continueStr = material3Light ? 'Continue' : 'CONTINUE'; + final String cancelStr = material3Light ? 'Cancel' : 'CANCEL'; + final Rect continueButtonRect = material3Light ? const Rect.fromLTRB(24.0, 212.0, 169.0, 260.0) : const Rect.fromLTRB(24.0, 212.0, 168.0, 260.0); + final Rect cancelButtonRect = material3Light ? const Rect.fromLTRB(177.0, 212.0, 294.0, 260.0) : const Rect.fromLTRB(176.0, 212.0, 292.0, 260.0); + expect(buttonMaterial(continueStr).color!.value, material3Light ? themeLight.colorScheme.primary.value : 0xff2196f3); + expect(buttonMaterial(continueStr).textStyle!.color!.value, 0xffffffff); + expect(buttonMaterial(continueStr).shape, buttonShape); + expect(tester.getRect(find.widgetWithText(TextButton, continueStr)), continueButtonRect); + + expect(buttonMaterial(cancelStr).color!.value, 0); + expect(buttonMaterial(cancelStr).textStyle!.color!.value, 0x8a000000); + expect(buttonMaterial(cancelStr).shape, buttonShape); + expect(tester.getRect(find.widgetWithText(TextButton, cancelStr)), cancelButtonRect); + + final ThemeData themeDark = ThemeData.dark(); + final bool material3Dark = themeDark.useMaterial3; + await tester.pumpWidget(buildFrame(themeDark)); await tester.pumpAndSettle(); // Complete the theme animation. - expect(buttonMaterial('CONTINUE').color!.value, 0); - expect(buttonMaterial('CONTINUE').textStyle!.color!.value, 0xffffffff); - expect(buttonMaterial('CONTINUE').shape, buttonShape); - expect(tester.getRect(find.widgetWithText(TextButton, 'CONTINUE')), continueButtonRect); + expect(buttonMaterial(continueStr).color!.value, 0); + expect(buttonMaterial(continueStr).textStyle!.color!.value, material3Dark ? themeDark.colorScheme.onSurface.value : 0xffffffff); + expect(buttonMaterial(continueStr).shape, buttonShape); + expect(tester.getRect(find.widgetWithText(TextButton, continueStr)), continueButtonRect); - expect(buttonMaterial('CANCEL').color!.value, 0); - expect(buttonMaterial('CANCEL').textStyle!.color!.value, 0xb3ffffff); - expect(buttonMaterial('CANCEL').shape, buttonShape); - expect(tester.getRect(find.widgetWithText(TextButton, 'CANCEL')), cancelButtonRect); + expect(buttonMaterial(cancelStr).color!.value, 0); + expect(buttonMaterial(cancelStr).textStyle!.color!.value, 0xb3ffffff); + expect(buttonMaterial(cancelStr).shape, buttonShape); + expect(tester.getRect(find.widgetWithText(TextButton, cancelStr)), cancelButtonRect); }); testWidgets('Stepper disabled button styles', (WidgetTester tester) async { @@ -910,22 +919,28 @@ testWidgets('Stepper custom indexed controls test', (WidgetTester tester) async ); } - await tester.pumpWidget(buildFrame(ThemeData.light())); + final ThemeData themeLight = ThemeData.light(); + final bool material3Light = themeLight.useMaterial3; + await tester.pumpWidget(buildFrame(themeLight)); - expect(buttonMaterial('CONTINUE').color!.value, 0); - expect(buttonMaterial('CONTINUE').textStyle!.color!.value, 0x61000000); + final String continueStr = material3Light ? 'Continue' : 'CONTINUE'; + final String cancelStr = material3Light ? 'Cancel' : 'CANCEL'; + expect(buttonMaterial(continueStr).color!.value, 0); + expect(buttonMaterial(continueStr).textStyle!.color!.value, material3Light ? themeLight.colorScheme.onSurface.withOpacity(0.38).value : 0x61000000); - expect(buttonMaterial('CANCEL').color!.value, 0); - expect(buttonMaterial('CANCEL').textStyle!.color!.value, 0x61000000); + expect(buttonMaterial(cancelStr).color!.value, 0); + expect(buttonMaterial(cancelStr).textStyle!.color!.value, material3Light ? themeLight.colorScheme.onSurface.withOpacity(0.38).value : 0x61000000); - await tester.pumpWidget(buildFrame(ThemeData.dark())); + final ThemeData themeDark = ThemeData.dark(); + final bool material3Dark = themeDark.useMaterial3; + await tester.pumpWidget(buildFrame(themeDark)); await tester.pumpAndSettle(); // Complete the theme animation. - expect(buttonMaterial('CONTINUE').color!.value, 0); - expect(buttonMaterial('CONTINUE').textStyle!.color!.value, 0x61ffffff); + expect(buttonMaterial(continueStr).color!.value, 0); + expect(buttonMaterial(continueStr).textStyle!.color!.value, material3Dark ? themeDark.colorScheme.onSurface.withOpacity(0.38).value : 0x61ffffff); - expect(buttonMaterial('CANCEL').color!.value, 0); - expect(buttonMaterial('CANCEL').textStyle!.color!.value, 0x61ffffff); + expect(buttonMaterial(cancelStr).color!.value, 0); + expect(buttonMaterial(cancelStr).textStyle!.color!.value, material3Dark ? themeDark.colorScheme.onSurface.withOpacity(0.38).value : 0x61ffffff); }); testWidgets('Vertical and Horizontal Stepper physics test', (WidgetTester tester) async { @@ -957,6 +972,37 @@ testWidgets('Stepper custom indexed controls test', (WidgetTester tester) async } }); + testWidgets('ScrollController is passed to the stepper listview', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + for (final StepperType type in StepperType.values) { + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Stepper( + controller: controller, + type: type, + steps: const <Step>[ + Step( + title: Text('Step 1'), + content: SizedBox( + width: 100.0, + height: 100.0, + ), + ), + ], + ), + ), + ), + ); + + final ListView listView = tester.widget<ListView>( + find.descendant(of: find.byType(Stepper), + matching: find.byType(ListView), + )); + expect(listView.controller, controller); + } + }); + testWidgets('Stepper horizontal size test', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/pull/77732 Widget buildFrame({ bool isActive = true, Brightness? brightness }) { diff --git a/packages/flutter/test/material/switch_list_tile_test.dart b/packages/flutter/test/material/switch_list_tile_test.dart index ca4adb4fff5d8..e1ffc363765f4 100644 --- a/packages/flutter/test/material/switch_list_tile_test.dart +++ b/packages/flutter/test/material/switch_list_tile_test.dart @@ -116,15 +116,16 @@ void main() { semantics.dispose(); }); - testWidgets('SwitchListTile has the right colors', (WidgetTester tester) async { + testWidgets('Material2 - SwitchListTile has the right colors', (WidgetTester tester) async { bool value = false; await tester.pumpWidget( MediaQuery( data: const MediaQueryData(padding: EdgeInsets.all(8.0)), - child: Directionality( - textDirection: TextDirection.ltr, - child: - StatefulBuilder( + child: Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Material( child: SwitchListTile( @@ -139,6 +140,7 @@ void main() { ), ); }, + ), ), ), ), @@ -168,6 +170,57 @@ void main() { ); }); + testWidgets('Material3 - SwitchListTile has the right colors', (WidgetTester tester) async { + bool value = false; + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(padding: EdgeInsets.all(8.0)), + child: Theme( + data: ThemeData(useMaterial3: true), + child: Directionality( + textDirection: TextDirection.ltr, + child: + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Material( + child: SwitchListTile( + value: value, + onChanged: (bool newValue) { + setState(() { value = newValue; }); + }, + activeColor: Colors.red[500], + activeTrackColor: Colors.green[500], + inactiveThumbColor: Colors.yellow[500], + inactiveTrackColor: Colors.blue[500], + ), + ); + }, + ), + ), + ), + ), + ); + + expect( + find.byType(Switch), + paints + ..rrect(color: Colors.blue[500]) + ..rrect() + ..rrect(color: Colors.yellow[500]) + ); + + await tester.tap(find.byType(Switch)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect(color: Colors.green[500]) + ..rrect() + ..rrect(color: Colors.red[500]) + ); + }); + testWidgets('SwitchListTile.adaptive delegates to', (WidgetTester tester) async { bool value = false; @@ -586,7 +639,7 @@ void main() { child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return Container( - width: 100, + width: 500, height: 100, color: Colors.white, child: SwitchListTile( @@ -613,12 +666,12 @@ void main() { Material.of(tester.element(find.byKey(key))), paints..rect()..rect( color: Colors.orange[500], - rect: const Rect.fromLTRB(350.0, 250.0, 450.0, 350.0), + rect: const Rect.fromLTRB(150.0, 250.0, 650.0, 350.0), ) ); }); - testWidgets('SwitchListTile respects thumbColor in active/enabled states', (WidgetTester tester) async { + testWidgets('Material2 - SwitchListTile respects thumbColor in active/enabled states', (WidgetTester tester) async { const Color activeEnabledThumbColor = Color(0xFF000001); const Color activeDisabledThumbColor = Color(0xFF000002); const Color inactiveEnabledThumbColor = Color(0xFF000003); @@ -640,8 +693,10 @@ void main() { final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor); Widget buildSwitchListTile({required bool enabled, required bool selected}) { - return wrap( - child: StatefulBuilder( + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return SwitchListTile( value: selected, @@ -649,25 +704,22 @@ void main() { onChanged: enabled ? (_) { } : null, ); }), + ), ); } await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: false)); await tester.pumpAndSettle(); expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect()..rrect()..rrect()..rrect() - ..rrect(color: inactiveDisabledThumbColor), + Material.of(tester.element(find.byType(Switch))), + paints..rrect()..rrect()..rrect()..rrect()..rrect(color: inactiveDisabledThumbColor) ); await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: true)); await tester.pumpAndSettle(); expect( Material.of(tester.element(find.byType(Switch))), - paints - ..rrect()..rrect()..rrect()..rrect() - ..rrect(color: activeDisabledThumbColor), + paints..rrect()..rrect()..rrect()..rrect()..rrect(color: activeDisabledThumbColor) ); await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: false)); @@ -675,9 +727,7 @@ void main() { expect( Material.of(tester.element(find.byType(Switch))), - paints - ..rrect()..rrect()..rrect()..rrect() - ..rrect(color: inactiveEnabledThumbColor), + paints..rrect()..rrect()..rrect()..rrect()..rrect(color: inactiveEnabledThumbColor) ); await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: true)); @@ -685,13 +735,79 @@ void main() { expect( Material.of(tester.element(find.byType(Switch))), - paints - ..rrect()..rrect()..rrect()..rrect() - ..rrect(color: activeEnabledThumbColor), + paints..rrect()..rrect()..rrect()..rrect()..rrect(color: activeEnabledThumbColor) + ); + }); + + testWidgets('Material3 - SwitchListTile respects thumbColor in active/enabled states', (WidgetTester tester) async { + const Color activeEnabledThumbColor = Color(0xFF000001); + const Color activeDisabledThumbColor = Color(0xFF000002); + const Color inactiveEnabledThumbColor = Color(0xFF000003); + const Color inactiveDisabledThumbColor = Color(0xFF000004); + + Color getThumbColor(Set<MaterialState> states) { + if (states.contains(MaterialState.disabled)) { + if (states.contains(MaterialState.selected)) { + return activeDisabledThumbColor; + } + return inactiveDisabledThumbColor; + } + if (states.contains(MaterialState.selected)) { + return activeEnabledThumbColor; + } + return inactiveEnabledThumbColor; + } + + final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor); + + Widget buildSwitchListTile({required bool enabled, required bool selected}) { + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: Material( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return SwitchListTile( + value: selected, + thumbColor: thumbColor, + onChanged: enabled ? (_) { } : null, + ); + }), + ), + ); + } + + await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: false)); + await tester.pumpAndSettle(); + expect( + Material.of(tester.element(find.byType(Switch))), + paints..rrect()..rrect()..rrect(color: inactiveDisabledThumbColor), + ); + + await tester.pumpWidget(buildSwitchListTile(enabled: false, selected: true)); + await tester.pumpAndSettle(); + expect( + Material.of(tester.element(find.byType(Switch))), + paints..rrect()..rrect()..rrect(color: activeDisabledThumbColor), + ); + + await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: false)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints..rrect()..rrect()..rrect(color: inactiveEnabledThumbColor), + ); + + await tester.pumpWidget(buildSwitchListTile(enabled: true, selected: true)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints..rrect()..rrect()..rrect(color: activeEnabledThumbColor), ); }); - testWidgets('SwitchListTile respects thumbColor in hovered/pressed states', (WidgetTester tester) async { + testWidgets('Material2 - SwitchListTile respects thumbColor in hovered/pressed states', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const Color hoveredThumbColor = Color(0xFF4caf50); const Color pressedThumbColor = Color(0xFFF44336); @@ -710,8 +826,8 @@ void main() { Widget buildSwitchListTile() { return MaterialApp( - theme: ThemeData(), - home: wrap( + theme: ThemeData(useMaterial3: false), + home: Material( child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return SwitchListTile( @@ -735,9 +851,7 @@ void main() { expect( Material.of(tester.element(find.byType(Switch))), - paints - ..rrect()..rrect()..rrect()..rrect() - ..rrect(color: hoveredThumbColor), + paints..rrect()..rrect()..rrect()..rrect()..rrect(color: hoveredThumbColor), ); // On pressed state @@ -745,9 +859,63 @@ void main() { await tester.pumpAndSettle(); expect( Material.of(tester.element(find.byType(Switch))), - paints - ..rrect()..rrect()..rrect()..rrect() - ..rrect(color: pressedThumbColor), + paints..rrect()..rrect()..rrect()..rrect()..rrect(color: pressedThumbColor), + ); + }); + + testWidgets('Material3 - SwitchListTile respects thumbColor in hovered/pressed states', (WidgetTester tester) async { + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + const Color hoveredThumbColor = Color(0xFF4caf50); + const Color pressedThumbColor = Color(0xFFF44336); + + Color getThumbColor(Set<MaterialState> states) { + if (states.contains(MaterialState.pressed)) { + return pressedThumbColor; + } + if (states.contains(MaterialState.hovered)) { + return hoveredThumbColor; + } + return Colors.transparent; + } + + final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor); + + Widget buildSwitchListTile() { + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: Material( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return SwitchListTile( + value: false, + thumbColor: thumbColor, + onChanged: (_) { }, + ); + }), + ), + ); + } + + await tester.pumpWidget(buildSwitchListTile()); + await tester.pumpAndSettle(); + + // Start hovering + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.moveTo(tester.getCenter(find.byType(Switch))); + + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints..rrect()..rrect()..rrect(color: hoveredThumbColor), + ); + + // On pressed state + await tester.press(find.byType(Switch)); + await tester.pumpAndSettle(); + expect( + Material.of(tester.element(find.byType(Switch))), + paints..rrect()..rrect()..rrect(color: pressedThumbColor), ); }); @@ -942,17 +1110,20 @@ void main() { ); }); - testWidgets('SwitchListTile respects materialTapTargetSize', (WidgetTester tester) async { + testWidgets('Material2 - SwitchListTile respects materialTapTargetSize', (WidgetTester tester) async { Widget buildSwitchListTile(MaterialTapTargetSize materialTapTargetSize) { - return wrap( - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return SwitchListTile( - materialTapTargetSize: materialTapTargetSize, - value: false, - onChanged: (_) {}, - ); - }), + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return SwitchListTile( + materialTapTargetSize: materialTapTargetSize, + value: false, + onChanged: (_) {}, + ); + }), + ), ); } @@ -967,12 +1138,75 @@ void main() { expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0)); }); - testWidgets('SwitchListTile.adaptive respects applyCupertinoTheme', (WidgetTester tester) async { - final ThemeData theme = ThemeData(); + testWidgets('Material3 - SwitchListTile respects materialTapTargetSize', (WidgetTester tester) async { + Widget buildSwitchListTile(MaterialTapTargetSize materialTapTargetSize) { + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: Material( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return SwitchListTile( + materialTapTargetSize: materialTapTargetSize, + value: false, + onChanged: (_) {}, + ); + }), + ), + ); + } + + await tester.pumpWidget(buildSwitchListTile(MaterialTapTargetSize.padded)); + final Switch switchWidget = tester.widget<Switch>(find.byType(Switch)); + expect(switchWidget.materialTapTargetSize, MaterialTapTargetSize.padded); + expect(tester.getSize(find.byType(Switch)), const Size(60.0, 48.0)); + + await tester.pumpWidget(buildSwitchListTile(MaterialTapTargetSize.shrinkWrap)); + final Switch switchWidget1 = tester.widget<Switch>(find.byType(Switch)); + expect(switchWidget1.materialTapTargetSize, MaterialTapTargetSize.shrinkWrap); + expect(tester.getSize(find.byType(Switch)), const Size(60.0, 40.0)); + }); + + testWidgets('Material2 - SwitchListTile.adaptive respects applyCupertinoTheme', (WidgetTester tester) async { Widget buildSwitchListTile(bool applyCupertinoTheme, TargetPlatform platform) { return MaterialApp( - theme: theme.copyWith(platform: platform), - home: wrap( + theme: ThemeData(useMaterial3: false, platform: platform), + home: Material( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return SwitchListTile.adaptive( + applyCupertinoTheme: applyCupertinoTheme, + value: true, + onChanged: (_) {}, + ); + }), + ), + ); + } + + for (final TargetPlatform platform in <TargetPlatform>[ TargetPlatform.iOS, TargetPlatform.macOS ]) { + await tester.pumpWidget(buildSwitchListTile(true, platform)); + await tester.pumpAndSettle(); + expect(find.byType(CupertinoSwitch), findsOneWidget); + expect( + Material.of(tester.element(find.byType(Switch))), + paints..rrect(color: const Color(0xFF2196F3)), + ); + + await tester.pumpWidget(buildSwitchListTile(false, platform)); + await tester.pumpAndSettle(); + expect(find.byType(CupertinoSwitch), findsOneWidget); + expect( + Material.of(tester.element(find.byType(Switch))), + paints..rrect(color: const Color(0xFF34C759)), + ); + } + }); + + testWidgets('Material3 - SwitchListTile.adaptive respects applyCupertinoTheme', (WidgetTester tester) async { + Widget buildSwitchListTile(bool applyCupertinoTheme, TargetPlatform platform) { + return MaterialApp( + theme: ThemeData(useMaterial3: true, platform: platform), + home: Material( child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { return SwitchListTile.adaptive( @@ -991,7 +1225,7 @@ void main() { expect(find.byType(CupertinoSwitch), findsOneWidget); expect( Material.of(tester.element(find.byType(Switch))), - paints..rrect(color: theme.useMaterial3 ? const Color(0xFF6750A4) : const Color(0xFF2196F3)), + paints..rrect(color: const Color(0xFF6750A4)), ); await tester.pumpWidget(buildSwitchListTile(false, platform)); @@ -1004,17 +1238,20 @@ void main() { } }); - testWidgets('SwitchListTile respects materialTapTargetSize', (WidgetTester tester) async { + testWidgets('Material2 - SwitchListTile respects materialTapTargetSize', (WidgetTester tester) async { Widget buildSwitchListTile(MaterialTapTargetSize materialTapTargetSize) { - return wrap( - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return SwitchListTile( - materialTapTargetSize: materialTapTargetSize, - value: false, - onChanged: (_) {}, - ); - }), + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return SwitchListTile( + materialTapTargetSize: materialTapTargetSize, + value: false, + onChanged: (_) {}, + ); + }), + ), ); } @@ -1029,6 +1266,34 @@ void main() { expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0)); }); + testWidgets('Material3 - SwitchListTile respects materialTapTargetSize', (WidgetTester tester) async { + Widget buildSwitchListTile(MaterialTapTargetSize materialTapTargetSize) { + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: Material( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return SwitchListTile( + materialTapTargetSize: materialTapTargetSize, + value: false, + onChanged: (_) {}, + ); + }), + ), + ); + } + + await tester.pumpWidget(buildSwitchListTile(MaterialTapTargetSize.padded)); + final Switch switchWidget = tester.widget<Switch>(find.byType(Switch)); + expect(switchWidget.materialTapTargetSize, MaterialTapTargetSize.padded); + expect(tester.getSize(find.byType(Switch)), const Size(60.0, 48.0)); + + await tester.pumpWidget(buildSwitchListTile(MaterialTapTargetSize.shrinkWrap)); + final Switch switchWidget1 = tester.widget<Switch>(find.byType(Switch)); + expect(switchWidget1.materialTapTargetSize, MaterialTapTargetSize.shrinkWrap); + expect(tester.getSize(find.byType(Switch)), const Size(60.0, 40.0)); + }); + testWidgets('SwitchListTile passes the value of dragStartBehavior to Switch', (WidgetTester tester) async { Widget buildSwitchListTile(DragStartBehavior dragStartBehavior) { return wrap( diff --git a/packages/flutter/test/material/switch_test.dart b/packages/flutter/test/material/switch_test.dart index 0264a4896873a..f7377f97d3d42 100644 --- a/packages/flutter/test/material/switch_test.dart +++ b/packages/flutter/test/material/switch_test.dart @@ -106,7 +106,7 @@ void main() { expect(tester.getSize(find.byType(Switch)), material3 ? const Size(60.0, 40.0) : const Size(59.0, 40.0)); }); - testWidgets('Switch does not get distorted upon changing constraints with parent - M2', (WidgetTester tester) async { + testWidgets('Material2 - Switch does not get distorted upon changing constraints with parent', (WidgetTester tester) async { const double maxWidth = 300; const double maxHeight = 100; @@ -114,6 +114,7 @@ void main() { Widget buildSwitch({required double width, required double height}) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Directionality( textDirection: TextDirection.ltr, @@ -144,7 +145,7 @@ void main() { )); await expectLater( find.byKey(boundaryKey), - matchesGoldenFile('switch_test.big.on.png'), + matchesGoldenFile('m2_switch_test.big.on.png'), ); await tester.pumpWidget(buildSwitch( @@ -153,7 +154,59 @@ void main() { )); await expectLater( find.byKey(boundaryKey), - matchesGoldenFile('switch_test.small.on.png'), + matchesGoldenFile('m2_switch_test.small.on.png'), + ); + }); + + testWidgets('Material3 - Switch does not get distorted upon changing constraints with parent', (WidgetTester tester) async { + const double maxWidth = 300; + const double maxHeight = 100; + + const ValueKey<String> boundaryKey = ValueKey<String>('switch container'); + + Widget buildSwitch({required double width, required double height}) { + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: Scaffold( + body: Directionality( + textDirection: TextDirection.ltr, + child: SizedBox( + width: maxWidth, + height: maxHeight, + child: RepaintBoundary( + key: boundaryKey, + child: SizedBox( + width: width, + height: height, + child: Switch( + dragStartBehavior: DragStartBehavior.down, + value: true, + onChanged: (_) {}, + ), + ), + ), + ), + ), + ), + ); + } + + await tester.pumpWidget(buildSwitch( + width: maxWidth, + height: maxHeight, + )); + await expectLater( + find.byKey(boundaryKey), + matchesGoldenFile('m3_switch_test.big.on.png'), + ); + + await tester.pumpWidget(buildSwitch( + width: 20, + height: 10, + )); + await expectLater( + find.byKey(boundaryKey), + matchesGoldenFile('m3_switch_test.small.on.png'), ); }); @@ -347,27 +400,30 @@ void main() { expect(value, isFalse); }); - testWidgets('Switch has default colors when enabled', (WidgetTester tester) async { + testWidgets('Material2 - Switch has default colors when enabled', (WidgetTester tester) async { bool value = false; await tester.pumpWidget( - Directionality( - textDirection: TextDirection.rtl, - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Material( - child: Center( - child: Switch( - dragStartBehavior: DragStartBehavior.down, - value: value, - onChanged: (bool newValue) { - setState(() { - value = newValue; - }); - }, + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Directionality( + textDirection: TextDirection.rtl, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Material( + child: Center( + child: Switch( + dragStartBehavior: DragStartBehavior.down, + value: value, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + ), ), - ), - ); - }, + ); + }, + ), ), ), ); @@ -403,18 +459,86 @@ void main() { ); }); - testWidgets('Switch has default colors when disabled', (WidgetTester tester) async { + testWidgets('Material3 - Switch has default colors when enabled', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + final ColorScheme colors = theme.colorScheme; + bool value = false; await tester.pumpWidget( - const Directionality( - textDirection: TextDirection.rtl, - child: Material( - child: Center( - child: Switch( - value: false, - onChanged: null, - ), + MaterialApp( + theme: theme, + home: Directionality( + textDirection: TextDirection.rtl, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Material( + child: Center( + child: Switch( + dragStartBehavior: DragStartBehavior.down, + value: value, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + ), + ), + ); + }, ), + ), + ), + ); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..save() + ..rrect( + style: PaintingStyle.fill, + color: colors.surfaceVariant, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect( + style: PaintingStyle.stroke, + color: colors.outline, + rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)), + ) + ..rrect(color: colors.outline), // thumb color + reason: 'Inactive enabled switch should match these colors', + ); + await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0)); + await tester.pump(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..save() + ..rrect( + style: PaintingStyle.fill, + color: colors.primary, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), ) + ..rrect() + ..rrect(color: colors.onPrimary), // thumb color + reason: 'Active enabled switch should match these colors', + ); + }); + + testWidgets('Material2 - Switch has default colors when disabled', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Directionality( + textDirection: TextDirection.rtl, + child: Material( + child: Center( + child: Switch( + value: false, + onChanged: null, + ), + ), + ) + ), ), ); @@ -433,13 +557,16 @@ void main() { ); await tester.pumpWidget( - const Directionality( - textDirection: TextDirection.rtl, - child: Material( - child: Center( - child: Switch( - value: true, - onChanged: null, + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Directionality( + textDirection: TextDirection.rtl, + child: Material( + child: Center( + child: Switch( + value: true, + onChanged: null, + ), ), ), ), @@ -461,11 +588,80 @@ void main() { ); }); - testWidgets('Switch default overlayColor resolves hovered/focused state', (WidgetTester tester) async { + testWidgets('Material3 - Inactive Switch has default colors when disabled', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(useMaterial3: true); + final ColorScheme colors = themeData.colorScheme; + + await tester.pumpWidget(MaterialApp( + theme: themeData, + home: const Directionality( + textDirection: TextDirection.rtl, + child: Material( + child: Center( + child: Switch( + value: false, + onChanged: null, + ), + ), + ), + ), + )); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..save() + ..rrect( + style: PaintingStyle.fill, + color: colors.surfaceVariant.withOpacity(0.12), + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect( + style: PaintingStyle.stroke, + color: colors.onSurface.withOpacity(0.12), + rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)), + ) + ..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)), // thumb color + reason: 'Inactive disabled switch should match these colors', + ); + }); + + testWidgets('Material3 - Active Switch has default colors when disabled', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(useMaterial3: true); + final ColorScheme colors = themeData.colorScheme; + await tester.pumpWidget(MaterialApp( + theme: themeData, + home: const Directionality( + textDirection: TextDirection.rtl, + child: Material( + child: Center( + child: Switch( + value: true, + onChanged: null, + ), + ), + ), + ), + )); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..save() + ..rrect( + style: PaintingStyle.fill, + color: colors.onSurface.withOpacity(0.12), + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect() + ..rrect(color: colors.surface), // thumb color + reason: 'Active disabled switch should match these colors', + ); + }); + + testWidgets('Material2 - Switch default overlayColor resolves hovered/focused state', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; - final bool material3 = theme.useMaterial3; - Finder findSwitch() { return find.byWidgetPredicate((Widget widget) => widget is Switch); } @@ -474,7 +670,7 @@ void main() { return Material.of(tester.element(findSwitch())); } await tester.pumpWidget(MaterialApp( - theme: theme, + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Switch( focusNode: focusNode, @@ -490,7 +686,7 @@ void main() { expect(getSwitchMaterial(tester), paints - ..circle(color: material3 ? theme.colorScheme.primary.withOpacity(0.12) : theme.focusColor) + ..circle(color: theme.focusColor) ); // On both hovered and focused, the overlay color should show hovered overlay color. @@ -503,50 +699,98 @@ void main() { await tester.pumpAndSettle(); expect(getSwitchMaterial(tester), - paints..circle(color: material3 ? theme.colorScheme.primary.withOpacity(0.08) : theme.hoverColor) + paints..circle(color: theme.hoverColor) ); }); - testWidgets('Switch can be set color', (WidgetTester tester) async { - bool value = false; - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.rtl, - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Material( - child: Center( - child: Switch( - dragStartBehavior: DragStartBehavior.down, - value: value, - onChanged: (bool newValue) { - setState(() { - value = newValue; - }); - }, - activeColor: Colors.red[500], - activeTrackColor: Colors.green[500], - inactiveThumbColor: Colors.yellow[500], - inactiveTrackColor: Colors.blue[500], - ), - ), - ); - }, - ), - ), - ); + testWidgets('Material3 - Switch default overlayColor resolves hovered/focused state', (WidgetTester tester) async { + final ThemeData theme = ThemeData(useMaterial3: true); + final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - color: Colors.blue[500], - rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)), - ) - ..rrect(color: const Color(0x33000000)) - ..rrect(color: const Color(0x24000000)) - ..rrect(color: const Color(0x1f000000)) - ..rrect(color: Colors.yellow[500]), + Finder findSwitch() { + return find.byWidgetPredicate((Widget widget) => widget is Switch); + } + + MaterialInkController? getSwitchMaterial(WidgetTester tester) { + return Material.of(tester.element(findSwitch())); + } + await tester.pumpWidget(MaterialApp( + theme: theme, + home: Scaffold( + body: Switch( + focusNode: focusNode, + value: true, + onChanged: (_) { }, + ), + ), + )); + + // Focused. + focusNode.requestFocus(); + await tester.pumpAndSettle(); + + expect(getSwitchMaterial(tester), + paints..circle(color: theme.colorScheme.primary.withOpacity(0.12)) + ); + + // On both hovered and focused, the overlay color should show hovered overlay color. + final Offset center = tester.getCenter(find.byType(Switch)); + final TestGesture gesture = await tester.createGesture( + kind: PointerDeviceKind.mouse, + ); + await gesture.addPointer(); + await gesture.moveTo(center); + await tester.pumpAndSettle(); + + expect(getSwitchMaterial(tester), + paints..circle(color: theme.colorScheme.primary.withOpacity(0.08)) + ); + }); + + testWidgets('Material2 - Switch can be set color', (WidgetTester tester) async { + bool value = false; + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Directionality( + textDirection: TextDirection.rtl, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Material( + child: Center( + child: Switch( + dragStartBehavior: DragStartBehavior.down, + value: value, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + activeColor: Colors.red[500], + activeTrackColor: Colors.green[500], + inactiveThumbColor: Colors.yellow[500], + inactiveTrackColor: Colors.blue[500], + ), + ), + ); + }, + ), + ), + ), + ); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + color: Colors.blue[500], + rrect: RRect.fromLTRBR(13.0, 17.0, 46.0, 31.0, const Radius.circular(7.0)), + ) + ..rrect(color: const Color(0x33000000)) + ..rrect(color: const Color(0x24000000)) + ..rrect(color: const Color(0x1f000000)) + ..rrect(color: Colors.yellow[500]), ); await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0)); await tester.pump(); @@ -565,6 +809,71 @@ void main() { ); }); + testWidgets('Material3 - Switch can be set color', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(useMaterial3: true); + final ColorScheme colors = themeData.colorScheme; + + bool value = false; + await tester.pumpWidget( + MaterialApp( + theme: themeData, + home: Directionality( + textDirection: TextDirection.rtl, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Material( + child: Center( + child: Switch( + dragStartBehavior: DragStartBehavior.down, + value: value, + onChanged: (bool newValue) { + setState(() { + value = newValue; + }); + }, + activeColor: Colors.red[500], + activeTrackColor: Colors.green[500], + inactiveThumbColor: Colors.yellow[500], + inactiveTrackColor: Colors.blue[500], + ), + ), + ); + }, + ), + ), + ), + ); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + style: PaintingStyle.fill, + color: Colors.blue[500], + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect( + style: PaintingStyle.stroke, + color: colors.outline, + ) + ..rrect(color: Colors.yellow[500]), // thumb color + ); + await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0)); + await tester.pump(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + style: PaintingStyle.fill, + color: Colors.green[500], + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect() + ..rrect(color: Colors.red[500]), // thumb color + ); + }); + testWidgets('Drag ends after animation completes', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/17773 @@ -849,12 +1158,13 @@ void main() { } }); - testWidgets('Switch is focusable and has correct focus color', (WidgetTester tester) async { + testWidgets('Material2 - Switch is focusable and has correct focus color', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; bool value = true; Widget buildApp({bool enabled = true}) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -930,6 +1240,93 @@ void main() { ); }); + testWidgets('Material3 - Switch is focusable and has correct focus color', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(useMaterial3: true); + final ColorScheme colors = themeData.colorScheme; + final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + bool value = true; + Widget buildApp({bool enabled = true}) { + return MaterialApp( + theme: themeData, + home: Material( + child: Center( + child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { + return Switch( + value: value, + onChanged: enabled ? (bool newValue) { + setState(() { + value = newValue; + }); + } : null, + focusColor: Colors.orange[500], + autofocus: true, + focusNode: focusNode, + ); + }), + ), + ), + ); + } + await tester.pumpWidget(buildApp()); + + // active, enabled switch + await tester.pumpAndSettle(); + expect(focusNode.hasPrimaryFocus, isTrue); + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + style: PaintingStyle.fill, + color: colors.primary, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..circle(color: Colors.orange[500]), + ); + + // Check the false value: inactive enabled switch + value = false; + await tester.pumpWidget(buildApp()); + await tester.pumpAndSettle(); + expect(focusNode.hasPrimaryFocus, isTrue); + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + style: PaintingStyle.fill, + color: colors.surfaceVariant, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect( + style: PaintingStyle.stroke, + color: colors.outline, + rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)), + ) + ..circle(color: Colors.orange[500]) + ); + + // Check what happens when disabled: inactive disabled switch. + value = false; + await tester.pumpWidget(buildApp(enabled: false)); + await tester.pumpAndSettle(); + expect(focusNode.hasPrimaryFocus, isFalse); + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + style: PaintingStyle.fill, + color: colors.surfaceVariant.withOpacity(0.12), + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect( + style: PaintingStyle.stroke, + color: colors.onSurface.withOpacity(0.12), + rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)), + ) + ..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)), + ); + }); + testWidgets('Switch with splash radius set', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const double splashRadius = 30; @@ -957,11 +1354,12 @@ void main() { ); }); - testWidgets('Switch can be hovered and has correct hover color', (WidgetTester tester) async { + testWidgets('Material2 - Switch can be hovered and has correct hover color', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; bool value = true; Widget buildApp({bool enabled = true}) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -1032,12 +1430,14 @@ void main() { ); }); - testWidgets('Switch can be toggled by keyboard shortcuts', (WidgetTester tester) async { + testWidgets('Material3 - Switch can be hovered and has correct hover color', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(useMaterial3: true); + final ColorScheme colors = themeData.colorScheme; tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; bool value = true; Widget buildApp({bool enabled = true}) { return MaterialApp( - theme: theme, + theme: themeData, home: Material( child: Center( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { @@ -1048,37 +1448,107 @@ void main() { value = newValue; }); } : null, - focusColor: Colors.orange[500], - autofocus: true, + hoverColor: Colors.orange[500], ); }), ), ), ); } + + // active enabled switch await tester.pumpWidget(buildApp()); await tester.pumpAndSettle(); - await tester.sendKeyEvent(LogicalKeyboardKey.enter); - await tester.pumpAndSettle(); - // On web, switches don't respond to the enter key. - expect(value, kIsWeb ? isTrue : isFalse); - await tester.sendKeyEvent(LogicalKeyboardKey.enter); - await tester.pumpAndSettle(); - expect(value, isTrue); - await tester.sendKeyEvent(LogicalKeyboardKey.space); + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + color: colors.primary, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect() + ..rrect(color: colors.onPrimary), + ); + + // Start hovering + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(); + await gesture.moveTo(tester.getCenter(find.byType(Switch))); + + await tester.pumpWidget(buildApp()); await tester.pumpAndSettle(); - expect(value, isFalse); - await tester.sendKeyEvent(LogicalKeyboardKey.space); + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + color: colors.primary, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..circle(color: Colors.orange[500]), + ); + + // Check what happens for disabled active switch + await tester.pumpWidget(buildApp(enabled: false)); await tester.pumpAndSettle(); - expect(value, isTrue); + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + color: colors.onSurface.withOpacity(0.12), + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect() + ..rrect(color: colors.surface.withOpacity(1.0)), + ); }); - testWidgets('Switch changes mouse cursor when hovered', (WidgetTester tester) async { - // Test Switch.adaptive() constructor - await tester.pumpWidget( - MaterialApp( + testWidgets('Switch can be toggled by keyboard shortcuts', (WidgetTester tester) async { + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + bool value = true; + Widget buildApp({bool enabled = true}) { + return MaterialApp( theme: theme, - home: Scaffold( + home: Material( + child: Center( + child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { + return Switch( + value: value, + onChanged: enabled ? (bool newValue) { + setState(() { + value = newValue; + }); + } : null, + focusColor: Colors.orange[500], + autofocus: true, + ); + }), + ), + ), + ); + } + await tester.pumpWidget(buildApp()); + await tester.pumpAndSettle(); + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + // On web, switches don't respond to the enter key. + expect(value, kIsWeb ? isTrue : isFalse); + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + expect(value, isTrue); + await tester.sendKeyEvent(LogicalKeyboardKey.space); + await tester.pumpAndSettle(); + expect(value, isFalse); + await tester.sendKeyEvent(LogicalKeyboardKey.space); + await tester.pumpAndSettle(); + expect(value, isTrue); + }); + + testWidgets('Switch changes mouse cursor when hovered', (WidgetTester tester) async { + // Test Switch.adaptive() constructor + await tester.pumpWidget( + MaterialApp( + theme: theme, + home: Scaffold( body: Align( alignment: Alignment.topLeft, child: Material( @@ -1224,7 +1694,7 @@ void main() { expect(updatedSwitchState.position.isDismissed, false); }); - testWidgets('Switch thumb color resolves in active/enabled states', (WidgetTester tester) async { + testWidgets('Material2 - Switch thumb color resolves in active/enabled states', (WidgetTester tester) async { const Color activeEnabledThumbColor = Color(0xFF000001); const Color activeDisabledThumbColor = Color(0xFF000002); const Color inactiveEnabledThumbColor = Color(0xFF000003); @@ -1247,14 +1717,17 @@ void main() { MaterialStateColor.resolveWith(getThumbColor); Widget buildSwitch({required bool enabled, required bool active}) { - return Directionality( - textDirection: TextDirection.rtl, - child: Material( - child: Center( - child: Switch( - thumbColor: thumbColor, - value: active, - onChanged: enabled ? (_) { } : null, + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Directionality( + textDirection: TextDirection.rtl, + child: Material( + child: Center( + child: Switch( + thumbColor: thumbColor, + value: active, + onChanged: enabled ? (_) { } : null, + ), ), ), ), @@ -1329,7 +1802,116 @@ void main() { ); }); - testWidgets('Switch thumb color resolves in hovered/focused states', (WidgetTester tester) async { + testWidgets('Material3 - Switch thumb color resolves in active/enabled states', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(useMaterial3: true); + final ColorScheme colors = themeData.colorScheme; + const Color activeEnabledThumbColor = Color(0xFF000001); + const Color activeDisabledThumbColor = Color(0xFF000002); + const Color inactiveEnabledThumbColor = Color(0xFF000003); + const Color inactiveDisabledThumbColor = Color(0xFF000004); + + Color getThumbColor(Set<MaterialState> states) { + if (states.contains(MaterialState.disabled)) { + if (states.contains(MaterialState.selected)) { + return activeDisabledThumbColor; + } + return inactiveDisabledThumbColor; + } + if (states.contains(MaterialState.selected)) { + return activeEnabledThumbColor; + } + return inactiveEnabledThumbColor; + } + + final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor); + + Widget buildSwitch({required bool enabled, required bool active}) { + return Theme( + data: themeData, + child: Directionality( + textDirection: TextDirection.rtl, + child: Material( + child: Center( + child: Switch( + thumbColor: thumbColor, + value: active, + onChanged: enabled ? (_) { } : null, + ), + ), + ), + ), + ); + } + + await tester.pumpWidget(buildSwitch(enabled: false, active: false)); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + style: PaintingStyle.fill, + color: colors.surfaceVariant.withOpacity(0.12), + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect( + style: PaintingStyle.stroke, + color: colors.onSurface.withOpacity(0.12), + rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)), + ) + ..rrect(color: inactiveDisabledThumbColor), + reason: 'Inactive disabled switch should default track and custom thumb color', + ); + + await tester.pumpWidget(buildSwitch(enabled: false, active: true)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + style: PaintingStyle.fill, + color: colors.onSurface.withOpacity(0.12), + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect() + ..rrect(color: activeDisabledThumbColor), + reason: 'Active disabled switch should match these colors', + ); + + await tester.pumpWidget(buildSwitch(enabled: true, active: false)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + style: PaintingStyle.fill, + color: colors.surfaceVariant, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect() + ..rrect(color: inactiveEnabledThumbColor), + reason: 'Inactive enabled switch should match these colors', + ); + + await tester.pumpWidget(buildSwitch(enabled: true, active: true)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + style: PaintingStyle.fill, + color: colors.primary, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect() + ..rrect(color: activeEnabledThumbColor), + reason: 'Active enabled switch should match these colors', + ); + }); + + testWidgets('Material2 - Switch thumb color resolves in hovered/focused states', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const Color hoveredThumbColor = Color(0xFF000001); @@ -1349,9 +1931,9 @@ void main() { MaterialStateColor.resolveWith(getThumbColor); Widget buildSwitch() { - return Directionality( - textDirection: TextDirection.rtl, - child: Material( + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( child: Center( child: Switch( focusNode: focusNode, @@ -1405,7 +1987,83 @@ void main() { ); }); - testWidgets('Track color resolves in active/enabled states', (WidgetTester tester) async { + testWidgets('Material3 - Switch thumb color resolves in hovered/focused states', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(useMaterial3: true); + final ColorScheme colors = themeData.colorScheme; + final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + const Color hoveredThumbColor = Color(0xFF000001); + const Color focusedThumbColor = Color(0xFF000002); + + Color getThumbColor(Set<MaterialState> states) { + if (states.contains(MaterialState.hovered)) { + return hoveredThumbColor; + } + if (states.contains(MaterialState.focused)) { + return focusedThumbColor; + } + return Colors.transparent; + } + + final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor); + + Widget buildSwitch() { + return MaterialApp( + theme: themeData, + home: Directionality( + textDirection: TextDirection.rtl, + child: Material( + child: Center( + child: Switch( + focusNode: focusNode, + autofocus: true, + value: true, + thumbColor: thumbColor, + onChanged: (_) { }, + ), + ), + ), + ), + ); + } + + await tester.pumpWidget(buildSwitch()); + await tester.pumpAndSettle(); + expect(focusNode.hasPrimaryFocus, isTrue); + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + style: PaintingStyle.fill, + color: colors.primary, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..circle(color: colors.primary.withOpacity(0.12)) + ..rrect(color: focusedThumbColor), + reason: 'active enabled switch should default track and custom thumb color', + ); + + // Start hovering + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(); + await gesture.moveTo(tester.getCenter(find.byType(Switch))); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + style: PaintingStyle.fill, + color: colors.primary, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..circle(color: colors.primary.withOpacity(0.08)) + ..rrect(color: hoveredThumbColor), + reason: 'active enabled switch should default track and custom thumb color', + ); + }); + + testWidgets('Material2 - Track color resolves in active/enabled states', (WidgetTester tester) async { const Color activeEnabledTrackColor = Color(0xFF000001); const Color activeDisabledTrackColor = Color(0xFF000002); const Color inactiveEnabledTrackColor = Color(0xFF000003); @@ -1428,9 +2086,9 @@ void main() { MaterialStateColor.resolveWith(getTrackColor); Widget buildSwitch({required bool enabled, required bool active}) { - return Directionality( - textDirection: TextDirection.rtl, - child: Material( + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Material( child: Center( child: Switch( trackColor: trackColor, @@ -1494,36 +2152,132 @@ void main() { ); }); - testWidgets('Switch track color resolves in hovered/focused states', (WidgetTester tester) async { - final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); - tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; - const Color hoveredTrackColor = Color(0xFF000001); - const Color focusedTrackColor = Color(0xFF000002); + testWidgets('Material3 - Track color resolves in active/enabled states', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(useMaterial3: true); + const Color activeEnabledTrackColor = Color(0xFF000001); + const Color activeDisabledTrackColor = Color(0xFF000002); + const Color inactiveEnabledTrackColor = Color(0xFF000003); + const Color inactiveDisabledTrackColor = Color(0xFF000004); Color getTrackColor(Set<MaterialState> states) { - if (states.contains(MaterialState.hovered)) { - return hoveredTrackColor; + if (states.contains(MaterialState.disabled)) { + if (states.contains(MaterialState.selected)) { + return activeDisabledTrackColor; + } + return inactiveDisabledTrackColor; } - if (states.contains(MaterialState.focused)) { - return focusedTrackColor; + if (states.contains(MaterialState.selected)) { + return activeEnabledTrackColor; } - return Colors.transparent; + return inactiveEnabledTrackColor; } final MaterialStateProperty<Color> trackColor = - MaterialStateColor.resolveWith(getTrackColor); + MaterialStateColor.resolveWith(getTrackColor); - Widget buildSwitch() { - return Directionality( - textDirection: TextDirection.rtl, - child: Material( - child: Center( - child: Switch( - focusNode: focusNode, - autofocus: true, - value: true, - trackColor: trackColor, - onChanged: (_) { }, + Widget buildSwitch({required bool enabled, required bool active}) { + return Theme( + data: themeData, + child: Directionality( + textDirection: TextDirection.rtl, + child: Material( + child: Center( + child: Switch( + trackColor: trackColor, + value: active, + onChanged: enabled ? (_) { } : null, + ), + ), + ), + ), + ); + } + + await tester.pumpWidget(buildSwitch(enabled: false, active: false)); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + color: inactiveDisabledTrackColor, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ), + reason: 'Inactive disabled switch track should use this value', + ); + + await tester.pumpWidget(buildSwitch(enabled: false, active: true)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + color: activeDisabledTrackColor, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ), + reason: 'Active disabled switch should match these colors', + ); + + await tester.pumpWidget(buildSwitch(enabled: true, active: false)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + color: inactiveEnabledTrackColor, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ), + reason: 'Inactive enabled switch should match these colors', + ); + + await tester.pumpWidget(buildSwitch(enabled: true, active: true)); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + color: activeEnabledTrackColor, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ), + reason: 'Active enabled switch should match these colors', + ); + }); + + testWidgets('Material2 - Switch track color resolves in hovered/focused states', (WidgetTester tester) async { + final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + const Color hoveredTrackColor = Color(0xFF000001); + const Color focusedTrackColor = Color(0xFF000002); + + Color getTrackColor(Set<MaterialState> states) { + if (states.contains(MaterialState.hovered)) { + return hoveredTrackColor; + } + if (states.contains(MaterialState.focused)) { + return focusedTrackColor; + } + return Colors.transparent; + } + + final MaterialStateProperty<Color> trackColor = + MaterialStateColor.resolveWith(getTrackColor); + + Widget buildSwitch() { + return MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Directionality( + textDirection: TextDirection.rtl, + child: Material( + child: Center( + child: Switch( + focusNode: focusNode, + autofocus: true, + value: true, + trackColor: trackColor, + onChanged: (_) { }, + ), ), ), ), @@ -1560,9 +2314,79 @@ void main() { ); }); - testWidgets('Switch thumb color is blended against surface color', (WidgetTester tester) async { + testWidgets('Material3 - Switch track color resolves in hovered/focused states', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(useMaterial3: true); + final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + const Color hoveredTrackColor = Color(0xFF000001); + const Color focusedTrackColor = Color(0xFF000002); + + Color getTrackColor(Set<MaterialState> states) { + if (states.contains(MaterialState.hovered)) { + return hoveredTrackColor; + } + if (states.contains(MaterialState.focused)) { + return focusedTrackColor; + } + return Colors.transparent; + } + + final MaterialStateProperty<Color> trackColor = + MaterialStateColor.resolveWith(getTrackColor); + + Widget buildSwitch() { + return Theme( + data: themeData, + child: Directionality( + textDirection: TextDirection.rtl, + child: Material( + child: Center( + child: Switch( + focusNode: focusNode, + autofocus: true, + value: true, + trackColor: trackColor, + onChanged: (_) { }, + ), + ), + ), + ), + ); + } + + await tester.pumpWidget(buildSwitch()); + await tester.pumpAndSettle(); + expect(focusNode.hasPrimaryFocus, isTrue); + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + color: focusedTrackColor, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ), + reason: 'Active enabled switch should match these colors', + ); + + // Start hovering + final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); + await gesture.addPointer(); + await gesture.moveTo(tester.getCenter(find.byType(Switch))); + await tester.pumpAndSettle(); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + color: hoveredTrackColor, + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ), + reason: 'Active enabled switch should match these colors', + ); + }); + + testWidgets('Material2 - Switch thumb color is blended against surface color', (WidgetTester tester) async { final Color activeDisabledThumbColor = Colors.blue.withOpacity(.60); - final ThemeData theme = ThemeData.light(); + final ThemeData theme = ThemeData.light(useMaterial3: false); Color getThumbColor(Set<MaterialState> states) { if (states.contains(MaterialState.disabled)) { @@ -1611,6 +2435,56 @@ void main() { ); }); + testWidgets('Material3 - Switch thumb color is blended against surface color', (WidgetTester tester) async { + final Color activeDisabledThumbColor = Colors.blue.withOpacity(.60); + final ThemeData theme = ThemeData(useMaterial3: true); + final ColorScheme colors = theme.colorScheme; + + Color getThumbColor(Set<MaterialState> states) { + if (states.contains(MaterialState.disabled)) { + return activeDisabledThumbColor; + } + return Colors.black; + } + + final MaterialStateProperty<Color> thumbColor = + MaterialStateColor.resolveWith(getThumbColor); + + Widget buildSwitch({required bool enabled, required bool active}) { + return Directionality( + textDirection: TextDirection.rtl, + child: Theme( + data: theme, + child: Material( + child: Center( + child: Switch( + thumbColor: thumbColor, + value: active, + onChanged: enabled ? (_) { } : null, + ), + ), + ), + ), + ); + } + + await tester.pumpWidget(buildSwitch(enabled: false, active: true)); + + final Color expectedThumbColor = Color.alphaBlend(activeDisabledThumbColor, theme.colorScheme.surface); + + expect( + Material.of(tester.element(find.byType(Switch))), + paints + ..rrect( + color: colors.onSurface.withOpacity(0.12), + rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), + ) + ..rrect() + ..rrect(color: expectedThumbColor), + reason: 'Active disabled thumb color should be blended on top of surface color', + ); + }); + testWidgets('Switch overlay color resolves in active/pressed/focused/hovered states', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; @@ -1962,7 +2836,7 @@ void main() { }); }); - group('Switch M3 tests', () { + group('Switch M3 only tests', () { testWidgets('M3 Switch has a 300-millisecond animation in total', (WidgetTester tester) async { final ThemeData theme = ThemeData(useMaterial3: true); bool value = false; @@ -2097,798 +2971,88 @@ void main() { expect(state.position.value, greaterThan(1)); }); - testWidgets('Switch has default colors when enabled - M3', (WidgetTester tester) async { - final ThemeData theme = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light); - final ColorScheme colors = theme.colorScheme; - bool value = false; - await tester.pumpWidget( - MaterialApp( - theme: theme, - home: Directionality( - textDirection: TextDirection.rtl, - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Material( - child: Center( - child: Switch( - dragStartBehavior: DragStartBehavior.down, - value: value, - onChanged: (bool newValue) { - setState(() { - value = newValue; - }); - }, - ), - ), + testWidgets('Switch thumb shows correct pressed color - M3', (WidgetTester tester) async { + final ThemeData themeData = ThemeData(useMaterial3: true); + final ColorScheme colors = themeData.colorScheme; + Widget buildApp({bool enabled = true, bool value = true}) { + return MaterialApp( + theme: themeData, + home: Material( + child: Center( + child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { + return Switch( + value: value, + onChanged: enabled ? (bool newValue) { + setState(() { + value = newValue; + }); + } : null, ); - }, + }), ), ), - ), - ); + ); + } - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..save() - ..rrect( - style: PaintingStyle.fill, - color: colors.surfaceVariant, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect( - style: PaintingStyle.stroke, - color: colors.outline, - rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)), - ) - ..rrect(color: colors.outline), // thumb color - reason: 'Inactive enabled switch should match these colors', - ); - await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0)); - await tester.pump(); + await tester.pumpWidget(buildApp()); + await tester.press(find.byType(Switch)); + await tester.pumpAndSettle(); - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..save() - ..rrect( - style: PaintingStyle.fill, - color: colors.primary, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect() - ..rrect(color: colors.onPrimary), // thumb color - reason: 'Active enabled switch should match these colors', - ); - }); - - testWidgets('Inactive Switch has default colors when disabled - M3', (WidgetTester tester) async { - final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light); - final ColorScheme colors = themeData.colorScheme; - - await tester.pumpWidget(MaterialApp( - theme: themeData, - home: const Directionality( - textDirection: TextDirection.rtl, - child: Material( - child: Center( - child: Switch( - value: false, - onChanged: null, - ), - ), - ), - ), - )); - - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..save() - ..rrect( - style: PaintingStyle.fill, - color: colors.surfaceVariant.withOpacity(0.12), - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect( - style: PaintingStyle.stroke, - color: colors.onSurface.withOpacity(0.12), - rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)), - ) - ..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)), // thumb color - reason: 'Inactive disabled switch should match these colors', - ); - }); - - testWidgets('Active Switch has default colors when disabled - M3', (WidgetTester tester) async { - final ThemeData themeData = ThemeData(useMaterial3: true, - colorSchemeSeed: const Color(0xff6750a4), - brightness: Brightness.light); - final ColorScheme colors = themeData.colorScheme; - await tester.pumpWidget(MaterialApp( - theme: themeData, - home: const Directionality( - textDirection: TextDirection.rtl, - child: Material( - child: Center( - child: Switch( - value: true, - onChanged: null, - ), - ), - ), - ), - )); - - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..save() - ..rrect( - style: PaintingStyle.fill, - color: colors.onSurface.withOpacity(0.12), - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect() - ..rrect(color: colors.surface), // thumb color - reason: 'Active disabled switch should match these colors', - ); - }); - - testWidgets('Switch can be set color - M3', (WidgetTester tester) async { - final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light); - final ColorScheme colors = themeData.colorScheme; - - bool value = false; - await tester.pumpWidget( - MaterialApp( - theme: themeData, - home: Directionality( - textDirection: TextDirection.rtl, - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Material( - child: Center( - child: Switch( - dragStartBehavior: DragStartBehavior.down, - value: value, - onChanged: (bool newValue) { - setState(() { - value = newValue; - }); - }, - activeColor: Colors.red[500], - activeTrackColor: Colors.green[500], - inactiveThumbColor: Colors.yellow[500], - inactiveTrackColor: Colors.blue[500], - ), - ), - ); - }, - ), - ), - ), - ); - - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - style: PaintingStyle.fill, - color: Colors.blue[500], - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect( - style: PaintingStyle.stroke, - color: colors.outline, - ) - ..rrect(color: Colors.yellow[500]), // thumb color - ); - await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0)); - await tester.pump(); - - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - style: PaintingStyle.fill, - color: Colors.green[500], - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect() - ..rrect(color: Colors.red[500]), // thumb color - ); - }); - - testWidgets('Switch is focusable and has correct focus color - M3', (WidgetTester tester) async { - final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light); - final ColorScheme colors = themeData.colorScheme; - final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); - tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; - bool value = true; - Widget buildApp({bool enabled = true}) { - return MaterialApp( - theme: themeData, - home: Material( - child: Center( - child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { - return Switch( - value: value, - onChanged: enabled ? (bool newValue) { - setState(() { - value = newValue; - }); - } : null, - focusColor: Colors.orange[500], - autofocus: true, - focusNode: focusNode, - ); - }), - ), - ), - ); - } - await tester.pumpWidget(buildApp()); - - // active, enabled switch - await tester.pumpAndSettle(); - expect(focusNode.hasPrimaryFocus, isTrue); - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - style: PaintingStyle.fill, - color: colors.primary, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..circle(color: Colors.orange[500]), - ); - - // Check the false value: inactive enabled switch - value = false; - await tester.pumpWidget(buildApp()); - await tester.pumpAndSettle(); - expect(focusNode.hasPrimaryFocus, isTrue); - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - style: PaintingStyle.fill, - color: colors.surfaceVariant, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect( - style: PaintingStyle.stroke, - color: colors.outline, - rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)), - ) - ..circle(color: Colors.orange[500]) - ); - - // Check what happens when disabled: inactive disabled switch. - value = false; - await tester.pumpWidget(buildApp(enabled: false)); - await tester.pumpAndSettle(); - expect(focusNode.hasPrimaryFocus, isFalse); - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - style: PaintingStyle.fill, - color: colors.surfaceVariant.withOpacity(0.12), - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect( - style: PaintingStyle.stroke, - color: colors.onSurface.withOpacity(0.12), - rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)), - ) - ..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)), - ); - }); - - testWidgets('Switch can be hovered and has correct hover color - M3', (WidgetTester tester) async { - final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light); - final ColorScheme colors = themeData.colorScheme; - tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; - bool value = true; - Widget buildApp({bool enabled = true}) { - return MaterialApp( - theme: themeData, - home: Material( - child: Center( - child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { - return Switch( - value: value, - onChanged: enabled ? (bool newValue) { - setState(() { - value = newValue; - }); - } : null, - hoverColor: Colors.orange[500], - ); - }), - ), - ), - ); - } - - // active enabled switch - await tester.pumpWidget(buildApp()); - await tester.pumpAndSettle(); - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - color: colors.primary, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect() - ..rrect(color: colors.onPrimary), - ); - - // Start hovering - final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); - await gesture.addPointer(); - await gesture.moveTo(tester.getCenter(find.byType(Switch))); - - await tester.pumpWidget(buildApp()); - await tester.pumpAndSettle(); - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - color: colors.primary, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..circle(color: Colors.orange[500]), - ); - - // Check what happens for disabled active switch - await tester.pumpWidget(buildApp(enabled: false)); - await tester.pumpAndSettle(); - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - color: colors.onSurface.withOpacity(0.12), - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect() - ..rrect(color: colors.surface.withOpacity(1.0)), - ); - }); - - testWidgets('Switch thumb shows correct pressed color - M3', (WidgetTester tester) async { - final ThemeData themeData = ThemeData(useMaterial3: true); - final ColorScheme colors = themeData.colorScheme; - Widget buildApp({bool enabled = true, bool value = true}) { - return MaterialApp( - theme: themeData, - home: Material( - child: Center( - child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) { - return Switch( - value: value, - onChanged: enabled ? (bool newValue) { - setState(() { - value = newValue; - }); - } : null, - ); - }), - ), - ), - ); - } - - await tester.pumpWidget(buildApp()); - await tester.press(find.byType(Switch)); - await tester.pumpAndSettle(); - - expect(Material.of(tester.element(find.byType(Switch))), - paints..rrect( - color: colors.primary, // track color - style: PaintingStyle.fill, - )..rrect( - color: Colors.transparent, // track outline color - style: PaintingStyle.stroke, - )..rrect(color: colors.primaryContainer, rrect: RRect.fromLTRBR(26.0, 10.0, 54.0, 38.0, const Radius.circular(14.0))), - ); - - await tester.pumpWidget(Container()); - await tester.pumpWidget(buildApp(value: false)); - await tester.press(find.byType(Switch)); - await tester.pumpAndSettle(); - - expect(Material.of(tester.element(find.byType(Switch))), - paints..rrect( - color: colors.surfaceVariant, // track color - style: PaintingStyle.fill - )..rrect( - color: colors.outline, // track outline color - style: PaintingStyle.stroke, - )..rrect(color: colors.onSurfaceVariant), - ); - - await tester.pumpWidget(Container()); - await tester.pumpWidget(buildApp(enabled: false)); - await tester.press(find.byType(Switch)); - await tester.pumpAndSettle(); - - expect(Material.of(tester.element(find.byType(Switch))), - paints..rrect( - color: colors.onSurface.withOpacity(0.12), // track color - style: PaintingStyle.fill, - )..rrect( - color: Colors.transparent, // track outline color - style: PaintingStyle.stroke, - )..rrect(color: colors.surface.withOpacity(1.0)), - ); - - await tester.pumpWidget(Container()); - await tester.pumpWidget(buildApp(enabled: false, value: false)); - await tester.press(find.byType(Switch)); - await tester.pumpAndSettle(); - - expect(Material.of(tester.element(find.byType(Switch))), - paints..rrect( - color: colors.surfaceVariant.withOpacity(0.12), // track color - style: PaintingStyle.fill, - )..rrect( - color: colors.onSurface.withOpacity(0.12), // track outline color - style: PaintingStyle.stroke, - )..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)), - ); - }, variant: TargetPlatformVariant.mobile()); - - testWidgets('Switch thumb color resolves in active/enabled states - M3', (WidgetTester tester) async { - final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light); - final ColorScheme colors = themeData.colorScheme; - const Color activeEnabledThumbColor = Color(0xFF000001); - const Color activeDisabledThumbColor = Color(0xFF000002); - const Color inactiveEnabledThumbColor = Color(0xFF000003); - const Color inactiveDisabledThumbColor = Color(0xFF000004); - - Color getThumbColor(Set<MaterialState> states) { - if (states.contains(MaterialState.disabled)) { - if (states.contains(MaterialState.selected)) { - return activeDisabledThumbColor; - } - return inactiveDisabledThumbColor; - } - if (states.contains(MaterialState.selected)) { - return activeEnabledThumbColor; - } - return inactiveEnabledThumbColor; - } - - final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor); - - Widget buildSwitch({required bool enabled, required bool active}) { - return Theme( - data: themeData, - child: Directionality( - textDirection: TextDirection.rtl, - child: Material( - child: Center( - child: Switch( - thumbColor: thumbColor, - value: active, - onChanged: enabled ? (_) { } : null, - ), - ), - ), - ), - ); - } - - await tester.pumpWidget(buildSwitch(enabled: false, active: false)); - - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - style: PaintingStyle.fill, - color: colors.surfaceVariant.withOpacity(0.12), - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect( - style: PaintingStyle.stroke, - color: colors.onSurface.withOpacity(0.12), - rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)), - ) - ..rrect(color: inactiveDisabledThumbColor), - reason: 'Inactive disabled switch should default track and custom thumb color', - ); - - await tester.pumpWidget(buildSwitch(enabled: false, active: true)); - await tester.pumpAndSettle(); - - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - style: PaintingStyle.fill, - color: colors.onSurface.withOpacity(0.12), - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect() - ..rrect(color: activeDisabledThumbColor), - reason: 'Active disabled switch should match these colors', - ); - - await tester.pumpWidget(buildSwitch(enabled: true, active: false)); - await tester.pumpAndSettle(); - - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - style: PaintingStyle.fill, - color: colors.surfaceVariant, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect() - ..rrect(color: inactiveEnabledThumbColor), - reason: 'Inactive enabled switch should match these colors', - ); - - await tester.pumpWidget(buildSwitch(enabled: true, active: true)); - await tester.pumpAndSettle(); - - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - style: PaintingStyle.fill, - color: colors.primary, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect() - ..rrect(color: activeEnabledThumbColor), - reason: 'Active enabled switch should match these colors', - ); - }); - - testWidgets('Switch thumb color resolves in hovered/focused states - M3', (WidgetTester tester) async { - final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light); - final ColorScheme colors = themeData.colorScheme; - final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); - tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; - const Color hoveredThumbColor = Color(0xFF000001); - const Color focusedThumbColor = Color(0xFF000002); - - Color getThumbColor(Set<MaterialState> states) { - if (states.contains(MaterialState.hovered)) { - return hoveredThumbColor; - } - if (states.contains(MaterialState.focused)) { - return focusedThumbColor; - } - return Colors.transparent; - } - - final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor); - - Widget buildSwitch() { - return MaterialApp( - theme: themeData, - home: Directionality( - textDirection: TextDirection.rtl, - child: Material( - child: Center( - child: Switch( - focusNode: focusNode, - autofocus: true, - value: true, - thumbColor: thumbColor, - onChanged: (_) { }, - ), - ), - ), - ), - ); - } - - await tester.pumpWidget(buildSwitch()); - await tester.pumpAndSettle(); - expect(focusNode.hasPrimaryFocus, isTrue); - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - style: PaintingStyle.fill, - color: colors.primary, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..circle(color: colors.primary.withOpacity(0.12)) - ..rrect(color: focusedThumbColor), - reason: 'active enabled switch should default track and custom thumb color', - ); - - // Start hovering - final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); - await gesture.addPointer(); - await gesture.moveTo(tester.getCenter(find.byType(Switch))); - await tester.pumpAndSettle(); - - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - style: PaintingStyle.fill, - color: colors.primary, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..circle(color: colors.primary.withOpacity(0.08)) - ..rrect(color: hoveredThumbColor), - reason: 'active enabled switch should default track and custom thumb color', - ); - }); - - testWidgets('Track color resolves in active/enabled states - M3', (WidgetTester tester) async { - final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light); - const Color activeEnabledTrackColor = Color(0xFF000001); - const Color activeDisabledTrackColor = Color(0xFF000002); - const Color inactiveEnabledTrackColor = Color(0xFF000003); - const Color inactiveDisabledTrackColor = Color(0xFF000004); - - Color getTrackColor(Set<MaterialState> states) { - if (states.contains(MaterialState.disabled)) { - if (states.contains(MaterialState.selected)) { - return activeDisabledTrackColor; - } - return inactiveDisabledTrackColor; - } - if (states.contains(MaterialState.selected)) { - return activeEnabledTrackColor; - } - return inactiveEnabledTrackColor; - } - - final MaterialStateProperty<Color> trackColor = - MaterialStateColor.resolveWith(getTrackColor); - - Widget buildSwitch({required bool enabled, required bool active}) { - return Theme( - data: themeData, - child: Directionality( - textDirection: TextDirection.rtl, - child: Material( - child: Center( - child: Switch( - trackColor: trackColor, - value: active, - onChanged: enabled ? (_) { } : null, - ), - ), - ), - ), - ); - } - - await tester.pumpWidget(buildSwitch(enabled: false, active: false)); - - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - color: inactiveDisabledTrackColor, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ), - reason: 'Inactive disabled switch track should use this value', - ); - - await tester.pumpWidget(buildSwitch(enabled: false, active: true)); - await tester.pumpAndSettle(); - - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - color: activeDisabledTrackColor, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ), - reason: 'Active disabled switch should match these colors', + expect(Material.of(tester.element(find.byType(Switch))), + paints..rrect( + color: colors.primary, // track color + style: PaintingStyle.fill, + )..rrect( + color: Colors.transparent, // track outline color + style: PaintingStyle.stroke, + )..rrect(color: colors.primaryContainer, rrect: RRect.fromLTRBR(26.0, 10.0, 54.0, 38.0, const Radius.circular(14.0))), ); - await tester.pumpWidget(buildSwitch(enabled: true, active: false)); + await tester.pumpWidget(Container()); + await tester.pumpWidget(buildApp(value: false)); + await tester.press(find.byType(Switch)); await tester.pumpAndSettle(); - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - color: inactiveEnabledTrackColor, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ), - reason: 'Inactive enabled switch should match these colors', + expect(Material.of(tester.element(find.byType(Switch))), + paints..rrect( + color: colors.surfaceVariant, // track color + style: PaintingStyle.fill + )..rrect( + color: colors.outline, // track outline color + style: PaintingStyle.stroke, + )..rrect(color: colors.onSurfaceVariant), ); - await tester.pumpWidget(buildSwitch(enabled: true, active: true)); + await tester.pumpWidget(Container()); + await tester.pumpWidget(buildApp(enabled: false)); + await tester.press(find.byType(Switch)); await tester.pumpAndSettle(); - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - color: activeEnabledTrackColor, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ), - reason: 'Active enabled switch should match these colors', - ); - }); - - testWidgets('Switch track color resolves in hovered/focused states', (WidgetTester tester) async { - final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light); - final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); - tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; - const Color hoveredTrackColor = Color(0xFF000001); - const Color focusedTrackColor = Color(0xFF000002); - - Color getTrackColor(Set<MaterialState> states) { - if (states.contains(MaterialState.hovered)) { - return hoveredTrackColor; - } - if (states.contains(MaterialState.focused)) { - return focusedTrackColor; - } - return Colors.transparent; - } - - final MaterialStateProperty<Color> trackColor = - MaterialStateColor.resolveWith(getTrackColor); - - Widget buildSwitch() { - return Theme( - data: themeData, - child: Directionality( - textDirection: TextDirection.rtl, - child: Material( - child: Center( - child: Switch( - focusNode: focusNode, - autofocus: true, - value: true, - trackColor: trackColor, - onChanged: (_) { }, - ), - ), - ), - ), - ); - } - - await tester.pumpWidget(buildSwitch()); - await tester.pumpAndSettle(); - expect(focusNode.hasPrimaryFocus, isTrue); - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - color: focusedTrackColor, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ), - reason: 'Active enabled switch should match these colors', + expect(Material.of(tester.element(find.byType(Switch))), + paints..rrect( + color: colors.onSurface.withOpacity(0.12), // track color + style: PaintingStyle.fill, + )..rrect( + color: Colors.transparent, // track outline color + style: PaintingStyle.stroke, + )..rrect(color: colors.surface.withOpacity(1.0)), ); - // Start hovering - final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); - await gesture.addPointer(); - await gesture.moveTo(tester.getCenter(find.byType(Switch))); + await tester.pumpWidget(Container()); + await tester.pumpWidget(buildApp(enabled: false, value: false)); + await tester.press(find.byType(Switch)); await tester.pumpAndSettle(); - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - color: hoveredTrackColor, - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ), - reason: 'Active enabled switch should match these colors', + expect(Material.of(tester.element(find.byType(Switch))), + paints..rrect( + color: colors.surfaceVariant.withOpacity(0.12), // track color + style: PaintingStyle.fill, + )..rrect( + color: colors.onSurface.withOpacity(0.12), // track outline color + style: PaintingStyle.stroke, + )..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)), ); - }); + }, variant: TargetPlatformVariant.mobile()); testWidgets('Track outline color resolves in active/enabled states', (WidgetTester tester) async { const Color activeEnabledTrackOutlineColor = Color(0xFF000001); @@ -3160,56 +3324,6 @@ void main() { ); }); - testWidgets('Switch thumb color is blended against surface color - M3', (WidgetTester tester) async { - final Color activeDisabledThumbColor = Colors.blue.withOpacity(.60); - final ThemeData theme = ThemeData.light(useMaterial3: true); - final ColorScheme colors = theme.colorScheme; - - Color getThumbColor(Set<MaterialState> states) { - if (states.contains(MaterialState.disabled)) { - return activeDisabledThumbColor; - } - return Colors.black; - } - - final MaterialStateProperty<Color> thumbColor = - MaterialStateColor.resolveWith(getThumbColor); - - Widget buildSwitch({required bool enabled, required bool active}) { - return Directionality( - textDirection: TextDirection.rtl, - child: Theme( - data: theme, - child: Material( - child: Center( - child: Switch( - thumbColor: thumbColor, - value: active, - onChanged: enabled ? (_) { } : null, - ), - ), - ), - ), - ); - } - - await tester.pumpWidget(buildSwitch(enabled: false, active: true)); - - final Color expectedThumbColor = Color.alphaBlend(activeDisabledThumbColor, theme.colorScheme.surface); - - expect( - Material.of(tester.element(find.byType(Switch))), - paints - ..rrect( - color: colors.onSurface.withOpacity(0.12), - rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), - ) - ..rrect() - ..rrect(color: expectedThumbColor), - reason: 'Active disabled thumb color should be blended on top of surface color', - ); - }); - testWidgets('Switch can set icon - M3', (WidgetTester tester) async { final ThemeData themeData = ThemeData( useMaterial3: true, diff --git a/packages/flutter/test/material/switch_theme_test.dart b/packages/flutter/test/material/switch_theme_test.dart index cc7e6b3645944..159defb60b811 100644 --- a/packages/flutter/test/material/switch_theme_test.dart +++ b/packages/flutter/test/material/switch_theme_test.dart @@ -86,7 +86,7 @@ void main() { expect(description[8], 'thumbIcon: MaterialStatePropertyAll(Icon(IconData(U+0007B)))'); }); - testWidgets('Switch is themeable', (WidgetTester tester) async { + testWidgets('Material2 - Switch is themeable', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const Color defaultThumbColor = Color(0xfffffff0); @@ -106,6 +106,7 @@ void main() { const Icon icon2 = Icon(Icons.close); final ThemeData themeData = ThemeData( + useMaterial3: false, switchTheme: SwitchThemeData( thumbColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.selected)) { @@ -151,7 +152,6 @@ void main() { }), ), ); - final bool material3 = themeData.useMaterial3; Widget buildSwitch({bool selected = false, bool autofocus = false}) { return MaterialApp( theme: themeData, @@ -171,42 +171,29 @@ void main() { await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), - material3 - ? (paints - ..rrect(color: defaultTrackColor) - ..rrect(color: defaultTrackOutlineColor, strokeWidth: defaultTrackOutlineWidth) - ..rrect(color: defaultThumbColor) - ..paragraph() - ) - : (paints + paints ..rrect(color: defaultTrackColor) ..rrect(color: defaultTrackOutlineColor, strokeWidth: defaultTrackOutlineWidth) ..rrect() ..rrect() ..rrect() ..rrect(color: defaultThumbColor) - ) ); // Size from MaterialTapTargetSize.shrinkWrap. - expect(tester.getSize(find.byType(Switch)), material3 ? const Size(60.0, 40.0) : const Size(59.0, 40.0)); + expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0)); // Selected switch. await tester.pumpWidget(buildSwitch(selected: true)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), - material3 - ? (paints - ..rrect(color: selectedTrackColor) - ..rrect(color: selectedTrackOutlineColor, strokeWidth: selectedTrackOutlineWidth) - ..rrect(color: selectedThumbColor)..paragraph()) - : (paints + paints ..rrect(color: selectedTrackColor) ..rrect(color: selectedTrackOutlineColor, strokeWidth: selectedTrackOutlineWidth) ..rrect() ..rrect() ..rrect() - ..rrect(color: selectedThumbColor)) + ..rrect(color: selectedThumbColor) ); // Switch with hover. @@ -222,7 +209,125 @@ void main() { expect(_getSwitchMaterial(tester), paints..circle(color: focusOverlayColor, radius: splashRadius)); }); - testWidgets('Switch properties are taken over the theme values', (WidgetTester tester) async { + testWidgets('Material3 - Switch is themeable', (WidgetTester tester) async { + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + + const Color defaultThumbColor = Color(0xfffffff0); + const Color selectedThumbColor = Color(0xfffffff1); + const Color defaultTrackColor = Color(0xfffffff2); + const Color selectedTrackColor = Color(0xfffffff3); + const Color defaultTrackOutlineColor = Color(0xfffffff4); + const Color selectedTrackOutlineColor = Color(0xfffffff5); + const double defaultTrackOutlineWidth = 3.0; + const double selectedTrackOutlineWidth = 6.0; + const MouseCursor mouseCursor = SystemMouseCursors.text; + const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap; + const Color focusOverlayColor = Color(0xfffffff4); + const Color hoverOverlayColor = Color(0xfffffff5); + const double splashRadius = 1.0; + const Icon icon1 = Icon(Icons.check); + const Icon icon2 = Icon(Icons.close); + + final ThemeData themeData = ThemeData( + useMaterial3: true, + switchTheme: SwitchThemeData( + thumbColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return selectedThumbColor; + } + return defaultThumbColor; + }), + trackColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return selectedTrackColor; + } + return defaultTrackColor; + }), + trackOutlineColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return selectedTrackOutlineColor; + } + return defaultTrackOutlineColor; + }), + trackOutlineWidth: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return selectedTrackOutlineWidth; + } + return defaultTrackOutlineWidth; + }), + mouseCursor: const MaterialStatePropertyAll<MouseCursor>(mouseCursor), + materialTapTargetSize: materialTapTargetSize, + overlayColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.focused)) { + return focusOverlayColor; + } + if (states.contains(MaterialState.hovered)) { + return hoverOverlayColor; + } + return null; + }), + splashRadius: splashRadius, + thumbIcon: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return icon1; + } + return icon2; + }), + ), + ); + Widget buildSwitch({bool selected = false, bool autofocus = false}) { + return MaterialApp( + theme: themeData, + home: Scaffold( + body: Switch( + dragStartBehavior: DragStartBehavior.down, + value: selected, + onChanged: (bool value) {}, + autofocus: autofocus, + ), + ), + ); + } + + // Switch. + await tester.pumpWidget(buildSwitch()); + await tester.pumpAndSettle(); + expect( + _getSwitchMaterial(tester), + paints + ..rrect(color: defaultTrackColor) + ..rrect(color: defaultTrackOutlineColor, strokeWidth: defaultTrackOutlineWidth) + ..rrect(color: defaultThumbColor) + ..paragraph() + ); + // Size from MaterialTapTargetSize.shrinkWrap. + expect(tester.getSize(find.byType(Switch)), const Size(60.0, 40.0)); + + // Selected switch. + await tester.pumpWidget(buildSwitch(selected: true)); + await tester.pumpAndSettle(); + expect( + _getSwitchMaterial(tester), + paints + ..rrect(color: selectedTrackColor) + ..rrect(color: selectedTrackOutlineColor, strokeWidth: selectedTrackOutlineWidth) + ..rrect(color: selectedThumbColor)..paragraph() + ); + + // Switch with hover. + await tester.pumpWidget(buildSwitch()); + await _pointGestureToSwitch(tester); + await tester.pumpAndSettle(); + expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text); + expect(_getSwitchMaterial(tester), paints..circle(color: hoverOverlayColor)); + + // Switch with focus. + await tester.pumpWidget(buildSwitch(autofocus: true)); + await tester.pumpAndSettle(); + expect(_getSwitchMaterial(tester), paints..circle(color: focusOverlayColor, radius: splashRadius)); + }); + + testWidgets('Material2 - Switch properties are taken over the theme values', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const Color themeDefaultThumbColor = Color(0xfffffff0); @@ -254,6 +359,7 @@ void main() { const double splashRadius = 2.0; final ThemeData themeData = ThemeData( + useMaterial3: false, switchTheme: SwitchThemeData( thumbColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.selected)) { @@ -299,7 +405,6 @@ void main() { }), ), ); - final bool material3 = themeData.useMaterial3; Widget buildSwitch({bool selected = false, bool autofocus = false}) { return MaterialApp( @@ -354,38 +459,192 @@ void main() { await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), - material3 - ? (paints - ..rrect(color: defaultTrackColor) - ..rrect(color: defaultOutlineColor, strokeWidth: defaultOutlineWidth) - ..rrect(color: defaultThumbColor)..paragraph(offset: const Offset(12, 16))) - : (paints + paints ..rrect(color: defaultTrackColor) ..rrect(color: defaultOutlineColor, strokeWidth: defaultOutlineWidth) ..rrect() ..rrect() ..rrect() - ..rrect(color: defaultThumbColor)) + ..rrect(color: defaultThumbColor) ); // Size from MaterialTapTargetSize.shrinkWrap. - expect(tester.getSize(find.byType(Switch)), material3 ? const Size(60.0, 40.0) : const Size(59.0, 40.0)); + expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0)); // Selected switch. await tester.pumpWidget(buildSwitch(selected: true)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), - material3 - ? (paints - ..rrect(color: selectedTrackColor)..rrect(color: selectedOutlineColor, strokeWidth: selectedOutlineWidth) - ..rrect(color: selectedThumbColor)) - : (paints + paints ..rrect(color: selectedTrackColor) ..rrect(color: selectedOutlineColor, strokeWidth: selectedOutlineWidth) ..rrect() ..rrect() ..rrect() - ..rrect(color: selectedThumbColor)) + ..rrect(color: selectedThumbColor) + ); + + // Switch with hover. + await tester.pumpWidget(buildSwitch()); + await _pointGestureToSwitch(tester); + await tester.pumpAndSettle(); + expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text); + expect(_getSwitchMaterial(tester), paints..circle(color: hoverColor)); + + // Switch with focus. + await tester.pumpWidget(buildSwitch(autofocus: true)); + await tester.pumpAndSettle(); + expect(_getSwitchMaterial(tester), paints..circle(color: focusColor, radius: splashRadius)); + }); + + testWidgets('Material3 - Switch properties are taken over the theme values', (WidgetTester tester) async { + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + + const Color themeDefaultThumbColor = Color(0xfffffff0); + const Color themeSelectedThumbColor = Color(0xfffffff1); + const Color themeDefaultTrackColor = Color(0xfffffff2); + const Color themeSelectedTrackColor = Color(0xfffffff3); + const Color themeDefaultOutlineColor = Color(0xfffffff6); + const Color themeSelectedOutlineColor = Color(0xfffffff7); + const double themeDefaultOutlineWidth = 5.0; + const double themeSelectedOutlineWidth = 7.0; + const MouseCursor themeMouseCursor = SystemMouseCursors.click; + const MaterialTapTargetSize themeMaterialTapTargetSize = MaterialTapTargetSize.padded; + const Color themeFocusOverlayColor = Color(0xfffffff4); + const Color themeHoverOverlayColor = Color(0xfffffff5); + const double themeSplashRadius = 1.0; + + const Color defaultThumbColor = Color(0xffffff0f); + const Color selectedThumbColor = Color(0xffffff1f); + const Color defaultTrackColor = Color(0xffffff2f); + const Color selectedTrackColor = Color(0xffffff3f); + const Color defaultOutlineColor = Color(0xffffff6f); + const Color selectedOutlineColor = Color(0xffffff7f); + const double defaultOutlineWidth = 6.0; + const double selectedOutlineWidth = 8.0; + const MouseCursor mouseCursor = SystemMouseCursors.text; + const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap; + const Color focusColor = Color(0xffffff4f); + const Color hoverColor = Color(0xffffff5f); + const double splashRadius = 2.0; + + final ThemeData themeData = ThemeData( + useMaterial3: true, + switchTheme: SwitchThemeData( + thumbColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return themeSelectedThumbColor; + } + return themeDefaultThumbColor; + }), + trackColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return themeSelectedTrackColor; + } + return themeDefaultTrackColor; + }), + trackOutlineColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return themeSelectedOutlineColor; + } + return themeDefaultOutlineColor; + }), + trackOutlineWidth: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return themeSelectedOutlineWidth; + } + return themeDefaultOutlineWidth; + }), + mouseCursor: const MaterialStatePropertyAll<MouseCursor>(themeMouseCursor), + materialTapTargetSize: themeMaterialTapTargetSize, + overlayColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.focused)) { + return themeFocusOverlayColor; + } + if (states.contains(MaterialState.hovered)) { + return themeHoverOverlayColor; + } + return null; + }), + splashRadius: themeSplashRadius, + thumbIcon: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return null; + } + return null; + }), + ), + ); + + Widget buildSwitch({bool selected = false, bool autofocus = false}) { + return MaterialApp( + theme: themeData, + home: Scaffold( + body: Switch( + value: selected, + onChanged: (bool value) {}, + autofocus: autofocus, + thumbColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return selectedThumbColor; + } + return defaultThumbColor; + }), + trackColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return selectedTrackColor; + } + return defaultTrackColor; + }), + trackOutlineColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return selectedOutlineColor; + } + return defaultOutlineColor; + }), + trackOutlineWidth: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return selectedOutlineWidth; + } + return defaultOutlineWidth; + }), + mouseCursor: mouseCursor, + materialTapTargetSize: materialTapTargetSize, + focusColor: focusColor, + hoverColor: hoverColor, + splashRadius: splashRadius, + thumbIcon: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return const Icon(Icons.add); + } + return const Icon(Icons.access_alarm); + }), + ), + ), + ); + } + + // Switch. + await tester.pumpWidget(buildSwitch()); + await tester.pumpAndSettle(); + expect( + _getSwitchMaterial(tester), + paints + ..rrect(color: defaultTrackColor) + ..rrect(color: defaultOutlineColor, strokeWidth: defaultOutlineWidth) + ..rrect(color: defaultThumbColor)..paragraph(offset: const Offset(12, 12)) + ); + // Size from MaterialTapTargetSize.shrinkWrap. + expect(tester.getSize(find.byType(Switch)), const Size(60.0, 40.0)); + + // Selected switch. + await tester.pumpWidget(buildSwitch(selected: true)); + await tester.pumpAndSettle(); + expect( + _getSwitchMaterial(tester), + paints + ..rrect(color: selectedTrackColor)..rrect(color: selectedOutlineColor, strokeWidth: selectedOutlineWidth) + ..rrect(color: selectedThumbColor) ); // Switch with hover. @@ -401,7 +660,7 @@ void main() { expect(_getSwitchMaterial(tester), paints..circle(color: focusColor, radius: splashRadius)); }); - testWidgets('Switch active and inactive properties are taken over the theme values', (WidgetTester tester) async { + testWidgets('Material2 - Switch active and inactive properties are taken over the theme values', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const Color themeDefaultThumbColor = Color(0xfffffff0); @@ -415,6 +674,7 @@ void main() { const Color selectedTrackColor = Color(0xffffff3f); final ThemeData themeData = ThemeData( + useMaterial3: false, switchTheme: SwitchThemeData( thumbColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.selected)) { @@ -430,7 +690,6 @@ void main() { }), ), ); - final bool material3 = themeData.useMaterial3; Widget buildSwitch({bool selected = false, bool autofocus = false}) { return MaterialApp( @@ -454,17 +713,12 @@ void main() { await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), - material3 - ? (paints - ..rrect(color: defaultTrackColor) - ..rrect(color: themeData.colorScheme.outline) - ..rrect(color: defaultThumbColor)) - : (paints - ..rrect(color: defaultTrackColor) - ..rrect() - ..rrect() - ..rrect() - ..rrect(color: defaultThumbColor)) + paints + ..rrect(color: defaultTrackColor) + ..rrect() + ..rrect() + ..rrect() + ..rrect(color: defaultThumbColor) ); // Selected switch. @@ -472,20 +726,87 @@ void main() { await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), - material3 - ? (paints - ..rrect(color: selectedTrackColor) - ..rrect(color: selectedThumbColor)) - : (paints + paints ..rrect(color: selectedTrackColor) ..rrect() ..rrect() ..rrect() - ..rrect(color: selectedThumbColor)) + ..rrect(color: selectedThumbColor) ); }); - testWidgets('Switch theme overlay color resolves in active/pressed states', (WidgetTester tester) async { + testWidgets('Material3 - Switch active and inactive properties are taken over the theme values', (WidgetTester tester) async { + tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; + + const Color themeDefaultThumbColor = Color(0xfffffff0); + const Color themeSelectedThumbColor = Color(0xfffffff1); + const Color themeDefaultTrackColor = Color(0xfffffff2); + const Color themeSelectedTrackColor = Color(0xfffffff3); + + const Color defaultThumbColor = Color(0xffffff0f); + const Color selectedThumbColor = Color(0xffffff1f); + const Color defaultTrackColor = Color(0xffffff2f); + const Color selectedTrackColor = Color(0xffffff3f); + + final ThemeData themeData = ThemeData( + useMaterial3: true, + switchTheme: SwitchThemeData( + thumbColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return themeSelectedThumbColor; + } + return themeDefaultThumbColor; + }), + trackColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return themeSelectedTrackColor; + } + return themeDefaultTrackColor; + }), + ), + ); + + Widget buildSwitch({bool selected = false, bool autofocus = false}) { + return MaterialApp( + theme: themeData, + home: Scaffold( + body: Switch( + value: selected, + onChanged: (bool value) {}, + autofocus: autofocus, + activeColor: selectedThumbColor, + inactiveThumbColor: defaultThumbColor, + activeTrackColor: selectedTrackColor, + inactiveTrackColor: defaultTrackColor, + ), + ), + ); + } + + // Unselected switch. + await tester.pumpWidget(buildSwitch()); + await tester.pumpAndSettle(); + expect( + _getSwitchMaterial(tester), + paints + ..rrect(color: defaultTrackColor) + ..rrect(color: themeData.colorScheme.outline) + ..rrect(color: defaultThumbColor) + ); + + // Selected switch. + await tester.pumpWidget(buildSwitch(selected: true)); + await tester.pumpAndSettle(); + expect( + _getSwitchMaterial(tester), + paints + ..rrect(color: selectedTrackColor) + ..rrect() + ..rrect(color: selectedThumbColor) + ); + }); + + testWidgets('Material2 - Switch theme overlay color resolves in active/pressed states', (WidgetTester tester) async { const Color activePressedOverlayColor = Color(0xFF000001); const Color inactivePressedOverlayColor = Color(0xFF000002); @@ -500,12 +821,12 @@ void main() { } const double splashRadius = 24.0; final ThemeData themeData = ThemeData( + useMaterial3: false, switchTheme: SwitchThemeData( overlayColor: MaterialStateProperty.resolveWith(getOverlayColor), splashRadius: splashRadius, ), ); - final bool material3 = themeData.useMaterial3; Widget buildSwitch({required bool active}) { return MaterialApp( @@ -525,20 +846,78 @@ void main() { expect( _getSwitchMaterial(tester), - material3 - ? ((paints + paints ..rrect() - ..rrect()) ..circle( color: inactivePressedOverlayColor, radius: splashRadius, - )) - : (paints + ), + reason: 'Inactive pressed Switch should have overlay color: $inactivePressedOverlayColor', + ); + + await tester.pumpWidget(buildSwitch(active: true)); + await tester.press(find.byType(Switch)); + await tester.pumpAndSettle(); + + expect( + _getSwitchMaterial(tester), + paints ..rrect() + ..circle( + color: activePressedOverlayColor, + radius: splashRadius, + ), + reason: 'Active pressed Switch should have overlay color: $activePressedOverlayColor', + ); + }); + + testWidgets('Material3 - Switch theme overlay color resolves in active/pressed states', (WidgetTester tester) async { + const Color activePressedOverlayColor = Color(0xFF000001); + const Color inactivePressedOverlayColor = Color(0xFF000002); + + Color? getOverlayColor(Set<MaterialState> states) { + if (states.contains(MaterialState.pressed)) { + if (states.contains(MaterialState.selected)) { + return activePressedOverlayColor; + } + return inactivePressedOverlayColor; + } + return null; + } + const double splashRadius = 24.0; + final ThemeData themeData = ThemeData( + useMaterial3: true, + switchTheme: SwitchThemeData( + overlayColor: MaterialStateProperty.resolveWith(getOverlayColor), + splashRadius: splashRadius, + ), + ); + + Widget buildSwitch({required bool active}) { + return MaterialApp( + theme: themeData, + home: Scaffold( + body: Switch( + value: active, + onChanged: (_) { }, + ), + ), + ); + } + + await tester.pumpWidget(buildSwitch(active: false)); + await tester.press(find.byType(Switch)); + await tester.pumpAndSettle(); + + expect( + _getSwitchMaterial(tester), + (paints + ..rrect() + ..rrect()) ..circle( color: inactivePressedOverlayColor, radius: splashRadius, - )), + ), reason: 'Inactive pressed Switch should have overlay color: $inactivePressedOverlayColor', ); @@ -558,7 +937,7 @@ void main() { ); }); - testWidgets('Local SwitchTheme can override global SwitchTheme', (WidgetTester tester) async { + testWidgets('Material2 - Local SwitchTheme can override global SwitchTheme', (WidgetTester tester) async { const Color globalThemeThumbColor = Color(0xfffffff1); const Color globalThemeTrackColor = Color(0xfffffff2); const Color globalThemeOutlineColor = Color(0xfffffff3); @@ -569,6 +948,7 @@ void main() { const double localThemeOutlineWidth = 4.0; final ThemeData themeData = ThemeData( + useMaterial3: false, switchTheme: const SwitchThemeData( thumbColor: MaterialStatePropertyAll<Color>(globalThemeThumbColor), trackColor: MaterialStatePropertyAll<Color>(globalThemeTrackColor), @@ -576,7 +956,6 @@ void main() { trackOutlineWidth: MaterialStatePropertyAll<double>(globalThemeOutlineWidth), ), ); - final bool material3 = themeData.useMaterial3; Widget buildSwitch({bool selected = false, bool autofocus = false}) { return MaterialApp( theme: themeData, @@ -602,18 +981,64 @@ void main() { await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), - material3 - ? (paints - ..rrect(color: localThemeTrackColor) - ..rrect(color: localThemeOutlineColor, strokeWidth: localThemeOutlineWidth) - ..rrect(color: localThemeThumbColor)) - : (paints + paints ..rrect(color: localThemeTrackColor) ..rrect(color: localThemeOutlineColor, strokeWidth: localThemeOutlineWidth) ..rrect() ..rrect() ..rrect() - ..rrect(color: localThemeThumbColor)) + ..rrect(color: localThemeThumbColor) + ); + }); + + testWidgets('Material3 - Local SwitchTheme can override global SwitchTheme', (WidgetTester tester) async { + const Color globalThemeThumbColor = Color(0xfffffff1); + const Color globalThemeTrackColor = Color(0xfffffff2); + const Color globalThemeOutlineColor = Color(0xfffffff3); + const double globalThemeOutlineWidth = 6.0; + const Color localThemeThumbColor = Color(0xffff0000); + const Color localThemeTrackColor = Color(0xffff0000); + const Color localThemeOutlineColor = Color(0xffff0000); + const double localThemeOutlineWidth = 4.0; + + final ThemeData themeData = ThemeData( + useMaterial3: true, + switchTheme: const SwitchThemeData( + thumbColor: MaterialStatePropertyAll<Color>(globalThemeThumbColor), + trackColor: MaterialStatePropertyAll<Color>(globalThemeTrackColor), + trackOutlineColor: MaterialStatePropertyAll<Color>(globalThemeOutlineColor), + trackOutlineWidth: MaterialStatePropertyAll<double>(globalThemeOutlineWidth), + ), + ); + Widget buildSwitch({bool selected = false, bool autofocus = false}) { + return MaterialApp( + theme: themeData, + home: Scaffold( + body: SwitchTheme( + data: const SwitchThemeData( + thumbColor: MaterialStatePropertyAll<Color>(localThemeThumbColor), + trackColor: MaterialStatePropertyAll<Color>(localThemeTrackColor), + trackOutlineColor: MaterialStatePropertyAll<Color>(localThemeOutlineColor), + trackOutlineWidth: MaterialStatePropertyAll<double>(localThemeOutlineWidth) + ), + child: Switch( + value: selected, + onChanged: (bool value) {}, + autofocus: autofocus, + ), + ), + ), + ); + } + + await tester.pumpWidget(buildSwitch(selected: true)); + await tester.pumpAndSettle(); + expect( + _getSwitchMaterial(tester), + paints + ..rrect(color: localThemeTrackColor) + ..rrect(color: localThemeOutlineColor, strokeWidth: localThemeOutlineWidth) + ..rrect(color: localThemeThumbColor) ); }); } diff --git a/packages/flutter/test/material/tab_bar_theme_test.dart b/packages/flutter/test/material/tab_bar_theme_test.dart index 2894cf8627bb5..4b8c5a924399e 100644 --- a/packages/flutter/test/material/tab_bar_theme_test.dart +++ b/packages/flutter/test/material/tab_bar_theme_test.dart @@ -607,15 +607,16 @@ void main() { ..line( color: theme.colorScheme.primary, strokeWidth: indicatorWeight, - p1: const Offset(65.5, indicatorY), - p2: const Offset(134.5, indicatorY), + p1: const Offset(bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 65.75 : 65.5, indicatorY), + p2: const Offset(bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? 134.25 : 134.5, indicatorY), ), ); }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('Tab bar defaults (primary)', (WidgetTester tester) async { // Test default label color and label styles. diff --git a/packages/flutter/test/material/tabs_test.dart b/packages/flutter/test/material/tabs_test.dart index 7a9e8514d27c3..c2194170fe01f 100644 --- a/packages/flutter/test/material/tabs_test.dart +++ b/packages/flutter/test/material/tabs_test.dart @@ -13,17 +13,20 @@ import '../rendering/recording_canvas.dart'; import '../widgets/semantics_tester.dart'; import 'feedback_tester.dart'; -Widget boilerplate({ Widget? child, TextDirection textDirection = TextDirection.ltr }) { - return Localizations( - locale: const Locale('en', 'US'), - delegates: const <LocalizationsDelegate<dynamic>>[ - DefaultMaterialLocalizations.delegate, - DefaultWidgetsLocalizations.delegate, - ], - child: Directionality( - textDirection: textDirection, - child: Material( - child: child, +Widget boilerplate({ Widget? child, TextDirection textDirection = TextDirection.ltr, bool? useMaterial3, TabBarTheme? tabBarTheme }) { + return Theme( + data: ThemeData(useMaterial3: useMaterial3, tabBarTheme: tabBarTheme), + child: Localizations( + locale: const Locale('en', 'US'), + delegates: const <LocalizationsDelegate<dynamic>>[ + DefaultMaterialLocalizations.delegate, + DefaultWidgetsLocalizations.delegate, + ], + child: Directionality( + textDirection: textDirection, + child: Material( + child: child, + ), ), ), ); @@ -115,9 +118,13 @@ Widget buildFrame({ EdgeInsetsGeometry? padding, TextDirection textDirection = TextDirection.ltr, TabAlignment? tabAlignment, + TabBarTheme? tabBarTheme, + bool? useMaterial3, }) { if (secondaryTabBar) { return boilerplate( + useMaterial3: useMaterial3, + tabBarTheme: tabBarTheme, textDirection: textDirection, child: DefaultTabController( animationDuration: animationDuration, @@ -136,6 +143,8 @@ Widget buildFrame({ } return boilerplate( + useMaterial3: useMaterial3, + tabBarTheme: tabBarTheme, textDirection: textDirection, child: DefaultTabController( animationDuration: animationDuration, @@ -196,9 +205,9 @@ class TabControllerFrameState extends State<TabControllerFrame> with SingleTicke } } -Widget buildLeftRightApp({required List<String> tabs, required String value, bool automaticIndicatorColorAdjustment = true}) { +Widget buildLeftRightApp({required List<String> tabs, required String value, bool automaticIndicatorColorAdjustment = true, ThemeData? themeData}) { return MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: themeData ?? ThemeData(platform: TargetPlatform.android), home: DefaultTabController( initialIndex: tabs.indexOf(value), length: tabs.length, @@ -313,19 +322,23 @@ void main() { }); testWidgets('Tab sizing - text', (WidgetTester tester) async { + final ThemeData theme = ThemeData(fontFamily: 'FlutterTest'); + final bool material3 = theme.useMaterial3; await tester.pumpWidget( - MaterialApp(theme: ThemeData(fontFamily: 'FlutterTest'), home: const Center(child: Material(child: Tab(text: 'x')))), + MaterialApp(theme: theme, home: const Center(child: Material(child: Tab(text: 'x')))), ); expect(tester.renderObject<RenderParagraph>(find.byType(RichText)).text.style!.fontFamily, 'FlutterTest'); - expect(tester.getSize(find.byType(Tab)), const Size(14.0, 46.0)); + expect(tester.getSize(find.byType(Tab)), material3 ? const Size(15.0, 46.0) : const Size(14.0, 46.0)); }); testWidgets('Tab sizing - icon and text', (WidgetTester tester) async { + final ThemeData theme = ThemeData(fontFamily: 'FlutterTest'); + final bool material3 = theme.useMaterial3; await tester.pumpWidget( - MaterialApp(theme: ThemeData(fontFamily: 'FlutterTest'), home: const Center(child: Material(child: Tab(icon: SizedBox(width: 10.0, height: 10.0), text: 'x')))), + MaterialApp(theme: theme, home: const Center(child: Material(child: Tab(icon: SizedBox(width: 10.0, height: 10.0), text: 'x')))), ); expect(tester.renderObject<RenderParagraph>(find.byType(RichText)).text.style!.fontFamily, 'FlutterTest'); - expect(tester.getSize(find.byType(Tab)), const Size(14.0, 72.0)); + expect(tester.getSize(find.byType(Tab)), material3 ? const Size(15.0, 72.0) : const Size(14.0, 72.0)); }); testWidgets('Tab sizing - icon, iconMargin and text', (WidgetTester tester) async { @@ -353,35 +366,43 @@ void main() { }); testWidgets('Tab sizing - icon and child', (WidgetTester tester) async { + final ThemeData theme = ThemeData(fontFamily: 'FlutterTest'); + final bool material3 = theme.useMaterial3; await tester.pumpWidget( - MaterialApp(theme: ThemeData(fontFamily: 'FlutterTest'), home: const Center(child: Material(child: Tab(icon: SizedBox(width: 10.0, height: 10.0), child: Text('x'))))), + MaterialApp(theme: theme, home: const Center(child: Material(child: Tab(icon: SizedBox(width: 10.0, height: 10.0), child: Text('x'))))), ); expect(tester.renderObject<RenderParagraph>(find.byType(RichText)).text.style!.fontFamily, 'FlutterTest'); - expect(tester.getSize(find.byType(Tab)), const Size(14.0, 72.0)); + expect(tester.getSize(find.byType(Tab)), material3 ? const Size(15.0, 72.0) : const Size(14.0, 72.0)); }); testWidgets('Tab color - normal', (WidgetTester tester) async { + final ThemeData theme = ThemeData(fontFamily: 'FlutterTest'); + final bool material3 = theme.useMaterial3; final Widget tabBar = TabBar(tabs: const <Widget>[SizedBox.shrink()], controller: TabController(length: 1, vsync: tester)); await tester.pumpWidget( - MaterialApp(home: Material(child: tabBar)), + MaterialApp(theme: theme, home: Material(child: tabBar)), ); - expect(find.byType(TabBar), paints..line(color: Colors.blue[500])); + expect(find.byType(TabBar), paints..line(color: material3 ? theme.colorScheme.surfaceVariant : Colors.blue[500])); }); testWidgets('Tab color - match', (WidgetTester tester) async { + final ThemeData theme = ThemeData(); + final bool material3 = theme.useMaterial3; final Widget tabBar = TabBar(tabs: const <Widget>[SizedBox.shrink()], controller: TabController(length: 1, vsync: tester)); await tester.pumpWidget( - MaterialApp(home: Material(color: const Color(0xff2196f3), child: tabBar)), + MaterialApp(theme: theme, home: Material(color: const Color(0xff2196f3), child: tabBar)), ); - expect(find.byType(TabBar), paints..line(color: Colors.white)); + expect(find.byType(TabBar), paints..line(color: material3 ? theme.colorScheme.surfaceVariant : Colors.white)); }); testWidgets('Tab color - transparency', (WidgetTester tester) async { + final ThemeData theme = ThemeData(); + final bool material3 = theme.useMaterial3; final Widget tabBar = TabBar(tabs: const <Widget>[SizedBox.shrink()], controller: TabController(length: 1, vsync: tester)); await tester.pumpWidget( - MaterialApp(home: Material(type: MaterialType.transparency, child: tabBar)), + MaterialApp(theme: theme, home: Material(type: MaterialType.transparency, child: tabBar)), ); - expect(find.byType(TabBar), paints..line(color: Colors.blue[500])); + expect(find.byType(TabBar), paints..line(color: material3 ? theme.colorScheme.surfaceVariant : Colors.blue[500])); }); testWidgets('TabBar default selected/unselected label style (primary)', (WidgetTester tester) async { @@ -391,10 +412,7 @@ void main() { const String selectedValue = 'A'; const String unselectedValue = 'C'; await tester.pumpWidget( - Theme( - data: theme, - child: buildFrame(tabs: tabs, value: selectedValue), - ), + buildFrame(tabs: tabs, value: selectedValue, useMaterial3: theme.useMaterial3), ); expect(find.text('A'), findsOneWidget); expect(find.text('B'), findsOneWidget); @@ -420,10 +438,7 @@ void main() { const String selectedValue = 'A'; const String unselectedValue = 'C'; await tester.pumpWidget( - Theme( - data: theme, - child: buildFrame(tabs: tabs, value: selectedValue, secondaryTabBar: true), - ), + buildFrame(tabs: tabs, value: selectedValue, secondaryTabBar: true, useMaterial3: theme.useMaterial3), ); expect(find.text('A'), findsOneWidget); expect(find.text('B'), findsOneWidget); @@ -455,8 +470,8 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: theme, home: boilerplate( + useMaterial3: theme.useMaterial3, child: Container( alignment: Alignment.topLeft, child: TabBar( @@ -473,19 +488,31 @@ void main() { const double indicatorWeight = 3.0; + + final RRect rrect = const bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') + ? RRect.fromLTRBAndCorners( + 64.75, + tabBarBox.size.height - indicatorWeight, + 135.25, + tabBarBox.size.height, + topLeft: const Radius.circular(3.0), + topRight: const Radius.circular(3.0), + ) + : RRect.fromLTRBAndCorners( + 64.5, + tabBarBox.size.height - indicatorWeight, + 135.5, + tabBarBox.size.height, + topLeft: const Radius.circular(3.0), + topRight: const Radius.circular(3.0), + ); + expect( tabBarBox, paints ..rrect( color: theme.colorScheme.primary, - rrect: RRect.fromLTRBAndCorners( - 64.5, - tabBarBox.size.height - indicatorWeight, - 135.5, - tabBarBox.size.height, - topLeft: const Radius.circular(3.0), - topRight: const Radius.circular(3.0), - ), + rrect: rrect, )); }); @@ -502,8 +529,8 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: theme, home: boilerplate( + useMaterial3: theme.useMaterial3, child: Container( alignment: Alignment.topLeft, child: TabBar.secondary( @@ -547,10 +574,7 @@ void main() { const String selectedValue = 'A'; const String unselectedValue = 'B'; await tester.pumpWidget( - Theme( - data: theme, - child: buildFrame(tabs: tabs, value: selectedValue), - ), + buildFrame(tabs: tabs, value: selectedValue, useMaterial3: theme.useMaterial3), ); RenderObject overlayColor() { @@ -587,10 +611,7 @@ void main() { const String selectedValue = 'A'; const String unselectedValue = 'B'; await tester.pumpWidget( - Theme( - data: theme, - child: buildFrame(tabs: tabs, value: selectedValue, secondaryTabBar: true), - ), + buildFrame(tabs: tabs, value: selectedValue, secondaryTabBar: true, useMaterial3: theme.useMaterial3), ); RenderObject overlayColor() { @@ -1536,7 +1557,7 @@ void main() { final List<String> tabs = <String>['A', 'B']; const Color indicatorColor = Color(0xFFFF0000); - await tester.pumpWidget(buildFrame(tabs: tabs, value: 'A', indicatorColor: indicatorColor, animationDuration: Duration.zero)); + await tester.pumpWidget(buildFrame(useMaterial3: false, tabs: tabs, value: 'A', indicatorColor: indicatorColor, animationDuration: Duration.zero)); final RenderBox box = tester.renderObject(find.byType(TabBar)); final TabIndicatorRecordingCanvas canvas = TabIndicatorRecordingCanvas(indicatorColor); @@ -1870,7 +1891,7 @@ void main() { final List<String> tabs = <String>['A', 'B']; const Color indicatorColor = Color(0xFFFF0000); - await tester.pumpWidget(buildFrame(tabs: tabs, value: 'A', indicatorColor: indicatorColor)); + await tester.pumpWidget(buildFrame(useMaterial3: false, tabs: tabs, value: 'A', indicatorColor: indicatorColor)); final RenderBox box = tester.renderObject(find.byType(TabBar)); final TabIndicatorRecordingCanvas canvas = TabIndicatorRecordingCanvas(indicatorColor); @@ -2289,6 +2310,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, child: Container( alignment: Alignment.topLeft, child: TabBar( @@ -2348,6 +2370,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, textDirection: TextDirection.rtl, child: Container( alignment: Alignment.topLeft, @@ -2409,6 +2432,7 @@ void main() { Widget buildFrame() { return boilerplate( + useMaterial3: false, child: Container( alignment: Alignment.topLeft, child: TabBar( @@ -2475,6 +2499,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, child: Container( alignment: Alignment.topLeft, child: TabBar( @@ -2544,6 +2569,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, textDirection: TextDirection.rtl, child: Container( alignment: Alignment.topLeft, @@ -2615,6 +2641,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, child: Container( alignment: Alignment.topLeft, child: TabBar( @@ -2685,6 +2712,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, textDirection: TextDirection.rtl, child: Container( alignment: Alignment.topLeft, @@ -2757,6 +2785,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, child: Container( alignment: Alignment.topLeft, child: TabBar( @@ -2835,6 +2864,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, textDirection: TextDirection.rtl, child: Container( alignment: Alignment.topLeft, @@ -3029,6 +3059,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, child: Container( alignment: Alignment.topLeft, child: TabBar( @@ -3099,6 +3130,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, child: Container( alignment: Alignment.topLeft, child: TabBar( @@ -3168,6 +3200,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, textDirection: TextDirection.rtl, child: Container( alignment: Alignment.topLeft, @@ -3230,6 +3263,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, child: Container( alignment: Alignment.topLeft, child: TabBar( @@ -3306,6 +3340,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, child: Semantics( container: true, child: TabBar( @@ -3572,6 +3607,7 @@ void main() { await tester.pumpWidget( boilerplate( + useMaterial3: false, child: Semantics( container: true, child: TabBar( @@ -3760,6 +3796,7 @@ void main() { Widget buildFrame(TabController controller) { return boilerplate( + useMaterial3: false, child: Container( alignment: Alignment.topLeft, child: TabBar( @@ -3963,23 +4000,27 @@ void main() { expect(tester.takeException(), null); }); - testWidgets('Default tab indicator color is white', (WidgetTester tester) async { + testWidgets('Default tab indicator color is white in M2 and surfaceVariant in M3', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/15958 final List<String> tabs = <String>['LEFT', 'RIGHT']; - await tester.pumpWidget(buildLeftRightApp(tabs: tabs, value: 'LEFT')); + final ThemeData theme = ThemeData(platform: TargetPlatform.android); + final bool material3 = theme.useMaterial3; + await tester.pumpWidget(buildLeftRightApp(themeData: theme, tabs: tabs, value: 'LEFT')); final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar)); expect(tabBarBox, paints..line( - color: Colors.white, + color: material3 ? theme.colorScheme.surfaceVariant : Colors.white, )); }); testWidgets('Tab indicator color should not be adjusted when disable [automaticIndicatorColorAdjustment]', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/68077 final List<String> tabs = <String>['LEFT', 'RIGHT']; - await tester.pumpWidget(buildLeftRightApp(tabs: tabs, value: 'LEFT', automaticIndicatorColorAdjustment: false)); + final ThemeData theme = ThemeData(platform: TargetPlatform.android); + final bool material3 = theme.useMaterial3; + await tester.pumpWidget(buildLeftRightApp(themeData: theme, tabs: tabs, value: 'LEFT', automaticIndicatorColorAdjustment: false)); final RenderBox tabBarBox = tester.firstRenderObject<RenderBox>(find.byType(TabBar)); expect(tabBarBox, paints..line( - color: const Color(0xff2196f3), + color: material3 ? theme.colorScheme.surfaceVariant : const Color(0xff2196f3), )); }); @@ -4083,6 +4124,7 @@ void main() { const Color splashColor = Color(0xf00fffff); await tester.pumpWidget( boilerplate( + useMaterial3: false, child: DefaultTabController( length: 1, child: TabBar( @@ -4348,12 +4390,12 @@ void main() { testWidgets('TabBar colors labels correctly', (WidgetTester tester) async { MaterialStateColor buildMSC(Color selectedColor, Color unselectedColor) { return MaterialStateColor - .resolveWith((Set<MaterialState> states) { - if (states.contains(MaterialState.selected)) { - return selectedColor; - } - return unselectedColor; - }); + .resolveWith((Set<MaterialState> states) { + if (states.contains(MaterialState.selected)) { + return selectedColor; + } + return unselectedColor; + }); } final Color materialLabelColor = buildMSC(const Color(0x00000000), const Color(0x00000001)); @@ -4382,7 +4424,7 @@ void main() { labelStyle: TextStyle(color: Color(0x00000017)), unselectedLabelStyle: TextStyle(color: Color(0x00000018)), ); - + final ThemeData theme = ThemeData(useMaterial3: false); Widget buildTabBar({ bool isLabelColorMSC = false, bool isLabelColorNull = false, @@ -4399,7 +4441,7 @@ void main() { : tabBarTheme; return boilerplate( child: Theme( - data: ThemeData(tabBarTheme: effectiveTheme), + data: theme.copyWith(tabBarTheme: effectiveTheme), child: DefaultTabController( length: 2, child: TabBar( @@ -4476,8 +4518,8 @@ void main() { isUnselectedLabelColorNull: true, isTabBarThemeNull: true, )); - expect(getTab1Color(), equals(ThemeData().primaryTextTheme.bodyText1!.color!.value)); - expect(getTab2Color(), equals(ThemeData().primaryTextTheme.bodyText1!.color!.withAlpha(0xB2).value)); + expect(getTab1Color(), equals(theme.primaryTextTheme.bodyText1!.color!.value)); + expect(getTab2Color(), equals(theme.primaryTextTheme.bodyText1!.color!.withAlpha(0xB2).value)); }); testWidgets('Replacing the tabController after disposing the old one', (WidgetTester tester) async { @@ -4767,6 +4809,7 @@ void main() { Widget buildTabControllerFrame(BuildContext context, TabController controller) { tabController = controller; return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( appBar: AppBar( bottom: TabBar( @@ -5251,6 +5294,7 @@ void main() { testWidgets('Change tab bar height', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: DefaultTabController( length: 4, child: Scaffold( @@ -5763,9 +5807,7 @@ void main() { final ThemeData theme = ThemeData(useMaterial3: true); final List<String> tabs = <String>['A', 'B', 'C']; - await tester.pumpWidget(Theme( - data: theme, - child: buildFrame(tabs: tabs, value: 'C')), + await tester.pumpWidget(buildFrame(tabs: tabs, value: 'C', useMaterial3: theme.useMaterial3), ); await tester.pumpAndSettle(); @@ -5816,8 +5858,7 @@ void main() { final List<String> tabs = <String>['A', 'B', 'C']; await tester.pumpWidget(MaterialApp( - theme: theme, - home: buildFrame(tabs: tabs, value: 'B'), + home: buildFrame(tabs: tabs, value: 'B', useMaterial3: theme.useMaterial3), ), ); @@ -5866,8 +5907,7 @@ void main() { final List<String> tabs = <String>['A', 'B', 'C']; await tester.pumpWidget(MaterialApp( - theme: theme, - home: buildFrame(tabs: tabs, value: 'B'), + home: buildFrame(tabs: tabs, value: 'B', useMaterial3: theme.useMaterial3), ), ); @@ -6003,8 +6043,9 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('TabBar default selected/unselected text style', (WidgetTester tester) async { final ThemeData theme = ThemeData(useMaterial3: false); @@ -6012,7 +6053,7 @@ void main() { const String selectedValue = 'A'; const String unSelectedValue = 'C'; - await tester.pumpWidget(buildFrame(tabs: tabs, value: selectedValue)); + await tester.pumpWidget(buildFrame(useMaterial3: false, tabs: tabs, value: selectedValue)); expect(find.text('A'), findsOneWidget); expect(find.text('B'), findsOneWidget); expect(find.text('C'), findsOneWidget); @@ -6042,13 +6083,7 @@ void main() { const String unSelectedValue = 'C'; const Color labelColor = Color(0xff0000ff); await tester.pumpWidget( - Theme( - data: ThemeData( - tabBarTheme: const TabBarTheme(labelColor: labelColor), - useMaterial3: false, - ), - child: buildFrame(tabs: tabs, value: selectedValue), - ), + buildFrame(tabs: tabs, value: selectedValue, useMaterial3: false, tabBarTheme: const TabBarTheme(labelColor: labelColor)), ); expect(find.text('A'), findsOneWidget); expect(find.text('B'), findsOneWidget); @@ -6146,8 +6181,8 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: theme, home: boilerplate( + useMaterial3: theme.useMaterial3, child: Container( alignment: Alignment.topLeft, child: TabBar( @@ -6192,8 +6227,8 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: theme, home: boilerplate( + useMaterial3: theme.useMaterial3, child: Container( alignment: Alignment.topLeft, child: TabBar.secondary( diff --git a/packages/flutter/test/material/text_button_test.dart b/packages/flutter/test/material/text_button_test.dart index 31ae30c2a845a..6e458587fbb2d 100644 --- a/packages/flutter/test/material/text_button_test.dart +++ b/packages/flutter/test/material/text_button_test.dart @@ -42,7 +42,7 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, material3 ? null : const Color(0xff000000)); + expect(material.shadowColor, material3 ? Colors.transparent : const Color(0xff000000)); expect(material.shape, material3 ? const StadiumBorder() : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); @@ -77,7 +77,7 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, material3 ? null : const Color(0xff000000)); + expect(material.shadowColor, material3 ? Colors.transparent : const Color(0xff000000)); expect(material.shape, material3 ? const StadiumBorder() : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); @@ -115,7 +115,7 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, material3 ? null : const Color(0xff000000)); + expect(material.shadowColor, material3 ? Colors.transparent : const Color(0xff000000)); expect(material.shape, material3 ? const StadiumBorder() : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); @@ -145,7 +145,7 @@ void main() { expect(material.clipBehavior, Clip.none); expect(material.color, Colors.transparent); expect(material.elevation, 0.0); - expect(material.shadowColor, material3 ? null : const Color(0xff000000)); + expect(material.shadowColor, material3 ? Colors.transparent : const Color(0xff000000)); expect(material.shape, material3 ? const StadiumBorder() : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); @@ -580,8 +580,7 @@ void main() { testWidgets('Does TextButton scale with font scale changes', (WidgetTester tester) async { await tester.pumpWidget( Theme( - // Force Material 2 typography. - data: ThemeData(textTheme: Typography.englishLike2014), + data: ThemeData(useMaterial3: false), child: Directionality( textDirection: TextDirection.ltr, child: MediaQuery( @@ -603,12 +602,11 @@ void main() { // textScaleFactor expands text, but not button. await tester.pumpWidget( Theme( - // Force Material 2 typography. - data: ThemeData(textTheme: Typography.englishLike2014), + data: ThemeData(useMaterial3: false), child: Directionality( textDirection: TextDirection.ltr, child: MediaQuery( - data: const MediaQueryData(textScaleFactor: 1.3), + data: const MediaQueryData(textScaleFactor: 1.25), child: Center( child: TextButton( onPressed: () { }, @@ -620,14 +618,19 @@ void main() { ), ); - expect(tester.getSize(find.byType(TextButton)), const Size(71.0, 48.0)); - expect(tester.getSize(find.byType(Text)), const Size(55.0, 18.0)); + const Size textButtonSize = bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') + ? Size(68.5, 48.0) + : Size(69.0, 48.0); + const Size textSize = bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') + ? Size(52.5, 18.0) + : Size(53.0, 18.0); + expect(tester.getSize(find.byType(TextButton)), textButtonSize); + expect(tester.getSize(find.byType(Text)), textSize); // Set text scale large enough to expand text and button. await tester.pumpWidget( Theme( - // Force Material 2 typography. - data: ThemeData(textTheme: Typography.englishLike2014), + data: ThemeData(useMaterial3: false), child: Directionality( textDirection: TextDirection.ltr, child: MediaQuery( @@ -650,7 +653,7 @@ void main() { testWidgets('TextButton size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async { Widget buildFrame(MaterialTapTargetSize tapTargetSize, Key key) { return Theme( - data: ThemeData(materialTapTargetSize: tapTargetSize), + data: ThemeData(useMaterial3: false, materialTapTargetSize: tapTargetSize), child: Directionality( textDirection: TextDirection.ltr, child: Center( @@ -919,7 +922,7 @@ void main() { Future<void> buildTest(VisualDensity visualDensity, { bool useText = false }) async { return tester.pumpWidget( MaterialApp( - theme: ThemeData(textTheme: Typography.englishLike2014), + theme: ThemeData(useMaterial3: false), home: Directionality( textDirection: TextDirection.rtl, child: Center( @@ -1059,10 +1062,8 @@ void main() { await tester.pumpWidget( MaterialApp( theme: ThemeData( + useMaterial3: false, colorScheme: const ColorScheme.light(), - // Force Material 2 defaults for the typography and size - // default values as the test was designed against these settings. - textTheme: Typography.englishLike2014, textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom(minimumSize: const Size(64, 36)), ), @@ -1478,7 +1479,7 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: ThemeData(textTheme: Typography.englishLike2014), + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( child: Column( diff --git a/packages/flutter/test/material/text_button_theme_test.dart b/packages/flutter/test/material/text_button_theme_test.dart index b7b6a7de50bb1..473e0a64473e7 100644 --- a/packages/flutter/test/material/text_button_theme_test.dart +++ b/packages/flutter/test/material/text_button_theme_test.dart @@ -12,11 +12,48 @@ void main() { expect(identical(TextButtonThemeData.lerp(data, data, 0.5), data), true); }); - testWidgets('Passing no TextButtonTheme returns defaults', (WidgetTester tester) async { + testWidgets('Material3: Passing no TextButtonTheme returns defaults', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); await tester.pumpWidget( MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme), + theme: ThemeData.from(useMaterial3: true, colorScheme: colorScheme), + home: Scaffold( + body: Center( + child: TextButton( + onPressed: () { }, + child: const Text('button'), + ), + ), + ), + ), + ); + + final Finder buttonMaterial = find.descendant( + of: find.byType(TextButton), + matching: find.byType(Material), + ); + + final Material material = tester.widget<Material>(buttonMaterial); + expect(material.animationDuration, const Duration(milliseconds: 200)); + expect(material.borderRadius, null); + expect(material.color, Colors.transparent); + expect(material.elevation, 0.0); + expect(material.shadowColor, Colors.transparent); + expect(material.shape, const StadiumBorder()); + expect(material.textStyle!.color, colorScheme.primary); + expect(material.textStyle!.fontFamily, 'Roboto'); + expect(material.textStyle!.fontSize, 14); + expect(material.textStyle!.fontWeight, FontWeight.w500); + + final Align align = tester.firstWidget<Align>(find.ancestor(of: find.text('button'), matching: find.byType(Align))); + expect(align.alignment, Alignment.center); + }); + + testWidgets('Material2: Passing no TextButtonTheme returns defaults', (WidgetTester tester) async { + const ColorScheme colorScheme = ColorScheme.light(); + await tester.pumpWidget( + MaterialApp( + theme: ThemeData.from(useMaterial3: false, colorScheme: colorScheme), home: Scaffold( body: Center( child: TextButton( @@ -189,14 +226,85 @@ void main() { }); }); - testWidgets('Theme shadowColor', (WidgetTester tester) async { + testWidgets('Material3: Theme shadowColor', (WidgetTester tester) async { + const ColorScheme colorScheme = ColorScheme.light(); + const Color shadowColor = Color(0xff000001); + const Color overriddenColor = Color(0xff000002); + + Widget buildFrame({ Color? overallShadowColor, Color? themeShadowColor, Color? shadowColor }) { + return MaterialApp( + theme: ThemeData.from( + useMaterial3: true, + colorScheme: colorScheme.copyWith(shadow: overallShadowColor), + ), + home: Scaffold( + body: Center( + child: TextButtonTheme( + data: TextButtonThemeData( + style: TextButton.styleFrom( + shadowColor: themeShadowColor, + ), + ), + child: Builder( + builder: (BuildContext context) { + return TextButton( + style: TextButton.styleFrom( + shadowColor: shadowColor, + ), + onPressed: () { }, + child: const Text('button'), + ); + }, + ), + ), + ), + ), + ); + } + + final Finder buttonMaterialFinder = find.descendant( + of: find.byType(TextButton), + matching: find.byType(Material), + ); + + await tester.pumpWidget(buildFrame()); + Material material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, Colors.transparent); + + await tester.pumpWidget(buildFrame(overallShadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, Colors.transparent); + + await tester.pumpWidget(buildFrame(themeShadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, shadowColor); + + await tester.pumpWidget(buildFrame(shadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, shadowColor); + + await tester.pumpWidget(buildFrame(overallShadowColor: overriddenColor, themeShadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, shadowColor); + + await tester.pumpWidget(buildFrame(themeShadowColor: overriddenColor, shadowColor: shadowColor)); + await tester.pumpAndSettle(); // theme animation + material = tester.widget<Material>(buttonMaterialFinder); + expect(material.shadowColor, shadowColor); + }); + + testWidgets('Material2: Theme shadowColor', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); const Color shadowColor = Color(0xff000001); const Color overriddenColor = Color(0xff000002); Widget buildFrame({ Color? overallShadowColor, Color? themeShadowColor, Color? shadowColor }) { return MaterialApp( - theme: ThemeData.from(colorScheme: colorScheme).copyWith( + theme: ThemeData.from(useMaterial3: false, colorScheme: colorScheme).copyWith( shadowColor: overallShadowColor, ), home: Scaffold( diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 3aabe0f4e9f82..b7a226e989f1a 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -26,6 +26,7 @@ import 'package:flutter_test/flutter_test.dart'; import '../widgets/clipboard_utils.dart'; import '../widgets/editable_text_utils.dart'; +import '../widgets/live_text_utils.dart'; import '../widgets/semantics_tester.dart'; import 'feedback_tester.dart'; @@ -34,6 +35,18 @@ typedef FormatEditUpdateCallback = void Function(TextEditingValue, TextEditingVa // On web, key events in text fields are handled by the browser. const bool areKeyEventsHandledByPlatform = isBrowser; +class CupertinoLocalizationsDelegate extends LocalizationsDelegate<CupertinoLocalizations> { + @override + bool isSupported(Locale locale) => true; + + @override + Future<CupertinoLocalizations> load(Locale locale) => + DefaultCupertinoLocalizations.load(locale); + + @override + bool shouldReload(CupertinoLocalizationsDelegate old) => false; +} + class MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> { @override bool isSupported(Locale locale) => true; @@ -75,6 +88,7 @@ Widget overlayWithEntry(OverlayEntry entry) { delegates: <LocalizationsDelegate<dynamic>>[ WidgetsLocalizationsDelegate(), MaterialLocalizationsDelegate(), + CupertinoLocalizationsDelegate(), ], child: DefaultTextEditingShortcuts( child: Directionality( @@ -190,6 +204,47 @@ void main() { ); } + testWidgets( + 'Live Text button shows and hides correctly when LiveTextStatus changes', + (WidgetTester tester) async { + final LiveTextInputTester liveTextInputTester = LiveTextInputTester(); + addTearDown(liveTextInputTester.dispose); + final TextEditingController controller = TextEditingController(text: ''); + const Key key = ValueKey<String>('TextField'); + final FocusNode focusNode = FocusNode(); + final Widget app = MaterialApp( + theme: ThemeData(platform: TargetPlatform.iOS), + home: Scaffold( + body: Center( + child: TextField( + key: key, + controller: controller, + focusNode: focusNode, + ), + ), + ), + ); + + liveTextInputTester.mockLiveTextInputEnabled = true; + await tester.pumpWidget(app); + focusNode.requestFocus(); + await tester.pumpAndSettle(); + + final Finder textFinder = find.byType(EditableText); + await tester.longPress(textFinder); + await tester.pumpAndSettle(); + expect( + findLiveTextButton(), + kIsWeb ? findsNothing : findsOneWidget, + ); + + liveTextInputTester.mockLiveTextInputEnabled = false; + await tester.longPress(textFinder); + await tester.pumpAndSettle(); + expect(findLiveTextButton(), findsNothing); + }, + ); + testWidgets('text field selection toolbar should hide when the user starts typing', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( @@ -839,19 +894,22 @@ void main() { }); testWidgets('Overflow clipBehavior none golden', (WidgetTester tester) async { - final Widget widget = overlay( - child: RepaintBoundary( - key: const ValueKey<int>(1), - child: SizedBox( - height: 200, - width: 200, - child: Center( - child: SizedBox( - // Make sure the input field is not high enough for the WidgetSpan. - height: 50, - child: TextField( - controller: OverflowWidgetTextEditingController(), - clipBehavior: Clip.none, + final Widget widget = Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: RepaintBoundary( + key: const ValueKey<int>(1), + child: SizedBox( + height: 200, + width: 200, + child: Center( + child: SizedBox( + // Make sure the input field is not high enough for the WidgetSpan. + height: 50, + child: TextField( + controller: OverflowWidgetTextEditingController(), + clipBehavior: Clip.none, + ), ), ), ), @@ -873,13 +931,16 @@ void main() { }); testWidgets('Material cursor android golden', (WidgetTester tester) async { - final Widget widget = overlay( - child: const RepaintBoundary( - key: ValueKey<int>(1), - child: TextField( - cursorColor: Colors.blue, - cursorWidth: 15, - cursorRadius: Radius.circular(3.0), + final Widget widget = Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: const RepaintBoundary( + key: ValueKey<int>(1), + child: TextField( + cursorColor: Colors.blue, + cursorWidth: 15, + cursorRadius: Radius.circular(3.0), + ), ), ), ); @@ -899,13 +960,16 @@ void main() { }); testWidgets('Material cursor golden', (WidgetTester tester) async { - final Widget widget = overlay( - child: const RepaintBoundary( - key: ValueKey<int>(1), - child: TextField( - cursorColor: Colors.blue, - cursorWidth: 15, - cursorRadius: Radius.circular(3.0), + final Widget widget = Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: const RepaintBoundary( + key: ValueKey<int>(1), + child: TextField( + cursorColor: Colors.blue, + cursorWidth: 15, + cursorRadius: Radius.circular(3.0), + ), ), ), ); @@ -969,8 +1033,9 @@ void main() { testWidgets('text field selection toolbar renders correctly inside opacity', (WidgetTester tester) async { await tester.pumpWidget( - const MaterialApp( - home: Scaffold( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Scaffold( body: Center( child: SizedBox( width: 100, @@ -1075,6 +1140,7 @@ void main() { ); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: RepaintBoundary( @@ -1123,6 +1189,7 @@ void main() { ); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: RepaintBoundary( @@ -1230,12 +1297,15 @@ void main() { final FocusNode focusNode = FocusNode(); EditableText.debugDeterministicCursor = true; await tester.pumpWidget( - overlay( - child: RepaintBoundary( - child: TextField( - cursorWidth: 15.0, - controller: controller, - focusNode: focusNode, + Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: RepaintBoundary( + child: TextField( + cursorWidth: 15.0, + controller: controller, + focusNode: focusNode, + ), ), ), ), @@ -1257,13 +1327,16 @@ void main() { final FocusNode focusNode = FocusNode(); EditableText.debugDeterministicCursor = true; await tester.pumpWidget( - overlay( - child: RepaintBoundary( - child: TextField( - cursorWidth: 15.0, - cursorRadius: const Radius.circular(3.0), - controller: controller, - focusNode: focusNode, + Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: RepaintBoundary( + child: TextField( + cursorWidth: 15.0, + cursorRadius: const Radius.circular(3.0), + controller: controller, + focusNode: focusNode, + ), ), ), ), @@ -1285,13 +1358,16 @@ void main() { final FocusNode focusNode = FocusNode(); EditableText.debugDeterministicCursor = true; await tester.pumpWidget( - overlay( - child: RepaintBoundary( - child: TextField( - cursorWidth: 15.0, - cursorHeight: 30.0, - controller: controller, - focusNode: focusNode, + Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: RepaintBoundary( + child: TextField( + cursorWidth: 15.0, + cursorHeight: 30.0, + controller: controller, + focusNode: focusNode, + ), ), ), ), @@ -1310,11 +1386,14 @@ void main() { final TextEditingController controller = TextEditingController(); await tester.pumpWidget( - overlay( - child: TextField( - key: textFieldKey, - controller: controller, - maxLines: null, + Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: TextField( + key: textFieldKey, + controller: controller, + maxLines: null, + ), ), ), ); @@ -2110,6 +2189,52 @@ void main() { variant: TargetPlatformVariant.mobile(), ); + testWidgets('Can select text with a mouse when wrapped in a GestureDetector with tap/double tap callbacks', (WidgetTester tester) async { + // This is a regression test for https://github.com/flutter/flutter/issues/129161. + final TextEditingController controller = TextEditingController(); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: GestureDetector( + onTap: () {}, + onDoubleTap: () {}, + 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')); + + final TestGesture gesture = await tester.startGesture(ePos, kind: PointerDeviceKind.mouse); + await tester.pump(); + await gesture.up(); + // This is to allow the GestureArena to decide a winner between TapGestureRecognizer, + // DoubleTapGestureRecognizer, and BaseTapAndDragGestureRecognizer. + await tester.pumpAndSettle(kDoubleTapTimeout); + expect(controller.selection.isCollapsed, true); + expect(controller.selection.baseOffset, testValue.indexOf('e')); + + await gesture.down(ePos); + await tester.pump(); + await gesture.moveTo(gPos); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(controller.selection.baseOffset, testValue.indexOf('e')); + expect(controller.selection.extentOffset, testValue.indexOf('g')); + }, variant: TargetPlatformVariant.desktop()); + testWidgets('Can select text by dragging with a mouse', (WidgetTester tester) async { final TextEditingController controller = TextEditingController(); @@ -3379,12 +3504,15 @@ void main() { ); await tester.pumpWidget( - overlay( - child: TextField( - dragStartBehavior: DragStartBehavior.down, - controller: controller, - maxLines: 3, - minLines: 3, + Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: TextField( + dragStartBehavior: DragStartBehavior.down, + controller: controller, + maxLines: 3, + minLines: 3, + ), ), ), ); @@ -3861,6 +3989,7 @@ void main() { final TextEditingController controller = TextEditingController(); await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Padding( padding: const EdgeInsets.all(30.0), @@ -4410,6 +4539,7 @@ void main() { Widget? prefix, }) { return boilerplate( + theme: ThemeData(useMaterial3: false), child: SizedBox( height: height, child: TextField( @@ -4548,12 +4678,15 @@ void main() { final TextEditingController controller = TextEditingController(); await tester.pumpWidget( - overlay( - child: TextField( - dragStartBehavior: DragStartBehavior.down, - controller: controller, - style: const TextStyle(color: Colors.black, fontSize: 34.0), - maxLines: 3, + Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: TextField( + dragStartBehavior: DragStartBehavior.down, + controller: controller, + style: const TextStyle(color: Colors.black, fontSize: 34.0), + maxLines: 3, + ), ), ), ); @@ -4796,11 +4929,14 @@ void main() { testWidgets('TextField errorText trumps helperText', (WidgetTester tester) async { await tester.pumpWidget( - overlay( - child: const TextField( - decoration: InputDecoration( - errorText: 'error text', - helperText: 'helper text', + Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: const TextField( + decoration: InputDecoration( + errorText: 'error text', + helperText: 'helper text', + ), ), ), ), @@ -4810,7 +4946,7 @@ void main() { }); testWidgets('TextField with default helperStyle', (WidgetTester tester) async { - final ThemeData themeData = ThemeData(hintColor: Colors.blue[500]); + final ThemeData themeData = ThemeData(hintColor: Colors.blue[500], useMaterial3: false); await tester.pumpWidget( overlay( child: Theme( @@ -5214,12 +5350,15 @@ void main() { testWidgets('Collapsed hint text placement', (WidgetTester tester) async { await tester.pumpWidget( - overlay( - child: const TextField( - decoration: InputDecoration.collapsed( - hintText: 'hint', + Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: const TextField( + decoration: InputDecoration.collapsed( + hintText: 'hint', + ), + strutStyle: StrutStyle.disabled, ), - strutStyle: StrutStyle.disabled, ), ), ); @@ -5585,11 +5724,14 @@ void main() { final TextEditingController controller = TextEditingController(); await tester.pumpWidget( - overlay( - child: SizedBox( - width: 100.0, - child: TextField( - controller: controller, + Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: SizedBox( + width: 100.0, + child: TextField( + controller: controller, + ), ), ), ), @@ -6358,6 +6500,7 @@ void main() { const String errorText = 'error text'; Widget buildFrame(bool enabled, bool hasError) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField( @@ -6405,6 +6548,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField( @@ -7251,40 +7395,43 @@ void main() { final Key keyB = UniqueKey(); await tester.pumpWidget( - overlay( - child: Row( - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - children: <Widget>[ - Expanded( - child: TextField( - key: keyA, - decoration: null, - controller: controllerA, - // The point size of the font must be a multiple of 4 until - // https://github.com/flutter/flutter/issues/122066 is resolved. - style: const TextStyle(fontFamily: 'FlutterTest', fontSize: 12.0), - strutStyle: StrutStyle.disabled, + Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: <Widget>[ + Expanded( + child: TextField( + key: keyA, + decoration: null, + controller: controllerA, + // The point size of the font must be a multiple of 4 until + // https://github.com/flutter/flutter/issues/122066 is resolved. + style: const TextStyle(fontFamily: 'FlutterTest', fontSize: 12.0), + strutStyle: StrutStyle.disabled, + ), ), - ), - const Text( - 'abc', - // The point size of the font must be a multiple of 4 until - // https://github.com/flutter/flutter/issues/122066 is resolved. - style: TextStyle(fontFamily: 'FlutterTest', fontSize: 24.0), - ), - Expanded( - child: TextField( - key: keyB, - decoration: null, - controller: controllerB, + const Text( + 'abc', // The point size of the font must be a multiple of 4 until // https://github.com/flutter/flutter/issues/122066 is resolved. - style: const TextStyle(fontFamily: 'FlutterTest', fontSize: 36.0), - strutStyle: StrutStyle.disabled, + style: TextStyle(fontFamily: 'FlutterTest', fontSize: 24.0), ), - ), - ], + Expanded( + child: TextField( + key: keyB, + decoration: null, + controller: controllerB, + // The point size of the font must be a multiple of 4 until + // https://github.com/flutter/flutter/issues/122066 is resolved. + style: const TextStyle(fontFamily: 'FlutterTest', fontSize: 36.0), + strutStyle: StrutStyle.disabled, + ), + ), + ], + ), ), ), ); @@ -7310,38 +7457,41 @@ void main() { final Key keyB = UniqueKey(); await tester.pumpWidget( - overlay( - child: Row( - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - children: <Widget>[ - Expanded( - child: TextField( - key: keyA, - decoration: null, - controller: controllerA, - // The point size of the font must be a multiple of 4 until - // https://github.com/flutter/flutter/issues/122066 is resolved. - style: const TextStyle(fontFamily: 'FlutterTest', fontSize: 12.0), + Theme( + data: ThemeData(useMaterial3: false), + child: overlay( + child: Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: <Widget>[ + Expanded( + child: TextField( + key: keyA, + decoration: null, + controller: controllerA, + // The point size of the font must be a multiple of 4 until + // https://github.com/flutter/flutter/issues/122066 is resolved. + style: const TextStyle(fontFamily: 'FlutterTest', fontSize: 12.0), + ), ), - ), - const Text( - 'abc', - // The point size of the font must be a multiple of 4 until - // https://github.com/flutter/flutter/issues/122066 is resolved. - style: TextStyle(fontFamily: 'FlutterTest', fontSize: 24.0), - ), - Expanded( - child: TextField( - key: keyB, - decoration: null, - controller: controllerB, + const Text( + 'abc', // The point size of the font must be a multiple of 4 until // https://github.com/flutter/flutter/issues/122066 is resolved. - style: const TextStyle(fontFamily: 'FlutterTest', fontSize: 36.0), + style: TextStyle(fontFamily: 'FlutterTest', fontSize: 24.0), ), - ), - ], + Expanded( + child: TextField( + key: keyB, + decoration: null, + controller: controllerB, + // The point size of the font must be a multiple of 4 until + // https://github.com/flutter/flutter/issues/122066 is resolved. + style: const TextStyle(fontFamily: 'FlutterTest', fontSize: 36.0), + ), + ), + ], + ), ), ), ); @@ -8138,8 +8288,9 @@ void main() { testWidgets('TextField displays text with text direction', (WidgetTester tester) async { await tester.pumpWidget( - const MaterialApp( - home: Material( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Material( child: TextField( textDirection: TextDirection.rtl, ), @@ -8158,8 +8309,9 @@ void main() { expect(topLeft.dx, equals(701)); await tester.pumpWidget( - const MaterialApp( - home: Material( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Material( child: TextField( textDirection: TextDirection.ltr, ), @@ -8366,6 +8518,7 @@ void main() { final TextEditingController controller = TextEditingController(text: 'Just some text'); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: MediaQuery( data: const MediaQueryData(textScaleFactor: 4.0), @@ -8582,6 +8735,7 @@ void main() { required TextAlign textAlign, }) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( child: Column( @@ -8652,6 +8806,7 @@ void main() { // Regression test for https://github.com/flutter/flutter/issues/23994 final ThemeData themeData = ThemeData( + useMaterial3: false, textTheme: TextTheme( titleMedium: TextStyle( color: Colors.blue[500], @@ -9471,6 +9626,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: TextField( dragStartBehavior: DragStartBehavior.down, @@ -9583,6 +9739,7 @@ void main() { ); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField( @@ -9673,6 +9830,7 @@ void main() { ); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField( @@ -10185,6 +10343,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: TextField( dragStartBehavior: DragStartBehavior.down, @@ -10285,6 +10444,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: TextField( dragStartBehavior: DragStartBehavior.down, @@ -11418,6 +11578,7 @@ void main() { ); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField( @@ -11506,6 +11667,7 @@ void main() { ); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField( @@ -11594,6 +11756,7 @@ void main() { ); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField( @@ -11759,6 +11922,7 @@ void main() { ); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField( @@ -12119,6 +12283,7 @@ void main() { final bool isTargetPlatformMobile = defaultTargetPlatform == TargetPlatform.iOS; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField( @@ -12232,6 +12397,7 @@ void main() { ); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField( @@ -12878,7 +13044,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: TextField( @@ -12923,7 +13089,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: TextField( @@ -12947,7 +13113,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: TextField( @@ -12978,7 +13144,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: TextField( @@ -13007,7 +13173,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: TextField( @@ -13036,7 +13202,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: TextField( @@ -13065,11 +13231,14 @@ void main() { testWidgets('Caret center position', (WidgetTester tester) async { await tester.pumpWidget( overlay( - child: const SizedBox( - width: 300.0, - child: TextField( - textAlign: TextAlign.center, - decoration: null, + child: Theme( + data: ThemeData(useMaterial3: false), + child: const SizedBox( + width: 300.0, + child: TextField( + textAlign: TextAlign.center, + decoration: null, + ), ), ), ), @@ -13105,11 +13274,14 @@ void main() { testWidgets('Caret indexes into trailing whitespace center align', (WidgetTester tester) async { await tester.pumpWidget( overlay( - child: const SizedBox( - width: 300.0, - child: TextField( - textAlign: TextAlign.center, - decoration: null, + child: Theme( + data: ThemeData(useMaterial3: false), + child: const SizedBox( + width: 300.0, + child: TextField( + textAlign: TextAlign.center, + decoration: null, + ), ), ), ), @@ -13573,7 +13745,7 @@ void main() { final ScrollController scrollController = ScrollController(); await tester.pumpWidget(MaterialApp( - theme: ThemeData(), + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( child: ListView( @@ -13604,7 +13776,7 @@ void main() { final ScrollController textFieldScrollController = ScrollController(); await tester.pumpWidget(MaterialApp( - theme: ThemeData(), + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( child: ListView( @@ -14291,6 +14463,7 @@ void main() { Widget textFieldBuilder({ FloatingLabelBehavior behavior = FloatingLabelBehavior.auto }) { return MaterialApp( theme: ThemeData( + useMaterial3: false, inputDecorationTheme: InputDecorationTheme( floatingLabelBehavior: behavior, ), @@ -14872,6 +15045,7 @@ void main() { final bool isTargetPlatformMobile = defaultTargetPlatform == TargetPlatform.iOS; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField(controller: controller), @@ -14977,6 +15151,7 @@ void main() { || defaultTargetPlatform == TargetPlatform.fuchsia; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField(controller: controller), @@ -15083,6 +15258,7 @@ void main() { final bool isTargetPlatformMobile = defaultTargetPlatform == TargetPlatform.iOS; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField(controller: controller), @@ -15187,6 +15363,7 @@ void main() { || defaultTargetPlatform == TargetPlatform.fuchsia; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextField(controller: controller), @@ -15661,6 +15838,49 @@ void main() { }, skip: kIsWeb, // [intended] on web the browser handles the context menu. ); + + testWidgets('contextMenuBuilder changes from default to null', (WidgetTester tester) async { + final GlobalKey key = GlobalKey(); + final TextEditingController controller = TextEditingController(text: ''); + await tester.pumpWidget(MaterialApp(home: Material(child: TextField(key: key, controller: controller)))); + + await tester.pump(); // Wait for autofocus to take effect. + + // Long-press to bring up the context menu. + final Finder textFinder = find.byType(EditableText); + await tester.longPress(textFinder); + tester.state<EditableTextState>(textFinder).showToolbar(); + await tester.pump(); + + expect(find.byType(AdaptiveTextSelectionToolbar), findsOneWidget); + + // Set contextMenuBuilder to null. + await tester.pumpWidget( + MaterialApp( + home: Material( + child: TextField( + key: key, + controller: controller, + contextMenuBuilder: null, + ), + ), + ), + ); + + // Trigger build one more time... + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Padding( + padding: EdgeInsets.zero, + child: TextField(key: key, controller: controller, contextMenuBuilder: null), + ), + ), + ), + ); + }, + skip: kIsWeb, // [intended] on web the browser handles the context menu. + ); }); group('magnifier builder', () { @@ -15671,7 +15891,7 @@ void main() { ); final TextField textField = TextField( magnifierConfiguration: TextMagnifierConfiguration( - magnifierBuilder: (_, __, ___) => customMagnifier, + magnifierBuilder: (BuildContext context, MagnifierController controller, ValueNotifier<MagnifierInfo>? info) => customMagnifier, ), ); @@ -15769,7 +15989,7 @@ void main() { controller: controller, magnifierConfiguration: TextMagnifierConfiguration( magnifierBuilder: ( - _, + BuildContext context, MagnifierController controller, ValueNotifier<MagnifierInfo> localMagnifierInfo ) { @@ -15833,7 +16053,7 @@ void main() { controller: controller, magnifierConfiguration: TextMagnifierConfiguration( magnifierBuilder: ( - _, + BuildContext context, MagnifierController controller, ValueNotifier<MagnifierInfo> localMagnifierInfo ) { @@ -15935,7 +16155,7 @@ void main() { controller: controller, magnifierConfiguration: TextMagnifierConfiguration( magnifierBuilder: ( - _, + BuildContext context, MagnifierController controller, ValueNotifier<MagnifierInfo> localMagnifierInfo ) { @@ -15986,6 +16206,54 @@ void main() { await tester.pumpAndSettle(); expect(find.byKey(fakeMagnifier.key!), findsNothing); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.iOS })); + + testWidgets('magnifier does not show when tapping outside field', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/128321 + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Padding( + padding: const EdgeInsets.all(20), + child: TextField( + magnifierConfiguration: TextMagnifierConfiguration( + magnifierBuilder: ( + BuildContext context, + MagnifierController controller, + ValueNotifier<MagnifierInfo> localMagnifierInfo + ) { + magnifierInfo = localMagnifierInfo; + return fakeMagnifier; + }, + ), + onTapOutside: (PointerDownEvent event) { + FocusManager.instance.primaryFocus?.unfocus(); + } + ), + ), + ), + ), + ); + + await tester.tapAt( + tester.getCenter(find.byType(TextField)), + ); + await tester.pump(); + + expect(find.byKey(fakeMagnifier.key!), findsNothing); + + final TestGesture gesture = await tester.startGesture( + tester.getBottomLeft(find.byType(TextField)) - const Offset(10.0, 20.0), + ); + await tester.pump(); + expect(find.byKey(fakeMagnifier.key!), findsNothing); + await gesture.up(); + await tester.pump(); + + expect(find.byKey(fakeMagnifier.key!), findsNothing); + }, + skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu. + variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android }), + ); }); group('TapRegion integration', () { @@ -16381,7 +16649,7 @@ class _ObscureTextTestWidgetState extends State<_ObscureTextTestWidget> { return MaterialApp( home: Scaffold( body: Builder( - builder: (_) { + builder: (BuildContext context) { return Column( children: <Widget>[ TextField( diff --git a/packages/flutter/test/material/text_form_field_test.dart b/packages/flutter/test/material/text_form_field_test.dart index afcaedbffdb74..b46266268ce22 100644 --- a/packages/flutter/test/material/text_form_field_test.dart +++ b/packages/flutter/test/material/text_form_field_test.dart @@ -591,12 +591,13 @@ void main() { }); - testWidgets('Disabled field hides helper and counter', (WidgetTester tester) async { + testWidgets('Disabled field hides helper and counter in M2', (WidgetTester tester) async { const String helperText = 'helper text'; const String counterText = 'counter text'; const String errorText = 'error text'; Widget buildFrame(bool enabled, bool hasError) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: TextFormField( diff --git a/packages/flutter/test/material/text_selection_test.dart b/packages/flutter/test/material/text_selection_test.dart index 91ef755b31a17..37c22e7ecbfb8 100644 --- a/packages/flutter/test/material/text_selection_test.dart +++ b/packages/flutter/test/material/text_selection_test.dart @@ -376,7 +376,7 @@ void main() { final TextEditingController controller = TextEditingController(text: 'abc def ghi'); await tester.pumpWidget(MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: Directionality( textDirection: TextDirection.ltr, child: MediaQuery( diff --git a/packages/flutter/test/material/text_selection_theme_test.dart b/packages/flutter/test/material/text_selection_theme_test.dart index 5f512f9a4a076..bc80531b162b7 100644 --- a/packages/flutter/test/material/text_selection_theme_test.dart +++ b/packages/flutter/test/material/text_selection_theme_test.dart @@ -60,9 +60,11 @@ void main() { }); testWidgets('Empty textSelectionTheme will use defaults', (WidgetTester tester) async { - const Color defaultCursorColor = Color(0xff2196f3); - const Color defaultSelectionColor = Color(0x662196f3); - const Color defaultSelectionHandleColor = Color(0xff2196f3); + final ThemeData theme = ThemeData(); + final bool material3 = theme.useMaterial3; + final Color defaultCursorColor = material3 ? theme.colorScheme.primary : const Color(0xff2196f3); + final Color defaultSelectionColor = material3 ? theme.colorScheme.primary.withOpacity(0.40) : const Color(0x662196f3); + final Color defaultSelectionHandleColor = material3 ? theme.colorScheme.primary : const Color(0xff2196f3); EditableText.debugDeterministicCursor = true; addTearDown(() { @@ -70,8 +72,9 @@ void main() { }); // Test TextField's cursor & selection color. await tester.pumpWidget( - const MaterialApp( - home: Material( + MaterialApp( + theme: theme, + home: const Material( child: TextField(autofocus: true), ), ), @@ -82,7 +85,7 @@ void main() { final EditableTextState editableTextState = tester.firstState(find.byType(EditableText)); final RenderEditable renderEditable = editableTextState.renderEditable; expect(renderEditable.cursorColor, defaultCursorColor); - expect(renderEditable.selectionColor?.value, defaultSelectionColor.value); + expect(renderEditable.selectionColor, defaultSelectionColor); // Test the selection handle color. await tester.pumpWidget( diff --git a/packages/flutter/test/material/theme_data_test.dart b/packages/flutter/test/material/theme_data_test.dart index 46aad3cb189d4..a69ff727cf270 100644 --- a/packages/flutter/test/material/theme_data_test.dart +++ b/packages/flutter/test/material/theme_data_test.dart @@ -23,7 +23,7 @@ void main() { test('Defaults to the default typography for the platform', () { for (final TargetPlatform platform in TargetPlatform.values) { - final ThemeData theme = ThemeData(platform: platform); + final ThemeData theme = ThemeData(platform: platform, useMaterial3: false); final Typography typography = Typography.material2018(platform: platform); expect( theme.textTheme, @@ -34,8 +34,8 @@ void main() { }); test('Default text theme contrasts with brightness', () { - final ThemeData lightTheme = ThemeData(brightness: Brightness.light); - final ThemeData darkTheme = ThemeData(brightness: Brightness.dark); + final ThemeData lightTheme = ThemeData(brightness: Brightness.light, useMaterial3: false); + final ThemeData darkTheme = ThemeData(brightness: Brightness.dark, useMaterial3: false); final Typography typography = Typography.material2018(platform: lightTheme.platform); expect(lightTheme.textTheme.titleLarge!.color, typography.black.titleLarge!.color); @@ -43,8 +43,8 @@ void main() { }); test('Default primary text theme contrasts with primary brightness', () { - final ThemeData lightTheme = ThemeData(primaryColor: Colors.white); - final ThemeData darkTheme = ThemeData(primaryColor: Colors.black); + final ThemeData lightTheme = ThemeData(primaryColor: Colors.white, useMaterial3: false); + final ThemeData darkTheme = ThemeData(primaryColor: Colors.black, useMaterial3: false); final Typography typography = Typography.material2018(platform: lightTheme.platform); expect(lightTheme.primaryTextTheme.titleLarge!.color, typography.black.titleLarge!.color); @@ -52,8 +52,8 @@ void main() { }); test('Default icon theme contrasts with brightness', () { - final ThemeData lightTheme = ThemeData(brightness: Brightness.light); - final ThemeData darkTheme = ThemeData(brightness: Brightness.dark); + final ThemeData lightTheme = ThemeData(brightness: Brightness.light, useMaterial3: false); + final ThemeData darkTheme = ThemeData(brightness: Brightness.dark, useMaterial3: false); final Typography typography = Typography.material2018(platform: lightTheme.platform); expect(lightTheme.textTheme.titleLarge!.color, typography.black.titleLarge!.color); @@ -61,8 +61,8 @@ void main() { }); test('Default primary icon theme contrasts with primary brightness', () { - final ThemeData lightTheme = ThemeData(primaryColor: Colors.white); - final ThemeData darkTheme = ThemeData(primaryColor: Colors.black); + final ThemeData lightTheme = ThemeData(primaryColor: Colors.white, useMaterial3: false); + final ThemeData darkTheme = ThemeData(primaryColor: Colors.black, useMaterial3: false); final Typography typography = Typography.material2018(platform: lightTheme.platform); expect(lightTheme.primaryTextTheme.titleLarge!.color, typography.black.titleLarge!.color); @@ -125,19 +125,6 @@ void main() { expect(ThemeData.estimateBrightnessForColor(Colors.indigo), equals(Brightness.dark)); }); - test('Can estimate brightness - indirectly', () { - expect(ThemeData(primaryColor: Colors.white).primaryColorBrightness, equals(Brightness.light)); - expect(ThemeData(primaryColor: Colors.black).primaryColorBrightness, equals(Brightness.dark)); - expect(ThemeData(primaryColor: Colors.blue).primaryColorBrightness, equals(Brightness.dark)); - expect(ThemeData(primaryColor: Colors.yellow).primaryColorBrightness, equals(Brightness.light)); - expect(ThemeData(primaryColor: Colors.deepOrange).primaryColorBrightness, equals(Brightness.dark)); - expect(ThemeData(primaryColor: Colors.orange).primaryColorBrightness, equals(Brightness.light)); - expect(ThemeData(primaryColor: Colors.lime).primaryColorBrightness, equals(Brightness.light)); - expect(ThemeData(primaryColor: Colors.grey).primaryColorBrightness, equals(Brightness.light)); - expect(ThemeData(primaryColor: Colors.teal).primaryColorBrightness, equals(Brightness.dark)); - expect(ThemeData(primaryColor: Colors.indigo).primaryColorBrightness, equals(Brightness.dark)); - }); - test('cursorColor', () { expect(const TextSelectionThemeData(cursorColor: Colors.red).cursorColor, Colors.red); }); @@ -182,7 +169,6 @@ void main() { expect(theme.colorScheme.brightness, Brightness.light); expect(theme.primaryColor, theme.colorScheme.primary); - expect(theme.primaryColorBrightness, Brightness.dark); expect(theme.canvasColor, theme.colorScheme.background); expect(theme.scaffoldBackgroundColor, theme.colorScheme.background); expect(theme.bottomAppBarColor, theme.colorScheme.surface); @@ -232,7 +218,6 @@ void main() { expect(theme.colorScheme.brightness, Brightness.dark); expect(theme.primaryColor, theme.colorScheme.surface); - expect(theme.primaryColorBrightness, Brightness.dark); expect(theme.canvasColor, theme.colorScheme.background); expect(theme.scaffoldBackgroundColor, theme.colorScheme.background); expect(theme.bottomAppBarColor, theme.colorScheme.surface); @@ -279,7 +264,6 @@ void main() { expect(theme.colorScheme.brightness, Brightness.light); expect(theme.primaryColor, theme.colorScheme.primary); - expect(theme.primaryColorBrightness, Brightness.dark); expect(theme.canvasColor, theme.colorScheme.background); expect(theme.scaffoldBackgroundColor, theme.colorScheme.background); expect(theme.bottomAppBarColor, theme.colorScheme.surface); @@ -327,7 +311,6 @@ void main() { expect(theme.colorScheme.brightness, Brightness.light); expect(theme.primaryColor, theme.colorScheme.primary); - expect(theme.primaryColorBrightness, Brightness.dark); expect(theme.canvasColor, theme.colorScheme.background); expect(theme.scaffoldBackgroundColor, theme.colorScheme.background); expect(theme.bottomAppBarColor, theme.colorScheme.surface); @@ -375,7 +358,6 @@ void main() { expect(theme.colorScheme.brightness, Brightness.dark); expect(theme.primaryColor, theme.colorScheme.surface); - expect(theme.primaryColorBrightness, Brightness.dark); expect(theme.canvasColor, theme.colorScheme.background); expect(theme.scaffoldBackgroundColor, theme.colorScheme.background); expect(theme.bottomAppBarColor, theme.colorScheme.surface); @@ -826,7 +808,6 @@ void main() { toggleButtonsTheme: const ToggleButtonsThemeData(textStyle: TextStyle(color: Colors.black)), tooltipTheme: const TooltipThemeData(height: 100), // DEPRECATED (newest deprecations at the bottom) - primaryColorBrightness: Brightness.dark, androidOverscrollIndicator: AndroidOverscrollIndicator.glow, toggleableActiveColor: Colors.black, selectedRowColor: Colors.black, @@ -946,7 +927,6 @@ void main() { tooltipTheme: const TooltipThemeData(height: 100), // DEPRECATED (newest deprecations at the bottom) - primaryColorBrightness: Brightness.light, androidOverscrollIndicator: AndroidOverscrollIndicator.stretch, toggleableActiveColor: Colors.white, selectedRowColor: Colors.white, @@ -1049,7 +1029,6 @@ void main() { tooltipTheme: otherTheme.tooltipTheme, // DEPRECATED (newest deprecations at the bottom) - primaryColorBrightness: otherTheme.primaryColorBrightness, androidOverscrollIndicator: otherTheme.androidOverscrollIndicator, toggleableActiveColor: otherTheme.toggleableActiveColor, selectedRowColor: otherTheme.selectedRowColor, @@ -1153,7 +1132,6 @@ void main() { expect(themeDataCopy.tooltipTheme, equals(otherTheme.tooltipTheme)); // DEPRECATED (newest deprecations at the bottom) - expect(themeDataCopy.primaryColorBrightness, equals(otherTheme.primaryColorBrightness)); expect(themeDataCopy.androidOverscrollIndicator, equals(otherTheme.androidOverscrollIndicator)); expect(themeDataCopy.toggleableActiveColor, equals(otherTheme.toggleableActiveColor)); expect(themeDataCopy.selectedRowColor, equals(otherTheme.selectedRowColor)); @@ -1288,7 +1266,6 @@ void main() { 'toggleButtonsTheme', 'tooltipTheme', // DEPRECATED (newest deprecations at the bottom) - 'primaryColorBrightness', 'androidOverscrollIndicator', 'toggleableActiveColor', 'selectedRowColor', diff --git a/packages/flutter/test/material/theme_defaults_test.dart b/packages/flutter/test/material/theme_defaults_test.dart index 23d1e47383eeb..db8eec4b7e792 100644 --- a/packages/flutter/test/material/theme_defaults_test.dart +++ b/packages/flutter/test/material/theme_defaults_test.dart @@ -11,12 +11,15 @@ void main() { group('FloatingActionButton', () { const BoxConstraints defaultFABConstraints = BoxConstraints.tightFor(width: 56.0, height: 56.0); const ShapeBorder defaultFABShape = CircleBorder(); + const ShapeBorder defaultFABShapeM3 = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0))); const EdgeInsets defaultFABPadding = EdgeInsets.zero; testWidgets('theme: ThemeData.light(), enabled: true', (WidgetTester tester) async { + final ThemeData theme = ThemeData.light(); + final bool material3 = theme.useMaterial3; await tester.pumpWidget( MaterialApp( - theme: ThemeData.light(), + theme: theme, home: Center( child: FloatingActionButton( onPressed: () { }, // button.enabled == true @@ -28,22 +31,24 @@ void main() { final RawMaterialButton raw = tester.widget<RawMaterialButton>(find.byType(RawMaterialButton)); expect(raw.enabled, true); - expect(raw.textStyle!.color, const Color(0xffffffff)); - expect(raw.fillColor, const Color(0xff2196f3)); + expect(raw.textStyle!.color, material3 ? theme.colorScheme.onPrimaryContainer : const Color(0xffffffff)); + expect(raw.fillColor, material3 ? theme.colorScheme.primaryContainer : const Color(0xff2196f3)); expect(raw.elevation, 6.0); - expect(raw.highlightElevation, 12.0); + expect(raw.highlightElevation, material3 ? 6.0 : 12.0); expect(raw.disabledElevation, 6.0); expect(raw.constraints, defaultFABConstraints); expect(raw.padding, defaultFABPadding); - expect(raw.shape, defaultFABShape); + expect(raw.shape, material3 ? defaultFABShapeM3 : defaultFABShape); expect(raw.animationDuration, defaultButtonDuration); expect(raw.materialTapTargetSize, MaterialTapTargetSize.padded); }); testWidgets('theme: ThemeData.light(), enabled: false', (WidgetTester tester) async { + final ThemeData theme = ThemeData.light(); + final bool material3 = theme.useMaterial3; await tester.pumpWidget( MaterialApp( - theme: ThemeData.light(), + theme: theme, home: const Center( child: FloatingActionButton( onPressed: null, // button.enabled == false @@ -55,16 +60,16 @@ void main() { final RawMaterialButton raw = tester.widget<RawMaterialButton>(find.byType(RawMaterialButton)); expect(raw.enabled, false); - expect(raw.textStyle!.color, const Color(0xffffffff)); - expect(raw.fillColor, const Color(0xff2196f3)); + expect(raw.textStyle!.color, material3 ? theme.colorScheme.onPrimaryContainer : const Color(0xffffffff)); + expect(raw.fillColor, material3 ? theme.colorScheme.primaryContainer : const Color(0xff2196f3)); // highlightColor, disabled button can't be pressed // splashColor, disabled button doesn't splash expect(raw.elevation, 6.0); - expect(raw.highlightElevation, 12.0); + expect(raw.highlightElevation, material3 ? 6.0 : 12.0); expect(raw.disabledElevation, 6.0); expect(raw.constraints, defaultFABConstraints); expect(raw.padding, defaultFABPadding); - expect(raw.shape, defaultFABShape); + expect(raw.shape, material3 ? defaultFABShapeM3 : defaultFABShape); expect(raw.animationDuration, defaultButtonDuration); expect(raw.materialTapTargetSize, MaterialTapTargetSize.padded); }); diff --git a/packages/flutter/test/material/theme_test.dart b/packages/flutter/test/material/theme_test.dart index 85f4fbbc4ed60..3a907941c33aa 100644 --- a/packages/flutter/test/material/theme_test.dart +++ b/packages/flutter/test/material/theme_test.dart @@ -10,6 +10,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { const TextTheme defaultGeometryTheme = Typography.englishLike2014; + const TextTheme defaultGeometryThemeM3 = Typography.englishLike2021; test('ThemeDataTween control test', () { final ThemeData light = ThemeData.light(); @@ -96,17 +97,37 @@ void main() { }); testWidgets('Fallback theme', (WidgetTester tester) async { + // For material 2 late BuildContext capturedContext; await tester.pumpWidget( - Builder( - builder: (BuildContext context) { - capturedContext = context; - return Container(); - }, + Theme( + data: ThemeData(useMaterial3: false), + child: Builder( + builder: (BuildContext context) { + capturedContext = context; + return Container(); + }, + ), ), ); - expect(Theme.of(capturedContext), equals(ThemeData.localize(ThemeData.fallback(), defaultGeometryTheme))); + expect(Theme.of(capturedContext), equals(ThemeData.localize(ThemeData.fallback(useMaterial3: false), defaultGeometryTheme))); + + // For material 3 + late BuildContext capturedContextM3; + await tester.pumpWidget( + Theme( + data: ThemeData(useMaterial3: true), + child: Builder( + builder: (BuildContext context) { + capturedContextM3 = context; + return Container(); + }, + ), + ), + ); + + expect(Theme.of(capturedContextM3), equals(ThemeData.localize(ThemeData.fallback(useMaterial3: true), defaultGeometryThemeM3))); }); testWidgets('ThemeData.localize memoizes the result', (WidgetTester tester) async { @@ -133,7 +154,8 @@ void main() { }); testWidgets('ThemeData with null typography uses proper defaults', (WidgetTester tester) async { - expect(ThemeData().typography, Typography.material2014()); + final ThemeData m2Theme = ThemeData(useMaterial3: false); + expect(m2Theme.typography, Typography.material2014()); final ThemeData m3Theme = ThemeData(useMaterial3: true); expect(m3Theme.typography, Typography.material2021(colorScheme: m3Theme.colorScheme)); }); @@ -414,15 +436,18 @@ void main() { expect(actualFontSize, kMagicFontSize); }); - testWidgets('Default Theme provides all basic TextStyle properties', (WidgetTester tester) async { + testWidgets('Default Theme provides all basic TextStyle properties - M2', (WidgetTester tester) async { late ThemeData theme; - await tester.pumpWidget(Directionality( - textDirection: TextDirection.ltr, - child: Builder( - builder: (BuildContext context) { - theme = Theme.of(context); - return const Text('A'); - }, + await tester.pumpWidget(Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: Builder( + builder: (BuildContext context) { + theme = Theme.of(context); + return const Text('A'); + }, + ), ), )); @@ -441,6 +466,7 @@ void main() { textTheme.bodySmall!, textTheme.labelLarge!, textTheme.labelMedium!, + // textTheme.labelSmall!, ]; } @@ -468,6 +494,63 @@ void main() { expect(theme.textTheme.displayLarge!.debugLabel, '(englishLike displayLarge 2014).merge(blackMountainView displayLarge)'); }); + testWidgets('Default Theme provides all basic TextStyle properties - M3', (WidgetTester tester) async { + late ThemeData theme; + await tester.pumpWidget(Theme( + data: ThemeData(useMaterial3: true), + child: Directionality( + textDirection: TextDirection.ltr, + child: Builder( + builder: (BuildContext context) { + theme = Theme.of(context); + return const Text('A'); + }, + ), + ), + )); + + List<TextStyle> extractStyles(TextTheme textTheme) { + return <TextStyle>[ + textTheme.displayLarge!, + textTheme.displayMedium!, + textTheme.displaySmall!, + textTheme.headlineLarge!, + textTheme.headlineMedium!, + textTheme.headlineSmall!, + textTheme.titleLarge!, + textTheme.titleMedium!, + textTheme.bodyLarge!, + textTheme.bodyMedium!, + textTheme.bodySmall!, + textTheme.labelLarge!, + textTheme.labelMedium!, + ]; + } + + for (final TextTheme textTheme in <TextTheme>[theme.textTheme, theme.primaryTextTheme]) { + for (final TextStyle style in extractStyles(textTheme).map<TextStyle>((TextStyle style) => _TextStyleProxy(style))) { + expect(style.inherit, false); + expect(style.color, isNotNull); + expect(style.fontFamily, isNotNull); + expect(style.fontSize, isNotNull); + expect(style.fontWeight, isNotNull); + expect(style.fontStyle, null); + expect(style.letterSpacing, isNotNull); + expect(style.wordSpacing, null); + expect(style.textBaseline, isNotNull); + expect(style.height, isNotNull); + expect(style.decoration, TextDecoration.none); + expect(style.decorationColor, isNotNull); + expect(style.decorationStyle, null); + expect(style.debugLabel, isNotNull); + expect(style.locale, null); + expect(style.background, null); + } + } + + expect(theme.textTheme.displayLarge!.debugLabel, '(englishLike displayLarge 2021).merge((blackMountainView displayLarge).apply)'); + }); + group('Cupertino theme', () { late int buildCount; CupertinoThemeData? actualTheme; @@ -496,26 +579,42 @@ void main() { context = null; }); - testWidgets('Default theme has defaults', (WidgetTester tester) async { - final CupertinoThemeData theme = await testTheme(tester, ThemeData.light()); - - expect(theme.brightness, Brightness.light); - expect(theme.primaryColor, Colors.blue); - expect(theme.scaffoldBackgroundColor, Colors.grey[50]); - expect(theme.primaryContrastingColor, Colors.white); - expect(theme.textTheme.textStyle.fontFamily, '.SF Pro Text'); - expect(theme.textTheme.textStyle.fontSize, 17.0); + testWidgets('Default light theme has defaults', (WidgetTester tester) async { + final CupertinoThemeData themeM2 = await testTheme(tester, ThemeData(useMaterial3: false)); + final CupertinoThemeData themeM3 = await testTheme(tester, ThemeData(useMaterial3: true)); + + expect(themeM2.brightness, Brightness.light); + expect(themeM2.primaryColor, Colors.blue); + expect(themeM2.scaffoldBackgroundColor, Colors.grey[50]); + expect(themeM2.primaryContrastingColor, Colors.white); + expect(themeM2.textTheme.textStyle.fontFamily, '.SF Pro Text'); + expect(themeM2.textTheme.textStyle.fontSize, 17.0); + + expect(themeM3.brightness, Brightness.light); + expect(themeM3.primaryColor, const Color(0xff6750a4)); + expect(themeM3.scaffoldBackgroundColor, const Color(0xfffffbfe)); // ColorScheme.background + expect(themeM3.primaryContrastingColor, Colors.white); + expect(themeM3.textTheme.textStyle.fontFamily, '.SF Pro Text'); + expect(themeM3.textTheme.textStyle.fontSize, 17.0); }); testWidgets('Dark theme has defaults', (WidgetTester tester) async { - final CupertinoThemeData theme = await testTheme(tester, ThemeData.dark()); - - expect(theme.brightness, Brightness.dark); - expect(theme.primaryColor, Colors.blue); - expect(theme.primaryContrastingColor, Colors.white); - expect(theme.scaffoldBackgroundColor, Colors.grey[850]); - expect(theme.textTheme.textStyle.fontFamily, '.SF Pro Text'); - expect(theme.textTheme.textStyle.fontSize, 17.0); + final CupertinoThemeData themeM2 = await testTheme(tester, ThemeData.dark(useMaterial3: false)); + final CupertinoThemeData themeM3 = await testTheme(tester, ThemeData.dark(useMaterial3: true)); + + expect(themeM2.brightness, Brightness.dark); + expect(themeM2.primaryColor, Colors.blue); + expect(themeM2.primaryContrastingColor, Colors.white); + expect(themeM2.scaffoldBackgroundColor, Colors.grey[850]); + expect(themeM2.textTheme.textStyle.fontFamily, '.SF Pro Text'); + expect(themeM2.textTheme.textStyle.fontSize, 17.0); + + expect(themeM3.brightness, Brightness.dark); + expect(themeM3.primaryColor, const Color(0xffd0bcff)); + expect(themeM3.primaryContrastingColor, const Color(0xff381e72)); + expect(themeM3.scaffoldBackgroundColor, const Color(0xff1c1b1f)); + expect(themeM3.textTheme.textStyle.fontFamily, '.SF Pro Text'); + expect(themeM3.textTheme.textStyle.fontSize, 17.0); }); testWidgets('MaterialTheme overrides the brightness', (WidgetTester tester) async { @@ -540,49 +639,103 @@ void main() { }); testWidgets('Can override material theme', (WidgetTester tester) async { - final CupertinoThemeData theme = await testTheme(tester, ThemeData( + final CupertinoThemeData themeM2 = await testTheme(tester, ThemeData( cupertinoOverrideTheme: const CupertinoThemeData( scaffoldBackgroundColor: CupertinoColors.lightBackgroundGray, ), + useMaterial3: false, )); - expect(theme.brightness, Brightness.light); + expect(themeM2.brightness, Brightness.light); // We took the scaffold background override but the rest are still cascaded - // to the material theme. - expect(theme.primaryColor, Colors.blue); - expect(theme.primaryContrastingColor, Colors.white); - expect(theme.scaffoldBackgroundColor, CupertinoColors.lightBackgroundGray); - expect(theme.textTheme.textStyle.fontFamily, '.SF Pro Text'); - expect(theme.textTheme.textStyle.fontSize, 17.0); + // to the material themeM2. + expect(themeM2.primaryColor, Colors.blue); + expect(themeM2.primaryContrastingColor, Colors.white); + expect(themeM2.scaffoldBackgroundColor, CupertinoColors.lightBackgroundGray); + expect(themeM2.textTheme.textStyle.fontFamily, '.SF Pro Text'); + expect(themeM2.textTheme.textStyle.fontSize, 17.0); + + final CupertinoThemeData themeM3 = await testTheme(tester, ThemeData( + cupertinoOverrideTheme: const CupertinoThemeData( + scaffoldBackgroundColor: CupertinoColors.lightBackgroundGray, + ), + useMaterial3: true, + )); + + expect(themeM3.brightness, Brightness.light); + // We took the scaffold background override but the rest are still cascaded + // to the material themeM3. + expect(themeM3.primaryColor, const Color(0xff6750a4)); + expect(themeM3.primaryContrastingColor, Colors.white); + expect(themeM3.scaffoldBackgroundColor, CupertinoColors.lightBackgroundGray); + expect(themeM3.textTheme.textStyle.fontFamily, '.SF Pro Text'); + expect(themeM3.textTheme.textStyle.fontSize, 17.0); }); testWidgets('Can override properties that are independent of material', (WidgetTester tester) async { - final CupertinoThemeData theme = await testTheme(tester, ThemeData( + final CupertinoThemeData themeM2 = await testTheme(tester, ThemeData( cupertinoOverrideTheme: const CupertinoThemeData( // The bar colors ignore all things material except brightness. barBackgroundColor: CupertinoColors.black, ), + useMaterial3: false, )); - expect(theme.primaryColor, Colors.blue); + expect(themeM2.primaryColor, Colors.blue); // MaterialBasedCupertinoThemeData should also function like a normal CupertinoThemeData. - expect(theme.barBackgroundColor, CupertinoColors.black); + expect(themeM2.barBackgroundColor, CupertinoColors.black); + + final CupertinoThemeData themeM3 = await testTheme(tester, ThemeData( + cupertinoOverrideTheme: const CupertinoThemeData( + // The bar colors ignore all things material except brightness. + barBackgroundColor: CupertinoColors.black, + ), + useMaterial3: true + )); + + expect(themeM3.primaryColor, const Color(0xff6750a4)); + // MaterialBasedCupertinoThemeData should also function like a normal CupertinoThemeData. + expect(themeM3.barBackgroundColor, CupertinoColors.black); }); - testWidgets('Changing material theme triggers rebuilds', (WidgetTester tester) async { - CupertinoThemeData theme = await testTheme(tester, ThemeData( + testWidgets('Changing material theme triggers rebuilds - M2', (WidgetTester tester) async { + CupertinoThemeData themeM2 = await testTheme(tester, ThemeData( + useMaterial3: false, primarySwatch: Colors.red, )); expect(buildCount, 1); - expect(theme.primaryColor, Colors.red); + expect(themeM2.primaryColor, Colors.red); - theme = await testTheme(tester, ThemeData( + themeM2 = await testTheme(tester, ThemeData( + useMaterial3: false, primarySwatch: Colors.orange, )); expect(buildCount, 2); - expect(theme.primaryColor, Colors.orange); + expect(themeM2.primaryColor, Colors.orange); + }); + + testWidgets('Changing material theme triggers rebuilds - M3', (WidgetTester tester) async { + CupertinoThemeData themeM3 = await testTheme(tester, ThemeData( + useMaterial3: true, + colorScheme: const ColorScheme.light( + primary: Colors.red + ), + )); + + expect(buildCount, 1); + expect(themeM3.primaryColor, Colors.red); + + themeM3 = await testTheme(tester, ThemeData( + useMaterial3: true, + colorScheme: const ColorScheme.light( + primary: Colors.orange + ), + )); + + expect(buildCount, 2); + expect(themeM3.primaryColor, Colors.orange); }); testWidgets( @@ -654,9 +807,10 @@ void main() { ); testWidgets( - 'Cupertino overrides do not block derivatives triggering rebuilds when derivatives are not overridden', + 'Cupertino overrides do not block derivatives triggering rebuilds when derivatives are not overridden - M2', (WidgetTester tester) async { CupertinoThemeData theme = await testTheme(tester, ThemeData( + useMaterial3: false, primarySwatch: Colors.purple, cupertinoOverrideTheme: const CupertinoThemeData( primaryContrastingColor: CupertinoColors.destructiveRed, @@ -668,6 +822,7 @@ void main() { expect(theme.primaryContrastingColor, CupertinoColors.destructiveRed); theme = await testTheme(tester, ThemeData( + useMaterial3: false, primarySwatch: Colors.green, cupertinoOverrideTheme: const CupertinoThemeData( primaryContrastingColor: CupertinoColors.destructiveRed, @@ -681,9 +836,43 @@ void main() { ); testWidgets( - 'copyWith only copies the overrides, not the material or cupertino derivatives', + 'Cupertino overrides do not block derivatives triggering rebuilds when derivatives are not overridden - M3', + (WidgetTester tester) async { + CupertinoThemeData theme = await testTheme(tester, ThemeData( + useMaterial3: true, + colorScheme: const ColorScheme.light( + primary: Colors.purple, + ), + cupertinoOverrideTheme: const CupertinoThemeData( + primaryContrastingColor: CupertinoColors.destructiveRed, + ), + )); + + expect(buildCount, 1); + expect(theme.textTheme.actionTextStyle.color, Colors.purple); + expect(theme.primaryContrastingColor, CupertinoColors.destructiveRed); + + theme = await testTheme(tester, ThemeData( + useMaterial3: true, + colorScheme: const ColorScheme.light( + primary: Colors.green, + ), + cupertinoOverrideTheme: const CupertinoThemeData( + primaryContrastingColor: CupertinoColors.destructiveRed, + ), + )); + + expect(buildCount, 2); + expect(theme.textTheme.actionTextStyle.color, Colors.green); + expect(theme.primaryContrastingColor, CupertinoColors.destructiveRed); + }, + ); + + testWidgets( + 'copyWith only copies the overrides, not the material or cupertino derivatives - M2', (WidgetTester tester) async { final CupertinoThemeData originalTheme = await testTheme(tester, ThemeData( + useMaterial3: false, primarySwatch: Colors.purple, cupertinoOverrideTheme: const CupertinoThemeData( primaryContrastingColor: CupertinoColors.activeOrange, @@ -695,6 +884,7 @@ void main() { ); final CupertinoThemeData theme = await testTheme(tester, ThemeData( + useMaterial3: false, primarySwatch: Colors.blue, cupertinoOverrideTheme: copiedTheme, )); @@ -706,9 +896,37 @@ void main() { ); testWidgets( - "Material themes with no cupertino overrides can also be copyWith'ed", + 'copyWith only copies the overrides, not the material or cupertino derivatives - M3', + (WidgetTester tester) async { + final CupertinoThemeData originalTheme = await testTheme(tester, ThemeData( + useMaterial3: true, + colorScheme: const ColorScheme.light(primary: Colors.purple), + cupertinoOverrideTheme: const CupertinoThemeData( + primaryContrastingColor: CupertinoColors.activeOrange, + ), + )); + + final CupertinoThemeData copiedTheme = originalTheme.copyWith( + barBackgroundColor: CupertinoColors.destructiveRed, + ); + + final CupertinoThemeData theme = await testTheme(tester, ThemeData( + useMaterial3: true, + colorScheme: const ColorScheme.light(primary: Colors.blue), + cupertinoOverrideTheme: copiedTheme, + )); + + expect(theme.primaryColor, Colors.blue); + expect(theme.primaryContrastingColor, CupertinoColors.activeOrange); + expect(theme.barBackgroundColor, CupertinoColors.destructiveRed); + }, + ); + + testWidgets( + "Material themes with no cupertino overrides can also be copyWith'ed - M2", (WidgetTester tester) async { final CupertinoThemeData originalTheme = await testTheme(tester, ThemeData( + useMaterial3: false, primarySwatch: Colors.purple, )); @@ -717,6 +935,7 @@ void main() { ); final CupertinoThemeData theme = await testTheme(tester, ThemeData( + useMaterial3: false, primarySwatch: Colors.blue, cupertinoOverrideTheme: copiedTheme, )); @@ -725,6 +944,29 @@ void main() { expect(theme.primaryContrastingColor, CupertinoColors.destructiveRed); }, ); + + testWidgets( + "Material themes with no cupertino overrides can also be copyWith'ed - M3", + (WidgetTester tester) async { + final CupertinoThemeData originalTheme = await testTheme(tester, ThemeData( + useMaterial3: true, + colorScheme: const ColorScheme.light(primary: Colors.purple), + )); + + final CupertinoThemeData copiedTheme = originalTheme.copyWith( + primaryContrastingColor: CupertinoColors.destructiveRed, + ); + + final CupertinoThemeData theme = await testTheme(tester, ThemeData( + useMaterial3: true, + colorScheme: const ColorScheme.light(primary: Colors.blue), + cupertinoOverrideTheme: copiedTheme, + )); + + expect(theme.primaryColor, Colors.blue); + expect(theme.primaryContrastingColor, CupertinoColors.destructiveRed); + }, + ); }); } diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart index 4ecec0f2347b7..1c1360c9cf71d 100644 --- a/packages/flutter/test/material/time_picker_test.dart +++ b/packages/flutter/test/material/time_picker_test.dart @@ -5,6 +5,7 @@ @TestOn('!chrome') library; +import 'dart:math' as math; import 'dart:ui'; import 'package:flutter/material.dart'; @@ -710,7 +711,7 @@ void main() { testWidgets('OK Cancel button and helpText layout', (WidgetTester tester) async { Widget buildFrame(TextDirection textDirection) { return MaterialApp( - theme: ThemeData.light().copyWith(useMaterial3: materialType == MaterialType.material3), + theme: ThemeData(useMaterial3: materialType == MaterialType.material3), home: Material( child: Center( child: Builder( @@ -744,14 +745,16 @@ void main() { switch (materialType) { case MaterialType.material2: expect(tester.getTopLeft(find.text(selectTimeString)), equals(const Offset(154, 155))); - expect(tester.getBottomRight(find.text(selectTimeString)), equals(const Offset(281, 165))); + expect(tester.getBottomRight(find.text(selectTimeString)), equals( + const bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? const Offset(280.5, 165) : const Offset(281, 165), + )); expect(tester.getBottomRight(find.text(okString)).dx, 644); expect(tester.getBottomLeft(find.text(okString)).dx, 616); expect(tester.getBottomRight(find.text(cancelString)).dx, 582); case MaterialType.material3: expect(tester.getTopLeft(find.text(selectTimeString)), equals(const Offset(138, 129))); - expect(tester.getBottomRight(find.text(selectTimeString)), equals(const Offset(292.0, 143.0))); - expect(tester.getBottomLeft(find.text(okString)).dx, 616); + expect(tester.getBottomRight(find.text(selectTimeString)), equals(const Offset(295.0, 149.0))); + expect(tester.getBottomLeft(find.text(okString)).dx, 615.5); expect(tester.getBottomRight(find.text(cancelString)).dx, 578); } @@ -764,16 +767,18 @@ void main() { switch (materialType) { case MaterialType.material2: - expect(tester.getTopLeft(find.text(selectTimeString)), equals(const Offset(519, 155))); + expect(tester.getTopLeft(find.text(selectTimeString)), equals( + const bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK') ? const Offset(519.5, 155) : const Offset(519, 155), + )); expect(tester.getBottomRight(find.text(selectTimeString)), equals(const Offset(646, 165))); expect(tester.getBottomLeft(find.text(okString)).dx, 156); expect(tester.getBottomRight(find.text(okString)).dx, 184); expect(tester.getBottomLeft(find.text(cancelString)).dx, 218); case MaterialType.material3: - expect(tester.getTopLeft(find.text(selectTimeString)), equals(const Offset(508, 129))); - expect(tester.getBottomRight(find.text(selectTimeString)), equals(const Offset(662, 143))); - expect(tester.getBottomLeft(find.text(okString)).dx, 156); - expect(tester.getBottomRight(find.text(okString)).dx, 184); + expect(tester.getTopLeft(find.text(selectTimeString)), equals(const Offset(505.0, 129.0))); + expect(tester.getBottomRight(find.text(selectTimeString)), equals(const Offset(662, 149))); + expect(tester.getBottomLeft(find.text(okString)).dx, 155.5); + expect(tester.getBottomRight(find.text(okString)).dx, 184.5); expect(tester.getBottomLeft(find.text(cancelString)).dx, 222); } @@ -804,7 +809,7 @@ void main() { final double amHeight2x = tester.getSize(find.text(amString)).height; expect(tester.getSize(find.text('41')).height, equals(minutesDisplayHeight)); - expect(amHeight2x, greaterThanOrEqualTo(amHeight * 2)); + expect(amHeight2x, math.min(38.0, amHeight * 2)); await tester.tap(find.text(okString)); // dismiss the dialog await tester.pumpAndSettle(); @@ -818,7 +823,7 @@ void main() { ); expect(tester.getSize(find.text('41')).height, equals(minutesDisplayHeight)); - expect(tester.getSize(find.text(amString)).height, equals(amHeight2x)); + expect(tester.getSize(find.text(amString)).height, math.min(38.0, amHeight * 2)); }); group('showTimePicker avoids overlapping display features', () { @@ -1690,55 +1695,53 @@ Future<void> mediaQueryBoilerplate( Orientation? orientation, }) async { await tester.pumpWidget( - Builder(builder: (BuildContext context) { - return Theme( - data: Theme.of(context).copyWith(useMaterial3: materialType == MaterialType.material3), - child: Localizations( - locale: const Locale('en', 'US'), - delegates: const <LocalizationsDelegate<dynamic>>[ - DefaultMaterialLocalizations.delegate, - DefaultWidgetsLocalizations.delegate, - ], - child: MediaQuery( - data: MediaQueryData( - alwaysUse24HourFormat: alwaysUse24HourFormat, - textScaleFactor: textScaleFactor, - accessibleNavigation: accessibleNavigation, - size: tester.view.physicalSize / tester.view.devicePixelRatio, - ), - child: Material( - child: Center( - child: Directionality( - textDirection: TextDirection.ltr, - child: Navigator( - onGenerateRoute: (RouteSettings settings) { - return MaterialPageRoute<void>(builder: (BuildContext context) { - return TextButton( - onPressed: () { - showTimePicker( - context: context, - initialTime: initialTime, - initialEntryMode: entryMode, - helpText: helpText, - hourLabelText: hourLabelText, - minuteLabelText: minuteLabelText, - errorInvalidText: errorInvalidText, - onEntryModeChanged: onEntryModeChange, - orientation: orientation, - ); - }, - child: const Text('X'), - ); - }); - }, - ), + Theme( + data: ThemeData(useMaterial3: materialType == MaterialType.material3), + child: Localizations( + locale: const Locale('en', 'US'), + delegates: const <LocalizationsDelegate<dynamic>>[ + DefaultMaterialLocalizations.delegate, + DefaultWidgetsLocalizations.delegate, + ], + child: MediaQuery( + data: MediaQueryData( + alwaysUse24HourFormat: alwaysUse24HourFormat, + textScaleFactor: textScaleFactor, + accessibleNavigation: accessibleNavigation, + size: tester.view.physicalSize / tester.view.devicePixelRatio, + ), + child: Material( + child: Center( + child: Directionality( + textDirection: TextDirection.ltr, + child: Navigator( + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute<void>(builder: (BuildContext context) { + return TextButton( + onPressed: () { + showTimePicker( + context: context, + initialTime: initialTime, + initialEntryMode: entryMode, + helpText: helpText, + hourLabelText: hourLabelText, + minuteLabelText: minuteLabelText, + errorInvalidText: errorInvalidText, + onEntryModeChanged: onEntryModeChange, + orientation: orientation, + ); + }, + child: const Text('X'), + ); + }); + }, ), ), ), ), ), - ); - }), + ), + ), ); if (tapButton) { await tester.tap(find.text('X')); diff --git a/packages/flutter/test/material/time_picker_theme_test.dart b/packages/flutter/test/material/time_picker_theme_test.dart index f12556cc1227f..dd2e5576908be 100644 --- a/packages/flutter/test/material/time_picker_theme_test.dart +++ b/packages/flutter/test/material/time_picker_theme_test.dart @@ -99,60 +99,88 @@ void main() { }); testWidgets('Passing no TimePickerThemeData uses defaults', (WidgetTester tester) async { - final ThemeData defaultTheme = ThemeData.fallback(); - await tester.pumpWidget(const _TimePickerLauncher()); + final ThemeData defaultTheme = ThemeData(); + final bool material3 = defaultTheme.useMaterial3; + await tester.pumpWidget(_TimePickerLauncher(themeData: defaultTheme)); await tester.tap(find.text('X')); await tester.pumpAndSettle(const Duration(seconds: 1)); final Material dialogMaterial = _dialogMaterial(tester); expect(dialogMaterial.color, defaultTheme.colorScheme.surface); - expect(dialogMaterial.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))); + expect(dialogMaterial.shape, material3 + ? const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(28.0))) + : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))) + ); final RenderBox dial = tester.firstRenderObject<RenderBox>(find.byType(CustomPaint)); expect( dial, - paints - ..circle(color: defaultTheme.colorScheme.onSurface.withOpacity(0.08)) // Dial background color. - ..circle(color: Color(defaultTheme.colorScheme.primary.value)), // Dial hand color. + material3 + ? (paints + ..circle(color: defaultTheme.colorScheme.surfaceVariant.withOpacity(0.08)) // Dial background color. + ..circle(color: Color(defaultTheme.colorScheme.primary.value))) + : (paints + ..circle(color: defaultTheme.colorScheme.onSurface.withOpacity(0.08)) // Dial background color. + ..circle(color: Color(defaultTheme.colorScheme.primary.value))), // Dial hand color. ); final RenderParagraph hourText = _textRenderParagraph(tester, '7'); expect( hourText.text.style, - Typography.material2014().englishLike.displayMedium! + material3 + ? (Typography.material2021().englishLike.displayLarge! + .merge(Typography.material2021().black.displayLarge) + .copyWith(color: defaultTheme.colorScheme.onPrimaryContainer, decorationColor: defaultTheme.colorScheme.onSurface)) + : (Typography.material2014().englishLike.displayMedium! .merge(Typography.material2014().black.displayMedium) - .copyWith(color: defaultTheme.colorScheme.primary), + .copyWith(color: defaultTheme.colorScheme.primary)), ); final RenderParagraph minuteText = _textRenderParagraph(tester, '15'); expect( minuteText.text.style, - Typography.material2014().englishLike.displayMedium! + material3 + ? (Typography.material2021().englishLike.displayLarge! + .merge(Typography.material2021().black.displayLarge) + .copyWith(color: defaultTheme.colorScheme.onSurface, decorationColor: defaultTheme.colorScheme.onSurface)) + : (Typography.material2014().englishLike.displayMedium! .merge(Typography.material2014().black.displayMedium) - .copyWith(color: defaultTheme.colorScheme.onSurface), + .copyWith(color: defaultTheme.colorScheme.onSurface)), ); final RenderParagraph amText = _textRenderParagraph(tester, 'AM'); expect( amText.text.style, - Typography.material2014().englishLike.titleMedium! + material3 + ? (Typography.material2021().englishLike.titleMedium! + .merge(Typography.material2021().black.titleMedium) + .copyWith(color: defaultTheme.colorScheme.onTertiaryContainer, decorationColor: defaultTheme.colorScheme.onSurface)) + : (Typography.material2014().englishLike.titleMedium! .merge(Typography.material2014().black.titleMedium) - .copyWith(color: defaultTheme.colorScheme.primary), + .copyWith(color: defaultTheme.colorScheme.primary)), ); final RenderParagraph pmText = _textRenderParagraph(tester, 'PM'); expect( pmText.text.style, - Typography.material2014().englishLike.titleMedium! + material3 + ? (Typography.material2021().englishLike.titleMedium! + .merge(Typography.material2021().black.titleMedium) + .copyWith(color: defaultTheme.colorScheme.onTertiaryContainer, decorationColor: defaultTheme.colorScheme.onSurface)) + : (Typography.material2014().englishLike.titleMedium! .merge(Typography.material2014().black.titleMedium) - .copyWith(color: defaultTheme.colorScheme.onSurface.withOpacity(0.6)), + .copyWith(color: defaultTheme.colorScheme.onSurface.withOpacity(0.6))), ); - final RenderParagraph helperText = _textRenderParagraph(tester, 'SELECT TIME'); + final RenderParagraph helperText = _textRenderParagraph(tester, material3 ? 'Select time' : 'SELECT TIME'); expect( helperText.text.style, - Typography.material2014().englishLike.labelSmall! - .merge(Typography.material2014().black.labelSmall), + material3 + ? (Typography.material2021().englishLike.bodyMedium! + .merge(Typography.material2021().black.bodyMedium) + .copyWith(color: defaultTheme.colorScheme.onSurface, decorationColor: defaultTheme.colorScheme.onSurface)) + : (Typography.material2014().englishLike.labelSmall! + .merge(Typography.material2014().black.labelSmall)), ); final CustomPaint dialPaint = tester.widget(findDialPaint); @@ -162,30 +190,44 @@ void main() { expect( // ignore: avoid_dynamic_calls primaryLabels.first.painter.text.style, - Typography.material2014().englishLike.bodyLarge! - .merge(Typography.material2014().black.bodyLarge) - .copyWith(color: defaultTheme.colorScheme.onSurface), + material3 + ? (Typography.material2021().englishLike.bodyLarge! + .merge(Typography.material2021().black.bodyLarge) + .copyWith(color: defaultTheme.colorScheme.onSurface, decorationColor: defaultTheme.colorScheme.onSurface)) + : (Typography.material2014().englishLike.bodyLarge! + .merge(Typography.material2014().black.bodyLarge) + .copyWith(color: defaultTheme.colorScheme.onSurface)), ); // ignore: avoid_dynamic_calls final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>; expect( // ignore: avoid_dynamic_calls selectedLabels.first.painter.text.style, - Typography.material2014().englishLike.bodyLarge! - .merge(Typography.material2014().white.bodyLarge) - .copyWith(color: defaultTheme.colorScheme.onPrimary), + material3 + ? (Typography.material2021().englishLike.bodyLarge! + .merge(Typography.material2021().black.bodyLarge) + .copyWith(color: defaultTheme.colorScheme.onPrimary, decorationColor: defaultTheme.colorScheme.onSurface)) + : (Typography.material2014().englishLike.bodyLarge! + .merge(Typography.material2014().white.bodyLarge) + .copyWith(color: defaultTheme.colorScheme.onPrimary)), ); final Material hourMaterial = _textMaterial(tester, '7'); - expect(hourMaterial.color, defaultTheme.colorScheme.primary.withOpacity(0.12)); - expect(hourMaterial.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))); + expect(hourMaterial.color, material3 ? defaultTheme.colorScheme.primaryContainer : defaultTheme.colorScheme.primary.withOpacity(0.12)); + expect(hourMaterial.shape, material3 + ? const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))) + : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))) + ); final Material minuteMaterial = _textMaterial(tester, '15'); - expect(minuteMaterial.color, defaultTheme.colorScheme.onSurface.withOpacity(0.12)); - expect(minuteMaterial.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))); + expect(minuteMaterial.color, material3 ? defaultTheme.colorScheme.surfaceVariant : defaultTheme.colorScheme.onSurface.withOpacity(0.12)); + expect(minuteMaterial.shape, material3 + ? const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))) + : const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))) + ); final Material amMaterial = _textMaterial(tester, 'AM'); - expect(amMaterial.color, defaultTheme.colorScheme.primary.withOpacity(0.12)); + expect(amMaterial.color, material3 ? defaultTheme.colorScheme.tertiaryContainer : defaultTheme.colorScheme.primary.withOpacity(0.12)); final Material pmMaterial = _textMaterial(tester, 'PM'); expect(pmMaterial.color, Colors.transparent); @@ -197,49 +239,61 @@ void main() { final Material dayPeriodMaterial = _dayPeriodMaterial(tester); expect( dayPeriodMaterial.shape, - RoundedRectangleBorder( - borderRadius: const BorderRadius.all(Radius.circular(4.0)), - side: BorderSide(color: expectedBorderColor), - ), + material3 + ? RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + side: BorderSide(color: defaultTheme.colorScheme.outline), + ) : RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(4.0)), + side: BorderSide(color: expectedBorderColor), + ), ); final Container dayPeriodDivider = _dayPeriodDivider(tester); expect( dayPeriodDivider.decoration, - BoxDecoration(border: Border(left: BorderSide(color: expectedBorderColor))), + material3 + ? BoxDecoration(border: Border(left: BorderSide(color: defaultTheme.colorScheme.outline))) + : BoxDecoration(border: Border(left: BorderSide(color: expectedBorderColor))), ); final IconButton entryModeIconButton = _entryModeIconButton(tester); expect( entryModeIconButton.color, - defaultTheme.colorScheme.onSurface.withOpacity(0.6), + material3 ? null : defaultTheme.colorScheme.onSurface.withOpacity(0.6), ); }); testWidgets('Passing no TimePickerThemeData uses defaults - input mode', (WidgetTester tester) async { - final ThemeData defaultTheme = ThemeData.fallback(); - await tester.pumpWidget(const _TimePickerLauncher(entryMode: TimePickerEntryMode.input)); + final ThemeData defaultTheme = ThemeData(); + final bool material3 = defaultTheme.useMaterial3; + await tester.pumpWidget(_TimePickerLauncher(themeData: defaultTheme, entryMode: TimePickerEntryMode.input)); await tester.tap(find.text('X')); await tester.pumpAndSettle(const Duration(seconds: 1)); final InputDecoration hourDecoration = _textField(tester, '7').decoration!; expect(hourDecoration.filled, true); - expect(hourDecoration.fillColor, MaterialStateColor.resolveWith((Set<MaterialState> states) => defaultTheme.colorScheme.onSurface.withOpacity(0.12))); - expect(hourDecoration.enabledBorder, const OutlineInputBorder(borderSide: BorderSide(color: Colors.transparent))); - expect(hourDecoration.errorBorder, OutlineInputBorder(borderSide: BorderSide(color: defaultTheme.colorScheme.error, width: 2))); - expect(hourDecoration.focusedBorder, OutlineInputBorder(borderSide: BorderSide(color: defaultTheme.colorScheme.primary, width: 2))); - expect(hourDecoration.focusedErrorBorder, OutlineInputBorder(borderSide: BorderSide(color: defaultTheme.colorScheme.error, width: 2))); + expect(hourDecoration.fillColor, material3 + ? defaultTheme.colorScheme.surfaceVariant + : MaterialStateColor.resolveWith((Set<MaterialState> states) => defaultTheme.colorScheme.onSurface.withOpacity(0.12))); + expect(hourDecoration.enabledBorder, material3 ? const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(8.0)), borderSide: BorderSide(color: Colors.transparent)) : const OutlineInputBorder(borderSide: BorderSide(color: Colors.transparent))); + expect(hourDecoration.errorBorder, material3 ? OutlineInputBorder(borderRadius: const BorderRadius.all(Radius.circular(8.0)), borderSide: BorderSide(color: defaultTheme.colorScheme.error, width: 2.0)) : OutlineInputBorder(borderSide: BorderSide(color: defaultTheme.colorScheme.error, width: 2))); + expect(hourDecoration.focusedBorder, material3 ? OutlineInputBorder(borderRadius: const BorderRadius.all(Radius.circular(8.0)), borderSide: BorderSide(color: defaultTheme.colorScheme.primary, width: 2.0)) : OutlineInputBorder(borderSide: BorderSide(color: defaultTheme.colorScheme.primary, width: 2))); + expect(hourDecoration.focusedErrorBorder, material3 ? OutlineInputBorder(borderRadius: const BorderRadius.all(Radius.circular(8.0)), borderSide: BorderSide(color: defaultTheme.colorScheme.error, width: 2.0)) : OutlineInputBorder(borderSide: BorderSide(color: defaultTheme.colorScheme.error, width: 2))); expect( hourDecoration.hintStyle, - Typography.material2014().englishLike.displayMedium! - .merge(defaultTheme.textTheme.displayMedium!.copyWith(color: defaultTheme.colorScheme.onSurface.withOpacity(0.36))), + material3 + ? TextStyle(color: defaultTheme.colorScheme.onSurface.withOpacity(0.36)) + : (Typography.material2014().englishLike.displayMedium! + .merge(defaultTheme.textTheme.displayMedium!.copyWith(color: defaultTheme.colorScheme.onSurface.withOpacity(0.36)))), ); }); testWidgets('Time picker uses values from TimePickerThemeData', (WidgetTester tester) async { final TimePickerThemeData timePickerTheme = _timePickerTheme(); final ThemeData theme = ThemeData(timePickerTheme: timePickerTheme); + final bool material3 = theme.useMaterial3; await tester.pumpWidget(_TimePickerLauncher(themeData: theme)); await tester.tap(find.text('X')); await tester.pumpAndSettle(const Duration(seconds: 1)); @@ -259,45 +313,69 @@ void main() { final RenderParagraph hourText = _textRenderParagraph(tester, '7'); expect( hourText.text.style, - Typography.material2014().englishLike.bodyMedium! + material3 + ? (Typography.material2021().englishLike.bodyMedium! + .merge(Typography.material2021().black.bodyMedium) + .merge(timePickerTheme.hourMinuteTextStyle) + .copyWith(color: _selectedColor, decorationColor: const Color(0xff1c1b1f))) + : (Typography.material2014().englishLike.bodyMedium! .merge(Typography.material2014().black.bodyMedium) .merge(timePickerTheme.hourMinuteTextStyle) - .copyWith(color: _selectedColor), + .copyWith(color: _selectedColor)), ); final RenderParagraph minuteText = _textRenderParagraph(tester, '15'); expect( minuteText.text.style, - Typography.material2014().englishLike.bodyMedium! + material3 + ? (Typography.material2021().englishLike.bodyMedium! + .merge(Typography.material2021().black.bodyMedium) + .merge(timePickerTheme.hourMinuteTextStyle) + .copyWith(color: _unselectedColor, decorationColor: const Color(0xff1c1b1f))) + : (Typography.material2014().englishLike.bodyMedium! .merge(Typography.material2014().black.bodyMedium) .merge(timePickerTheme.hourMinuteTextStyle) - .copyWith(color: _unselectedColor), + .copyWith(color: _unselectedColor)), ); final RenderParagraph amText = _textRenderParagraph(tester, 'AM'); expect( amText.text.style, - Typography.material2014().englishLike.titleMedium! + material3 + ? (Typography.material2021().englishLike.bodyMedium! + .merge(Typography.material2021().black.bodyMedium) + .merge(timePickerTheme.hourMinuteTextStyle) + .copyWith(color: _selectedColor, decorationColor: const Color(0xff1c1b1f))) + : (Typography.material2014().englishLike.titleMedium! .merge(Typography.material2014().black.titleMedium) .merge(timePickerTheme.dayPeriodTextStyle) - .copyWith(color: _selectedColor), + .copyWith(color: _selectedColor)), ); final RenderParagraph pmText = _textRenderParagraph(tester, 'PM'); expect( pmText.text.style, - Typography.material2014().englishLike.titleMedium! + material3 + ? (Typography.material2021().englishLike.bodyMedium! + .merge(Typography.material2021().black.bodyMedium) + .merge(timePickerTheme.hourMinuteTextStyle) + .copyWith(color: _unselectedColor, decorationColor: const Color(0xff1c1b1f))) + : (Typography.material2014().englishLike.titleMedium! .merge(Typography.material2014().black.titleMedium) .merge(timePickerTheme.dayPeriodTextStyle) - .copyWith(color: _unselectedColor), + .copyWith(color: _unselectedColor)), ); - final RenderParagraph helperText = _textRenderParagraph(tester, 'SELECT TIME'); + final RenderParagraph helperText = _textRenderParagraph(tester, material3 ? 'Select time' : 'SELECT TIME'); expect( helperText.text.style, - Typography.material2014().englishLike.bodyMedium! + material3 + ? (Typography.material2021().englishLike.bodyMedium! + .merge(Typography.material2021().black.bodyMedium) + .merge(timePickerTheme.helpTextStyle).copyWith(color: theme.colorScheme.onSurface, decorationColor: theme.colorScheme.onSurface)) + : (Typography.material2014().englishLike.bodyMedium! .merge(Typography.material2014().black.bodyMedium) - .merge(timePickerTheme.helpTextStyle), + .merge(timePickerTheme.helpTextStyle)), ); final CustomPaint dialPaint = tester.widget(findDialPaint); @@ -307,18 +385,26 @@ void main() { expect( // ignore: avoid_dynamic_calls primaryLabels.first.painter.text.style, - Typography.material2014().englishLike.bodyLarge! + material3 + ? (Typography.material2021().englishLike.bodyLarge! + .merge(Typography.material2021().black.bodyLarge) + .copyWith(color: _unselectedColor, decorationColor: theme.colorScheme.onSurface)) + : (Typography.material2014().englishLike.bodyLarge! .merge(Typography.material2014().black.bodyLarge) - .copyWith(color: _unselectedColor), + .copyWith(color: _unselectedColor)), ); // ignore: avoid_dynamic_calls final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>; expect( // ignore: avoid_dynamic_calls selectedLabels.first.painter.text.style, - Typography.material2014().englishLike.bodyLarge! + material3 + ? (Typography.material2021().englishLike.bodyLarge! + .merge(Typography.material2021().black.bodyLarge) + .copyWith(color: _selectedColor, decorationColor: theme.colorScheme.onSurface)) + : (Typography.material2014().englishLike.bodyLarge! .merge(Typography.material2014().white.bodyLarge) - .copyWith(color: _selectedColor), + .copyWith(color: _selectedColor)), ); final Material hourMaterial = _textMaterial(tester, '7'); @@ -350,7 +436,7 @@ void main() { final IconButton entryModeIconButton = _entryModeIconButton(tester); expect( entryModeIconButton.color, - timePickerTheme.entryModeIconColor, + material3 ? null : timePickerTheme.entryModeIconColor, ); }); @@ -379,7 +465,7 @@ void main() { await tester.pumpAndSettle(const Duration(seconds: 1)); final InputDecoration hourDecoration = _textField(tester, '7').decoration!; - expect(hourDecoration.fillColor, timePickerTheme.hourMinuteColor); + expect(hourDecoration.fillColor?.value, timePickerTheme.hourMinuteColor?.value); }); } diff --git a/packages/flutter/test/material/toggle_buttons_test.dart b/packages/flutter/test/material/toggle_buttons_test.dart index 40db27a1f213d..ebf81c391f15c 100644 --- a/packages/flutter/test/material/toggle_buttons_test.dart +++ b/packages/flutter/test/material/toggle_buttons_test.dart @@ -17,10 +17,22 @@ import '../widgets/semantics_tester.dart'; const double _defaultBorderWidth = 1.0; -Widget boilerplate({required Widget child}) { - return Directionality( - textDirection: TextDirection.ltr, - child: Center(child: child), +Widget boilerplate({ + bool? useMaterial3, + MaterialTapTargetSize? tapTargetSize, + required Widget child, +}) { + return Theme( + data: ThemeData( + useMaterial3: useMaterial3, + materialTapTargetSize: tapTargetSize, + ), + child: Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: Material(child: child), + ), + ), ); } @@ -34,16 +46,14 @@ void main() { } final ThemeData theme = ThemeData(); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - onPressed: (int index) {}, - isSelected: const <bool>[false, true], - children: const <Widget>[ - Text('First child'), - Text('Second child'), - ], - ), + boilerplate( + child: ToggleButtons( + onPressed: (int index) {}, + isSelected: const <bool>[false, true], + children: const <Widget>[ + Text('First child'), + Text('Second child'), + ], ), ), ); @@ -70,25 +80,23 @@ void main() { final List<bool> isSelected = <bool>[false, true]; final ThemeData theme = ThemeData(); await tester.pumpWidget( - Material( - child: StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return boilerplate( - child: ToggleButtons( - onPressed: (int index) { - setState(() { - isSelected[index] = !isSelected[index]; - }); - }, - isSelected: isSelected, - children: const <Widget>[ - Text('First child'), - Text('Second child'), - ], - ), - ); - }, - ), + StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return boilerplate( + child: ToggleButtons( + onPressed: (int index) { + setState(() { + isSelected[index] = !isSelected[index]; + }); + }, + isSelected: isSelected, + children: const <Widget>[ + Text('First child'), + Text('Second child'), + ], + ), + ); + }, ), ); @@ -132,15 +140,13 @@ void main() { final ThemeData theme = ThemeData(); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: isSelected, - children: const <Widget>[ - Text('First child'), - Text('Second child'), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: isSelected, + children: const <Widget>[ + Text('First child'), + Text('Second child'), + ], ), ), ); @@ -178,15 +184,13 @@ void main() { (WidgetTester tester) async { await expectLater( () => tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false], - children: const <Widget>[ - Text('First child'), - Text('Second child'), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false], + children: const <Widget>[ + Text('First child'), + Text('Second child'), + ], ), ), ), @@ -205,16 +209,14 @@ void main() { testWidgets('Default text style is applied', (WidgetTester tester) async { final ThemeData theme = ThemeData(); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false, true], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First child'), - Text('Second child'), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false, true], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First child'), + Text('Second child'), + ], ), ), ); @@ -237,21 +239,19 @@ void main() { testWidgets('Custom text style except color is applied', (WidgetTester tester) async { await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false, true], - onPressed: (int index) {}, - textStyle: const TextStyle( - textBaseline: TextBaseline.ideographic, - fontSize: 20.0, - color: Colors.orange, - ), - children: const <Widget>[ - Text('First child'), - Text('Second child'), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false, true], + onPressed: (int index) {}, + textStyle: const TextStyle( + textBaseline: TextBaseline.ideographic, + fontSize: 20.0, + color: Colors.orange, + ), + children: const <Widget>[ + Text('First child'), + Text('Second child'), + ], ), ), ); @@ -276,17 +276,15 @@ void main() { testWidgets('Default BoxConstraints', (WidgetTester tester) async { await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false, false, false], - onPressed: (int index) {}, - children: const <Widget>[ - Icon(Icons.check), - Icon(Icons.access_alarm), - Icon(Icons.cake), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false, false, false], + onPressed: (int index) {}, + children: const <Widget>[ + Icon(Icons.check), + Icon(Icons.access_alarm), + Icon(Icons.cake), + ], ), ), ); @@ -305,21 +303,19 @@ void main() { testWidgets('Custom BoxConstraints', (WidgetTester tester) async { // Test for minimum constraints await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - constraints: const BoxConstraints( - minWidth: 50.0, - minHeight: 60.0, - ), - isSelected: const <bool>[false, false, false], - onPressed: (int index) {}, - children: const <Widget>[ - Icon(Icons.check), - Icon(Icons.access_alarm), - Icon(Icons.cake), - ], - ), + boilerplate( + child: ToggleButtons( + constraints: const BoxConstraints( + minWidth: 50.0, + minHeight: 60.0, + ), + isSelected: const <bool>[false, false, false], + onPressed: (int index) {}, + children: const <Widget>[ + Icon(Icons.check), + Icon(Icons.access_alarm), + Icon(Icons.cake), + ], ), ), ); @@ -336,22 +332,20 @@ void main() { // Test for maximum constraints await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - constraints: const BoxConstraints( - maxWidth: 20.0, - maxHeight: 10.0, - ), - isSelected: const <bool>[false, false, false], - onPressed: (int index) {}, - children: const <Widget>[ - Icon(Icons.check), - Icon(Icons.access_alarm), - Icon(Icons.cake), - ], - ), + boilerplate( + child: ToggleButtons( + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + constraints: const BoxConstraints( + maxWidth: 20.0, + maxHeight: 10.0, + ), + isSelected: const <bool>[false, false, false], + onPressed: (int index) {}, + children: const <Widget>[ + Icon(Icons.check), + Icon(Icons.access_alarm), + Icon(Icons.cake), + ], ), ), ); @@ -384,18 +378,16 @@ void main() { } final ThemeData theme = ThemeData(); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false], - onPressed: (int index) {}, - children: const <Widget>[ - Row(children: <Widget>[ - Text('First child'), - Icon(Icons.check), - ]), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false], + onPressed: (int index) {}, + children: const <Widget>[ + Row(children: <Widget>[ + Text('First child'), + Icon(Icons.check), + ]), + ], ), ), ); @@ -411,18 +403,16 @@ void main() { ); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[true], - onPressed: (int index) {}, - children: const <Widget>[ - Row(children: <Widget>[ - Text('First child'), - Icon(Icons.check), - ]), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[true], + onPressed: (int index) {}, + children: const <Widget>[ + Row(children: <Widget>[ + Text('First child'), + Icon(Icons.check), + ]), + ], ), ), ); @@ -438,17 +428,15 @@ void main() { ); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[true], - children: const <Widget>[ - Row(children: <Widget>[ - Text('First child'), - Icon(Icons.check), - ]), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[true], + children: const <Widget>[ + Row(children: <Widget>[ + Text('First child'), + Icon(Icons.check), + ]), + ], ), ), ); @@ -491,19 +479,17 @@ void main() { expect(theme.colorScheme.onSurface.withOpacity(0.38), isNot(disabledColor)); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - color: enabledColor, - isSelected: const <bool>[false], - onPressed: (int index) {}, - children: const <Widget>[ - Row(children: <Widget>[ - Text('First child'), - Icon(Icons.check), - ]), - ], - ), + boilerplate( + child: ToggleButtons( + color: enabledColor, + isSelected: const <bool>[false], + onPressed: (int index) {}, + children: const <Widget>[ + Row(children: <Widget>[ + Text('First child'), + Icon(Icons.check), + ]), + ], ), ), ); @@ -513,19 +499,17 @@ void main() { expect(iconTheme(Icons.check).data.color, enabledColor); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - selectedColor: selectedColor, - isSelected: const <bool>[true], - onPressed: (int index) {}, - children: const <Widget>[ - Row(children: <Widget>[ - Text('First child'), - Icon(Icons.check), - ]), - ], - ), + boilerplate( + child: ToggleButtons( + selectedColor: selectedColor, + isSelected: const <bool>[true], + onPressed: (int index) {}, + children: const <Widget>[ + Row(children: <Widget>[ + Text('First child'), + Icon(Icons.check), + ]), + ], ), ), ); @@ -535,18 +519,16 @@ void main() { expect(iconTheme(Icons.check).data.color, selectedColor); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - disabledColor: disabledColor, - isSelected: const <bool>[true], - children: const <Widget>[ - Row(children: <Widget>[ - Text('First child'), - Icon(Icons.check), - ]), - ], - ), + boilerplate( + child: ToggleButtons( + disabledColor: disabledColor, + isSelected: const <bool>[true], + children: const <Widget>[ + Row(children: <Widget>[ + Text('First child'), + Icon(Icons.check), + ]), + ], ), ), ); @@ -560,17 +542,15 @@ void main() { testWidgets('Default button fillColor - unselected', (WidgetTester tester) async { final ThemeData theme = ThemeData(); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false], - onPressed: (int index) {}, - children: const <Widget>[ - Row(children: <Widget>[ - Text('First child'), - ]), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false], + onPressed: (int index) {}, + children: const <Widget>[ + Row(children: <Widget>[ + Text('First child'), + ]), + ], ), ), ); @@ -589,17 +569,15 @@ void main() { testWidgets('Default button fillColor - selected', (WidgetTester tester) async { final ThemeData theme = ThemeData(); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[true], - onPressed: (int index) {}, - children: const <Widget>[ - Row(children: <Widget>[ - Text('First child'), - ]), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[true], + onPressed: (int index) {}, + children: const <Widget>[ + Row(children: <Widget>[ + Text('First child'), + ]), + ], ), ), ); @@ -618,16 +596,14 @@ void main() { testWidgets('Default button fillColor - disabled', (WidgetTester tester) async { final ThemeData theme = ThemeData(); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[true], - children: const <Widget>[ - Row(children: <Widget>[ - Text('First child'), - ]), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[true], + children: const <Widget>[ + Row(children: <Widget>[ + Text('First child'), + ]), + ], ), ), ); @@ -646,18 +622,16 @@ void main() { testWidgets('Custom button fillColor', (WidgetTester tester) async { const Color customFillColor = Colors.green; await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - fillColor: customFillColor, - isSelected: const <bool>[true], - onPressed: (int index) {}, - children: const <Widget>[ - Row(children: <Widget>[ - Text('First child'), - ]), - ], - ), + boilerplate( + child: ToggleButtons( + fillColor: customFillColor, + isSelected: const <bool>[true], + onPressed: (int index) {}, + children: const <Widget>[ + Row(children: <Widget>[ + Text('First child'), + ]), + ], ), ), ); @@ -684,17 +658,15 @@ void main() { const Color selectedFillColor = Colors.yellow; await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - fillColor: selectedFillColor, - isSelected: const <bool>[false, true], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First child'), - Text('Second child'), - ], - ), + boilerplate( + child: ToggleButtons( + fillColor: selectedFillColor, + isSelected: const <bool>[false, true], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First child'), + Text('Second child'), + ], ), ), ); @@ -705,16 +677,14 @@ void main() { expect(buttonColor('Second child').color, selectedFillColor); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - fillColor: selectedFillColor, - isSelected: const <bool>[false, true], - children: const <Widget>[ - Text('First child'), - Text('Second child'), - ], - ), + boilerplate( + child: ToggleButtons( + fillColor: selectedFillColor, + isSelected: const <bool>[false, true], + children: const <Widget>[ + Text('First child'), + Text('Second child'), + ], ), ), ); @@ -746,17 +716,15 @@ void main() { } await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - fillColor: MaterialStateColor.resolveWith(getFillColor), - isSelected: const <bool>[false, true], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First child'), - Text('Second child'), - ], - ), + boilerplate( + child: ToggleButtons( + fillColor: MaterialStateColor.resolveWith(getFillColor), + isSelected: const <bool>[false, true], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First child'), + Text('Second child'), + ], ), ), ); @@ -768,16 +736,14 @@ void main() { // disabled await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - fillColor: MaterialStateColor.resolveWith(getFillColor), - isSelected: const <bool>[false, true], - children: const <Widget>[ - Text('First child'), - Text('Second child'), - ], - ), + boilerplate( + child: ToggleButtons( + fillColor: MaterialStateColor.resolveWith(getFillColor), + isSelected: const <bool>[false, true], + children: const <Widget>[ + Text('First child'), + Text('Second child'), + ], ), ), ); @@ -792,16 +758,14 @@ void main() { final ThemeData theme = ThemeData(); final FocusNode focusNode = FocusNode(); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false], - onPressed: (int index) {}, - focusNodes: <FocusNode>[focusNode], - children: const <Widget>[ - Text('First child'), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false], + onPressed: (int index) {}, + focusNodes: <FocusNode>[focusNode], + children: const <Widget>[ + Text('First child'), + ], ), ), ); @@ -859,16 +823,14 @@ void main() { final ThemeData theme = ThemeData(); final FocusNode focusNode = FocusNode(); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[true], - onPressed: (int index) {}, - focusNodes: <FocusNode>[focusNode], - children: const <Widget>[ - Text('First child'), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[true], + onPressed: (int index) {}, + focusNodes: <FocusNode>[focusNode], + children: const <Widget>[ + Text('First child'), + ], ), ), ); @@ -930,20 +892,18 @@ void main() { final FocusNode focusNode = FocusNode(); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - splashColor: splashColor, - highlightColor: highlightColor, - hoverColor: hoverColor, - focusColor: focusColor, - isSelected: const <bool>[true], - onPressed: (int index) {}, - focusNodes: <FocusNode>[focusNode], - children: const <Widget>[ - Text('First child'), - ], - ), + boilerplate( + child: ToggleButtons( + splashColor: splashColor, + highlightColor: highlightColor, + hoverColor: hoverColor, + focusColor: focusColor, + isSelected: const <bool>[true], + onPressed: (int index) {}, + focusNodes: <FocusNode>[focusNode], + children: const <Widget>[ + Text('First child'), + ], ), ), ); @@ -998,16 +958,14 @@ void main() { (WidgetTester tester) async { final ThemeData theme = ThemeData(); const double defaultBorderWidth = 1.0; - await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First child'), - ], - ), + await tester.pumpWidget( + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First child'), + ], ), ), ); @@ -1029,15 +987,13 @@ void main() { ); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[true], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First child'), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[true], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First child'), + ], ), ), ); @@ -1058,14 +1014,12 @@ void main() { ); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false], - children: const <Widget>[ - Text('First child'), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false], + children: const <Widget>[ + Text('First child'), + ], ), ), ); @@ -1096,17 +1050,15 @@ void main() { const double customWidth = 2.0; await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - borderColor: borderColor, - borderWidth: customWidth, - isSelected: const <bool>[false], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First child'), - ], - ), + boilerplate( + child: ToggleButtons( + borderColor: borderColor, + borderWidth: customWidth, + isSelected: const <bool>[false], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First child'), + ], ), ), ); @@ -1128,17 +1080,15 @@ void main() { ); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - selectedBorderColor: selectedBorderColor, - borderWidth: customWidth, - isSelected: const <bool>[true], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First child'), - ], - ), + boilerplate( + child: ToggleButtons( + selectedBorderColor: selectedBorderColor, + borderWidth: customWidth, + isSelected: const <bool>[true], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First child'), + ], ), ), ); @@ -1159,16 +1109,14 @@ void main() { ); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - disabledBorderColor: disabledBorderColor, - borderWidth: customWidth, - isSelected: const <bool>[false], - children: const <Widget>[ - Text('First child'), - ], - ), + boilerplate( + child: ToggleButtons( + disabledBorderColor: disabledBorderColor, + borderWidth: customWidth, + isSelected: const <bool>[false], + children: const <Widget>[ + Text('First child'), + ], ), ), ); @@ -1204,12 +1152,10 @@ void main() { ]; await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false, true, false], - children: children, - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false, true, false], + children: children, ), ), ); @@ -1247,12 +1193,10 @@ void main() { ]; await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false, true, false], - children: children, - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false, true, false], + children: children, ), ), ); @@ -1299,13 +1243,11 @@ void main() { // Update border width and widget sized to verify layout updates correctly const double customBorderWidth = 5.0; await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - borderWidth: customBorderWidth, - isSelected: const <bool>[false, true, false], - children: childrenRebuilt, - ), + boilerplate( + child: ToggleButtons( + borderWidth: customBorderWidth, + isSelected: const <bool>[false, true, false], + children: childrenRebuilt, ), ), ); @@ -1331,27 +1273,30 @@ void main() { // The point size of the fonts must be a multiple of 4 until // https://github.com/flutter/flutter/issues/122066 is resolved. await tester.pumpWidget( - Material( - child: boilerplate( - child: Row( - crossAxisAlignment: CrossAxisAlignment.baseline, - textBaseline: TextBaseline.alphabetic, - children: <Widget>[ - ToggleButtons( - borderWidth: 5.0, - isSelected: const <bool>[false, true], - children: const <Widget>[ - Text('First child', style: TextStyle(fontFamily: 'FlutterTest', fontSize: 8.0)), - Text('Second child', style: TextStyle(fontFamily: 'FlutterTest', fontSize: 8.0)), - ], - ), - const MaterialButton( - onPressed: null, - child: Text('Material Button', style: TextStyle(fontFamily: 'FlutterTest', fontSize: 20.0)), - ), - const Text('Text', style: TextStyle(fontFamily: 'FlutterTest', fontSize: 28.0)), - ], - ), + boilerplate( + useMaterial3: false, + child: Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: <Widget>[ + ToggleButtons( + borderWidth: 5.0, + isSelected: const <bool>[false, true], + children: const <Widget>[ + Text('First child', style: TextStyle(fontFamily: 'FlutterTest', fontSize: 8.0)), + Text('Second child', style: TextStyle(fontFamily: 'FlutterTest', fontSize: 8.0)), + ], + ), + ElevatedButton( + onPressed: null, + style: ElevatedButton.styleFrom(textStyle: const TextStyle( + fontFamily: 'FlutterTest', + fontSize: 20.0, + )), + child: const Text('Elevated Button'), + ), + const Text('Text', style: TextStyle(fontFamily: 'FlutterTest', fontSize: 28.0)), + ], ), ), ); @@ -1366,11 +1311,11 @@ void main() { final double firstToggleButtonDy = tester.getBottomLeft(find.text('First child')).dy; final double secondToggleButtonDy = tester.getBottomLeft(find.text('Second child')).dy; - final double materialButtonDy = tester.getBottomLeft(find.text('Material Button')).dy; + final double elevatedButtonDy = tester.getBottomLeft(find.text('Elevated Button')).dy; final double textDy = tester.getBottomLeft(find.text('Text')).dy; expect(firstToggleButtonDy, secondToggleButtonDy); - expect(firstToggleButtonDy, materialButtonDy - 3.0); + expect(firstToggleButtonDy, elevatedButtonDy - 3.0); expect(firstToggleButtonDy, textDy - 5.0); }); @@ -1427,17 +1372,15 @@ void main() { (WidgetTester tester) async { final ThemeData theme = ThemeData(); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false, true, false], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First child'), - Text('Second child'), - Text('Third child'), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false, true, false], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First child'), + Text('Second child'), + Text('Third child'), + ], ), ), ); @@ -1509,18 +1452,16 @@ void main() { (WidgetTester tester) async { final ThemeData theme = ThemeData(); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - direction: Axis.vertical, - isSelected: const <bool>[false, true, false], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First child'), - Text('Second child'), - Text('Third child'), - ], - ), + boilerplate( + child: ToggleButtons( + direction: Axis.vertical, + isSelected: const <bool>[false, true, false], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First child'), + Text('Second child'), + Text('Third child'), + ], ), ), ); @@ -1597,19 +1538,17 @@ void main() { 'VerticalDirection test when direction is vertical.', (WidgetTester tester) async { await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - direction: Axis.vertical, - verticalDirection: VerticalDirection.up, - isSelected: const <bool>[false, true, false], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First child'), - Text('Second child'), - Text('Third child'), - ], - ), + boilerplate( + child: ToggleButtons( + direction: Axis.vertical, + verticalDirection: VerticalDirection.up, + isSelected: const <bool>[false, true, false], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First child'), + Text('Second child'), + Text('Third child'), + ], ), ), ); @@ -1623,22 +1562,19 @@ void main() { testWidgets('Tap target size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async { Widget buildFrame(MaterialTapTargetSize tapTargetSize, Key key) { - return Theme( - data: ThemeData(materialTapTargetSize: tapTargetSize), - child: Material( - child: boilerplate( - child: ToggleButtons( - key: key, - constraints: const BoxConstraints(minWidth: 32.0, minHeight: 32.0), - isSelected: const <bool>[false, true, false], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First'), - Text('Second'), - Text('Third'), - ], - ), - ), + return boilerplate( + useMaterial3: false, + tapTargetSize: tapTargetSize, + child: ToggleButtons( + key: key, + constraints: const BoxConstraints(minWidth: 32.0, minHeight: 32.0), + isSelected: const <bool>[false, true, false], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First'), + Text('Second'), + Text('Third'), + ], ), ); } @@ -1654,20 +1590,19 @@ void main() { testWidgets('Tap target size is configurable', (WidgetTester tester) async { Widget buildFrame(MaterialTapTargetSize tapTargetSize, Key key) { - return Material( - child: boilerplate( - child: ToggleButtons( - key: key, - tapTargetSize: tapTargetSize, - constraints: const BoxConstraints(minWidth: 32.0, minHeight: 32.0), - isSelected: const <bool>[false, true, false], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First'), - Text('Second'), - Text('Third'), - ], - ), + return boilerplate( + useMaterial3: false, + child: ToggleButtons( + key: key, + tapTargetSize: tapTargetSize, + constraints: const BoxConstraints(minWidth: 32.0, minHeight: 32.0), + isSelected: const <bool>[false, true, false], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First'), + Text('Second'), + Text('Third'), + ], ), ); } @@ -1683,22 +1618,20 @@ void main() { testWidgets('Tap target size is configurable for vertical axis', (WidgetTester tester) async { Widget buildFrame(MaterialTapTargetSize tapTargetSize, Key key) { - return Material( - child: boilerplate( - child: ToggleButtons( - key: key, - tapTargetSize: tapTargetSize, - constraints: const BoxConstraints(minWidth: 32.0, minHeight: 32.0), - direction: Axis.vertical, - isSelected: const <bool>[false, true, false], - onPressed: (int index) {}, - children: const <Widget>[ - Text('1'), - Text('2'), - Text('3'), - ], - ), - ), + return boilerplate( + child: ToggleButtons( + key: key, + tapTargetSize: tapTargetSize, + constraints: const BoxConstraints(minWidth: 32.0, minHeight: 32.0), + direction: Axis.vertical, + isSelected: const <bool>[false, true, false], + onPressed: (int index) {}, + children: const <Widget>[ + Text('1'), + Text('2'), + Text('3'), + ], + ), ); } @@ -1713,19 +1646,18 @@ void main() { // Regression test for https://github.com/flutter/flutter/issues/73725 testWidgets('Border radius paint test when there is only one button', (WidgetTester tester) async { - final ThemeData theme = ThemeData(); + final ThemeData theme = ThemeData(useMaterial3: false); await tester.pumpWidget( - Material( - child: boilerplate( - child: RepaintBoundary( - child: ToggleButtons( - borderRadius: const BorderRadius.all(Radius.circular(7.0)), - isSelected: const <bool>[true], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First child'), - ], - ), + boilerplate( + useMaterial3: false, + child: RepaintBoundary( + child: ToggleButtons( + borderRadius: const BorderRadius.all(Radius.circular(7.0)), + isSelected: const <bool>[true], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First child'), + ], ), ), ), @@ -1760,22 +1692,21 @@ void main() { testWidgets('Border radius paint test when Radius.x or Radius.y equal 0.0', (WidgetTester tester) async { await tester.pumpWidget( - Material( - child: boilerplate( - child: RepaintBoundary( - child: ToggleButtons( - borderRadius: const BorderRadius.only( - topRight: Radius.elliptical(10, 0), - topLeft: Radius.elliptical(0, 10), - bottomRight: Radius.elliptical(0, 10), - bottomLeft: Radius.elliptical(10, 0), - ), - isSelected: const <bool>[true], - onPressed: (int index) {}, - children: const <Widget>[ - Text('First child'), - ], + boilerplate( + useMaterial3: false, + child: RepaintBoundary( + child: ToggleButtons( + borderRadius: const BorderRadius.only( + topRight: Radius.elliptical(10, 0), + topLeft: Radius.elliptical(0, 10), + bottomRight: Radius.elliptical(0, 10), + bottomLeft: Radius.elliptical(10, 0), ), + isSelected: const <bool>[true], + onPressed: (int index) {}, + children: const <Widget>[ + Text('First child'), + ], ), ), ), @@ -1827,19 +1758,17 @@ void main() { testWidgets('ToggleButtons changes mouse cursor when the button is hovered', (WidgetTester tester) async { await tester.pumpWidget( - Material( - child: boilerplate( - child: MouseRegion( - cursor: SystemMouseCursors.forbidden, - child: ToggleButtons( - mouseCursor: SystemMouseCursors.text, - onPressed: (int index) {}, - isSelected: const <bool>[false, true], - children: const <Widget>[ - Text('First child'), - Text('Second child'), - ], - ), + boilerplate( + child: MouseRegion( + cursor: SystemMouseCursors.forbidden, + child: ToggleButtons( + mouseCursor: SystemMouseCursors.text, + onPressed: (int index) {}, + isSelected: const <bool>[false, true], + children: const <Widget>[ + Text('First child'), + Text('Second child'), + ], ), ), ), @@ -1854,18 +1783,16 @@ void main() { // Test default cursor await tester.pumpWidget( - Material( - child: boilerplate( - child: MouseRegion( - cursor: SystemMouseCursors.forbidden, - child: ToggleButtons( - onPressed: (int index) {}, - isSelected: const <bool>[false, true], - children: const <Widget>[ - Text('First child'), - Text('Second child'), - ], - ), + boilerplate( + child: MouseRegion( + cursor: SystemMouseCursors.forbidden, + child: ToggleButtons( + onPressed: (int index) {}, + isSelected: const <bool>[false, true], + children: const <Widget>[ + Text('First child'), + Text('Second child'), + ], ), ), ), @@ -1875,17 +1802,15 @@ void main() { // Test default cursor when disabled await tester.pumpWidget( - Material( - child: boilerplate( - child: MouseRegion( - cursor: SystemMouseCursors.forbidden, - child: ToggleButtons( - isSelected: const <bool>[false, true], - children: const <Widget>[ - Text('First child'), - Text('Second child'), - ], - ), + boilerplate( + child: MouseRegion( + cursor: SystemMouseCursors.forbidden, + child: ToggleButtons( + isSelected: const <bool>[false, true], + children: const <Widget>[ + Text('First child'), + Text('Second child'), + ], ), ), ), @@ -1897,14 +1822,12 @@ void main() { testWidgets('ToggleButtons focus, hover, and highlight elevations are 0', (WidgetTester tester) async { final List<FocusNode> focusNodes = <FocusNode>[FocusNode(), FocusNode()]; await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[true, false], - onPressed: (int index) { }, - focusNodes: focusNodes, - children: const <Widget>[Text('one'), Text('two')], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[true, false], + onPressed: (int index) { }, + focusNodes: focusNodes, + children: const <Widget>[Text('one'), Text('two')], ), ), ); @@ -1944,17 +1867,15 @@ void main() { testWidgets('Toggle buttons height matches MaterialTapTargetSize.padded height', (WidgetTester tester) async { await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false, false, false], - onPressed: (int index) {}, - children: const <Widget>[ - Icon(Icons.check), - Icon(Icons.access_alarm), - Icon(Icons.cake), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false, false, false], + onPressed: (int index) {}, + children: const <Widget>[ + Icon(Icons.check), + Icon(Icons.access_alarm), + Icon(Icons.cake), + ], ), ), ); @@ -1972,21 +1893,19 @@ void main() { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false, false, false], - onPressed: (int index) {}, - constraints: const BoxConstraints.tightFor( - width: 86, - height: 32, - ), - children: const <Widget>[ - Icon(Icons.check), - Icon(Icons.access_alarm), - Icon(Icons.cake), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false, false, false], + onPressed: (int index) {}, + constraints: const BoxConstraints.tightFor( + width: 86, + height: 32, + ), + children: const <Widget>[ + Icon(Icons.check), + Icon(Icons.access_alarm), + Icon(Icons.cake), + ], ), ), ); @@ -2060,16 +1979,14 @@ void main() { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( - Material( - child: boilerplate( - child: ToggleButtons( - isSelected: const <bool>[false, true], - onPressed: (int index) {}, - children: const <Widget>[ - Icon(Icons.check), - Icon(Icons.access_alarm), - ], - ), + boilerplate( + child: ToggleButtons( + isSelected: const <bool>[false, true], + onPressed: (int index) {}, + children: const <Widget>[ + Icon(Icons.check), + Icon(Icons.access_alarm), + ], ), ), ); diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart index 386f59f12f3c4..84d637190d762 100644 --- a/packages/flutter/test/material/tooltip_test.dart +++ b/packages/flutter/test/material/tooltip_test.dart @@ -142,32 +142,35 @@ void main() { testWidgetsWithLeakTracking('Does tooltip end up in the right place - top left', (WidgetTester tester) async { final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>(); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Overlay( - initialEntries: <OverlayEntry>[ - OverlayEntry( - builder: (BuildContext context) { - return Stack( - children: <Widget>[ - Positioned( - left: 0.0, - top: 0.0, - child: Tooltip( - key: tooltipKey, - message: tooltipText, - height: 20.0, - padding: const EdgeInsets.all(5.0), - verticalOffset: 20.0, - preferBelow: false, - child: const SizedBox.shrink(), + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: <OverlayEntry>[ + OverlayEntry( + builder: (BuildContext context) { + return Stack( + children: <Widget>[ + Positioned( + left: 0.0, + top: 0.0, + child: Tooltip( + key: tooltipKey, + message: tooltipText, + height: 20.0, + padding: const EdgeInsets.all(5.0), + verticalOffset: 20.0, + preferBelow: false, + child: const SizedBox.shrink(), + ), ), - ), - ], - ); - }, - ), - ], + ], + ); + }, + ), + ], + ), ), ), ); @@ -362,32 +365,35 @@ void main() { testWidgetsWithLeakTracking('Does tooltip end up in the right place - way off to the right', (WidgetTester tester) async { final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>(); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Overlay( - initialEntries: <OverlayEntry>[ - OverlayEntry( - builder: (BuildContext context) { - return Stack( - children: <Widget>[ - Positioned( - left: 1600.0, - top: 300.0, - child: Tooltip( - key: tooltipKey, - message: tooltipText, - height: 10.0, - padding: EdgeInsets.zero, - verticalOffset: 10.0, - preferBelow: true, - child: const SizedBox.shrink(), + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: <OverlayEntry>[ + OverlayEntry( + builder: (BuildContext context) { + return Stack( + children: <Widget>[ + Positioned( + left: 1600.0, + top: 300.0, + child: Tooltip( + key: tooltipKey, + message: tooltipText, + height: 10.0, + padding: EdgeInsets.zero, + verticalOffset: 10.0, + preferBelow: true, + child: const SizedBox.shrink(), + ), ), - ), - ], - ); - }, - ), - ], + ], + ); + }, + ), + ], + ), ), ), ); @@ -416,32 +422,35 @@ void main() { testWidgetsWithLeakTracking('Does tooltip end up in the right place - near the edge', (WidgetTester tester) async { final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>(); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Overlay( - initialEntries: <OverlayEntry>[ - OverlayEntry( - builder: (BuildContext context) { - return Stack( - children: <Widget>[ - Positioned( - left: 780.0, - top: 300.0, - child: Tooltip( - key: tooltipKey, - message: tooltipText, - height: 10.0, - padding: EdgeInsets.zero, - verticalOffset: 10.0, - preferBelow: true, - child: const SizedBox.shrink(), + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: <OverlayEntry>[ + OverlayEntry( + builder: (BuildContext context) { + return Stack( + children: <Widget>[ + Positioned( + left: 780.0, + top: 300.0, + child: Tooltip( + key: tooltipKey, + message: tooltipText, + height: 10.0, + padding: EdgeInsets.zero, + verticalOffset: 10.0, + preferBelow: true, + child: const SizedBox.shrink(), + ), ), - ), - ], - ); - }, - ), - ], + ], + ); + }, + ), + ], + ), ), ), ); @@ -580,6 +589,7 @@ void main() { testWidgetsWithLeakTracking('Default tooltip message textStyle - light', (WidgetTester tester) async { final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>(); await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Tooltip( key: tooltipKey, message: tooltipText, @@ -604,6 +614,7 @@ void main() { final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>(); await tester.pumpWidget(MaterialApp( theme: ThemeData( + useMaterial3: false, brightness: Brightness.dark, ), home: Tooltip( @@ -760,20 +771,23 @@ void main() { testWidgetsWithLeakTracking('Does tooltip end up with the right default size, shape, and color', (WidgetTester tester) async { final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>(); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Overlay( - initialEntries: <OverlayEntry>[ - OverlayEntry( - builder: (BuildContext context) { - return Tooltip( - key: tooltipKey, - message: tooltipText, - child: const SizedBox.shrink(), - ); - }, - ), - ], + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: <OverlayEntry>[ + OverlayEntry( + builder: (BuildContext context) { + return Tooltip( + key: tooltipKey, + message: tooltipText, + child: const SizedBox.shrink(), + ); + }, + ), + ], + ), ), ), ); @@ -797,6 +811,7 @@ void main() { final GlobalKey<TooltipState> tooltipKey = GlobalKey<TooltipState>(); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Tooltip( key: tooltipKey, message: tooltipText, @@ -828,21 +843,24 @@ void main() { color: Color(0x80800000), ); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: Overlay( - initialEntries: <OverlayEntry>[ - OverlayEntry( - builder: (BuildContext context) { - return Tooltip( - key: tooltipKey, - decoration: customDecoration, - message: tooltipText, - child: const SizedBox.shrink(), - ); - }, - ), - ], + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: <OverlayEntry>[ + OverlayEntry( + builder: (BuildContext context) { + return Tooltip( + key: tooltipKey, + decoration: customDecoration, + message: tooltipText, + child: const SizedBox.shrink(), + ); + }, + ), + ], + ), ), ), ); @@ -1041,15 +1059,9 @@ void main() { ), ); - // The tooltip overlay still on the tree and it will removed in the next frame. - - // Dispatch the mouse in and out events before the overlay detached. - await gesture.moveTo(tester.getCenter(find.text(tooltipText))); - await gesture.moveTo(Offset.zero); - await tester.pumpAndSettle(); - - // Go without crashes. - await gesture.removePointer(); + // The tooltip should be removed, including the overlay child. + expect(find.text(tooltipText), findsNothing); + expect(find.byTooltip(tooltipText), findsNothing); }); testWidgetsWithLeakTracking('Calling ensureTooltipVisible on an unmounted TooltipState returns false', (WidgetTester tester) async { @@ -1435,58 +1447,32 @@ void main() { semantics.dispose(); }); - testWidgetsWithLeakTracking('Tooltip overlay does not update', (WidgetTester tester) async { - Widget buildApp(String text) { - return MaterialApp( - home: Center( - child: Tooltip( - message: text, - child: Container( - width: 100.0, - height: 100.0, - color: Colors.green[500], - ), - ), - ), - ); - } - - await tester.pumpWidget(buildApp(tooltipText)); - await tester.longPress(find.byType(Tooltip)); - expect(find.text(tooltipText), findsOneWidget); - await tester.pumpWidget(buildApp('NEW')); - expect(find.text(tooltipText), findsOneWidget); - await tester.tapAt(const Offset(5.0, 5.0)); - await tester.pump(); - await tester.pump(const Duration(seconds: 1)); - expect(find.text(tooltipText), findsNothing); - await tester.longPress(find.byType(Tooltip)); - expect(find.text(tooltipText), findsNothing); - }); - testWidgetsWithLeakTracking('Tooltip text scales with textScaleFactor', (WidgetTester tester) async { Widget buildApp(String text, { required double textScaleFactor }) { - return MediaQuery( - data: MediaQueryData(textScaleFactor: textScaleFactor), - child: Directionality( - textDirection: TextDirection.ltr, - child: Navigator( - onGenerateRoute: (RouteSettings settings) { - return MaterialPageRoute<void>( - builder: (BuildContext context) { - return Center( - child: Tooltip( - message: text, - child: Container( - width: 100.0, - height: 100.0, - color: Colors.green[500], + return Theme( + data: ThemeData(useMaterial3: false), + child: MediaQuery( + data: MediaQueryData(textScaleFactor: textScaleFactor), + child: Directionality( + textDirection: TextDirection.ltr, + child: Navigator( + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute<void>( + builder: (BuildContext context) { + return Center( + child: Tooltip( + message: text, + child: Container( + width: 100.0, + height: 100.0, + color: Colors.green[500], + ), ), - ), - ); - }, - ); - }, + ); + }, + ); + }, + ), ), ), ); @@ -1993,7 +1979,7 @@ void main() { } }); - testWidgets('Tooltip trigger mode ignores mouse events', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Tooltip trigger mode ignores mouse events', (WidgetTester tester) async { await tester.pumpWidget( const MaterialApp( home: Tooltip( @@ -2021,7 +2007,7 @@ void main() { expect(find.text(tooltipText), findsOneWidget); }); - testWidgets('Tooltip does not block other mouse regions', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Tooltip does not block other mouse regions', (WidgetTester tester) async { bool entered = false; await tester.pumpWidget( @@ -2046,7 +2032,7 @@ void main() { expect(entered, isTrue); }); - testWidgets('Does not rebuild on mouse connect/disconnect', (WidgetTester tester) async { + testWidgetsWithLeakTracking('Does not rebuild on mouse connect/disconnect', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/117627 int buildCount = 0; await tester.pumpWidget( @@ -2100,6 +2086,289 @@ void main() { await _testGestureTap(tester, textSpan); expect(isTapped, isTrue); }); + + testWidgetsWithLeakTracking('Hold mouse button down and hover over the Tooltip widget', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Center( + child: SizedBox.square( + dimension: 10.0, + child: Tooltip( + message: tooltipText, + waitDuration: Duration(seconds: 1), + triggerMode: TooltipTriggerMode.longPress, + child: SizedBox.expand(), + ), + ), + ), + ), + ); + + final TestGesture mouseGesture = await tester.startGesture(Offset.zero, kind: PointerDeviceKind.mouse); + addTearDown(mouseGesture.removePointer); + await mouseGesture.moveTo(tester.getCenter(find.byTooltip(tooltipText))); + await tester.pump(const Duration(seconds: 1)); + expect( + find.text(tooltipText), findsOneWidget, reason: 'Tooltip should be visible when hovered.'); + + await mouseGesture.up(); + await tester.pump(const Duration(days: 10)); + await tester.pumpAndSettle(); + expect( + find.text(tooltipText), + findsOneWidget, + reason: 'Tooltip should be visible even when there is a PointerUp when hovered.', + ); + + await mouseGesture.moveTo(Offset.zero); + await tester.pump(const Duration(seconds: 1)); + await tester.pumpAndSettle(); + expect( + find.text(tooltipText), + findsNothing, + reason: 'Tooltip should be dismissed with no hovering mouse cursor.' , + ); + }); + + testWidgetsWithLeakTracking('Hovered text should dismiss when clicked outside', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Center( + child: SizedBox.square( + dimension: 10.0, + child: Tooltip( + message: tooltipText, + waitDuration: Duration(seconds: 1), + triggerMode: TooltipTriggerMode.longPress, + child: SizedBox.expand(), + ), + ), + ), + ), + ); + + // Avoid using startGesture here to avoid the PointDown event from also being + // interpreted as a PointHover event by the Tooltip. + final TestGesture mouseGesture1 = await tester.createGesture(kind: PointerDeviceKind.mouse); + addTearDown(mouseGesture1.removePointer); + await mouseGesture1.moveTo(tester.getCenter(find.byTooltip(tooltipText))); + await tester.pump(const Duration(seconds: 1)); + expect(find.text(tooltipText), findsOneWidget, reason: 'Tooltip should be visible when hovered.'); + + // Tapping on the Tooltip widget should dismiss the tooltip, since the + // trigger mode is longPress. + await tester.tap(find.byTooltip(tooltipText)); + await tester.pump(); + await tester.pumpAndSettle(); + expect(find.text(tooltipText), findsNothing); + await mouseGesture1.removePointer(); + + final TestGesture mouseGesture2 = await tester.createGesture(kind: PointerDeviceKind.mouse); + addTearDown(mouseGesture2.removePointer); + await mouseGesture2.moveTo(tester.getCenter(find.byTooltip(tooltipText))); + await tester.pump(const Duration(seconds: 1)); + expect(find.text(tooltipText), findsOneWidget, reason: 'Tooltip should be visible when hovered.'); + + await tester.tapAt(Offset.zero); + await tester.pump(); + await tester.pumpAndSettle(); + expect(find.text(tooltipText), findsNothing, reason: 'Tapping outside of the Tooltip widget should dismiss the tooltip.'); + }); + + testWidgetsWithLeakTracking('Mouse tap and hover over the Tooltip widget', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/127575 . + await tester.pumpWidget( + const MaterialApp( + home: Center( + child: SizedBox.square( + dimension: 10.0, + child: Tooltip( + message: tooltipText, + waitDuration: Duration(seconds: 1), + triggerMode: TooltipTriggerMode.longPress, + child: SizedBox.expand(), + ), + ), + ), + ), + ); + + // The PointDown event is also interpreted as a PointHover event by the + // Tooltip. This should be pretty rare but since it's more of a tap event + // than a hover event, the tooltip shouldn't show unless the triggerMode + // is set to tap. + final TestGesture mouseGesture1 = await tester.startGesture( + tester.getCenter(find.byTooltip(tooltipText)), + kind: PointerDeviceKind.mouse, + ); + addTearDown(mouseGesture1.removePointer); + await tester.pump(const Duration(seconds: 1)); + expect( + find.text(tooltipText), + findsNothing, + reason: 'Tooltip should NOT be visible when hovered and tapped, when trigger mode is not tap', + ); + await mouseGesture1.up(); + await mouseGesture1.removePointer(); + await tester.pump(const Duration(days: 10)); + await tester.pumpAndSettle(); + + await tester.pumpWidget( + const MaterialApp( + home: Center( + child: SizedBox.square( + dimension: 10.0, + child: Tooltip( + message: tooltipText, + waitDuration: Duration(seconds: 1), + triggerMode: TooltipTriggerMode.tap, + child: SizedBox.expand(), + ), + ), + ), + ), + ); + + final TestGesture mouseGesture2 = await tester.startGesture( + tester.getCenter(find.byTooltip(tooltipText)), + kind: PointerDeviceKind.mouse, + ); + addTearDown(mouseGesture2.removePointer); + // The tap should be ignored, since Tooltip does not track "trigger gestures" + // for mouse devices. + await tester.pump(const Duration(milliseconds: 100)); + await mouseGesture2.up(); + await tester.pump(const Duration(seconds: 1)); + expect( + find.text(tooltipText), + findsNothing, + reason: 'Tooltip should NOT be visible when hovered and tapped, when trigger mode is tap', + ); + }); + + testWidgetsWithLeakTracking('Tooltip does not rebuild for fade in / fade out animation', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Center( + child: SizedBox.square( + dimension: 10.0, + child: Tooltip( + message: tooltipText, + waitDuration: Duration(seconds: 1), + triggerMode: TooltipTriggerMode.longPress, + child: SizedBox.expand(), + ), + ), + ), + ), + ); + final TooltipState tooltipState = tester.state(find.byType(Tooltip)); + final Element element = tooltipState.context as Element; + // The Tooltip widget itself is almost stateless thus doesn't need + // rebuilding. + expect(element.dirty, isFalse); + + expect(tooltipState.ensureTooltipVisible(), isTrue); + expect(element.dirty, isFalse); + await tester.pump(const Duration(seconds: 1)); + expect(element.dirty, isFalse); + + expect(Tooltip.dismissAllToolTips(), isTrue); + expect(element.dirty, isFalse); + await tester.pump(const Duration(seconds: 1)); + expect(element.dirty, isFalse); + }); + + testWidgets('Tooltip does not initialize animation controller in dispose process', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Center( + child: Tooltip( + message: tooltipText, + waitDuration: Duration(seconds: 1), + triggerMode: TooltipTriggerMode.longPress, + child: SizedBox.square(dimension: 50), + ), + ), + ), + ); + + await tester.startGesture(tester.getCenter(find.byType(Tooltip))); + await tester.pumpWidget(const SizedBox()); + expect(tester.takeException(), isNull); + }); + + testWidgets('Tooltip does not crash when showing the tooltip but the OverlayPortal is unmounted, during dispose', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: SelectionArea( + child: Center( + child: Tooltip( + message: tooltipText, + waitDuration: Duration(seconds: 1), + triggerMode: TooltipTriggerMode.longPress, + child: SizedBox.square(dimension: 50), + ), + ), + ), + ), + ); + + final TooltipState tooltipState = tester.state(find.byType(Tooltip)); + await tester.startGesture(tester.getCenter(find.byType(Tooltip))); + tooltipState.ensureTooltipVisible(); + await tester.pumpWidget(const SizedBox()); + expect(tester.takeException(), isNull); + }); + + testWidgets('Tooltip is not selectable', (WidgetTester tester) async { + const String tooltipText = 'AAAAAAAAAAAAAAAAAAAAAAA'; + String? selectedText; + await tester.pumpWidget( + MaterialApp( + home: SelectionArea( + onSelectionChanged: (SelectedContent? content) { selectedText = content?.plainText; }, + child: const Center( + child: Column( + children: <Widget>[ + Text('Select Me'), + Tooltip( + message: tooltipText, + waitDuration: Duration(seconds: 1), + triggerMode: TooltipTriggerMode.longPress, + child: SizedBox.square(dimension: 50), + ), + ], + ), + ), + ), + ), + ); + + final TooltipState tooltipState = tester.state(find.byType(Tooltip)); + + final Rect textRect = tester.getRect(find.text('Select Me')); + final TestGesture gesture = await tester.startGesture(Alignment.centerLeft.alongSize(textRect.size) + textRect.topLeft); + // Drag from centerLeft to centerRight to select the text. + await tester.pump(const Duration(seconds: 1)); + await gesture.moveTo(Alignment.centerRight.alongSize(textRect.size) + textRect.topLeft); + await tester.pump(); + + tooltipState.ensureTooltipVisible(); + await tester.pump(); + // Make sure the tooltip becomes visible. + expect(find.text(tooltipText), findsOneWidget); + assert(selectedText != null); + + final Rect tooltipTextRect = tester.getRect(find.text(tooltipText)); + // Now drag from centerLeft to centerRight to select the tooltip text. + await gesture.moveTo(Alignment.centerLeft.alongSize(tooltipTextRect.size) + tooltipTextRect.topLeft); + await tester.pump(); + await gesture.moveTo(Alignment.centerRight.alongSize(tooltipTextRect.size) + tooltipTextRect.topLeft); + await tester.pump(); + + expect(selectedText, isNot(contains('A'))); + }); } Future<void> setWidgetForTooltipMode( @@ -2138,5 +2407,5 @@ SemanticsNode _findDebugSemantics(RenderObject object) { if (object.debugSemantics != null) { return object.debugSemantics!; } - return _findDebugSemantics(object.parent! as RenderObject); + return _findDebugSemantics(object.parent!); } diff --git a/packages/flutter/test/material/tooltip_theme_test.dart b/packages/flutter/test/material/tooltip_theme_test.dart index b163d524f2d05..b2961b6e56746 100644 --- a/packages/flutter/test/material/tooltip_theme_test.dart +++ b/packages/flutter/test/material/tooltip_theme_test.dart @@ -686,6 +686,7 @@ void main() { textDirection: TextDirection.ltr, child: Theme( data: ThemeData( + useMaterial3: false, tooltipTheme: const TooltipThemeData( decoration: customDecoration, ), @@ -723,22 +724,25 @@ void main() { color: Color(0x80800000), ); await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: TooltipTheme( - data: const TooltipThemeData(decoration: customDecoration), - child: Overlay( - initialEntries: <OverlayEntry>[ - OverlayEntry( - builder: (BuildContext context) { - return Tooltip( - key: key, - message: tooltipText, - child: const SizedBox.shrink(), - ); - }, - ), - ], + Theme( + data: ThemeData(useMaterial3: false), + child: Directionality( + textDirection: TextDirection.ltr, + child: TooltipTheme( + data: const TooltipThemeData(decoration: customDecoration), + child: Overlay( + initialEntries: <OverlayEntry>[ + OverlayEntry( + builder: (BuildContext context) { + return Tooltip( + key: key, + message: tooltipText, + child: const SizedBox.shrink(), + ); + }, + ), + ], + ), ), ), ), @@ -1330,5 +1334,5 @@ SemanticsNode findDebugSemantics(RenderObject object) { if (object.debugSemantics != null) { return object.debugSemantics!; } - return findDebugSemantics(object.parent! as RenderObject); + return findDebugSemantics(object.parent!); } diff --git a/packages/flutter/test/material/value_indicating_slider_test.dart b/packages/flutter/test/material/value_indicating_slider_test.dart index c6a75f900ae0c..4e1e46575e1aa 100644 --- a/packages/flutter/test/material/value_indicating_slider_test.dart +++ b/packages/flutter/test/material/value_indicating_slider_test.dart @@ -189,8 +189,9 @@ void main() { }); group('Material 2', () { - // Tests that are only relevant for Material 2. Once ThemeData.useMaterial3 - // is turned on by default, these tests can be removed. + // These tests are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. testWidgets('Slider value indicator', (WidgetTester tester) async { await _buildValueIndicatorStaticSlider( diff --git a/packages/flutter/test/painting/_test_http_request.dart b/packages/flutter/test/painting/_test_http_request.dart index 9bb9be7ffe3db..8324d9b5e2a86 100644 --- a/packages/flutter/test/painting/_test_http_request.dart +++ b/packages/flutter/test/painting/_test_http_request.dart @@ -2,14 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// For now, we're hiding dart:js_interop's `@JS` to avoid a conflict with -// package:js' `@JS`. In the future, we should be able to remove package:js -// altogether and just import dart:js_interop. -import 'dart:js_interop' hide JS; +import 'dart:js_interop'; import 'package:flutter/src/services/dom.dart'; -import 'package:js/js.dart'; -import 'package:js/js_util.dart' as js_util; /// Defines a new property on an Object. @JS('Object.defineProperty') @@ -17,13 +12,12 @@ external JSVoid objectDefineProperty(JSAny o, JSString symbol, JSAny desc); void createGetter(JSAny mock, String key, JSAny? Function() get) { objectDefineProperty( - mock, - key.toJS, - js_util.jsify( - <String, Object>{ - 'get': () { return get(); }.toJS - } - ) as JSAny); + mock, + key.toJS, + <String, JSFunction>{ + 'get': (() => get()).toJS, + }.jsify()!, + ); } @JS() @@ -51,13 +45,12 @@ class TestHttpRequest { ); // TODO(srujzs): This is needed for when we reify JS types. Right now, JSAny // is a typedef for Object?, but when we reify, it'll be its own type. - // ignore: unnecessary_cast final JSAny mock = _mock as JSAny; - createGetter(mock, 'headers', () => js_util.jsify(headers) as JSAny); + createGetter(mock, 'headers', () => headers.jsify()); createGetter(mock, - 'responseHeaders', () => js_util.jsify(responseHeaders) as JSAny); + 'responseHeaders', () => responseHeaders.jsify()); createGetter(mock, 'status', () => status.toJS); - createGetter(mock, 'response', () => js_util.jsify(response) as JSAny); + createGetter(mock, 'response', () => response.jsify()); } late DomXMLHttpRequestMock _mock; diff --git a/packages/flutter/test/painting/image_provider_network_image_test.dart b/packages/flutter/test/painting/image_provider_network_image_test.dart index 10fb270446ed3..086ffa5669c5b 100644 --- a/packages/flutter/test/painting/image_provider_network_image_test.dart +++ b/packages/flutter/test/painting/image_provider_network_image_test.dart @@ -16,6 +16,7 @@ import '../rendering/rendering_tester.dart'; void main() { TestRenderingFlutterBinding.ensureInitialized(); + HttpOverrides.global = _FakeHttpOverrides(); Future<Codec> basicDecoder(ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) { return PaintingBinding.instance.instantiateImageCodecFromBuffer(buffer, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false); @@ -24,12 +25,14 @@ void main() { late _FakeHttpClient httpClient; setUp(() { + _FakeHttpOverrides.createHttpClientCalls = 0; httpClient = _FakeHttpClient(); debugNetworkImageHttpClientProvider = () => httpClient; }); tearDown(() { debugNetworkImageHttpClientProvider = null; + expect(_FakeHttpOverrides.createHttpClientCalls, 0); PaintingBinding.instance.imageCache.clear(); PaintingBinding.instance.imageCache.clearLiveImages(); }); @@ -212,6 +215,22 @@ void main() { }); } +/// Override `HttpClient()` to throw an error. +/// +/// This ensures that these tests never cause a call to the [HttpClient] +/// constructor. +/// +/// Regression test for <https://github.com/flutter/flutter/issues/129532>. +class _FakeHttpOverrides extends HttpOverrides { + static int createHttpClientCalls = 0; + + @override + HttpClient createHttpClient(SecurityContext? context) { + createHttpClientCalls++; + throw Exception('This test tried to create an HttpClient.'); + } +} + class _FakeHttpClient extends Fake implements HttpClient { final _FakeHttpClientRequest request = _FakeHttpClientRequest(); Object? thrownError; @@ -224,6 +243,7 @@ class _FakeHttpClient extends Fake implements HttpClient { return request; } } + class _FakeHttpClientRequest extends Fake implements HttpClientRequest { final _FakeHttpClientResponse response = _FakeHttpClientResponse(); diff --git a/packages/flutter/test/painting/image_resolution_test.dart b/packages/flutter/test/painting/image_resolution_test.dart index fd64c06bd01c0..2a784de9e0764 100644 --- a/packages/flutter/test/painting/image_resolution_test.dart +++ b/packages/flutter/test/painting/image_resolution_test.dart @@ -18,7 +18,7 @@ class TestAssetBundle extends CachingAssetBundle { @override Future<ByteData> load(String key) async { - if (key == 'AssetManifest.smcbin') { + if (key == 'AssetManifest.bin') { return const StandardMessageCodec().encodeMessage(_assetBundleMap)!; } @@ -43,8 +43,6 @@ void main() { final Map<String, List<Map<Object?, Object?>>> assetBundleMap = <String, List<Map<Object?, Object?>>>{}; - assetBundleMap[mainAssetPath] = <Map<Object?, Object?>>[]; - final AssetImage assetImage = AssetImage( mainAssetPath, bundle: TestAssetBundle(assetBundleMap), @@ -160,15 +158,17 @@ void main() { double chosenAssetRatio, String expectedAssetPath, ) { - final Map<String, List<Map<Object?, Object?>>> assetBundleMap = - <String, List<Map<Object?, Object?>>>{}; - - final Map<Object?, Object?> mainAssetVariantManifestEntry = <Object?, Object?>{}; - mainAssetVariantManifestEntry['asset'] = variantPath; - mainAssetVariantManifestEntry['dpr'] = 3.0; - assetBundleMap[mainAssetPath] = <Map<Object?, Object?>>[mainAssetVariantManifestEntry]; - - final TestAssetBundle testAssetBundle = TestAssetBundle(assetBundleMap); + const Map<String, List<Map<Object?, Object?>>> assetManifest = + <String, List<Map<Object?, Object?>>>{ + 'assets/normalFolder/normalFile.png': <Map<Object?, Object?>>[ + <Object?, Object?>{'asset': 'assets/normalFolder/normalFile.png'}, + <Object?, Object?>{ + 'asset': 'assets/normalFolder/3.0x/normalFile.png', + 'dpr': 3.0 + }, + ] + }; + final TestAssetBundle testAssetBundle = TestAssetBundle(assetManifest); final AssetImage assetImage = AssetImage( mainAssetPath, diff --git a/packages/flutter/test/painting/image_stream_test.dart b/packages/flutter/test/painting/image_stream_test.dart index 0f376c63ea99c..0c9bc32f606ef 100644 --- a/packages/flutter/test/painting/image_stream_test.dart +++ b/packages/flutter/test/painting/image_stream_test.dart @@ -26,8 +26,6 @@ class FakeFrameInfo implements FrameInfo { @override Image get image => _image; - int get imageHandleCount => image.debugGetOpenHandleStackTraces()!.length; - FakeFrameInfo clone() { return FakeFrameInfo( _duration, diff --git a/packages/flutter/test/painting/shape_decoration_test.dart b/packages/flutter/test/painting/shape_decoration_test.dart index 9c6adf10dacb9..402aa70c3b6e1 100644 --- a/packages/flutter/test/painting/shape_decoration_test.dart +++ b/packages/flutter/test/painting/shape_decoration_test.dart @@ -48,6 +48,14 @@ void main() { expect(identical(ShapeDecoration.lerp(shape, shape, 0.5), shape), true); }); + test('ShapeDecoration.lerp null a,b', () { + const Decoration a = ShapeDecoration(shape: CircleBorder()); + const Decoration b = ShapeDecoration(shape: RoundedRectangleBorder()); + expect(Decoration.lerp(a, null, 0.0), a); + expect(Decoration.lerp(null, b, 0.0), b); + expect(Decoration.lerp(null, null, 0.0), null); + }); + test('ShapeDecoration.lerp and hit test', () { const Decoration a = ShapeDecoration(shape: CircleBorder()); const Decoration b = ShapeDecoration(shape: RoundedRectangleBorder()); diff --git a/packages/flutter/test/painting/system_fonts_test.dart b/packages/flutter/test/painting/system_fonts_test.dart index f8da1d010d4d0..480e6517c56ae 100644 --- a/packages/flutter/test/painting/system_fonts_test.dart +++ b/packages/flutter/test/painting/system_fonts_test.dart @@ -180,11 +180,11 @@ void main() { ); // Metrics should be refreshed // ignore: avoid_dynamic_calls - expect(state.numberLabelWidth - 46.0 < precisionErrorTolerance, isTrue); + expect(state.numberLabelWidth, lessThan(46.0 + precisionErrorTolerance)); // ignore: avoid_dynamic_calls - expect(state.numberLabelHeight - 23.0 < precisionErrorTolerance, isTrue); + expect(state.numberLabelHeight, lessThan(23.0 + precisionErrorTolerance)); // ignore: avoid_dynamic_calls - expect(state.numberLabelBaseline - 18.400070190429688 < precisionErrorTolerance, isTrue); + expect(state.numberLabelBaseline, lessThan(18.400070190429688 + precisionErrorTolerance)); final Element element = tester.element(find.byType(CupertinoTimerPicker)); expect(element.dirty, isTrue); }, skip: isBrowser); // TODO(yjbanov): cupertino does not work on the Web yet: https://github.com/flutter/flutter/issues/41920 diff --git a/packages/flutter/test/painting/text_painter_test.dart b/packages/flutter/test/painting/text_painter_test.dart index e39e597707348..57b6d63023597 100644 --- a/packages/flutter/test/painting/text_painter_test.dart +++ b/packages/flutter/test/painting/text_painter_test.dart @@ -1253,34 +1253,34 @@ void main() { ..text = const TextSpan(text: 'TEXT') ..textDirection = TextDirection.ltr; - FlutterError? exception; - try { - painter.getPositionForOffset(Offset.zero); - } on FlutterError catch (e) { - exception = e; - } - expect(exception?.message, contains('The TextPainter has never been laid out.')); - exception = null; - - try { - painter.layout(); - painter.getPositionForOffset(Offset.zero); - } on FlutterError catch (e) { - exception = e; - } - - expect(exception, isNull); - exception = null; + expect( + () => painter.getPositionForOffset(Offset.zero), + throwsA(isA<FlutterError>().having( + (FlutterError error) => error.message, + 'message', + contains('The TextPainter has never been laid out.'), + )), + ); - try { - painter.markNeedsLayout(); - painter.getPositionForOffset(Offset.zero); - } on FlutterError catch (e) { - exception = e; - } + expect( + () { + painter.layout(); + painter.getPositionForOffset(Offset.zero); + }, + returnsNormally, + ); - expect(exception?.message, contains('The calls that first invalidated the text layout were:')); - exception = null; + expect( + () { + painter.markNeedsLayout(); + painter.getPositionForOffset(Offset.zero); + }, + throwsA(isA<FlutterError>().having( + (FlutterError error) => error.message, + 'message', + contains('The calls that first invalidated the text layout were:'), + )), + ); painter.dispose(); }); @@ -1507,6 +1507,27 @@ void main() { painter.dispose(); }); + + test('TextPainter line breaking does not round to integers', () { + if (! const bool.hasEnvironment('SKPARAGRAPH_REMOVE_ROUNDING_HACK')) { + return; + } + const double fontSize = 1.25; + const String text = '12345'; + assert((fontSize * text.length).truncate() != fontSize * text.length); + final TextPainter painter = TextPainter( + textDirection: TextDirection.ltr, + text: const TextSpan(text: text, style: TextStyle(fontSize: fontSize)), + )..layout(maxWidth: text.length * fontSize); + + expect(painter.maxIntrinsicWidth, text.length * fontSize); + switch (painter.computeLineMetrics()) { + case [ui.LineMetrics(width: final double width)]: + expect(width, text.length * fontSize); + case final List<ui.LineMetrics> metrics: + expect(metrics, hasLength(1)); + } + }, skip: kIsWeb && !isCanvasKit); // [intended] Browsers seem to always round font/glyph metrics. } class MockCanvas extends Fake implements Canvas { diff --git a/packages/flutter/test/rendering/cached_intrinsics_test.dart b/packages/flutter/test/rendering/cached_intrinsics_test.dart index 756a4967d54dc..bc42ef20c4c0e 100644 --- a/packages/flutter/test/rendering/cached_intrinsics_test.dart +++ b/packages/flutter/test/rendering/cached_intrinsics_test.dart @@ -118,7 +118,7 @@ void main() { expect(parentData!.offset.dy, -(viewHeight / 2.0)); expect(test.calls, 2); // The layout constraints change will clear the cached data. - final RenderObject parent = test.parent! as RenderObject; + final RenderObject parent = test.parent!; expect(parent.debugNeedsLayout, false); // Do not forget notify parent dirty after the cached data be cleared by `layout()` diff --git a/packages/flutter/test/rendering/editable_test.dart b/packages/flutter/test/rendering/editable_test.dart index dc2be0886fed3..0a31aa54e15ab 100644 --- a/packages/flutter/test/rendering/editable_test.dart +++ b/packages/flutter/test/rendering/editable_test.dart @@ -13,6 +13,25 @@ import 'mock_canvas.dart'; import 'recording_canvas.dart'; import 'rendering_tester.dart'; +void _applyParentData(List<RenderBox> inlineRenderBoxes, InlineSpan span) { + int index = 0; + RenderBox? previousBox; + span.visitChildren((InlineSpan span) { + if (span is! WidgetSpan) { + return true; + } + + final RenderBox box = inlineRenderBoxes[index]; + box.parentData = TextParentData() + ..span = span + ..previousSibling = previousBox; + (previousBox?.parentData as TextParentData?)?.nextSibling = box; + index += 1; + previousBox = box; + return true; + }); +} + class _FakeEditableTextState with TextSelectionDelegate { @override TextEditingValue textEditingValue = TextEditingValue.empty; @@ -987,7 +1006,7 @@ void main() { editable.painter = null; editable.paintCount = 0; - final AbstractNode? parent = editable.parent; + final RenderObject? parent = editable.parent; if (parent is RenderConstrainedBox) { parent.child = null; } @@ -1327,7 +1346,7 @@ void main() { selection: const TextSelection.collapsed(offset: 3), children: renderBoxes, ); - + _applyParentData(renderBoxes, editable.text!); layout(editable); editable.hasFocus = true; pumpFrame(); @@ -1370,6 +1389,7 @@ void main() { children: renderBoxes, ); + _applyParentData(renderBoxes, editable.text!); layout(editable); editable.hasFocus = true; pumpFrame(); @@ -1415,6 +1435,7 @@ void main() { ); // Force a line wrap + _applyParentData(renderBoxes, editable.text!); layout(editable, constraints: const BoxConstraints(maxWidth: 75)); editable.hasFocus = true; pumpFrame(); @@ -1465,6 +1486,7 @@ void main() { ); // Force a line wrap + _applyParentData(renderBoxes, editable.text!); layout(editable, constraints: const BoxConstraints(maxWidth: 75)); editable.hasFocus = true; pumpFrame(); @@ -1520,6 +1542,7 @@ void main() { children: renderBoxes, ); + _applyParentData(renderBoxes, editable.text!); // Force a line wrap layout(editable, constraints: const BoxConstraints(maxWidth: 75)); editable.hasFocus = true; @@ -1535,6 +1558,45 @@ void main() { expect(composingRect, null); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/61021 + test('WidgetSpan render box is painted at correct offset when scrolled', () async { + final TextSelectionDelegate delegate = _FakeEditableTextState() + ..textEditingValue = const TextEditingValue( + text: 'test', + selection: TextSelection.collapsed(offset: 3), + ); + final List<RenderBox> renderBoxes = <RenderBox>[ + RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr), + ]; + final ViewportOffset viewportOffset = ViewportOffset.fixed(100.0); + final RenderEditable editable = RenderEditable( + backgroundCursorColor: Colors.grey, + selectionColor: Colors.black, + textDirection: TextDirection.ltr, + cursorColor: Colors.red, + offset: viewportOffset, + textSelectionDelegate: delegate, + startHandleLayerLink: LayerLink(), + endHandleLayerLink: LayerLink(), + maxLines: null, + text: TextSpan( + style: const TextStyle(height: 1.0, fontSize: 10.0), + children: <InlineSpan>[ + const TextSpan(text: 'test'), + WidgetSpan(child: Container(width: 10, height: 10, color: Colors.blue)), + ], + ), + selection: const TextSelection.collapsed(offset: 3), + children: renderBoxes, + ); + _applyParentData(renderBoxes, editable.text!); + layout(editable); + editable.hasFocus = true; + pumpFrame(); + + final Rect composingRect = editable.getRectForComposingRange(const TextRange(start: 4, end: 5))!; + expect(composingRect, const Rect.fromLTRB(40.0, -100.0, 54.0, -86.0)); + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/61021 + test('can compute IntrinsicWidth for WidgetSpans', () { // Regression test for https://github.com/flutter/flutter/issues/59316 const double screenWidth = 1000.0; @@ -1554,6 +1616,7 @@ void main() { selectionColor: Colors.black, textDirection: TextDirection.ltr, cursorColor: Colors.red, + cursorWidth: 0.0, offset: viewportOffset, textSelectionDelegate: delegate, startHandleLayerLink: LayerLink(), @@ -1571,12 +1634,12 @@ void main() { textScaleFactor: 2.0, children: renderBoxes, ); + _applyParentData(renderBoxes, editable.text!); layout(editable, constraints: const BoxConstraints(maxWidth: screenWidth)); - editable.hasFocus = true; - final double maxIntrinsicWidth = editable.computeMaxIntrinsicWidth(fixedHeight); - pumpFrame(); - - expect(maxIntrinsicWidth, 278); + expect(editable.computeMaxIntrinsicWidth(fixedHeight), + 2.0 * 10.0 * 4 + 14.0 * 7 + 1.0, + reason: "intrinsic width = scale factor * width of 'test' + width of 'one two' + _caretMargin", + ); }); test('hits correct WidgetSpan when not scrolled', () { @@ -1613,6 +1676,7 @@ void main() { ), children: renderBoxes, ); + _applyParentData(renderBoxes, editable.text!); layout(editable, constraints: BoxConstraints.loose(const Size(500.0, 500.0))); // Prepare for painting after layout. pumpFrame(phase: EnginePhase.compositingBits); @@ -1759,6 +1823,52 @@ void main() { rrect: expectedRRect )); }); + + test('getWordAtOffset with a negative position', () { + const String text = 'abc'; + final _FakeEditableTextState delegate = _FakeEditableTextState() + ..textEditingValue = const TextEditingValue(text: text); + final ViewportOffset viewportOffset = ViewportOffset.zero(); + final RenderEditable editable = RenderEditable( + backgroundCursorColor: Colors.grey, + selectionColor: Colors.black, + textDirection: TextDirection.ltr, + cursorColor: Colors.red, + offset: viewportOffset, + textSelectionDelegate: delegate, + startHandleLayerLink: LayerLink(), + endHandleLayerLink: LayerLink(), + text: const TextSpan( + text: text, + style: TextStyle(height: 1.0, fontSize: 10.0), + ), + ); + + layout(editable, onErrors: expectNoFlutterErrors); + + // Cause text metrics to be computed. + editable.computeDistanceToActualBaseline(TextBaseline.alphabetic); + + final TextSelection selection; + try { + selection = editable.getWordAtOffset(const TextPosition( + offset: -1, + affinity: TextAffinity.upstream, + )); + } catch (error) { + // In debug mode, negative offsets are caught by an assertion. + expect(error, isA<AssertionError>()); + return; + } + + // Web's Paragraph.getWordBoundary behaves differently for a negative + // position. + if (kIsWeb) { + expect(selection, const TextSelection.collapsed(offset: 0)); + } else { + expect(selection, const TextSelection.collapsed(offset: text.length)); + } + }); } class _TestRenderEditable extends RenderEditable { diff --git a/packages/flutter/test/rendering/image_test.dart b/packages/flutter/test/rendering/image_test.dart index 85737f632b9f2..49da81bdfbcb0 100644 --- a/packages/flutter/test/rendering/image_test.dart +++ b/packages/flutter/test/rendering/image_test.dart @@ -200,7 +200,8 @@ Future<void> main() async { final RenderImage renderImage = RenderImage(image: image.clone()); expect(image.debugGetOpenHandleStackTraces()!.length, 2); - renderImage.image = renderImage.image; + // Testing short-circuit logic of setter. + renderImage.image = renderImage.image; // ignore: no_self_assignments expect(image.debugGetOpenHandleStackTraces()!.length, 2); renderImage.image = null; diff --git a/packages/flutter/test/rendering/layers_test.dart b/packages/flutter/test/rendering/layers_test.dart index cc95ace405455..04d7404d4f3a6 100644 --- a/packages/flutter/test/rendering/layers_test.dart +++ b/packages/flutter/test/rendering/layers_test.dart @@ -899,8 +899,7 @@ void main() { expect(() => layer.markNeedsAddToScene(), throwsAssertionError); expect(() => layer.debugMarkClean(), throwsAssertionError); expect(() => layer.updateSubtreeNeedsAddToScene(), throwsAssertionError); - expect(() => layer.dropChild(ContainerLayer()), throwsAssertionError); - expect(() => layer.adoptChild(ContainerLayer()), throwsAssertionError); + expect(() => layer.remove(), throwsAssertionError); expect(() => (layer as ContainerLayer).append(ContainerLayer()), throwsAssertionError); expect(() => layer.engineLayer = null, throwsAssertionError); compositedB1 = true; diff --git a/packages/flutter/test/rendering/localized_fonts_test.dart b/packages/flutter/test/rendering/localized_fonts_test.dart index b1cbc5eb12299..b7ff7be2c52ad 100644 --- a/packages/flutter/test/rendering/localized_fonts_test.dart +++ b/packages/flutter/test/rendering/localized_fonts_test.dart @@ -15,11 +15,12 @@ import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets( - 'RichText TextSpan styles with different locales', + 'Material2 - RichText TextSpan styles with different locales', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), supportedLocales: const <Locale>[ Locale('en', 'US'), Locale('ja'), @@ -54,16 +55,63 @@ void main() { await expectLater( find.byType(RichText), - matchesGoldenFile('localized_fonts.rich_text.styled_text_span.png'), + matchesGoldenFile('m2_localized_fonts.rich_text.styled_text_span.png'), ); }, ); testWidgets( - 'Text with locale-specific glyphs, ambient locale', + 'Material3 - RichText TextSpan styles with different locales', + (WidgetTester tester) async { + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: true), + supportedLocales: const <Locale>[ + Locale('en', 'US'), + Locale('ja'), + Locale('zh'), + ], + home: Builder( + builder: (BuildContext context) { + const String character = '骨'; + final TextStyle style = Theme.of(context).textTheme.displayMedium!; + return Scaffold( + body: Container( + padding: const EdgeInsets.all(48.0), + alignment: Alignment.center, + child: RepaintBoundary( + // Expected result can be seen here: + // https://user-images.githubusercontent.com/1377460/40503473-faad6f34-5f42-11e8-972b-d83b727c9d0e.png + child: RichText( + text: TextSpan( + children: <TextSpan>[ + TextSpan(text: character, style: style.copyWith(locale: const Locale('ja'))), + TextSpan(text: character, style: style.copyWith(locale: const Locale('zh'))), + ], + ), + ), + ), + ), + ); + }, + ), + ), + ); + + await expectLater( + find.byType(RichText), + matchesGoldenFile('m3_localized_fonts.rich_text.styled_text_span.png'), + ); + }, + ); + + testWidgets( + 'Material2 - Text with locale-specific glyphs, ambient locale', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), supportedLocales: const <Locale>[ Locale('en', 'US'), Locale('ja'), @@ -111,10 +159,63 @@ void main() { ); testWidgets( - 'Text with locale-specific glyphs, explicit locale', + 'Material3 - Text with locale-specific glyphs, ambient locale', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: true), + supportedLocales: const <Locale>[ + Locale('en', 'US'), + Locale('ja'), + Locale('zh'), + ], + home: Builder( + builder: (BuildContext context) { + const String character = '骨'; + final TextStyle style = Theme.of(context).textTheme.displayMedium!; + return Scaffold( + body: Container( + padding: const EdgeInsets.all(48.0), + alignment: Alignment.center, + child: RepaintBoundary( + // Expected result can be seen here: + // https://user-images.githubusercontent.com/1377460/40503473-faad6f34-5f42-11e8-972b-d83b727c9d0e.png + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: <Widget>[ + Localizations.override( + context: context, + locale: const Locale('ja'), + child: Text(character, style: style), + ), + Localizations.override( + context: context, + locale: const Locale('zh'), + child: Text(character, style: style), + ), + ], + ), + ), + ), + ); + }, + ), + ), + ); + + await expectLater( + find.byType(Row), + matchesGoldenFile('m3_localized_fonts.text_ambient_locale.chars.png'), + ); + }, + ); + + testWidgets( + 'Material2 - Text with locale-specific glyphs, explicit locale', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), supportedLocales: const <Locale>[ Locale('en', 'US'), Locale('ja'), @@ -148,9 +249,52 @@ void main() { await expectLater( find.byType(Row), - matchesGoldenFile('localized_fonts.text_explicit_locale.chars.png'), + matchesGoldenFile('m2_localized_fonts.text_explicit_locale.chars.png'), ); }, ); + testWidgets( + 'Material3 - Text with locale-specific glyphs, explicit locale', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: true), + supportedLocales: const <Locale>[ + Locale('en', 'US'), + Locale('ja'), + Locale('zh'), + ], + home: Builder( + builder: (BuildContext context) { + const String character = '骨'; + final TextStyle style = Theme.of(context).textTheme.displayMedium!; + return Scaffold( + body: Container( + padding: const EdgeInsets.all(48.0), + alignment: Alignment.center, + child: RepaintBoundary( + // Expected result can be seen here: + // https://user-images.githubusercontent.com/1377460/40503473-faad6f34-5f42-11e8-972b-d83b727c9d0e.png + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: <Widget>[ + Text(character, style: style, locale: const Locale('ja')), + Text(character, style: style, locale: const Locale('zh')), + ], + ), + ), + ), + ); + }, + ), + ), + ); + + await expectLater( + find.byType(Row), + matchesGoldenFile('m3_localized_fonts.text_explicit_locale.chars.png'), + ); + }, + ); } diff --git a/packages/flutter/test/rendering/mouse_tracker_test_utils.dart b/packages/flutter/test/rendering/mouse_tracker_test_utils.dart index de07b3e2b2ed8..1f9c5bfe052dc 100644 --- a/packages/flutter/test/rendering/mouse_tracker_test_utils.dart +++ b/packages/flutter/test/rendering/mouse_tracker_test_utils.dart @@ -48,7 +48,7 @@ class TestMouseTrackerFlutterBinding extends BindingBase final SchedulerPhase? lastPhase = _overridePhase; _overridePhase = SchedulerPhase.persistentCallbacks; addPostFrameCallback((_) { - mouseTracker.updateAllDevices(renderView.hitTestMouseTrackers); + mouseTracker.updateAllDevices(); }); _overridePhase = lastPhase; } diff --git a/packages/flutter/test/rendering/non_normalized_constraints_test.dart b/packages/flutter/test/rendering/non_normalized_constraints_test.dart new file mode 100644 index 0000000000000..b57b69b3035e9 --- /dev/null +++ b/packages/flutter/test/rendering/non_normalized_constraints_test.dart @@ -0,0 +1,43 @@ +// 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. + +// THIS TEST IS SENSITIVE TO LINE NUMBERS AT THE TOP OF THIS FILE + +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class RenderFoo extends RenderShiftedBox { + RenderFoo({ RenderBox? child }) : super(child); + + @override + void performLayout() { + child?.layout(const BoxConstraints( // THIS MUST BE LINE 17 + minWidth: 100.0, maxWidth: 50.0, + )); + } +} + +class Foo extends SingleChildRenderObjectWidget { + const Foo({ super.key, super.child }); + + @override + RenderFoo createRenderObject(BuildContext context) { + return RenderFoo(); + } +} + +// END OF SENSITIVE SECTION + +void main() { + testWidgets('Stack parsing in non-normalized constraints error', (WidgetTester tester) async { + await tester.pumpWidget(const Foo(child: Placeholder()), Duration.zero, EnginePhase.layout); + final Object? exception = tester.takeException(); + final String text = exception.toString(); + expect(text, contains('BoxConstraints has non-normalized width constraints.')); + expect(text, contains('which probably computed the invalid constraints in question:\n RenderFoo.performLayout (')); + expect(text, contains('non_normalized_constraints_test.dart:17:12')); + }, skip: kIsWeb); // [intended] stack traces on web are insufficiently predictable +} diff --git a/packages/flutter/test/rendering/non_render_object_root_test.dart b/packages/flutter/test/rendering/non_render_object_root_test.dart deleted file mode 100644 index f5d33d3b25ac5..0000000000000 --- a/packages/flutter/test/rendering/non_render_object_root_test.dart +++ /dev/null @@ -1,61 +0,0 @@ -// 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/foundation.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'rendering_tester.dart'; - -class RealRoot extends AbstractNode { - RealRoot(this.child) { - adoptChild(child); - } - - final RenderObject child; - - @override - void redepthChildren() { - redepthChild(child); - } - - @override - void attach(Object owner) { - super.attach(owner); - child.attach(owner as PipelineOwner); - } - - @override - void detach() { - super.detach(); - child.detach(); - } - - @override - PipelineOwner? get owner => super.owner as PipelineOwner?; - - void layout() { - child.layout(BoxConstraints.tight(const Size(500.0, 500.0))); - } -} - -void main() { - TestRenderingFlutterBinding.ensureInitialized(); - - test('non-RenderObject roots', () { - RenderPositionedBox child; - final RealRoot root = RealRoot( - child = RenderPositionedBox( - child: RenderSizedBox(const Size(100.0, 100.0)), - ), - ); - root.attach(PipelineOwner()); - - child.scheduleInitialLayout(); - root.layout(); - - child.markNeedsLayout(); - root.layout(); - }); -} diff --git a/packages/flutter/test/rendering/paragraph_test.dart b/packages/flutter/test/rendering/paragraph_test.dart index 04d8b5760aa69..8ae7c59ec126e 100644 --- a/packages/flutter/test/rendering/paragraph_test.dart +++ b/packages/flutter/test/rendering/paragraph_test.dart @@ -14,6 +14,25 @@ import 'rendering_tester.dart'; const String _kText = "I polished up that handle so carefullee\nThat now I am the Ruler of the Queen's Navee!"; +void _applyParentData(List<RenderBox> inlineRenderBoxes, InlineSpan span) { + int index = 0; + RenderBox? previousBox; + span.visitChildren((InlineSpan span) { + if (span is! WidgetSpan) { + return true; + } + + final RenderBox box = inlineRenderBoxes[index]; + box.parentData = TextParentData() + ..span = span + ..previousSibling = previousBox; + (previousBox?.parentData as TextParentData?)?.nextSibling = box; + index += 1; + previousBox = box; + return true; + }); +} + // A subclass of RenderParagraph that returns an empty list in getBoxesForSelection // for a given TextSelection. // This is intended to simulate SkParagraph's implementation of Paragraph.getBoxesForRange, @@ -504,6 +523,7 @@ void main() { textDirection: TextDirection.ltr, children: renderBoxes, ); + _applyParentData(renderBoxes, text); layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0)); final List<ui.TextBox> boxes = paragraph.getBoxesForSelection( @@ -544,6 +564,7 @@ void main() { textDirection: TextDirection.ltr, children: renderBoxes, ); + _applyParentData(renderBoxes, text); layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0)); final List<ui.TextBox> boxes = paragraph.getBoxesForSelection( @@ -559,91 +580,6 @@ void main() { expect(boxes[4], const TextBox.fromLTRBD(48.0, 0.0, 62.0, 14.0, TextDirection.ltr)); }, skip: isBrowser); // https://github.com/flutter/flutter/issues/61020 - test('can compute IntrinsicHeight for widget span', () { - // Regression test for https://github.com/flutter/flutter/issues/59316 - const double screenWidth = 100.0; - const String sentence = 'one two'; - List<RenderBox> renderBoxes = <RenderBox>[ - RenderParagraph(const TextSpan(text: sentence), textDirection: TextDirection.ltr), - ]; - RenderParagraph paragraph = RenderParagraph( - const TextSpan( - children: <InlineSpan> [ - WidgetSpan(child: Text(sentence)), - ], - ), - children: renderBoxes, - textDirection: TextDirection.ltr, - ); - layout(paragraph, constraints: const BoxConstraints(maxWidth: screenWidth)); - final double singleLineHeight = paragraph.computeMaxIntrinsicHeight(screenWidth); - expect(singleLineHeight, 14.0); - - pumpFrame(); - renderBoxes = <RenderBox>[ - RenderParagraph(const TextSpan(text: sentence), textDirection: TextDirection.ltr), - ]; - paragraph = RenderParagraph( - const TextSpan( - children: <InlineSpan> [ - WidgetSpan(child: Text(sentence)), - ], - ), - textScaleFactor: 2.0, - children: renderBoxes, - textDirection: TextDirection.ltr, - ); - - layout(paragraph, constraints: const BoxConstraints(maxWidth: screenWidth)); - final double maxIntrinsicHeight = paragraph.computeMaxIntrinsicHeight(screenWidth); - final double minIntrinsicHeight = paragraph.computeMinIntrinsicHeight(screenWidth); - // intrinsicHeight = singleLineHeight * textScaleFactor * two lines. - expect(maxIntrinsicHeight, singleLineHeight * 2.0 * 2); - expect(maxIntrinsicHeight, minIntrinsicHeight); - }); - - test('can compute IntrinsicWidth for widget span', () { - // Regression test for https://github.com/flutter/flutter/issues/59316 - const double screenWidth = 1000.0; - const double fixedHeight = 1000.0; - const String sentence = 'one two'; - List<RenderBox> renderBoxes = <RenderBox>[ - RenderParagraph(const TextSpan(text: sentence), textDirection: TextDirection.ltr), - ]; - RenderParagraph paragraph = RenderParagraph( - const TextSpan( - children: <InlineSpan> [ - WidgetSpan(child: Text(sentence)), - ], - ), - children: renderBoxes, - textDirection: TextDirection.ltr, - ); - layout(paragraph, constraints: const BoxConstraints(maxWidth: screenWidth)); - final double widthForOneLine = paragraph.computeMaxIntrinsicWidth(fixedHeight); - expect(widthForOneLine, 98.0); - - pumpFrame(); - renderBoxes = <RenderBox>[ - RenderParagraph(const TextSpan(text: sentence), textDirection: TextDirection.ltr), - ]; - paragraph = RenderParagraph( - const TextSpan( - children: <InlineSpan> [ - WidgetSpan(child: Text(sentence)), - ], - ), - textScaleFactor: 2.0, - children: renderBoxes, - textDirection: TextDirection.ltr, - ); - - layout(paragraph, constraints: const BoxConstraints(maxWidth: screenWidth)); - final double maxIntrinsicWidth = paragraph.computeMaxIntrinsicWidth(fixedHeight); - // maxIntrinsicWidth = widthForOneLine * textScaleFactor - expect(maxIntrinsicWidth, widthForOneLine * 2.0); - }); - test('inline widgets multiline test', () { const TextSpan text = TextSpan( text: 'a', @@ -676,6 +612,7 @@ void main() { textDirection: TextDirection.ltr, children: renderBoxes, ); + _applyParentData(renderBoxes, text); layout(paragraph, constraints: const BoxConstraints(maxWidth: 50.0)); final List<ui.TextBox> boxes = paragraph.getBoxesForSelection( @@ -715,6 +652,7 @@ void main() { children: renderBoxes, textDirection: TextDirection.ltr, ); + _applyParentData(renderBoxes, paragraph.text); layout(paragraph, constraints: const BoxConstraints(maxWidth: screenWidth)); final SemanticsNode result = SemanticsNode(); final SemanticsNode truncatedChild = SemanticsNode(); @@ -815,6 +753,7 @@ void main() { children: renderBoxes, textDirection: TextDirection.ltr, ); + _applyParentData(renderBoxes, paragraph.text); layout(paragraph); final SemanticsNode node = SemanticsNode(); @@ -866,10 +805,14 @@ void main() { expect(paintingContext.canvas.drawnRect, isNull); expect(paintingContext.canvas.drawnRectPaint, isNull); selectionParagraph(paragraph, const TextPosition(offset: 1), const TextPosition(offset: 5)); + + paintingContext.canvas.clear(); paragraph.paint(paintingContext, Offset.zero); expect(paintingContext.canvas.drawnRect, const Rect.fromLTWH(14.0, 0.0, 56.0, 14.0)); expect(paintingContext.canvas.drawnRectPaint!.style, PaintingStyle.fill); expect(paintingContext.canvas.drawnRectPaint!.color, selectionColor); + // Selection highlight is painted before text. + expect(paintingContext.canvas.drawnItemTypes, <Type>[Rect, ui.Paragraph]); selectionParagraph(paragraph, const TextPosition(offset: 2), const TextPosition(offset: 4)); paragraph.paint(paintingContext, Offset.zero); @@ -878,6 +821,33 @@ void main() { expect(paintingContext.canvas.drawnRectPaint!.color, selectionColor); }); +// Regression test for https://github.com/flutter/flutter/issues/126652. + test('paints selection when tap at chinese character', () async { + final TestSelectionRegistrar registrar = TestSelectionRegistrar(); + const Color selectionColor = Color(0xAF6694e8); + final RenderParagraph paragraph = RenderParagraph( + const TextSpan(text: '你好'), + textDirection: TextDirection.ltr, + registrar: registrar, + selectionColor: selectionColor, + ); + layout(paragraph); + final MockPaintingContext paintingContext = MockPaintingContext(); + paragraph.paint(paintingContext, Offset.zero); + expect(paintingContext.canvas.drawnRect, isNull); + expect(paintingContext.canvas.drawnRectPaint, isNull); + + for (final Selectable selectable in (paragraph.registrar! as TestSelectionRegistrar).selectables) { + selectable.dispatchSelectionEvent(const SelectWordSelectionEvent(globalPosition: Offset(7, 0))); + } + + paintingContext.canvas.clear(); + paragraph.paint(paintingContext, Offset.zero); + expect(paintingContext.canvas.drawnRect!.isEmpty, false); + expect(paintingContext.canvas.drawnRectPaint!.style, PaintingStyle.fill); + expect(paintingContext.canvas.drawnRectPaint!.color, selectionColor); + }, skip: isBrowser); // https://github.com/flutter/flutter/issues/61016 + test('getPositionForOffset works', () async { final RenderParagraph paragraph = RenderParagraph(const TextSpan(text: '1234567'), textDirection: TextDirection.ltr); layout(paragraph); @@ -901,6 +871,7 @@ void main() { registrar: registrar, children: renderBoxes, ); + _applyParentData(renderBoxes, paragraph.text); layout(paragraph); // The widget span will register to the selection container without going // through the render paragraph. @@ -1353,15 +1324,24 @@ void main() { class MockCanvas extends Fake implements Canvas { Rect? drawnRect; Paint? drawnRectPaint; + List<Type> drawnItemTypes=<Type>[]; @override void drawRect(Rect rect, Paint paint) { drawnRect = rect; drawnRectPaint = paint; + drawnItemTypes.add(Rect); } @override - void drawParagraph(ui.Paragraph paragraph, Offset offset) { } + void drawParagraph(ui.Paragraph paragraph, Offset offset) { + drawnItemTypes.add(ui.Paragraph); + } + void clear() { + drawnRect = null; + drawnRectPaint = null; + drawnItemTypes.clear(); + } } class MockPaintingContext extends Fake implements PaintingContext { diff --git a/packages/flutter/test/rendering/proxy_box_test.dart b/packages/flutter/test/rendering/proxy_box_test.dart index 13991f1ae714d..38fe9a0a0b47a 100644 --- a/packages/flutter/test/rendering/proxy_box_test.dart +++ b/packages/flutter/test/rendering/proxy_box_test.dart @@ -803,10 +803,10 @@ void main() { }); test('Offstage implements paintsChild correctly', () { - final RenderBox box = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 20)); - final RenderBox parent = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 20)); + final RenderConstrainedBox box = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 20)); + final RenderConstrainedBox parent = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 20)); final RenderOffstage offstage = RenderOffstage(offstage: false, child: box); - parent.adoptChild(offstage); + parent.child = offstage; expect(offstage.paintsChild(box), true); @@ -817,9 +817,7 @@ void main() { test('Opacity implements paintsChild correctly', () { final RenderBox box = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 20)); - final RenderBox parent = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 20)); final RenderOpacity opacity = RenderOpacity(child: box); - parent.adoptChild(opacity); expect(opacity.paintsChild(box), true); @@ -830,10 +828,8 @@ void main() { test('AnimatedOpacity sets paint matrix to zero when alpha == 0', () { final RenderBox box = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 20)); - final RenderBox parent = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 20)); final AnimationController opacityAnimation = AnimationController(value: 1, vsync: FakeTickerProvider()); final RenderAnimatedOpacity opacity = RenderAnimatedOpacity(opacity: opacityAnimation, child: box); - parent.adoptChild(opacity); // Make it listen to the animation. opacity.attach(PipelineOwner()); @@ -847,10 +843,8 @@ void main() { test('AnimatedOpacity sets paint matrix to zero when alpha == 0 (sliver)', () { final RenderSliver sliver = RenderSliverToBoxAdapter(child: RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 20))); - final RenderBox parent = RenderConstrainedBox(additionalConstraints: const BoxConstraints.tightFor(width: 20)); final AnimationController opacityAnimation = AnimationController(value: 1, vsync: FakeTickerProvider()); final RenderSliverAnimatedOpacity opacity = RenderSliverAnimatedOpacity(opacity: opacityAnimation, sliver: sliver); - parent.adoptChild(opacity); // Make it listen to the animation. opacity.attach(PipelineOwner()); diff --git a/packages/flutter/test/rendering/simple_semantics_test.dart b/packages/flutter/test/rendering/simple_semantics_test.dart index 797ac93a2a2d8..fcfbb3771a493 100644 --- a/packages/flutter/test/rendering/simple_semantics_test.dart +++ b/packages/flutter/test/rendering/simple_semantics_test.dart @@ -7,7 +7,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'rendering_tester.dart'; - void main() { TestRenderingFlutterBinding.ensureInitialized(); diff --git a/packages/flutter/test/rendering/view_test.dart b/packages/flutter/test/rendering/view_test.dart index 2af8acd1378a2..a3367a691fbef 100644 --- a/packages/flutter/test/rendering/view_test.dart +++ b/packages/flutter/test/rendering/view_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'mock_canvas.dart'; import 'rendering_tester.dart'; void main() { @@ -69,4 +70,76 @@ void main() { expect(viewConfigurationA.hashCode, viewConfigurationB.hashCode); expect(viewConfigurationA.hashCode != viewConfigurationC.hashCode, true); }); + + test('invokes DebugPaintCallback', () { + final PaintPattern paintsOrangeRect = paints..rect( + color: orange, + rect: orangeRect, + ); + final PaintPattern paintsGreenRect = paints..rect( + color: green, + rect: greenRect, + ); + final PaintPattern paintOrangeAndGreenRect = paints + ..rect( + color: orange, + rect: orangeRect, + ) + ..rect( + color: green, + rect: greenRect, + ); + void paintCallback(PaintingContext context, Offset offset, RenderView renderView) { + context.canvas.drawRect( + greenRect, + Paint()..color = green, + ); + } + + layout(TestRenderObject()); + expect( + TestRenderingFlutterBinding.instance.renderView, + paintsOrangeRect, + ); + expect( + TestRenderingFlutterBinding.instance.renderView, + isNot(paintsGreenRect), + ); + + RenderView.debugAddPaintCallback(paintCallback); + expect( + TestRenderingFlutterBinding.instance.renderView, + paintOrangeAndGreenRect, + ); + + RenderView.debugRemovePaintCallback(paintCallback); + expect( + TestRenderingFlutterBinding.instance.renderView, + paintsOrangeRect, + ); + expect( + TestRenderingFlutterBinding.instance.renderView, + isNot(paintsGreenRect), + ); + }); +} + +const Color orange = Color(0xFFFF9000); +const Color green = Color(0xFF0FF900); +const Rect orangeRect = Rect.fromLTWH(10, 10, 50, 75); +const Rect greenRect = Rect.fromLTWH(20, 20, 100, 150); + +class TestRenderObject extends RenderBox { + @override + void performLayout() { + size = constraints.biggest; + } + + @override + void paint(PaintingContext context, Offset offset) { + context.canvas.drawRect( + orangeRect, + Paint()..color = orange, + ); + } } diff --git a/packages/flutter/test/semantics/semantics_owner_test.dart b/packages/flutter/test/semantics/semantics_owner_test.dart index 4b87fa60eb07a..18c93f5eb62ed 100644 --- a/packages/flutter/test/semantics/semantics_owner_test.dart +++ b/packages/flutter/test/semantics/semantics_owner_test.dart @@ -42,6 +42,7 @@ void main() { tester.binding.performSemanticsAction(SemanticsActionEvent( type: SemanticsAction.showOnScreen, nodeId: nodeId, + viewId: tester.view.viewId, )); semantics.dispose(); }); diff --git a/packages/flutter/test/semantics/semantics_update_test.dart b/packages/flutter/test/semantics/semantics_update_test.dart index 8fffef80ca91d..e5a41228b13ed 100644 --- a/packages/flutter/test/semantics/semantics_update_test.dart +++ b/packages/flutter/test/semantics/semantics_update_test.dart @@ -218,37 +218,13 @@ class SemanticsUpdateBuilderSpy extends Fake implements ui.SemanticsUpdateBuilde // Makes sure we don't send the same id twice. assert(!observations.containsKey(id)); observations[id] = SemanticsNodeUpdateObservation( - id: id, - flags: flags, - actions: actions, - maxValueLength: maxValueLength, - currentValueLength: currentValueLength, - textSelectionBase: textSelectionBase, - textSelectionExtent: textSelectionExtent, - platformViewId: platformViewId, - scrollChildren: scrollChildren, - scrollIndex: scrollIndex, - scrollPosition: scrollPosition, - scrollExtentMax: scrollExtentMax, - scrollExtentMin: scrollExtentMin, - elevation: elevation, - thickness: thickness, - rect: rect, label: label, labelAttributes: labelAttributes, hint: hint, hintAttributes: hintAttributes, value: value, valueAttributes: valueAttributes, - increasedValue: increasedValue, - increasedValueAttributes: increasedValueAttributes, - decreasedValue: decreasedValue, - decreasedValueAttributes: decreasedValueAttributes, - textDirection: textDirection, - transform: transform, childrenInTraversalOrder: childrenInTraversalOrder, - childrenInHitTestOrder: childrenInHitTestOrder, - additionalActions: additionalActions, ); } @@ -262,68 +238,20 @@ class SemanticsUpdateBuilderSpy extends Fake implements ui.SemanticsUpdateBuilde class SemanticsNodeUpdateObservation { const SemanticsNodeUpdateObservation({ - required this.id, - required this.flags, - required this.actions, - required this.maxValueLength, - required this.currentValueLength, - required this.textSelectionBase, - required this.textSelectionExtent, - required this.platformViewId, - required this.scrollChildren, - required this.scrollIndex, - required this.scrollPosition, - required this.scrollExtentMax, - required this.scrollExtentMin, - required this.elevation, - required this.thickness, - required this.rect, required this.label, this.labelAttributes, required this.value, this.valueAttributes, - required this.increasedValue, - this.increasedValueAttributes, - required this.decreasedValue, - this.decreasedValueAttributes, required this.hint, this.hintAttributes, - this.textDirection, - required this.transform, required this.childrenInTraversalOrder, - required this.childrenInHitTestOrder, - required this.additionalActions, }); - final int id; - final int flags; - final int actions; - final int maxValueLength; - final int currentValueLength; - final int textSelectionBase; - final int textSelectionExtent; - final int platformViewId; - final int scrollChildren; - final int scrollIndex; - final double scrollPosition; - final double scrollExtentMax; - final double scrollExtentMin; - final double elevation; - final double thickness; - final Rect rect; final String label; final List<StringAttribute>? labelAttributes; final String value; final List<StringAttribute>? valueAttributes; - final String increasedValue; - final List<StringAttribute>? increasedValueAttributes; - final String decreasedValue; - final List<StringAttribute>? decreasedValueAttributes; final String hint; final List<StringAttribute>? hintAttributes; - final TextDirection? textDirection; - final Float64List transform; final Int32List childrenInTraversalOrder; - final Int32List childrenInHitTestOrder; - final Int32List additionalActions; } diff --git a/packages/flutter/test/services/asset_bundle_test.dart b/packages/flutter/test/services/asset_bundle_test.dart index 961482d0cb617..efa2f7d7cc006 100644 --- a/packages/flutter/test/services/asset_bundle_test.dart +++ b/packages/flutter/test/services/asset_bundle_test.dart @@ -19,7 +19,7 @@ class TestAssetBundle extends CachingAssetBundle { return ByteData.view(Uint8List.fromList(const Utf8Encoder().convert('{"one": ["one"]}')).buffer); } - if (key == 'AssetManifest.smcbin') { + if (key == 'AssetManifest.bin') { return const StandardMessageCodec().encodeMessage(<String, Object>{ 'one': <Object>[] })!; @@ -76,8 +76,8 @@ void main() { expect(firstLoadStructuredDataResult, 'one'); expect(secondLoadStructuredDataResult, 'one'); - final String firstLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData('AssetManifest.smcbin', (ByteData value) => Future<String>.value('one')); - final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData('AssetManifest.smcbin', (ByteData value) => Future<String>.value('two')); + final String firstLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData value) => Future<String>.value('one')); + final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData value) => Future<String>.value('two')); expect(firstLoadStructuredBinaryDataResult, 'one'); expect(secondLoadStructuredBinaryDataResult, 'one'); }); @@ -95,9 +95,9 @@ void main() { final String secondLoadStructuredDataResult = await bundle.loadStructuredData('AssetManifest.json', (String value) => Future<String>.value('two')); expect(secondLoadStructuredDataResult, 'two'); - await bundle.loadStructuredBinaryData('AssetManifest.smcbin', (ByteData value) => Future<String>.value('one')); + await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData value) => Future<String>.value('one')); bundle.clear(); - final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData('AssetManifest.smcbin', (ByteData value) => Future<String>.value('two')); + final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData value) => Future<String>.value('two')); expect(secondLoadStructuredBinaryDataResult, 'two'); }); @@ -114,9 +114,9 @@ void main() { final String secondLoadStructuredDataResult = await bundle.loadStructuredData('AssetManifest.json', (String value) => Future<String>.value('two')); expect(secondLoadStructuredDataResult, 'two'); - await bundle.loadStructuredBinaryData('AssetManifest.smcbin', (ByteData value) => Future<String>.value('one')); - bundle.evict('AssetManifest.smcbin'); - final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData('AssetManifest.smcbin', (ByteData value) => Future<String>.value('two')); + await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData value) => Future<String>.value('one')); + bundle.evict('AssetManifest.bin'); + final String secondLoadStructuredBinaryDataResult = await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData value) => Future<String>.value('two')); expect(secondLoadStructuredBinaryDataResult, 'two'); }); @@ -191,7 +191,7 @@ void main() { test('loadStructuredBinaryData correctly loads ByteData', () async { final TestAssetBundle bundle = TestAssetBundle(); final Map<Object?, Object?> assetManifest = - await bundle.loadStructuredBinaryData('AssetManifest.smcbin', (ByteData data) => const StandardMessageCodec().decodeMessage(data) as Map<Object?, Object?>); + await bundle.loadStructuredBinaryData('AssetManifest.bin', (ByteData data) => const StandardMessageCodec().decodeMessage(data) as Map<Object?, Object?>); expect(assetManifest.keys.toList(), equals(<String>['one'])); expect(assetManifest['one'], <Object>[]); }); diff --git a/packages/flutter/test/services/asset_manifest_test.dart b/packages/flutter/test/services/asset_manifest_test.dart index e715ba6b352cf..c06ffd0126b64 100644 --- a/packages/flutter/test/services/asset_manifest_test.dart +++ b/packages/flutter/test/services/asset_manifest_test.dart @@ -8,15 +8,22 @@ import 'package:flutter_test/flutter_test.dart'; class TestAssetBundle extends AssetBundle { @override Future<ByteData> load(String key) async { - if (key == 'AssetManifest.smcbin') { + if (key == 'AssetManifest.bin') { final Map<String, List<Object>> binManifestData = <String, List<Object>>{ 'assets/foo.png': <Object>[ + <String, Object>{ + 'asset': 'assets/foo.png', + }, <String, Object>{ 'asset': 'assets/2x/foo.png', 'dpr': 2.0 - } + }, + ], + 'assets/bar.png': <Object>[ + <String, Object>{ + 'asset': 'assets/bar.png', + }, ], - 'assets/bar.png': <Object>[], }; final ByteData data = const StandardMessageCodec().encodeMessage(binManifestData)!; @@ -32,7 +39,6 @@ class TestAssetBundle extends AssetBundle { } } - void main() { TestWidgetsFlutterBinding.ensureInitialized(); diff --git a/packages/flutter/test/services/fake_platform_views.dart b/packages/flutter/test/services/fake_platform_views.dart index 0cf5f367767db..7d85e93733f32 100644 --- a/packages/flutter/test/services/fake_platform_views.dart +++ b/packages/flutter/test/services/fake_platform_views.dart @@ -503,6 +503,7 @@ class FakeHtmlPlatformViewsController { final Map<dynamic, dynamic> args = call.arguments as Map<dynamic, dynamic>; final int id = args['id'] as int; final String viewType = args['viewType'] as String; + final Object? params = args['params']; if (_views.containsKey(id)) { throw PlatformException( @@ -522,7 +523,7 @@ class FakeHtmlPlatformViewsController { await createCompleter!.future; } - _views[id] = FakeHtmlPlatformView(id, viewType); + _views[id] = FakeHtmlPlatformView(id, viewType, params); return Future<int?>.sync(() => null); } @@ -658,10 +659,11 @@ class FakeUiKitView { @immutable class FakeHtmlPlatformView { - const FakeHtmlPlatformView(this.id, this.type); + const FakeHtmlPlatformView(this.id, this.type, [this.creationParams]); final int id; final String type; + final Object? creationParams; @override bool operator ==(Object other) { @@ -670,14 +672,15 @@ class FakeHtmlPlatformView { } return other is FakeHtmlPlatformView && other.id == id - && other.type == type; + && other.type == type + && other.creationParams == creationParams; } @override - int get hashCode => Object.hash(id, type); + int get hashCode => Object.hash(id, type, creationParams); @override String toString() { - return 'FakeHtmlPlatformView(id: $id, type: $type)'; + return 'FakeHtmlPlatformView(id: $id, type: $type, params: $creationParams)'; } } diff --git a/packages/flutter/test/services/lifecycle_test.dart b/packages/flutter/test/services/lifecycle_test.dart index ebd800e5e5f15..c275691b3cd0c 100644 --- a/packages/flutter/test/services/lifecycle_test.dart +++ b/packages/flutter/test/services/lifecycle_test.dart @@ -2,22 +2,45 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:ui'; + import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('initialLifecycleState is used to init state paused', (WidgetTester tester) async { - // The lifecycleState is null initially in tests as there is no - // initialLifecycleState. - expect(ServicesBinding.instance.lifecycleState, equals(null)); - // Mock the Window to provide paused as the AppLifecycleState + expect(ServicesBinding.instance.lifecycleState, isNull); final TestWidgetsFlutterBinding binding = tester.binding; + binding.resetLifecycleState(); // Use paused as the initial state. binding.platformDispatcher.initialLifecycleStateTestValue = 'AppLifecycleState.paused'; binding.readTestInitialLifecycleStateFromNativeWindow(); // Re-attempt the initialization. // The lifecycleState should now be the state we passed above, // even though no lifecycle event was fired from the platform. - expect(ServicesBinding.instance.lifecycleState.toString(), equals('AppLifecycleState.paused')); + expect(binding.lifecycleState.toString(), equals('AppLifecycleState.paused')); + }); + testWidgets('Handles all of the allowed states of AppLifecycleState', (WidgetTester tester) async { + final TestWidgetsFlutterBinding binding = tester.binding; + for (final AppLifecycleState state in AppLifecycleState.values) { + binding.resetLifecycleState(); + binding.platformDispatcher.initialLifecycleStateTestValue = state.toString(); + binding.readTestInitialLifecycleStateFromNativeWindow(); + expect(ServicesBinding.instance.lifecycleState.toString(), equals(state.toString())); + } + }); + test('AppLifecycleState values are in the right order for the state machine to be correct', () { + expect( + AppLifecycleState.values, + equals( + <AppLifecycleState>[ + AppLifecycleState.detached, + AppLifecycleState.resumed, + AppLifecycleState.inactive, + AppLifecycleState.hidden, + AppLifecycleState.paused, + ], + ), + ); }); } diff --git a/packages/flutter/test/widgets/actions_test.dart b/packages/flutter/test/widgets/actions_test.dart index 704d6ffafd50a..d428c994d04aa 100644 --- a/packages/flutter/test/widgets/actions_test.dart +++ b/packages/flutter/test/widgets/actions_test.dart @@ -863,10 +863,10 @@ void main() { await tester.pumpWidget( MaterialApp( home: FocusableActionDetector( - child: MaterialButton( + child: ElevatedButton( + onPressed: () {}, focusNode: buttonNode, child: const Text('Test'), - onPressed: () {}, ), ), ), @@ -882,10 +882,10 @@ void main() { MaterialApp( home: FocusableActionDetector( descendantsAreFocusable: false, - child: MaterialButton( + child: ElevatedButton( + onPressed: () {}, focusNode: buttonNode, child: const Text('Test'), - onPressed: () {}, ), ), ), @@ -910,15 +910,15 @@ void main() { home: FocusableActionDetector( child: Column( children: <Widget>[ - MaterialButton( + ElevatedButton( + onPressed: () {}, focusNode: buttonNode1, child: const Text('Node 1'), - onPressed: () {}, ), - MaterialButton( + ElevatedButton( + onPressed: () {}, focusNode: buttonNode2, child: const Text('Node 2'), - onPressed: () {}, ), ], ), @@ -941,15 +941,15 @@ void main() { descendantsAreTraversable: false, child: Column( children: <Widget>[ - MaterialButton( + ElevatedButton( + onPressed: () {}, focusNode: buttonNode1, child: const Text('Node 1'), - onPressed: () {}, ), - MaterialButton( + ElevatedButton( + onPressed: () {}, focusNode: buttonNode2, child: const Text('Node 2'), - onPressed: () {}, ), ], ), diff --git a/packages/flutter/test/widgets/animated_grid_test.dart b/packages/flutter/test/widgets/animated_grid_test.dart index 7a86f650813b6..dcec143a9e705 100644 --- a/packages/flutter/test/widgets/animated_grid_test.dart +++ b/packages/flutter/test/widgets/animated_grid_test.dart @@ -646,6 +646,45 @@ void main() { expect(tester.widget<CustomScrollView>(find.byType(CustomScrollView)).clipBehavior, clipBehavior); }); + + testWidgets('AnimatedGrid applies MediaQuery padding', (WidgetTester tester) async { + const EdgeInsets padding = EdgeInsets.all(30.0); + EdgeInsets? innerMediaQueryPadding; + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData( + padding: EdgeInsets.all(30.0), + ), + child: AnimatedGrid( + initialItemCount: 6, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + ), + itemBuilder: (BuildContext context, int index, Animation<double> animation) { + innerMediaQueryPadding = MediaQuery.paddingOf(context); + return const Placeholder(); + }, + ), + ), + ), + ); + final Offset topLeft = tester.getTopLeft(find.byType(Placeholder).first); + // Automatically apply the top padding into sliver. + expect(topLeft, Offset(0.0, padding.top)); + + // Scroll to the bottom. + await tester.drag(find.byType(AnimatedGrid), const Offset(0.0, -1000.0)); + await tester.pumpAndSettle(); + + final Offset bottomRight = tester.getBottomRight(find.byType(Placeholder).last); + // Automatically apply the bottom padding into sliver. + expect(bottomRight, Offset(800.0, 600.0 - padding.bottom)); + + // Verify that the left/right padding is not applied. + expect(innerMediaQueryPadding, const EdgeInsets.symmetric(horizontal: 30.0)); + }); } class _StatefulListItem extends StatefulWidget { diff --git a/packages/flutter/test/widgets/animated_list_test.dart b/packages/flutter/test/widgets/animated_list_test.dart index 89a6ce6ac9211..6e585c9fcd2dc 100644 --- a/packages/flutter/test/widgets/animated_list_test.dart +++ b/packages/flutter/test/widgets/animated_list_test.dart @@ -649,6 +649,42 @@ void main() { expect(tester.widget<CustomScrollView>(find.byType(CustomScrollView)).shrinkWrap, true); }); + + testWidgets('AnimatedList applies MediaQuery padding', (WidgetTester tester) async { + const EdgeInsets padding = EdgeInsets.all(30.0); + EdgeInsets? innerMediaQueryPadding; + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData( + padding: EdgeInsets.all(30.0), + ), + child: AnimatedList( + initialItemCount: 3, + itemBuilder: (BuildContext context, int index, Animation<double> animation) { + innerMediaQueryPadding = MediaQuery.paddingOf(context); + return const Placeholder(); + }, + ), + ), + ), + ); + final Offset topLeft = tester.getTopLeft(find.byType(Placeholder).first); + // Automatically apply the top padding into sliver. + expect(topLeft, Offset(0.0, padding.top)); + + // Scroll to the bottom. + await tester.drag(find.byType(AnimatedList), const Offset(0.0, -1000.0)); + await tester.pumpAndSettle(); + + final Offset bottomLeft = tester.getBottomLeft(find.byType(Placeholder).last); + // Automatically apply the bottom padding into sliver. + expect(bottomLeft, Offset(0.0, 600.0 - padding.bottom)); + + // Verify that the left/right padding is not applied. + expect(innerMediaQueryPadding, const EdgeInsets.symmetric(horizontal: 30.0)); + }); } diff --git a/packages/flutter/test/widgets/app_lifecycle_listener_test.dart b/packages/flutter/test/widgets/app_lifecycle_listener_test.dart new file mode 100644 index 0000000000000..a93b97628b37e --- /dev/null +++ b/packages/flutter/test/widgets/app_lifecycle_listener_test.dart @@ -0,0 +1,190 @@ +// 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:ui'; + +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + late AppLifecycleListener listener; + + Future<void> setAppLifeCycleState(AppLifecycleState state) async { + final ByteData? message = const StringCodec().encodeMessage(state.toString()); + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .handlePlatformMessage('flutter/lifecycle', message, (_) {}); + } + + Future<void> sendAppExitRequest() async { + final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('System.requestAppExit')); + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .handlePlatformMessage('flutter/platform', message, (_) {}); + } + + setUp(() async { + WidgetsFlutterBinding.ensureInitialized(); + WidgetsBinding.instance + ..resetEpoch() + ..platformDispatcher.onBeginFrame = null + ..platformDispatcher.onDrawFrame = null; + final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.instance; + binding.readTestInitialLifecycleStateFromNativeWindow(); + // Reset the state to detached. Going to paused first makes it a valid + // transition from any state, since the intermediate transitions will be + // generated. + await setAppLifeCycleState(AppLifecycleState.paused); + await setAppLifeCycleState(AppLifecycleState.detached); + }); + + tearDown(() { + listener.dispose(); + final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.instance; + binding.resetLifecycleState(); + binding.platformDispatcher.resetInitialLifecycleState(); + assert(TestAppLifecycleListener.registerCount == 0, + 'There were ${TestAppLifecycleListener.registerCount} listeners that were not disposed of in tests.'); + }); + + testWidgets('Default Diagnostics', (WidgetTester tester) async { + listener = TestAppLifecycleListener(binding: tester.binding); + expect(listener.toString(), + equalsIgnoringHashCodes('TestAppLifecycleListener#00000(binding: <AutomatedTestWidgetsFlutterBinding>)')); + }); + + testWidgets('Diagnostics', (WidgetTester tester) async { + Future<AppExitResponse> handleExitRequested() async { + return AppExitResponse.cancel; + } + + listener = TestAppLifecycleListener( + binding: WidgetsBinding.instance, + onExitRequested: handleExitRequested, + onStateChange: (AppLifecycleState _) {}, + ); + expect( + listener.toString(), + equalsIgnoringHashCodes( + 'TestAppLifecycleListener#00000(binding: <AutomatedTestWidgetsFlutterBinding>, onStateChange, onExitRequested)')); + }); + + testWidgets('listens to AppLifecycleState', (WidgetTester tester) async { + final List<AppLifecycleState> states = <AppLifecycleState>[tester.binding.lifecycleState!]; + void stateChange(AppLifecycleState state) { + states.add(state); + } + + listener = TestAppLifecycleListener( + binding: WidgetsBinding.instance, + onStateChange: stateChange, + ); + expect(states, equals(<AppLifecycleState>[AppLifecycleState.detached])); + await setAppLifeCycleState(AppLifecycleState.inactive); + // "resumed" is generated. + expect(states, + equals(<AppLifecycleState>[AppLifecycleState.detached, AppLifecycleState.resumed, AppLifecycleState.inactive])); + await setAppLifeCycleState(AppLifecycleState.resumed); + expect( + states, + equals(<AppLifecycleState>[ + AppLifecycleState.detached, + AppLifecycleState.resumed, + AppLifecycleState.inactive, + AppLifecycleState.resumed + ])); + }); + + testWidgets('Triggers correct state transition callbacks', (WidgetTester tester) async { + final List<String> transitions = <String>[]; + listener = TestAppLifecycleListener( + binding: WidgetsBinding.instance, + onDetach: () => transitions.add('detach'), + onHide: () => transitions.add('hide'), + onInactive: () => transitions.add('inactive'), + onPause: () => transitions.add('pause'), + onRestart: () => transitions.add('restart'), + onResume: () => transitions.add('resume'), + onShow: () => transitions.add('show'), + ); + + // Try "standard" sequence + await setAppLifeCycleState(AppLifecycleState.resumed); + expect(transitions, equals(<String>['resume'])); + await setAppLifeCycleState(AppLifecycleState.inactive); + expect(transitions, equals(<String>['resume', 'inactive'])); + await setAppLifeCycleState(AppLifecycleState.hidden); + expect(transitions, equals(<String>['resume', 'inactive', 'hide'])); + await setAppLifeCycleState(AppLifecycleState.paused); + expect(transitions, equals(<String>['resume', 'inactive', 'hide', 'pause'])); + + // Go back to resume + transitions.clear(); + await setAppLifeCycleState(AppLifecycleState.hidden); + expect(transitions, equals(<String>['restart'])); + await setAppLifeCycleState(AppLifecycleState.inactive); + expect(transitions, equals(<String>['restart', 'show'])); + await setAppLifeCycleState(AppLifecycleState.resumed); + expect(transitions, equals(<String>['restart', 'show', 'resume'])); + + // Generates intermediate states. + transitions.clear(); + await setAppLifeCycleState(AppLifecycleState.paused); + expect(transitions, equals(<String>['inactive', 'hide', 'pause'])); + // Wraps around from pause to detach. + await setAppLifeCycleState(AppLifecycleState.detached); + expect(transitions, equals(<String>['inactive', 'hide', 'pause', 'detach'])); + await setAppLifeCycleState(AppLifecycleState.resumed); + expect(transitions, equals(<String>['inactive', 'hide', 'pause', 'detach', 'resume'])); + await setAppLifeCycleState(AppLifecycleState.paused); + expect(transitions, equals(<String>['inactive', 'hide', 'pause', 'detach', 'resume', 'inactive', 'hide', 'pause'])); + transitions.clear(); + await setAppLifeCycleState(AppLifecycleState.resumed); + expect(transitions, equals(<String>['restart', 'show', 'resume'])); + + // Asserts on bad transitions + await expectLater(() => setAppLifeCycleState(AppLifecycleState.detached), throwsAssertionError); + await setAppLifeCycleState(AppLifecycleState.paused); + await setAppLifeCycleState(AppLifecycleState.detached); + }); + + testWidgets('Receives exit requests', (WidgetTester tester) async { + bool exitRequested = false; + Future<AppExitResponse> handleExitRequested() async { + exitRequested = true; + return AppExitResponse.cancel; + } + + listener = TestAppLifecycleListener( + binding: WidgetsBinding.instance, + onExitRequested: handleExitRequested, + ); + await sendAppExitRequest(); + expect(exitRequested, isTrue); + }); +} + +class TestAppLifecycleListener extends AppLifecycleListener { + TestAppLifecycleListener({ + super.binding, + super.onResume, + super.onInactive, + super.onHide, + super.onShow, + super.onPause, + super.onRestart, + super.onDetach, + super.onExitRequested, + super.onStateChange, + }) { + registerCount += 1; + } + + static int registerCount = 0; + + @override + void dispose() { + super.dispose(); + registerCount -= 1; + } +} diff --git a/packages/flutter/test/widgets/async_lifecycle_test.dart b/packages/flutter/test/widgets/async_lifecycle_test.dart index 62f1e2f3f3cb0..193152002b818 100644 --- a/packages/flutter/test/widgets/async_lifecycle_test.dart +++ b/packages/flutter/test/widgets/async_lifecycle_test.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; class InvalidOnInitLifecycleWidget extends StatefulWidget { @@ -25,9 +25,9 @@ class InvalidOnInitLifecycleWidgetState extends State<InvalidOnInitLifecycleWidg } class InvalidDidUpdateWidgetLifecycleWidget extends StatefulWidget { - const InvalidDidUpdateWidgetLifecycleWidget({super.key, required this.id}); + const InvalidDidUpdateWidgetLifecycleWidget({super.key, required this.color}); - final int id; + final Color color; @override InvalidDidUpdateWidgetLifecycleWidgetState createState() => InvalidDidUpdateWidgetLifecycleWidgetState(); @@ -41,7 +41,7 @@ class InvalidDidUpdateWidgetLifecycleWidgetState extends State<InvalidDidUpdateW @override Widget build(BuildContext context) { - return Container(); + return ColoredBox(color: widget.color); } } @@ -53,8 +53,8 @@ void main() { }); testWidgets('async didUpdateWidget throws FlutterError', (WidgetTester tester) async { - await tester.pumpWidget(const InvalidDidUpdateWidgetLifecycleWidget(id: 1)); - await tester.pumpWidget(const InvalidDidUpdateWidgetLifecycleWidget(id: 2)); + await tester.pumpWidget(const InvalidDidUpdateWidgetLifecycleWidget(color: Colors.green)); + await tester.pumpWidget(const InvalidDidUpdateWidgetLifecycleWidget(color: Colors.red)); expect(tester.takeException(), isFlutterError); }); diff --git a/packages/flutter/test/widgets/automatic_keep_alive_test.dart b/packages/flutter/test/widgets/automatic_keep_alive_test.dart index 1daf6f078267f..eb2718fbd0543 100644 --- a/packages/flutter/test/widgets/automatic_keep_alive_test.dart +++ b/packages/flutter/test/widgets/automatic_keep_alive_test.dart @@ -600,23 +600,7 @@ class _AlwaysKeepAliveState extends State<_AlwaysKeepAlive> with AutomaticKeepAl } } -class RenderBoxKeepAlive extends RenderBox { - State<StatefulWidget> createState() => AlwaysKeepAliveRenderBoxState(); -} - -class AlwaysKeepAliveRenderBoxState extends State<_AlwaysKeepAlive> with AutomaticKeepAliveClientMixin<_AlwaysKeepAlive> { - @override - bool get wantKeepAlive => true; - - @override - Widget build(BuildContext context) { - super.build(context); - return const SizedBox( - height: 48.0, - child: Text('keep me alive'), - ); - } -} +class RenderBoxKeepAlive extends RenderBox { } mixin KeepAliveParentDataMixinAlt implements KeepAliveParentDataMixin { @override @@ -631,14 +615,6 @@ class RenderSliverMultiBoxAdaptorAlt extends RenderSliver with RenderSliverHelpers, RenderSliverWithKeepAliveMixin { - RenderSliverMultiBoxAdaptorAlt({ - RenderSliverBoxChildManager? childManager, - }) : _childManager = childManager; - - @protected - RenderSliverBoxChildManager? get childManager => _childManager; - final RenderSliverBoxChildManager? _childManager; - final List<RenderBox> children = <RenderBox>[]; void insert(RenderBox child, { RenderBox? after }) { diff --git a/packages/flutter/test/widgets/backdrop_filter_test.dart b/packages/flutter/test/widgets/backdrop_filter_test.dart index ebc77b1d06497..a112fe9e666c8 100644 --- a/packages/flutter/test/widgets/backdrop_filter_test.dart +++ b/packages/flutter/test/widgets/backdrop_filter_test.dart @@ -13,9 +13,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - testWidgets("BackdropFilter's cull rect does not shrink", (WidgetTester tester) async { + testWidgets("Material2 - BackdropFilter's cull rect does not shrink", (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Stack( fit: StackFit.expand, @@ -46,13 +47,111 @@ void main() { ); await expectLater( find.byType(RepaintBoundary).first, - matchesGoldenFile('backdrop_filter_test.cull_rect.png'), + matchesGoldenFile('m2_backdrop_filter_test.cull_rect.png'), ); }); - testWidgets('BackdropFilter blendMode on saveLayer', (WidgetTester tester) async { + testWidgets("Material3 - BackdropFilter's cull rect does not shrink", (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: true), + home: Scaffold( + body: Stack( + fit: StackFit.expand, + children: <Widget>[ + Text('0 0 ' * 10000), + Center( + // ClipRect needed for filtering the 200x200 area instead of the + // whole screen. + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 5.0, + sigmaY: 5.0, + ), + child: Container( + alignment: Alignment.center, + width: 200.0, + height: 200.0, + child: const Text('Hello World'), + ), + ), + ), + ), + ], + ), + ), + ), + ); + await expectLater( + find.byType(RepaintBoundary).first, + matchesGoldenFile('m3_backdrop_filter_test.cull_rect.png'), + ); + }); + + testWidgets('Material2 - BackdropFilter blendMode on saveLayer', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: Scaffold( + body: Opacity( + opacity: 0.9, + child: Stack( + fit: StackFit.expand, + children: <Widget>[ + Text('0 0 ' * 10000), + Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + // ClipRect needed for filtering the 200x200 area instead of the + // whole screen. + children: <Widget>[ + ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 5.0, + sigmaY: 5.0, + ), + child: Container( + alignment: Alignment.center, + width: 200.0, + height: 200.0, + color: Colors.yellow.withAlpha(0x7), + ), + ), + ), + ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 5.0, + sigmaY: 5.0, + ), + blendMode: BlendMode.src, + child: Container( + alignment: Alignment.center, + width: 200.0, + height: 200.0, + color: Colors.yellow.withAlpha(0x7), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ); + await expectLater( + find.byType(RepaintBoundary).first, + matchesGoldenFile('m2_backdrop_filter_test.saveLayer.blendMode.png'), + ); + }); + + testWidgets('Material3 - BackdropFilter blendMode on saveLayer', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: true), home: Scaffold( body: Opacity( opacity: 0.9, @@ -104,7 +203,7 @@ void main() { ); await expectLater( find.byType(RepaintBoundary).first, - matchesGoldenFile('backdrop_filter_test.saveLayer.blendMode.png'), + matchesGoldenFile('m3_backdrop_filter_test.saveLayer.blendMode.png'), ); }); } diff --git a/packages/flutter/test/widgets/basic_test.dart b/packages/flutter/test/widgets/basic_test.dart index 8b35975b57db0..5fb6bc82ef0b7 100644 --- a/packages/flutter/test/widgets/basic_test.dart +++ b/packages/flutter/test/widgets/basic_test.dart @@ -463,6 +463,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Row( crossAxisAlignment: CrossAxisAlignment.baseline, @@ -470,11 +471,11 @@ void main() { children: <Widget>[ Text('big text', key: key1, - style: const TextStyle(fontFamily: 'FlutterTest', fontSize: fontSize1), + style: const TextStyle(fontFamily: 'FlutterTest', fontSize: fontSize1, height: 1.0), ), Text('one\ntwo\nthree\nfour\nfive\nsix\nseven', key: key2, - style: const TextStyle(fontFamily: 'FlutterTest', fontSize: fontSize2), + style: const TextStyle(fontFamily: 'FlutterTest', fontSize: fontSize2, height: 1.0), ), ], ), @@ -517,6 +518,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Row( crossAxisAlignment: CrossAxisAlignment.baseline, @@ -524,11 +526,11 @@ void main() { children: <Widget>[ Text('big text', key: key1, - style: const TextStyle(fontFamily: 'FlutterTest', fontSize: fontSize1), + style: const TextStyle(fontFamily: 'FlutterTest', fontSize: fontSize1, height: 1.0), ), Text('one\ntwo\nthree\nfour\nfive\nsix\nseven', key: key2, - style: const TextStyle(fontFamily: 'FlutterTest', fontSize: fontSize2), + style: const TextStyle(fontFamily: 'FlutterTest', fontSize: fontSize2, height: 1.0), ), const FlutterLogo(size: 250), ], diff --git a/packages/flutter/test/widgets/binding_test.dart b/packages/flutter/test/widgets/binding_test.dart index d4acb9a8e8e5e..928fc1fa948c2 100644 --- a/packages/flutter/test/widgets/binding_test.dart +++ b/packages/flutter/test/widgets/binding_test.dart @@ -17,11 +17,11 @@ class MemoryPressureObserver with WidgetsBindingObserver { } class AppLifecycleStateObserver with WidgetsBindingObserver { - late AppLifecycleState lifecycleState; + List<AppLifecycleState> accumulatedStates = <AppLifecycleState>[]; @override void didChangeAppLifecycleState(AppLifecycleState state) { - lifecycleState = state; + accumulatedStates.add(state); } } @@ -66,19 +66,58 @@ void main() { final AppLifecycleStateObserver observer = AppLifecycleStateObserver(); WidgetsBinding.instance.addObserver(observer); - setAppLifeCycleState(AppLifecycleState.paused); - expect(observer.lifecycleState, AppLifecycleState.paused); - - setAppLifeCycleState(AppLifecycleState.resumed); - expect(observer.lifecycleState, AppLifecycleState.resumed); - - setAppLifeCycleState(AppLifecycleState.inactive); - expect(observer.lifecycleState, AppLifecycleState.inactive); - - setAppLifeCycleState(AppLifecycleState.detached); - expect(observer.lifecycleState, AppLifecycleState.detached); - - setAppLifeCycleState(AppLifecycleState.resumed); + await setAppLifeCycleState(AppLifecycleState.paused); + expect(observer.accumulatedStates, <AppLifecycleState>[AppLifecycleState.paused]); + + observer.accumulatedStates.clear(); + await setAppLifeCycleState(AppLifecycleState.resumed); + expect(observer.accumulatedStates, <AppLifecycleState>[ + AppLifecycleState.hidden, + AppLifecycleState.inactive, + AppLifecycleState.resumed, + ]); + + observer.accumulatedStates.clear(); + await setAppLifeCycleState(AppLifecycleState.paused); + expect(observer.accumulatedStates, <AppLifecycleState>[ + AppLifecycleState.inactive, + AppLifecycleState.hidden, + AppLifecycleState.paused, + ]); + + observer.accumulatedStates.clear(); + await setAppLifeCycleState(AppLifecycleState.inactive); + expect(observer.accumulatedStates, <AppLifecycleState>[ + AppLifecycleState.hidden, + AppLifecycleState.inactive, + ]); + + observer.accumulatedStates.clear(); + await setAppLifeCycleState(AppLifecycleState.hidden); + expect(observer.accumulatedStates, <AppLifecycleState>[ + AppLifecycleState.hidden, + ]); + + observer.accumulatedStates.clear(); + await setAppLifeCycleState(AppLifecycleState.paused); + expect(observer.accumulatedStates, <AppLifecycleState>[ + AppLifecycleState.paused, + ]); + + observer.accumulatedStates.clear(); + await setAppLifeCycleState(AppLifecycleState.detached); + expect(observer.accumulatedStates, <AppLifecycleState>[ + AppLifecycleState.detached, + ]); + + observer.accumulatedStates.clear(); + await setAppLifeCycleState(AppLifecycleState.resumed); + expect(observer.accumulatedStates, <AppLifecycleState>[ + AppLifecycleState.resumed, + ]); + + observer.accumulatedStates.clear(); + await expectLater(() async => setAppLifeCycleState(AppLifecycleState.detached), throwsAssertionError); }); testWidgets('didPushRoute callback', (WidgetTester tester) async { @@ -87,7 +126,7 @@ void main() { const String testRouteName = 'testRouteName'; final ByteData message = const JSONMethodCodec().encodeMethodCall(const MethodCall('pushRoute', testRouteName)); - await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) { }); + await tester.binding.defaultBinaryMessenger.handlePlatformMessage('flutter/navigation', message, (_) {}); expect(observer.pushedRoute, testRouteName); WidgetsBinding.instance.removeObserver(observer); @@ -199,26 +238,29 @@ void main() { testWidgets('Application lifecycle affects frame scheduling', (WidgetTester tester) async { expect(tester.binding.hasScheduledFrame, isFalse); - setAppLifeCycleState(AppLifecycleState.paused); + await setAppLifeCycleState(AppLifecycleState.paused); expect(tester.binding.hasScheduledFrame, isFalse); - setAppLifeCycleState(AppLifecycleState.resumed); + await setAppLifeCycleState(AppLifecycleState.resumed); expect(tester.binding.hasScheduledFrame, isTrue); await tester.pump(); expect(tester.binding.hasScheduledFrame, isFalse); - setAppLifeCycleState(AppLifecycleState.inactive); + await setAppLifeCycleState(AppLifecycleState.inactive); + expect(tester.binding.hasScheduledFrame, isFalse); + + await setAppLifeCycleState(AppLifecycleState.paused); expect(tester.binding.hasScheduledFrame, isFalse); - setAppLifeCycleState(AppLifecycleState.detached); + await setAppLifeCycleState(AppLifecycleState.detached); expect(tester.binding.hasScheduledFrame, isFalse); - setAppLifeCycleState(AppLifecycleState.inactive); + await setAppLifeCycleState(AppLifecycleState.inactive); expect(tester.binding.hasScheduledFrame, isTrue); await tester.pump(); expect(tester.binding.hasScheduledFrame, isFalse); - setAppLifeCycleState(AppLifecycleState.paused); + await setAppLifeCycleState(AppLifecycleState.paused); expect(tester.binding.hasScheduledFrame, isFalse); tester.binding.scheduleFrame(); @@ -242,7 +284,7 @@ void main() { expect(frameCount, 1); // Get the tester back to a resumed state for subsequent tests. - setAppLifeCycleState(AppLifecycleState.resumed); + await setAppLifeCycleState(AppLifecycleState.resumed); expect(tester.binding.hasScheduledFrame, isTrue); await tester.pump(); }); diff --git a/packages/flutter/test/widgets/color_filter_test.dart b/packages/flutter/test/widgets/color_filter_test.dart index 153e6d88b81d6..3135844c03db0 100644 --- a/packages/flutter/test/widgets/color_filter_test.dart +++ b/packages/flutter/test/widgets/color_filter_test.dart @@ -41,7 +41,7 @@ void main() { colorFilter: sepia, child: MaterialApp( title: 'Flutter Demo', - theme: ThemeData(primarySwatch: Colors.blue), + theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: false), home: Scaffold( appBar: AppBar( title: const Text('Sepia ColorFilter Test'), diff --git a/packages/flutter/test/widgets/decorated_sliver_test.dart b/packages/flutter/test/widgets/decorated_sliver_test.dart new file mode 100644 index 0000000000000..a3d328fe72cd7 --- /dev/null +++ b/packages/flutter/test/widgets/decorated_sliver_test.dart @@ -0,0 +1,504 @@ +// 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. + +// This file is run as part of a reduced test set in CI on Mac and Windows +// machines. +@Tags(<String>['reduced-test-set']) +library; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../rendering/mock_canvas.dart'; + +void main() { + testWidgets('DecoratedSliver creates, paints, and disposes BoxPainter', (WidgetTester tester) async { + final TestDecoration decoration = TestDecoration(); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: CustomScrollView( + slivers: <Widget>[ + DecoratedSliver( + decoration: decoration, + sliver: const SliverToBoxAdapter( + child: SizedBox(width: 100, height: 100), + ), + ) + ], + ) + ) + )); + + expect(decoration.painters, hasLength(1)); + expect(decoration.painters.last.lastConfiguration!.size, const Size(800, 100)); + expect(decoration.painters.last.lastOffset, Offset.zero); + expect(decoration.painters.last.disposed, false); + + await tester.pumpWidget(const SizedBox()); + + expect(decoration.painters, hasLength(1)); + expect(decoration.painters.last.disposed, true); + }); + + testWidgets('DecoratedSliver can update box painter', (WidgetTester tester) async { + final TestDecoration decorationA = TestDecoration(); + final TestDecoration decorationB = TestDecoration(); + + Decoration activateDecoration = decorationA; + late StateSetter localSetState; + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + localSetState = setState; + return CustomScrollView( + slivers: <Widget>[ + DecoratedSliver( + decoration: activateDecoration, + sliver: const SliverToBoxAdapter( + child: SizedBox(width: 100, height: 100), + ), + ) + ], + ); + }, + ) + ) + )); + + expect(decorationA.painters, hasLength(1)); + expect(decorationA.painters.last.paintCount, 1); + expect(decorationB.painters, hasLength(0)); + + localSetState(() { + activateDecoration = decorationB; + }); + await tester.pump(); + + expect(decorationA.painters, hasLength(1)); + expect(decorationB.painters, hasLength(1)); + expect(decorationB.painters.last.paintCount, 1); + }); + + testWidgets('DecoratedSliver can update DecorationPosition', (WidgetTester tester) async { + final TestDecoration decoration = TestDecoration(); + + DecorationPosition activePosition = DecorationPosition.foreground; + late StateSetter localSetState; + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + localSetState = setState; + return CustomScrollView( + slivers: <Widget>[ + DecoratedSliver( + decoration: decoration, + position: activePosition, + sliver: const SliverToBoxAdapter( + child: SizedBox(width: 100, height: 100), + ), + ) + ], + ); + }, + ) + ) + )); + + expect(decoration.painters, hasLength(1)); + expect(decoration.painters.last.paintCount, 1); + + localSetState(() { + activePosition = DecorationPosition.background; + }); + await tester.pump(); + + expect(decoration.painters, hasLength(1)); + expect(decoration.painters.last.paintCount, 2); + }); + + testWidgets('DecoratedSliver golden test', (WidgetTester tester) async { + const BoxDecoration decoration = BoxDecoration( + color: Colors.blue, + ); + + final Key backgroundKey = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: RepaintBoundary( + key: backgroundKey, + child: CustomScrollView( + slivers: <Widget>[ + DecoratedSliver( + decoration: decoration, + sliver: SliverPadding( + padding: const EdgeInsets.all(16), + sliver: SliverList( + delegate: SliverChildListDelegate.fixed(<Widget>[ + Container( + height: 100, + color: Colors.red, + ), + Container( + height: 100, + color: Colors.yellow, + ), + Container( + height: 100, + color: Colors.red, + ), + ]), + ), + ), + ), + ], + ), + ), + ) + )); + + await expectLater(find.byKey(backgroundKey), matchesGoldenFile('decorated_sliver.moon.background.png')); + + final Key foregroundKey = UniqueKey(); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: RepaintBoundary( + key: foregroundKey, + child: CustomScrollView( + slivers: <Widget>[ + DecoratedSliver( + decoration: decoration, + position: DecorationPosition.foreground, + sliver: SliverPadding( + padding: const EdgeInsets.all(16), + sliver: SliverList( + delegate: SliverChildListDelegate.fixed(<Widget>[ + Container( + height: 100, + color: Colors.red, + ), + Container( + height: 100, + color: Colors.yellow, + ), + Container( + height: 100, + color: Colors.red, + ), + ]), + ), + ), + ), + ], + ), + ), + ) + )); + + await expectLater(find.byKey(foregroundKey), matchesGoldenFile('decorated_sliver.moon.foreground.png')); + }); + + testWidgets('DecoratedSliver paints its border correctly vertically', (WidgetTester tester) async { + const Key key = Key('DecoratedSliver with border'); + const Color black = Color(0xFF000000); + final ScrollController controller = ScrollController(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + height: 300, + width: 100, + child: CustomScrollView( + controller: controller, + slivers: <Widget>[ + DecoratedSliver( + key: key, + decoration: BoxDecoration(border: Border.all()), + sliver: const SliverToBoxAdapter( + child: SizedBox(width: 100, height: 500), + ), + ), + ], + ), + ), + ), + )); + controller.jumpTo(200); + await tester.pumpAndSettle(); + expect(find.byKey(key), paints..rect( + rect: const Offset(0.5, -199.5) & const Size(99, 499), + color: black, + style: PaintingStyle.stroke, + strokeWidth: 1.0, + )); + }); + + testWidgets('DecoratedSliver paints its border correctly vertically reverse', (WidgetTester tester) async { + const Key key = Key('DecoratedSliver with border'); + const Color black = Color(0xFF000000); + final ScrollController controller = ScrollController(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + height: 300, + width: 100, + child: CustomScrollView( + controller: controller, + reverse: true, + slivers: <Widget>[ + DecoratedSliver( + key: key, + decoration: BoxDecoration(border: Border.all()), + sliver: const SliverToBoxAdapter( + child: SizedBox(width: 100, height: 500), + ), + ), + ], + ), + ), + ), + )); + controller.jumpTo(200); + await tester.pumpAndSettle(); + expect(find.byKey(key), paints..rect( + rect: const Offset(0.5, -199.5) & const Size(99, 499), + color: black, + style: PaintingStyle.stroke, + strokeWidth: 1.0, + )); + }); + + + + testWidgets('DecoratedSliver paints its border correctly horizontally', (WidgetTester tester) async { + const Key key = Key('DecoratedSliver with border'); + const Color black = Color(0xFF000000); + final ScrollController controller = ScrollController(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + height: 100, + width: 300, + child: CustomScrollView( + scrollDirection: Axis.horizontal, + controller: controller, + slivers: <Widget>[ + DecoratedSliver( + key: key, + decoration: BoxDecoration(border: Border.all()), + sliver: const SliverToBoxAdapter( + child: SizedBox(width: 500, height: 100), + ), + ), + ], + ), + ), + ), + )); + controller.jumpTo(200); + await tester.pumpAndSettle(); + expect(find.byKey(key), paints..rect( + rect: const Offset(-199.5, 0.5) & const Size(499, 99), + color: black, + style: PaintingStyle.stroke, + strokeWidth: 1.0, + )); + }); + + testWidgets('DecoratedSliver paints its border correctly horizontally reverse', (WidgetTester tester) async { + const Key key = Key('DecoratedSliver with border'); + const Color black = Color(0xFF000000); + final ScrollController controller = ScrollController(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + height: 100, + width: 300, + child: CustomScrollView( + scrollDirection: Axis.horizontal, + reverse: true, + controller: controller, + slivers: <Widget>[ + DecoratedSliver( + key: key, + decoration: BoxDecoration(border: Border.all()), + sliver: const SliverToBoxAdapter( + child: SizedBox(width: 500, height: 100), + ), + ), + ], + ), + ), + ), + )); + controller.jumpTo(200); + await tester.pumpAndSettle(); + expect(find.byKey(key), paints..rect( + rect: const Offset(-199.5, 0.5) & const Size(499, 99), + color: black, + style: PaintingStyle.stroke, + strokeWidth: 1.0, + )); + }); + + + testWidgets('DecoratedSliver works with SliverMainAxisGroup', (WidgetTester tester) async { + const Key key = Key('DecoratedSliver with border'); + const Color black = Color(0xFF000000); + final ScrollController controller = ScrollController(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + height: 100, + width: 300, + child: CustomScrollView( + controller: controller, + slivers: <Widget>[ + DecoratedSliver( + key: key, + decoration: BoxDecoration(border: Border.all()), + sliver: const SliverMainAxisGroup( + slivers: <Widget>[ + SliverToBoxAdapter(child: SizedBox(height: 100)), + SliverToBoxAdapter(child: SizedBox(height: 100)), + ], + ), + ), + ], + ), + ), + ), + )); + await tester.pumpAndSettle(); + expect(find.byKey(key), paints..rect( + rect: const Offset(0.5, 0.5) & const Size(299, 199), + color: black, + style: PaintingStyle.stroke, + strokeWidth: 1.0, + )); + }); + + testWidgets('DecoratedSliver works with SliverCrossAxisGroup', (WidgetTester tester) async { + const Key key = Key('DecoratedSliver with border'); + const Color black = Color(0xFF000000); + final ScrollController controller = ScrollController(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + height: 100, + width: 300, + child: CustomScrollView( + controller: controller, + slivers: <Widget>[ + DecoratedSliver( + key: key, + decoration: BoxDecoration(border: Border.all()), + sliver: const SliverCrossAxisGroup( + slivers: <Widget>[ + SliverToBoxAdapter(child: SizedBox(height: 100)), + SliverToBoxAdapter(child: SizedBox(height: 100)), + ], + ), + ), + ], + ), + ), + ), + )); + await tester.pumpAndSettle(); + expect(find.byKey(key), paints..rect( + rect: const Offset(0.5, 0.5) & const Size(299, 99), + color: black, + style: PaintingStyle.stroke, + strokeWidth: 1.0, + )); + }); + + testWidgets('DecoratedSliver draws only up to the bottom cache when sliver has infinite scroll extent', (WidgetTester tester) async { + const Key key = Key('DecoratedSliver with border'); + const Color black = Color(0xFF000000); + final ScrollController controller = ScrollController(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + height: 100, + width: 300, + child: CustomScrollView( + controller: controller, + slivers: <Widget>[ + DecoratedSliver( + key: key, + decoration: BoxDecoration(border: Border.all()), + sliver: SliverList.builder( + itemBuilder: (BuildContext context, int index) => const SizedBox(height: 100), + ), + ), + ], + ), + ), + ), + )); + expect(find.byKey(key), paints..rect( + rect: const Offset(0.5, 0.5) & const Size(299, 349), + color: black, + style: PaintingStyle.stroke, + strokeWidth: 1.0, + )); + controller.jumpTo(200); + await tester.pumpAndSettle(); + // Note that the bottom edge is of the rect is the same as above. + expect(find.byKey(key), paints..rect( + rect: const Offset(0.5, -199.5) & const Size(299, 549), + color: black, + style: PaintingStyle.stroke, + strokeWidth: 1.0, + )); + }); +} + +class TestDecoration extends Decoration { + final List<TestBoxPainter> painters = <TestBoxPainter>[]; + + @override + BoxPainter createBoxPainter([VoidCallback? onChanged]) { + final TestBoxPainter painter = TestBoxPainter(); + painters.add(painter); + return painter; + } +} + +class TestBoxPainter extends BoxPainter { + Offset? lastOffset; + ImageConfiguration? lastConfiguration; + bool disposed = false; + int paintCount = 0; + + @override + void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) { + lastOffset = offset; + lastConfiguration = configuration; + paintCount += 1; + } + + @override + void dispose() { + assert(!disposed); + disposed = true; + super.dispose(); + } +} diff --git a/packages/flutter/test/widgets/default_colors_test.dart b/packages/flutter/test/widgets/default_colors_test.dart index c3f3eaf204db8..af9fa9dca52f0 100644 --- a/packages/flutter/test/widgets/default_colors_test.dart +++ b/packages/flutter/test/widgets/default_colors_test.dart @@ -83,7 +83,7 @@ void main() { child: Align( key: key, alignment: Alignment.topLeft, - child: const Text('Éxp', textDirection: TextDirection.ltr, style: TextStyle(fontSize: _crispText)), + child: const Text('Éxp', textDirection: TextDirection.ltr, style: TextStyle(fontSize: _crispText, color: Color(0xFF000000))), ), ), ), @@ -97,7 +97,7 @@ void main() { await _expectColors( tester, find.byType(Align), - <Color>{ const Color(0xFFFFFFFF) }, + <Color>{ const Color(0xFFFFFFFF), const Color(0xFF000000) }, ); // fake a "select all" event to select the text Actions.invoke(key.currentContext!, const SelectAllTextIntent(SelectionChangedCause.keyboard)); @@ -105,13 +105,13 @@ void main() { await _expectColors( tester, find.byType(Align), - <Color>{ const Color(0xFFFFFFFF), const Color(0xFFBFBFBF) }, // 0x80808080 blended with 0xFFFFFFFF + <Color>{ const Color(0xFFFFFFFF), const Color(0xFF000000), const Color(0xFFBFBFBF) }, // 0x80808080 blended with 0xFFFFFFFF <Offset, Color>{ - Offset.zero: const Color(0xFFBFBFBF), // the selected text - const Offset(10, 10): const Color(0xFFBFBFBF), // the selected text + Offset.zero: const Color(0xFF000000), // the selected text + const Offset(10, 10): const Color(0xFF000000), // the selected text const Offset(50, 95): const Color(0xFFBFBFBF), // the selected background (under the É) const Offset(250, 50): const Color(0xFFBFBFBF), // the selected background (above the p) - const Offset(250, 95): const Color(0xFFBFBFBF), // the selected text (the p) + const Offset(250, 95): const Color(0xFF000000), // the selected text (the p) const Offset(400, 400): const Color(0xFFFFFFFF), // the background const Offset(799, 599): const Color(0xFFFFFFFF), // the background }, diff --git a/packages/flutter/test/widgets/editable_text_cursor_test.dart b/packages/flutter/test/widgets/editable_text_cursor_test.dart index 7b79f183c4ca8..62640cb5c1a2d 100644 --- a/packages/flutter/test/widgets/editable_text_cursor_test.dart +++ b/packages/flutter/test/widgets/editable_text_cursor_test.dart @@ -771,6 +771,54 @@ void main() { await checkCursorBlinking(); }); + testWidgets('Turning showCursor off stops the cursor', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/108187. + final bool debugDeterministicCursor = EditableText.debugDeterministicCursor; + // This doesn't really matter. + EditableText.debugDeterministicCursor = false; + addTearDown(() { EditableText.debugDeterministicCursor = debugDeterministicCursor; }); + const Key key = Key('EditableText'); + + Widget buildEditableText({ required bool showCursor }) { + return MediaQuery( + data: const MediaQueryData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: EditableText( + key: key, + backgroundCursorColor: Colors.grey, + // Use animation controller to animate cursor blink for testing. + cursorOpacityAnimates: true, + controller: controller, + focusNode: focusNode, + style: textStyle, + cursorColor: cursorColor, + showCursor: showCursor, + ), + ), + ); + } + late final EditableTextState editableTextState = tester.state(find.byKey(key)); + await tester.pumpWidget(buildEditableText(showCursor: false)); + await tester.tap(find.byKey(key)); + await tester.pump(); + + // No cursor even when focused. + expect(editableTextState.cursorCurrentlyVisible, false); + + // The EditableText still has focus, so the cursor should starts blinking. + await tester.pumpWidget(buildEditableText(showCursor: true)); + expect(editableTextState.cursorCurrentlyVisible, true); + await tester.pump(); + expect(editableTextState.cursorCurrentlyVisible, true); + + // readOnly disables blinking cursor. + await tester.pumpWidget(buildEditableText(showCursor: false)); + expect(editableTextState.cursorCurrentlyVisible, false); + await tester.pump(); + expect(editableTextState.cursorCurrentlyVisible, false); + }); + // Regression test for https://github.com/flutter/flutter/pull/30475. testWidgets('Trying to select with the floating cursor does not crash', (WidgetTester tester) async { const String text = 'hello world this is fun and cool and awesome!'; @@ -879,6 +927,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Padding( padding: const EdgeInsets.only(top: 0.25), child: Material( diff --git a/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart b/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart index db23cc8cc55c4..2c4a9edbc5576 100644 --- a/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart +++ b/packages/flutter/test/widgets/editable_text_show_on_screen_test.dart @@ -276,7 +276,7 @@ void main() { final ScrollController scrollController = ScrollController(); final TextEditingController controller = TextEditingController(); final FocusNode focusNode = FocusNode(); - controller.text = 'Start\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nEnd'; + controller.text = "Start${'\n' * 39}End"; await tester.pumpWidget(MaterialApp( home: Center( @@ -322,6 +322,58 @@ void main() { expect(scrollController.offset, greaterThan(0.0)); }); + testWidgets('focused multi-line editable does not scroll to old position when non-collapsed selection set', (WidgetTester tester) async { + final ScrollController scrollController = ScrollController(); + final TextEditingController controller = TextEditingController(); + final FocusNode focusNode = FocusNode(); + final String text = "Start${'\n' * 39}End"; + controller.value = TextEditingValue(text: text, selection: TextSelection.collapsed(offset: text.length - 3)); + + await tester.pumpWidget(MaterialApp( + home: Center( + child: SizedBox( + height: 300.0, + child: ListView( + controller: scrollController, + children: <Widget>[ + EditableText( + backgroundCursorColor: Colors.grey, + maxLines: null, // multiline + controller: controller, + focusNode: focusNode, + style: textStyle, + cursorColor: cursorColor, + ), + ], + ), + ), + ), + )); + + // Bring keyboard up and verify that end of EditableText is not on screen. + await tester.showKeyboard(find.byType(EditableText)); + await tester.pumpAndSettle(); + + scrollController.jumpTo(0.0); + await tester.pumpAndSettle(); + final RenderBox render = tester.renderObject(find.byType(EditableText)); + expect(render.size.height, greaterThan(500.0)); + expect(scrollController.offset, 0.0); + + // Change selection to non-collapased so that cursor isn't shown + // and the location requires a bit of scroll. + tester.testTextInput.updateEditingValue(TextEditingValue( + text: text, + selection: const TextSelection(baseOffset: 26, extentOffset: 27), + )); + await tester.pumpAndSettle(); + + // Selection extent scrolls into view. + expect(find.byType(EditableText), findsOneWidget); + expect(render.size.height, greaterThan(500.0)); + expect(scrollController.offset, 28.0); + }); + testWidgets('scrolls into view with scrollInserts after the keyboard pops up', (WidgetTester tester) async { final ScrollController scrollController = ScrollController(); final TextEditingController controller = TextEditingController(); diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index ee94c538cb5e5..4cc86a032ac4d 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -15,6 +15,7 @@ import 'package:flutter_test/flutter_test.dart'; import '../rendering/mock_canvas.dart'; import '../widgets/clipboard_utils.dart'; import 'editable_text_utils.dart'; +import 'live_text_utils.dart'; import 'semantics_tester.dart'; Matcher matchesMethodCall(String method, { dynamic args }) => _MatchesMethodCall(method, arguments: args == null ? null : wrapMatcher(args)); @@ -118,6 +119,114 @@ void main() { expect(tester.testTextInput.setClientArgs!['inputAction'], equals(serializedActionName)); } + testWidgets( + 'Tapping the Live Text button calls onLiveTextInput', + (WidgetTester tester) async { + bool invokedLiveTextInputSuccessfully = false; + final GlobalKey key = GlobalKey(); + final TextEditingController controller = TextEditingController(text: ''); + await tester.pumpWidget( + MaterialApp( + home: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 400, + child: EditableText( + maxLines: 10, + controller: controller, + showSelectionHandles: true, + autofocus: true, + focusNode: FocusNode(), + style: Typography.material2018().black.subtitle1!, + cursorColor: Colors.blue, + backgroundCursorColor: Colors.grey, + keyboardType: TextInputType.text, + textAlign: TextAlign.right, + selectionControls: materialTextSelectionHandleControls, + contextMenuBuilder: ( + BuildContext context, + EditableTextState editableTextState, + ) { + return CupertinoAdaptiveTextSelectionToolbar.editable( + key: key, + clipboardStatus: ClipboardStatus.pasteable, + onCopy: null, + onCut: null, + onPaste: null, + onSelectAll: null, + onLiveTextInput: () { + invokedLiveTextInputSuccessfully = true; + }, + anchors: const TextSelectionToolbarAnchors(primaryAnchor: Offset.zero), + ); + }, + ), + ), + ), + ), + ); + + await tester.pump(); // Wait for autofocus to take effect. + + expect(find.byKey(key), findsNothing); + + // Long-press to bring up the context menu. + final Finder textFinder = find.byType(EditableText); + await tester.longPress(textFinder); + tester.state<EditableTextState>(textFinder).showToolbar(); + await tester.pumpAndSettle(); + + expect(find.byKey(key), findsOneWidget); + expect(findLiveTextButton(), findsOneWidget); + await tester.tap(findLiveTextButton()); + await tester.pump(); + expect(invokedLiveTextInputSuccessfully, isTrue); + }, + skip: kIsWeb, // [intended] + ); + + // Regression test for https://github.com/flutter/flutter/issues/126312. + testWidgets('when open input connection in didUpdateWidget, should not throw', (WidgetTester tester) async { + final Key key = GlobalKey(); + + await tester.pumpWidget( + MaterialApp( + home: EditableText( + key: key, + backgroundCursorColor: Colors.grey, + controller: TextEditingController(text: 'blah blah'), + focusNode: focusNode, + readOnly: true, + style: textStyle, + cursorColor: cursorColor, + selectionControls: materialTextSelectionControls, + ), + ), + ); + + focusNode.requestFocus(); + await tester.pump(); + + // Reparent the EditableText, so that the parent has not yet been laid + // out when didUpdateWidget is called. + await tester.pumpWidget( + MaterialApp( + home: FractionalTranslation( + translation: const Offset(0.1, 0.1), + child: EditableText( + key: key, + backgroundCursorColor: Colors.grey, + controller: TextEditingController(text: 'blah blah'), + focusNode: focusNode, + style: textStyle, + cursorColor: cursorColor, + selectionControls: materialTextSelectionControls, + ), + ), + ), + ); + }); + testWidgets('Text with selection can be shown on the screen when the keyboard shown', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/119628 addTearDown(tester.view.reset); @@ -9610,7 +9719,7 @@ void main() { expect(tester.takeException(), isNull); await tester.pumpWidget(Container()); - expect(tester.takeException(), isNotNull); + expect(tester.takeException(), isAssertionError); }); }); diff --git a/packages/flutter/test/widgets/ensure_visible_test.dart b/packages/flutter/test/widgets/ensure_visible_test.dart index e4155acc545b5..16f493804cadb 100644 --- a/packages/flutter/test/widgets/ensure_visible_test.dart +++ b/packages/flutter/test/widgets/ensure_visible_test.dart @@ -8,7 +8,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -Finder findKey(int i) => find.byKey(ValueKey<int>(i)); +Finder findKey(int i) => find.byKey(ValueKey<int>(i), skipOffstage: false); Widget buildSingleChildScrollView(Axis scrollDirection, { bool reverse = false }) { return Directionality( @@ -147,6 +147,47 @@ void main() { await tester.pump(); await tester.pump(const Duration(milliseconds: 1020)); expect(tester.getBottomRight(findKey(3)).dy, equals(500.0)); + + // Regression test for https://github.com/flutter/flutter/issues/128749 + // Reset to zero position. + tester.state<ScrollableState>(find.byType(Scrollable)).position.jumpTo(0.0); + await tester.pump(); + // 4 is not currently visible as the SingleChildScrollView is contained + // within a centered SizedBox. + expect(tester.getBottomLeft(findKey(4)).dy, equals(100.0)); + expect(tester.getBottomLeft(findKey(6)).dy, equals(500.0)); + Scrollable.ensureVisible( + findContext(6), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + Scrollable.ensureVisible( + findContext(5), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + // 5 and 6 are already visible beyond the top edge, so no change. + expect(tester.getBottomLeft(findKey(4)).dy, equals(100.0)); + expect(tester.getBottomLeft(findKey(6)).dy, equals(500.0)); + Scrollable.ensureVisible( + findContext(4), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + // Since it is reversed, 4 should have come into view at the top + // edge of the scrollable, matching the alignment expectation. + expect(tester.getBottomLeft(findKey(4)).dy, equals(300.0)); + expect(tester.getBottomLeft(findKey(6)).dy, equals(700.0)); + + // Bring 6 back into view at the trailing edge, checking the other + // alignment. + Scrollable.ensureVisible( + findContext(6), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd, + ); + await tester.pump(); + expect(tester.getBottomLeft(findKey(4)).dy, equals(100.0)); + expect(tester.getBottomLeft(findKey(6)).dy, equals(500.0)); }); testWidgets('SingleChildScrollView ensureVisible Axis.horizontal reverse', (WidgetTester tester) async { @@ -174,6 +215,52 @@ void main() { await tester.pump(); await tester.pump(const Duration(milliseconds: 1020)); expect(tester.getBottomRight(findKey(3)).dx, equals(700.0)); + + // Regression test for https://github.com/flutter/flutter/issues/128749 + // Reset to zero position. + tester.state<ScrollableState>(find.byType(Scrollable)).position.jumpTo(0.0); + await tester.pump(); + // 4 is not currently visible as the SingleChildScrollView is contained + // within a centered SizedBox. + expect(tester.getBottomLeft(findKey(3)).dx, equals(-100.0)); + expect(tester.getBottomLeft(findKey(6)).dx, equals(500.0)); + Scrollable.ensureVisible( + findContext(6), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + Scrollable.ensureVisible( + findContext(5), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + Scrollable.ensureVisible( + findContext(4), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + // 4, 5 and 6 are already visible beyond the left edge, so no change. + expect(tester.getBottomLeft(findKey(3)).dx, equals(-100.0)); + expect(tester.getBottomLeft(findKey(6)).dx, equals(500.0)); + Scrollable.ensureVisible( + findContext(3), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + // Since it is reversed, 3 should have come into view at the leading + // edge of the scrollable, matching the alignment expectation. + expect(tester.getBottomLeft(findKey(3)).dx, equals(100.0)); + expect(tester.getBottomLeft(findKey(6)).dx, equals(700.0)); + + // Bring 6 back into view at the trailing edge, checking the other + // alignment. + Scrollable.ensureVisible( + findContext(6), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd, + ); + await tester.pump(); + expect(tester.getBottomLeft(findKey(3)).dx, equals(-100.0)); + expect(tester.getBottomLeft(findKey(6)).dx, equals(500.0)); }); testWidgets('SingleChildScrollView ensureVisible rotated child', (WidgetTester tester) async { @@ -407,6 +494,46 @@ void main() { await tester.pump(); await tester.pump(const Duration(milliseconds: 1020)); expect(tester.getBottomRight(findKey(3)).dy, equals(500.0)); + + // Regression test for https://github.com/flutter/flutter/issues/128749 + // Reset to zero position. + await prepare(0.0); + // 2 is not currently visible as the ListView is contained + // within a centered SizedBox. + expect(tester.getBottomLeft(findKey(2)).dy, equals(100.0)); + expect(tester.getBottomLeft(findKey(0)).dy, equals(500.0)); + Scrollable.ensureVisible( + findContext(0), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + Scrollable.ensureVisible( + findContext(1), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + // 0 and 1 are already visible beyond the top edge, so no change. + expect(tester.getBottomLeft(findKey(2)).dy, equals(100.0)); + expect(tester.getBottomLeft(findKey(0)).dy, equals(500.0)); + Scrollable.ensureVisible( + findContext(2), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + // Since it is reversed, 2 should have come into view at the top + // edge of the scrollable, matching the alignment expectation. + expect(tester.getBottomLeft(findKey(2)).dy, equals(300.0)); + expect(tester.getBottomLeft(findKey(0)).dy, equals(700.0)); + + // Bring 0 back into view at the trailing edge, checking the other + // alignment. + Scrollable.ensureVisible( + findContext(0), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd, + ); + await tester.pump(); + expect(tester.getBottomLeft(findKey(2)).dy, equals(100.0)); + expect(tester.getBottomLeft(findKey(0)).dy, equals(500.0)); }); testWidgets('ListView ensureVisible Axis.horizontal reverse', (WidgetTester tester) async { @@ -443,6 +570,51 @@ void main() { await tester.pump(); await tester.pump(const Duration(milliseconds: 1020)); expect(tester.getBottomRight(findKey(3)).dx, equals(700.0)); + + // Regression test for https://github.com/flutter/flutter/issues/128749 + // Reset to zero position. + await prepare(0.0); + // 3 is not currently visible as the ListView is contained + // within a centered SizedBox. + expect(tester.getBottomLeft(findKey(3)).dx, equals(-100.0)); + expect(tester.getBottomLeft(findKey(0)).dx, equals(500.0)); + Scrollable.ensureVisible( + findContext(0), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + Scrollable.ensureVisible( + findContext(1), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + Scrollable.ensureVisible( + findContext(2), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + // 0, 1 and 2 are already visible beyond the left edge, so no change. + expect(tester.getBottomLeft(findKey(3)).dx, equals(-100.0)); + expect(tester.getBottomLeft(findKey(0)).dx, equals(500.0)); + Scrollable.ensureVisible( + findContext(3), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + // Since it is reversed, 3 should have come into view at the leading + // edge of the scrollable, matching the alignment expectation. + expect(tester.getBottomLeft(findKey(3)).dx, equals(100.0)); + expect(tester.getBottomLeft(findKey(0)).dx, equals(700.0)); + + // Bring 0 back into view at the trailing edge, checking the other + // alignment. + Scrollable.ensureVisible( + findContext(0), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd, + ); + await tester.pump(); + expect(tester.getBottomLeft(findKey(3)).dx, equals(-100.0)); + expect(tester.getBottomLeft(findKey(0)).dx, equals(500.0)); }); testWidgets('ListView ensureVisible negative child', (WidgetTester tester) async { @@ -662,6 +834,46 @@ void main() { await tester.pump(); await tester.pump(const Duration(milliseconds: 1020)); expect(tester.getBottomRight(findKey(3)).dy, equals(500.0)); + + // Regression test for https://github.com/flutter/flutter/issues/128749 + // Reset to zero position. + await prepare(0.0); + // 2 is not currently visible as the ListView is contained + // within a centered SizedBox. + expect(tester.getBottomLeft(findKey(2)).dy, equals(100.0)); + expect(tester.getBottomLeft(findKey(0)).dy, equals(500.0)); + Scrollable.ensureVisible( + findContext(0), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + Scrollable.ensureVisible( + findContext(1), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + // 0 and 1 are already visible beyond the top edge, so no change. + expect(tester.getBottomLeft(findKey(2)).dy, equals(100.0)); + expect(tester.getBottomLeft(findKey(0)).dy, equals(500.0)); + Scrollable.ensureVisible( + findContext(2), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + // Since it is reversed, 2 should have come into view at the top + // edge of the scrollable, matching the alignment expectation. + expect(tester.getBottomLeft(findKey(2)).dy, equals(300.0)); + expect(tester.getBottomLeft(findKey(0)).dy, equals(700.0)); + + // Bring 0 back into view at the trailing edge, checking the other + // alignment. + Scrollable.ensureVisible( + findContext(0), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd, + ); + await tester.pump(); + expect(tester.getBottomLeft(findKey(2)).dy, equals(100.0)); + expect(tester.getBottomLeft(findKey(0)).dy, equals(500.0)); }); testWidgets('ListView ensureVisible Axis.horizontal reverse', (WidgetTester tester) async { @@ -698,6 +910,51 @@ void main() { await tester.pump(); await tester.pump(const Duration(milliseconds: 1020)); expect(tester.getBottomRight(findKey(3)).dx, equals(700.0)); + + // Regression test for https://github.com/flutter/flutter/issues/128749 + // Reset to zero position. + await prepare(0.0); + // 3 is not currently visible as the ListView is contained + // within a centered SizedBox. + expect(tester.getBottomLeft(findKey(3)).dx, equals(-100.0)); + expect(tester.getBottomLeft(findKey(0)).dx, equals(500.0)); + Scrollable.ensureVisible( + findContext(0), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + Scrollable.ensureVisible( + findContext(1), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + Scrollable.ensureVisible( + findContext(2), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + // 0, 1 and 2 are already visible beyond the left edge, so no change. + expect(tester.getBottomLeft(findKey(3)).dx, equals(-100.0)); + expect(tester.getBottomLeft(findKey(0)).dx, equals(500.0)); + Scrollable.ensureVisible( + findContext(3), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart, + ); + await tester.pump(); + // Since it is reversed, 3 should have come into view at the leading + // edge of the scrollable, matching the alignment expectation. + expect(tester.getBottomLeft(findKey(3)).dx, equals(100.0)); + expect(tester.getBottomLeft(findKey(0)).dx, equals(700.0)); + + // Bring 0 back into view at the trailing edge, checking the other + // alignment. + Scrollable.ensureVisible( + findContext(0), + alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd, + ); + await tester.pump(); + expect(tester.getBottomLeft(findKey(3)).dx, equals(-100.0)); + expect(tester.getBottomLeft(findKey(0)).dx, equals(500.0)); }); }); diff --git a/packages/flutter/test/widgets/fade_in_image_test.dart b/packages/flutter/test/widgets/fade_in_image_test.dart index 94933c1193fac..a10a77bd2498c 100644 --- a/packages/flutter/test/widgets/fade_in_image_test.dart +++ b/packages/flutter/test/widgets/fade_in_image_test.dart @@ -30,16 +30,6 @@ class FadeInImageParts { expect(animatedFadeOutFadeInElement, isNotNull); return animatedFadeOutFadeInElement!.state; } - - Element? get semanticsElement { - Element? result; - fadeInImageElement.visitChildren((Element child) { - if (child.widget is Semantics) { - result = child; - } - }); - return result; - } } class FadeInImageElements { diff --git a/packages/flutter/test/widgets/focus_traversal_test.dart b/packages/flutter/test/widgets/focus_traversal_test.dart index 905771bcddaaf..e03f77628bf9b 100644 --- a/packages/flutter/test/widgets/focus_traversal_test.dart +++ b/packages/flutter/test/widgets/focus_traversal_test.dart @@ -331,7 +331,7 @@ void main() { policy: WidgetOrderTraversalPolicy(), child: Center( child: Builder(builder: (BuildContext context) { - return MaterialButton( + return ElevatedButton( key: key1, focusNode: testNode1, autofocus: true, @@ -340,7 +340,7 @@ void main() { MaterialPageRoute<void>( builder: (BuildContext context) { return Center( - child: MaterialButton( + child: ElevatedButton( key: key2, focusNode: testNode2, autofocus: true, @@ -1218,7 +1218,7 @@ void main() { child: Builder(builder: (BuildContext context) { return FocusTraversalOrder( order: const NumericFocusOrder(0), - child: MaterialButton( + child: ElevatedButton( key: key1, focusNode: testNode1, autofocus: true, @@ -1229,7 +1229,7 @@ void main() { return Center( child: FocusTraversalOrder( order: const NumericFocusOrder(0), - child: MaterialButton( + child: ElevatedButton( key: key2, focusNode: testNode2, autofocus: true, diff --git a/packages/flutter/test/widgets/framework_test.dart b/packages/flutter/test/widgets/framework_test.dart index 2e70d1f6a5b6a..4d82aa85308ab 100644 --- a/packages/flutter/test/widgets/framework_test.dart +++ b/packages/flutter/test/widgets/framework_test.dart @@ -1592,13 +1592,13 @@ void main() { builder.properties.any((DiagnosticsNode property) => property.name == 'dependencies' && property.value != null), isTrue, ); - final DiagnosticsProperty<List<DiagnosticsNode>> dependenciesProperty = - builder.properties.firstWhere((DiagnosticsNode property) => property.name == 'dependencies') as DiagnosticsProperty<List<DiagnosticsNode>>; + final DiagnosticsProperty<Set<InheritedElement>> dependenciesProperty = + builder.properties.firstWhere((DiagnosticsNode property) => property.name == 'dependencies') as DiagnosticsProperty<Set<InheritedElement>>; expect(dependenciesProperty, isNotNull); - final List<DiagnosticsNode> dependencies = dependenciesProperty.value!; + final Set<InheritedElement> dependencies = dependenciesProperty.value!; expect(dependencies.length, equals(3)); - expect(dependencies.toString(), '[ButtonBarTheme, Directionality, FocusTraversalOrder]'); + expect(dependenciesProperty.toDescription(), '[ButtonBarTheme, Directionality, FocusTraversalOrder]'); }); testWidgets('BuildOwner.globalKeyCount keeps track of in-use global keys', (WidgetTester tester) async { @@ -1689,7 +1689,7 @@ void main() { testWidgets('RenderObjectElement.unmount disposes of its renderObject', (WidgetTester tester) async { await tester.pumpWidget(const Placeholder()); - final RenderObjectElement element = tester.allElements.whereType<RenderObjectElement>().first; + final RenderObjectElement element = tester.allElements.whereType<RenderObjectElement>().last; final RenderObject renderObject = element.renderObject; expect(renderObject.debugDisposed, false); diff --git a/packages/flutter/test/widgets/heroes_test.dart b/packages/flutter/test/widgets/heroes_test.dart index a9a66029ce8b0..463f5ba99c8d4 100644 --- a/packages/flutter/test/widgets/heroes_test.dart +++ b/packages/flutter/test/widgets/heroes_test.dart @@ -1182,22 +1182,20 @@ Future<void> main() async { await tester.pump(const Duration(milliseconds: 100)); expect(tester.getTopLeft(find.byKey(heroABKey)).dy, 100.0); - bool isVisible(Element node) { - bool visible = true; - node.visitAncestorElements((Element ancestor) { - final RenderObject r = ancestor.renderObject!; - if (r is RenderAnimatedOpacity && r.opacity.value == 0) { - visible = false; + bool isVisible(RenderObject node) { + RenderObject? currentNode = node; + while (currentNode != null) { + if (currentNode is RenderAnimatedOpacity && currentNode.opacity.value == 0) { return false; } - return true; - }); - return visible; + currentNode = currentNode.parent; + } + return true; } // Of all heroes only one should be visible now. - final Iterable<Element> elements = find.text('Hero').evaluate(); - expect(elements.where(isVisible).length, 1); + final Iterable<RenderObject> renderObjects = find.text('Hero').evaluate().map((Element e) => e.renderObject!); + expect(renderObjects.where(isVisible).length, 1); // Hero BC's flight finishes normally. await tester.pump(const Duration(milliseconds: 300)); @@ -2921,7 +2919,7 @@ Future<void> main() async { final ScrollController controller = ScrollController(); RenderAnimatedOpacity? findRenderAnimatedOpacity() { - AbstractNode? parent = tester.renderObject(find.byType(Placeholder)); + RenderObject? parent = tester.renderObject(find.byType(Placeholder)); while (parent is RenderObject && parent is! RenderAnimatedOpacity) { parent = parent.parent; } diff --git a/packages/flutter/test/widgets/html_element_view_test.dart b/packages/flutter/test/widgets/html_element_view_test.dart index 4b58794dd0b57..b485aef6c9235 100644 --- a/packages/flutter/test/widgets/html_element_view_test.dart +++ b/packages/flutter/test/widgets/html_element_view_test.dart @@ -73,6 +73,42 @@ void main() { ); }); + testWidgets('Create HTML view with creation params', (WidgetTester tester) async { + final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); + final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController(); + viewsController.registerViewType('webview'); + await tester.pumpWidget( + const Column( + children: <Widget>[ + SizedBox( + width: 200.0, + height: 100.0, + child: HtmlElementView( + viewType: 'webview', + creationParams: 'foobar', + ), + ), + SizedBox( + width: 200.0, + height: 100.0, + child: HtmlElementView( + viewType: 'webview', + creationParams: 123, + ), + ), + ], + ), + ); + + expect( + viewsController.views, + unorderedEquals(<FakeHtmlPlatformView>[ + FakeHtmlPlatformView(currentViewId + 1, 'webview', 'foobar'), + FakeHtmlPlatformView(currentViewId + 2, 'webview', 123), + ]), + ); + }); + testWidgets('Resize HTML view', (WidgetTester tester) async { final int currentViewId = platformViewsRegistry.getNextPlatformViewId(); final FakeHtmlPlatformViewsController viewsController = FakeHtmlPlatformViewsController(); diff --git a/packages/flutter/test/widgets/icon_test.dart b/packages/flutter/test/widgets/icon_test.dart index 93cb51147d7f2..4b53d670a33ed 100644 --- a/packages/flutter/test/widgets/icon_test.dart +++ b/packages/flutter/test/widgets/icon_test.dart @@ -127,6 +127,20 @@ void main() { expect(richText.text.style!.fontFamily, equals('Roboto')); }); + testWidgets('Icon with custom fontFamilyFallback', (WidgetTester tester) async { + await tester.pumpWidget( + const Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: Icon(IconData(0x41, fontFamilyFallback: <String>['FallbackFont'])), + ), + ), + ); + + final RichText richText = tester.firstWidget(find.byType(RichText)); + expect(richText.text.style!.fontFamilyFallback, equals(<String>['FallbackFont'])); + }); + testWidgets('Icon with semantic label', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); diff --git a/packages/flutter/test/widgets/image_filter_test.dart b/packages/flutter/test/widgets/image_filter_test.dart index 612456681152d..29da67bae4d7a 100644 --- a/packages/flutter/test/widgets/image_filter_test.dart +++ b/packages/flutter/test/widgets/image_filter_test.dart @@ -95,7 +95,7 @@ void main() { imageFilter: matrix, child: MaterialApp( title: 'Flutter Demo', - theme: ThemeData(primarySwatch: Colors.blue), + theme: ThemeData(useMaterial3: false, primarySwatch: Colors.blue), home: Scaffold( appBar: AppBar( title: const Text('Matrix ImageFilter Test'), @@ -132,7 +132,7 @@ void main() { imageFilter: matrixFilter, child: MaterialApp( title: 'Flutter Demo', - theme: ThemeData(primarySwatch: Colors.blue), + theme: ThemeData(useMaterial3: false, primarySwatch: Colors.blue), home: Scaffold( appBar: AppBar( title: const Text('Matrix ImageFilter Test'), diff --git a/packages/flutter/test/widgets/image_resolution_test.dart b/packages/flutter/test/widgets/image_resolution_test.dart index dedd14adcd793..46f0478058d59 100644 --- a/packages/flutter/test/widgets/image_resolution_test.dart +++ b/packages/flutter/test/widgets/image_resolution_test.dart @@ -19,16 +19,15 @@ import '../image_data.dart'; ByteData testByteData(double scale) => ByteData(8)..setFloat64(0, scale); double scaleOf(ByteData data) => data.getFloat64(0); -final Map<Object?, Object?> testManifest = json.decode(''' -{ - "assets/image.png" : [ - {"asset": "assets/1.5x/image.png", "dpr": 1.5}, - {"asset": "assets/2.0x/image.png", "dpr": 2.0}, - {"asset": "assets/3.0x/image.png", "dpr": 3.0}, - {"asset": "assets/4.0x/image.png", "dpr": 4.0} - ] -} -''') as Map<Object?, Object?>; +final Map<Object?, Object?> testManifest = <Object?, Object?>{ + 'assets/image.png' : <Map<String, Object>>[ + <String, String>{'asset': 'assets/image.png'}, + <String, Object>{'asset': 'assets/1.5x/image.png', 'dpr': 1.5}, + <String, Object>{'asset': 'assets/2.0x/image.png', 'dpr': 2.0}, + <String, Object>{'asset': 'assets/3.0x/image.png', 'dpr': 3.0}, + <String, Object>{'asset': 'assets/4.0x/image.png', 'dpr': 4.0} + ], +}; class TestAssetBundle extends CachingAssetBundle { TestAssetBundle({ required Map<Object?, Object?> manifest }) { @@ -41,7 +40,7 @@ class TestAssetBundle extends CachingAssetBundle { Future<ByteData> load(String key) { late ByteData data; switch (key) { - case 'AssetManifest.smcbin': + case 'AssetManifest.bin': data = manifest; case 'assets/image.png': data = testByteData(1.0); @@ -303,13 +302,12 @@ void main() { // if higher resolution assets are not available we will pick the best // available. testWidgets('Low-resolution assets', (WidgetTester tester) async { - final Map<Object?, Object?> manifest = json.decode(''' - { - "assets/image.png" : [ - {"asset": "assets/1.5x/image.png", "dpr": 1.5} - ] - } - ''') as Map<Object?, Object?>; + const Map<Object?, Object?> manifest = <Object?, Object?>{ + 'assets/image.png': <Map<String, Object>>[ + <String, Object>{'asset': 'assets/image.png'}, + <String, Object>{'asset': 'assets/1.5x/image.png', 'dpr': 1.5}, + ], + }; final AssetBundle bundle = TestAssetBundle(manifest: manifest); Future<void> testRatio({required double ratio, required double expectedScale}) async { diff --git a/packages/flutter/test/widgets/interactive_viewer_test.dart b/packages/flutter/test/widgets/interactive_viewer_test.dart index 789d18dc16896..b2adda8d87b1f 100644 --- a/packages/flutter/test/widgets/interactive_viewer_test.dart +++ b/packages/flutter/test/widgets/interactive_viewer_test.dart @@ -1840,6 +1840,57 @@ void main() { expect(transformationController.value.getMaxScaleOnAxis(), moreOrLessEquals(1.499302500056767)); }); + testWidgets('trackpad pointer scroll events cause scale', (WidgetTester tester) async { + final TransformationController transformationController = TransformationController(); + const double boundaryMargin = 50.0; + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: InteractiveViewer( + boundaryMargin: const EdgeInsets.all(boundaryMargin), + transformationController: transformationController, + trackpadScrollCausesScale: true, + child: const SizedBox(width: 200.0, height: 200.0), + ), + ), + ), + ), + ); + + expect(transformationController.value.getMaxScaleOnAxis(), 1.0); + + // Send a vertical scroll. + final TestPointer pointer = TestPointer(1, PointerDeviceKind.trackpad); + final Offset center = tester.getCenter(find.byType(SizedBox)); + Offset scrollAmnt = const Offset(0, -138.0); + await tester.sendEventToBinding(pointer.hover(center)); + await tester.pump(); + expect(transformationController.value.getMaxScaleOnAxis(), 1.0); + await tester.sendEventToBinding(pointer.scroll(scrollAmnt)); + await tester.pump(); + expect(transformationController.value.getMaxScaleOnAxis(), moreOrLessEquals(1.9937155332430823)); + + // Scroll should not have translated the box, so the box should still be at the + // center of the InteractiveViewer. + Vector3 translation = transformationController.value.getTranslation(); + expect(translation.x, moreOrLessEquals(-99.37155332430822)); + expect(translation.y, moreOrLessEquals(-99.37155332430822)); + + // Send a horizontal scroll. + scrollAmnt = const Offset(-138, 0); + await tester.sendEventToBinding(pointer.scroll(scrollAmnt)); + await tester.pump(); + + // Horizontal scroll should not cause a scale change. + expect(transformationController.value.getMaxScaleOnAxis(), moreOrLessEquals(1.9937155332430823)); + + // Horizontal scroll should not have changed the translation of the box. + translation = transformationController.value.getTranslation(); + expect(translation.x, moreOrLessEquals(-99.37155332430822)); + expect(translation.y, moreOrLessEquals(-99.37155332430822)); + }); + testWidgets('Scaling inertia', (WidgetTester tester) async { final TransformationController transformationController = TransformationController(); const double boundaryMargin = 50.0; diff --git a/packages/flutter/test/widgets/list_view_viewporting_test.dart b/packages/flutter/test/widgets/list_view_viewporting_test.dart index 277b20e0d1512..43739d6a74fc4 100644 --- a/packages/flutter/test/widgets/list_view_viewporting_test.dart +++ b/packages/flutter/test/widgets/list_view_viewporting_test.dart @@ -231,7 +231,7 @@ void main() { testWidgets('ListView reinvoke builders', (WidgetTester tester) async { late StateSetter setState; - ThemeData themeData = ThemeData.light(); + ThemeData themeData = ThemeData.light(useMaterial3: false); Widget itemBuilder(BuildContext context, int index) { return Container( @@ -263,7 +263,7 @@ void main() { expect(widget.color, equals(Colors.blue)); setState(() { - themeData = ThemeData(primarySwatch: Colors.green); + themeData = ThemeData(primarySwatch: Colors.green, useMaterial3: false); }); await tester.pump(); diff --git a/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart b/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart index 2e6d8e6b4eeb2..a762693219cb0 100644 --- a/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart +++ b/packages/flutter/test/widgets/list_wheel_scroll_view_test.dart @@ -1644,7 +1644,7 @@ void main() { }); testWidgets('ListWheelScrollView does not crash and does not allow taps on children that were laid out, but not painted', (WidgetTester tester) async { - // Regression test for https://github.com/flutter/flutter/issues/12649 + // Regression test for https://github.com/flutter/flutter/issues/126491 final FixedExtentScrollController controller = FixedExtentScrollController(); final List<int> children = List<int>.generate(100, (int index) => index); diff --git a/packages/flutter/test/widgets/live_text_utils.dart b/packages/flutter/test/widgets/live_text_utils.dart new file mode 100644 index 0000000000000..5e7b3c0ffcd20 --- /dev/null +++ b/packages/flutter/test/widgets/live_text_utils.dart @@ -0,0 +1,40 @@ +// 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/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +/// A mock class to control the return result of Live Text input functions. +class LiveTextInputTester { + LiveTextInputTester() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, _handler); + } + + bool mockLiveTextInputEnabled = false; + + Future<Object?> _handler(MethodCall methodCall) async { + // Need to set Clipboard.hasStrings method handler because when showing the tool bar, + // the Clipboard.hasStrings will also be invoked. If this isn't handled, + // an exception will be thrown. + if (methodCall.method == 'Clipboard.hasStrings') { + return <String, bool>{'value': true}; + } + if (methodCall.method == 'LiveText.isLiveTextInputAvailable') { + return mockLiveTextInputEnabled; + } + return false; + } + + void dispose() { + assert(TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.checkMockMessageHandler(SystemChannels.platform.name, _handler)); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, null); + } +} + +/// A function to find the live text button. +Finder findLiveTextButton() => find.byWidgetPredicate((Widget widget) => + widget is CustomPaint && + '${widget.painter?.runtimeType}' == '_LiveTextIconPainter', +); diff --git a/packages/flutter/test/widgets/mark_needs_build_test.dart b/packages/flutter/test/widgets/mark_needs_build_test.dart index dbe1d3c4728b2..9fa2d008b3360 100644 --- a/packages/flutter/test/widgets/mark_needs_build_test.dart +++ b/packages/flutter/test/widgets/mark_needs_build_test.dart @@ -106,7 +106,7 @@ class _TestWidgetState extends State<TestWidget> { calledDuringBuild++; }); return SizedBox.expand( - child: Text(Directionality.of(context).toString()), + child: Text('${widget.value}: ${Directionality.of(context)}'), ); } } diff --git a/packages/flutter/test/widgets/nested_scroll_view_test.dart b/packages/flutter/test/widgets/nested_scroll_view_test.dart index c418746510300..c4b6e271aa8b5 100644 --- a/packages/flutter/test/widgets/nested_scroll_view_test.dart +++ b/packages/flutter/test/widgets/nested_scroll_view_test.dart @@ -32,17 +32,8 @@ Widget buildTest({ Key? key, bool expanded = true, }) { - return Localizations( - locale: const Locale('en', 'US'), - delegates: const <LocalizationsDelegate<dynamic>>[ - DefaultMaterialLocalizations.delegate, - DefaultWidgetsLocalizations.delegate, - ], - child: Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(), - child: Scaffold( + return MaterialApp( + home: Scaffold( drawerDragStartBehavior: DragStartBehavior.down, body: DefaultTabController( length: 4, @@ -114,8 +105,6 @@ Widget buildTest({ ), ), ), - ), - ), ); } @@ -577,7 +566,7 @@ void main() { const List<String> tabs = <String>['Hello', 'World']; int buildCount = 0; await tester.pumpWidget( - MaterialApp(home: Material(child: + MaterialApp(theme: ThemeData(useMaterial3: false), home: Material(child: // THE FOLLOWING SECTION IS FROM THE NestedScrollView DOCUMENTATION // (EXCEPT FOR THE CHANGES TO THE buildCount COUNTER) DefaultTabController( @@ -871,7 +860,11 @@ void main() { headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { return <Widget>[ const SliverPersistentHeader( - delegate: TestHeader(key: key1), + delegate: TestHeader( + key: key1, + minExtent: 100.0, + maxExtent: 100.0, + ), ), ]; }, @@ -2270,6 +2263,7 @@ void main() { // Regression tests for https://github.com/flutter/flutter/issues/17096 Widget buildBallisticTest(ScrollController controller) { return MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: NestedScrollView( controller: controller, @@ -2908,17 +2902,324 @@ void main() { // Restructuring inner scrollable while scroll is in progress shouldn't crash. await tester.pumpWidget(buildApp(nested: true)); }); + + testWidgets('SliverOverlapInjector asserts when there is no SliverOverlapAbsorber', (WidgetTester tester) async { + Widget buildApp() { + return MaterialApp( + home: Scaffold( + body: NestedScrollView( + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { + return <Widget>[ + const SliverAppBar(), + ]; + }, + body: Builder( + builder: (BuildContext context) { + return CustomScrollView( + slivers: <Widget>[ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + ], + ); + } + ), + ), + ), + ); + } + final List<Object> exceptions = <Object>[]; + final FlutterExceptionHandler? oldHandler = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + await tester.pumpWidget(buildApp()); + FlutterError.onError = oldHandler; + expect(exceptions.length, 4); + expect(exceptions[0], isAssertionError); + expect( + (exceptions[0] as AssertionError).message, + contains('SliverOverlapInjector has found no absorbed extent to inject.'), + ); + }); + + group('NestedScrollView properly sets drag', () { + Future<bool> canDrag(WidgetTester tester) async { + await tester.drag( + find.byType(CustomScrollView), + const Offset(0.0, -20.0), + ); + await tester.pumpAndSettle(); + final NestedScrollViewState nestedScrollView = tester.state<NestedScrollViewState>( + find.byType(NestedScrollView) + ); + return nestedScrollView.outerController.position.pixels > 0.0 + || nestedScrollView.innerController.position.pixels > 0.0; + } + + Widget buildTest({ + // The body length is to test when the nested scroll view should or + // should not be allowing drag. + required _BodyLength bodyLength, + Widget? header, + bool applyOverlap = false, + }) { + return MaterialApp( + home: Scaffold( + body: NestedScrollView( + headerSliverBuilder: (BuildContext context, _) { + if (applyOverlap) { + return <Widget>[ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: header, + ), + ]; + } + return header != null ? <Widget>[ header ] : <Widget>[]; + }, + body: Builder( + builder: (BuildContext context) { + return CustomScrollView( + slivers: <Widget>[ + SliverList.builder( + itemCount: switch (bodyLength) { + _BodyLength.short => 10, + _BodyLength.long => 100, + }, + itemBuilder: (_, int index) => Text('Item $index'), + ), + ], + ); + } + ), + ), + ) + ); + } + testWidgets('when headerSliverBuilder is empty', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/117316 + // Regression test for https://github.com/flutter/flutter/issues/46089 + // Short body / long body + for (final _BodyLength bodyLength in _BodyLength.values) { + await tester.pumpWidget( + buildTest(bodyLength: bodyLength), + ); + await tester.pumpAndSettle(); + switch (bodyLength) { + case _BodyLength.short: + expect(await canDrag(tester), isFalse); + case _BodyLength.long: + expect(await canDrag(tester), isTrue); + } + } + }, variant: TargetPlatformVariant.all()); + + testWidgets('when headerSliverBuilder extent is 0', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/79077 + // Short body / long body + for (final _BodyLength bodyLength in _BodyLength.values) { + // SliverPersistentHeader + await tester.pumpWidget( + buildTest( + bodyLength: bodyLength, + header: const SliverPersistentHeader( + delegate: TestHeader(minExtent: 0.0, maxExtent: 0.0), + ), + ), + ); + await tester.pumpAndSettle(); + switch (bodyLength) { + case _BodyLength.short: + expect(await canDrag(tester), isFalse); + case _BodyLength.long: + expect(await canDrag(tester), isTrue); + } + + // SliverPersistentHeader pinned + await tester.pumpWidget( + buildTest( + bodyLength: bodyLength, + header: const SliverPersistentHeader( + pinned: true, + delegate: TestHeader(minExtent: 0.0, maxExtent: 0.0), + ), + ), + ); + await tester.pumpAndSettle(); + switch (bodyLength) { + case _BodyLength.short: + expect(await canDrag(tester), isFalse); + case _BodyLength.long: + expect(await canDrag(tester), isTrue); + } + + // SliverPersistentHeader floating + await tester.pumpWidget( + buildTest( + bodyLength: bodyLength, + header: const SliverPersistentHeader( + floating: true, + delegate: TestHeader(minExtent: 0.0, maxExtent: 0.0), + ), + ), + ); + await tester.pumpAndSettle(); + switch (bodyLength) { + case _BodyLength.short: + expect(await canDrag(tester), isFalse); + case _BodyLength.long: + expect(await canDrag(tester), isTrue); + } + + // SliverPersistentHeader pinned+floating + await tester.pumpWidget( + buildTest( + bodyLength: bodyLength, + header: const SliverPersistentHeader( + pinned: true, + floating: true, + delegate: TestHeader(minExtent: 0.0, maxExtent: 0.0), + ), + ), + ); + await tester.pumpAndSettle(); + switch (bodyLength) { + case _BodyLength.short: + expect(await canDrag(tester), isFalse); + case _BodyLength.long: + expect(await canDrag(tester), isTrue); + } + + // SliverPersistentHeader w/ overlap + await tester.pumpWidget( + buildTest( + bodyLength: bodyLength, + applyOverlap: true, + header: const SliverPersistentHeader( + delegate: TestHeader(minExtent: 0.0, maxExtent: 0.0), + ), + ), + ); + await tester.pumpAndSettle(); + switch (bodyLength) { + case _BodyLength.short: + expect(await canDrag(tester), isFalse); + case _BodyLength.long: + expect(await canDrag(tester), isTrue); + } + + // SliverPersistentHeader pinned w/ overlap + await tester.pumpWidget( + buildTest( + bodyLength: bodyLength, + applyOverlap: true, + header: const SliverPersistentHeader( + pinned: true, + delegate: TestHeader(minExtent: 0.0, maxExtent: 0.0), + ), + ), + ); + await tester.pumpAndSettle(); + switch (bodyLength) { + case _BodyLength.short: + expect(await canDrag(tester), isFalse); + case _BodyLength.long: + expect(await canDrag(tester), isTrue); + } + + // SliverPersistentHeader floating w/ overlap + await tester.pumpWidget( + buildTest( + bodyLength: bodyLength, + applyOverlap: true, + header: const SliverPersistentHeader( + floating: true, + delegate: TestHeader(minExtent: 0.0, maxExtent: 0.0), + ), + ), + ); + await tester.pumpAndSettle(); + switch (bodyLength) { + case _BodyLength.short: + expect(await canDrag(tester), isFalse); + case _BodyLength.long: + expect(await canDrag(tester), isTrue); + } + + // SliverPersistentHeader pinned+floating w/ overlap + await tester.pumpWidget( + buildTest( + bodyLength: bodyLength, + applyOverlap: true, + header: const SliverPersistentHeader( + floating: true, + pinned: true, + delegate: TestHeader(minExtent: 0.0, maxExtent: 0.0), + ), + ), + ); + await tester.pumpAndSettle(); + switch (bodyLength) { + case _BodyLength.short: + expect(await canDrag(tester), isFalse); + case _BodyLength.long: + expect(await canDrag(tester), isTrue); + } + } + }, variant: TargetPlatformVariant.all()); + + testWidgets('With a pinned SliverAppBar', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/110956 + // Regression test for https://github.com/flutter/flutter/issues/127282 + // Regression test for https://github.com/flutter/flutter/issues/32563 + // Regression test for https://github.com/flutter/flutter/issues/79077 + // Short / long body + for (final _BodyLength bodyLength in _BodyLength.values) { + await tester.pumpWidget( + buildTest( + bodyLength: bodyLength, + applyOverlap: true, + header: const SliverAppBar( + title: Text('Test'), + pinned: true, + bottom: PreferredSize( + preferredSize: Size.square(25), + child: SizedBox(), + ), + ), + ), + ); + await tester.pumpAndSettle(); + switch (bodyLength) { + case _BodyLength.short: + expect(await canDrag(tester), isFalse); + case _BodyLength.long: + expect(await canDrag(tester), isTrue); + } + } + }); + }); } double appBarHeight(WidgetTester tester) => tester.getSize(find.byType(AppBar, skipOffstage: false)).height; +enum _BodyLength { + short, + long, +} + class TestHeader extends SliverPersistentHeaderDelegate { - const TestHeader({ this.key }); + const TestHeader({ + this.key, + required this.minExtent, + required this.maxExtent, + }); final Key? key; @override - double get minExtent => 100.0; + final double minExtent; @override - double get maxExtent => 100.0; + final double maxExtent; @override Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { return Placeholder(key: key); diff --git a/packages/flutter/test/widgets/opacity_test.dart b/packages/flutter/test/widgets/opacity_test.dart index 73f97f327518c..804f35a0dbbc8 100644 --- a/packages/flutter/test/widgets/opacity_test.dart +++ b/packages/flutter/test/widgets/opacity_test.dart @@ -155,6 +155,7 @@ void main() { testWidgets('offset is correctly handled in Opacity', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: SingleChildScrollView( child: RepaintBoundary( diff --git a/packages/flutter/test/widgets/overlay_portal_test.dart b/packages/flutter/test/widgets/overlay_portal_test.dart index 85b1a71a677f5..702a5db64058d 100644 --- a/packages/flutter/test/widgets/overlay_portal_test.dart +++ b/packages/flutter/test/widgets/overlay_portal_test.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -29,7 +28,7 @@ class _ManyRelayoutBoundaries extends StatelessWidget { void rebuildLayoutBuilderSubtree(RenderBox descendant) { assert(descendant is! RenderConstrainedLayoutBuilder<BoxConstraints, RenderBox>); - AbstractNode? node = descendant.parent; + RenderObject? node = descendant.parent; while (node != null) { if (node is! RenderConstrainedLayoutBuilder<BoxConstraints, RenderBox>) { node = node.parent; @@ -74,7 +73,7 @@ List<RenderObject> _ancestorRenderTheaters(RenderObject child) { if (node.runtimeType.toString() == '_RenderTheater') { results.add(node); } - final AbstractNode? parent = node.parent; + final RenderObject? parent = node.parent; node = parent is RenderObject? parent : null; } return results; @@ -165,6 +164,97 @@ void main() { await tester.pumpWidget(SizedBox(child: widget)); }); + testWidgets('Safe to hide overlay child and remove OverlayPortal in the same frame', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/129025. + final Widget widget = Directionality( + key: GlobalKey(debugLabel: 'key'), + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: <OverlayEntry>[ + OverlayEntry( + builder: (BuildContext context) { + return OverlayPortal( + controller: controller1, + overlayChildBuilder: (BuildContext context) => const SizedBox(), + child: const SizedBox(), + ); + }, + ), + ], + ), + ); + + controller1.show(); + await tester.pumpWidget(widget); + + controller1.hide(); + await tester.pumpWidget(const SizedBox()); + expect(tester.takeException(), isNull); + }); + + testWidgets('Safe to hide overlay child and reparent OverlayPortal in the same frame', (WidgetTester tester) async { + final OverlayPortal overlayPortal = OverlayPortal( + key: GlobalKey(debugLabel: 'key'), + controller: controller1, + overlayChildBuilder: (BuildContext context) => const SizedBox(), + child: const SizedBox(), + ); + + List<Widget> children = <Widget>[ const SizedBox(), overlayPortal ]; + + late StateSetter setState; + final Widget widget = Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: <OverlayEntry>[ + OverlayStatefulEntry( + builder: (BuildContext context, StateSetter setter) { + setState = setter; + return Column(children: children); + }, + ), + ], + ), + ); + + controller1.show(); + await tester.pumpWidget(widget); + + controller1.hide(); + setState(() { + children = <Widget>[ overlayPortal, const SizedBox() ]; + }); + await tester.pumpWidget(widget); + expect(tester.takeException(), isNull); + }); + + testWidgets('Safe to hide overlay child and reparent OverlayPortal in the same frame 2', (WidgetTester tester) async { + final Widget widget = Directionality( + key: GlobalKey(debugLabel: 'key'), + textDirection: TextDirection.ltr, + child: Overlay( + initialEntries: <OverlayEntry>[ + OverlayEntry( + builder: (BuildContext context) { + return OverlayPortal( + controller: controller1, + overlayChildBuilder: (BuildContext context) => const SizedBox(), + child: const SizedBox(), + ); + }, + ), + ], + ), + ); + + controller1.show(); + await tester.pumpWidget(widget); + + controller1.hide(); + await tester.pumpWidget(SizedBox(child: widget)); + expect(tester.takeException(), isNull); + }); + testWidgets('Throws when the same controller is attached to multiple OverlayPortal', (WidgetTester tester) async { final OverlayPortalController controller = OverlayPortalController(debugLabel: 'local controller'); final Widget widget = Directionality( @@ -565,6 +655,63 @@ void main() { verifyTreeIsClean(); }); + testWidgets('Adding/Removing OverlayPortal in LayoutBuilder during layout', (WidgetTester tester) async { + final GlobalKey widgetKey = GlobalKey(debugLabel: 'widget'); + final GlobalKey overlayKey = GlobalKey(debugLabel: 'overlay'); + controller1.hide(); + late StateSetter setState; + Size size = Size.zero; + + final Widget overlayPortal = OverlayPortal( + key: widgetKey, + controller: controller1, + overlayChildBuilder: (BuildContext context) => const Placeholder(), + child: const Placeholder(), + ); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + key: overlayKey, + initialEntries: <OverlayEntry>[ + OverlayEntry( + builder: (BuildContext context) { + return StatefulBuilder( + builder: (BuildContext context, StateSetter stateSetter) { + setState = stateSetter; + return Center( + child: SizedBox.fromSize( + size: size, + child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { + // This layout callback adds/removes an OverlayPortal during layout. + return constraints.maxHeight > 0 ? overlayPortal : const SizedBox(); + }), + ), + ); + } + ); + } + ), + ], + ), + ), + ); + controller1.show(); + await tester.pump(); + expect(tester.takeException(), isNull); + + // Adds the OverlayPortal from within a LayoutBuilder, in a layout callback. + setState(() { size = const Size(300, 300); }); + await tester.pump(); + expect(tester.takeException(), isNull); + + // Removes the OverlayPortal from within a LayoutBuilder, in a layout callback. + setState(() { size = Size.zero; }); + await tester.pump(); + expect(tester.takeException(), isNull); + }); + testWidgets('Change overlay constraints', (WidgetTester tester) async { final GlobalKey widgetKey = GlobalKey(debugLabel: 'widget outer'); final GlobalKey overlayKey = GlobalKey(debugLabel: 'overlay'); @@ -1408,6 +1555,64 @@ void main() { expect(counter2.layoutCount, 3); }); }); + + testWidgets('Safe to move the overlay child to a different Overlay and remove the old Overlay', (WidgetTester tester) async { + controller1.show(); + final GlobalKey key = GlobalKey(debugLabel: 'key'); + final GlobalKey oldOverlayKey = GlobalKey(debugLabel: 'old overlay'); + final GlobalKey newOverlayKey = GlobalKey(debugLabel: 'new overlay'); + final GlobalKey overlayChildKey = GlobalKey(debugLabel: 'overlay child key'); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + key: oldOverlayKey, + initialEntries: <OverlayEntry>[ + OverlayEntry( + builder: (BuildContext context) { + return OverlayPortal( + key: key, + controller: controller1, + overlayChildBuilder: (BuildContext context) => SizedBox(key: overlayChildKey), + child: const SizedBox(), + ); + }, + ), + ], + ), + ), + ); + + expect(find.byKey(overlayChildKey), findsOneWidget); + expect(find.byKey(newOverlayKey), findsNothing); + expect(find.byKey(oldOverlayKey), findsOneWidget); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: Overlay( + key: newOverlayKey, + initialEntries: <OverlayEntry>[ + OverlayEntry( + builder: (BuildContext context) { + return OverlayPortal( + key: key, + controller: controller1, + overlayChildBuilder: (BuildContext context) => SizedBox(key: overlayChildKey), + child: const SizedBox(), + ); + }, + ), + ], + ), + ), + ); + + expect(tester.takeException(), isNull); + expect(find.byKey(overlayChildKey), findsOneWidget); + expect(find.byKey(newOverlayKey), findsOneWidget); + expect(find.byKey(oldOverlayKey), findsNothing); + }); }); group('Paint order', () { @@ -1525,14 +1730,14 @@ void main() { final List<RenderObject> childrenVisited = <RenderObject>[]; theater.visitChildren(childrenVisited.add); expect(childrenVisited.length, 3); - expect(childrenVisited, containsAllInOrder(<AbstractNode>[child1Box.parent!, child2Box.parent!])); + expect(childrenVisited, containsAllInOrder(<RenderObject>[child1Box.parent!, child2Box.parent!])); childrenVisited.clear(); setState(() { reparented = true; }); await tester.pump(); theater.visitChildren(childrenVisited.add); // The child list stays the same. - expect(childrenVisited, containsAllInOrder(<AbstractNode>[child1Box.parent!, child2Box.parent!])); + expect(childrenVisited, containsAllInOrder(<RenderObject>[child1Box.parent!, child2Box.parent!])); }); }); } diff --git a/packages/flutter/test/widgets/page_route_builder_test.dart b/packages/flutter/test/widgets/page_route_builder_test.dart index 6bf6b7c9b9f1c..3e6090fa80195 100644 --- a/packages/flutter/test/widgets/page_route_builder_test.dart +++ b/packages/flutter/test/widgets/page_route_builder_test.dart @@ -11,13 +11,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; class TestPage extends StatelessWidget { - const TestPage({super.key}); + const TestPage({ super.key, this.useMaterial3 }); + + final bool? useMaterial3; @override Widget build(BuildContext context) { return MaterialApp( title: 'Test', theme: ThemeData( + useMaterial3: useMaterial3, primarySwatch: Colors.blue, ), home: const HomePage(), @@ -90,13 +93,23 @@ class ModalPage extends StatelessWidget { } void main() { - testWidgets('Barriers show when using PageRouteBuilder', (WidgetTester tester) async { - await tester.pumpWidget(const TestPage()); + testWidgets('Material2 - Barriers show when using PageRouteBuilder', (WidgetTester tester) async { + await tester.pumpWidget(const TestPage(useMaterial3: false)); + await tester.tap(find.byType(FloatingActionButton)); + await tester.pumpAndSettle(); + await expectLater( + find.byType(TestPage), + matchesGoldenFile('m2_page_route_builder.barrier.png'), + ); + }); + + testWidgets('Material3 - Barriers show when using PageRouteBuilder', (WidgetTester tester) async { + await tester.pumpWidget(const TestPage(useMaterial3: true)); await tester.tap(find.byType(FloatingActionButton)); await tester.pumpAndSettle(); await expectLater( find.byType(TestPage), - matchesGoldenFile('page_route_builder.barrier.png'), + matchesGoldenFile('m3_page_route_builder.barrier.png'), ); }); } diff --git a/packages/flutter/test/widgets/physical_model_test.dart b/packages/flutter/test/widgets/physical_model_test.dart index c5ac33cbe693b..7d3146b9c1b76 100644 --- a/packages/flutter/test/widgets/physical_model_test.dart +++ b/packages/flutter/test/widgets/physical_model_test.dart @@ -47,20 +47,23 @@ void main() { testWidgets('PhysicalModel - clips when overflows and elevation is 0', (WidgetTester tester) async { const Key key = Key('test'); await tester.pumpWidget( - const MediaQuery( - key: key, - data: MediaQueryData(), - child: Directionality( - textDirection: TextDirection.ltr, - child: Padding( - padding: EdgeInsets.all(50), - child: Row( - children: <Widget>[ - Material(child: Text('A long long long long long long long string')), - Material(child: Text('A long long long long long long long string')), - Material(child: Text('A long long long long long long long string')), - Material(child: Text('A long long long long long long long string')), - ], + Theme( + data: ThemeData(useMaterial3: false), + child: const MediaQuery( + key: key, + data: MediaQueryData(), + child: Directionality( + textDirection: TextDirection.ltr, + child: Padding( + padding: EdgeInsets.all(50), + child: Row( + children: <Widget>[ + Material(child: Text('A long long long long long long long string')), + Material(child: Text('A long long long long long long long string')), + Material(child: Text('A long long long long long long long string')), + Material(child: Text('A long long long long long long long string')), + ], + ), ), ), ), diff --git a/packages/flutter/test/widgets/platform_view_test.dart b/packages/flutter/test/widgets/platform_view_test.dart index 1016909fea135..7b4bc720370ec 100644 --- a/packages/flutter/test/widgets/platform_view_test.dart +++ b/packages/flutter/test/widgets/platform_view_test.dart @@ -3168,4 +3168,28 @@ void main() { expect(controller.dispatchedPointerEvents, hasLength(1)); expect(controller.dispatchedPointerEvents[0], isA<PointerHoverEvent>()); }); + + testWidgets('HtmlElementView can be instantiated', (WidgetTester tester) async { + late final Widget htmlElementView; + expect(() { + htmlElementView = const HtmlElementView(viewType: 'webview'); + }, returnsNormally); + + await tester.pumpWidget( + Center( + child: SizedBox( + width: 100, + height: 100, + child: htmlElementView, + ), + ) + ); + await tester.pumpAndSettle(); + + // This file runs on non-web platforms, so we expect `HtmlElementView` to + // fail. + final dynamic exception = tester.takeException(); + expect(exception, isAssertionError); + expect(exception.toString(), contains('HtmlElementView is only available on Flutter Web')); + }); } diff --git a/packages/flutter/test/widgets/render_object_element_test.dart b/packages/flutter/test/widgets/render_object_element_test.dart index b3accf97eb2fb..a485d3fed6950 100644 --- a/packages/flutter/test/widgets/render_object_element_test.dart +++ b/packages/flutter/test/widgets/render_object_element_test.dart @@ -137,8 +137,8 @@ class SwapperElementWithProperOverrides extends SwapperElement { } class RenderSwapper extends RenderBox { - RenderBox? _stable; RenderBox? get stable => _stable; + RenderBox? _stable; set stable(RenderBox? child) { if (child == _stable) { return; @@ -153,8 +153,8 @@ class RenderSwapper extends RenderBox { } bool? _swapperIsOnTop; - RenderBox? _swapper; RenderBox? get swapper => _swapper; + RenderBox? _swapper; void setSwapper(RenderBox? child, bool isOnTop) { if (isOnTop != _swapperIsOnTop) { _swapperIsOnTop = isOnTop; @@ -174,11 +174,11 @@ class RenderSwapper extends RenderBox { @override void visitChildren(RenderObjectVisitor visitor) { - if (_stable != null) { - visitor(_stable!); + if (stable != null) { + visitor(stable!); } - if (_swapper != null) { - visitor(_swapper!); + if (swapper != null) { + visitor(swapper!); } } @@ -210,14 +210,14 @@ class RenderSwapper extends RenderBox { minHeight: constraints.minHeight / 2, maxHeight: constraints.maxHeight / 2, ); - if (_stable != null) { - final BoxParentData stableParentData = _stable!.parentData! as BoxParentData; - _stable!.layout(childConstraints); + if (stable != null) { + final BoxParentData stableParentData = stable!.parentData! as BoxParentData; + stable!.layout(childConstraints); stableParentData.offset = _swapperIsOnTop! ? bottomOffset : topOffset; } - if (_swapper != null) { - final BoxParentData swapperParentData = _swapper!.parentData! as BoxParentData; - _swapper!.layout(childConstraints); + if (swapper != null) { + final BoxParentData swapperParentData = swapper!.parentData! as BoxParentData; + swapper!.layout(childConstraints); swapperParentData.offset = _swapperIsOnTop! ? topOffset : bottomOffset; } } diff --git a/packages/flutter/test/widgets/reparent_state_harder_test.dart b/packages/flutter/test/widgets/reparent_state_harder_test.dart index f022a820d78e1..d7029bb888192 100644 --- a/packages/flutter/test/widgets/reparent_state_harder_test.dart +++ b/packages/flutter/test/widgets/reparent_state_harder_test.dart @@ -63,10 +63,8 @@ class DummyStatefulWidgetState extends State<DummyStatefulWidget> { class RekeyableDummyStatefulWidgetWrapper extends StatefulWidget { const RekeyableDummyStatefulWidgetWrapper({ super.key, - this.child, required this.initialKey, }); - final Widget? child; final GlobalKey initialKey; @override RekeyableDummyStatefulWidgetWrapperState createState() => RekeyableDummyStatefulWidgetWrapperState(); diff --git a/packages/flutter/test/widgets/router_test.dart b/packages/flutter/test/widgets/router_test.dart index be0b375185698..f07d59245be12 100644 --- a/packages/flutter/test/widgets/router_test.dart +++ b/packages/flutter/test/widgets/router_test.dart @@ -1610,10 +1610,6 @@ class SimpleNavigatorRouterDelegate extends RouterDelegate<RouteInformation> wit RouteInformation get routeInformation => _routeInformation; late RouteInformation _routeInformation; - set routeInformation(RouteInformation newValue) { - _routeInformation = newValue; - notifyListeners(); - } SimpleRouterDelegateBuilder builder; SimpleNavigatorRouterDelegatePopPage<void> onPopPage; @@ -1711,10 +1707,6 @@ class SimpleAsyncRouterDelegate extends RouterDelegate<RouteInformation> with Ch RouteInformation? get routeInformation => _routeInformation; RouteInformation? _routeInformation; - set routeInformation(RouteInformation? newValue) { - _routeInformation = newValue; - notifyListeners(); - } SimpleRouterDelegateBuilder builder; late Future<void> setNewRouteFuture; diff --git a/packages/flutter/test/widgets/scrollable_helpers_test.dart b/packages/flutter/test/widgets/scrollable_helpers_test.dart index 9909c572882b7..17b623dc09f61 100644 --- a/packages/flutter/test/widgets/scrollable_helpers_test.dart +++ b/packages/flutter/test/widgets/scrollable_helpers_test.dart @@ -549,4 +549,33 @@ void main() { equals(const Rect.fromLTRB(0.0, 100.0, 800.0, 200.0)), ); }, variant: KeySimulatorTransitModeVariant.all()); + + testWidgets('Can scroll using intents only', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: ListView( + children: const <Widget>[ + SizedBox(height: 600.0, child: Text('The cow as white as milk')), + SizedBox(height: 600.0, child: Text('The cape as red as blood')), + SizedBox(height: 600.0, child: Text('The hair as yellow as corn')), + ], + ), + ), + ); + expect(find.text('The cow as white as milk'), findsOneWidget); + expect(find.text('The cape as red as blood'), findsNothing); + expect(find.text('The hair as yellow as corn'), findsNothing); + Actions.invoke(tester.element(find.byType(SliverList)), const ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page)); + await tester.pump(); // start scroll + await tester.pump(const Duration(milliseconds: 1000)); // end scroll + expect(find.text('The cow as white as milk'), findsOneWidget); + expect(find.text('The cape as red as blood'), findsOneWidget); + expect(find.text('The hair as yellow as corn'), findsNothing); + Actions.invoke(tester.element(find.byType(SliverList)), const ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page)); + await tester.pump(); // start scroll + await tester.pump(const Duration(milliseconds: 1000)); // end scroll + expect(find.text('The cow as white as milk'), findsNothing); + expect(find.text('The cape as red as blood'), findsOneWidget); + expect(find.text('The hair as yellow as corn'), findsOneWidget); + }); } diff --git a/packages/flutter/test/widgets/scrollable_selection_test.dart b/packages/flutter/test/widgets/scrollable_selection_test.dart index a6b37798c9cf1..54a07e2f487e2 100644 --- a/packages/flutter/test/widgets/scrollable_selection_test.dart +++ b/packages/flutter/test/widgets/scrollable_selection_test.dart @@ -158,6 +158,7 @@ void main() { testWidgets('select to scroll works for small scrollable', (WidgetTester tester) async { final ScrollController controller = ScrollController(); await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: SelectionArea( selectionControls: materialTextSelectionControls, child: Scaffold( diff --git a/packages/flutter/test/widgets/scrollable_test.dart b/packages/flutter/test/widgets/scrollable_test.dart index 366e05896b99c..01ba9bccef8d3 100644 --- a/packages/flutter/test/widgets/scrollable_test.dart +++ b/packages/flutter/test/widgets/scrollable_test.dart @@ -947,6 +947,7 @@ void main() { final ScrollController outerController = ScrollController(); final ScrollController innerController = ScrollController(); await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: SingleChildScrollView( controller: outerController, diff --git a/packages/flutter/test/widgets/scrollbar_test.dart b/packages/flutter/test/widgets/scrollbar_test.dart index 09616939b9e56..4a20a61c183fa 100644 --- a/packages/flutter/test/widgets/scrollbar_test.dart +++ b/packages/flutter/test/widgets/scrollbar_test.dart @@ -693,7 +693,7 @@ void main() { child: MediaQuery( data: const MediaQueryData(), child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: SingleChildScrollView( controller: scrollController, @@ -958,7 +958,7 @@ void main() { child: PrimaryScrollController( controller: scrollController, child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), @@ -1013,7 +1013,7 @@ void main() { child: PrimaryScrollController( controller: scrollController, child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), @@ -1071,7 +1071,7 @@ void main() { child: PrimaryScrollController( controller: scrollController, child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: const SingleChildScrollView( primary: true, @@ -1139,7 +1139,7 @@ void main() { child: PrimaryScrollController( controller: scrollController, child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), @@ -1262,7 +1262,7 @@ void main() { child: PrimaryScrollController( controller: scrollController, child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), @@ -1428,7 +1428,7 @@ void main() { FlutterError.onError = handler; }); - testWidgets('RawScrollbar.isAlwaysShown asserts that a ScrollPosition is attached', (WidgetTester tester) async { + testWidgets('RawScrollbar.thumbVisibility asserts that a ScrollPosition is attached', (WidgetTester tester) async { final FlutterExceptionHandler? handler = FlutterError.onError; FlutterErrorDetails? error; FlutterError.onError = (FlutterErrorDetails details) { @@ -1441,7 +1441,7 @@ void main() { child: MediaQuery( data: const MediaQueryData(), child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: ScrollController(), thumbColor: const Color(0x11111111), child: const SingleChildScrollView( @@ -1518,7 +1518,7 @@ void main() { child: PrimaryScrollController( controller: scrollController, child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: const SingleChildScrollView( child: SizedBox(width: 4000.0, height: 4000.0), @@ -1691,7 +1691,7 @@ void main() { child: PrimaryScrollController( controller: scrollController, child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: const SingleChildScrollView( reverse: true, @@ -1764,7 +1764,7 @@ void main() { data: const MediaQueryData(), child: RawScrollbar( mainAxisMargin: 10, - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: SingleChildScrollView( controller: scrollController, @@ -1797,7 +1797,7 @@ void main() { borderRadius: BorderRadius.all(Radius.circular(8.0)) ), controller: scrollController, - isAlwaysShown: true, + thumbVisibility: true, child: SingleChildScrollView( controller: scrollController, child: const SizedBox(height: 1000.0), @@ -1833,7 +1833,7 @@ void main() { controller: scrollController, minThumbLength: 21, minOverscrollLength: 8, - isAlwaysShown: true, + thumbVisibility: true, child: SingleChildScrollView( controller: scrollController, child: const SizedBox(width: 1000.0, height: 50000.0), @@ -1859,7 +1859,7 @@ void main() { shape: const CircleBorder(side: BorderSide(width: 2.0)), thickness: 36.0, controller: scrollController, - isAlwaysShown: true, + thumbVisibility: true, child: SingleChildScrollView( controller: scrollController, child: const SizedBox(height: 1000.0, width: 1000), @@ -1894,7 +1894,7 @@ void main() { child: RawScrollbar( controller: scrollController, crossAxisMargin: 30, - isAlwaysShown: true, + thumbVisibility: true, child: SingleChildScrollView( controller: scrollController, child: const SizedBox(width: 1000.0, height: 1000.0), @@ -1920,7 +1920,7 @@ void main() { thickness: 20, shape: const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(8))), controller: scrollController, - isAlwaysShown: true, + thumbVisibility: true, child: SingleChildScrollView( controller: scrollController, child: const SizedBox(height: 1000.0, width: 1000.0), @@ -1952,7 +1952,7 @@ void main() { data: const MediaQueryData(), child: RawScrollbar( controller: scrollController, - isAlwaysShown: true, + thumbVisibility: true, minOverscrollLength: 8.0, minThumbLength: 36.0, child: SingleChildScrollView( @@ -1984,7 +1984,7 @@ void main() { data: const MediaQueryData(), child: RawScrollbar( controller: scrollController, - isAlwaysShown: true, + thumbVisibility: true, child: SingleChildScrollView( controller: scrollController, child: const SizedBox(height: 1000.0), @@ -2010,7 +2010,7 @@ void main() { data: const MediaQueryData(), child: RawScrollbar( controller: scrollController, - isAlwaysShown: true, + thumbVisibility: true, child: SingleChildScrollView( controller: scrollController, child: SizedBox(width: double.infinity, height: height) @@ -2044,7 +2044,7 @@ void main() { child: PrimaryScrollController( controller: scrollController, child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: const SingleChildScrollView( child: SizedBox( @@ -2084,7 +2084,7 @@ void main() { child: MediaQuery( data: const MediaQueryData(), child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: verticalScrollController, // This scrollbar will receive scroll notifications from both nested // scroll views of opposite axes, but should stay on the vertical @@ -2149,7 +2149,7 @@ void main() { return notification.depth == 0; }, controller: scrollController, - isAlwaysShown: true, + thumbVisibility: true, child: SingleChildScrollView( controller: scrollController, child: const SingleChildScrollView(), @@ -2179,7 +2179,7 @@ void main() { child: RawScrollbar( controller: scrollController, interactive: true, - isAlwaysShown: true, + thumbVisibility: true, child: SingleChildScrollView( controller: scrollController, child: Container( @@ -2222,7 +2222,7 @@ void main() { child: PrimaryScrollController( controller: scrollController, child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: CustomScrollView( primary: true, @@ -2304,7 +2304,7 @@ void main() { child: PrimaryScrollController( controller: scrollController, child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: CustomScrollView( center: uniqueKey, @@ -2730,7 +2730,7 @@ void main() { child: PrimaryScrollController( controller: scrollController, child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: CustomScrollView( controller: scrollController, @@ -2800,7 +2800,7 @@ void main() { child: PrimaryScrollController( controller: scrollController, child: RawScrollbar( - isAlwaysShown: true, + thumbVisibility: true, controller: scrollController, child: CustomScrollView( controller: scrollController, diff --git a/packages/flutter/test/widgets/selectable_region_context_menu_test.dart b/packages/flutter/test/widgets/selectable_region_context_menu_test.dart index 5ba4616402daf..5ec1891246ce1 100644 --- a/packages/flutter/test/widgets/selectable_region_context_menu_test.dart +++ b/packages/flutter/test/widgets/selectable_region_context_menu_test.dart @@ -5,20 +5,31 @@ @TestOn('browser') // This file contains web-only library. library; -import 'dart:html' as html; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:web/web.dart' as web; + +extension on web.HTMLCollection { + Iterable<web.Element> get iterable => _genIterable(this); +} +extension on web.CSSRuleList { + Iterable<web.CSSRule> get iterable => _genIterable(this); +} + +Iterable<T> _genIterable<T>(dynamic jsCollection) { + // ignore: avoid_dynamic_calls + return Iterable<T>.generate(jsCollection.length as int, (int index) => jsCollection.item(index) as T,); +} void main() { - html.Element? element; + web.HTMLElement? element; PlatformSelectableRegionContextMenu.debugOverrideRegisterViewFactory = (String viewType, Object Function(int viewId) fn, {bool isVisible = true}) { - element = fn(0) as html.Element; + element = fn(0) as web.HTMLElement; // The element needs to be attached to the document body to receive mouse // events. - html.document.body!.append(element!); + web.document.body!.append(element); }; // This force register the dom element. PlatformSelectableRegionContextMenu(child: const Placeholder()); @@ -28,17 +39,22 @@ void main() { expect(element, isNotNull); expect(element!.style.width, '100%'); expect(element!.style.height, '100%'); - expect(element!.classes.length, 1); - final String className = element!.classes.first; + expect(element!.classList.length, 1); + final String className = element!.className; - expect(html.document.head!.children, isNotEmpty); + expect(web.document.head!.children.iterable, isNotEmpty); bool foundStyle = false; - for (final html.Element element in html.document.head!.children) { - if (element is! html.StyleElement) { + for (final web.Element element in web.document.head!.children.iterable) { + if (element.tagName != 'STYLE') { continue; } - final html.CssStyleSheet sheet = element.sheet! as html.CssStyleSheet; - foundStyle = sheet.rules!.any((html.CssRule rule) => rule.cssText!.contains(className)); + final web.CSSRuleList? rules = (element as web.HTMLStyleElement).sheet?.rules; + if (rules != null) { + foundStyle = rules.iterable.any((web.CSSRule rule) => rule.cssText.contains(className)); + } + if (foundStyle) { + break; + } } expect(foundStyle, isTrue); }); @@ -62,11 +78,13 @@ void main() { // Dispatch right click. element!.dispatchEvent( - html.MouseEvent( + web.MouseEvent( 'mousedown', - button: 2, - clientX: 200, - clientY: 300, + web.MouseEventInit( + button: 2, + clientX: 200, + clientY: 300, + ), ), ); final RenderSelectionSpy renderSelectionSpy = tester.renderObject<RenderSelectionSpy>(find.byKey(spy)); @@ -140,8 +158,7 @@ class RenderSelectionSpy extends RenderProxyBox } @override - SelectionGeometry get value => _value; - SelectionGeometry _value = const SelectionGeometry( + final SelectionGeometry value = const SelectionGeometry( hasContent: true, status: SelectionStatus.uncollapsed, startSelectionPoint: SelectionPoint( @@ -155,15 +172,6 @@ class RenderSelectionSpy extends RenderProxyBox handleType: TextSelectionHandleType.left, ), ); - set value(SelectionGeometry other) { - if (other == _value) { - return; - } - _value = other; - for (final VoidCallback callback in listeners) { - callback(); - } - } @override void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle) { } diff --git a/packages/flutter/test/widgets/selectable_region_test.dart b/packages/flutter/test/widgets/selectable_region_test.dart index 21bedecaaf682..e2903ab77837e 100644 --- a/packages/flutter/test/widgets/selectable_region_test.dart +++ b/packages/flutter/test/widgets/selectable_region_test.dart @@ -310,6 +310,82 @@ void main() { expect(edgeEvent.globalPosition, const Offset(200.0, 50.0)); }); + testWidgets( + 'touch long press cancel does not send ClearSelectionEvent', + (WidgetTester tester) async { + final UniqueKey spy = UniqueKey(); + await tester.pumpWidget( + MaterialApp( + home: SelectableRegion( + focusNode: FocusNode(), + selectionControls: materialTextSelectionControls, + child: SelectionSpy(key: spy), + ), + ), + ); + await tester.pumpAndSettle(); + + final RenderSelectionSpy renderSelectionSpy = + tester.renderObject<RenderSelectionSpy>(find.byKey(spy)); + renderSelectionSpy.events.clear(); + final TestGesture gesture = + await tester.startGesture(const Offset(200.0, 200.0)); + + addTearDown(gesture.removePointer); + + await tester.pump(const Duration(milliseconds: 500)); + await gesture.cancel(); + expect( + renderSelectionSpy.events.any((SelectionEvent element) => element is ClearSelectionEvent), + isFalse, + ); + }, + ); + + testWidgets( + 'scrolling after the selection does not send ClearSelectionEvent', + (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/128765 + final UniqueKey spy = UniqueKey(); + await tester.pumpWidget( + MaterialApp( + home: SizedBox( + height: 750, + child: SingleChildScrollView( + child: SizedBox( + height: 2000, + child: SelectableRegion( + focusNode: FocusNode(), + selectionControls: materialTextSelectionControls, + child: SelectionSpy(key: spy), + ), + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + + final RenderSelectionSpy renderSelectionSpy = tester.renderObject<RenderSelectionSpy>(find.byKey(spy)); + renderSelectionSpy.events.clear(); + final TestGesture selectGesture = await tester.startGesture(const Offset(200.0, 200.0)); + addTearDown(selectGesture.removePointer); + await tester.pump(const Duration(milliseconds: 500)); + await selectGesture.up(); + expect(renderSelectionSpy.events.length, 1); + expect(renderSelectionSpy.events[0], isA<SelectWordSelectionEvent>()); + + renderSelectionSpy.events.clear(); + final TestGesture scrollGesture = + await tester.startGesture(const Offset(250.0, 850.0)); + await tester.pump(const Duration(milliseconds: 500)); + await scrollGesture.moveTo(Offset.zero); + await scrollGesture.up(); + await tester.pumpAndSettle(); + expect(renderSelectionSpy.events.length, 0); + }, + ); + testWidgets('mouse long press does not send select-word event', (WidgetTester tester) async { final UniqueKey spy = UniqueKey(); await tester.pumpWidget( @@ -560,6 +636,403 @@ void main() { await gesture.up(); }); + testWidgets( + 'right-click mouse can select word at position on Apple platforms', + (WidgetTester tester) async { + Set<ContextMenuButtonType> buttonTypes = <ContextMenuButtonType>{}; + final UniqueKey toolbarKey = UniqueKey(); + await tester.pumpWidget( + MaterialApp( + home: SelectableRegion( + focusNode: FocusNode(), + selectionControls: materialTextSelectionHandleControls, + contextMenuBuilder: ( + BuildContext context, + SelectableRegionState selectableRegionState, + ) { + buttonTypes = selectableRegionState.contextMenuButtonItems + .map((ContextMenuButtonItem buttonItem) => buttonItem.type) + .toSet(); + return SizedBox.shrink(key: toolbarKey); + }, + child: const Center( + child: Text('How are you'), + ), + ), + ), + ); + + expect(buttonTypes.isEmpty, true); + expect(find.byKey(toolbarKey), findsNothing); + + final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText))); + final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton); + addTearDown(gesture.removePointer); + await tester.pump(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3)); + + await gesture.up(); + await tester.pump(); + + expect(buttonTypes, contains(ContextMenuButtonType.copy)); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsOneWidget); + + await gesture.down(textOffsetToPosition(paragraph, 6)); + await tester.pump(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 4, extentOffset: 7)); + + await gesture.up(); + await tester.pump(); + + expect(buttonTypes, contains(ContextMenuButtonType.copy)); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsOneWidget); + + await gesture.down(textOffsetToPosition(paragraph, 9)); + await tester.pump(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 8, extentOffset: 11)); + + await gesture.up(); + await tester.pump(); + + expect(buttonTypes, contains(ContextMenuButtonType.copy)); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsOneWidget); + + // Clear selection. + await tester.tapAt(textOffsetToPosition(paragraph, 1)); + await tester.pump(); + expect(paragraph.selections.isEmpty, true); + expect(find.byKey(toolbarKey), findsNothing); + }, + variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }), + skip: kIsWeb, // [intended] Web uses its native context menu. + ); + + testWidgets( + 'right-click mouse at the same position as previous right-click toggles the context menu on macOS', + (WidgetTester tester) async { + Set<ContextMenuButtonType> buttonTypes = <ContextMenuButtonType>{}; + final UniqueKey toolbarKey = UniqueKey(); + await tester.pumpWidget( + MaterialApp( + home: SelectableRegion( + focusNode: FocusNode(), + selectionControls: materialTextSelectionHandleControls, + contextMenuBuilder: ( + BuildContext context, + SelectableRegionState selectableRegionState, + ) { + buttonTypes = selectableRegionState.contextMenuButtonItems + .map((ContextMenuButtonItem buttonItem) => buttonItem.type) + .toSet(); + return SizedBox.shrink(key: toolbarKey); + }, + child: const Center( + child: Text('How are you'), + ), + ), + ), + ); + + expect(buttonTypes.isEmpty, true); + expect(find.byKey(toolbarKey), findsNothing); + + final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText))); + final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton); + addTearDown(gesture.removePointer); + await tester.pump(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3)); + + await gesture.up(); + await tester.pump(); + + expect(buttonTypes, contains(ContextMenuButtonType.copy)); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsOneWidget); + + await gesture.down(textOffsetToPosition(paragraph, 2)); + await tester.pump(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 3)); + + await gesture.up(); + await tester.pump(); + + // Right-click at same position will toggle the context menu off. + expect(buttonTypes, contains(ContextMenuButtonType.copy)); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsNothing); + + await gesture.down(textOffsetToPosition(paragraph, 9)); + await tester.pump(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 8, extentOffset: 11)); + + await gesture.up(); + await tester.pump(); + + expect(buttonTypes, contains(ContextMenuButtonType.copy)); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsOneWidget); + + await gesture.down(textOffsetToPosition(paragraph, 9)); + await tester.pump(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 8, extentOffset: 11)); + + await gesture.up(); + await tester.pump(); + + // Right-click at same position will toggle the context menu off. + expect(buttonTypes, contains(ContextMenuButtonType.copy)); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsNothing); + + await gesture.down(textOffsetToPosition(paragraph, 6)); + await tester.pump(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 4, extentOffset: 7)); + + await gesture.up(); + await tester.pump(); + + expect(buttonTypes, contains(ContextMenuButtonType.copy)); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsOneWidget); + + // Clear selection. + await tester.tapAt(textOffsetToPosition(paragraph, 1)); + await tester.pump(); + expect(paragraph.selections.isEmpty, true); + expect(find.byKey(toolbarKey), findsNothing); + }, + variant: TargetPlatformVariant.only(TargetPlatform.macOS), + skip: kIsWeb, // [intended] Web uses its native context menu. + ); + + testWidgets( + 'right-click mouse shows the context menu at position on Android, Fucshia, and Windows', + (WidgetTester tester) async { + Set<ContextMenuButtonType> buttonTypes = <ContextMenuButtonType>{}; + final UniqueKey toolbarKey = UniqueKey(); + await tester.pumpWidget( + MaterialApp( + home: SelectableRegion( + focusNode: FocusNode(), + selectionControls: materialTextSelectionHandleControls, + contextMenuBuilder: ( + BuildContext context, + SelectableRegionState selectableRegionState, + ) { + buttonTypes = selectableRegionState.contextMenuButtonItems + .map((ContextMenuButtonItem buttonItem) => buttonItem.type) + .toSet(); + return SizedBox.shrink(key: toolbarKey); + }, + child: const Center( + child: Text('How are you'), + ), + ), + ), + ); + + expect(buttonTypes.isEmpty, true); + expect(find.byKey(toolbarKey), findsNothing); + + final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText))); + final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton); + addTearDown(gesture.removePointer); + await tester.pump(); + // Selection is collapsed so none is reported. + expect(paragraph.selections.isEmpty, true); + + await gesture.up(); + await tester.pump(); + + expect(buttonTypes.length, 1); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsOneWidget); + + await gesture.down(textOffsetToPosition(paragraph, 6)); + await tester.pump(); + expect(paragraph.selections.isEmpty, true); + + await gesture.up(); + await tester.pump(); + + expect(buttonTypes.length, 1); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsOneWidget); + + await gesture.down(textOffsetToPosition(paragraph, 9)); + await tester.pump(); + expect(paragraph.selections.isEmpty, true); + + await gesture.up(); + await tester.pump(); + + expect(buttonTypes.length, 1); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsOneWidget); + + // Clear selection. + await tester.tapAt(textOffsetToPosition(paragraph, 1)); + await tester.pump(); + expect(paragraph.selections.isEmpty, true); + expect(find.byKey(toolbarKey), findsNothing); + + // Create an uncollapsed selection by dragging. + final TestGesture dragGesture = await tester.startGesture(textOffsetToPosition(paragraph, 0), kind: PointerDeviceKind.mouse); + addTearDown(dragGesture.removePointer); + await tester.pump(); + await dragGesture.moveTo(textOffsetToPosition(paragraph, 5)); + await tester.pump(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5)); + await dragGesture.up(); + await tester.pump(); + + // Right click on previous selection should not collapse the selection. + await gesture.down(textOffsetToPosition(paragraph, 2)); + await tester.pump(); + await gesture.up(); + await tester.pump(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5)); + expect(find.byKey(toolbarKey), findsOneWidget); + + // Right click anywhere outside previous selection should collapse the + // selection. + await gesture.down(textOffsetToPosition(paragraph, 7)); + await tester.pump(); + await gesture.up(); + await tester.pump(); + expect(paragraph.selections.isEmpty, true); + expect(find.byKey(toolbarKey), findsOneWidget); + + // Clear selection. + await tester.tapAt(textOffsetToPosition(paragraph, 1)); + await tester.pump(); + expect(paragraph.selections.isEmpty, true); + expect(find.byKey(toolbarKey), findsNothing); + }, + variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.windows }), + skip: kIsWeb, // [intended] Web uses its native context menu. + ); + + testWidgets( + 'right-click mouse toggles the context menu on Linux', + (WidgetTester tester) async { + Set<ContextMenuButtonType> buttonTypes = <ContextMenuButtonType>{}; + final UniqueKey toolbarKey = UniqueKey(); + await tester.pumpWidget( + MaterialApp( + home: SelectableRegion( + focusNode: FocusNode(), + selectionControls: materialTextSelectionHandleControls, + contextMenuBuilder: ( + BuildContext context, + SelectableRegionState selectableRegionState, + ) { + buttonTypes = selectableRegionState.contextMenuButtonItems + .map((ContextMenuButtonItem buttonItem) => buttonItem.type) + .toSet(); + return SizedBox.shrink(key: toolbarKey); + }, + child: const Center( + child: Text('How are you'), + ), + ), + ), + ); + + expect(buttonTypes.isEmpty, true); + expect(find.byKey(toolbarKey), findsNothing); + + final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.text('How are you'), matching: find.byType(RichText))); + final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 2), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton); + addTearDown(gesture.removePointer); + await tester.pump(); + // Selection is collapsed so none is reported. + expect(paragraph.selections.isEmpty, true); + + await gesture.up(); + await tester.pump(); + + // Context menu toggled on. + expect(buttonTypes.length, 1); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsOneWidget); + + await gesture.down(textOffsetToPosition(paragraph, 6)); + await tester.pump(); + expect(paragraph.selections.isEmpty, true); + + await gesture.up(); + await tester.pump(); + + // Context menu toggled off. + expect(find.byKey(toolbarKey), findsNothing); + + await gesture.down(textOffsetToPosition(paragraph, 9)); + await tester.pump(); + expect(paragraph.selections.isEmpty, true); + + await gesture.up(); + await tester.pump(); + + // Context menu toggled on. + expect(buttonTypes.length, 1); + expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); + expect(find.byKey(toolbarKey), findsOneWidget); + + // Clear selection. + await tester.tapAt(textOffsetToPosition(paragraph, 1)); + await tester.pump(); + expect(paragraph.selections.isEmpty, true); + expect(find.byKey(toolbarKey), findsNothing); + + final TestGesture dragGesture = await tester.startGesture(textOffsetToPosition(paragraph, 0), kind: PointerDeviceKind.mouse); + addTearDown(dragGesture.removePointer); + await tester.pump(); + await dragGesture.moveTo(textOffsetToPosition(paragraph, 5)); + await tester.pump(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5)); + await dragGesture.up(); + await tester.pump(); + + // Right click on previous selection should not collapse the selection. + await gesture.down(textOffsetToPosition(paragraph, 2)); + await tester.pump(); + await gesture.up(); + await tester.pump(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5)); + expect(find.byKey(toolbarKey), findsOneWidget); + + // Right click anywhere outside previous selection should first toggle the context + // menu off. + await gesture.down(textOffsetToPosition(paragraph, 7)); + await tester.pump(); + await gesture.up(); + await tester.pump(); + expect(paragraph.selections[0], const TextSelection(baseOffset: 0, extentOffset: 5)); + expect(find.byKey(toolbarKey), findsNothing); + + // Right click again should collapse the selection and toggle the context + // menu on. + await gesture.down(textOffsetToPosition(paragraph, 7)); + await tester.pump(); + await gesture.up(); + await tester.pump(); + expect(paragraph.selections.isEmpty, true); + expect(find.byKey(toolbarKey), findsOneWidget); + + // Clear selection. + await tester.tapAt(textOffsetToPosition(paragraph, 1)); + await tester.pump(); + expect(paragraph.selections.isEmpty, true); + expect(find.byKey(toolbarKey), findsNothing); + }, + variant: TargetPlatformVariant.only(TargetPlatform.linux), + skip: kIsWeb, // [intended] Web uses its native context menu. + ); + testWidgets('can copy a selection made with the mouse', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( @@ -769,6 +1242,50 @@ void main() { skip: isBrowser, // https://github.com/flutter/flutter/issues/61020 ); + testWidgets( + 'can select word when a selectables rect is completely inside of another selectables rect', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/127076. + final UniqueKey outerText = UniqueKey(); + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: SelectableRegion( + focusNode: FocusNode(), + selectionControls: materialTextSelectionControls, + child: Scaffold( + body: Center( + child: Text.rich( + const TextSpan( + children: <InlineSpan>[ + TextSpan( + text: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + ), + WidgetSpan(child: Text('Some text in a WidgetSpan. ')), + TextSpan(text: 'Hello, world.'), + ], + ), + key: outerText, + ), + ), + ), + ), + ), + ); + final RenderParagraph paragraph = tester.renderObject<RenderParagraph>(find.descendant(of: find.byKey(outerText), matching: find.byType(RichText)).first); + // Right click to select word at position. + final TestGesture gesture = await tester.startGesture(textOffsetToPosition(paragraph, 125), kind: PointerDeviceKind.mouse, buttons: kSecondaryMouseButton); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.up(); + await tester.pump(); + // Should select "Hello". + expect(paragraph.selections[0], const TextSelection(baseOffset: 124, extentOffset: 129)); + }, + variant: TargetPlatformVariant.only(TargetPlatform.macOS), + skip: isBrowser, // https://github.com/flutter/flutter/issues/61020 + ); + testWidgets( 'widget span is ignored if it does not contain text - non Apple', (WidgetTester tester) async { @@ -2009,8 +2526,7 @@ class RenderSelectionSpy extends RenderProxyBox } @override - SelectionGeometry get value => _value; - SelectionGeometry _value = const SelectionGeometry( + final SelectionGeometry value = const SelectionGeometry( hasContent: true, status: SelectionStatus.uncollapsed, startSelectionPoint: SelectionPoint( @@ -2024,15 +2540,6 @@ class RenderSelectionSpy extends RenderProxyBox handleType: TextSelectionHandleType.left, ), ); - set value(SelectionGeometry other) { - if (other == _value) { - return; - } - _value = other; - for (final VoidCallback callback in listeners) { - callback(); - } - } @override void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle) { } diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index 137c8553a297b..34099ff2e0510 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -59,20 +59,23 @@ Widget overlay({ Widget? child }) { } Widget overlayWithEntry(OverlayEntry entry) { - return Localizations( - locale: const Locale('en', 'US'), - delegates: <LocalizationsDelegate<dynamic>>[ - WidgetsLocalizationsDelegate(), - MaterialLocalizationsDelegate(), - ], - child: Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(size: Size(800.0, 600.0)), - child: Overlay( - initialEntries: <OverlayEntry>[ - entry, - ], + return Theme( + data: ThemeData(useMaterial3: false), + child: Localizations( + locale: const Locale('en', 'US'), + delegates: <LocalizationsDelegate<dynamic>>[ + WidgetsLocalizationsDelegate(), + MaterialLocalizationsDelegate(), + ], + child: Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(size: Size(800.0, 600.0)), + child: Overlay( + initialEntries: <OverlayEntry>[ + entry, + ], + ), ), ), ), @@ -80,19 +83,22 @@ Widget overlayWithEntry(OverlayEntry entry) { } Widget boilerplate({ Widget? child }) { - return Localizations( - locale: const Locale('en', 'US'), - delegates: <LocalizationsDelegate<dynamic>>[ - WidgetsLocalizationsDelegate(), - MaterialLocalizationsDelegate(), - ], - child: Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(size: Size(800.0, 600.0)), - child: Center( - child: Material( - child: child, + return Theme( + data: ThemeData(useMaterial3: false), + child:Localizations( + locale: const Locale('en', 'US'), + delegates: <LocalizationsDelegate<dynamic>>[ + WidgetsLocalizationsDelegate(), + MaterialLocalizationsDelegate(), + ], + child: Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(size: Size(800.0, 600.0)), + child: Center( + child: Material( + child: child, + ), ), ), ), @@ -100,6 +106,7 @@ Widget boilerplate({ Widget? child }) { ); } + Future<void> skipPastScrollingAnimation(WidgetTester tester) async { await tester.pump(); await tester.pump(const Duration(milliseconds: 200)); @@ -4130,7 +4137,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: SelectableText('something'), @@ -4153,7 +4160,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: SelectableText( @@ -4174,7 +4181,7 @@ void main() { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: SelectableText( @@ -4200,7 +4207,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: SelectableText( @@ -4224,7 +4231,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: SelectableText( @@ -4256,7 +4263,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: SelectableText( @@ -4285,7 +4292,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: SelectableText( @@ -4314,7 +4321,7 @@ void main() { (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( - theme: ThemeData(platform: TargetPlatform.android), + theme: ThemeData(platform: TargetPlatform.android, useMaterial3: false), home: const Material( child: Center( child: SelectableText( @@ -5019,6 +5026,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Center( child: SelectableText( @@ -5080,8 +5088,9 @@ void main() { testWidgets('text selection style 1', (WidgetTester tester) async { await tester.pumpWidget( - const MaterialApp( - home: Material( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Material( child: Center( child: Column( children: <Widget>[ @@ -5132,8 +5141,9 @@ void main() { testWidgets('text selection style 2', (WidgetTester tester) async { await tester.pumpWidget( - const MaterialApp( - home: Material( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const Material( child: Center( child: Column( children: <Widget>[ @@ -5184,6 +5194,7 @@ void main() { testWidgets('keeps alive when has focus', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: DefaultTabController( length: 2, child: Scaffold( @@ -5347,8 +5358,9 @@ void main() { const TextStyle textStyle = TextStyle(color: Color(0xff00ff00), fontSize: 12.0); await tester.pumpWidget( - const MaterialApp( - home: SelectableText.rich( + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: const SelectableText.rich( TextSpan( text: 'Abcd', style: textStyle, @@ -5367,6 +5379,7 @@ void main() { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: SelectableText( 'I love Flutter!', onSelectionChanged: (TextSelection s, _) { @@ -5406,6 +5419,7 @@ void main() { TextSelection? selection; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Material( child: Builder( builder: (BuildContext context) { diff --git a/packages/flutter/test/widgets/semantics_10_test.dart b/packages/flutter/test/widgets/semantics_10_test.dart index 4b452b14be195..0ef416efd64bf 100644 --- a/packages/flutter/test/widgets/semantics_10_test.dart +++ b/packages/flutter/test/widgets/semantics_10_test.dart @@ -96,13 +96,13 @@ class RenderTest extends RenderProxyBox { void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); - if (!_isSemanticBoundary) { + if (!isSemanticBoundary) { return; } config - ..isSemanticBoundary = _isSemanticBoundary - ..label = _label + ..isSemanticBoundary = isSemanticBoundary + ..label = label ..textDirection = TextDirection.ltr; } diff --git a/packages/flutter/test/widgets/semantics_child_configs_delegate_test.dart b/packages/flutter/test/widgets/semantics_child_configs_delegate_test.dart index 33f3b28278176..a562a53891d6f 100644 --- a/packages/flutter/test/widgets/semantics_child_configs_delegate_test.dart +++ b/packages/flutter/test/widgets/semantics_child_configs_delegate_test.dart @@ -357,6 +357,6 @@ class RenderTestConfigDelegate extends RenderProxyBox { @override void describeSemanticsConfiguration(SemanticsConfiguration config) { - config.childConfigurationsDelegate = _delegate; + config.childConfigurationsDelegate = delegate; } } diff --git a/packages/flutter/test/widgets/semantics_event_test.dart b/packages/flutter/test/widgets/semantics_event_test.dart index 4cfd1f6145be2..164d450734edc 100644 --- a/packages/flutter/test/widgets/semantics_event_test.dart +++ b/packages/flutter/test/widgets/semantics_event_test.dart @@ -47,6 +47,15 @@ void main() { }, ); }); + test('FocusSemanticEvent.toMap', () { + expect( + const FocusSemanticEvent().toMap(), + <String, dynamic>{ + 'type': 'focus', + 'data': <String, dynamic>{}, + }, + ); + }); } class TestSemanticsEvent extends SemanticsEvent { diff --git a/packages/flutter/test/widgets/shortcuts_test.dart b/packages/flutter/test/widgets/shortcuts_test.dart index 74d1d5931e735..0481f8cd6b585 100644 --- a/packages/flutter/test/widgets/shortcuts_test.dart +++ b/packages/flutter/test/widgets/shortcuts_test.dart @@ -601,18 +601,12 @@ void main() { testWidgets('Shortcuts.manager lets manager handle shortcuts', (WidgetTester tester) async { final GlobalKey containerKey = GlobalKey(); final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[]; - bool shortcutsSet = false; - void onShortcutsSet() { - shortcutsSet = true; - } final TestShortcutManager testManager = TestShortcutManager( pressedKeys, - onShortcutsSet: onShortcutsSet, shortcuts: <LogicalKeySet, Intent>{ LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(), }, ); - shortcutsSet = false; bool invoked = false; await tester.pumpWidget( Actions( @@ -636,7 +630,6 @@ void main() { await tester.pump(); await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft); expect(invoked, isTrue); - expect(shortcutsSet, isFalse); expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.shiftLeft])); }); @@ -1953,10 +1946,9 @@ class TestIntent2 extends Intent { } class TestShortcutManager extends ShortcutManager { - TestShortcutManager(this.keys, { super.shortcuts, this.onShortcutsSet }); + TestShortcutManager(this.keys, { super.shortcuts }); List<LogicalKeyboardKey> keys; - VoidCallback? onShortcutsSet; @override KeyEventResult handleKeypress(BuildContext context, RawKeyEvent event) { diff --git a/packages/flutter/test/widgets/sliver_cross_axis_group_test.dart b/packages/flutter/test/widgets/sliver_cross_axis_group_test.dart index f54b743d50954..745f2c28ff403 100644 --- a/packages/flutter/test/widgets/sliver_cross_axis_group_test.dart +++ b/packages/flutter/test/widgets/sliver_cross_axis_group_test.dart @@ -511,6 +511,301 @@ void main() { expect(first.localToGlobal(Offset.zero), Offset.zero); expect(second.localToGlobal(Offset.zero), const Offset(VIEWPORT_WIDTH / 2, 0)); }); + + testWidgets('SliverPinnedPersistentHeader is painted within bounds of SliverCrossAxisGroup', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverCrossAxisGroup( + controller: controller, + slivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 600)), + SliverPersistentHeader( + delegate: TestDelegate(), + pinned: true, + ), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + final RenderSliverCrossAxisGroup renderGroup = tester.renderObject(find.byType(SliverCrossAxisGroup)) as RenderSliverCrossAxisGroup; + expect(renderGroup.geometry!.scrollExtent, equals(600)); + controller.jumpTo(560); + await tester.pumpAndSettle(); + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + // Paint extent after header's layout is 60.0, so we must offset by -20.0 to fit within the 40.0 remaining extent. + expect(renderHeader.geometry!.paintExtent, equals(60.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(-20.0)); + }); + + testWidgets('SliverFloatingPersistentHeader is painted within bounds of SliverCrossAxisGroup', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverCrossAxisGroup( + controller: controller, + slivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 600)), + SliverPersistentHeader( + delegate: TestDelegate(), + floating: true, + ), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + await tester.pumpAndSettle(); + final RenderSliverCrossAxisGroup renderGroup = tester.renderObject(find.byType(SliverCrossAxisGroup)) as RenderSliverCrossAxisGroup; + expect(renderGroup.geometry!.scrollExtent, equals(600)); + controller.jumpTo(600.0); + await tester.pumpAndSettle(); + final TestGesture gesture = await tester.startGesture(const Offset(150.0, 300.0)); + await gesture.moveBy(const Offset(0.0, 40)); + await tester.pump(); + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + // Paint extent after header's layout is 40.0, so no need to correct the paintOffset. + expect(renderHeader.geometry!.paintExtent, equals(40.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + }); + + testWidgets('SliverPinnedPersistentHeader is painted within bounds of SliverCrossAxisGroup with different minExtent/maxExtent', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverCrossAxisGroup( + controller: controller, + slivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 600)), + SliverPersistentHeader( + delegate: TestDelegate(minExtent: 40.0), + pinned: true, + ), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + final RenderSliverCrossAxisGroup renderGroup = tester.renderObject(find.byType(SliverCrossAxisGroup)) as RenderSliverCrossAxisGroup; + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + expect(renderGroup.geometry!.scrollExtent, equals(600)); + controller.jumpTo(570); + await tester.pumpAndSettle(); + // Paint extent of the header is 40.0, so we must provide an offset of -10.0 to make it fit in the 30.0 remaining paint extent of the group. + expect(renderHeader.geometry!.paintExtent, equals(40.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(-10.0)); + // Pinned headers should not expand to the maximum extent unless the scroll offset is at the top of the sliver group. + controller.jumpTo(550); + await tester.pumpAndSettle(); + expect(renderHeader.geometry!.paintExtent, equals(40.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + }); + + testWidgets('SliverFloatingPersistentHeader is painted within bounds of SliverCrossAxisGroup with different minExtent/maxExtent', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverCrossAxisGroup( + controller: controller, + slivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 600)), + SliverPersistentHeader( + delegate: TestDelegate(minExtent: 40.0), + floating: true, + ), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + await tester.pumpAndSettle(); + final RenderSliverCrossAxisGroup renderGroup = tester.renderObject(find.byType(SliverCrossAxisGroup)) as RenderSliverCrossAxisGroup; + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + expect(renderGroup.geometry!.scrollExtent, equals(600)); + + controller.jumpTo(600); + await tester.pumpAndSettle(); + + final TestGesture gesture = await tester.startGesture(const Offset(150.0, 300.0)); + await gesture.moveBy(const Offset(0.0, 30.0)); + await tester.pump(); + // Paint extent after header's layout is 30.0, so no need to correct the paintOffset. + expect(renderHeader.geometry!.paintExtent, equals(30.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + // Floating headers should expand to maximum extent as we continue scrolling. + await gesture.moveBy(const Offset(0.0, 20.0)); + await tester.pump(); + expect(renderHeader.geometry!.paintExtent, equals(50.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + }); + + testWidgets('SliverPinnedFloatingPersistentHeader is painted within bounds of SliverCrossAxisGroup with different minExtent/maxExtent', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverCrossAxisGroup( + controller: controller, + slivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 600)), + SliverPersistentHeader( + delegate: TestDelegate(minExtent: 40.0), + pinned: true, + floating: true, + ), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + await tester.pumpAndSettle(); + final RenderSliverCrossAxisGroup renderGroup = tester.renderObject(find.byType(SliverCrossAxisGroup)) as RenderSliverCrossAxisGroup; + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + expect(renderGroup.geometry!.scrollExtent, equals(600)); + + controller.jumpTo(600); + await tester.pumpAndSettle(); + + final TestGesture gesture = await tester.startGesture(const Offset(150.0, 300.0)); + await gesture.moveBy(const Offset(0.0, 30.0)); + await tester.pump(); + // Paint extent after header's layout is 40.0, so we need to adjust by -10.0. + expect(renderHeader.geometry!.paintExtent, equals(40.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(-10.0)); + // Pinned floating headers should expand to maximum extent as we continue scrolling. + await gesture.moveBy(const Offset(0.0, 20.0)); + await tester.pump(); + expect(renderHeader.geometry!.paintExtent, equals(50.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + }); + + testWidgets('SliverAppBar with floating: false, pinned: false, snap: false is painted within bounds of SliverCrossAxisGroup', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverCrossAxisGroup( + controller: controller, + slivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 600)), + const SliverAppBar( + toolbarHeight: 30, + expandedHeight: 60, + ), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + await tester.pumpAndSettle(); + final RenderSliverCrossAxisGroup renderGroup = tester.renderObject(find.byType(SliverCrossAxisGroup)) as RenderSliverCrossAxisGroup; + expect(renderGroup.geometry!.scrollExtent, equals(600)); + + controller.jumpTo(600); + await tester.pumpAndSettle(); + controller.jumpTo(570); + await tester.pumpAndSettle(); + + // At a scroll offset of 570, a normal scrolling header should be out of view. + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + expect(renderHeader.geometry!.paintExtent, equals(0.0)); + }); + + testWidgets('SliverAppBar with floating: true, pinned: false, snap: true is painted within bounds of SliverCrossAxisGroup', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverCrossAxisGroup( + controller: controller, + slivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 600)), + const SliverAppBar( + toolbarHeight: 30, + expandedHeight: 60, + floating: true, + snap: true, + ), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + await tester.pumpAndSettle(); + final RenderSliverCrossAxisGroup renderGroup = tester.renderObject(find.byType(SliverCrossAxisGroup)) as RenderSliverCrossAxisGroup; + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + expect(renderGroup.geometry!.scrollExtent, equals(600)); + + controller.jumpTo(600); + await tester.pumpAndSettle(); + + final TestGesture gesture = await tester.startGesture(const Offset(150.0, 300.0)); + await gesture.moveBy(const Offset(0.0, 10)); + await tester.pump(); + + // The snap animation does not go through until the gesture is released. + expect(renderHeader.geometry!.paintExtent, equals(10)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + + // Once it is released, the header's paint extent becomes the maximum and the group sets an offset of -50.0. + await gesture.up(); + await tester.pumpAndSettle(); + expect(renderHeader.geometry!.paintExtent, equals(60)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(-50.0)); + }); + + testWidgets('SliverAppBar with floating: true, pinned: true, snap: true is painted within bounds of SliverCrossAxisGroup', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverCrossAxisGroup( + controller: controller, + slivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 600)), + const SliverAppBar( + toolbarHeight: 30, + expandedHeight: 60, + floating: true, + pinned: true, + snap: true, + ), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + await tester.pumpAndSettle(); + final RenderSliverCrossAxisGroup renderGroup = tester.renderObject(find.byType(SliverCrossAxisGroup)) as RenderSliverCrossAxisGroup; + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + expect(renderGroup.geometry!.scrollExtent, equals(600)); + + controller.jumpTo(600); + await tester.pumpAndSettle(); + + final TestGesture gesture = await tester.startGesture(const Offset(150.0, 300.0)); + await gesture.moveBy(const Offset(0.0, 10)); + await tester.pump(); + + expect(renderHeader.geometry!.paintExtent, equals(30.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(-20.0)); + + // Once we lift the gesture up, the animation should finish. + await gesture.up(); + await tester.pumpAndSettle(); + expect(renderHeader.geometry!.paintExtent, equals(60.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(-50.0)); + }); + + testWidgets('SliverFloatingPersistentHeader scroll direction is not affected by controller.jumpTo', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverCrossAxisGroup( + controller: controller, + slivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 600)), + SliverPersistentHeader( + delegate: TestDelegate(), + floating: true, + ), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + await tester.pumpAndSettle(); + final RenderSliverCrossAxisGroup renderGroup = tester.renderObject(find.byType(SliverCrossAxisGroup)) as RenderSliverCrossAxisGroup; + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + expect(renderGroup.geometry!.scrollExtent, equals(600)); + + controller.jumpTo(600); + await tester.pumpAndSettle(); + controller.jumpTo(570); + await tester.pumpAndSettle(); + + // If renderHeader._lastStartedScrollDirection is not ScrollDirection.forward, then we shouldn't see the header at all. + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + }); } Widget _buildSliverList({ @@ -550,21 +845,42 @@ Widget _buildSliverCrossAxisGroup({ double viewportWidth = VIEWPORT_WIDTH, Axis scrollDirection = Axis.vertical, bool reverse = false, + List<Widget> otherSlivers = const <Widget>[], }) { - return Directionality( - textDirection: TextDirection.ltr, - child: Align( - alignment: Alignment.topLeft, - child: SizedBox( - height: viewportHeight, - width: viewportWidth, - child: CustomScrollView( - scrollDirection: scrollDirection, - reverse: reverse, - controller: controller, - slivers: <Widget>[SliverCrossAxisGroup(slivers: slivers)], + return MaterialApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + height: viewportHeight, + width: viewportWidth, + child: CustomScrollView( + scrollDirection: scrollDirection, + reverse: reverse, + controller: controller, + slivers: <Widget>[SliverCrossAxisGroup(slivers: slivers), ...otherSlivers], + ), ), ), - ), + ) ); } + +class TestDelegate extends SliverPersistentHeaderDelegate { + TestDelegate({ this.maxExtent = 60.0, this.minExtent = 60.0 }); + + @override + final double maxExtent; + + @override + final double minExtent; + + @override + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { + return Container(height: maxExtent); + } + + @override + bool shouldRebuild(TestDelegate oldDelegate) => true; +} diff --git a/packages/flutter/test/widgets/sliver_fill_remaining_test.dart b/packages/flutter/test/widgets/sliver_fill_remaining_test.dart index 79f0b506334c0..f0279e27437fc 100644 --- a/packages/flutter/test/widgets/sliver_fill_remaining_test.dart +++ b/packages/flutter/test/widgets/sliver_fill_remaining_test.dart @@ -24,6 +24,7 @@ void main() { }) { return MaterialApp( theme: ThemeData( + useMaterial3: false, materialTapTargetSize: MaterialTapTargetSize.padded, ), home: Scaffold( diff --git a/packages/flutter/test/widgets/sliver_main_axis_group_test.dart b/packages/flutter/test/widgets/sliver_main_axis_group_test.dart new file mode 100644 index 0000000000000..46d518589d381 --- /dev/null +++ b/packages/flutter/test/widgets/sliver_main_axis_group_test.dart @@ -0,0 +1,688 @@ +// 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/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; + +const double VIEWPORT_HEIGHT = 600; +const double VIEWPORT_WIDTH = 300; + +void main() { + testWidgets('SliverMainAxisGroup is laid out properly', (WidgetTester tester) async { + final List<int> items = List<int>.generate(20, (int i) => i); + final ScrollController controller = ScrollController(); + + await tester.pumpWidget( + _buildSliverMainAxisGroup( + controller: controller, + slivers: <Widget>[ + _buildSliverList(itemMainAxisExtent: 300, items: items, label: (int item) => Text('Group 0 Tile $item')), + _buildSliverList(itemMainAxisExtent: 200, items: items, label: (int item) => Text('Group 1 Tile $item')), + ], + ), + ); + await tester.pumpAndSettle(); + + expect(controller.offset, 0); + + expect(find.text('Group 0 Tile 0'), findsOneWidget); + expect(find.text('Group 0 Tile 1'), findsOneWidget); + expect(find.text('Group 0 Tile 2'), findsNothing); + + expect(find.text('Group 1 Tile 0'), findsNothing); + + const double scrollOffset = 19 * 300.0; + controller.jumpTo(scrollOffset); + await tester.pumpAndSettle(); + + expect(controller.offset, scrollOffset); + expect(find.text('Group 0 Tile 18'), findsNothing); + expect(find.text('Group 0 Tile 19'), findsOneWidget); + expect(find.text('Group 1 Tile 0'), findsOneWidget); + + final List<RenderSliverList> renderSlivers = tester.renderObjectList<RenderSliverList>(find.byType(SliverList)).toList(); + final RenderSliverList first = renderSlivers[0]; + final RenderSliverList second = renderSlivers[1]; + + expect(first.geometry!.layoutExtent, equals(300.0)); + expect(second.geometry!.layoutExtent, equals(300.0)); + expect(first.geometry!.scrollExtent, equals(20 * 300.0)); + expect(second.geometry!.scrollExtent, equals(20 * 200.0)); + + expect((first.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + expect(first.constraints.scrollOffset, equals(19 * 300.0)); + expect((second.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(1 * 300.0)); + + final RenderSliverMainAxisGroup renderGroup = + tester.renderObject<RenderSliverMainAxisGroup>(find.byType(SliverMainAxisGroup)); + expect(renderGroup.geometry!.scrollExtent, equals(300 * 20 + 200 * 20)); + expect(renderGroup.geometry!.hasVisualOverflow, isTrue); + }); + + testWidgets('SliverMainAxisGroup is laid out properly when reversed', (WidgetTester tester) async { + final List<int> items = List<int>.generate(20, (int i) => i); + final ScrollController controller = ScrollController(); + + await tester.pumpWidget( + _buildSliverMainAxisGroup( + controller: controller, + reverse: true, + slivers: <Widget>[ + _buildSliverList(itemMainAxisExtent: 300, items: items, label: (int item) => Text('Group 0 Tile $item')), + _buildSliverList(itemMainAxisExtent: 200, items: items, label: (int item) => Text('Group 1 Tile $item')), + ], + ), + ); + await tester.pumpAndSettle(); + + expect(controller.offset, 0); + + expect(find.text('Group 0 Tile 0'), findsOneWidget); + expect(find.text('Group 0 Tile 1'), findsOneWidget); + expect(find.text('Group 0 Tile 2'), findsNothing); + + expect(find.text('Group 1 Tile 0'), findsNothing); + + const double scrollOffset = 19 * 300.0; + controller.jumpTo(scrollOffset); + await tester.pumpAndSettle(); + + expect(controller.offset, scrollOffset); + expect(find.text('Group 0 Tile 18'), findsNothing); + expect(find.text('Group 0 Tile 19'), findsOneWidget); + expect(find.text('Group 1 Tile 0'), findsOneWidget); + + final List<RenderSliverList> renderSlivers = tester.renderObjectList<RenderSliverList>(find.byType(SliverList)).toList(); + final RenderSliverList first = renderSlivers[0]; + final RenderSliverList second = renderSlivers[1]; + + expect(first.geometry!.layoutExtent, equals(300.0)); + expect(second.geometry!.layoutExtent, equals(300.0)); + expect(first.geometry!.scrollExtent, equals(20 * 300.0)); + expect(second.geometry!.scrollExtent, equals(20 * 200.0)); + + expect((first.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + expect(first.constraints.scrollOffset, equals(19 * 300.0)); + expect((second.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(1 * 300.0)); + + final RenderSliverMainAxisGroup renderGroup = + tester.renderObject<RenderSliverMainAxisGroup>(find.byType(SliverMainAxisGroup)); + expect(renderGroup.geometry!.scrollExtent, equals(300 * 20 + 200 * 20)); + expect(renderGroup.geometry!.hasVisualOverflow, isTrue); + }); + + testWidgets('SliverMainAxisGroup is laid out properly when horizontal', (WidgetTester tester) async { + final List<int> items = List<int>.generate(20, (int i) => i); + final ScrollController controller = ScrollController(); + + await tester.pumpWidget( + _buildSliverMainAxisGroup( + controller: controller, + scrollDirection: Axis.horizontal, + slivers: <Widget>[ + _buildSliverList(itemMainAxisExtent: 300, items: items, label: (int item) => Text('Group 0 Tile $item'), scrollDirection: Axis.horizontal), + _buildSliverList(itemMainAxisExtent: 200, items: items, label: (int item) => Text('Group 1 Tile $item'), scrollDirection: Axis.horizontal), + ], + ), + ); + await tester.pumpAndSettle(); + + expect(controller.offset, 0); + + expect(find.text('Group 0 Tile 0'), findsOneWidget); + expect(find.text('Group 0 Tile 1'), findsNothing); + + expect(find.text('Group 1 Tile 0'), findsNothing); + + const double scrollOffset = 19 * 300.0; + controller.jumpTo(scrollOffset); + await tester.pumpAndSettle(); + + expect(controller.offset, scrollOffset); + expect(find.text('Group 0 Tile 18'), findsNothing); + expect(find.text('Group 0 Tile 19'), findsOneWidget); + expect(find.text('Group 1 Tile 0'), findsNothing); + + const double scrollOffset2 = 20 * 300.0; + controller.jumpTo(scrollOffset2); + await tester.pumpAndSettle(); + expect(find.text('Group 0 Tile 19'), findsNothing); + expect(find.text('Group 1 Tile 0'), findsOneWidget); + + final List<RenderSliverList> renderSlivers = tester.renderObjectList<RenderSliverList>(find.byType(SliverList)).toList(); + final RenderSliverList first = renderSlivers[0]; + final RenderSliverList second = renderSlivers[1]; + + expect(first.geometry!.layoutExtent, equals(0.0)); + expect(second.geometry!.layoutExtent, equals(300.0)); + expect(first.geometry!.scrollExtent, equals(20 * 300.0)); + expect(second.geometry!.scrollExtent, equals(20 * 200.0)); + + expect((first.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + expect(first.constraints.scrollOffset, equals(20 * 300.0)); + expect((second.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + + final RenderSliverMainAxisGroup renderGroup = + tester.renderObject<RenderSliverMainAxisGroup>(find.byType(SliverMainAxisGroup)); + expect(renderGroup.geometry!.scrollExtent, equals(300 * 20 + 200 * 20)); + expect(renderGroup.geometry!.hasVisualOverflow, isTrue); + }); + + testWidgets('SliverMainAxisGroup is laid out properly when horizontal, reversed', (WidgetTester tester) async { + final List<int> items = List<int>.generate(20, (int i) => i); + final ScrollController controller = ScrollController(); + + await tester.pumpWidget( + _buildSliverMainAxisGroup( + controller: controller, + scrollDirection: Axis.horizontal, + reverse: true, + slivers: <Widget>[ + _buildSliverList(itemMainAxisExtent: 300, items: items, label: (int item) => Text('Group 0 Tile $item'), scrollDirection: Axis.horizontal), + _buildSliverList(itemMainAxisExtent: 200, items: items, label: (int item) => Text('Group 1 Tile $item'), scrollDirection: Axis.horizontal), + ], + ), + ); + await tester.pumpAndSettle(); + + expect(controller.offset, 0); + + expect(find.text('Group 0 Tile 0'), findsOneWidget); + expect(find.text('Group 0 Tile 1'), findsNothing); + + expect(find.text('Group 1 Tile 0'), findsNothing); + + const double scrollOffset = 19 * 300.0; + controller.jumpTo(scrollOffset); + await tester.pumpAndSettle(); + + expect(controller.offset, scrollOffset); + expect(find.text('Group 0 Tile 18'), findsNothing); + expect(find.text('Group 0 Tile 19'), findsOneWidget); + expect(find.text('Group 1 Tile 0'), findsNothing); + + const double scrollOffset2 = 20 * 300.0; + controller.jumpTo(scrollOffset2); + await tester.pumpAndSettle(); + expect(find.text('Group 0 Tile 19'), findsNothing); + expect(find.text('Group 1 Tile 0'), findsOneWidget); + + final List<RenderSliverList> renderSlivers = tester.renderObjectList<RenderSliverList>(find.byType(SliverList)).toList(); + final RenderSliverList first = renderSlivers[0]; + final RenderSliverList second = renderSlivers[1]; + + expect(first.geometry!.layoutExtent, equals(0.0)); + expect(second.geometry!.layoutExtent, equals(300.0)); + expect(first.geometry!.scrollExtent, equals(20 * 300.0)); + expect(second.geometry!.scrollExtent, equals(20 * 200.0)); + + expect((first.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + expect(first.constraints.scrollOffset, equals(20 * 300.0)); + expect((second.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + + final RenderSliverMainAxisGroup renderGroup = + tester.renderObject<RenderSliverMainAxisGroup>(find.byType(SliverMainAxisGroup)); + expect(renderGroup.geometry!.scrollExtent, equals(300 * 20 + 200 * 20)); + expect(renderGroup.geometry!.hasVisualOverflow, isTrue); + }); + + testWidgets('Hit test works properly on various parts of SliverMainAxisGroup', (WidgetTester tester) async { + final List<int> items = List<int>.generate(20, (int i) => i); + final ScrollController controller = ScrollController(); + + String? clickedTile; + + int group = 0; + int tile = 0; + + await tester.pumpWidget(_buildSliverMainAxisGroup( + controller: controller, + slivers: <Widget>[ + _buildSliverList( + itemMainAxisExtent: 300, + items: items, + label: (int item) => tile == item && group == 0 + ? TextButton( + onPressed: () => clickedTile = 'Group 0 Tile $item', + child: Text('Group 0 Tile $item'), + ) + : Text('Group 0 Tile $item'), + ), + _buildSliverList( + items: items, + label: (int item) => tile == item && group == 1 + ? TextButton( + onPressed: () => clickedTile = 'Group 1 Tile $item', + child: Text('Group 1 Tile $item'), + ) + : Text('Group 1 Tile $item'), + ), + ]), + ); + await tester.pumpAndSettle(); + await tester.tap(find.byType(TextButton)); + await tester.pumpAndSettle(); + expect(clickedTile, equals('Group 0 Tile 0')); + + clickedTile = null; + group = 1; + tile = 2; + await tester.pumpWidget(_buildSliverMainAxisGroup( + controller: controller, + slivers: <Widget>[ + _buildSliverList( + itemMainAxisExtent: 300, + items: items, + label: (int item) => tile == item && group == 0 + ? TextButton( + onPressed: () => clickedTile = 'Group 0 Tile $item', + child: Text('Group 0 Tile $item'), + ) + : Text('Group 0 Tile $item'), + ), + _buildSliverList( + items: items, + label: (int item) => tile == item && group == 1 + ? TextButton( + onPressed: () => clickedTile = 'Group 1 Tile $item', + child: Text('Group 1 Tile $item'), + ) + : Text('Group 1 Tile $item'), + ), + ]), + ); + controller.jumpTo(300.0 * 20); + await tester.pumpAndSettle(); + await tester.tap(find.byType(TextButton)); + await tester.pumpAndSettle(); + expect(clickedTile, equals('Group 1 Tile 2')); + }); + + testWidgets('applyPaintTransform is implemented properly', (WidgetTester tester) async { + await tester.pumpWidget(_buildSliverMainAxisGroup( + slivers: <Widget>[ + const SliverToBoxAdapter(child: Text('first box')), + const SliverToBoxAdapter(child: Text('second box')), + ]), + ); + await tester.pumpAndSettle(); + + // localToGlobal calculates offset via applyPaintTransform + final RenderBox first = tester.renderObject(find.text('first box')) as RenderBox; + final RenderBox second = tester.renderObject(find.text('second box')); + expect(first.localToGlobal(Offset.zero), Offset.zero); + expect(second.localToGlobal(Offset.zero), Offset(0, first.size.height)); + }); + + testWidgets('visitChildrenForSemantics visits children in the correct order', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverMainAxisGroup( + controller: controller, + slivers: const <Widget>[ + SliverToBoxAdapter(child: SizedBox(height: 200)), + SliverToBoxAdapter(child: SizedBox(height: 300)), + SliverToBoxAdapter(child: SizedBox(height: 500)), + SliverToBoxAdapter(child: SizedBox(height: 400)), + ]), + ); + controller.jumpTo(300); + await tester.pumpAndSettle(); + + final List<RenderSliver> visitedChildren = <RenderSliver>[]; + final RenderSliverMainAxisGroup renderGroup = tester.renderObject<RenderSliverMainAxisGroup>(find.byType(SliverMainAxisGroup)); + void visitor(RenderObject child) { + visitedChildren.add(child as RenderSliver); + } + renderGroup.visitChildrenForSemantics(visitor); + expect(visitedChildren.length, equals(2)); + expect(visitedChildren[0].geometry!.scrollExtent, equals(300)); + expect(visitedChildren[1].geometry!.scrollExtent, equals(500)); + }); + + testWidgets('SliverPinnedPersistentHeader is painted within bounds of SliverMainAxisGroup', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverMainAxisGroup( + controller: controller, + slivers: <Widget>[ + SliverPersistentHeader( + delegate: TestDelegate(), + pinned: true, + ), + const SliverToBoxAdapter(child: SizedBox(height: 600)), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + final RenderSliverMainAxisGroup renderGroup = tester.renderObject(find.byType(SliverMainAxisGroup)) as RenderSliverMainAxisGroup; + // Scroll extent is the total of the box sliver and the sliver persistent header. + expect(renderGroup.geometry!.scrollExtent, equals(600.0 + 60.0)); + controller.jumpTo(620); + await tester.pumpAndSettle(); + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + // Paint extent after header's layout is 60.0, so we must offset by -20.0 to fit within the 40.0 remaining extent. + expect(renderHeader.geometry!.paintExtent, equals(60.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(-20.0)); + }); + + + testWidgets('SliverFloatingPersistentHeader is painted within bounds of SliverMainAxisGroup', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverMainAxisGroup( + controller: controller, + slivers: <Widget>[ + SliverPersistentHeader( + delegate: TestDelegate(), + floating: true, + ), + const SliverToBoxAdapter(child: SizedBox(height: 600)), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + await tester.pumpAndSettle(); + final RenderSliverMainAxisGroup renderGroup = tester.renderObject(find.byType(SliverMainAxisGroup)) as RenderSliverMainAxisGroup; + expect(renderGroup.geometry!.scrollExtent, equals(660)); + controller.jumpTo(660.0); + await tester.pumpAndSettle(); + final TestGesture gesture = await tester.startGesture(const Offset(150.0, 300.0)); + await gesture.moveBy(const Offset(0.0, 40)); + await tester.pump(); + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + // Paint extent after header's layout is 40.0, so no need to correct the paintOffset. + expect(renderHeader.geometry!.paintExtent, equals(40.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + }); + + testWidgets('SliverPinnedPersistentHeader is painted within bounds of SliverMainAxisGroup with different minExtent/maxExtent', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverMainAxisGroup( + controller: controller, + slivers: <Widget>[ + SliverPersistentHeader( + delegate: TestDelegate(minExtent: 40.0), + pinned: true, + ), + const SliverToBoxAdapter(child: SizedBox(height: 600)), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + final RenderSliverMainAxisGroup renderGroup = tester.renderObject(find.byType(SliverMainAxisGroup)) as RenderSliverMainAxisGroup; + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + expect(renderGroup.geometry!.scrollExtent, equals(660)); + controller.jumpTo(630); + await tester.pumpAndSettle(); + // Paint extent of the header is 40.0, so we must provide an offset of -10.0 to make it fit in the 30.0 remaining paint extent of the group. + expect(renderHeader.geometry!.paintExtent, equals(40.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(-10.0)); + controller.jumpTo(610); + await tester.pumpAndSettle(); + expect(renderHeader.geometry!.paintExtent, equals(40.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + }); + + testWidgets('SliverFloatingPersistentHeader is painted within bounds of SliverMainAxisGroup with different minExtent/maxExtent', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverMainAxisGroup( + controller: controller, + slivers: <Widget>[ + SliverPersistentHeader( + delegate: TestDelegate(minExtent: 40.0), + floating: true, + ), + const SliverToBoxAdapter(child: SizedBox(height: 600)), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + await tester.pumpAndSettle(); + final RenderSliverMainAxisGroup renderGroup = tester.renderObject(find.byType(SliverMainAxisGroup)) as RenderSliverMainAxisGroup; + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + expect(renderGroup.geometry!.scrollExtent, equals(660)); + + controller.jumpTo(660); + await tester.pumpAndSettle(); + + final TestGesture gesture = await tester.startGesture(const Offset(150.0, 300.0)); + await gesture.moveBy(const Offset(0.0, 30.0)); + await tester.pump(); + // Paint extent after header's layout is 30.0, so no need to correct the paintOffset. + expect(renderHeader.geometry!.paintExtent, equals(30.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + // Floating headers should expand to maximum extent as we continue scrolling. + await gesture.moveBy(const Offset(0.0, 20.0)); + await tester.pump(); + expect(renderHeader.geometry!.paintExtent, equals(50.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + }); + + testWidgets('SliverPinnedFloatingPersistentHeader is painted within bounds of SliverMainAxisGroup with different minExtent/maxExtent', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverMainAxisGroup( + controller: controller, + slivers: <Widget>[ + SliverPersistentHeader( + delegate: TestDelegate(minExtent: 40.0), + pinned: true, + floating: true, + ), + const SliverToBoxAdapter(child: SizedBox(height: 600)), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + await tester.pumpAndSettle(); + final RenderSliverMainAxisGroup renderGroup = tester.renderObject(find.byType(SliverMainAxisGroup)) as RenderSliverMainAxisGroup; + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + expect(renderGroup.geometry!.scrollExtent, equals(660)); + + controller.jumpTo(660); + await tester.pumpAndSettle(); + + final TestGesture gesture = await tester.startGesture(const Offset(150.0, 300.0)); + await gesture.moveBy(const Offset(0.0, 30.0)); + await tester.pump(); + // Paint extent after header's layout is 40.0, so we need to adjust by -10.0. + expect(renderHeader.geometry!.paintExtent, equals(40.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(-10.0)); + // Pinned floating headers should expand to maximum extent as we continue scrolling. + await gesture.moveBy(const Offset(0.0, 20.0)); + await tester.pump(); + expect(renderHeader.geometry!.paintExtent, equals(50.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + }); + + testWidgets('SliverAppBar with floating: false, pinned: false, snap: false is painted within bounds of SliverMainAxisGroup', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverMainAxisGroup( + controller: controller, + slivers: <Widget>[ + const SliverAppBar( + toolbarHeight: 30, + expandedHeight: 60, + ), + const SliverToBoxAdapter(child: SizedBox(height: 600)), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + await tester.pumpAndSettle(); + final RenderSliverMainAxisGroup renderGroup = tester.renderObject(find.byType(SliverMainAxisGroup)) as RenderSliverMainAxisGroup; + expect(renderGroup.geometry!.scrollExtent, equals(660)); + + controller.jumpTo(660); + await tester.pumpAndSettle(); + controller.jumpTo(630); + await tester.pumpAndSettle(); + + // At a scroll offset of 630, a normal scrolling header should be out of view. + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + expect(renderHeader.constraints.scrollOffset, equals(630)); + expect(renderHeader.geometry!.layoutExtent, equals(0.0)); + }); + + testWidgets('SliverAppBar with floating: true, pinned: false, snap: true is painted within bounds of SliverMainAxisGroup', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverMainAxisGroup( + controller: controller, + slivers: <Widget>[ + const SliverAppBar( + toolbarHeight: 30, + expandedHeight: 60, + floating: true, + snap: true, + ), + const SliverToBoxAdapter(child: SizedBox(height: 600)), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + await tester.pumpAndSettle(); + final RenderSliverMainAxisGroup renderGroup = tester.renderObject(find.byType(SliverMainAxisGroup)) as RenderSliverMainAxisGroup; + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + expect(renderGroup.geometry!.scrollExtent, equals(660)); + + controller.jumpTo(660); + await tester.pumpAndSettle(); + + final TestGesture gesture = await tester.startGesture(const Offset(150.0, 300.0)); + await gesture.moveBy(const Offset(0.0, 10)); + await tester.pump(); + + // The snap animation does not go through until the gesture is released. + expect(renderHeader.geometry!.paintExtent, equals(10)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(0.0)); + + // Once it is released, the header's paint extent becomes the maximum and the group sets an offset of -50.0. + await gesture.up(); + await tester.pumpAndSettle(); + expect(renderHeader.geometry!.paintExtent, equals(60)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(-50.0)); + }); + + testWidgets('SliverAppBar with floating: true, pinned: true, snap: true is painted within bounds of SliverMainAxisGroup', (WidgetTester tester) async { + final ScrollController controller = ScrollController(); + await tester.pumpWidget(_buildSliverMainAxisGroup( + controller: controller, + slivers: <Widget>[ + const SliverAppBar( + toolbarHeight: 30, + expandedHeight: 60, + floating: true, + pinned: true, + snap: true, + ), + const SliverToBoxAdapter(child: SizedBox(height: 600)), + ], + otherSlivers: <Widget>[ + const SliverToBoxAdapter(child: SizedBox(height: 2400)), + ], + )); + await tester.pumpAndSettle(); + final RenderSliverMainAxisGroup renderGroup = tester.renderObject(find.byType(SliverMainAxisGroup)) as RenderSliverMainAxisGroup; + final RenderSliverPersistentHeader renderHeader = tester.renderObject(find.byType(SliverPersistentHeader)) as RenderSliverPersistentHeader; + expect(renderGroup.geometry!.scrollExtent, equals(660)); + + controller.jumpTo(660); + await tester.pumpAndSettle(); + + final TestGesture gesture = await tester.startGesture(const Offset(150.0, 300.0)); + await gesture.moveBy(const Offset(0.0, 10)); + await tester.pump(); + + expect(renderHeader.geometry!.paintExtent, equals(30.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(-20.0)); + + // Once we lift the gesture up, the animation should finish. + await gesture.up(); + await tester.pumpAndSettle(); + expect(renderHeader.geometry!.paintExtent, equals(60.0)); + expect((renderHeader.parentData! as SliverPhysicalParentData).paintOffset.dy, equals(-50.0)); + }); +} + +Widget _buildSliverList({ + double itemMainAxisExtent = 100, + List<int> items = const <int>[], + required Widget Function(int) label, + Axis scrollDirection = Axis.vertical, +}) { + return SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int i) { + return scrollDirection == Axis.vertical + ? SizedBox( + key: ValueKey<int>(items[i]), + height: itemMainAxisExtent, + child: label(items[i]), + ) + : SizedBox( + key: ValueKey<int>(items[i]), + width: itemMainAxisExtent, + child: label(items[i])); + }, + findChildIndexCallback: (Key key) { + final ValueKey<int> valueKey = key as ValueKey<int>; + final int index = items.indexOf(valueKey.value); + return index == -1 ? null : index; + }, + childCount: items.length, + ), + ); +} + +Widget _buildSliverMainAxisGroup({ + required List<Widget> slivers, + ScrollController? controller, + double viewportHeight = VIEWPORT_HEIGHT, + double viewportWidth = VIEWPORT_WIDTH, + Axis scrollDirection = Axis.vertical, + bool reverse = false, + List<Widget> otherSlivers = const <Widget>[], +}) { + return MaterialApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: Align( + alignment: Alignment.topLeft, + child: SizedBox( + height: viewportHeight, + width: viewportWidth, + child: CustomScrollView( + scrollDirection: scrollDirection, + reverse: reverse, + controller: controller, + slivers: <Widget>[SliverMainAxisGroup(slivers: slivers), ...otherSlivers], + ), + ), + ), + ), + ); +} + +class TestDelegate extends SliverPersistentHeaderDelegate { + TestDelegate({ this.maxExtent = 60.0, this.minExtent = 60.0 }); + + @override + final double maxExtent; + + @override + final double minExtent; + + @override + Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { + return Container(height: maxExtent); + } + + @override + bool shouldRebuild(TestDelegate oldDelegate) => true; +} diff --git a/packages/flutter/test/widgets/slivers_appbar_floating_pinned_test.dart b/packages/flutter/test/widgets/slivers_appbar_floating_pinned_test.dart index 4831cb697d23a..ff3ec8c8030ba 100644 --- a/packages/flutter/test/widgets/slivers_appbar_floating_pinned_test.dart +++ b/packages/flutter/test/widgets/slivers_appbar_floating_pinned_test.dart @@ -244,35 +244,25 @@ void main() { testWidgets('Sliver appbars - floating and pinned - second app bar stacks below', (WidgetTester tester) async { final ScrollController controller = ScrollController(); await tester.pumpWidget( - Localizations( - locale: const Locale('en', 'us'), - delegates: const <LocalizationsDelegate<dynamic>>[ - DefaultWidgetsLocalizations.delegate, - DefaultMaterialLocalizations.delegate, - ], - child: Directionality( - textDirection: TextDirection.ltr, - child: MediaQuery( - data: const MediaQueryData(), - child: CustomScrollView( - controller: controller, - slivers: <Widget>[ - const SliverAppBar(floating: true, pinned: true, expandedHeight: 200.0, title: Text('A')), - const SliverAppBar(primary: false, pinned: true, title: Text('B')), - SliverList( - delegate: SliverChildListDelegate( - const <Widget>[ - Text('C'), - Text('D'), - SizedBox(height: 500.0), - Text('E'), - SizedBox(height: 500.0), - ], - ), - ), - ], + MaterialApp( + theme: ThemeData(useMaterial3: false), + home: CustomScrollView( + controller: controller, + slivers: <Widget>[ + const SliverAppBar(floating: true, pinned: true, expandedHeight: 200.0, title: Text('A')), + const SliverAppBar(primary: false, pinned: true, title: Text('B')), + SliverList( + delegate: SliverChildListDelegate( + const <Widget>[ + Text('C'), + Text('D'), + SizedBox(height: 500.0), + Text('E'), + SizedBox(height: 500.0), + ], + ), ), - ), + ], ), ), ); diff --git a/packages/flutter/test/widgets/slivers_keepalive_test.dart b/packages/flutter/test/widgets/slivers_keepalive_test.dart index 5065232cbe180..ae7dc37c4cc46 100644 --- a/packages/flutter/test/widgets/slivers_keepalive_test.dart +++ b/packages/flutter/test/widgets/slivers_keepalive_test.dart @@ -319,7 +319,7 @@ void main() { WidgetTest2(text: 'child 2', key: UniqueKey()), ]; await tester.pumpWidget( - SwitchingSliverListTest(viewportFraction: 0.1, children: childList), + SwitchingSliverListTest(children: childList), ); final _WidgetTest0State state0 = tester.state(find.byType(WidgetTest0)); final _WidgetTest1State state1 = tester.state(find.byType(WidgetTest1)); @@ -330,32 +330,32 @@ void main() { childList = createSwitchedChildList(childList, 0, 2); await tester.pumpWidget( - SwitchingSliverListTest(viewportFraction: 0.1, children: childList), + SwitchingSliverListTest(children: childList), ); childList = createSwitchedChildList(childList, 1, 2); await tester.pumpWidget( - SwitchingSliverListTest(viewportFraction: 0.1, children: childList), + SwitchingSliverListTest(children: childList), ); childList = createSwitchedChildList(childList, 1, 2); await tester.pumpWidget( - SwitchingSliverListTest(viewportFraction: 0.1, children: childList), + SwitchingSliverListTest(children: childList), ); childList = createSwitchedChildList(childList, 0, 1); await tester.pumpWidget( - SwitchingSliverListTest(viewportFraction: 0.1, children: childList), + SwitchingSliverListTest(children: childList), ); childList = createSwitchedChildList(childList, 0, 2); await tester.pumpWidget( - SwitchingSliverListTest(viewportFraction: 0.1, children: childList), + SwitchingSliverListTest(children: childList), ); childList = createSwitchedChildList(childList, 0, 1); await tester.pumpWidget( - SwitchingSliverListTest(viewportFraction: 0.1, children: childList), + SwitchingSliverListTest(children: childList), ); expect(state0.hasBeenDisposed, false); expect(state1.hasBeenDisposed, true); @@ -369,7 +369,7 @@ void main() { WidgetTest2(text: 'child 2', key: UniqueKey()), ]; await tester.pumpWidget( - SwitchingSliverListTest(viewportFraction: 0.1, children: childList), + SwitchingSliverListTest(children: childList), ); final _WidgetTest0State state0 = tester.state(find.byType(WidgetTest0)); final _WidgetTest1State state1 = tester.state(find.byType(WidgetTest1)); @@ -381,7 +381,7 @@ void main() { childList = createSwitchedChildList(childList, 0, 1); childList.removeAt(2); await tester.pumpWidget( - SwitchingSliverListTest(viewportFraction: 0.1, children: childList), + SwitchingSliverListTest(children: childList), ); expect(find.text('child 0'), findsOneWidget); expect(find.text('child 1'), findsOneWidget); @@ -505,12 +505,10 @@ class SwitchingChildListTest extends StatelessWidget { class SwitchingSliverListTest extends StatelessWidget { const SwitchingSliverListTest({ required this.children, - this.viewportFraction = 1.0, super.key, }); final List<Widget> children; - final double viewportFraction; @override Widget build(BuildContext context) { diff --git a/packages/flutter/test/widgets/slivers_test.dart b/packages/flutter/test/widgets/slivers_test.dart index 6dbee9a25b48e..8f7bab5655438 100644 --- a/packages/flutter/test/widgets/slivers_test.dart +++ b/packages/flutter/test/widgets/slivers_test.dart @@ -426,6 +426,7 @@ void main() { } await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: CustomScrollView( slivers: <Widget> [ diff --git a/packages/flutter/test/widgets/snapshot_widget_test.dart b/packages/flutter/test/widgets/snapshot_widget_test.dart index 751a610e15938..05512bae827d7 100644 --- a/packages/flutter/test/widgets/snapshot_widget_test.dart +++ b/packages/flutter/test/widgets/snapshot_widget_test.dart @@ -335,10 +335,6 @@ class TestPainter extends SnapshotPainter { super.removeListener(listener); } - void notify() { - notifyListeners(); - } - @override void paintSnapshot(PaintingContext context, Offset offset, Size size, ui.Image image, Size sourceSize, double pixelRatio) { count += 1; diff --git a/packages/flutter/test/widgets/tap_and_drag_gestures_test.dart b/packages/flutter/test/widgets/tap_and_drag_gestures_test.dart index 972e7c08fa9f8..33027beeee4c2 100644 --- a/packages/flutter/test/widgets/tap_and_drag_gestures_test.dart +++ b/packages/flutter/test/widgets/tap_and_drag_gestures_test.dart @@ -694,6 +694,44 @@ void main() { 'panend#2']); }); + // This is a regression test for https://github.com/flutter/flutter/issues/129161. + testGesture('Beats TapGestureRecognizer and DoubleTapGestureRecognizer when the pointer has not moved and this recognizer is the first in the arena', (GestureTester tester) { + setUpTapAndPanGestureRecognizer(); + + final TapGestureRecognizer taps = TapGestureRecognizer() + ..onTapDown = (TapDownDetails details) { + events.add('tapdown'); + } + ..onTapUp = (TapUpDetails details) { + events.add('tapup'); + } + ..onTapCancel = () { + events.add('tapscancel'); + }; + + final DoubleTapGestureRecognizer doubleTaps = DoubleTapGestureRecognizer() + ..onDoubleTapDown = (TapDownDetails details) { + events.add('doubletapdown'); + } + ..onDoubleTap = () { + events.add('doubletapup'); + } + ..onDoubleTapCancel = () { + events.add('doubletapcancel'); + }; + + tapAndDrag.addPointer(down1); + taps.addPointer(down1); + doubleTaps.addPointer(down1); + tester.closeArena(1); + tester.route(down1); + tester.route(up1); + GestureBinding.instance.gestureArena.sweep(1); + // Wait for GestureArena to resolve itself. + tester.async.elapse(kDoubleTapTimeout); + expect(events, <String>['down#1', 'up#1']); + }); + testGesture('Beats TapGestureRecognizer when the pointer has not moved and this recognizer is the first in the arena', (GestureTester tester) { setUpTapAndPanGestureRecognizer(); diff --git a/packages/flutter/test/widgets/text_golden_test.dart b/packages/flutter/test/widgets/text_golden_test.dart index fe8661d0cd2a4..4267da2d79a18 100644 --- a/packages/flutter/test/widgets/text_golden_test.dart +++ b/packages/flutter/test/widgets/text_golden_test.dart @@ -191,6 +191,7 @@ void main() { testWidgets('Text Fade', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( backgroundColor: Colors.transparent, body: RepaintBoundary( @@ -520,7 +521,7 @@ void main() { testWidgets('Text Inline widget', (WidgetTester tester) async { await tester.pumpWidget( - Center( + Theme(data: ThemeData(useMaterial3: false), child: Center( child: RepaintBoundary( child: Material( child: Directionality( @@ -604,7 +605,7 @@ void main() { ), ), ), - ), + )), ); await expectLater( find.byType(Container), @@ -616,6 +617,7 @@ void main() { await tester.pumpWidget( Center( child: MaterialApp( + theme: ThemeData(useMaterial3: false), home: RepaintBoundary( child: Material( child: Container( @@ -662,6 +664,7 @@ void main() { await tester.pumpWidget( Center( child: MaterialApp( + theme: ThemeData(useMaterial3: false), home: RepaintBoundary( child: Material( child: Container( @@ -788,97 +791,100 @@ void main() { testWidgets('Text Inline widget baseline', (WidgetTester tester) async { await tester.pumpWidget( - Center( - child: RepaintBoundary( - child: Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Container( - width: 400.0, - height: 200.0, - decoration: const BoxDecoration( - color: Color(0xff00ff00), - ), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 200, maxHeight: 100), - child: const Text.rich( - TextSpan( - text: 'C ', - style: TextStyle( - fontSize: 16, - ), - children: <InlineSpan>[ - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: true, onChanged: null), - ), - WidgetSpan( - child: Checkbox(value: false, onChanged: null), + Theme( + data: ThemeData(useMaterial3: false), + child: Center( + child: RepaintBoundary( + child: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Container( + width: 400.0, + height: 200.0, + decoration: const BoxDecoration( + color: Color(0xff00ff00), + ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 200, maxHeight: 100), + child: const Text.rich( + TextSpan( + text: 'C ', + style: TextStyle( + fontSize: 16, ), - TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffffff00), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffff0000), + children: <InlineSpan>[ + WidgetSpan( + alignment: PlaceholderAlignment.baseline, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: true, onChanged: null), + ), + WidgetSpan( + child: Checkbox(value: false, onChanged: null), + ), + TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), + WidgetSpan( + alignment: PlaceholderAlignment.baseline, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 50.0, + height: 55.0, + child: DecoratedBox( + decoration: BoxDecoration( + color: Color(0xffffff00), + ), + child: Center( + child:SizedBox( + width: 10.0, + height: 15.0, + child: DecoratedBox( + decoration: BoxDecoration( + color: Color(0xffff0000), + ), ), ), ), ), ), ), - ), - TextSpan(text: 'hello world! seize the day!'), - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), + TextSpan(text: 'hello world! seize the day!'), + WidgetSpan( + alignment: PlaceholderAlignment.baseline, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: false, onChanged: null), ), - ), - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), + WidgetSpan( + alignment: PlaceholderAlignment.baseline, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 20, + height: 20, + child: Checkbox(value: true, onChanged: null), + ), ), - ), - WidgetSpan( - alignment: PlaceholderAlignment.baseline, - baseline: TextBaseline.alphabetic, - child: Text('embedded'), - ), - TextSpan(text: 'ref'), - ], + WidgetSpan( + alignment: PlaceholderAlignment.baseline, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: false, onChanged: null), + ), + WidgetSpan( + alignment: PlaceholderAlignment.baseline, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 20, + height: 20, + child: Checkbox(value: true, onChanged: null), + ), + ), + WidgetSpan( + alignment: PlaceholderAlignment.baseline, + baseline: TextBaseline.alphabetic, + child: Text('embedded'), + ), + TextSpan(text: 'ref'), + ], + ), + textDirection: TextDirection.ltr, ), - textDirection: TextDirection.ltr, ), ), ), @@ -895,97 +901,100 @@ void main() { testWidgets('Text Inline widget aboveBaseline', (WidgetTester tester) async { await tester.pumpWidget( - Center( - child: RepaintBoundary( - child: Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Container( - width: 400.0, - height: 200.0, - decoration: const BoxDecoration( - color: Color(0xff00ff00), - ), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 200, maxHeight: 100), - child: const Text.rich( - TextSpan( - text: 'C ', - style: TextStyle( - fontSize: 16, - ), - children: <InlineSpan>[ - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: true, onChanged: null), - ), - WidgetSpan( - child: Checkbox(value: false, onChanged: null), + Theme( + data: ThemeData(useMaterial3: false), + child: Center( + child: RepaintBoundary( + child: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Container( + width: 400.0, + height: 200.0, + decoration: const BoxDecoration( + color: Color(0xff00ff00), + ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 200, maxHeight: 100), + child: const Text.rich( + TextSpan( + text: 'C ', + style: TextStyle( + fontSize: 16, ), - TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffffff00), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffff0000), + children: <InlineSpan>[ + WidgetSpan( + alignment: PlaceholderAlignment.aboveBaseline, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: true, onChanged: null), + ), + WidgetSpan( + child: Checkbox(value: false, onChanged: null), + ), + TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), + WidgetSpan( + alignment: PlaceholderAlignment.aboveBaseline, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 50.0, + height: 55.0, + child: DecoratedBox( + decoration: BoxDecoration( + color: Color(0xffffff00), + ), + child: Center( + child:SizedBox( + width: 10.0, + height: 15.0, + child: DecoratedBox( + decoration: BoxDecoration( + color: Color(0xffff0000), + ), ), ), ), ), ), ), - ), - TextSpan(text: 'hello world! seize the day!'), - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), + TextSpan(text: 'hello world! seize the day!'), + WidgetSpan( + alignment: PlaceholderAlignment.aboveBaseline, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: false, onChanged: null), ), - ), - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), + WidgetSpan( + alignment: PlaceholderAlignment.aboveBaseline, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 20, + height: 20, + child: Checkbox(value: true, onChanged: null), + ), ), - ), - WidgetSpan( - alignment: PlaceholderAlignment.aboveBaseline, - baseline: TextBaseline.alphabetic, - child: Text('embedded'), - ), - TextSpan(text: 'ref'), - ], + WidgetSpan( + alignment: PlaceholderAlignment.aboveBaseline, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: false, onChanged: null), + ), + WidgetSpan( + alignment: PlaceholderAlignment.aboveBaseline, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 20, + height: 20, + child: Checkbox(value: true, onChanged: null), + ), + ), + WidgetSpan( + alignment: PlaceholderAlignment.aboveBaseline, + baseline: TextBaseline.alphabetic, + child: Text('embedded'), + ), + TextSpan(text: 'ref'), + ], + ), + textDirection: TextDirection.ltr, ), - textDirection: TextDirection.ltr, ), ), ), @@ -1002,97 +1011,100 @@ void main() { testWidgets('Text Inline widget belowBaseline', (WidgetTester tester) async { await tester.pumpWidget( - Center( - child: RepaintBoundary( - child: Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Container( - width: 400.0, - height: 200.0, - decoration: const BoxDecoration( - color: Color(0xff00ff00), - ), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 200, maxHeight: 100), - child: const Text.rich( - TextSpan( - text: 'C ', - style: TextStyle( - fontSize: 16, - ), - children: <InlineSpan>[ - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: true, onChanged: null), - ), - WidgetSpan( - child: Checkbox(value: false, onChanged: null), + Theme( + data: ThemeData(useMaterial3: false), + child: Center( + child: RepaintBoundary( + child: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Container( + width: 400.0, + height: 200.0, + decoration: const BoxDecoration( + color: Color(0xff00ff00), + ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 200, maxHeight: 100), + child: const Text.rich( + TextSpan( + text: 'C ', + style: TextStyle( + fontSize: 16, ), - TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffffff00), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffff0000), + children: <InlineSpan>[ + WidgetSpan( + alignment: PlaceholderAlignment.belowBaseline, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: true, onChanged: null), + ), + WidgetSpan( + child: Checkbox(value: false, onChanged: null), + ), + TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), + WidgetSpan( + alignment: PlaceholderAlignment.belowBaseline, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 50.0, + height: 55.0, + child: DecoratedBox( + decoration: BoxDecoration( + color: Color(0xffffff00), + ), + child: Center( + child:SizedBox( + width: 10.0, + height: 15.0, + child: DecoratedBox( + decoration: BoxDecoration( + color: Color(0xffff0000), + ), ), ), ), ), ), ), - ), - TextSpan(text: 'hello world! seize the day!'), - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), + TextSpan(text: 'hello world! seize the day!'), + WidgetSpan( + alignment: PlaceholderAlignment.belowBaseline, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: false, onChanged: null), ), - ), - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), + WidgetSpan( + alignment: PlaceholderAlignment.belowBaseline, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 20, + height: 20, + child: Checkbox(value: true, onChanged: null), + ), ), - ), - WidgetSpan( - alignment: PlaceholderAlignment.belowBaseline, - baseline: TextBaseline.alphabetic, - child: Text('embedded'), - ), - TextSpan(text: 'ref'), - ], + WidgetSpan( + alignment: PlaceholderAlignment.belowBaseline, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: false, onChanged: null), + ), + WidgetSpan( + alignment: PlaceholderAlignment.belowBaseline, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 20, + height: 20, + child: Checkbox(value: true, onChanged: null), + ), + ), + WidgetSpan( + alignment: PlaceholderAlignment.belowBaseline, + baseline: TextBaseline.alphabetic, + child: Text('embedded'), + ), + TextSpan(text: 'ref'), + ], + ), + textDirection: TextDirection.ltr, ), - textDirection: TextDirection.ltr, ), ), ), @@ -1109,97 +1121,100 @@ void main() { testWidgets('Text Inline widget top', (WidgetTester tester) async { await tester.pumpWidget( - Center( - child: RepaintBoundary( - child: Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Container( - width: 400.0, - height: 200.0, - decoration: const BoxDecoration( - color: Color(0xff00ff00), - ), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 200, maxHeight: 100), - child: const Text.rich( - TextSpan( - text: 'C ', - style: TextStyle( - fontSize: 16, - ), - children: <InlineSpan>[ - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: true, onChanged: null), - ), - WidgetSpan( - child: Checkbox(value: false, onChanged: null), + Theme( + data: ThemeData(useMaterial3: false), + child: Center( + child: RepaintBoundary( + child: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Container( + width: 400.0, + height: 200.0, + decoration: const BoxDecoration( + color: Color(0xff00ff00), + ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 200, maxHeight: 100), + child: const Text.rich( + TextSpan( + text: 'C ', + style: TextStyle( + fontSize: 16, ), - TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffffff00), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffff0000), + children: <InlineSpan>[ + WidgetSpan( + alignment: PlaceholderAlignment.top, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: true, onChanged: null), + ), + WidgetSpan( + child: Checkbox(value: false, onChanged: null), + ), + TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), + WidgetSpan( + alignment: PlaceholderAlignment.top, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 50.0, + height: 55.0, + child: DecoratedBox( + decoration: BoxDecoration( + color: Color(0xffffff00), + ), + child: Center( + child:SizedBox( + width: 10.0, + height: 15.0, + child: DecoratedBox( + decoration: BoxDecoration( + color: Color(0xffff0000), + ), ), ), ), ), ), ), - ), - TextSpan(text: 'hello world! seize the day!'), - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), + TextSpan(text: 'hello world! seize the day!'), + WidgetSpan( + alignment: PlaceholderAlignment.top, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: false, onChanged: null), ), - ), - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), + WidgetSpan( + alignment: PlaceholderAlignment.top, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 20, + height: 20, + child: Checkbox(value: true, onChanged: null), + ), ), - ), - WidgetSpan( - alignment: PlaceholderAlignment.top, - baseline: TextBaseline.alphabetic, - child: Text('embedded'), - ), - TextSpan(text: 'ref'), - ], + WidgetSpan( + alignment: PlaceholderAlignment.top, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: false, onChanged: null), + ), + WidgetSpan( + alignment: PlaceholderAlignment.top, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 20, + height: 20, + child: Checkbox(value: true, onChanged: null), + ), + ), + WidgetSpan( + alignment: PlaceholderAlignment.top, + baseline: TextBaseline.alphabetic, + child: Text('embedded'), + ), + TextSpan(text: 'ref'), + ], + ), + textDirection: TextDirection.ltr, ), - textDirection: TextDirection.ltr, ), ), ), @@ -1216,97 +1231,100 @@ void main() { testWidgets('Text Inline widget middle', (WidgetTester tester) async { await tester.pumpWidget( - Center( - child: RepaintBoundary( - child: Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Container( - width: 400.0, - height: 200.0, - decoration: const BoxDecoration( - color: Color(0xff00ff00), - ), - child: ConstrainedBox( - constraints: const BoxConstraints(maxWidth: 200, maxHeight: 100), - child: const Text.rich( - TextSpan( - text: 'C ', - style: TextStyle( - fontSize: 16, - ), - children: <InlineSpan>[ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: true, onChanged: null), - ), - WidgetSpan( - child: Checkbox(value: false, onChanged: null), + Theme( + data: ThemeData(useMaterial3: false), + child: Center( + child: RepaintBoundary( + child: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Container( + width: 400.0, + height: 200.0, + decoration: const BoxDecoration( + color: Color(0xff00ff00), + ), + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 200, maxHeight: 100), + child: const Text.rich( + TextSpan( + text: 'C ', + style: TextStyle( + fontSize: 16, ), - TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 50.0, - height: 55.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffffff00), - ), - child: Center( - child:SizedBox( - width: 10.0, - height: 15.0, - child: DecoratedBox( - decoration: BoxDecoration( - color: Color(0xffff0000), + children: <InlineSpan>[ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: true, onChanged: null), + ), + WidgetSpan( + child: Checkbox(value: false, onChanged: null), + ), + TextSpan(text: 'He ', style: TextStyle(fontSize: 20)), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 50.0, + height: 55.0, + child: DecoratedBox( + decoration: BoxDecoration( + color: Color(0xffffff00), + ), + child: Center( + child:SizedBox( + width: 10.0, + height: 15.0, + child: DecoratedBox( + decoration: BoxDecoration( + color: Color(0xffff0000), + ), ), ), ), ), ), ), - ), - TextSpan(text: 'hello world! seize the day!'), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), + TextSpan(text: 'hello world! seize the day!'), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: false, onChanged: null), ), - ), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: Checkbox(value: false, onChanged: null), - ), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: SizedBox( - width: 20, - height: 20, - child: Checkbox(value: true, onChanged: null), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 20, + height: 20, + child: Checkbox(value: true, onChanged: null), + ), ), - ), - WidgetSpan( - alignment: PlaceholderAlignment.middle, - baseline: TextBaseline.alphabetic, - child: Text('embedded'), - ), - TextSpan(text: 'ref'), - ], + WidgetSpan( + alignment: PlaceholderAlignment.middle, + baseline: TextBaseline.alphabetic, + child: Checkbox(value: false, onChanged: null), + ), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + baseline: TextBaseline.alphabetic, + child: SizedBox( + width: 20, + height: 20, + child: Checkbox(value: true, onChanged: null), + ), + ), + WidgetSpan( + alignment: PlaceholderAlignment.middle, + baseline: TextBaseline.alphabetic, + child: Text('embedded'), + ), + TextSpan(text: 'ref'), + ], + ), + textDirection: TextDirection.ltr, ), - textDirection: TextDirection.ltr, ), ), ), diff --git a/packages/flutter/test/widgets/text_test.dart b/packages/flutter/test/widgets/text_test.dart index ba727f123d216..b501a783f079d 100644 --- a/packages/flutter/test/widgets/text_test.dart +++ b/packages/flutter/test/widgets/text_test.dart @@ -208,6 +208,7 @@ void main() { double textScaleFactor = 1.0; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( appBar: AppBar(title: const Text('title')), body: Center( @@ -236,6 +237,7 @@ void main() { textScaleFactor = textScaleFactor * 5; await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( appBar: AppBar(title: const Text('title')), body: Center( @@ -264,6 +266,23 @@ void main() { expect(renderText.size.height, singleLineHeight * textScaleFactor * 3); }); + testWidgets("Inline widgets' scaled sizes are constrained", (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/130588 + await tester.pumpWidget( + const Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: SizedBox( + width: 502.5454545454545, + child: Text.rich(WidgetSpan(child: Row()), textScaleFactor: 0.95), + ), + ), + ), + ); + + expect(tester.takeException(), isNull); + }); + testWidgets('semanticsLabel can override text label', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( @@ -997,7 +1016,7 @@ void main() { TestSemantics( label: 'INTERRUPTION', textDirection: TextDirection.rtl, - rect: const Rect.fromLTRB(0.0, 0.0, 40.0, 80.0), + rect: const Rect.fromLTRB(0.0, 0.0, 20.0, 40.0), ), TestSemantics( label: 'sky', @@ -1132,6 +1151,7 @@ void main() { Future<void> createText(TextWidthBasis textWidthBasis) { return tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Center( // Each word takes up more than a half of a line. Together they @@ -1537,6 +1557,59 @@ void main() { expect(paragraph.getMinIntrinsicWidth(0.0), 200); }); + testWidgets('can compute intrinsic width and height for widget span with text scaling', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/59316 + const Key textKey = Key('RichText'); + Widget textWithNestedInlineSpans({ required double textScaleFactor, required double screenWidth }) { + return Directionality( + textDirection: TextDirection.ltr, + child: Center( + child: OverflowBox( + alignment: Alignment.topLeft, + maxWidth: screenWidth, + child: RichText( + key: textKey, + textScaleFactor: textScaleFactor, + text: const TextSpan(children: <InlineSpan>[ + WidgetSpan(child: Text('one two')), + ]), + ), + ), + ), + ); + } + // The render object is going to be reused across widget tree rebuilds. + late final RenderParagraph outerParagraph = tester.renderObject(find.byKey(textKey)); + + await tester.pumpWidget(textWithNestedInlineSpans(textScaleFactor: 1.0, screenWidth: 100.0)); + expect( + outerParagraph.getMaxIntrinsicHeight(100.0), + 14.0, + reason: 'singleLineHeight = 14.0', + ); + + await tester.pumpWidget(textWithNestedInlineSpans(textScaleFactor: 2.0, screenWidth: 100.0)); + expect( + outerParagraph.getMinIntrinsicHeight(100.0), + 14.0 * 2.0 * 2, + reason: 'intrinsicHeight = singleLineHeight * textScaleFactor * two lines.', + ); + + await tester.pumpWidget(textWithNestedInlineSpans(textScaleFactor: 1.0, screenWidth: 1000.0)); + expect( + outerParagraph.getMaxIntrinsicWidth(1000.0), + 14.0 * 7, + reason: 'intrinsic width = 14.0 * 7', + ); + + await tester.pumpWidget(textWithNestedInlineSpans(textScaleFactor: 2.0, screenWidth: 1000.0)); + expect( + outerParagraph.getMaxIntrinsicWidth(1000.0), + 14.0 * 2.0 * 7, + reason: 'intrinsic width = glyph advance * textScaleFactor * num of glyphs', + ); + }); + testWidgets('Text uses TextStyle.overflow', (WidgetTester tester) async { const TextOverflow overflow = TextOverflow.fade; diff --git a/packages/flutter/test/widgets/two_dimensional_scroll_view_test.dart b/packages/flutter/test/widgets/two_dimensional_scroll_view_test.dart new file mode 100644 index 0000000000000..9da89311ceb4b --- /dev/null +++ b/packages/flutter/test/widgets/two_dimensional_scroll_view_test.dart @@ -0,0 +1,327 @@ +// 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/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/src/gestures/monodrag.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'two_dimensional_utils.dart'; + +Widget? _testChildBuilder(BuildContext context, ChildVicinity vicinity) { + return SizedBox( + height: 200, + width: 200, + child: Center(child: Text('C${vicinity.xIndex}:R${vicinity.yIndex}')), + ); +} + +void main() { + group('TwoDimensionalScrollView',() { + testWidgets('asserts the axis directions do not conflict with one another', (WidgetTester tester) async { + final List<Object> exceptions = <Object>[]; + final FlutterExceptionHandler? oldHandler = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + // Horizontal wrong + await tester.pumpWidget(MaterialApp( + home: SimpleBuilderTableView( + delegate: TwoDimensionalChildBuilderDelegate(builder: (_, __) => null), + horizontalDetails: const ScrollableDetails.vertical(), + // Horizontal has default const ScrollableDetails.horizontal() + ), + )); + + // Vertical wrong + await tester.pumpWidget(MaterialApp( + home: SimpleBuilderTableView( + delegate: TwoDimensionalChildBuilderDelegate(builder: (_, __) => null), + verticalDetails: const ScrollableDetails.horizontal(), + // Horizontal has default const ScrollableDetails.horizontal() + ), + )); + + // Both wrong + await tester.pumpWidget(MaterialApp( + home: SimpleBuilderTableView( + delegate: TwoDimensionalChildBuilderDelegate(builder: (_, __) => null), + verticalDetails: const ScrollableDetails.horizontal(), + horizontalDetails: const ScrollableDetails.vertical(), + ), + )); + + FlutterError.onError = oldHandler; + expect(exceptions.length, 3); + for (final Object exception in exceptions) { + expect(exception, isAssertionError); + expect((exception as AssertionError).message, contains('are not Axis')); + } + }, variant: TargetPlatformVariant.all()); + + testWidgets('ScrollableDetails.controller can set initial scroll positions, modify within bounds', (WidgetTester tester) async { + final ScrollController verticalController = ScrollController(initialScrollOffset: 100); + final ScrollController horizontalController = ScrollController(initialScrollOffset: 50); + + await tester.pumpWidget(MaterialApp( + home: SimpleBuilderTableView( + verticalDetails: ScrollableDetails.vertical(controller: verticalController), + horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController), + delegate: TwoDimensionalChildBuilderDelegate( + builder: _testChildBuilder, + maxXIndex: 99, + maxYIndex: 99, + ), + ), + )); + await tester.pumpAndSettle(); + + expect(verticalController.position.pixels, 100); + expect(verticalController.position.maxScrollExtent, 19400); + expect(horizontalController.position.pixels, 50); + expect(horizontalController.position.maxScrollExtent, 19200); + + verticalController.jumpTo(verticalController.position.maxScrollExtent); + horizontalController.jumpTo(horizontalController.position.maxScrollExtent); + await tester.pump(); + + expect(verticalController.position.pixels, 19400); + expect(horizontalController.position.pixels, 19200); + + // Out of bounds + verticalController.jumpTo(verticalController.position.maxScrollExtent + 100); + horizontalController.jumpTo(horizontalController.position.maxScrollExtent + 100); + // Account for varying scroll physics for different platforms (overscroll) + await tester.pumpAndSettle(); + + expect(verticalController.position.pixels, 19400); + expect(horizontalController.position.pixels, 19200); + }, variant: TargetPlatformVariant.all()); + + testWidgets('Properly assigns the PrimaryScrollController to the main axis on the correct platform', (WidgetTester tester) async { + late ScrollController controller; + Widget buildForPrimaryScrollController({ + bool? explicitPrimary, + Axis mainAxis = Axis.vertical, + bool addControllerConflict = false, + }) { + return MaterialApp( + home: PrimaryScrollController( + controller: controller, + child: SimpleBuilderTableView( + mainAxis: mainAxis, + primary: explicitPrimary, + verticalDetails: ScrollableDetails.vertical( + controller: addControllerConflict && mainAxis == Axis.vertical + ? ScrollController() + : null + ), + horizontalDetails: ScrollableDetails.horizontal( + controller: addControllerConflict && mainAxis == Axis.horizontal + ? ScrollController() + : null + ), + delegate: TwoDimensionalChildBuilderDelegate( + builder: _testChildBuilder, + maxXIndex: 99, + maxYIndex: 99, + ), + ), + ), + ); + } + + // Horizontal default - horizontal never automatically adopts PSC + controller = ScrollController(); + await tester.pumpWidget(buildForPrimaryScrollController( + mainAxis: Axis.horizontal, + )); + await tester.pumpAndSettle(); + + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + expect(controller.hasClients, isFalse); + } + + // Horizontal explicitly true + controller = ScrollController(); + await tester.pumpWidget(buildForPrimaryScrollController( + mainAxis: Axis.horizontal, + explicitPrimary: true, + )); + await tester.pumpAndSettle(); + + switch (defaultTargetPlatform) { + // Primary explicitly true is always adopted. + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + expect(controller.hasClients, isTrue); + expect(controller.position.axis, Axis.horizontal); + } + + // Horizontal explicitly false + controller = ScrollController(); + await tester.pumpWidget(buildForPrimaryScrollController( + mainAxis: Axis.horizontal, + explicitPrimary: false, + )); + await tester.pumpAndSettle(); + + switch (defaultTargetPlatform) { + // Primary explicitly false is never adopted. + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + expect(controller.hasClients, isFalse); + } + + // Vertical default + controller = ScrollController(); + await tester.pumpWidget(buildForPrimaryScrollController()); + await tester.pumpAndSettle(); + + switch (defaultTargetPlatform) { + // Mobile platforms inherit the PSC without explicitly setting + // primary + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + expect(controller.hasClients, isTrue); + expect(controller.position.axis, Axis.vertical); + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + expect(controller.hasClients, isFalse); + } + + // Vertical explicitly true + controller = ScrollController(); + await tester.pumpWidget(buildForPrimaryScrollController( + explicitPrimary: true, + )); + await tester.pumpAndSettle(); + + switch (defaultTargetPlatform) { + // Primary explicitly true is always adopted. + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + expect(controller.hasClients, isTrue); + expect(controller.position.axis, Axis.vertical); + } + + // Vertical explicitly false + controller = ScrollController(); + await tester.pumpWidget(buildForPrimaryScrollController( + explicitPrimary: false, + )); + await tester.pumpAndSettle(); + + switch (defaultTargetPlatform) { + // Primary explicitly false is never adopted. + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + expect(controller.hasClients, isFalse); + } + + // Assertions + final List<Object> exceptions = <Object>[]; + final FlutterExceptionHandler? oldHandler = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + // Vertical asserts ScrollableDetails.controller has not been provided if + // primary is explicitly set + controller = ScrollController(); + await tester.pumpWidget(buildForPrimaryScrollController( + explicitPrimary: true, + addControllerConflict: true, + )); + expect(exceptions.length, 1); + expect(exceptions[0], isAssertionError); + expect( + (exceptions[0] as AssertionError).message, + contains('TwoDimensionalScrollView.primary was explicitly set to true'), + ); + exceptions.clear(); + + // Horizontal asserts ScrollableDetails.controller has not been provided + // if primary is explicitly set true + controller = ScrollController(); + await tester.pumpWidget(buildForPrimaryScrollController( + mainAxis: Axis.horizontal, + explicitPrimary: true, + addControllerConflict: true, + )); + expect(exceptions.length, 1); + expect(exceptions[0], isAssertionError); + expect( + (exceptions[0] as AssertionError).message, + contains('TwoDimensionalScrollView.primary was explicitly set to true'), + ); + FlutterError.onError = oldHandler; + }, variant: TargetPlatformVariant.all()); + + testWidgets('TwoDimensionalScrollable receives the correct details from TwoDimensionalScrollView', (WidgetTester tester) async { + late BuildContext capturedContext; + // Default + await tester.pumpWidget(MaterialApp( + home: SimpleBuilderTableView( + delegate: TwoDimensionalChildBuilderDelegate( + builder: (BuildContext context, ChildVicinity vicinity) { + capturedContext = context; + return Text(vicinity.toString()); + }, + ), + ), + )); + await tester.pumpAndSettle(); + TwoDimensionalScrollableState scrollable = TwoDimensionalScrollable.of( + capturedContext, + ); + expect(scrollable.widget.verticalDetails.direction, AxisDirection.down); + expect(scrollable.widget.horizontalDetails.direction, AxisDirection.right); + expect(scrollable.widget.diagonalDragBehavior, DiagonalDragBehavior.none); + expect(scrollable.widget.dragStartBehavior, DragStartBehavior.start); + + // Customized + await tester.pumpWidget(MaterialApp( + home: SimpleBuilderTableView( + verticalDetails: const ScrollableDetails.vertical(reverse: true), + horizontalDetails: const ScrollableDetails.horizontal(reverse: true), + diagonalDragBehavior: DiagonalDragBehavior.weightedContinuous, + dragStartBehavior: DragStartBehavior.down, + delegate: TwoDimensionalChildBuilderDelegate( + builder: _testChildBuilder, + ), + ), + )); + await tester.pumpAndSettle(); + scrollable = TwoDimensionalScrollable.of(capturedContext); + expect(scrollable.widget.verticalDetails.direction, AxisDirection.up); + expect(scrollable.widget.horizontalDetails.direction, AxisDirection.left); + expect(scrollable.widget.diagonalDragBehavior, DiagonalDragBehavior.weightedContinuous); + expect(scrollable.widget.dragStartBehavior, DragStartBehavior.down); + }, variant: TargetPlatformVariant.all()); + }); +} diff --git a/packages/flutter/test/widgets/two_dimensional_utils.dart b/packages/flutter/test/widgets/two_dimensional_utils.dart new file mode 100644 index 0000000000000..5eb3bf2602082 --- /dev/null +++ b/packages/flutter/test/widgets/two_dimensional_utils.dart @@ -0,0 +1,426 @@ +// 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:math' as math; + +import 'package:flutter/foundation.dart' show clampDouble; +import 'package:flutter/gestures.dart' show DragStartBehavior; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart' show ViewportOffset; + + +// BUILDER DELEGATE --- + +final TwoDimensionalChildBuilderDelegate builderDelegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 5, + maxYIndex: 5, + builder: (BuildContext context, ChildVicinity vicinity) { + return Container( + color: vicinity.xIndex.isEven && vicinity.yIndex.isEven + ? Colors.amber[100] + : (vicinity.xIndex.isOdd && vicinity.yIndex.isOdd + ? Colors.blueAccent[100] + : null), + height: 200, + width: 200, + child: Center(child: Text('R${vicinity.xIndex}:C${vicinity.yIndex}')), + ); + } +); + +// Creates a simple 2D table of 200x200 squares with a builder delegate. +Widget simpleBuilderTest({ + Axis mainAxis = Axis.vertical, + bool? primary, + ScrollableDetails? verticalDetails, + ScrollableDetails? horizontalDetails, + TwoDimensionalChildBuilderDelegate? delegate, + double? cacheExtent, + DiagonalDragBehavior? diagonalDrag, + Clip? clipBehavior, + String? restorationID, + bool useCacheExtent = false, + bool applyDimensions = true, + bool forgetToLayoutChild = false, + bool setLayoutOffset = true, +}) { + return MaterialApp( + theme: ThemeData(useMaterial3: false), + restorationScopeId: restorationID, + home: Scaffold( + body: SimpleBuilderTableView( + mainAxis: mainAxis, + verticalDetails: verticalDetails ?? const ScrollableDetails.vertical(), + horizontalDetails: horizontalDetails ?? const ScrollableDetails.horizontal(), + cacheExtent: cacheExtent, + useCacheExtent: useCacheExtent, + diagonalDragBehavior: diagonalDrag ?? DiagonalDragBehavior.none, + clipBehavior: clipBehavior ?? Clip.hardEdge, + delegate: delegate ?? builderDelegate, + applyDimensions: applyDimensions, + forgetToLayoutChild: forgetToLayoutChild, + setLayoutOffset: setLayoutOffset, + ), + ), + ); +} + +class SimpleBuilderTableView extends TwoDimensionalScrollView { + const SimpleBuilderTableView({ + super.key, + super.primary, + super.mainAxis = Axis.vertical, + super.verticalDetails = const ScrollableDetails.vertical(), + super.horizontalDetails = const ScrollableDetails.horizontal(), + required TwoDimensionalChildBuilderDelegate delegate, + super.cacheExtent, + super.diagonalDragBehavior = DiagonalDragBehavior.none, + super.dragStartBehavior = DragStartBehavior.start, + super.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, + super.clipBehavior = Clip.hardEdge, + this.useCacheExtent = false, + this.applyDimensions = true, + this.forgetToLayoutChild = false, + this.setLayoutOffset = true, + }) : super(delegate: delegate); + + // Piped through for testing in RenderTwoDimensionalViewport + final bool useCacheExtent; + final bool applyDimensions; + final bool forgetToLayoutChild; + final bool setLayoutOffset; + + @override + Widget buildViewport(BuildContext context, ViewportOffset verticalOffset, ViewportOffset horizontalOffset) { + return SimpleBuilderTableViewport( + horizontalOffset: horizontalOffset, + horizontalAxisDirection: horizontalDetails.direction, + verticalOffset: verticalOffset, + verticalAxisDirection: verticalDetails.direction, + mainAxis: mainAxis, + delegate: delegate as TwoDimensionalChildBuilderDelegate, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + useCacheExtent: useCacheExtent, + applyDimensions: applyDimensions, + forgetToLayoutChild: forgetToLayoutChild, + setLayoutOffset: setLayoutOffset, + ); + } +} + +class SimpleBuilderTableViewport extends TwoDimensionalViewport { + const SimpleBuilderTableViewport({ + super.key, + required super.verticalOffset, + required super.verticalAxisDirection, + required super.horizontalOffset, + required super.horizontalAxisDirection, + required TwoDimensionalChildBuilderDelegate delegate, + required super.mainAxis, + super.cacheExtent, + super.clipBehavior = Clip.hardEdge, + this.useCacheExtent = false, + this.applyDimensions = true, + this.forgetToLayoutChild = false, + this.setLayoutOffset = true, + }) : super(delegate: delegate); + + // Piped through for testing in RenderTwoDimensionalViewport + final bool useCacheExtent; + final bool applyDimensions; + final bool forgetToLayoutChild; + final bool setLayoutOffset; + + @override + RenderTwoDimensionalViewport createRenderObject(BuildContext context) { + return RenderSimpleBuilderTableViewport( + horizontalOffset: horizontalOffset, + horizontalAxisDirection: horizontalAxisDirection, + verticalOffset: verticalOffset, + verticalAxisDirection: verticalAxisDirection, + mainAxis: mainAxis, + delegate: delegate as TwoDimensionalChildBuilderDelegate, + childManager: context as TwoDimensionalChildManager, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + useCacheExtent: useCacheExtent, + applyDimensions: applyDimensions, + forgetToLayoutChild: forgetToLayoutChild, + setLayoutOffset: setLayoutOffset, + ); + } + + @override + void updateRenderObject(BuildContext context, RenderSimpleBuilderTableViewport renderObject) { + renderObject + ..horizontalOffset = horizontalOffset + ..horizontalAxisDirection = horizontalAxisDirection + ..verticalOffset = verticalOffset + ..verticalAxisDirection = verticalAxisDirection + ..mainAxis = mainAxis + ..delegate = delegate + ..cacheExtent = cacheExtent + ..clipBehavior = clipBehavior; + } +} + +class RenderSimpleBuilderTableViewport extends RenderTwoDimensionalViewport { + RenderSimpleBuilderTableViewport({ + required super.horizontalOffset, + required super.horizontalAxisDirection, + required super.verticalOffset, + required super.verticalAxisDirection, + required TwoDimensionalChildBuilderDelegate delegate, + required super.mainAxis, + required super.childManager, + super.cacheExtent, + super.clipBehavior = Clip.hardEdge, + this.applyDimensions = true, + this.setLayoutOffset = true, + this.useCacheExtent = false, + this.forgetToLayoutChild = false, + }) : super(delegate: delegate); + + // These are to test conditions to validate subclass implementations after + // layoutChildSequence + final bool applyDimensions; + final bool setLayoutOffset; + final bool useCacheExtent; + final bool forgetToLayoutChild; + + RenderBox? testGetChildFor(ChildVicinity vicinity) => getChildFor(vicinity); + + @override + void layoutChildSequence() { + // Really simple table implementation for testing. + // Every child is 200x200 square + final double horizontalPixels = horizontalOffset.pixels; + final double verticalPixels = verticalOffset.pixels; + final double viewportWidth = viewportDimension.width + (useCacheExtent ? cacheExtent : 0.0); + final double viewportHeight = viewportDimension.height + (useCacheExtent ? cacheExtent : 0.0); + final TwoDimensionalChildBuilderDelegate builderDelegate = delegate as TwoDimensionalChildBuilderDelegate; + + final int maxRowIndex; + final int maxColumnIndex; + maxRowIndex = builderDelegate.maxYIndex ?? 5; + maxColumnIndex = builderDelegate.maxXIndex ?? 5; + + final int leadingColumn = math.max((horizontalPixels / 200).floor(), 0); + final int leadingRow = math.max((verticalPixels / 200).floor(), 0); + final int trailingColumn = math.min( + ((horizontalPixels + viewportWidth) / 200).ceil(), + maxColumnIndex, + ); + final int trailingRow = math.min( + ((verticalPixels + viewportHeight) / 200).ceil(), + maxRowIndex, + ); + + double xLayoutOffset = (leadingColumn * 200) - horizontalOffset.pixels; + for (int column = leadingColumn; column <= trailingColumn; column++) { + double yLayoutOffset = (leadingRow * 200) - verticalOffset.pixels; + for (int row = leadingRow; row <= trailingRow; row++) { + final ChildVicinity vicinity = ChildVicinity(xIndex: column, yIndex: row); + final RenderBox child = buildOrObtainChildFor(vicinity)!; + if (!forgetToLayoutChild) { + child.layout(constraints.tighten(width: 200.0, height: 200.0)); + } + + if (setLayoutOffset) { + parentDataOf(child).layoutOffset = Offset(xLayoutOffset, yLayoutOffset); + } + yLayoutOffset += 200; + } + xLayoutOffset += 200; + } + if (applyDimensions) { + final double verticalExtent = 200 * (maxRowIndex + 1); + verticalOffset.applyContentDimensions( + 0.0, + clampDouble(verticalExtent - viewportDimension.height, 0.0, double.infinity), + ); + final double horizontalExtent = 200 * (maxColumnIndex + 1); + horizontalOffset.applyContentDimensions( + 0.0, + clampDouble(horizontalExtent - viewportDimension.width, 0.0, double.infinity), + ); + } + } +} + +// LIST DELEGATE --- +final List<List<Widget>> children = List<List<Widget>>.generate( + 100, + (int xIndex) { + return List<Widget>.generate( + 100, + (int yIndex) { + return Container( + color: xIndex.isEven && yIndex.isEven + ? Colors.amber[100] + : (xIndex.isOdd && yIndex.isOdd + ? Colors.blueAccent[100] + : null), + height: 200, + width: 200, + child: Center(child: Text('R$xIndex:C$yIndex')), + ); + }, + ); + }, +); + +// Builds a simple 2D table of 200x200 squares with a list delegate. +Widget simpleListTest({ + Axis mainAxis = Axis.vertical, + bool? primary, + ScrollableDetails? verticalDetails, + ScrollableDetails? horizontalDetails, + TwoDimensionalChildListDelegate? delegate, + double? cacheExtent, + DiagonalDragBehavior? diagonalDrag, + Clip? clipBehavior, +}) { + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: Scaffold( + body: SimpleListTableView( + mainAxis: mainAxis, + verticalDetails: verticalDetails ?? const ScrollableDetails.vertical(), + horizontalDetails: horizontalDetails ?? const ScrollableDetails.horizontal(), + cacheExtent: cacheExtent, + diagonalDragBehavior: diagonalDrag ?? DiagonalDragBehavior.none, + clipBehavior: clipBehavior ?? Clip.hardEdge, + delegate: delegate ?? TwoDimensionalChildListDelegate(children: children), + ), + ), + ); +} + +class SimpleListTableView extends TwoDimensionalScrollView { + const SimpleListTableView({ + super.key, + super.primary, + super.mainAxis = Axis.vertical, + super.verticalDetails = const ScrollableDetails.vertical(), + super.horizontalDetails = const ScrollableDetails.horizontal(), + required TwoDimensionalChildListDelegate delegate, + super.cacheExtent, + super.diagonalDragBehavior = DiagonalDragBehavior.none, + super.dragStartBehavior = DragStartBehavior.start, + super.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual, + super.clipBehavior = Clip.hardEdge, + }) : super(delegate: delegate); + + @override + Widget buildViewport(BuildContext context, ViewportOffset verticalOffset, ViewportOffset horizontalOffset) { + return SimpleListTableViewport( + horizontalOffset: horizontalOffset, + horizontalAxisDirection: horizontalDetails.direction, + verticalOffset: verticalOffset, + verticalAxisDirection: verticalDetails.direction, + mainAxis: mainAxis, + delegate: delegate as TwoDimensionalChildListDelegate, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + ); + } +} + +class SimpleListTableViewport extends TwoDimensionalViewport { + const SimpleListTableViewport({ + super.key, + required super.verticalOffset, + required super.verticalAxisDirection, + required super.horizontalOffset, + required super.horizontalAxisDirection, + required TwoDimensionalChildListDelegate delegate, + required super.mainAxis, + super.cacheExtent, + super.clipBehavior = Clip.hardEdge, + }) : super(delegate: delegate); + + @override + RenderTwoDimensionalViewport createRenderObject(BuildContext context) { + return RenderSimpleListTableViewport( + horizontalOffset: horizontalOffset, + horizontalAxisDirection: horizontalAxisDirection, + verticalOffset: verticalOffset, + verticalAxisDirection: verticalAxisDirection, + mainAxis: mainAxis, + delegate: delegate as TwoDimensionalChildListDelegate, + childManager: context as TwoDimensionalChildManager, + cacheExtent: cacheExtent, + clipBehavior: clipBehavior, + ); + } + + @override + void updateRenderObject(BuildContext context, RenderSimpleListTableViewport renderObject) { + renderObject + ..horizontalOffset = horizontalOffset + ..horizontalAxisDirection = horizontalAxisDirection + ..verticalOffset = verticalOffset + ..verticalAxisDirection = verticalAxisDirection + ..mainAxis = mainAxis + ..delegate = delegate + ..cacheExtent = cacheExtent + ..clipBehavior = clipBehavior; + } +} + +class RenderSimpleListTableViewport extends RenderTwoDimensionalViewport { + RenderSimpleListTableViewport({ + required super.horizontalOffset, + required super.horizontalAxisDirection, + required super.verticalOffset, + required super.verticalAxisDirection, + required TwoDimensionalChildListDelegate delegate, + required super.mainAxis, + required super.childManager, + super.cacheExtent, + super.clipBehavior = Clip.hardEdge, + }) : super(delegate: delegate); + + @override + void layoutChildSequence() { + // Really simple table implementation for testing. + // Every child is 200x200 square + final double horizontalPixels = horizontalOffset.pixels; + final double verticalPixels = verticalOffset.pixels; + final TwoDimensionalChildListDelegate listDelegate = delegate as TwoDimensionalChildListDelegate; + final int rowCount; + final int columnCount; + rowCount = listDelegate.children.length - 1; + columnCount = listDelegate.children[0].length - 1; + + final int leadingColumn = math.max((horizontalPixels / 200).floor(), 0); + final int leadingRow = math.max((verticalPixels / 200).floor(), 0); + final int trailingColumn = math.min( + ((horizontalPixels + viewportDimension.width) / 200).ceil(), + columnCount, + ); + final int trailingRow = math.min( + ((verticalPixels + viewportDimension.height) / 200).ceil(), + rowCount, + ); + + double xLayoutOffset = (leadingColumn * 200) - horizontalOffset.pixels; + for (int column = leadingColumn; column <= trailingColumn; column++) { + double yLayoutOffset = (leadingRow * 200) - verticalOffset.pixels; + for (int row = leadingRow; row <= trailingRow; row++) { + final ChildVicinity vicinity = ChildVicinity(xIndex: column, yIndex: row); + final RenderBox child = buildOrObtainChildFor(vicinity)!; + child.layout(constraints.tighten(width: 200.0, height: 200.0)); + + parentDataOf(child).layoutOffset = Offset(xLayoutOffset, yLayoutOffset); + yLayoutOffset += 200; + } + xLayoutOffset += 200; + } + verticalOffset.applyContentDimensions(0, 200 * 100 - viewportDimension.height); + horizontalOffset.applyContentDimensions(0, 200 * 100 - viewportDimension.width); + } +} diff --git a/packages/flutter/test/widgets/two_dimensional_viewport_test.dart b/packages/flutter/test/widgets/two_dimensional_viewport_test.dart new file mode 100644 index 0000000000000..53fb879e0759b --- /dev/null +++ b/packages/flutter/test/widgets/two_dimensional_viewport_test.dart @@ -0,0 +1,1923 @@ +// 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/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'two_dimensional_utils.dart'; + +void main() { + group('TwoDimensionalChildDelegate', () { + group('TwoDimensionalChildBuilderDelegate', () { + testWidgets('repaintBoundaries', (WidgetTester tester) async { + // Default - adds repaint boundaries + await tester.pumpWidget(simpleBuilderTest( + delegate: TwoDimensionalChildBuilderDelegate( + // Only build 1 child + maxXIndex: 0, + maxYIndex: 0, + builder: (BuildContext context, ChildVicinity vicinity) { + return SizedBox( + height: 200, + width: 200, + child: Center(child: Text('C${vicinity.xIndex}:R${vicinity.yIndex}')), + ); + } + ) + )); + await tester.pumpAndSettle(); + + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + expect(find.byType(RepaintBoundary), findsNWidgets(7)); + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + expect(find.byType(RepaintBoundary), findsNWidgets(3)); + } + + // None + await tester.pumpWidget(simpleBuilderTest( + delegate: TwoDimensionalChildBuilderDelegate( + // Only build 1 child + maxXIndex: 0, + maxYIndex: 0, + addRepaintBoundaries: false, + builder: (BuildContext context, ChildVicinity vicinity) { + return SizedBox( + height: 200, + width: 200, + child: Center(child: Text('C${vicinity.xIndex}:R${vicinity.yIndex}')), + ); + } + ) + )); + await tester.pumpAndSettle(); + + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + expect(find.byType(RepaintBoundary), findsNWidgets(6)); + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + expect(find.byType(RepaintBoundary), findsNWidgets(2)); + } + }, variant: TargetPlatformVariant.all()); + + testWidgets('will return null from build for exceeding maxXIndex and maxYIndex', (WidgetTester tester) async { + late BuildContext capturedContext; + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + // Only build 1 child + maxXIndex: 0, + maxYIndex: 0, + addRepaintBoundaries: false, + builder: (BuildContext context, ChildVicinity vicinity) { + capturedContext = context; + return SizedBox( + height: 200, + width: 200, + child: Center(child: Text('C${vicinity.xIndex}:R${vicinity.yIndex}')), + ); + } + ); + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + await tester.pumpAndSettle(); + // maxXIndex + expect( + delegate.build(capturedContext, const ChildVicinity(xIndex: 1, yIndex: 0)), + isNull, + ); + + // maxYIndex + expect( + delegate.build(capturedContext, const ChildVicinity(xIndex: 0, yIndex: 1)), + isNull, + ); + + // Both + expect( + delegate.build(capturedContext, const ChildVicinity(xIndex: 1, yIndex: 1)), + isNull, + ); + }, variant: TargetPlatformVariant.all()); + + test('maxXIndex and maxYIndex assertions', () { + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 0, + maxYIndex: 0, + builder: (BuildContext context, ChildVicinity vicinity) { + return const SizedBox.shrink(); + } + ); + // Update + delegate.maxXIndex = -1; // No exception. + expect( + () { + delegate.maxXIndex = -2; + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('value == null || value >= -1'), + ), + ), + ); + delegate.maxYIndex = -1; // No exception + expect( + () { + delegate.maxYIndex = -2; + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('value == null || value >= -1'), + ), + ), + ); + // Constructor + expect( + () { + TwoDimensionalChildBuilderDelegate( + maxXIndex: -2, + maxYIndex: 0, + builder: (BuildContext context, ChildVicinity vicinity) { + return const SizedBox.shrink(); + } + ); + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('maxXIndex == null || maxXIndex >= -1'), + ), + ), + ); + expect( + () { + TwoDimensionalChildBuilderDelegate( + maxXIndex: 0, + maxYIndex: -2, + builder: (BuildContext context, ChildVicinity vicinity) { + return const SizedBox.shrink(); + } + ); + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('maxYIndex == null || maxYIndex >= -1'), + ), + ), + ); + }); + + testWidgets('throws an error when builder throws', (WidgetTester tester) async { + final List<Object> exceptions = <Object>[]; + final FlutterExceptionHandler? oldHandler = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + // Only build 1 child + maxXIndex: 0, + maxYIndex: 0, + addRepaintBoundaries: false, + builder: (BuildContext context, ChildVicinity vicinity) { + throw 'Builder error!'; + } + ); + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + await tester.pumpAndSettle(); + FlutterError.onError = oldHandler; + + expect(exceptions.isNotEmpty, isTrue); + expect(exceptions.length, 1); + expect(exceptions[0] as String, contains('Builder error!')); + }, variant: TargetPlatformVariant.all()); + + testWidgets('shouldRebuild', (WidgetTester tester) async { + expect(builderDelegate.shouldRebuild(builderDelegate), isTrue); + }, variant: TargetPlatformVariant.all()); + }); + + group('TwoDimensionalChildListDelegate', () { + testWidgets('repaintBoundaries', (WidgetTester tester) async { + final List<List<Widget>> children = <List<Widget>>[]; + children.add(<Widget>[ + const SizedBox( + height: 200, + width: 200, + child: Center(child: Text('R0:C0')), + ) + ]); + // Default - adds repaint boundaries + await tester.pumpWidget(simpleListTest( + delegate: TwoDimensionalChildListDelegate( + // Only builds 1 child + children: children, + ) + )); + await tester.pumpAndSettle(); + + // In the tests below the number of RepaintBoundary widgets depends on: + // ModalRoute - builds 2 + // GlowingOverscrollIndicator - builds 2 + // TwoDimensionalChildListDelegate - builds 1 unless addRepaintBoundaries is false + + void expectModalRoute() { + expect(ModalRoute.of(tester.element(find.byType(SimpleListTableViewport))), isA<MaterialPageRoute<void>>()); + } + + switch (defaultTargetPlatform) { + case TargetPlatform.fuchsia: + expectModalRoute(); + expect(find.byType(GlowingOverscrollIndicator), findsNWidgets(2)); + expect(find.byType(RepaintBoundary), findsNWidgets(7)); + + case TargetPlatform.android: + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + expectModalRoute(); + expect(find.byType(RepaintBoundary), findsNWidgets(3)); + } + + // None + await tester.pumpWidget(simpleListTest( + delegate: TwoDimensionalChildListDelegate( + // Different children triggers rebuild + children: <List<Widget>>[<Widget>[Container()]], + addRepaintBoundaries: false, + ) + )); + await tester.pumpAndSettle(); + + switch (defaultTargetPlatform) { + case TargetPlatform.fuchsia: + expectModalRoute(); + expect(find.byType(GlowingOverscrollIndicator), findsNWidgets(2)); + expect(find.byType(RepaintBoundary), findsNWidgets(6)); + + case TargetPlatform.android: + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + case TargetPlatform.windows: + expectModalRoute(); + expect(find.byType(RepaintBoundary), findsNWidgets(2)); + } + }, variant: TargetPlatformVariant.all()); + + testWidgets('will return null for a ChildVicinity outside of list bounds', (WidgetTester tester) async { + final List<List<Widget>> children = <List<Widget>>[]; + children.add(<Widget>[ + const SizedBox( + height: 200, + width: 200, + child: Center(child: Text('R0:C0')), + ) + ]); + final TwoDimensionalChildListDelegate delegate = TwoDimensionalChildListDelegate( + // Only builds 1 child + children: children, + ); + + // X index + expect( + delegate.build(_NullBuildContext(), const ChildVicinity(xIndex: 1, yIndex: 0)), + isNull, + ); + // Y index + expect( + delegate.build(_NullBuildContext(), const ChildVicinity(xIndex: 0, yIndex: 1)), + isNull, + ); + + // Both + expect( + delegate.build(_NullBuildContext(), const ChildVicinity(xIndex: 1, yIndex: 1)), + isNull, + ); + }, variant: TargetPlatformVariant.all()); + + testWidgets('shouldRebuild', (WidgetTester tester) async { + final List<List<Widget>> children = <List<Widget>>[]; + children.add(<Widget>[ + const SizedBox( + height: 200, + width: 200, + child: Center(child: Text('R0:C0')), + ) + ]); + final TwoDimensionalChildListDelegate delegate = TwoDimensionalChildListDelegate( + // Only builds 1 child + children: children, + ); + expect(delegate.shouldRebuild(delegate), isFalse); + + final List<List<Widget>> newChildren = <List<Widget>>[]; + final TwoDimensionalChildListDelegate oldDelegate = TwoDimensionalChildListDelegate( + children: newChildren, + ); + + expect(delegate.shouldRebuild(oldDelegate), isTrue); + }, variant: TargetPlatformVariant.all()); + }); + }); + + group('TwoDimensionalScrollable', () { + testWidgets('.of, .maybeOf', (WidgetTester tester) async { + late BuildContext capturedContext; + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 0, + maxYIndex: 0, + builder: (BuildContext context, ChildVicinity vicinity) { + capturedContext = context; + return const SizedBox.square(dimension: 200); + } + ); + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + await tester.pumpAndSettle(); + + expect(TwoDimensionalScrollable.of(capturedContext), isNotNull); + expect(TwoDimensionalScrollable.maybeOf(capturedContext), isNotNull); + + await tester.pumpWidget(Builder( + builder: (BuildContext context) { + capturedContext = context; + TwoDimensionalScrollable.of(context); + return Container(); + } + )); + await tester.pumpAndSettle(); + final dynamic exception = tester.takeException(); + expect(exception, isFlutterError); + final FlutterError error = exception as FlutterError; + expect(error.toString(), contains( + 'TwoDimensionalScrollable.of() was called with a context that does ' + 'not contain a TwoDimensionalScrollable widget.' + )); + + expect(TwoDimensionalScrollable.maybeOf(capturedContext), isNull); + }, variant: TargetPlatformVariant.all()); + + testWidgets('horizontal and vertical getters', (WidgetTester tester) async { + late BuildContext capturedContext; + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 0, + maxYIndex: 0, + builder: (BuildContext context, ChildVicinity vicinity) { + capturedContext = context; + return const SizedBox.square(dimension: 200); + } + ); + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + await tester.pumpAndSettle(); + + final TwoDimensionalScrollableState scrollable = TwoDimensionalScrollable.of(capturedContext); + expect(scrollable.verticalScrollable.position.pixels, 0.0); + expect(scrollable.horizontalScrollable.position.pixels, 0.0); + }, variant: TargetPlatformVariant.all()); + + testWidgets('creates fallback ScrollControllers if not provided by ScrollableDetails', (WidgetTester tester) async { + late BuildContext capturedContext; + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 0, + maxYIndex: 0, + builder: (BuildContext context, ChildVicinity vicinity) { + capturedContext = context; + return const SizedBox.square(dimension: 200); + } + ); + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + await tester.pumpAndSettle(); + + // Vertical + final ScrollableState vertical = Scrollable.of(capturedContext, axis: Axis.vertical); + expect(vertical.widget.controller, isNotNull); + // Horizontal + final ScrollableState horizontal = Scrollable.of(capturedContext, axis: Axis.horizontal); + expect(horizontal.widget.controller, isNotNull); + }, variant: TargetPlatformVariant.all()); + + testWidgets('asserts the axis directions do not conflict with one another', (WidgetTester tester) async { + final List<Object> exceptions = <Object>[]; + final FlutterExceptionHandler? oldHandler = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + // Horizontal mismatch + await tester.pumpWidget(TwoDimensionalScrollable( + horizontalDetails: const ScrollableDetails.horizontal(), + verticalDetails: const ScrollableDetails.horizontal(), + viewportBuilder: (BuildContext context, ViewportOffset verticalPosition, ViewportOffset horizontalPosition) { + return Container(); + }, + )); + + // Vertical mismatch + await tester.pumpWidget(TwoDimensionalScrollable( + horizontalDetails: const ScrollableDetails.vertical(), + verticalDetails: const ScrollableDetails.vertical(), + viewportBuilder: (BuildContext context, ViewportOffset verticalPosition, ViewportOffset horizontalPosition) { + return Container(); + }, + )); + + // Both + await tester.pumpWidget(TwoDimensionalScrollable( + horizontalDetails: const ScrollableDetails.vertical(), + verticalDetails: const ScrollableDetails.horizontal(), + viewportBuilder: (BuildContext context, ViewportOffset verticalPosition, ViewportOffset horizontalPosition) { + return Container(); + }, + )); + + expect(exceptions.length, 3); + for (final Object exception in exceptions) { + expect(exception, isAssertionError); + expect((exception as AssertionError).message, contains('are not Axis')); + } + FlutterError.onError = oldHandler; + }, variant: TargetPlatformVariant.all()); + + testWidgets('correctly sets restorationIds', (WidgetTester tester) async { + late BuildContext capturedContext; + // with restorationID set + await tester.pumpWidget(WidgetsApp( + color: const Color(0xFFFFFFFF), + restorationScopeId: 'Test ID', + builder: (BuildContext context, Widget? child) => TwoDimensionalScrollable( + restorationId: 'Custom Restoration ID', + horizontalDetails: const ScrollableDetails.horizontal(), + verticalDetails: const ScrollableDetails.vertical(), + viewportBuilder: (BuildContext context, ViewportOffset verticalPosition, ViewportOffset horizontalPosition) { + return SizedBox.square( + dimension: 200, + child: Builder( + builder: (BuildContext context) { + capturedContext = context; + return Container(); + }, + ) + ); + }, + ), + )); + await tester.pumpAndSettle(); + + expect( + RestorationScope.of(capturedContext).restorationId, + 'Custom Restoration ID', + ); + expect( + Scrollable.of(capturedContext, axis: Axis.vertical).widget.restorationId, + 'OuterVerticalTwoDimensionalScrollable', + ); + expect( + Scrollable.of(capturedContext, axis: Axis.horizontal).widget.restorationId, + 'InnerHorizontalTwoDimensionalScrollable', + ); + + // default restorationID + await tester.pumpWidget(TwoDimensionalScrollable( + horizontalDetails: const ScrollableDetails.horizontal(), + verticalDetails: const ScrollableDetails.vertical(), + viewportBuilder: (BuildContext context, ViewportOffset verticalPosition, ViewportOffset horizontalPosition) { + return SizedBox.square( + dimension: 200, + child: Builder( + builder: (BuildContext context) { + capturedContext = context; + return Container(); + }, + ) + ); + }, + )); + await tester.pumpAndSettle(); + + expect( + RestorationScope.maybeOf(capturedContext), + isNull, + ); + expect( + Scrollable.of(capturedContext, axis: Axis.vertical).widget.restorationId, + 'OuterVerticalTwoDimensionalScrollable', + ); + expect( + Scrollable.of(capturedContext, axis: Axis.horizontal).widget.restorationId, + 'InnerHorizontalTwoDimensionalScrollable', + ); + }, variant: TargetPlatformVariant.all()); + + testWidgets('Restoration works', (WidgetTester tester) async { + await tester.pumpWidget(WidgetsApp( + color: const Color(0xFFFFFFFF), + restorationScopeId: 'Test ID', + builder: (BuildContext context, Widget? child) => TwoDimensionalScrollable( + restorationId: 'Custom Restoration ID', + horizontalDetails: const ScrollableDetails.horizontal(), + verticalDetails: const ScrollableDetails.vertical(), + viewportBuilder: (BuildContext context, ViewportOffset verticalPosition, ViewportOffset horizontalPosition) { + return SimpleBuilderTableViewport( + verticalOffset: verticalPosition, + verticalAxisDirection: AxisDirection.down, + horizontalOffset: horizontalPosition, + horizontalAxisDirection: AxisDirection.right, + delegate: builderDelegate, + mainAxis: Axis.vertical, + ); + }, + ), + )); + await tester.pumpAndSettle(); + + await restoreScrollAndVerify(tester); + }, variant: TargetPlatformVariant.all()); + + testWidgets('Inner Scrollables receive the correct details from TwoDimensionalScrollable', (WidgetTester tester) async { + // Default + late BuildContext capturedContext; + await tester.pumpWidget(TwoDimensionalScrollable( + horizontalDetails: const ScrollableDetails.horizontal(), + verticalDetails: const ScrollableDetails.vertical(), + viewportBuilder: (BuildContext context, ViewportOffset verticalPosition, ViewportOffset horizontalPosition) { + return SizedBox.square( + dimension: 200, + child: Builder( + builder: (BuildContext context) { + capturedContext = context; + return Container(); + }, + ) + ); + }, + )); + await tester.pumpAndSettle(); + + // Vertical + ScrollableState vertical = Scrollable.of(capturedContext, axis: Axis.vertical); + expect(vertical.widget.key, isNotNull); + expect(vertical.widget.axisDirection, AxisDirection.down); + expect(vertical.widget.controller, isNotNull); + expect(vertical.widget.physics, isNull); + expect(vertical.widget.clipBehavior, Clip.hardEdge); + expect(vertical.widget.incrementCalculator, isNull); + expect(vertical.widget.excludeFromSemantics, isFalse); + expect(vertical.widget.restorationId, 'OuterVerticalTwoDimensionalScrollable'); + expect(vertical.widget.dragStartBehavior, DragStartBehavior.start); + + // Horizontal + ScrollableState horizontal = Scrollable.of(capturedContext, axis: Axis.horizontal); + expect(horizontal.widget.key, isNotNull); + expect(horizontal.widget.axisDirection, AxisDirection.right); + expect(horizontal.widget.controller, isNotNull); + expect(horizontal.widget.physics, isNull); + expect(horizontal.widget.clipBehavior, Clip.hardEdge); + expect(horizontal.widget.incrementCalculator, isNull); + expect(horizontal.widget.excludeFromSemantics, isFalse); + expect(horizontal.widget.restorationId, 'InnerHorizontalTwoDimensionalScrollable'); + expect(horizontal.widget.dragStartBehavior, DragStartBehavior.start); + + // Customized + final ScrollController horizontalController = ScrollController(); + final ScrollController verticalController = ScrollController(); + double calculator(_) => 0.0; + await tester.pumpWidget(TwoDimensionalScrollable( + incrementCalculator: calculator, + excludeFromSemantics: true, + dragStartBehavior: DragStartBehavior.down, + horizontalDetails: ScrollableDetails.horizontal( + reverse: true, + controller: horizontalController, + physics: const ClampingScrollPhysics(), + decorationClipBehavior: Clip.antiAlias, + ), + verticalDetails: ScrollableDetails.vertical( + reverse: true, + controller: verticalController, + physics: const AlwaysScrollableScrollPhysics(), + decorationClipBehavior: Clip.antiAliasWithSaveLayer, + ), + viewportBuilder: (BuildContext context, ViewportOffset verticalPosition, ViewportOffset horizontalPosition) { + return SizedBox.square( + dimension: 200, + child: Builder( + builder: (BuildContext context) { + capturedContext = context; + return Container(); + }, + ) + ); + }, + )); + await tester.pumpAndSettle(); + + // Vertical + vertical = Scrollable.of(capturedContext, axis: Axis.vertical); + expect(vertical.widget.key, isNotNull); + expect(vertical.widget.axisDirection, AxisDirection.up); + expect(vertical.widget.controller, verticalController); + expect(vertical.widget.physics, const AlwaysScrollableScrollPhysics()); + expect(vertical.widget.clipBehavior, Clip.antiAliasWithSaveLayer); + expect( + vertical.widget.incrementCalculator!(ScrollIncrementDetails( + type: ScrollIncrementType.line, + metrics: verticalController.position, + )), + 0.0, + ); + expect(vertical.widget.excludeFromSemantics, isTrue); + expect(vertical.widget.restorationId, 'OuterVerticalTwoDimensionalScrollable'); + expect(vertical.widget.dragStartBehavior, DragStartBehavior.down); + + // Horizontal + horizontal = Scrollable.of(capturedContext, axis: Axis.horizontal); + expect(horizontal.widget.key, isNotNull); + expect(horizontal.widget.axisDirection, AxisDirection.left); + expect(horizontal.widget.controller, horizontalController); + expect(horizontal.widget.physics, const ClampingScrollPhysics()); + expect(horizontal.widget.clipBehavior, Clip.antiAlias); + expect( + horizontal.widget.incrementCalculator!(ScrollIncrementDetails( + type: ScrollIncrementType.line, + metrics: horizontalController.position, + )), + 0.0, + ); + expect(horizontal.widget.excludeFromSemantics, isTrue); + expect(horizontal.widget.restorationId, 'InnerHorizontalTwoDimensionalScrollable'); + expect(horizontal.widget.dragStartBehavior, DragStartBehavior.down); + }, variant: TargetPlatformVariant.all()); + + group('DiagonalDragBehavior', () { + testWidgets('none (default)', (WidgetTester tester) async { + // Vertical and horizontal axes are locked. + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: simpleBuilderTest( + verticalDetails: ScrollableDetails.vertical(controller: verticalController), + horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController), + ) + )); + await tester.pumpAndSettle(); + final Finder findScrollable = find.byElementPredicate((Element e) => e.widget is TwoDimensionalScrollable); + + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + await tester.drag(findScrollable, const Offset(0.0, -100.0)); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 80.0); + expect(horizontalController.position.pixels, 0.0); + await tester.drag(findScrollable, const Offset(-100.0, 0.0)); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 80.0); + expect(horizontalController.position.pixels, 80.0); + // Drag with and x and y offset, only vertical will accept the gesture + // since the x is < kTouchSlop + await tester.drag(findScrollable, const Offset(-10.0, -50.0)); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 110.0); + expect(horizontalController.position.pixels, 80.0); + // Drag with and x and y offset, only horizontal will accept the gesture + // since the y is < kTouchSlop + await tester.drag(findScrollable, const Offset(-50.0, -10.0)); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 110.0); + expect(horizontalController.position.pixels, 110.0); + // Drag with and x and y offset, only vertical will accept the gesture + // x is > kTouchSlop, larger offset wins + await tester.drag(findScrollable, const Offset(-20.0, -50.0)); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 140.0); + expect(horizontalController.position.pixels, 110.0); + // Drag with and x and y offset, only horizontal will accept the gesture + // y is > kTouchSlop, larger offset wins + await tester.drag(findScrollable, const Offset(-50.0, -20.0)); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 140.0); + expect(horizontalController.position.pixels, 140.0); + }, variant: TargetPlatformVariant.all()); + + testWidgets('weightedEvent', (WidgetTester tester) async { + // For weighted event, the winning axis is locked for the duration of + // the gesture. + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: simpleBuilderTest( + diagonalDrag: DiagonalDragBehavior.weightedEvent, + verticalDetails: ScrollableDetails.vertical(controller: verticalController), + horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController), + ) + )); + await tester.pumpAndSettle(); + final Finder findScrollable = find.byElementPredicate((Element e) => e.widget is TwoDimensionalScrollable); + + // Locks to vertical axis - simple. + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + TestGesture gesture = await tester.startGesture(tester.getCenter(findScrollable)); + // In this case, the vertical axis clearly wins. + Offset secondLocation = tester.getCenter(findScrollable) + const Offset(0.0, -50.0); + await gesture.moveTo(secondLocation); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 50.0); + expect(horizontalController.position.pixels, 0.0); + // Gesture has not ended yet, move with horizontal diff + Offset thirdLocation = secondLocation + const Offset(-30, -15); + await gesture.moveTo(thirdLocation); + await tester.pumpAndSettle(); + // Only vertical diff applied + expect(verticalController.position.pixels, 65.0); + expect(horizontalController.position.pixels, 0.0); + await gesture.up(); + await tester.pumpAndSettle(); + + // Lock to vertical axis - scrolls diagonally until certain + verticalController.jumpTo(0.0); + horizontalController.jumpTo(0.0); + await tester.pump(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + gesture = await tester.startGesture(tester.getCenter(findScrollable)); + // In this case, the no one clearly wins, so it moves diagonally. + secondLocation = tester.getCenter(findScrollable) + const Offset(-50.0, -50.0); + await gesture.moveTo(secondLocation); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 50.0); + expect(horizontalController.position.pixels, 50.0); + // Gesture has not ended yet, move clearly indicating vertical + thirdLocation = secondLocation + const Offset(-20, -50); + await gesture.moveTo(thirdLocation); + await tester.pumpAndSettle(); + // Only vertical diff applied + expect(verticalController.position.pixels, 100.0); + expect(horizontalController.position.pixels, 50.0); + // Gesture has not ended yet, and vertical axis has won for the gesture + // continue only vertical scrolling. + Offset fourthLocation = thirdLocation + const Offset(-30, -30); + await gesture.moveTo(fourthLocation); + await tester.pumpAndSettle(); + // Only vertical diff applied + expect(verticalController.position.pixels, 130.0); + expect(horizontalController.position.pixels, 50.0); + await gesture.up(); + await tester.pumpAndSettle(); + + // Locks to horizontal axis - simple. + verticalController.jumpTo(0.0); + horizontalController.jumpTo(0.0); + await tester.pump(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + gesture = await tester.startGesture(tester.getCenter(findScrollable)); + // In this case, the horizontal axis clearly wins. + secondLocation = tester.getCenter(findScrollable) + const Offset(-50.0, 0.0); + await gesture.moveTo(secondLocation); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 50.0); + // Gesture has not ended yet, move with vertical diff + thirdLocation = secondLocation + const Offset(-15, -30); + await gesture.moveTo(thirdLocation); + await tester.pumpAndSettle(); + // Only vertical diff applied + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 65.0); + await gesture.up(); + await tester.pumpAndSettle(); + + // Lock to horizontal axis - scrolls diagonally until certain + verticalController.jumpTo(0.0); + horizontalController.jumpTo(0.0); + await tester.pump(); + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + gesture = await tester.startGesture(tester.getCenter(findScrollable)); + // In this case, the no one clearly wins, so it moves diagonally. + secondLocation = tester.getCenter(findScrollable) + const Offset(-50.0, -50.0); + await gesture.moveTo(secondLocation); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 50.0); + expect(horizontalController.position.pixels, 50.0); + // Gesture has not ended yet, move clearly indicating horizontal + thirdLocation = secondLocation + const Offset(-50, -20); + await gesture.moveTo(thirdLocation); + await tester.pumpAndSettle(); + // Only horizontal diff applied + expect(verticalController.position.pixels, 50.0); + expect(horizontalController.position.pixels, 100.0); + // Gesture has not ended yet, and horizontal axis has won for the gesture + // continue only horizontal scrolling. + fourthLocation = thirdLocation + const Offset(-30, -30); + await gesture.moveTo(fourthLocation); + await tester.pumpAndSettle(); + // Only horizontal diff applied + expect(verticalController.position.pixels, 50.0); + expect(horizontalController.position.pixels, 130.0); + await gesture.up(); + await tester.pumpAndSettle(); + }, variant: TargetPlatformVariant.all()); + + testWidgets('weightedContinuous', (WidgetTester tester) async { + // For weighted continuous, the winning axis can change if the axis + // differential for the gesture exceeds kTouchSlop. So it can lock, and + // remain locked, if the user maintains a generally straight gesture, + // otherwise it will unlock and re-evaluate. + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: simpleBuilderTest( + diagonalDrag: DiagonalDragBehavior.weightedContinuous, + verticalDetails: ScrollableDetails.vertical(controller: verticalController), + horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController), + ) + )); + await tester.pumpAndSettle(); + final Finder findScrollable = find.byElementPredicate((Element e) => e.widget is TwoDimensionalScrollable); + + // Locks to vertical, and then unlocks, resets to horizontal, then + // unlocks and scrolls diagonally. + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + final TestGesture gesture = await tester.startGesture(tester.getCenter(findScrollable)); + // In this case, the vertical axis clearly wins. + final Offset secondLocation = tester.getCenter(findScrollable) + const Offset(0.0, -50.0); + await gesture.moveTo(secondLocation); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 50.0); + expect(horizontalController.position.pixels, 0.0); + // Gesture has not ended yet, move with horizontal diff, but still + // dominant vertical + final Offset thirdLocation = secondLocation + const Offset(-15, -50); + await gesture.moveTo(thirdLocation); + await tester.pumpAndSettle(); + // Only vertical diff applied since kTouchSlop was not exceeded in the + // horizontal axis from one drag event to the next. + expect(verticalController.position.pixels, 100.0); + expect(horizontalController.position.pixels, 0.0); + // Gesture has not ended yet, move with unlocking horizontal diff + final Offset fourthLocation = thirdLocation + const Offset(-50, -15); + await gesture.moveTo(fourthLocation); + await tester.pumpAndSettle(); + // Only horizontal diff applied + expect(verticalController.position.pixels, 100.0); + expect(horizontalController.position.pixels, 50.0); + // Gesture has not ended yet, move with unlocking diff that results in + // diagonal move since neither wins. + final Offset fifthLocation = fourthLocation + const Offset(-50, -50); + await gesture.moveTo(fifthLocation); + await tester.pumpAndSettle(); + // Only horizontal diff applied + expect(verticalController.position.pixels, 150.0); + expect(horizontalController.position.pixels, 100.0); + await gesture.up(); + await tester.pumpAndSettle(); + }, variant: TargetPlatformVariant.all()); + + testWidgets('free', (WidgetTester tester) async { + // For free, anything goes. + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + await tester.pumpWidget(Directionality( + textDirection: TextDirection.ltr, + child: simpleBuilderTest( + diagonalDrag: DiagonalDragBehavior.free, + verticalDetails: ScrollableDetails.vertical(controller: verticalController), + horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController), + ) + )); + await tester.pumpAndSettle(); + final Finder findScrollable = find.byElementPredicate((Element e) => e.widget is TwoDimensionalScrollable); + + // Nothing locks. + expect(verticalController.position.pixels, 0.0); + expect(horizontalController.position.pixels, 0.0); + final TestGesture gesture = await tester.startGesture(tester.getCenter(findScrollable)); + final Offset secondLocation = tester.getCenter(findScrollable) + const Offset(0.0, -50.0); + await gesture.moveTo(secondLocation); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 50.0); + expect(horizontalController.position.pixels, 0.0); + final Offset thirdLocation = secondLocation + const Offset(-15, -50); + await gesture.moveTo(thirdLocation); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 100.0); + expect(horizontalController.position.pixels, 15.0); + final Offset fourthLocation = thirdLocation + const Offset(-50, -15); + await gesture.moveTo(fourthLocation); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 115.0); + expect(horizontalController.position.pixels, 65.0); + final Offset fifthLocation = fourthLocation + const Offset(-50, -50); + await gesture.moveTo(fifthLocation); + await tester.pumpAndSettle(); + expect(verticalController.position.pixels, 165.0); + expect(horizontalController.position.pixels, 115.0); + await gesture.up(); + await tester.pumpAndSettle(); + }); + }); + }); + + testWidgets('TwoDimensionalViewport asserts against axes mismatch', (WidgetTester tester) async { + // Horizontal mismatch + expect( + () { + SimpleBuilderTableViewport( + verticalOffset: ViewportOffset.fixed(0.0), + verticalAxisDirection: AxisDirection.left, + horizontalOffset: ViewportOffset.fixed(0.0), + horizontalAxisDirection: AxisDirection.right, + delegate: builderDelegate, + mainAxis: Axis.vertical, + ); + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('AxisDirection is not Axis.'), + ), + ), + ); + + // Vertical mismatch + expect( + () { + SimpleBuilderTableViewport( + verticalOffset: ViewportOffset.fixed(0.0), + verticalAxisDirection: AxisDirection.up, + horizontalOffset: ViewportOffset.fixed(0.0), + horizontalAxisDirection: AxisDirection.down, + delegate: builderDelegate, + mainAxis: Axis.vertical, + ); + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('AxisDirection is not Axis.'), + ), + ), + ); + + // Both + expect( + () { + SimpleBuilderTableViewport( + verticalOffset: ViewportOffset.fixed(0.0), + verticalAxisDirection: AxisDirection.left, + horizontalOffset: ViewportOffset.fixed(0.0), + horizontalAxisDirection: AxisDirection.down, + delegate: builderDelegate, + mainAxis: Axis.vertical, + ); + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('AxisDirection is not Axis.'), + ), + ), + ); + }); + + test('TwoDimensionalViewportParentData', () { + // Default vicinity is invalid + final TwoDimensionalViewportParentData parentData = TwoDimensionalViewportParentData(); + expect(parentData.vicinity, ChildVicinity.invalid); + + // toString + parentData + ..vicinity = const ChildVicinity(xIndex: 10, yIndex: 10) + ..paintOffset = const Offset(20.0, 20.0) + ..layoutOffset = const Offset(20.0, 20.0); + expect( + parentData.toString(), + 'vicinity=(xIndex: 10, yIndex: 10); layoutOffset=Offset(20.0, 20.0); ' + 'paintOffset=Offset(20.0, 20.0); not visible ', + ); + }); + + test('ChildVicinity comparable', () { + const ChildVicinity baseVicinity = ChildVicinity(xIndex: 0, yIndex: 0); + const ChildVicinity sameXVicinity = ChildVicinity(xIndex: 0, yIndex: 2); + const ChildVicinity sameYVicinity = ChildVicinity(xIndex: 3, yIndex: 0); + const ChildVicinity sameNothingVicinity = ChildVicinity(xIndex: 20, yIndex: 30); + // == + expect(baseVicinity == baseVicinity, isTrue); + expect(baseVicinity == sameXVicinity, isFalse); + expect(baseVicinity == sameYVicinity, isFalse); + expect(baseVicinity == sameNothingVicinity, isFalse); + + // compareTo + expect(baseVicinity.compareTo(baseVicinity), 0); + expect(baseVicinity.compareTo(sameXVicinity), -2); + expect(baseVicinity.compareTo(sameYVicinity), -3); + expect(baseVicinity.compareTo(sameNothingVicinity), -20); + + // toString + expect(baseVicinity.toString(), '(xIndex: 0, yIndex: 0)'); + expect(sameXVicinity.toString(), '(xIndex: 0, yIndex: 2)'); + expect(sameYVicinity.toString(), '(xIndex: 3, yIndex: 0)'); + expect(sameNothingVicinity.toString(), '(xIndex: 20, yIndex: 30)'); + }); + + group('RenderTwoDimensionalViewport', () { + testWidgets('asserts against axes mismatch', (WidgetTester tester) async { + // Horizontal mismatch + expect( + () { + RenderSimpleBuilderTableViewport( + verticalOffset: ViewportOffset.fixed(0.0), + verticalAxisDirection: AxisDirection.left, + horizontalOffset: ViewportOffset.fixed(0.0), + horizontalAxisDirection: AxisDirection.right, + delegate: builderDelegate, + mainAxis: Axis.vertical, + childManager: _NullBuildContext(), + ); + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('AxisDirection is not Axis.'), + ), + ), + ); + + // Vertical mismatch + expect( + () { + RenderSimpleBuilderTableViewport( + verticalOffset: ViewportOffset.fixed(0.0), + verticalAxisDirection: AxisDirection.up, + horizontalOffset: ViewportOffset.fixed(0.0), + horizontalAxisDirection: AxisDirection.down, + delegate: builderDelegate, + mainAxis: Axis.vertical, + childManager: _NullBuildContext(), + ); + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('AxisDirection is not Axis.'), + ), + ), + ); + + // Both + expect( + () { + RenderSimpleBuilderTableViewport( + verticalOffset: ViewportOffset.fixed(0.0), + verticalAxisDirection: AxisDirection.left, + horizontalOffset: ViewportOffset.fixed(0.0), + horizontalAxisDirection: AxisDirection.down, + delegate: builderDelegate, + mainAxis: Axis.vertical, + childManager: _NullBuildContext(), + ); + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('AxisDirection is not Axis.'), + ), + ), + ); + }); + + testWidgets('getters', (WidgetTester tester) async { + final UniqueKey childKey = UniqueKey(); + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 0, + maxYIndex: 0, + builder: (BuildContext context, ChildVicinity vicinity) { + return SizedBox.square(key: childKey, dimension: 200); + } + ); + final RenderSimpleBuilderTableViewport renderViewport = RenderSimpleBuilderTableViewport( + verticalOffset: ViewportOffset.fixed(10.0), + verticalAxisDirection: AxisDirection.down, + horizontalOffset: ViewportOffset.fixed(20.0), + horizontalAxisDirection: AxisDirection.right, + delegate: delegate, + mainAxis: Axis.vertical, + childManager: _NullBuildContext(), + ); + + expect(renderViewport.clipBehavior, Clip.hardEdge); + expect(renderViewport.cacheExtent, RenderAbstractViewport.defaultCacheExtent); + expect(renderViewport.isRepaintBoundary, isTrue); + expect(renderViewport.sizedByParent, isTrue); + // No size yet, should assert. + expect( + () { + renderViewport.viewportDimension; + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('hasSize'), + ), + ), + ); + expect(renderViewport.horizontalOffset.pixels, 20.0); + expect(renderViewport.horizontalAxisDirection, AxisDirection.right); + expect(renderViewport.verticalOffset.pixels, 10.0); + expect(renderViewport.verticalAxisDirection, AxisDirection.down); + expect(renderViewport.delegate, delegate); + expect(renderViewport.mainAxis, Axis.vertical); + + // viewportDimension when hasSize + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + await tester.pumpAndSettle(); + final RenderTwoDimensionalViewport viewport = getViewport(tester, childKey); + expect(viewport.viewportDimension, const Size(800.0, 600.0)); + }, variant: TargetPlatformVariant.all()); + + testWidgets('Children are organized according to mainAxis', (WidgetTester tester) async { + final Map<ChildVicinity, UniqueKey> childKeys = <ChildVicinity, UniqueKey>{}; + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 5, + maxYIndex: 5, + builder: (BuildContext context, ChildVicinity vicinity) { + childKeys[vicinity] = UniqueKey(); + return SizedBox.square(key: childKeys[vicinity], dimension: 200); + } + ); + TwoDimensionalViewportParentData parentDataOf(RenderBox child) { + return child.parentData! as TwoDimensionalViewportParentData; + } + // mainAxis is vertical (default) + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + await tester.pumpAndSettle(); + RenderTwoDimensionalViewport viewport = getViewport( + tester, + childKeys.values.first, + ); + expect(viewport.mainAxis, Axis.vertical); + // first child + expect( + parentDataOf(viewport.firstChild!).vicinity, + const ChildVicinity(xIndex: 0, yIndex: 0), + ); + expect( + parentDataOf(viewport.childAfter(viewport.firstChild!)!).vicinity, + const ChildVicinity(xIndex: 1, yIndex: 0), + ); + expect( + viewport.childBefore(viewport.firstChild!), + isNull, + ); + // last child + expect( + parentDataOf(viewport.lastChild!).vicinity, + const ChildVicinity(xIndex: 4, yIndex: 3), + ); + expect( + viewport.childAfter(viewport.lastChild!), + isNull, + ); + expect( + parentDataOf(viewport.childBefore(viewport.lastChild!)!).vicinity, + const ChildVicinity(xIndex: 3, yIndex: 3), + ); + + // mainAxis is horizontal + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + mainAxis: Axis.horizontal, + )); + await tester.pumpAndSettle(); + viewport = getViewport(tester, childKeys.values.first); + expect(viewport.mainAxis, Axis.horizontal); + // first child + expect( + parentDataOf(viewport.firstChild!).vicinity, + const ChildVicinity(xIndex: 0, yIndex: 0), + ); + expect( + parentDataOf(viewport.childAfter(viewport.firstChild!)!).vicinity, + const ChildVicinity(xIndex: 0, yIndex: 1), + ); + expect( + viewport.childBefore(viewport.firstChild!), + isNull, + ); + // last child + expect( + parentDataOf(viewport.lastChild!).vicinity, + const ChildVicinity(xIndex: 4, yIndex: 3), + ); + expect( + viewport.childAfter(viewport.lastChild!), + isNull, + ); + expect( + parentDataOf(viewport.childBefore(viewport.lastChild!)!).vicinity, + const ChildVicinity(xIndex: 4, yIndex: 2), + ); + }, variant: TargetPlatformVariant.all()); + + testWidgets('sets up parent data', (WidgetTester tester) async { + // Also tests computeAbsolutePaintOffsetFor & computeChildPaintExtent + // Regression test for https://github.com/flutter/flutter/issues/128723 + final Map<ChildVicinity, UniqueKey> childKeys = <ChildVicinity, UniqueKey>{}; + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 5, + maxYIndex: 5, + builder: (BuildContext context, ChildVicinity vicinity) { + childKeys[vicinity] = UniqueKey(); + return SizedBox.square(key: childKeys[vicinity], dimension: 200); + } + ); + + // parent data is TwoDimensionalViewportParentData + TwoDimensionalViewportParentData parentDataOf(RenderBox child) { + return child.parentData! as TwoDimensionalViewportParentData; + } + + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + useCacheExtent: true, + )); + await tester.pumpAndSettle(); + + RenderTwoDimensionalViewport viewport = getViewport( + tester, + childKeys.values.first, + ); + + // first child + // parentData is computed correctly - normal axes + // - layoutOffset, paintOffset, isVisible, ChildVicinity + TwoDimensionalViewportParentData childParentData = parentDataOf(viewport.firstChild!); + expect(childParentData.vicinity, const ChildVicinity(xIndex: 0, yIndex: 0)); + expect(childParentData.isVisible, isTrue); + expect(childParentData.paintOffset, Offset.zero); + expect(childParentData.layoutOffset, Offset.zero); + // The last child is in the cache extent, and should not be visible. + childParentData = parentDataOf(viewport.lastChild!); + expect(childParentData.vicinity, const ChildVicinity(xIndex: 5, yIndex: 5)); + expect(childParentData.isVisible, isFalse); + expect(childParentData.paintOffset, const Offset(1000.0, 1000.0)); + expect(childParentData.layoutOffset, const Offset(1000.0, 1000.0)); + + // parentData is computed correctly - reverse axes + // - vertical reverse + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + verticalDetails: const ScrollableDetails.vertical(reverse: true), + )); + await tester.pumpAndSettle(); + + viewport = getViewport(tester, childKeys.values.first); + + childParentData = parentDataOf(viewport.firstChild!); + expect(childParentData.vicinity, const ChildVicinity(xIndex: 0, yIndex: 0)); + expect(childParentData.isVisible, isTrue); + expect(childParentData.paintOffset, const Offset(0.0, 400.0)); + expect(childParentData.layoutOffset, Offset.zero); + // The last child is in the cache extent, and should not be visible. + childParentData = parentDataOf(viewport.lastChild!); + expect(childParentData.vicinity, const ChildVicinity(xIndex: 5, yIndex: 5)); + expect(childParentData.isVisible, isFalse); + expect(childParentData.paintOffset, const Offset(1000.0, -600.0)); + expect(childParentData.layoutOffset, const Offset(1000.0, 1000.0)); + + // - horizontal reverse + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + horizontalDetails: const ScrollableDetails.horizontal(reverse: true), + )); + await tester.pumpAndSettle(); + + viewport = getViewport(tester, childKeys.values.first); + + childParentData = parentDataOf(viewport.firstChild!); + expect(childParentData.vicinity, const ChildVicinity(xIndex: 0, yIndex: 0)); + expect(childParentData.isVisible, isTrue); + expect(childParentData.paintOffset, const Offset(600.0, 0.0)); + expect(childParentData.layoutOffset, Offset.zero); + // The last child is in the cache extent, and should not be visible. + childParentData = parentDataOf(viewport.lastChild!); + expect(childParentData.vicinity, const ChildVicinity(xIndex: 5, yIndex: 5)); + expect(childParentData.isVisible, isFalse); + expect(childParentData.paintOffset, const Offset(-400.0, 1000.0)); + expect(childParentData.layoutOffset, const Offset(1000.0, 1000.0)); + + // - both reverse + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + horizontalDetails: const ScrollableDetails.horizontal(reverse: true), + verticalDetails: const ScrollableDetails.vertical(reverse: true), + )); + await tester.pumpAndSettle(); + + viewport = getViewport(tester, childKeys.values.first); + + childParentData = parentDataOf(viewport.firstChild!); + expect(childParentData.vicinity, const ChildVicinity(xIndex: 0, yIndex: 0)); + expect(childParentData.isVisible, isTrue); + expect(childParentData.paintOffset, const Offset(600.0, 400.0)); + expect(childParentData.layoutOffset, Offset.zero); + // The last child is in the cache extent, and should not be visible. + childParentData = parentDataOf(viewport.lastChild!); + expect(childParentData.vicinity, const ChildVicinity(xIndex: 5, yIndex: 5)); + expect(childParentData.isVisible, isFalse); + expect(childParentData.paintOffset, const Offset(-400.0, -600.0)); + expect(childParentData.layoutOffset, const Offset(1000.0, 1000.0)); + + // Change the scroll positions to test partially visible. + final ScrollController verticalController = ScrollController(); + final ScrollController horizontalController = ScrollController(); + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController), + verticalDetails: ScrollableDetails.vertical(controller: verticalController), + )); + await tester.pumpAndSettle(); + verticalController.jumpTo(50.0); + horizontalController.jumpTo(50.0); + await tester.pump(); + + viewport = getViewport(tester, childKeys.values.first); + + childParentData = parentDataOf(viewport.firstChild!); + expect(childParentData.vicinity, const ChildVicinity(xIndex: 0, yIndex: 0)); + expect(childParentData.isVisible, isTrue); + expect(childParentData.paintOffset, const Offset(-50.0, -50.0)); + expect(childParentData.layoutOffset, const Offset(-50.0, -50.0)); + }, variant: TargetPlatformVariant.all()); + + testWidgets('debugDescribeChildren', (WidgetTester tester) async { + final Map<ChildVicinity, UniqueKey> childKeys = <ChildVicinity, UniqueKey>{}; + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 5, + maxYIndex: 5, + builder: (BuildContext context, ChildVicinity vicinity) { + childKeys[vicinity] = UniqueKey(); + return SizedBox.square(key: childKeys[vicinity], dimension: 200); + } + ); + + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + await tester.pumpAndSettle(); + + final RenderTwoDimensionalViewport viewport = getViewport( + tester, + childKeys.values.first, + ); + final List<DiagnosticsNode> result = viewport.debugDescribeChildren(); + expect(result.length, 20); + expect( + result.first.toString(), + equalsIgnoringHashCodes('(xIndex: 0, yIndex: 0): RenderRepaintBoundary#00000'), + ); + expect( + result.last.toString(), + equalsIgnoringHashCodes('(xIndex: 4, yIndex: 3): RenderRepaintBoundary#00000 NEEDS-PAINT'), + ); + }, variant: TargetPlatformVariant.all()); + + testWidgets('asserts that both axes are bounded', (WidgetTester tester) async { + final List<Object> exceptions = <Object>[]; + final FlutterExceptionHandler? oldHandler = FlutterError.onError; + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + // Compose unbounded - vertical axis + await tester.pumpWidget(WidgetsApp( + color: const Color(0xFFFFFFFF), + builder: (BuildContext context, Widget? child) => Column( + children: <Widget>[ + SimpleBuilderTableView(delegate: builderDelegate) + ] + ), + )); + await tester.pumpAndSettle(); + FlutterError.onError = oldHandler; + expect(exceptions.isNotEmpty, isTrue); + expect((exceptions[0] as FlutterError).message, contains('unbounded')); + + exceptions.clear(); + FlutterError.onError = (FlutterErrorDetails details) { + exceptions.add(details.exception); + }; + // Compose unbounded - horizontal axis + await tester.pumpWidget(WidgetsApp( + color: const Color(0xFFFFFFFF), + builder: (BuildContext context, Widget? child) => Row( + children: <Widget>[ + SimpleBuilderTableView(delegate: builderDelegate) + ] + ), + )); + await tester.pumpAndSettle(); + FlutterError.onError = oldHandler; + expect(exceptions.isNotEmpty, isTrue); + expect((exceptions[0] as FlutterError).message, contains('unbounded')); + }, variant: TargetPlatformVariant.all()); + + testWidgets('computeDryLayout asserts axes are bounded', (WidgetTester tester) async { + final UniqueKey childKey = UniqueKey(); + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 0, + maxYIndex: 0, + builder: (BuildContext context, ChildVicinity vicinity) { + return SizedBox.square(key: childKey, dimension: 200); + } + ); + // Call computeDryLayout with unbounded constraints + await tester.pumpWidget(simpleBuilderTest(delegate: delegate)); + final RenderTwoDimensionalViewport viewport = getViewport( + tester, + childKey, + ); + expect( + () { + viewport.computeDryLayout(const BoxConstraints()); + }, + throwsA( + isA<FlutterError>().having( + (FlutterError error) => error.message, + 'error.message', + contains('unbounded'), + ), + ), + ); + }, variant: TargetPlatformVariant.all()); + + testWidgets('correctly resizes dimensions', (WidgetTester tester) async { + final UniqueKey childKey = UniqueKey(); + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 0, + maxYIndex: 0, + builder: (BuildContext context, ChildVicinity vicinity) { + return SizedBox.square(key: childKey, dimension: 200); + } + ); + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + await tester.pumpAndSettle(); + RenderTwoDimensionalViewport viewport = getViewport( + tester, + childKey, + ); + expect(viewport.viewportDimension, const Size(800.0, 600.0)); + tester.view.physicalSize = const Size(300.0, 300.0); + tester.view.devicePixelRatio = 1; + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + await tester.pumpAndSettle(); + viewport = getViewport(tester, childKey); + expect(viewport.viewportDimension, const Size(300.0, 300.0)); + tester.view.resetPhysicalSize(); + tester.view.resetDevicePixelRatio(); + }, variant: TargetPlatformVariant.all()); + + testWidgets('Rebuilds when delegate changes', (WidgetTester tester) async { + final UniqueKey firstChildKey = UniqueKey(); + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 0, + maxYIndex: 0, + addRepaintBoundaries: false, + builder: (BuildContext context, ChildVicinity vicinity) { + return SizedBox.square(key: firstChildKey, dimension: 200); + } + ); + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + RenderTwoDimensionalViewport viewport = getViewport(tester, firstChildKey); + expect(viewport.firstChild, tester.renderObject<RenderBox>(find.byKey(firstChildKey))); + // New delegate + final UniqueKey newChildKey = UniqueKey(); + final TwoDimensionalChildBuilderDelegate newDelegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 0, + maxYIndex: 0, + addRepaintBoundaries: false, + builder: (BuildContext context, ChildVicinity vicinity) { + return Container(key: newChildKey, height: 300, width: 300, color: const Color(0xFFFFFFFF)); + } + ); + await tester.pumpWidget(simpleBuilderTest( + delegate: newDelegate, + )); + viewport = getViewport(tester, newChildKey); + expect(firstChildKey, isNot(newChildKey)); + expect(find.byKey(firstChildKey), findsNothing); + expect(find.byKey(newChildKey), findsOneWidget); + expect(viewport.firstChild, tester.renderObject<RenderBox>(find.byKey(newChildKey))); + }, variant: TargetPlatformVariant.all()); + + testWidgets('hitTestChildren', (WidgetTester tester) async { + final List<ChildVicinity> taps = <ChildVicinity>[]; + final Map<ChildVicinity, UniqueKey> childKeys = <ChildVicinity, UniqueKey>{}; + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 19, + maxYIndex: 19, + builder: (BuildContext context, ChildVicinity vicinity) { + childKeys[vicinity] = UniqueKey(); + return SizedBox.square( + dimension: 200, + child: Center( + child: FloatingActionButton( + key: childKeys[vicinity], + onPressed: () { + taps.add(vicinity); + }, + ), + ), + ); + } + ); + + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + useCacheExtent: true, // Untappable children are rendered in the cache extent + )); + await tester.pumpAndSettle(); + // Regular orientation + // Offset at center of first child + await tester.tapAt(const Offset(100.0, 100.0)); + await tester.pump(); + expect(taps.contains(const ChildVicinity(xIndex: 0, yIndex: 0)), isTrue); + // Offset by child location + await tester.tap(find.byKey(childKeys[const ChildVicinity(xIndex: 2, yIndex: 2)]!)); + await tester.pump(); + expect(taps.contains(const ChildVicinity(xIndex: 2, yIndex: 2)), isTrue); + // Offset out of bounds + await tester.tap( + find.byKey(childKeys[const ChildVicinity(xIndex: 5, yIndex: 5)]!), + warnIfMissed: false, + ); + await tester.pump(); + expect(taps.contains(const ChildVicinity(xIndex: 5, yIndex: 5)), isFalse); + + // Reversed + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + verticalDetails: const ScrollableDetails.vertical(reverse: true), + horizontalDetails: const ScrollableDetails.horizontal(reverse: true), + useCacheExtent: true, // Untappable children are rendered in the cache extent + )); + await tester.pumpAndSettle(); + // Offset at center of first child + await tester.tapAt(const Offset(700.0, 500.0)); + await tester.pump(); + expect(taps.contains(const ChildVicinity(xIndex: 0, yIndex: 0)), isTrue); + // Offset by child location + await tester.tap(find.byKey(childKeys[const ChildVicinity(xIndex: 2, yIndex: 2)]!)); + await tester.pump(); + expect(taps.contains(const ChildVicinity(xIndex: 2, yIndex: 2)), isTrue); + // Offset out of bounds + await tester.tap( + find.byKey(childKeys[const ChildVicinity(xIndex: 5, yIndex: 5)]!), + warnIfMissed: false, + ); + await tester.pump(); + expect(taps.contains(const ChildVicinity(xIndex: 5, yIndex: 5)), isFalse); + }, variant: TargetPlatformVariant.all()); + + testWidgets('getChildFor', (WidgetTester tester) async { + final Map<ChildVicinity, UniqueKey> childKeys = <ChildVicinity, UniqueKey>{}; + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 5, + maxYIndex: 5, + builder: (BuildContext context, ChildVicinity vicinity) { + childKeys[vicinity] = UniqueKey(); + return SizedBox.square(key: childKeys[vicinity], dimension: 200); + } + ); + + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + await tester.pumpAndSettle(); + + final RenderSimpleBuilderTableViewport viewport = getViewport( + tester, childKeys.values.first, + ) as RenderSimpleBuilderTableViewport; + // returns child + expect( + viewport.testGetChildFor(const ChildVicinity(xIndex: 0, yIndex: 0)), + isNotNull, + ); + expect( + viewport.testGetChildFor(const ChildVicinity(xIndex: 0, yIndex: 0)), + viewport.firstChild, + ); + + // returns null + expect( + viewport.testGetChildFor(const ChildVicinity(xIndex: 10, yIndex: 10)), + isNull, + ); + }, variant: TargetPlatformVariant.all()); + + testWidgets('asserts vicinity is valid when children are asked to build', (WidgetTester tester) async { + final Map<ChildVicinity, UniqueKey> childKeys = <ChildVicinity, UniqueKey>{}; + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 5, + maxYIndex: 5, + builder: (BuildContext context, ChildVicinity vicinity) { + childKeys[vicinity] = UniqueKey(); + return SizedBox.square(key: childKeys[vicinity], dimension: 200); + } + ); + + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + await tester.pumpAndSettle(); + + final RenderTwoDimensionalViewport viewport = getViewport( + tester, + childKeys.values.first, + ); + expect( + () { + viewport.buildOrObtainChildFor(ChildVicinity.invalid); + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('ChildVicinity.invalid'), + ), + ), + ); + }, variant: TargetPlatformVariant.all()); + + testWidgets('asserts that content dimensions have been applied', (WidgetTester tester) async { + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 5, + maxYIndex: 5, + builder: (BuildContext context, ChildVicinity vicinity) { + return const SizedBox.square(dimension: 200); + } + ); + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + // Will cause the test implementation to not set dimensions + applyDimensions: false, + )); + final FlutterError error = tester.takeException() as FlutterError; + expect(error.message, contains('was not given content dimensions')); + }, variant: TargetPlatformVariant.all()); + + testWidgets('will not rebuild a child if it can be reused', (WidgetTester tester) async { + final List<ChildVicinity> builtChildren = <ChildVicinity>[]; + final ScrollController controller = ScrollController(); + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 5, + maxYIndex: 5, + builder: (BuildContext context, ChildVicinity vicinity) { + builtChildren.add(vicinity); + return const SizedBox.square(dimension: 200); + } + ); + + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + verticalDetails: ScrollableDetails.vertical(controller: controller), + )); + expect(controller.position.pixels, 0.0); + expect(builtChildren.length, 20); + expect(builtChildren[0], const ChildVicinity(xIndex: 0, yIndex: 0)); + builtChildren.clear(); + controller.jumpTo(1.0); // Move slightly to trigger another layout + await tester.pump(); + expect(controller.position.pixels, 1.0); + expect(builtChildren.length, 5); // Next row of children was built + // Children from the first layout pass were re-used, not rebuilt. + expect( + builtChildren.contains(const ChildVicinity(xIndex: 0, yIndex: 0)), + isFalse, + ); + }, variant: TargetPlatformVariant.all()); + + testWidgets('asserts the layoutOffset has been set by the subclass', (WidgetTester tester) async { + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 5, + maxYIndex: 5, + builder: (BuildContext context, ChildVicinity vicinity) { + return const SizedBox.square(dimension: 200); + } + ); + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + // Will cause the test implementation to not set the layoutOffset of + // the parent data + setLayoutOffset: false, + )); + final AssertionError error = tester.takeException() as AssertionError; + expect(error.message, contains('was not provided a layoutOffset')); + }, variant: TargetPlatformVariant.all()); + + testWidgets('asserts the children have a size after layoutChildSequence', (WidgetTester tester) async { + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 5, + maxYIndex: 5, + builder: (BuildContext context, ChildVicinity vicinity) { + return const SizedBox.square(dimension: 200); + } + ); + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + // Will cause the test implementation to not actually layout the + // children it asked for. + forgetToLayoutChild: true, + )); + final AssertionError error = tester.takeException() as AssertionError; + expect(error.toString(), contains('child.hasSize')); + }, variant: TargetPlatformVariant.all()); + + testWidgets('does not support intrinsics', (WidgetTester tester) async { + final Map<ChildVicinity, UniqueKey> childKeys = <ChildVicinity, UniqueKey>{}; + final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate( + maxXIndex: 5, + maxYIndex: 5, + builder: (BuildContext context, ChildVicinity vicinity) { + childKeys[vicinity] = UniqueKey(); + return SizedBox.square(key: childKeys[vicinity], dimension: 200); + } + ); + + await tester.pumpWidget(simpleBuilderTest( + delegate: delegate, + )); + await tester.pumpAndSettle(); + + final RenderTwoDimensionalViewport viewport = getViewport( + tester, + childKeys.values.first, + ); + expect( + () { + viewport.computeMinIntrinsicWidth(100); + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('does not support returning intrinsic dimensions'), + ), + ), + ); + expect( + () { + viewport.computeMaxIntrinsicWidth(100); + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('does not support returning intrinsic dimensions'), + ), + ), + ); + expect( + () { + viewport.computeMinIntrinsicHeight(100); + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('does not support returning intrinsic dimensions'), + ), + ), + ); + expect( + () { + viewport.computeMaxIntrinsicHeight(100); + }, + throwsA( + isA<AssertionError>().having( + (AssertionError error) => error.toString(), + 'description', + contains('does not support returning intrinsic dimensions'), + ), + ), + ); + }, variant: TargetPlatformVariant.all()); + }); +} + +RenderTwoDimensionalViewport getViewport(WidgetTester tester, Key childKey) { + return RenderAbstractViewport.of( + tester.renderObject(find.byKey(childKey)) + ) as RenderSimpleBuilderTableViewport; +} + +class _NullBuildContext implements BuildContext, TwoDimensionalChildManager { + @override + dynamic noSuchMethod(Invocation invocation) => throw UnimplementedError(); +} + +Future<void> restoreScrollAndVerify(WidgetTester tester) async { + final Finder findScrollable = find.byElementPredicate((Element e) => e.widget is TwoDimensionalScrollable); + + tester.state<TwoDimensionalScrollableState>(findScrollable).horizontalScrollable.position.jumpTo(100); + tester.state<TwoDimensionalScrollableState>(findScrollable).verticalScrollable.position.jumpTo(100); + await tester.pump(); + await tester.restartAndRestore(); + + expect( + tester.state<TwoDimensionalScrollableState>(findScrollable).horizontalScrollable.position.pixels, + 100.0, + ); + expect( + tester.state<TwoDimensionalScrollableState>(findScrollable).verticalScrollable.position.pixels, + 100.0, + ); + + final TestRestorationData data = await tester.getRestorationData(); + tester.state<TwoDimensionalScrollableState>(findScrollable).horizontalScrollable.position.jumpTo(0); + tester.state<TwoDimensionalScrollableState>(findScrollable).verticalScrollable.position.jumpTo(0); + await tester.pump(); + await tester.restoreFrom(data); + + expect( + tester.state<TwoDimensionalScrollableState>(findScrollable).horizontalScrollable.position.pixels, + 100.0, + ); + expect( + tester.state<TwoDimensionalScrollableState>(findScrollable).verticalScrollable.position.pixels, + 100.0, + ); +} + +// Validates covariant through analysis. +mixin _SomeDelegateMixin on TwoDimensionalChildDelegate {} + +class _SomeRenderTwoDimensionalViewport extends RenderTwoDimensionalViewport { // ignore: unused_element + _SomeRenderTwoDimensionalViewport({ + required super.horizontalOffset, + required super.horizontalAxisDirection, + required super.verticalOffset, + required super.verticalAxisDirection, + required _SomeDelegateMixin super.delegate, + required super.mainAxis, + required super.childManager, + }); + + @override + _SomeDelegateMixin get delegate => super.delegate as _SomeDelegateMixin; + @override + set delegate(_SomeDelegateMixin value) { // Analysis would fail without covariant + super.delegate = value; + } + + @override + void layoutChildSequence() {} +} diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart index aa64ca4789d4b..743b4da59ea8f 100644 --- a/packages/flutter/test/widgets/widget_inspector_test.dart +++ b/packages/flutter/test/widgets/widget_inspector_test.dart @@ -240,7 +240,41 @@ extension TextFromString on String { } } +final List<Object> _weakValueTests = <Object>[1, 1.0, 'hello', true, false, Object(), <int>[3, 4], DateTime(2023)]; + void main() { + group('$InspectorReferenceData', (){ + for (final Object item in _weakValueTests) { + test('can be created for any type but $Record, $item', () async { + final InspectorReferenceData weakValue = InspectorReferenceData(item, 'id'); + expect(weakValue.value, item); + }); + } + + test('throws for $Record', () async { + expect(()=> InspectorReferenceData((1, 2), 'id'), throwsA(isA<ArgumentError>())); + }); + }); + + group('$WeakMap', (){ + for (final Object item in _weakValueTests) { + test('assigns and removes value, $item', () async { + final WeakMap<Object, Object> weakMap = WeakMap<Object, Object>(); + weakMap[item] = 1; + expect(weakMap[item], 1); + expect(weakMap.remove(item), 1); + expect(weakMap[item], null); + }); + } + + for (final Object item in _weakValueTests) { + test('returns null for absent value, $item', () async { + final WeakMap<Object, Object> weakMap = WeakMap<Object, Object>(); + expect(weakMap[item], null); + }); + } + }); + _TestWidgetInspectorService.runTests(); } @@ -261,6 +295,10 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { } }); + test ('objectToDiagnosticsNode returns null for non-diagnosticable', () { + expect(WidgetInspectorService.objectToDiagnosticsNode(Alignment.bottomCenter), isNull); + }); + testWidgets('WidgetInspector smoke test', (WidgetTester tester) async { // This is a smoke test to verify that adding the inspector doesn't crash. await tester.pumpWidget( @@ -878,7 +916,6 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { expect(chainNode['node'], isMap); final Map<String, Object?> jsonNode = chainNode['node']! as Map<String, Object?>; expect(service.toObject(jsonNode['valueId']! as String), equals(element)); - expect(service.toObject(jsonNode['objectId']! as String), isA<DiagnosticsNode>()); expect(chainNode['children'], isList); final List<Object?> jsonChildren = chainNode['children']! as List<Object?>; @@ -894,24 +931,22 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { expect(jsonChildren[j], isMap); final Map<String, Object?> childJson = jsonChildren[j]! as Map<String, Object?>; expect(service.toObject(childJson['valueId']! as String), equals(childrenElements[j])); - expect(service.toObject(childJson['objectId']! as String), isA<DiagnosticsNode>()); } } }); test('WidgetInspectorService getProperties', () { - final DiagnosticsNode diagnostic = const Text('a', textDirection: TextDirection.ltr).toDiagnosticsNode(); + const Diagnosticable diagnosticable = Text('a', textDirection: TextDirection.ltr); const String group = 'group'; service.disposeAllGroups(); - final String id = service.toId(diagnostic, group)!; + final String id = service.toId(diagnosticable, group)!; final List<Object?> propertiesJson = json.decode(service.getProperties(id, group)) as List<Object?>; - final List<DiagnosticsNode> properties = diagnostic.getProperties(); + final List<DiagnosticsNode> properties = diagnosticable.toDiagnosticsNode().getProperties(); expect(properties, isNotEmpty); expect(propertiesJson.length, equals(properties.length)); for (int i = 0; i < propertiesJson.length; ++i) { final Map<String, Object?> propertyJson = propertiesJson[i]! as Map<String, Object?>; expect(service.toObject(propertyJson['valueId'] as String?), equals(properties[i].value)); - expect(service.toObject(propertyJson['objectId']! as String), isA<DiagnosticsNode>()); } }); @@ -940,7 +975,6 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { for (int i = 0; i < propertiesJson.length; ++i) { final Map<String, Object?> propertyJson = propertiesJson[i]! as Map<String, Object?>; expect(service.toObject(propertyJson['valueId']! as String), equals(children[i].value)); - expect(service.toObject(propertyJson['objectId']! as String), isA<DiagnosticsNode>()); } }); @@ -2086,7 +2120,6 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { expect(chainNode['node'], isMap); final Map<String, Object?> jsonNode = chainNode['node']! as Map<String, Object?>; expect(service.toObject(jsonNode['valueId']! as String), equals(element)); - expect(service.toObject(jsonNode['objectId']! as String), isA<DiagnosticsNode>()); expect(chainNode['children'], isList); final List<Object?> jsonChildren = chainNode['children']! as List<Object?>; @@ -2102,26 +2135,24 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { expect(jsonChildren[j], isMap); final Map<String, Object?> childJson = jsonChildren[j]! as Map<String, Object?>; expect(service.toObject(childJson['valueId']! as String), equals(childrenElements[j])); - expect(service.toObject(childJson['objectId']! as String), isA<DiagnosticsNode>()); } } }); test('ext.flutter.inspector.getProperties', () async { - final DiagnosticsNode diagnostic = const Text('a', textDirection: TextDirection.ltr).toDiagnosticsNode(); + const Diagnosticable diagnosticable = Text('a', textDirection: TextDirection.ltr); const String group = 'group'; - final String id = service.toId(diagnostic, group)!; + final String id = service.toId(diagnosticable, group)!; final List<Object?> propertiesJson = (await service.testExtension( WidgetInspectorServiceExtensions.getProperties.name, <String, String>{'arg': id, 'objectGroup': group}, ))! as List<Object?>; - final List<DiagnosticsNode> properties = diagnostic.getProperties(); + final List<DiagnosticsNode> properties = diagnosticable.toDiagnosticsNode().getProperties(); expect(properties, isNotEmpty); expect(propertiesJson.length, equals(properties.length)); for (int i = 0; i < propertiesJson.length; ++i) { final Map<String, Object?> propertyJson = propertiesJson[i]! as Map<String, Object?>; expect(service.toObject(propertyJson['valueId'] as String?), equals(properties[i].value)); - expect(service.toObject(propertyJson['objectId']! as String), isA<DiagnosticsNode>()); } }); @@ -2152,7 +2183,6 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { for (int i = 0; i < propertiesJson.length; ++i) { final Map<String, Object?> propertyJson = propertiesJson[i]! as Map<String, Object?>; expect(service.toObject(propertyJson['valueId']! as String), equals(children[i].value)); - expect(service.toObject(propertyJson['objectId']! as String), isA<DiagnosticsNode>()); } }); @@ -2171,26 +2201,26 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { ), ), ); - final DiagnosticsNode diagnostic = find.byType(Stack).evaluate().first.toDiagnosticsNode(); - final String id = service.toId(diagnostic, group)!; + final Diagnosticable diagnosticable = find.byType(Stack).evaluate().first; + final String id = service.toId(diagnosticable, group)!; final List<Object?> childrenJson = (await service.testExtension( WidgetInspectorServiceExtensions.getChildrenDetailsSubtree.name, <String, String>{'arg': id, 'objectGroup': group}, ))! as List<Object?>; - final List<DiagnosticsNode> children = diagnostic.getChildren(); + final List<DiagnosticsNode> children = diagnosticable.toDiagnosticsNode().getChildren(); expect(children.length, equals(3)); expect(childrenJson.length, equals(children.length)); for (int i = 0; i < childrenJson.length; ++i) { final Map<String, Object?> childJson = childrenJson[i]! as Map<String, Object?>; expect(service.toObject(childJson['valueId']! as String), equals(children[i].value)); - expect(service.toObject(childJson['objectId']! as String), isA<DiagnosticsNode>()); final List<Object?> propertiesJson = childJson['properties']! as List<Object?>; - final DiagnosticsNode diagnosticsNode = service.toObject(childJson['objectId']! as String)! as DiagnosticsNode; - final List<DiagnosticsNode> expectedProperties = diagnosticsNode.getProperties(); + final Element element = service.toObject(childJson['valueId']! as String)! as Element; + final List<DiagnosticsNode> expectedProperties = element.toDiagnosticsNode().getProperties(); + final Iterable<Object?> propertyValues = expectedProperties.map((DiagnosticsNode e) => e.value.toString()); for (final Map<String, Object?> propertyJson in propertiesJson.cast<Map<String, Object?>>()) { - final Object? property = service.toObject(propertyJson['objectId']! as String); - expect(property, isA<DiagnosticsNode>()); - expect(expectedProperties, contains(property)); + final String id = propertyJson['valueId']! as String; + final String property = service.toObject(id)!.toString(); + expect(propertyValues, contains(property)); } } }); @@ -2210,31 +2240,31 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { ), ), ); - final DiagnosticsNode diagnostic = find.byType(Stack).evaluate().first.toDiagnosticsNode(); - final String id = service.toId(diagnostic, group)!; + final Diagnosticable diagnosticable = find.byType(Stack).evaluate().first; + final String id = service.toId(diagnosticable, group)!; final Map<String, Object?> subtreeJson = (await service.testExtension( WidgetInspectorServiceExtensions.getDetailsSubtree.name, <String, String>{'arg': id, 'objectGroup': group}, ))! as Map<String, Object?>; - expect(subtreeJson['objectId'], equals(id)); + expect(subtreeJson['valueId'], equals(id)); final List<Object?> childrenJson = subtreeJson['children']! as List<Object?>; - final List<DiagnosticsNode> children = diagnostic.getChildren(); + final List<DiagnosticsNode> children = diagnosticable.toDiagnosticsNode().getChildren(); expect(children.length, equals(3)); expect(childrenJson.length, equals(children.length)); for (int i = 0; i < childrenJson.length; ++i) { final Map<String, Object?> childJson = childrenJson[i]! as Map<String, Object?>; expect(service.toObject(childJson['valueId']! as String), equals(children[i].value)); - expect(service.toObject(childJson['objectId']! as String), isA<DiagnosticsNode>()); final List<Object?> propertiesJson = childJson['properties']! as List<Object?>; for (final Map<String, Object?> propertyJson in propertiesJson.cast<Map<String, Object?>>()) { expect(propertyJson, isNot(contains('children'))); } - final DiagnosticsNode diagnosticsNode = service.toObject(childJson['objectId']! as String)! as DiagnosticsNode; - final List<DiagnosticsNode> expectedProperties = diagnosticsNode.getProperties(); + final Element element = service.toObject(childJson['valueId']! as String)! as Element; + final List<DiagnosticsNode> expectedProperties = element.toDiagnosticsNode().getProperties(); + final Iterable<Object?> propertyValues = expectedProperties.map((DiagnosticsNode e) => e.value.toString()); for (final Map<String, Object?> propertyJson in propertiesJson.cast<Map<String, Object?>>()) { - final Object property = service.toObject(propertyJson['objectId']! as String)!; - expect(property, isA<DiagnosticsNode>()); - expect(expectedProperties, contains(property)); + final String id = propertyJson['valueId']! as String; + final String property = service.toObject(id)!.toString(); + expect(propertyValues, contains(property)); } } @@ -2259,13 +2289,12 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { a.children.add(b.toDiagnosticsNode()); b.related = a; - final DiagnosticsNode diagnostic = a.toDiagnosticsNode(); - final String id = service.toId(diagnostic, group)!; + final String id = service.toId(a, group)!; final Map<String, Object?> subtreeJson = (await service.testExtension( WidgetInspectorServiceExtensions.getDetailsSubtree.name, <String, String>{'arg': id, 'objectGroup': group}, ))! as Map<String, Object?>; - expect(subtreeJson['objectId'], equals(id)); + expect(subtreeJson['valueId'], equals(id)); expect(subtreeJson, contains('children')); final List<Object?> propertiesJson = subtreeJson['properties']! as List<Object?>; expect(propertiesJson.length, equals(1)); @@ -2290,7 +2319,6 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { testWidgets('ext.flutter.inspector.getRootWidgetSummaryTree', (WidgetTester tester) async { const String group = 'test-group'; - await tester.pumpWidget( const Directionality( textDirection: TextDirection.ltr, @@ -2303,6 +2331,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { ), ), ); + final Element elementA = find.text('a').evaluate().first; service.disposeAllGroups(); @@ -2318,6 +2347,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { WidgetInspectorServiceExtensions.getRootWidgetSummaryTree.name, <String, String>{'objectGroup': group}, ))! as Map<String, Object?>; + // We haven't yet properly specified which directories are summary tree // directories so we get an empty tree other than the root that is always // included. @@ -2353,7 +2383,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { List<Object?> alternateChildrenJson = (await service.testExtension( WidgetInspectorServiceExtensions.getChildrenSummaryTree.name, - <String, String>{'arg': rootJson['objectId']! as String, 'objectGroup': group}, + <String, String>{'arg': rootJson['valueId']! as String, 'objectGroup': group}, ))! as List<Object?>; expect(alternateChildrenJson.length, equals(1)); Map<String, Object?> childJson = childrenJson[0]! as Map<String, Object?>; @@ -2365,7 +2395,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { childrenJson = childJson['children']! as List<Object?>; alternateChildrenJson = (await service.testExtension( WidgetInspectorServiceExtensions.getChildrenSummaryTree.name, - <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}, + <String, String>{'arg': childJson['valueId']! as String, 'objectGroup': group}, ))! as List<Object?>; expect(alternateChildrenJson.length, equals(1)); expect(childrenJson.length, equals(1)); @@ -2379,7 +2409,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { childrenJson = childJson['children']! as List<Object?>; alternateChildrenJson = (await service.testExtension( WidgetInspectorServiceExtensions.getChildrenSummaryTree.name, - <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}, + <String, String>{'arg': childJson['valueId']! as String, 'objectGroup': group}, ))! as List<Object?>; expect(alternateChildrenJson.length, equals(3)); expect(childrenJson.length, equals(3)); @@ -2390,7 +2420,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { expect(alternateChildJson['valueId'], equals(childJson['valueId'])); alternateChildrenJson = (await service.testExtension( WidgetInspectorServiceExtensions.getChildrenSummaryTree.name, - <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}, + <String, String>{'arg': childJson['valueId']! as String, 'objectGroup': group}, ))! as List<Object?>; expect(alternateChildrenJson.length , equals(0)); // Tests are failing when this typo is fixed. @@ -2449,7 +2479,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { List<Object?> alternateChildrenJson = (await service.testExtension( WidgetInspectorServiceExtensions.getChildrenSummaryTree.name, - <String, String>{'arg': rootJson['objectId']! as String, 'objectGroup': group}, + <String, String>{'arg': rootJson['valueId']! as String, 'objectGroup': group}, ))! as List<Object?>; expect(alternateChildrenJson.length, equals(1)); Map<String, Object?> childJson = childrenJson[0]! as Map<String, Object?>; @@ -2461,7 +2491,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { childrenJson = childJson['children']! as List<Object?>; alternateChildrenJson = (await service.testExtension( WidgetInspectorServiceExtensions.getChildrenSummaryTree.name, - <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}, + <String, String>{'arg': childJson['valueId']! as String, 'objectGroup': group}, ))! as List<Object?>; expect(alternateChildrenJson.length, equals(1)); expect(childrenJson.length, equals(1)); @@ -2475,7 +2505,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { childrenJson = childJson['children']! as List<Object?>; alternateChildrenJson = (await service.testExtension( WidgetInspectorServiceExtensions.getChildrenSummaryTree.name, - <String, String>{'arg': childJson['objectId']! as String, 'objectGroup': group}, + <String, String>{'arg': childJson['valueId']! as String, 'objectGroup': group}, ))! as List<Object?>; expect(alternateChildrenJson.length, equals(3)); expect(childrenJson.length, equals(3)); @@ -4382,8 +4412,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { final Element rowElement = tester.element(find.byType(Row)); service.setSelection(rowElement, group); - final DiagnosticsNode diagnostic = rowElement.toDiagnosticsNode(); - final String id = service.toId(diagnostic, group)!; + final String id = service.toId(rowElement, group)!; final Map<String, Object?> result = (await service.testExtension( WidgetInspectorServiceExtensions.getLayoutExplorerNode.name, <String, String>{'id': id, 'groupName': group, 'subtreeDepth': '1'}, @@ -4428,8 +4457,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { final Element flexibleElement = tester.element(find.byType(Flexible).first); service.setSelection(flexibleElement, group); - final DiagnosticsNode diagnostic = flexibleElement.toDiagnosticsNode(); - final String id = service.toId(diagnostic, group)!; + final String id = service.toId(flexibleElement, group)!; final Map<String, Object?> result = (await service.testExtension( WidgetInspectorServiceExtensions.getLayoutExplorerNode.name, <String, String>{'id': id, 'groupName': group, 'subtreeDepth': '1'}, @@ -4470,16 +4498,14 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { await pumpWidgetForLayoutExplorer(tester); final Element element = tester.element(find.byType(Directionality).first); - Element? root; + late Element root; element.visitAncestorElements((Element ancestor) { root = ancestor; return true; }); - expect(root, isNotNull); service.setSelection(root, group); - final DiagnosticsNode diagnostic = root!.toDiagnosticsNode(); - final String id = service.toId(diagnostic, group)!; + final String id = service.toId(root, group)!; final Map<String, Object?> result = (await service.testExtension( WidgetInspectorServiceExtensions.getLayoutExplorerNode.name, <String, String>{'id': id, 'groupName': group, 'subtreeDepth': '1'}, @@ -4510,8 +4536,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { final Element childElement = tester.element(find.byType(Flexible).first); service.setSelection(childElement, group); - final DiagnosticsNode diagnostic = childElement.toDiagnosticsNode(); - final String id = service.toId(diagnostic, group)!; + final String id = service.toId(childElement, group)!; Map<String, Object?> result = (await service.testExtension( WidgetInspectorServiceExtensions.getLayoutExplorerNode.name, <String, String>{'id': id, 'groupName': group, 'subtreeDepth': '1'}, @@ -4541,8 +4566,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { final Element childElement = tester.element(find.byType(Flexible).first); service.setSelection(childElement, group); - final DiagnosticsNode diagnostic = childElement.toDiagnosticsNode(); - final String id = service.toId(diagnostic, group)!; + final String id = service.toId(childElement, group)!; Map<String, Object?> result = (await service.testExtension( WidgetInspectorServiceExtensions.getLayoutExplorerNode.name, <String, String>{'id': id, 'groupName': group, 'subtreeDepth': '1'}, @@ -4572,8 +4596,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { final Element rowElement = tester.element(find.byType(Row).first); service.setSelection(rowElement, group); - final DiagnosticsNode diagnostic = rowElement.toDiagnosticsNode(); - final String id = service.toId(diagnostic, group)!; + final String id = service.toId(rowElement, group)!; Map<String, Object?> result = (await service.testExtension( WidgetInspectorServiceExtensions.getLayoutExplorerNode.name, <String, String>{'id': id, 'groupName': group, 'subtreeDepth': '1'}, @@ -4985,7 +5008,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { final List<Object?> childrenOfMaterialApp = (childrenOfRoot.first! as Map<String, Object?>)['children']! as List<Object?>; final Map<String, Object?> scaffold = childrenOfMaterialApp.first! as Map<String, Object?>; expect(scaffold['description'], 'Scaffold'); - final String objectId = scaffold['objectId']! as String; + final String objectId = scaffold['valueId']! as String; final String details = service.getDetailsSubtree(objectId, 'foo2'); // ignore: avoid_dynamic_calls final List<Object?> detailedChildren = json.decode(details)['children'] as List<Object?>; diff --git a/packages/flutter/test_fixes/services/services.dart b/packages/flutter/test_fixes/services/services.dart index 6e171fc8f57ef..bbb5250f96f5f 100644 --- a/packages/flutter/test_fixes/services/services.dart +++ b/packages/flutter/test_fixes/services/services.dart @@ -5,6 +5,10 @@ import 'package:flutter/services.dart'; void main() { + // Changes made in https://github.com/flutter/flutter/pull/122446 + final clipboardData1 = ClipboardData(); + final clipboardData2 = ClipboardData(text: null); + // Changes made in https://github.com/flutter/flutter/pull/60320 final SurfaceAndroidViewController surfaceController = SurfaceAndroidViewController( viewId: 10, diff --git a/packages/flutter/test_fixes/services/services.dart.expect b/packages/flutter/test_fixes/services/services.dart.expect index 7a69dc54c7f54..469d50056c814 100644 --- a/packages/flutter/test_fixes/services/services.dart.expect +++ b/packages/flutter/test_fixes/services/services.dart.expect @@ -5,6 +5,10 @@ import 'package:flutter/services.dart'; void main() { + // Changes made in https://github.com/flutter/flutter/pull/122446 + final clipboardData1 = ClipboardData(text: ''); + final clipboardData2 = ClipboardData(text: ''); + // Changes made in https://github.com/flutter/flutter/pull/60320 final SurfaceAndroidViewController surfaceController = SurfaceAndroidViewController( viewId: 10, diff --git a/packages/flutter/test_private/bin/test_private.dart b/packages/flutter/test_private/bin/test_private.dart index 25b89f3c38047..372ddb6a3904e 100644 --- a/packages/flutter/test_private/bin/test_private.dart +++ b/packages/flutter/test_private/bin/test_private.dart @@ -195,7 +195,13 @@ class TestCase { // Use Flutter's analysis_options.yaml file from packages/flutter. File(path.join(tmpdir.absolute.path, 'analysis_options.yaml')) - .writeAsStringSync('include: ${path.toUri(path.join(flutterRoot.path, 'packages', 'flutter', 'analysis_options.yaml'))}'); + .writeAsStringSync( + 'include: ${path.toUri(path.join(flutterRoot.path, 'packages', 'flutter', 'analysis_options.yaml'))}\n' + 'linter:\n' + ' rules:\n' + // The code does wonky things with the part-of directive that cause false positives. + ' unreachable_from_main: false' + ); return true; } diff --git a/packages/flutter/test_private/pubspec.yaml b/packages/flutter/test_private/pubspec.yaml index c841572ef0a1b..84443b3ce2526 100644 --- a/packages/flutter/test_private/pubspec.yaml +++ b/packages/flutter/test_private/pubspec.yaml @@ -11,10 +11,10 @@ dependencies: process: 4.2.4 process_runner: 4.1.2 - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" platform: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: d68f +# PUBSPEC CHECKSUM: bc90 diff --git a/packages/flutter/test_private/test/pubspec.yaml b/packages/flutter/test_private/test/pubspec.yaml index e6aba9c0474db..c2e5ee2e909fa 100644 --- a/packages/flutter/test_private/test/pubspec.yaml +++ b/packages/flutter/test_private/test/pubspec.yaml @@ -19,8 +19,7 @@ dependencies: async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -28,7 +27,8 @@ dependencies: stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_goldens: @@ -39,4 +39,4 @@ dev_dependencies: platform: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" process: 4.2.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 2319 +# PUBSPEC CHECKSUM: 6c3d diff --git a/packages/flutter_driver/lib/src/common/handler_factory.dart b/packages/flutter_driver/lib/src/common/handler_factory.dart index def0c660a9287..05e70aee8d2fe 100644 --- a/packages/flutter_driver/lib/src/common/handler_factory.dart +++ b/packages/flutter_driver/lib/src/common/handler_factory.dart @@ -320,7 +320,7 @@ mixin CommandHandlerFactory { SemanticsNode? node; while (renderObject != null && node == null) { node = renderObject.debugSemantics; - renderObject = renderObject.parent as RenderObject?; + renderObject = renderObject.parent; } if (node == null) { throw StateError('No semantics data found'); diff --git a/packages/flutter_driver/lib/src/driver/vmservice_driver.dart b/packages/flutter_driver/lib/src/driver/vmservice_driver.dart index 0b5506bffa829..99fdc6fcb1cd9 100644 --- a/packages/flutter_driver/lib/src/driver/vmservice_driver.dart +++ b/packages/flutter_driver/lib/src/driver/vmservice_driver.dart @@ -433,7 +433,7 @@ class VMServiceFlutterDriver extends FlutterDriver { } while (currentStart < endTime!); return Timeline.fromJson(<String, Object>{ 'traceEvents': <Object?> [ - for (Map<String, Object?>? chunk in chunks) + for (final Map<String, Object?>? chunk in chunks) ...chunk!['traceEvents']! as List<Object?>, ], }); diff --git a/packages/flutter_driver/pubspec.yaml b/packages/flutter_driver/pubspec.yaml index fea1925da052e..d817a82060eb7 100644 --- a/packages/flutter_driver/pubspec.yaml +++ b/packages/flutter_driver/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: sdk: flutter path: 1.8.3 meta: 1.9.1 - vm_service: 11.6.0 + vm_service: 11.7.1 webdriver: 3.0.2 async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -23,8 +23,7 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" platform: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" process: 4.2.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -34,25 +33,27 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: fake_async: 1.3.1 - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" convert: 3.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -64,11 +65,11 @@ dev_dependencies: shelf_web_socket: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webkit_inspection_protocol: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" yaml: 3.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 6737 +# PUBSPEC CHECKSUM: a395 diff --git a/packages/flutter_goldens/pubspec.yaml b/packages/flutter_goldens/pubspec.yaml index ada762b77c0b5..07e7fd2d56a37 100644 --- a/packages/flutter_goldens/pubspec.yaml +++ b/packages/flutter_goldens/pubspec.yaml @@ -23,8 +23,7 @@ dependencies: collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -32,8 +31,9 @@ dependencies: stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 537a +# PUBSPEC CHECKSUM: 6a9e diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es.arb index d245e1d6e6aa6..fbbcdf4e484c0 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es.arb @@ -18,7 +18,7 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "tabSemanticsLabel": "Pestaña $tabIndex de $tabCount", "modalBarrierDismissLabel": "Cerrar", "searchTextFieldPlaceholderLabel": "Buscar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_419.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_419.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_419.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_419.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_AR.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_AR.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_AR.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_AR.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_BO.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_BO.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_BO.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_BO.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_CL.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_CL.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_CL.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_CL.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_CO.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_CO.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_CO.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_CO.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_CR.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_CR.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_CR.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_CR.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_DO.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_DO.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_DO.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_DO.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_EC.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_EC.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_EC.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_EC.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_GT.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_GT.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_GT.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_GT.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_HN.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_HN.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_HN.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_HN.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_MX.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_MX.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_MX.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_MX.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_NI.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_NI.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_NI.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_NI.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PA.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PA.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PA.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PA.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PE.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PE.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PE.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PE.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PR.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PR.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PR.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PR.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PY.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PY.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PY.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PY.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_SV.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_SV.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_SV.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_SV.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_US.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_US.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_US.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_US.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_UY.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_UY.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_UY.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_UY.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_VE.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_VE.arb index eca276c0b0910..272e2aea1a2c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_VE.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_VE.arb @@ -20,6 +20,6 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Pegar", - "selectAllButtonLabel": "Seleccionar todos", + "selectAllButtonLabel": "Seleccionar todo", "modalBarrierDismissLabel": "Descartar" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_pl.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_pl.arb index 0e0a483b20611..19aa5b8299845 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_pl.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_pl.arb @@ -28,9 +28,9 @@ "cutButtonLabel": "Wytnij", "copyButtonLabel": "Kopiuj", "pasteButtonLabel": "Wklej", - "selectAllButtonLabel": "Wybierz wszystkie", + "selectAllButtonLabel": "Zaznacz wszystko", "tabSemanticsLabel": "Karta $tabIndex z $tabCount", "modalBarrierDismissLabel": "Zamknij", "searchTextFieldPlaceholderLabel": "Szukaj", - "noSpellCheckReplacementsLabel": "No Replacements Found" + "noSpellCheckReplacementsLabel": "Nie znaleziono zastąpień" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_pt.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_pt.arb index 0c0741bd2bb36..23bf41fc61338 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_pt.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_pt.arb @@ -18,7 +18,7 @@ "cutButtonLabel": "Cortar", "copyButtonLabel": "Copiar", "pasteButtonLabel": "Colar", - "selectAllButtonLabel": "Selecionar Tudo", + "selectAllButtonLabel": "Selecionar tudo", "tabSemanticsLabel": "Guia $tabIndex de $tabCount", "modalBarrierDismissLabel": "Dispensar", "searchTextFieldPlaceholderLabel": "Pesquisar", diff --git a/packages/flutter_localizations/lib/src/l10n/generated_cupertino_localizations.dart b/packages/flutter_localizations/lib/src/l10n/generated_cupertino_localizations.dart index 513764d9ac0d4..90a899f0a3d57 100644 --- a/packages/flutter_localizations/lib/src/l10n/generated_cupertino_localizations.dart +++ b/packages/flutter_localizations/lib/src/l10n/generated_cupertino_localizations.dart @@ -2720,7 +2720,7 @@ class CupertinoLocalizationEs extends GlobalCupertinoLocalizations { String get searchTextFieldPlaceholderLabel => 'Buscar'; @override - String get selectAllButtonLabel => 'Seleccionar todos'; + String get selectAllButtonLabel => 'Seleccionar todo'; @override String get tabSemanticsLabelRaw => r'Pestaña $tabIndex de $tabCount'; @@ -9410,7 +9410,7 @@ class CupertinoLocalizationPl extends GlobalCupertinoLocalizations { String get modalBarrierDismissLabel => 'Zamknij'; @override - String get noSpellCheckReplacementsLabel => 'No Replacements Found'; + String get noSpellCheckReplacementsLabel => 'Nie znaleziono zastąpień'; @override String get pasteButtonLabel => 'Wklej'; @@ -9422,7 +9422,7 @@ class CupertinoLocalizationPl extends GlobalCupertinoLocalizations { String get searchTextFieldPlaceholderLabel => 'Szukaj'; @override - String get selectAllButtonLabel => 'Wybierz wszystkie'; + String get selectAllButtonLabel => 'Zaznacz wszystko'; @override String get tabSemanticsLabelRaw => r'Karta $tabIndex z $tabCount'; @@ -9572,7 +9572,7 @@ class CupertinoLocalizationPt extends GlobalCupertinoLocalizations { String get searchTextFieldPlaceholderLabel => 'Pesquisar'; @override - String get selectAllButtonLabel => 'Selecionar Tudo'; + String get selectAllButtonLabel => 'Selecionar tudo'; @override String get tabSemanticsLabelRaw => r'Guia $tabIndex de $tabCount'; @@ -9667,9 +9667,6 @@ class CupertinoLocalizationPtPt extends CupertinoLocalizationPt { @override String get timerPickerSecondLabelOther => 'seg'; - @override - String get selectAllButtonLabel => 'Selecionar tudo'; - @override String get modalBarrierDismissLabel => 'Ignorar'; } diff --git a/packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart b/packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart index d575829598394..d936427c1653a 100644 --- a/packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart +++ b/packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart @@ -416,6 +416,9 @@ class MaterialLocalizationAf extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Stoor'; + @override + String get scanTextButtonLabel => 'Skena umbhalo'; + @override String get scrimLabel => 'Skerm'; @@ -891,6 +894,9 @@ class MaterialLocalizationAm extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'አስቀምጥ'; + @override + String get scanTextButtonLabel => 'ጽሑፍ ይቃኙ'; + @override String get scrimLabel => 'ገዳቢ'; @@ -1366,6 +1372,9 @@ class MaterialLocalizationAr extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'الحفظ'; + @override + String get scanTextButtonLabel => 'مسح النص'; + @override String get scrimLabel => 'تمويه'; @@ -1841,6 +1850,9 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'ছেভ কৰক'; + @override + String get scanTextButtonLabel => 'স্কেন টেক্সট'; + @override String get scrimLabel => 'স্ক্ৰিম'; @@ -2316,6 +2328,9 @@ class MaterialLocalizationAz extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Yadda saxlayın'; + @override + String get scanTextButtonLabel => 'Mətni skan edin'; + @override String get scrimLabel => 'Kətan'; @@ -2791,6 +2806,9 @@ class MaterialLocalizationBe extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Захаваць'; + @override + String get scanTextButtonLabel => 'Сканаваць тэкст'; + @override String get scrimLabel => 'Палатно'; @@ -3266,6 +3284,9 @@ class MaterialLocalizationBg extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Запазване'; + @override + String get scanTextButtonLabel => 'Сканиране на текст'; + @override String get scrimLabel => 'Скрим'; @@ -3741,6 +3762,9 @@ class MaterialLocalizationBn extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'সেভ করুন'; + @override + String get scanTextButtonLabel => 'পাঠ্য স্ক্যান করুন'; + @override String get scrimLabel => 'স্ক্রিম'; @@ -4216,6 +4240,9 @@ class MaterialLocalizationBs extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Sačuvaj'; + @override + String get scanTextButtonLabel => 'Skeniraj tekst'; + @override String get scrimLabel => 'Rubno'; @@ -4691,6 +4718,9 @@ class MaterialLocalizationCa extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Desa'; + @override + String get scanTextButtonLabel => 'Escaneja el text'; + @override String get scrimLabel => 'Fons atenuat'; @@ -5166,6 +5196,9 @@ class MaterialLocalizationCs extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Uložit'; + @override + String get scanTextButtonLabel => 'Naskenujte text'; + @override String get scrimLabel => 'Scrim'; @@ -5641,6 +5674,9 @@ class MaterialLocalizationCy extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Cadw'; + @override + String get scanTextButtonLabel => 'Scan text'; + @override String get scrimLabel => 'Scrim'; @@ -6116,6 +6152,9 @@ class MaterialLocalizationDa extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Gem'; + @override + String get scanTextButtonLabel => 'Scan tekst'; + @override String get scrimLabel => 'Dæmpeskærm'; @@ -6591,6 +6630,9 @@ class MaterialLocalizationDe extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Speichern'; + @override + String get scanTextButtonLabel => 'Text scannen'; + @override String get scrimLabel => 'Gitter'; @@ -7130,6 +7172,9 @@ class MaterialLocalizationEl extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Αποθήκευση'; + @override + String get scanTextButtonLabel => 'Σάρωση κειμένου'; + @override String get scrimLabel => 'Επικάλυψη'; @@ -7605,6 +7650,9 @@ class MaterialLocalizationEn extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Save'; + @override + String get scanTextButtonLabel => 'Scan text'; + @override String get scrimLabel => 'Scrim'; @@ -8814,6 +8862,9 @@ class MaterialLocalizationEs extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Guardar'; + @override + String get scanTextButtonLabel => 'Escanear texto'; + @override String get scrimLabel => 'Sombreado'; @@ -12672,6 +12723,9 @@ class MaterialLocalizationEt extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Salvesta'; + @override + String get scanTextButtonLabel => 'Skanni teksti'; + @override String get scrimLabel => 'Sirm'; @@ -13147,6 +13201,9 @@ class MaterialLocalizationEu extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Gorde'; + @override + String get scanTextButtonLabel => 'Eskaneatu testua'; + @override String get scrimLabel => 'Barrera'; @@ -13622,6 +13679,9 @@ class MaterialLocalizationFa extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'ذخیره'; + @override + String get scanTextButtonLabel => 'اسکن متن'; + @override String get scrimLabel => 'رویه'; @@ -14097,6 +14157,9 @@ class MaterialLocalizationFi extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Tallenna'; + @override + String get scanTextButtonLabel => 'Skannaa tekstiä'; + @override String get scrimLabel => 'Sermi'; @@ -14572,6 +14635,9 @@ class MaterialLocalizationFil extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'I-save'; + @override + String get scanTextButtonLabel => 'I-scan ang text'; + @override String get scrimLabel => 'Scrim'; @@ -15047,6 +15113,9 @@ class MaterialLocalizationFr extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Enregistrer'; + @override + String get scanTextButtonLabel => 'Numériser du texte'; + @override String get scrimLabel => 'Fond'; @@ -15664,6 +15733,9 @@ class MaterialLocalizationGl extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Gardar'; + @override + String get scanTextButtonLabel => 'Escanear texto'; + @override String get scrimLabel => 'Sombreado'; @@ -16139,6 +16211,9 @@ class MaterialLocalizationGsw extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Speichern'; + @override + String get scanTextButtonLabel => 'Text scannen'; + @override String get scrimLabel => 'Gitter'; @@ -16614,6 +16689,9 @@ class MaterialLocalizationGu extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'સાચવો'; + @override + String get scanTextButtonLabel => 'ટેક્સ્ટ સ્કેન કરો'; + @override String get scrimLabel => 'સ્ક્રિમ'; @@ -17089,6 +17167,9 @@ class MaterialLocalizationHe extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'שמירה'; + @override + String get scanTextButtonLabel => 'סרוק טקסט'; + @override String get scrimLabel => 'מיסוך'; @@ -17564,6 +17645,9 @@ class MaterialLocalizationHi extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'सेव करें'; + @override + String get scanTextButtonLabel => 'पाठ स्कैन करें'; + @override String get scrimLabel => 'स्क्रिम'; @@ -18039,6 +18123,9 @@ class MaterialLocalizationHr extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Spremi'; + @override + String get scanTextButtonLabel => 'Skeniraj tekst'; + @override String get scrimLabel => 'Rubno'; @@ -18514,6 +18601,9 @@ class MaterialLocalizationHu extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Mentés'; + @override + String get scanTextButtonLabel => 'Szöveg beolvasása'; + @override String get scrimLabel => 'Borítás'; @@ -18989,6 +19079,9 @@ class MaterialLocalizationHy extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Պահել'; + @override + String get scanTextButtonLabel => 'Սկանավորեք տեքստը'; + @override String get scrimLabel => 'Դիմակ'; @@ -19464,6 +19557,9 @@ class MaterialLocalizationId extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Simpan'; + @override + String get scanTextButtonLabel => 'Pindai teks'; + @override String get scrimLabel => 'Scrim'; @@ -19939,6 +20035,9 @@ class MaterialLocalizationIs extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Vista'; + @override + String get scanTextButtonLabel => 'Skannaðu texta'; + @override String get scrimLabel => 'Möskvi'; @@ -20414,6 +20513,9 @@ class MaterialLocalizationIt extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Salva'; + @override + String get scanTextButtonLabel => 'Scansiona il testo'; + @override String get scrimLabel => 'Rete'; @@ -20889,6 +20991,9 @@ class MaterialLocalizationJa extends GlobalMaterialLocalizations { @override String get saveButtonLabel => '保存'; + @override + String get scanTextButtonLabel => 'テキストをスキャン'; + @override String get scrimLabel => 'スクリム'; @@ -21364,6 +21469,9 @@ class MaterialLocalizationKa extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'შენახვა'; + @override + String get scanTextButtonLabel => 'ტექსტის სკანირება'; + @override String get scrimLabel => 'სკრიმი'; @@ -21839,6 +21947,9 @@ class MaterialLocalizationKk extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Сақтау'; + @override + String get scanTextButtonLabel => 'Мәтінді сканерлеу'; + @override String get scrimLabel => 'Кенеп'; @@ -22314,6 +22425,9 @@ class MaterialLocalizationKm extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'រក្សាទុក'; + @override + String get scanTextButtonLabel => 'ស្កេនអត្ថបទ'; + @override String get scrimLabel => 'ផ្ទាំងស្រអាប់'; @@ -22789,6 +22903,9 @@ class MaterialLocalizationKn extends GlobalMaterialLocalizations { @override String get saveButtonLabel => '\u{c89}\u{cb3}\u{cbf}\u{cb8}\u{cbf}'; + @override + String get scanTextButtonLabel => '\u{caa}\u{ca0}\u{ccd}\u{caf}\u{cb5}\u{ca8}\u{ccd}\u{ca8}\u{cc1}\u{20}\u{cb8}\u{ccd}\u{c95}\u{ccd}\u{caf}\u{cbe}\u{ca8}\u{ccd}\u{20}\u{cae}\u{cbe}\u{ca1}\u{cbf}'; + @override String get scrimLabel => '\u{cb8}\u{ccd}\u{c95}\u{ccd}\u{cb0}\u{cbf}\u{cae}\u{ccd}'; @@ -23264,6 +23381,9 @@ class MaterialLocalizationKo extends GlobalMaterialLocalizations { @override String get saveButtonLabel => '저장'; + @override + String get scanTextButtonLabel => '스캔 텍스트'; + @override String get scrimLabel => '스크림'; @@ -23739,6 +23859,9 @@ class MaterialLocalizationKy extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Сактоо'; + @override + String get scanTextButtonLabel => 'Текстти скандоо'; + @override String get scrimLabel => 'Кенеп'; @@ -24214,6 +24337,9 @@ class MaterialLocalizationLo extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'ບັນທຶກ'; + @override + String get scanTextButtonLabel => 'ສະແກນຂໍ້ຄວາມ'; + @override String get scrimLabel => 'Scrim'; @@ -24689,6 +24815,9 @@ class MaterialLocalizationLt extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Išsaugoti'; + @override + String get scanTextButtonLabel => 'Nuskaityti tekstą'; + @override String get scrimLabel => 'Užsklanda'; @@ -25164,6 +25293,9 @@ class MaterialLocalizationLv extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Saglabāt'; + @override + String get scanTextButtonLabel => 'Skenēt tekstu'; + @override String get scrimLabel => 'Pārklājums'; @@ -25639,6 +25771,9 @@ class MaterialLocalizationMk extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Зачувај'; + @override + String get scanTextButtonLabel => 'Скенирајте текст'; + @override String get scrimLabel => 'Скрим'; @@ -26114,6 +26249,9 @@ class MaterialLocalizationMl extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'സംരക്ഷിക്കുക'; + @override + String get scanTextButtonLabel => 'ടെക്സ്റ്റ് സ്കാൻ ചെയ്യുക'; + @override String get scrimLabel => 'സ്ക്രിം'; @@ -26589,6 +26727,9 @@ class MaterialLocalizationMn extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Хадгалах'; + @override + String get scanTextButtonLabel => 'Текст сканнердах'; + @override String get scrimLabel => 'Скрим'; @@ -27064,6 +27205,9 @@ class MaterialLocalizationMr extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'सेव्ह करा'; + @override + String get scanTextButtonLabel => 'मजकूर स्कॅन करा'; + @override String get scrimLabel => 'स्क्रिम'; @@ -27539,6 +27683,9 @@ class MaterialLocalizationMs extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Simpan'; + @override + String get scanTextButtonLabel => 'Pindai teks'; + @override String get scrimLabel => 'Scrim'; @@ -28014,6 +28161,9 @@ class MaterialLocalizationMy extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'သိမ်းရန်'; + @override + String get scanTextButtonLabel => 'စာသားကို စကင်ဖတ်ပါ။'; + @override String get scrimLabel => 'Scrim'; @@ -28489,6 +28639,9 @@ class MaterialLocalizationNb extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Lagre'; + @override + String get scanTextButtonLabel => 'Scan tekst'; + @override String get scrimLabel => 'Vev'; @@ -28964,6 +29117,9 @@ class MaterialLocalizationNe extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'सेभ गर्नुहोस्'; + @override + String get scanTextButtonLabel => 'पाठ स्क्यान गर्नुहोस्'; + @override String get scrimLabel => 'स्क्रिम'; @@ -29439,6 +29595,9 @@ class MaterialLocalizationNl extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Opslaan'; + @override + String get scanTextButtonLabel => 'Tekst scannen'; + @override String get scrimLabel => 'Scrim'; @@ -29914,6 +30073,9 @@ class MaterialLocalizationNo extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Lagre'; + @override + String get scanTextButtonLabel => 'Skann tekst'; + @override String get scrimLabel => 'Vev'; @@ -30389,6 +30551,9 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'ସେଭ କରନ୍ତୁ'; + @override + String get scanTextButtonLabel => 'ପାଠ୍ୟ ସ୍କାନ୍ କରନ୍ତୁ'; + @override String get scrimLabel => 'ସ୍କ୍ରିମ'; @@ -30864,6 +31029,9 @@ class MaterialLocalizationPa extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'ਰੱਖਿਅਤ ਕਰੋ'; + @override + String get scanTextButtonLabel => 'ਟੈਕਸਟ ਸਕੈਨ ਕਰੋ'; + @override String get scrimLabel => 'ਸਕ੍ਰਿਮ'; @@ -31339,6 +31507,9 @@ class MaterialLocalizationPl extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Zapisz'; + @override + String get scanTextButtonLabel => 'Zeskanuj tekst'; + @override String get scrimLabel => 'Siatka'; @@ -31814,6 +31985,9 @@ class MaterialLocalizationPs extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'SAVE'; + @override + String get scanTextButtonLabel => 'متن سکین کړئ'; + @override String get scrimLabel => 'Scrim'; @@ -32289,6 +32463,9 @@ class MaterialLocalizationPt extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Salvar'; + @override + String get scanTextButtonLabel => 'Digitalizar texto'; + @override String get scrimLabel => 'Scrim'; @@ -32915,6 +33092,9 @@ class MaterialLocalizationRo extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Salvați'; + @override + String get scanTextButtonLabel => 'Scanați textul'; + @override String get scrimLabel => 'Material'; @@ -33390,6 +33570,9 @@ class MaterialLocalizationRu extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Сохранить'; + @override + String get scanTextButtonLabel => 'Сканировать текст'; + @override String get scrimLabel => 'Маска'; @@ -33865,6 +34048,9 @@ class MaterialLocalizationSi extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'සුරකින්න'; + @override + String get scanTextButtonLabel => 'පෙළ පරිලෝකනය කරන්න'; + @override String get scrimLabel => 'ස්ක්‍රිම්'; @@ -34340,6 +34526,9 @@ class MaterialLocalizationSk extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Uložiť'; + @override + String get scanTextButtonLabel => 'Naskenujte text'; + @override String get scrimLabel => 'Scrim'; @@ -34815,6 +35004,9 @@ class MaterialLocalizationSl extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Shrani'; + @override + String get scanTextButtonLabel => 'Skeniraj besedilo'; + @override String get scrimLabel => 'Scrim'; @@ -35290,6 +35482,9 @@ class MaterialLocalizationSq extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Ruaj'; + @override + String get scanTextButtonLabel => 'Skanoni tekstin'; + @override String get scrimLabel => 'Kanavacë'; @@ -35765,6 +35960,9 @@ class MaterialLocalizationSr extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Сачувај'; + @override + String get scanTextButtonLabel => 'Скенирајте текст'; + @override String get scrimLabel => 'Скрим'; @@ -36554,6 +36752,9 @@ class MaterialLocalizationSv extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Spara'; + @override + String get scanTextButtonLabel => 'Skanna text'; + @override String get scrimLabel => 'Scrim'; @@ -37029,6 +37230,9 @@ class MaterialLocalizationSw extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Hifadhi'; + @override + String get scanTextButtonLabel => 'Changanua maandishi'; + @override String get scrimLabel => 'Scrim'; @@ -37504,6 +37708,9 @@ class MaterialLocalizationTa extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'சேமி'; + @override + String get scanTextButtonLabel => 'உரையை ஸ்கேன் செய்யவும்'; + @override String get scrimLabel => 'ஸ்க்ரிம்'; @@ -37979,6 +38186,9 @@ class MaterialLocalizationTe extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'సేవ్ చేయండి'; + @override + String get scanTextButtonLabel => 'వచనాన్ని స్కాన్ చేయండి'; + @override String get scrimLabel => 'స్క్రిమ్'; @@ -38454,6 +38664,9 @@ class MaterialLocalizationTh extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'บันทึก'; + @override + String get scanTextButtonLabel => 'สแกนข้อความ'; + @override String get scrimLabel => 'Scrim'; @@ -38929,6 +39142,9 @@ class MaterialLocalizationTl extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'I-save'; + @override + String get scanTextButtonLabel => 'I-scan ang text'; + @override String get scrimLabel => 'Scrim'; @@ -39404,6 +39620,9 @@ class MaterialLocalizationTr extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Kaydet'; + @override + String get scanTextButtonLabel => 'Metni tara'; + @override String get scrimLabel => 'opaklık katmanı'; @@ -39879,6 +40098,9 @@ class MaterialLocalizationUk extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Зберегти'; + @override + String get scanTextButtonLabel => 'Сканувати текст'; + @override String get scrimLabel => 'Маскувальний фон'; @@ -40354,6 +40576,9 @@ class MaterialLocalizationUr extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'محفوظ کریں'; + @override + String get scanTextButtonLabel => 'متن کو اسکین کریں'; + @override String get scrimLabel => 'اسکریم'; @@ -40829,6 +41054,9 @@ class MaterialLocalizationUz extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Saqlash'; + @override + String get scanTextButtonLabel => 'Matnni skanerlash'; + @override String get scrimLabel => 'Kanop'; @@ -41304,6 +41532,9 @@ class MaterialLocalizationVi extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Lưu'; + @override + String get scanTextButtonLabel => 'Quét văn bản'; + @override String get scrimLabel => 'Scrim'; @@ -41779,6 +42010,9 @@ class MaterialLocalizationZh extends GlobalMaterialLocalizations { @override String get saveButtonLabel => '保存'; + @override + String get scanTextButtonLabel => '扫描文本'; + @override String get scrimLabel => '纱罩'; @@ -42747,6 +42981,9 @@ class MaterialLocalizationZu extends GlobalMaterialLocalizations { @override String get saveButtonLabel => 'Londoloza'; + @override + String get scanTextButtonLabel => 'Skena umbhalo'; + @override String get scrimLabel => 'I-Scrim'; diff --git a/packages/flutter_localizations/lib/src/l10n/material_af.arb b/packages/flutter_localizations/lib/src/l10n/material_af.arb index 9305eaff8ef81..c593ac8c8228e 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_af.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_af.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Gaan voort", "copyButtonLabel": "Kopieer", "cutButtonLabel": "Knip", + "scanTextButtonLabel": "Skena umbhalo", "okButtonLabel": "OK", "pasteButtonLabel": "Plak", "selectAllButtonLabel": "Kies alles", diff --git a/packages/flutter_localizations/lib/src/l10n/material_am.arb b/packages/flutter_localizations/lib/src/l10n/material_am.arb index 9252e27227a86..3538c87c0f2b5 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_am.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_am.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "ቀጥል", "copyButtonLabel": "ቅዳ", "cutButtonLabel": "ቁረጥ", + "scanTextButtonLabel": "ጽሑፍ ይቃኙ", "okButtonLabel": "እሺ", "pasteButtonLabel": "ለጥፍ", "selectAllButtonLabel": "ሁሉንም ምረጥ", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ar.arb b/packages/flutter_localizations/lib/src/l10n/material_ar.arb index 09e0deb146e16..73f9d2b1181a6 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ar.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ar.arb @@ -35,6 +35,7 @@ "continueButtonLabel": "المتابعة", "copyButtonLabel": "نسخ", "cutButtonLabel": "قص", + "scanTextButtonLabel": "مسح النص", "okButtonLabel": "حسنًا", "pasteButtonLabel": "لصق", "selectAllButtonLabel": "اختيار الكل", diff --git a/packages/flutter_localizations/lib/src/l10n/material_as.arb b/packages/flutter_localizations/lib/src/l10n/material_as.arb index 947cb777e7a8f..f358a744115e2 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_as.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_as.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "অব্যাহত ৰাখক", "copyButtonLabel": "প্ৰতিলিপি কৰক", "cutButtonLabel": "কাট কৰক", + "scanTextButtonLabel": "স্কেন টেক্সট", "okButtonLabel": "ঠিক আছে", "pasteButtonLabel": "পে'ষ্ট কৰক", "selectAllButtonLabel": "সকলো বাছনি কৰক", diff --git a/packages/flutter_localizations/lib/src/l10n/material_az.arb b/packages/flutter_localizations/lib/src/l10n/material_az.arb index ecda4f6a12b2a..ea0736f368180 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_az.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_az.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Davam edin", "copyButtonLabel": "Kopyalayın", "cutButtonLabel": "Kəsin", + "scanTextButtonLabel": "Mətni skan edin", "okButtonLabel": "OK", "pasteButtonLabel": "Yerləşdirin", "selectAllButtonLabel": "Hamısını seçin", diff --git a/packages/flutter_localizations/lib/src/l10n/material_be.arb b/packages/flutter_localizations/lib/src/l10n/material_be.arb index d370cfaf26596..d58cb145704de 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_be.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_be.arb @@ -31,6 +31,7 @@ "continueButtonLabel": "Працягнуць", "copyButtonLabel": "Капіраваць", "cutButtonLabel": "Выразаць", + "scanTextButtonLabel": "Сканаваць тэкст", "okButtonLabel": "ОК", "pasteButtonLabel": "Уставіць", "selectAllButtonLabel": "Выбраць усе", diff --git a/packages/flutter_localizations/lib/src/l10n/material_bg.arb b/packages/flutter_localizations/lib/src/l10n/material_bg.arb index bc18ea7dac76c..fb6074c48d6de 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_bg.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_bg.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Напред", "copyButtonLabel": "Копиране", "cutButtonLabel": "Изрязване", + "scanTextButtonLabel": "Сканиране на текст", "okButtonLabel": "OK", "pasteButtonLabel": "Поставяне", "selectAllButtonLabel": "Избиране на всички", diff --git a/packages/flutter_localizations/lib/src/l10n/material_bn.arb b/packages/flutter_localizations/lib/src/l10n/material_bn.arb index cb6809b260c49..03587bc348245 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_bn.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_bn.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "চালিয়ে যান", "copyButtonLabel": "কপি করুন", "cutButtonLabel": "কাট করুন", + "scanTextButtonLabel": "পাঠ্য স্ক্যান করুন", "okButtonLabel": "ঠিক আছে", "pasteButtonLabel": "পেস্ট করুন", "selectAllButtonLabel": "সব বেছে নিন", diff --git a/packages/flutter_localizations/lib/src/l10n/material_bs.arb b/packages/flutter_localizations/lib/src/l10n/material_bs.arb index 8915688b00ec1..bbe68a1dafc4a 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_bs.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_bs.arb @@ -28,6 +28,7 @@ "continueButtonLabel": "Nastavi", "copyButtonLabel": "Kopiraj", "cutButtonLabel": "Izreži", + "scanTextButtonLabel": "Skeniraj tekst", "okButtonLabel": "Uredu", "pasteButtonLabel": "Zalijepi", "selectAllButtonLabel": "Odaberi sve", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ca.arb b/packages/flutter_localizations/lib/src/l10n/material_ca.arb index 031a078b7209b..aecdb082777ea 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ca.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ca.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Continua", "copyButtonLabel": "Copia", "cutButtonLabel": "Retalla", + "scanTextButtonLabel": "Escaneja el text", "okButtonLabel": "D'ACORD", "pasteButtonLabel": "Enganxa", "selectAllButtonLabel": "Selecciona-ho tot", diff --git a/packages/flutter_localizations/lib/src/l10n/material_cs.arb b/packages/flutter_localizations/lib/src/l10n/material_cs.arb index 4dcd0628fb776..1e17c2e1c9a5b 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_cs.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_cs.arb @@ -31,6 +31,7 @@ "continueButtonLabel": "Pokračovat", "copyButtonLabel": "Kopírovat", "cutButtonLabel": "Vyjmout", + "scanTextButtonLabel": "Naskenujte text", "okButtonLabel": "OK", "pasteButtonLabel": "Vložit", "selectAllButtonLabel": "Vybrat vše", diff --git a/packages/flutter_localizations/lib/src/l10n/material_cy.arb b/packages/flutter_localizations/lib/src/l10n/material_cy.arb index 35831c845ec49..1cf88afa69ea4 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_cy.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_cy.arb @@ -150,5 +150,6 @@ "expansionTileExpandedTapHint": "Collapse", "expansionTileCollapsedTapHint": "Expand for more details", "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "collapsedHint": "Expanded", + "scanTextButtonLabel": "Scan text" } diff --git a/packages/flutter_localizations/lib/src/l10n/material_da.arb b/packages/flutter_localizations/lib/src/l10n/material_da.arb index 98d147e92e02c..52c309eda3564 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_da.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_da.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Fortsæt", "copyButtonLabel": "Kopiér", "cutButtonLabel": "Klip", + "scanTextButtonLabel": "Scan tekst", "okButtonLabel": "OK", "pasteButtonLabel": "Indsæt", "selectAllButtonLabel": "Markér alt", diff --git a/packages/flutter_localizations/lib/src/l10n/material_de.arb b/packages/flutter_localizations/lib/src/l10n/material_de.arb index f72cfe3c19d56..9a5fbefb09190 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_de.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_de.arb @@ -26,6 +26,7 @@ "continueButtonLabel": "Weiter", "copyButtonLabel": "Kopieren", "cutButtonLabel": "Ausschneiden", + "scanTextButtonLabel": "Text scannen", "okButtonLabel": "OK", "pasteButtonLabel": "Einsetzen", "selectAllButtonLabel": "Alle auswählen", diff --git a/packages/flutter_localizations/lib/src/l10n/material_el.arb b/packages/flutter_localizations/lib/src/l10n/material_el.arb index c0727743dc5bc..e9f3408a997ce 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_el.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_el.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Συνέχεια", "copyButtonLabel": "Αντιγραφή", "cutButtonLabel": "Αποκοπή", + "scanTextButtonLabel": "Σάρωση κειμένου", "okButtonLabel": "ΟΚ", "pasteButtonLabel": "Επικόλληση", "selectAllButtonLabel": "Επιλογή όλων", diff --git a/packages/flutter_localizations/lib/src/l10n/material_en.arb b/packages/flutter_localizations/lib/src/l10n/material_en.arb index dc2e11e49b510..dd9746dcd5956 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_en.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_en.arb @@ -192,6 +192,11 @@ "description": "The label for cut buttons and menu items." }, + "scanTextButtonLabel": "Scan text", + "@scanTextButtonLabel": { + "description": "The label for scan text buttons and menu items for starting the insertion of text via OCR." + }, + "okButtonLabel": "OK", "@okButtonLabel": { "description": "The label for OK buttons and menu items." diff --git a/packages/flutter_localizations/lib/src/l10n/material_es.arb b/packages/flutter_localizations/lib/src/l10n/material_es.arb index 4ceef3a18cc54..59eed0c4324de 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_es.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_es.arb @@ -26,6 +26,7 @@ "continueButtonLabel": "Continuar", "copyButtonLabel": "Copiar", "cutButtonLabel": "Cortar", + "scanTextButtonLabel": "Escanear texto", "okButtonLabel": "ACEPTAR", "pasteButtonLabel": "Pegar", "selectAllButtonLabel": "Seleccionar todo", diff --git a/packages/flutter_localizations/lib/src/l10n/material_et.arb b/packages/flutter_localizations/lib/src/l10n/material_et.arb index 90f8c507a3ba3..97fb40a2a7ede 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_et.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_et.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Jätka", "copyButtonLabel": "Kopeeri", "cutButtonLabel": "Lõika", + "scanTextButtonLabel": "Skanni teksti", "okButtonLabel": "OK", "pasteButtonLabel": "Kleebi", "selectAllButtonLabel": "Vali kõik", diff --git a/packages/flutter_localizations/lib/src/l10n/material_eu.arb b/packages/flutter_localizations/lib/src/l10n/material_eu.arb index 3d0b043d9346a..8e299211b2f8c 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_eu.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_eu.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Egin aurrera", "copyButtonLabel": "Kopiatu", "cutButtonLabel": "Ebaki", + "scanTextButtonLabel": "Eskaneatu testua", "okButtonLabel": "Ados", "pasteButtonLabel": "Itsatsi", "selectAllButtonLabel": "Hautatu guztiak", diff --git a/packages/flutter_localizations/lib/src/l10n/material_fa.arb b/packages/flutter_localizations/lib/src/l10n/material_fa.arb index 7dc1bcde30a9e..20c7fbcfd9937 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_fa.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_fa.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "ادامه", "copyButtonLabel": "کپی", "cutButtonLabel": "برش", + "scanTextButtonLabel": "اسکن متن", "okButtonLabel": "تأیید", "pasteButtonLabel": "جای‌گذاری", "selectAllButtonLabel": "انتخاب همه", diff --git a/packages/flutter_localizations/lib/src/l10n/material_fi.arb b/packages/flutter_localizations/lib/src/l10n/material_fi.arb index ba4ce53ed144d..8db1a1af41adb 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_fi.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_fi.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Jatka", "copyButtonLabel": "Kopioi", "cutButtonLabel": "Leikkaa", + "scanTextButtonLabel": "Skannaa tekstiä", "okButtonLabel": "OK", "pasteButtonLabel": "Liitä", "selectAllButtonLabel": "Valitse kaikki", diff --git a/packages/flutter_localizations/lib/src/l10n/material_fil.arb b/packages/flutter_localizations/lib/src/l10n/material_fil.arb index c1541844e620e..fbf38a18b99a2 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_fil.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_fil.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Magpatuloy", "copyButtonLabel": "Kopyahin", "cutButtonLabel": "I-cut", + "scanTextButtonLabel": "I-scan ang text", "okButtonLabel": "OK", "pasteButtonLabel": "I-paste", "selectAllButtonLabel": "Piliin lahat", diff --git a/packages/flutter_localizations/lib/src/l10n/material_fr.arb b/packages/flutter_localizations/lib/src/l10n/material_fr.arb index 39f5122f0b504..accbb33f232e9 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_fr.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_fr.arb @@ -26,6 +26,7 @@ "continueButtonLabel": "Continuer", "copyButtonLabel": "Copier", "cutButtonLabel": "Couper", + "scanTextButtonLabel": "Numériser du texte", "okButtonLabel": "OK", "pasteButtonLabel": "Coller", "selectAllButtonLabel": "Tout sélectionner", diff --git a/packages/flutter_localizations/lib/src/l10n/material_gl.arb b/packages/flutter_localizations/lib/src/l10n/material_gl.arb index 0e8a200638d82..2557795963089 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_gl.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_gl.arb @@ -26,6 +26,7 @@ "continueButtonLabel": "Continuar", "copyButtonLabel": "Copiar", "cutButtonLabel": "Cortar", + "scanTextButtonLabel": "Escanear texto", "okButtonLabel": "Aceptar", "pasteButtonLabel": "Pegar", "selectAllButtonLabel": "Seleccionar todo", diff --git a/packages/flutter_localizations/lib/src/l10n/material_gsw.arb b/packages/flutter_localizations/lib/src/l10n/material_gsw.arb index f8a66c88c2468..1d803e7b2ff91 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_gsw.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_gsw.arb @@ -30,6 +30,7 @@ "continueButtonLabel": "Weiter", "copyButtonLabel": "Kopieren", "cutButtonLabel": "Ausschneiden", + "scanTextButtonLabel": "Text scannen", "okButtonLabel": "OK", "pasteButtonLabel": "Einsetzen", "selectAllButtonLabel": "Alle auswählen", diff --git a/packages/flutter_localizations/lib/src/l10n/material_gu.arb b/packages/flutter_localizations/lib/src/l10n/material_gu.arb index 053afe5f3ceda..371690c084b97 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_gu.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_gu.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "ચાલુ રાખો", "copyButtonLabel": "કૉપિ કરો", "cutButtonLabel": "કાપો", + "scanTextButtonLabel": "ટેક્સ્ટ સ્કેન કરો", "okButtonLabel": "ઓકે", "pasteButtonLabel": "પેસ્ટ કરો", "selectAllButtonLabel": "બધા પસંદ કરો", diff --git a/packages/flutter_localizations/lib/src/l10n/material_he.arb b/packages/flutter_localizations/lib/src/l10n/material_he.arb index 73991d6c8eed6..c1f33f5bb61f2 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_he.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_he.arb @@ -31,6 +31,7 @@ "continueButtonLabel": "המשך", "copyButtonLabel": "העתקה", "cutButtonLabel": "גזירה", + "scanTextButtonLabel": "סרוק טקסט", "okButtonLabel": "אישור", "pasteButtonLabel": "הדבקה", "selectAllButtonLabel": "בחירת הכול", diff --git a/packages/flutter_localizations/lib/src/l10n/material_hi.arb b/packages/flutter_localizations/lib/src/l10n/material_hi.arb index 4ccdf90019457..78fe9a1d30228 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_hi.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_hi.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "जारी रखें", "copyButtonLabel": "कॉपी करें", "cutButtonLabel": "काटें", + "scanTextButtonLabel": "पाठ स्कैन करें", "okButtonLabel": "ठीक है", "pasteButtonLabel": "चिपकाएं", "selectAllButtonLabel": "सभी को चुनें", diff --git a/packages/flutter_localizations/lib/src/l10n/material_hr.arb b/packages/flutter_localizations/lib/src/l10n/material_hr.arb index 1a199173f4a0a..7535eb9e70ed8 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_hr.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_hr.arb @@ -28,6 +28,7 @@ "continueButtonLabel": "Nastavi", "copyButtonLabel": "Kopiraj", "cutButtonLabel": "Izreži", + "scanTextButtonLabel": "Skeniraj tekst", "okButtonLabel": "U REDU", "pasteButtonLabel": "Zalijepi", "selectAllButtonLabel": "Odaberi sve", diff --git a/packages/flutter_localizations/lib/src/l10n/material_hu.arb b/packages/flutter_localizations/lib/src/l10n/material_hu.arb index 654be05879526..88c57ae8b3c57 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_hu.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_hu.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Folytatás", "copyButtonLabel": "Másolás", "cutButtonLabel": "Kivágás", + "scanTextButtonLabel": "Szöveg beolvasása", "okButtonLabel": "OK", "pasteButtonLabel": "Beillesztés", "selectAllButtonLabel": "Összes kijelölése", diff --git a/packages/flutter_localizations/lib/src/l10n/material_hy.arb b/packages/flutter_localizations/lib/src/l10n/material_hy.arb index 4e1d29cc28129..02d1ab87b93ad 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_hy.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_hy.arb @@ -30,6 +30,7 @@ "continueButtonLabel": "Շարունակել", "copyButtonLabel": "Պատճենել", "cutButtonLabel": "Կտրել", + "scanTextButtonLabel": "Սկանավորեք տեքստը", "okButtonLabel": "Եղավ", "pasteButtonLabel": "Տեղադրել", "selectAllButtonLabel": "Նշել բոլորը", diff --git a/packages/flutter_localizations/lib/src/l10n/material_id.arb b/packages/flutter_localizations/lib/src/l10n/material_id.arb index e48f988c20296..131bface74bec 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_id.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_id.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Lanjutkan", "copyButtonLabel": "Salin", "cutButtonLabel": "Potong", + "scanTextButtonLabel": "Pindai teks", "okButtonLabel": "OKE", "pasteButtonLabel": "Tempel", "selectAllButtonLabel": "Pilih semua", diff --git a/packages/flutter_localizations/lib/src/l10n/material_is.arb b/packages/flutter_localizations/lib/src/l10n/material_is.arb index 2dfd4ed24fc0e..30078d757f967 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_is.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_is.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Áfram", "copyButtonLabel": "Afrita", "cutButtonLabel": "Klippa", + "scanTextButtonLabel": "Skannaðu texta", "okButtonLabel": "Í lagi", "pasteButtonLabel": "Líma", "selectAllButtonLabel": "Velja allt", diff --git a/packages/flutter_localizations/lib/src/l10n/material_it.arb b/packages/flutter_localizations/lib/src/l10n/material_it.arb index 9f7f4a8303e16..1f35104a9d98d 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_it.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_it.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Continua", "copyButtonLabel": "Copia", "cutButtonLabel": "Taglia", + "scanTextButtonLabel": "Scansiona il testo", "okButtonLabel": "OK", "pasteButtonLabel": "Incolla", "selectAllButtonLabel": "Seleziona tutto", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ja.arb b/packages/flutter_localizations/lib/src/l10n/material_ja.arb index 59f145fe45be1..58e3c810ba73b 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ja.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ja.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "続行", "copyButtonLabel": "コピー", "cutButtonLabel": "切り取り", + "scanTextButtonLabel": "テキストをスキャン", "okButtonLabel": "OK", "pasteButtonLabel": "貼り付け", "selectAllButtonLabel": "すべて選択", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ka.arb b/packages/flutter_localizations/lib/src/l10n/material_ka.arb index 36ff3fe76172c..b221c94792889 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ka.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ka.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "გაგრძელება", "copyButtonLabel": "კოპირება", "cutButtonLabel": "ამოჭრა", + "scanTextButtonLabel": "ტექსტის სკანირება", "okButtonLabel": "კარგი", "pasteButtonLabel": "ჩასმა", "selectAllButtonLabel": "ყველას არჩევა", diff --git a/packages/flutter_localizations/lib/src/l10n/material_kk.arb b/packages/flutter_localizations/lib/src/l10n/material_kk.arb index b35d5989d8fbe..a318f00a48881 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_kk.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_kk.arb @@ -27,6 +27,7 @@ "continueButtonLabel": "Жалғастыру", "copyButtonLabel": "Көшіру", "cutButtonLabel": "Қию", + "scanTextButtonLabel": "Мәтінді сканерлеу", "okButtonLabel": "Иә", "pasteButtonLabel": "Қою", "selectAllButtonLabel": "Барлығын таңдау", diff --git a/packages/flutter_localizations/lib/src/l10n/material_km.arb b/packages/flutter_localizations/lib/src/l10n/material_km.arb index 7d61ff71db53a..9c2a624991e25 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_km.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_km.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "បន្ត", "copyButtonLabel": "ចម្លង", "cutButtonLabel": "កាត់", + "scanTextButtonLabel": "ស្កេនអត្ថបទ", "okButtonLabel": "យល់ព្រម", "pasteButtonLabel": "ដាក់​ចូល", "selectAllButtonLabel": "ជ្រើសរើស​ទាំងអស់", diff --git a/packages/flutter_localizations/lib/src/l10n/material_kn.arb b/packages/flutter_localizations/lib/src/l10n/material_kn.arb index 7beddc5670de1..624941d984c0d 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_kn.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_kn.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "\u0cae\u0cc1\u0c82\u0ca6\u0cc1\u0cb5\u0cb0\u0cbf\u0cb8\u0cbf", "copyButtonLabel": "\u0ca8\u0c95\u0cb2\u0cbf\u0cb8\u0cbf", "cutButtonLabel": "\u0c95\u0ca4\u0ccd\u0ca4\u0cb0\u0cbf\u0cb8\u0cbf", + "scanTextButtonLabel": "\u0caa\u0ca0\u0ccd\u0caf\u0cb5\u0ca8\u0ccd\u0ca8\u0cc1\u0020\u0cb8\u0ccd\u0c95\u0ccd\u0caf\u0cbe\u0ca8\u0ccd\u0020\u0cae\u0cbe\u0ca1\u0cbf", "okButtonLabel": "\u0cb8\u0cb0\u0cbf", "pasteButtonLabel": "\u0c85\u0c82\u0c9f\u0cbf\u0cb8\u0cbf", "selectAllButtonLabel": "\u0c8e\u0cb2\u0ccd\u0cb2\u0cb5\u0ca8\u0ccd\u0ca8\u0cc2\u0020\u0c86\u0caf\u0ccd\u0c95\u0cc6\u0020\u0cae\u0cbe\u0ca1\u0cbf", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ko.arb b/packages/flutter_localizations/lib/src/l10n/material_ko.arb index b0d7fc88f3cf2..4b2eeee024c9a 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ko.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ko.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "계속", "copyButtonLabel": "복사", "cutButtonLabel": "잘라냄", + "scanTextButtonLabel": "스캔 텍스트", "okButtonLabel": "확인", "pasteButtonLabel": "붙여넣기", "selectAllButtonLabel": "전체 선택", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ky.arb b/packages/flutter_localizations/lib/src/l10n/material_ky.arb index e751ea37e8692..568e08e294238 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ky.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ky.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Улантуу", "copyButtonLabel": "Көчүрүү", "cutButtonLabel": "Кесүү", + "scanTextButtonLabel": "Текстти скандоо", "okButtonLabel": "Макул", "pasteButtonLabel": "Чаптоо", "selectAllButtonLabel": "Баарын тандоо", diff --git a/packages/flutter_localizations/lib/src/l10n/material_lo.arb b/packages/flutter_localizations/lib/src/l10n/material_lo.arb index c28b7be49d155..2b02046d148c5 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_lo.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_lo.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "ສືບຕໍ່", "copyButtonLabel": "ສຳເນົາ", "cutButtonLabel": "ຕັດ", + "scanTextButtonLabel": "ສະແກນຂໍ້ຄວາມ", "okButtonLabel": "ຕົກລົງ", "pasteButtonLabel": "ວາງ", "selectAllButtonLabel": "ເລືອກທັງໝົດ", diff --git a/packages/flutter_localizations/lib/src/l10n/material_lt.arb b/packages/flutter_localizations/lib/src/l10n/material_lt.arb index 82bf6e24c4933..62d77cb56ac26 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_lt.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_lt.arb @@ -31,6 +31,7 @@ "continueButtonLabel": "Tęsti", "copyButtonLabel": "Kopijuoti", "cutButtonLabel": "Iškirpti", + "scanTextButtonLabel": "Nuskaityti tekstą", "okButtonLabel": "GERAI", "pasteButtonLabel": "Įklijuoti", "selectAllButtonLabel": "Pasirinkti viską", diff --git a/packages/flutter_localizations/lib/src/l10n/material_lv.arb b/packages/flutter_localizations/lib/src/l10n/material_lv.arb index 62bdf7edf9ce9..3c84e58cea342 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_lv.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_lv.arb @@ -26,6 +26,7 @@ "continueButtonLabel": "Turpināt", "copyButtonLabel": "Kopēt", "cutButtonLabel": "Izgriezt", + "scanTextButtonLabel": "Skenēt tekstu", "okButtonLabel": "LABI", "pasteButtonLabel": "Ielīmēt", "selectAllButtonLabel": "Atlasīt visu", diff --git a/packages/flutter_localizations/lib/src/l10n/material_mk.arb b/packages/flutter_localizations/lib/src/l10n/material_mk.arb index 8f479b8883f69..99586cbc339ed 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_mk.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_mk.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Продолжи", "copyButtonLabel": "Копирај", "cutButtonLabel": "Исечи", + "scanTextButtonLabel": "Скенирајте текст", "okButtonLabel": "Во ред", "pasteButtonLabel": "Залепи", "selectAllButtonLabel": "Избери ги сите", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ml.arb b/packages/flutter_localizations/lib/src/l10n/material_ml.arb index bda009d4b3038..28896359cc47d 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ml.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ml.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "തുടരുക", "copyButtonLabel": "പകർത്തുക", "cutButtonLabel": "മുറിക്കുക", + "scanTextButtonLabel": "ടെക്സ്റ്റ് സ്കാൻ ചെയ്യുക", "okButtonLabel": "ശരി", "pasteButtonLabel": "ഒട്ടിക്കുക", "selectAllButtonLabel": "എല്ലാം തിരഞ്ഞെടുക്കുക", diff --git a/packages/flutter_localizations/lib/src/l10n/material_mn.arb b/packages/flutter_localizations/lib/src/l10n/material_mn.arb index 9d2cd46341707..a960b7b141edb 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_mn.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_mn.arb @@ -26,6 +26,7 @@ "continueButtonLabel": "Үргэлжлүүлэх", "copyButtonLabel": "Хуулах", "cutButtonLabel": "Таслах", + "scanTextButtonLabel": "Текст сканнердах", "okButtonLabel": "OK", "pasteButtonLabel": "Буулгах", "selectAllButtonLabel": "Бүгдийг сонгох", diff --git a/packages/flutter_localizations/lib/src/l10n/material_mr.arb b/packages/flutter_localizations/lib/src/l10n/material_mr.arb index 35b68b5503353..ccc1ab9019bd7 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_mr.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_mr.arb @@ -26,6 +26,7 @@ "continueButtonLabel": "पुढे सुरू ठेवा", "copyButtonLabel": "कॉपी करा", "cutButtonLabel": "कट करा", + "scanTextButtonLabel": "मजकूर स्कॅन करा", "okButtonLabel": "ओके", "pasteButtonLabel": "पेस्ट करा", "selectAllButtonLabel": "सर्व निवडा", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ms.arb b/packages/flutter_localizations/lib/src/l10n/material_ms.arb index fed2636a7f5ec..2efd94a47bbd9 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ms.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ms.arb @@ -26,6 +26,7 @@ "continueButtonLabel": "Teruskan", "copyButtonLabel": "Salin", "cutButtonLabel": "Potong", + "scanTextButtonLabel": "Pindai teks", "okButtonLabel": "OK", "pasteButtonLabel": "Tampal", "selectAllButtonLabel": "Pilih semua", diff --git a/packages/flutter_localizations/lib/src/l10n/material_my.arb b/packages/flutter_localizations/lib/src/l10n/material_my.arb index b29091ee06c91..06cde1220d69b 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_my.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_my.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "ရှေ့ဆက်ရန်", "copyButtonLabel": "မိတ္တူကူးရန်", "cutButtonLabel": "ဖြတ်ယူရန်", + "scanTextButtonLabel": "စာသားကို စကင်ဖတ်ပါ။", "okButtonLabel": "OK", "pasteButtonLabel": "ကူးထည့်ရန်", "selectAllButtonLabel": "အားလုံး ရွေးရန်", diff --git a/packages/flutter_localizations/lib/src/l10n/material_nb.arb b/packages/flutter_localizations/lib/src/l10n/material_nb.arb index f8e53f425d0e3..0a4810524f235 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_nb.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_nb.arb @@ -28,6 +28,7 @@ "continueButtonLabel": "Fortsett", "copyButtonLabel": "Kopiér", "cutButtonLabel": "Klipp ut", + "scanTextButtonLabel": "Scan tekst", "okButtonLabel": "OK", "pasteButtonLabel": "Lim inn", "selectAllButtonLabel": "Velg alle", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ne.arb b/packages/flutter_localizations/lib/src/l10n/material_ne.arb index 55c65de2de7d9..7c53636103d85 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ne.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ne.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "जारी राख्नुहोस्", "copyButtonLabel": "प्रतिलिपि गर्नुहोस्", "cutButtonLabel": "काट्नुहोस्", + "scanTextButtonLabel": "पाठ स्क्यान गर्नुहोस्", "okButtonLabel": "ठिक छ", "pasteButtonLabel": "टाँस्नुहोस्", "selectAllButtonLabel": "सबै बटनहरू चयन गर्नुहोस्", diff --git a/packages/flutter_localizations/lib/src/l10n/material_nl.arb b/packages/flutter_localizations/lib/src/l10n/material_nl.arb index 090aa051e3d2c..16d0bceebf9e0 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_nl.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_nl.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Doorgaan", "copyButtonLabel": "Kopiëren", "cutButtonLabel": "Knippen", + "scanTextButtonLabel": "Tekst scannen", "okButtonLabel": "OK", "pasteButtonLabel": "Plakken", "selectAllButtonLabel": "Alles selecteren", diff --git a/packages/flutter_localizations/lib/src/l10n/material_no.arb b/packages/flutter_localizations/lib/src/l10n/material_no.arb index f8e53f425d0e3..a311a70b250c2 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_no.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_no.arb @@ -28,6 +28,7 @@ "continueButtonLabel": "Fortsett", "copyButtonLabel": "Kopiér", "cutButtonLabel": "Klipp ut", + "scanTextButtonLabel": "Skann tekst", "okButtonLabel": "OK", "pasteButtonLabel": "Lim inn", "selectAllButtonLabel": "Velg alle", diff --git a/packages/flutter_localizations/lib/src/l10n/material_or.arb b/packages/flutter_localizations/lib/src/l10n/material_or.arb index 834be306eb5bf..299d57c067bcf 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_or.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_or.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "ଜାରି ରଖନ୍ତୁ", "copyButtonLabel": "କପି କରନ୍ତୁ", "cutButtonLabel": "କଟ୍ କରନ୍ତୁ", + "scanTextButtonLabel": "ପାଠ୍ୟ ସ୍କାନ୍ କରନ୍ତୁ", "okButtonLabel": "ଠିକ୍ ଅଛି", "pasteButtonLabel": "ପେଷ୍ଟ କରନ୍ତୁ", "selectAllButtonLabel": "ସବୁ ଚୟନ କରନ୍ତୁ", diff --git a/packages/flutter_localizations/lib/src/l10n/material_pa.arb b/packages/flutter_localizations/lib/src/l10n/material_pa.arb index c0d9ba71e3c0c..d48c4611a6da6 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_pa.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_pa.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "ਜਾਰੀ ਰੱਖੋ", "copyButtonLabel": "ਕਾਪੀ ਕਰੋ", "cutButtonLabel": "ਕੱਟ ਕਰੋ", + "scanTextButtonLabel": "ਟੈਕਸਟ ਸਕੈਨ ਕਰੋ", "okButtonLabel": "ਠੀਕ ਹੈ", "pasteButtonLabel": "ਪੇਸਟ ਕਰੋ", "selectAllButtonLabel": "ਸਭ ਚੁਣੋ", diff --git a/packages/flutter_localizations/lib/src/l10n/material_pl.arb b/packages/flutter_localizations/lib/src/l10n/material_pl.arb index 3385571c58091..47695b170de7e 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_pl.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_pl.arb @@ -31,6 +31,7 @@ "continueButtonLabel": "Dalej", "copyButtonLabel": "Kopiuj", "cutButtonLabel": "Wytnij", + "scanTextButtonLabel": "Zeskanuj tekst", "okButtonLabel": "OK", "pasteButtonLabel": "Wklej", "selectAllButtonLabel": "Zaznacz wszystko", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ps.arb b/packages/flutter_localizations/lib/src/l10n/material_ps.arb index 524bb28c8f0f0..042b8055dbc7f 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ps.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ps.arb @@ -28,6 +28,7 @@ "continueButtonLabel": "منځپانګې", "copyButtonLabel": "کاپی", "cutButtonLabel": "کم کړئ", + "scanTextButtonLabel": "متن سکین کړئ", "okButtonLabel": "سمه ده", "pasteButtonLabel": "پیټ کړئ", "selectAllButtonLabel": "غوره کړئ", diff --git a/packages/flutter_localizations/lib/src/l10n/material_pt.arb b/packages/flutter_localizations/lib/src/l10n/material_pt.arb index e34cb8d5516c9..c671af7dab373 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_pt.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_pt.arb @@ -29,6 +29,7 @@ "continueButtonLabel": "Continuar", "copyButtonLabel": "Copiar", "cutButtonLabel": "Cortar", + "scanTextButtonLabel": "Digitalizar texto", "okButtonLabel": "OK", "pasteButtonLabel": "Colar", "selectAllButtonLabel": "Selecionar tudo", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ro.arb b/packages/flutter_localizations/lib/src/l10n/material_ro.arb index 2c909c41cd0db..fcd7130a5d472 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ro.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ro.arb @@ -29,6 +29,7 @@ "continueButtonLabel": "Continuați", "copyButtonLabel": "Copiați", "cutButtonLabel": "Decupați", + "scanTextButtonLabel": "Scanați textul", "okButtonLabel": "OK", "pasteButtonLabel": "Inserați", "selectAllButtonLabel": "Selectați tot", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ru.arb b/packages/flutter_localizations/lib/src/l10n/material_ru.arb index 3027f585c2fb4..9b81a9cc66bc8 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ru.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ru.arb @@ -32,6 +32,7 @@ "continueButtonLabel": "Продолжить", "copyButtonLabel": "Копировать", "cutButtonLabel": "Вырезать", + "scanTextButtonLabel": "Сканировать текст", "okButtonLabel": "ОК", "pasteButtonLabel": "Вставить", "selectAllButtonLabel": "Выбрать все", diff --git a/packages/flutter_localizations/lib/src/l10n/material_si.arb b/packages/flutter_localizations/lib/src/l10n/material_si.arb index f4acf36a7f9ae..4ac3ecfe99298 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_si.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_si.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "ඉදිරියට යන්න", "copyButtonLabel": "පිටපත් කරන්න", "cutButtonLabel": "කපන්න", + "scanTextButtonLabel": "පෙළ පරිලෝකනය කරන්න", "okButtonLabel": "හරි", "pasteButtonLabel": "අලවන්න", "selectAllButtonLabel": "සියල්ල තෝරන්න", diff --git a/packages/flutter_localizations/lib/src/l10n/material_sk.arb b/packages/flutter_localizations/lib/src/l10n/material_sk.arb index deb8c5e77349d..a85cfaf3b0936 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_sk.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_sk.arb @@ -31,6 +31,7 @@ "continueButtonLabel": "Pokračovať", "copyButtonLabel": "Kopírovať", "cutButtonLabel": "Vystrihnúť", + "scanTextButtonLabel": "Naskenujte text", "okButtonLabel": "OK", "pasteButtonLabel": "Prilepiť", "selectAllButtonLabel": "Vybrať všetko", diff --git a/packages/flutter_localizations/lib/src/l10n/material_sl.arb b/packages/flutter_localizations/lib/src/l10n/material_sl.arb index dd6941d070636..7fe2795860d4b 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_sl.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_sl.arb @@ -31,6 +31,7 @@ "continueButtonLabel": "Naprej", "copyButtonLabel": "Kopiraj", "cutButtonLabel": "Izreži", + "scanTextButtonLabel": "Skeniraj besedilo", "okButtonLabel": "V REDU", "pasteButtonLabel": "Prilepi", "selectAllButtonLabel": "Izberi vse", diff --git a/packages/flutter_localizations/lib/src/l10n/material_sq.arb b/packages/flutter_localizations/lib/src/l10n/material_sq.arb index 123b5e490d7a5..9db947b7c5025 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_sq.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_sq.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Vazhdo", "copyButtonLabel": "Kopjo", "cutButtonLabel": "Prit", + "scanTextButtonLabel": "Skanoni tekstin", "okButtonLabel": "Në rregull", "pasteButtonLabel": "Ngjit", "selectAllButtonLabel": "Zgjidh të gjitha", diff --git a/packages/flutter_localizations/lib/src/l10n/material_sr.arb b/packages/flutter_localizations/lib/src/l10n/material_sr.arb index c041c7c1ef0dc..05865175bdb61 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_sr.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_sr.arb @@ -28,6 +28,7 @@ "continueButtonLabel": "Настави", "copyButtonLabel": "Копирај", "cutButtonLabel": "Исеци", + "scanTextButtonLabel": "Скенирајте текст", "okButtonLabel": "Потврди", "pasteButtonLabel": "Налепи", "selectAllButtonLabel": "Изабери све", diff --git a/packages/flutter_localizations/lib/src/l10n/material_sv.arb b/packages/flutter_localizations/lib/src/l10n/material_sv.arb index e73b740fdf89e..5e4573804a750 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_sv.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_sv.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Fortsätt", "copyButtonLabel": "Kopiera", "cutButtonLabel": "Klipp ut", + "scanTextButtonLabel": "Skanna text", "okButtonLabel": "OK", "pasteButtonLabel": "Klistra in", "selectAllButtonLabel": "Markera allt", diff --git a/packages/flutter_localizations/lib/src/l10n/material_sw.arb b/packages/flutter_localizations/lib/src/l10n/material_sw.arb index 518629e6eb90d..b31a119b598b0 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_sw.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_sw.arb @@ -26,6 +26,7 @@ "continueButtonLabel": "Endelea", "copyButtonLabel": "Nakili", "cutButtonLabel": "Kata", + "scanTextButtonLabel": "Changanua maandishi", "okButtonLabel": "Sawa", "pasteButtonLabel": "Bandika", "selectAllButtonLabel": "Chagua vyote", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ta.arb b/packages/flutter_localizations/lib/src/l10n/material_ta.arb index 0fbc0d4947ada..f6fd8c3b3b6cc 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ta.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ta.arb @@ -17,6 +17,7 @@ "reorderItemLeft": "இடப்புறம் நகர்த்தவும்", "reorderItemRight": "வலப்புறம் நகர்த்தவும்", "cutButtonLabel": "வெட்டு", + "scanTextButtonLabel": "உரையை ஸ்கேன் செய்யவும்", "pasteButtonLabel": "ஒட்டு", "previousMonthTooltip": "முந்தைய மாதம்", "nextMonthTooltip": "அடுத்த மாதம்", diff --git a/packages/flutter_localizations/lib/src/l10n/material_te.arb b/packages/flutter_localizations/lib/src/l10n/material_te.arb index 39223608f175d..4220e89b98015 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_te.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_te.arb @@ -24,6 +24,7 @@ "closeButtonLabel": "మూసివేయండి", "continueButtonLabel": "కొనసాగించండి", "copyButtonLabel": "కాపీ చేయి", + "scanTextButtonLabel": "వచనాన్ని స్కాన్ చేయండి", "cutButtonLabel": "కత్తిరించండి", "okButtonLabel": "సరే", "pasteButtonLabel": "పేస్ట్ చేయండి", diff --git a/packages/flutter_localizations/lib/src/l10n/material_th.arb b/packages/flutter_localizations/lib/src/l10n/material_th.arb index 9ee0ceedaae69..a0180b3b9b372 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_th.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_th.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "ต่อไป", "copyButtonLabel": "คัดลอก", "cutButtonLabel": "ตัด", + "scanTextButtonLabel": "สแกนข้อความ", "okButtonLabel": "ตกลง", "pasteButtonLabel": "วาง", "selectAllButtonLabel": "เลือกทั้งหมด", diff --git a/packages/flutter_localizations/lib/src/l10n/material_tl.arb b/packages/flutter_localizations/lib/src/l10n/material_tl.arb index c1541844e620e..fbf38a18b99a2 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_tl.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_tl.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Magpatuloy", "copyButtonLabel": "Kopyahin", "cutButtonLabel": "I-cut", + "scanTextButtonLabel": "I-scan ang text", "okButtonLabel": "OK", "pasteButtonLabel": "I-paste", "selectAllButtonLabel": "Piliin lahat", diff --git a/packages/flutter_localizations/lib/src/l10n/material_tr.arb b/packages/flutter_localizations/lib/src/l10n/material_tr.arb index 87566e4ae902a..b91d74d61651f 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_tr.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_tr.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Devam", "copyButtonLabel": "Kopyala", "cutButtonLabel": "Kes", + "scanTextButtonLabel": "Metni tara", "okButtonLabel": "Tamam", "pasteButtonLabel": "Yapıştır", "selectAllButtonLabel": "Tümünü seç", diff --git a/packages/flutter_localizations/lib/src/l10n/material_uk.arb b/packages/flutter_localizations/lib/src/l10n/material_uk.arb index f1edc1028ea95..fe9dcddaaebf4 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_uk.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_uk.arb @@ -31,6 +31,7 @@ "continueButtonLabel": "Продовжити", "copyButtonLabel": "Копіювати", "cutButtonLabel": "Вирізати", + "scanTextButtonLabel": "Сканувати текст", "okButtonLabel": "OK", "pasteButtonLabel": "Вставити", "selectAllButtonLabel": "Вибрати всі", diff --git a/packages/flutter_localizations/lib/src/l10n/material_ur.arb b/packages/flutter_localizations/lib/src/l10n/material_ur.arb index 32b7f08e28e7a..c11abd3d38b6b 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_ur.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_ur.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "جاری رکھیں", "copyButtonLabel": "کاپی کریں", "cutButtonLabel": "کٹ کریں", + "scanTextButtonLabel": "متن کو اسکین کریں", "okButtonLabel": "ٹھیک ہے", "pasteButtonLabel": "پیسٹ کریں", "selectAllButtonLabel": "سبھی کو منتخب کریں", diff --git a/packages/flutter_localizations/lib/src/l10n/material_uz.arb b/packages/flutter_localizations/lib/src/l10n/material_uz.arb index 8ddf03cd2ca9b..f773009cc91f2 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_uz.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_uz.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Davom etish", "copyButtonLabel": "Nusxa olish", "cutButtonLabel": "Kesib olish", + "scanTextButtonLabel": "Matnni skanerlash", "okButtonLabel": "OK", "pasteButtonLabel": "Joylash", "selectAllButtonLabel": "Hammasi", diff --git a/packages/flutter_localizations/lib/src/l10n/material_vi.arb b/packages/flutter_localizations/lib/src/l10n/material_vi.arb index d4bd90f6d3cfc..8f57237cc0f32 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_vi.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_vi.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Tiếp tục", "copyButtonLabel": "Sao chép", "cutButtonLabel": "Cắt", + "scanTextButtonLabel": "Quét văn bản", "okButtonLabel": "OK", "pasteButtonLabel": "Dán", "selectAllButtonLabel": "Chọn tất cả", diff --git a/packages/flutter_localizations/lib/src/l10n/material_zh.arb b/packages/flutter_localizations/lib/src/l10n/material_zh.arb index 4e6cd306ca9cf..66a28a60bc1af 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_zh.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_zh.arb @@ -21,6 +21,7 @@ "closeButtonLabel": "关闭", "copyButtonLabel": "复制", "cutButtonLabel": "剪切", + "scanTextButtonLabel": "扫描文本", "okButtonLabel": "确定", "pasteButtonLabel": "粘贴", "selectAllButtonLabel": "全选", diff --git a/packages/flutter_localizations/lib/src/l10n/material_zu.arb b/packages/flutter_localizations/lib/src/l10n/material_zu.arb index 76dd7b1e5b8ff..aae990ba20d7a 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_zu.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_zu.arb @@ -25,6 +25,7 @@ "continueButtonLabel": "Qhubeka", "copyButtonLabel": "Kopisha", "cutButtonLabel": "Sika", + "scanTextButtonLabel": "Skena umbhalo", "okButtonLabel": "KULUNGILE", "pasteButtonLabel": "Namathisela", "selectAllButtonLabel": "Khetha konke", diff --git a/packages/flutter_localizations/pubspec.yaml b/packages/flutter_localizations/pubspec.yaml index 783e89f488d95..df2d0b4195bb2 100644 --- a/packages/flutter_localizations/pubspec.yaml +++ b/packages/flutter_localizations/pubspec.yaml @@ -13,11 +13,11 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -26,12 +26,12 @@ dev_dependencies: async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 9c58 +# PUBSPEC CHECKSUM: 837c diff --git a/packages/flutter_localizations/test/basics_test.dart b/packages/flutter_localizations/test/basics_test.dart index 4ac2375a7d0d2..3ab92f6a4e72a 100644 --- a/packages/flutter_localizations/test/basics_test.dart +++ b/packages/flutter_localizations/test/basics_test.dart @@ -9,6 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('Nested Localizations', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( // Creates the outer Localizations widget. + theme: ThemeData(useMaterial3: true), home: ListView( children: <Widget>[ const LocalizationTracker(key: ValueKey<String>('outer')), @@ -20,11 +21,12 @@ void main() { ], ), )); - + // Most localized aspects of the TextTheme text styles are the same for the default US local and + // for Chinese for Material3. The baselines for all text styles differ. final LocalizationTrackerState outerTracker = tester.state(find.byKey(const ValueKey<String>('outer'), skipOffstage: false)); - expect(outerTracker.bodySmallFontSize, 12.0); + expect(outerTracker.textBaseline, TextBaseline.alphabetic); final LocalizationTrackerState innerTracker = tester.state(find.byKey(const ValueKey<String>('inner'), skipOffstage: false)); - expect(innerTracker.bodySmallFontSize, 13.0); + expect(innerTracker.textBaseline, TextBaseline.ideographic); }); testWidgets('Localizations is compatible with ChangeNotifier.dispose() called during didChangeDependencies', (WidgetTester tester) async { @@ -92,11 +94,11 @@ class LocalizationTracker extends StatefulWidget { } class LocalizationTrackerState extends State<LocalizationTracker> { - late double bodySmallFontSize; + late TextBaseline textBaseline; @override Widget build(BuildContext context) { - bodySmallFontSize = Theme.of(context).textTheme.bodySmall!.fontSize!; + textBaseline = Theme.of(context).textTheme.bodySmall!.textBaseline!; return Container(); } } diff --git a/packages/flutter_localizations/test/material/date_picker_test.dart b/packages/flutter_localizations/test/material/date_picker_test.dart index 4d7984465c31f..bfae7a2dba4a5 100644 --- a/packages/flutter_localizations/test/material/date_picker_test.dart +++ b/packages/flutter_localizations/test/material/date_picker_test.dart @@ -93,79 +93,123 @@ void main() { }); testWidgets('locale parameter overrides ambient locale', (WidgetTester tester) async { - await tester.pumpWidget(MaterialApp( - locale: const Locale('en', 'US'), - supportedLocales: const <Locale>[ - Locale('en', 'US'), - Locale('fr', 'CA'), - ], - localizationsDelegates: GlobalMaterialLocalizations.delegates, - home: Material( - child: Builder( - builder: (BuildContext context) { - return TextButton( - onPressed: () async { - await showDatePicker( - context: context, - initialDate: initialDate, - firstDate: firstDate, - lastDate: lastDate, - locale: const Locale('fr', 'CA'), - ); - }, - child: const Text('X'), - ); - }, + Widget buildFrame(bool useMaterial3) { + return MaterialApp( + theme: ThemeData(useMaterial3: useMaterial3), + locale: const Locale('en', 'US'), + supportedLocales: const <Locale>[ + Locale('en', 'US'), + Locale('fr', 'CA'), + ], + localizationsDelegates: GlobalMaterialLocalizations.delegates, + home: Material( + child: Builder( + builder: (BuildContext context) { + return TextButton( + onPressed: () async { + await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + locale: const Locale('fr', 'CA'), + ); + }, + child: const Text('X'), + ); + }, + ), ), - ), - )); + ); + } + + Element getPicker() => tester.element(find.byType(CalendarDatePicker)); + await tester.pumpWidget(buildFrame(true)); await tester.tap(find.text('X')); - await tester.pumpAndSettle(const Duration(seconds: 1)); + await tester.pumpAndSettle(); - final Element picker = tester.element(find.byType(CalendarDatePicker)); expect( - Localizations.localeOf(picker), + Localizations.localeOf(getPicker()), const Locale('fr', 'CA'), ); + expect( + Directionality.of(getPicker()), + TextDirection.ltr, + ); + await tester.tap(find.text('Annuler')); + + // The tests below are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. + + await tester.pumpWidget(buildFrame(false)); + await tester.tap(find.text('X')); + await tester.pumpAndSettle(); + + expect( + Localizations.localeOf(getPicker()), + const Locale('fr', 'CA'), + ); expect( - Directionality.of(picker), + Directionality.of(getPicker()), TextDirection.ltr, ); await tester.tap(find.text('ANNULER')); + }); testWidgets('textDirection parameter overrides ambient textDirection', (WidgetTester tester) async { - await tester.pumpWidget(MaterialApp( - locale: const Locale('en', 'US'), - home: Material( - child: Builder( - builder: (BuildContext context) { - return TextButton( - onPressed: () async { - await showDatePicker( - context: context, - initialDate: initialDate, - firstDate: firstDate, - lastDate: lastDate, - textDirection: TextDirection.rtl, - ); - }, - child: const Text('X'), - ); - }, + Widget buildFrame(bool useMaterial3) { + return MaterialApp( + theme: ThemeData(useMaterial3: useMaterial3), + locale: const Locale('en', 'US'), + home: Material( + child: Builder( + builder: (BuildContext context) { + return TextButton( + onPressed: () async { + await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + textDirection: TextDirection.rtl, + ); + }, + child: const Text('X'), + ); + }, + ), ), - ), - )); + ); + } + + Element getPicker() => tester.element(find.byType(CalendarDatePicker)); + await tester.pumpWidget(buildFrame(true)); await tester.tap(find.text('X')); - await tester.pumpAndSettle(const Duration(seconds: 1)); + await tester.pumpAndSettle(); - final Element picker = tester.element(find.byType(CalendarDatePicker)); expect( - Directionality.of(picker), + Directionality.of(getPicker()), + TextDirection.rtl, + ); + + await tester.tap(find.text('Cancel')); + + // The tests below are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. + + await tester.pumpWidget(buildFrame(false)); + await tester.tap(find.text('X')); + await tester.pumpAndSettle(); + + expect( + Directionality.of(getPicker()), TextDirection.rtl, ); @@ -173,45 +217,70 @@ void main() { }); testWidgets('textDirection parameter takes precedence over locale parameter', (WidgetTester tester) async { - await tester.pumpWidget(MaterialApp( - locale: const Locale('en', 'US'), - supportedLocales: const <Locale>[ - Locale('en', 'US'), - Locale('fr', 'CA'), - ], - localizationsDelegates: GlobalMaterialLocalizations.delegates, - home: Material( - child: Builder( - builder: (BuildContext context) { - return TextButton( - onPressed: () async { - await showDatePicker( - context: context, - initialDate: initialDate, - firstDate: firstDate, - lastDate: lastDate, - locale: const Locale('fr', 'CA'), - textDirection: TextDirection.rtl, - ); - }, - child: const Text('X'), - ); - }, + Widget buildFrame(bool useMaterial3) { + return MaterialApp( + theme: ThemeData(useMaterial3: useMaterial3), + locale: const Locale('en', 'US'), + supportedLocales: const <Locale>[ + Locale('en', 'US'), + Locale('fr', 'CA'), + ], + localizationsDelegates: GlobalMaterialLocalizations.delegates, + home: Material( + child: Builder( + builder: (BuildContext context) { + return TextButton( + onPressed: () async { + await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: firstDate, + lastDate: lastDate, + locale: const Locale('fr', 'CA'), + textDirection: TextDirection.rtl, + ); + }, + child: const Text('X'), + ); + }, + ), ), - ), - )); + ); + } + + Element getPicker() => tester.element(find.byType(CalendarDatePicker)); + await tester.pumpWidget(buildFrame(true)); await tester.tap(find.text('X')); await tester.pumpAndSettle(const Duration(seconds: 1)); - final Element picker = tester.element(find.byType(CalendarDatePicker)); expect( - Localizations.localeOf(picker), + Localizations.localeOf(getPicker()), + const Locale('fr', 'CA'), + ); + + expect( + Directionality.of(getPicker()), + TextDirection.rtl, + ); + + await tester.tap(find.text('Annuler')); + + // The tests below are only relevant for Material 2. Once Material 2 + // support is deprecated and the APIs are removed, these tests + // can be deleted. + + await tester.pumpWidget(buildFrame(false)); + await tester.tap(find.text('X')); + await tester.pumpAndSettle(); + + expect( + Localizations.localeOf(getPicker()), const Locale('fr', 'CA'), ); expect( - Directionality.of(picker), + Directionality.of(getPicker()), TextDirection.rtl, ); diff --git a/packages/flutter_localizations/test/material/time_picker_test.dart b/packages/flutter_localizations/test/material/time_picker_test.dart index 3a07db3fbd8bc..b23019d71af12 100644 --- a/packages/flutter_localizations/test/material/time_picker_test.dart +++ b/packages/flutter_localizations/test/material/time_picker_test.dart @@ -220,7 +220,7 @@ void main() { } }); - testWidgets('uses single-ring 24-hour dial for all formats', (WidgetTester tester) async { + testWidgets('Material2 uses single-ring 24-hour dial for all locales', (WidgetTester tester) async { const List<Locale> locales = <Locale>[ Locale('en', 'US'), // h Locale('en', 'GB'), // HH @@ -228,11 +228,11 @@ void main() { ]; for (final Locale locale in locales) { // Tap along the segment stretching from the center to the edge at - // 12:00 AM position. Because there's only one ring, no matter where you - // tap the time will be the same. + // 12:00 AM position. Because there's only one ring, in the M2 + // DatePicker no matter where you tap the time will be the same. for (int i = 1; i < 10; i++) { TimeOfDay? result; - final Offset center = await startPicker(tester, (TimeOfDay? time) { result = time; }, locale: locale); + final Offset center = await startPicker(tester, (TimeOfDay? time) { result = time; }, locale: locale, useMaterial3: false); final Size size = tester.getSize(find.byKey(const Key('time-picker-dial'))); final double dy = (size.height / 2.0 / 10) * i; await tester.tapAt(Offset(center.dx, center.dy - dy)); @@ -242,34 +242,57 @@ void main() { } }); + testWidgets('Material3 uses a double-ring 24-hour dial for 24 hour locales', (WidgetTester tester) async { + Future<void> testLocale(Locale locale, int startFactor, int endFactor, TimeOfDay expectedTime) async { + // For locales that display 24 hour time, factors 1-5 put the tap on the + // inner ring's "12" (the inner ring goes from 12-23). Otherwise the offset + // should land on the outer ring's "00". + for (int factor = startFactor; factor < endFactor; factor += 1) { + TimeOfDay? result; + final Offset center = await startPicker(tester, (TimeOfDay? time) { result = time; }, locale: locale, useMaterial3: true); + final Size size = tester.getSize(find.byKey(const Key('time-picker-dial'))); + final double dy = (size.height / 2.0 / 10) * factor; + await tester.tapAt(Offset(center.dx, center.dy - dy)); + await finishPicker(tester); + expect(result, equals(expectedTime), reason: 'Failed for locale=$locale with factor=$factor'); + } + } + + await testLocale(const Locale('en', 'US'), 1, 10, const TimeOfDay(hour: 0, minute: 0)); // 12 hour + await testLocale(const Locale('en', 'ES'), 1, 10, const TimeOfDay(hour: 0, minute: 0)); // 12 hour + await testLocale(const Locale('en', 'GB'), 1, 5, const TimeOfDay(hour: 12, minute: 0)); // 24 hour, inner ring + await testLocale(const Locale('en', 'GB'), 6, 10, const TimeOfDay(hour: 0, minute: 0)); // 24 hour, outer ring + }); + const List<String> labels12To11 = <String>['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']; - const List<String> labels00To22TwoDigit = <String>['00', '02', '04', '06', '08', '10', '12', '14', '16', '18', '20', '22']; + const List<String> labels00To22TwoDigit = <String>['00', '02', '04', '06', '08', '10', '12', '14', '16', '18', '20', '22']; // Material 2 + const List<String> labels00To23TwoDigit = <String>[ // Material 3 + '00', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23']; - Future<void> mediaQueryBoilerplate(WidgetTester tester, bool alwaysUse24HourFormat) async { + Future<void> mediaQueryBoilerplate(WidgetTester tester, {required bool alwaysUse24HourFormat, bool? useMaterial3}) async { await tester.pumpWidget( - Localizations( - locale: const Locale('en', 'US'), - delegates: const <LocalizationsDelegate<dynamic>>[ - GlobalMaterialLocalizations.delegate, - DefaultWidgetsLocalizations.delegate, - ], - child: MediaQuery( - data: MediaQueryData(alwaysUse24HourFormat: alwaysUse24HourFormat), - child: Material( - child: Directionality( - textDirection: TextDirection.ltr, - child: Navigator( - onGenerateRoute: (RouteSettings settings) { - return MaterialPageRoute<void>(builder: (BuildContext context) { - return TextButton( - onPressed: () { - showTimePicker(context: context, initialTime: const TimeOfDay(hour: 7, minute: 0)); - }, - child: const Text('X'), - ); - }); - }, - ), + MaterialApp( + theme: ThemeData(useMaterial3: useMaterial3), + builder: (BuildContext context, Widget? child) { + return MediaQuery( + data: MediaQueryData(alwaysUse24HourFormat: alwaysUse24HourFormat), + child: child!, + ); + }, + home: Material( + child: Directionality( + textDirection: TextDirection.ltr, + child: Navigator( + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute<void>(builder: (BuildContext context) { + return TextButton( + onPressed: () { + showTimePicker(context: context, initialTime: const TimeOfDay(hour: 7, minute: 0)); + }, + child: const Text('X'), + ); + }); + }, ), ), ), @@ -281,7 +304,7 @@ void main() { } testWidgets('respects MediaQueryData.alwaysUse24HourFormat == false', (WidgetTester tester) async { - await mediaQueryBoilerplate(tester, false); + await mediaQueryBoilerplate(tester, alwaysUse24HourFormat: false); final CustomPaint dialPaint = tester.widget(find.byKey(const ValueKey<String>('time-picker-dial'))); final dynamic dialPainter = dialPaint.painter; @@ -302,8 +325,30 @@ void main() { ); }); - testWidgets('respects MediaQueryData.alwaysUse24HourFormat == true', (WidgetTester tester) async { - await mediaQueryBoilerplate(tester, true); + testWidgets('Material3 respects MediaQueryData.alwaysUse24HourFormat == true', (WidgetTester tester) async { + await mediaQueryBoilerplate(tester, alwaysUse24HourFormat: true, useMaterial3: true); + + final CustomPaint dialPaint = tester.widget(find.byKey(const ValueKey<String>('time-picker-dial'))); + final dynamic dialPainter = dialPaint.painter; + // ignore: avoid_dynamic_calls + final List<dynamic> primaryLabels = dialPainter.primaryLabels as List<dynamic>; + expect( + // ignore: avoid_dynamic_calls + primaryLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!), + labels00To23TwoDigit, + ); + + // ignore: avoid_dynamic_calls + final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>; + expect( + // ignore: avoid_dynamic_calls + selectedLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!), + labels00To23TwoDigit, + ); + }); + + testWidgets('Material2 respects MediaQueryData.alwaysUse24HourFormat == true', (WidgetTester tester) async { + await mediaQueryBoilerplate(tester, alwaysUse24HourFormat: true, useMaterial3: false); final CustomPaint dialPaint = tester.widget(find.byKey(const ValueKey<String>('time-picker-dial'))); final dynamic dialPainter = dialPaint.painter; @@ -330,15 +375,18 @@ class _TimePickerLauncher extends StatelessWidget { this.onChanged, required this.locale, this.entryMode = TimePickerEntryMode.dial, + this.useMaterial3, }); final ValueChanged<TimeOfDay?>? onChanged; final Locale locale; final TimePickerEntryMode entryMode; + final bool? useMaterial3; @override Widget build(BuildContext context) { return MaterialApp( + theme: ThemeData(useMaterial3: useMaterial3), locale: locale, supportedLocales: <Locale>[locale], localizationsDelegates: GlobalMaterialLocalizations.delegates, @@ -368,11 +416,13 @@ Future<Offset> startPicker( WidgetTester tester, ValueChanged<TimeOfDay?> onChanged, { Locale locale = const Locale('en', 'US'), + bool? useMaterial3, }) async { await tester.pumpWidget( _TimePickerLauncher( onChanged: onChanged, locale: locale, + useMaterial3: useMaterial3, ), ); await tester.tap(find.text('X')); diff --git a/packages/flutter_localizations/test/text_test.dart b/packages/flutter_localizations/test/text_test.dart index b0501f048bac6..310cdf0ea2d86 100644 --- a/packages/flutter_localizations/test/text_test.dart +++ b/packages/flutter_localizations/test/text_test.dart @@ -17,6 +17,7 @@ void main() { final Key targetKey = UniqueKey(); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: true), routes: <String, WidgetBuilder>{ '/next': (BuildContext context) { return const Text('Next'); @@ -75,20 +76,20 @@ void main() { Offset bottomLeft = tester.getBottomLeft(find.text('hello, world')); Offset bottomRight = tester.getBottomRight(find.text('hello, world')); - expect(topLeft, const Offset(392.0, 299.5)); - expect(topRight, const Offset(596.0, 299.5)); - expect(bottomLeft, const Offset(392.0, 316.5)); - expect(bottomRight, const Offset(596.0, 316.5)); + expect(topLeft, const Offset(392.0, 298.0)); + expect(topRight, const Offset(562.0, 298.0)); + expect(bottomLeft, const Offset(392.0, 318.0)); + expect(bottomRight, const Offset(562.0, 318.0)); topLeft = tester.getTopLeft(find.text('你好,世界')); topRight = tester.getTopRight(find.text('你好,世界')); bottomLeft = tester.getBottomLeft(find.text('你好,世界')); bottomRight = tester.getBottomRight(find.text('你好,世界')); - expect(topLeft, const Offset(392.0, 347.5)); - expect(topRight, const Offset(477.0, 347.5)); - expect(bottomLeft, const Offset(392.0, 364.5)); - expect(bottomRight, const Offset(477.0, 364.5)); + expect(topLeft, const Offset(392.0, 346.0)); + expect(topRight, const Offset(463.0, 346.0)); + expect(bottomLeft, const Offset(392.0, 366.0)); + expect(bottomRight, const Offset(463.0, 366.0)); }); testWidgets('Text baseline with EN locale', (WidgetTester tester) async { @@ -101,6 +102,7 @@ void main() { final Key targetKey = UniqueKey(); await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: true), routes: <String, WidgetBuilder>{ '/next': (BuildContext context) { return const Text('Next'); @@ -159,19 +161,19 @@ void main() { Offset bottomLeft = tester.getBottomLeft(find.text('hello, world')); Offset bottomRight = tester.getBottomRight(find.text('hello, world')); - expect(topLeft, const Offset(392.0, 300.0)); - expect(topRight, const Offset(584.0, 300.0)); - expect(bottomLeft, const Offset(392.0, 316)); - expect(bottomRight, const Offset(584.0, 316)); + expect(topLeft, const Offset(392.0, 298.0)); + expect(topRight, const Offset(562.0, 298.0)); + expect(bottomLeft, const Offset(392.0, 318.0)); + expect(bottomRight, const Offset(562.0, 318.0)); topLeft = tester.getTopLeft(find.text('你好,世界')); topRight = tester.getTopRight(find.text('你好,世界')); bottomLeft = tester.getBottomLeft(find.text('你好,世界')); bottomRight = tester.getBottomRight(find.text('你好,世界')); - expect(topLeft, const Offset(392.0, 348.0)); - expect(topRight, const Offset(472.0, 348.0)); - expect(bottomLeft, const Offset(392.0, 364.0)); - expect(bottomRight, const Offset(472.0, 364.0)); + expect(topLeft, const Offset(392.0, 346.0)); + expect(topRight, const Offset(463.0, 346.0)); + expect(bottomLeft, const Offset(392.0, 366.0)); + expect(bottomRight, const Offset(463.0, 366.0)); }); } diff --git a/packages/flutter_test/lib/src/_matchers_io.dart b/packages/flutter_test/lib/src/_matchers_io.dart index 9c56372d60a10..74a3d497609b5 100644 --- a/packages/flutter_test/lib/src/_matchers_io.dart +++ b/packages/flutter_test/lib/src/_matchers_io.dart @@ -24,7 +24,7 @@ Future<ui.Image> captureImage(Element element) { assert(element.renderObject != null); RenderObject renderObject = element.renderObject!; while (!renderObject.isRepaintBoundary) { - renderObject = renderObject.parent! as RenderObject; + renderObject = renderObject.parent!; } assert(!renderObject.debugNeedsPaint); final OffsetLayer layer = renderObject.debugLayer! as OffsetLayer; @@ -124,7 +124,7 @@ class MatchesGoldenFile extends AsyncMatcher { image.dispose(); } } - }, additionalTime: const Duration(minutes: 1)); + }); } @override diff --git a/packages/flutter_test/lib/src/_matchers_web.dart b/packages/flutter_test/lib/src/_matchers_web.dart index 71a78c3427065..749b57164eda3 100644 --- a/packages/flutter_test/lib/src/_matchers_web.dart +++ b/packages/flutter_test/lib/src/_matchers_web.dart @@ -77,7 +77,7 @@ class MatchesGoldenFile extends AsyncMatcher { } on TestFailure catch (ex) { return ex.message; } - }, additionalTime: const Duration(seconds: 22)); + }); _renderElement(view, _findRepaintBoundary(e)); return result; } @@ -93,7 +93,7 @@ RenderObject _findRepaintBoundary(Element element) { assert(element.renderObject != null); RenderObject renderObject = element.renderObject!; while (!renderObject.isRepaintBoundary) { - renderObject = renderObject.parent! as RenderObject; + renderObject = renderObject.parent!; } return renderObject; } diff --git a/packages/flutter_test/lib/src/animation_sheet.dart b/packages/flutter_test/lib/src/animation_sheet.dart index 9f5a64e9186e7..22973287e9fee 100644 --- a/packages/flutter_test/lib/src/animation_sheet.dart +++ b/packages/flutter_test/lib/src/animation_sheet.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:math' as math; import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; @@ -136,7 +135,7 @@ class AnimationSheetBuilder { /// The frame is only recorded if the `recording` argument is true, or during /// a procedure that is wrapped within [recording]. In either case, the /// painted result of each frame will be stored and later available for - /// [display]. If neither condition is met, the frames are not recorded, which + /// [collate]. If neither condition is met, the frames are not recorded, which /// is useful during setup phases. /// /// The `child` must not be null. @@ -158,113 +157,6 @@ class AnimationSheetBuilder { ); } - /// Constructs a widget that renders the recorded frames in an animation sheet. - /// - /// The resulting widget takes as much space as its parent allows, which is - /// usually the screen size. It is then filled with all recorded frames, each - /// having a size specified by [frameSize], chronologically from top-left to - /// bottom-right in a row-major order. - /// - /// This widget does not check whether its size fits all recorded frames. - /// Having too many frames can cause overflow errors, while having too few can - /// waste the size of golden files. Therefore you should usually adjust the - /// viewport size to [sheetSize] before calling this method. - /// - /// The `key` is applied to the root widget. - /// - /// This method can only be called if at least one frame has been recorded. - /// - /// The [display] is the legacy way of acquiring the output for comparison. - /// It is not recommended because it requires more boilerplate, and produces - /// a much large image than necessary: each pixel is rendered in 3x3 pixels - /// without higher definition. Use [collate] instead. - /// - /// Using this way includes the following steps: - /// - /// * Create an instance of this class. - /// * Pump frames that render the target widget wrapped in [record]. Every frame - /// that has `recording` being true will be recorded. - /// * Adjust the size of the test viewport to the [sheetSize] (see the - /// documentation of [sheetSize] for more information). - /// * Pump a frame that renders [display], which shows all recorded frames in an - /// animation sheet, and can be matched against the golden test. - /// - /// {@tool snippet} - /// The following example shows how to record an animation sheet of an [InkWell] - /// being pressed then released. - /// - /// ```dart - /// testWidgets('Inkwell animation sheet', (WidgetTester tester) async { - /// // Create instance - /// final AnimationSheetBuilder animationSheet = AnimationSheetBuilder(frameSize: const Size(48, 24)); - /// - /// final Widget target = Material( - /// child: Directionality( - /// textDirection: TextDirection.ltr, - /// child: InkWell( - /// splashColor: Colors.blue, - /// onTap: () {}, - /// ), - /// ), - /// ); - /// - /// // Optional: setup before recording (`recording` is false) - /// await tester.pumpWidget(animationSheet.record( - /// target, - /// recording: false, - /// )); - /// - /// final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(InkWell))); - /// - /// // Start recording (`recording` is true) - /// await tester.pumpFrames(animationSheet.record( - /// target, - /// recording: true, - /// ), const Duration(seconds: 1)); - /// - /// await gesture.up(); - /// - /// await tester.pumpFrames(animationSheet.record( - /// target, - /// recording: true, - /// ), const Duration(seconds: 1)); - /// - /// // Adjust view port size - /// tester.binding.setSurfaceSize(animationSheet.sheetSize()); - /// - /// // Display - /// final Widget display = await animationSheet.display(); - /// await tester.pumpWidget(display); - /// - /// // Compare against golden file - /// await expectLater( - /// find.byWidget(display), - /// matchesGoldenFile('inkwell.press.animation.png'), - /// ); - /// }, skip: isBrowser); // Animation sheet does not support browser https://github.com/flutter/flutter/issues/56001 - /// ``` - /// {@end-tool} - @Deprecated( - 'Use AnimationSheetBuilder.collate instead. ' - 'This feature was deprecated after v2.3.0-13.0.pre.', - ) - Future<Widget> display({Key? key}) async { - assert(_recordedFrames.isNotEmpty); - final List<ui.Image> frames = await _frames; - return _CellSheet( - key: key, - cellSize: frameSize, - children: frames.map((ui.Image image) => RawImage( - image: image.clone(), - width: frameSize.width, - height: frameSize.height, - // Disable quality enhancement because the point of this class is to - // precisely record what the widget looks like. - filterQuality: ui.FilterQuality.none, - )).toList(), - ); - } - /// Returns an result image by putting all frames together in a table. /// /// This method returns a table of captured frames, `cellsPerRow` images @@ -277,39 +169,6 @@ class AnimationSheetBuilder { 'No frames are collected. Have you forgot to set `recording` to true?'); return _collateFrames(frames, frameSize, cellsPerRow); } - - /// Returns the smallest size that can contain all recorded frames. - /// - /// This is used to adjust the viewport during unit tests, i.e. the size of - /// virtual screen. Having too many frames recorded than the default viewport - /// size can contain will lead to overflow errors, while having too few frames - /// means the golden file might be larger than necessary. - /// - /// The [sheetSize] returns the smallest possible size by placing the - /// recorded frames, each of which has a size specified by [frameSize], in a - /// row-major grid with a maximum width specified by `maxWidth`, and returns - /// the size of that grid. - /// - /// Setting the viewport size during a widget test usually involves - /// [TestWidgetsFlutterBinding.setSurfaceSize] and [WidgetTester.binding]. - /// - /// The `maxWidth` defaults to the width of the default viewport, 800.0. - /// - /// This method can only be called if at least one frame has been recorded. - @Deprecated( - 'The `sheetSize` is only useful for `display`, which should be migrated to `collate`. ' - 'This feature was deprecated after v2.3.0-13.0.pre.', - ) - Size sheetSize({double maxWidth = _kDefaultTestViewportWidth}) { - assert(_recordedFrames.isNotEmpty); - final int cellsPerRow = (maxWidth / frameSize.width).floor(); - final int rowNum = (_recordedFrames.length / cellsPerRow).ceil(); - final double width = math.min(cellsPerRow, _recordedFrames.length) * frameSize.width; - return Size(width, frameSize.height * rowNum); - } - - // The width of _kDefaultTestViewportSize in [TestViewConfiguration]. - static const double _kDefaultTestViewportWidth = 800.0; } typedef _RecordedHandler = void Function(Future<ui.Image> image); @@ -446,45 +305,6 @@ Future<ui.Image> _collateFrames(List<ui.Image> frames, Size frameSize, int cells return image; } -// Layout children in a grid of fixed-sized cells. -// -// The sheet fills up as much space as the parent allows. The cells are -// positioned from top left to bottom right in a row-major order. -class _CellSheet extends StatelessWidget { - _CellSheet({ - super.key, - required this.cellSize, - required this.children, - }) : assert(children.isNotEmpty); - - final Size cellSize; - final List<Widget> children; - - @override - Widget build(BuildContext context) { - return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) { - final double rowWidth = constraints.biggest.width; - final int cellsPerRow = (rowWidth / cellSize.width).floor(); - final List<Widget> rows = <Widget>[]; - for (int rowStart = 0; rowStart < children.length; rowStart += cellsPerRow) { - final Iterable<Widget> rowTargets = children.sublist(rowStart, math.min(rowStart + cellsPerRow, children.length)); - rows.add(Row( - textDirection: TextDirection.ltr, - children: rowTargets.map((Widget target) => SizedBox.fromSize( - size: cellSize, - child: target, - )).toList(), - )); - } - return Column( - textDirection: TextDirection.ltr, - crossAxisAlignment: CrossAxisAlignment.start, - children: rows, - ); - }); - } -} - class _RenderRootableRepaintBoundary extends RenderRepaintBoundary { // Like [toImage], but captures an image of all layers (composited by // RenderView and its children) clipped by the region of this object. diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index cd226a047e92e..23caf1f33b3d3 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -310,19 +310,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase @protected bool get registerTestTextInput => true; - /// This method has no effect. - /// - /// This method was previously used to change the timeout of the test. However, - /// in practice having short timeouts was found to be nothing but trouble, - /// primarily being a cause flakes rather than helping debug tests. - /// - /// For this reason, this method has been deprecated. - @Deprecated( - 'This method has no effect. ' - 'This feature was deprecated after v2.6.0-1.0.pre.' - ) - void addTime(Duration duration) { } - /// Delay for `duration` of time. /// /// In the automated test environment ([AutomatedTestWidgetsFlutterBinding], @@ -379,7 +366,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase @override void initInstances() { - // This is intialized here because it's needed for the `super.initInstances` + // This is initialized here because it's needed for the `super.initInstances` // call. It can't be handled as a ctor initializer because it's dependent // on `platformDispatcher`. It can't be handled in the ctor itself because // the base class ctor is called first and calls `initInstances`. @@ -465,17 +452,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase /// are required to wait for the returned future to complete before calling /// this method again. Attempts to do otherwise will result in a /// [TestFailure] error being thrown. - /// - /// The `additionalTime` argument was previously used with - /// [AutomatedTestWidgetsFlutterBinding.addTime] but now has no effect. - Future<T?> runAsync<T>( - Future<T> Function() callback, { - @Deprecated( - 'This parameter has no effect. ' - 'This feature was deprecated after v2.6.0-1.0.pre.' - ) - Duration additionalTime = const Duration(milliseconds: 1000), - }); + Future<T?> runAsync<T>(Future<T> Function() callback); /// Artificially calls dispatchLocalesChanged on the Widget binding, /// then flushes microtasks. @@ -499,6 +476,17 @@ abstract class TestWidgetsFlutterBinding extends BindingBase }); } + @override + Future<ui.AppExitResponse> exitApplication(ui.AppExitType exitType, [int exitCode = 0]) async { + switch (exitType) { + case ui.AppExitType.cancelable: + // The test framework shouldn't actually exit when requested. + return ui.AppExitResponse.cancel; + case ui.AppExitType.required: + throw FlutterError('Unexpected application exit request while running test'); + } + } + /// Re-attempts the initialization of the lifecycle state after providing /// test values in [TestWindow.initialLifecycleStateTestValue]. void readTestInitialLifecycleStateFromNativeWindow() { @@ -563,17 +551,24 @@ abstract class TestWidgetsFlutterBinding extends BindingBase }); } - /// Convert the given point from the global coordinate space to the local - /// one. + /// Convert the given point from the global coordinate space of the provided + /// [RenderView] to its local one. + /// + /// This method operates in logical pixels for both coordinate spaces. It does + /// not apply the device pixel ratio (used to translate to/from physical + /// pixels). /// /// For definitions for coordinate spaces, see [TestWidgetsFlutterBinding]. - Offset globalToLocal(Offset point) => point; + Offset globalToLocal(Offset point, RenderView view) => point; /// Convert the given point from the local coordinate space to the global - /// one. + /// coordinate space of the [RenderView]. + /// + /// This method operates in logical pixels for both coordinate spaces. It does + /// not apply the device pixel ratio to translate to physical pixels. /// /// For definitions for coordinate spaces, see [TestWidgetsFlutterBinding]. - Offset localToGlobal(Offset point) => point; + Offset localToGlobal(Offset point, RenderView view) => point; /// The source of the current pointer event. /// @@ -761,13 +756,6 @@ abstract class TestWidgetsFlutterBinding extends BindingBase Future<void> Function() testBody, VoidCallback invariantTester, { String description = '', - @Deprecated( - 'This parameter has no effect. Use the `timeout` parameter on `testWidgets` instead. ' - 'This feature was deprecated after v2.6.0-1.0.pre.' - ) - // TODO(pdblasi-google): Do not remove until https://github.com/flutter/flutter/issues/124346 - // is complete, as this removal will cascade into `integration_test` - Duration? timeout, }); /// This is called during test execution before and after the body has been @@ -929,8 +917,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase try { treeDump = rootElement?.toDiagnosticsNode() ?? DiagnosticsNode.message('<no tree>'); // We try to stringify the tree dump here (though we immediately discard the result) because - // we want to make sure that if it can't be serialised, we replace it with a message that - // says the tree could not be serialised. Otherwise, the real exception might get obscured + // we want to make sure that if it can't be serialized, we replace it with a message that + // says the tree could not be serialized. Otherwise, the real exception might get obscured // by side-effects of the underlying issues causing the tree dumping code to flail. treeDump.toStringDeep(); } catch (exception) { @@ -1150,6 +1138,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase keyEventManager.clearState(); // ignore: invalid_use_of_visible_for_testing_member RendererBinding.instance.initMouseTracker(); + // ignore: invalid_use_of_visible_for_testing_member + ServicesBinding.instance.resetLifecycleState(); } } @@ -1235,7 +1225,6 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { } _phase = newPhase; if (hasScheduledFrame) { - addTime(const Duration(milliseconds: 500)); _currentFakeAsync!.flushMicrotasks(); handleBeginFrame(Duration( milliseconds: _clock!.now().millisecondsSinceEpoch, @@ -1249,10 +1238,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { } @override - Future<T?> runAsync<T>( - Future<T> Function() callback, { - Duration additionalTime = const Duration(milliseconds: 1000), - }) { + Future<T?> runAsync<T>(Future<T> Function() callback) { assert(() { if (_pendingAsyncTasks == null) { return true; @@ -1279,8 +1265,6 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { ), ); - addTime(additionalTime); - return realAsyncZone.run<Future<T?>>(() { final Completer<T?> result = Completer<T?>(); _pendingAsyncTasks = Completer<void>(); @@ -1434,13 +1418,6 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { Future<void> Function() testBody, VoidCallback invariantTester, { String description = '', - @Deprecated( - 'This parameter has no effect. Use the `timeout` parameter on `testWidgets` instead. ' - 'This feature was deprecated after v2.6.0-1.0.pre.' - ) - // TODO(pdblasi-google): Do not remove until https://github.com/flutter/flutter/issues/124346 - // is complete, as this removal will cascade into `integration_test` - Duration? timeout, }) { assert(!inTest); assert(_currentFakeAsync == null); @@ -1652,6 +1629,8 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { void initInstances() { super.initInstances(); _instance = this; + + RenderView.debugAddPaintCallback(_handleRenderViewPaint); } /// The current [LiveTestWidgetsFlutterBinding], if one has been created. @@ -1780,21 +1759,68 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { } } - @override - void initRenderView() { - renderView = _LiveTestRenderView( - configuration: createViewConfiguration(), - onNeedPaint: _handleViewNeedsPaint, - view: platformDispatcher.implicitView!, - ); - renderView.prepareInitialFrame(); + void _markViewNeedsPaint() { + _viewNeedsPaint = true; + renderView.markNeedsPaint(); } - _LiveTestRenderView get _liveTestRenderView => super.renderView as _LiveTestRenderView; + TextPainter? _label; + static const TextStyle _labelStyle = TextStyle( + fontFamily: 'sans-serif', + fontSize: 10.0, + ); - void _handleViewNeedsPaint() { - _viewNeedsPaint = true; - renderView.markNeedsPaint(); + void _setDescription(String value) { + if (value.isEmpty) { + _label = null; + return; + } + // TODO(ianh): Figure out if the test name is actually RTL. + _label ??= TextPainter(textAlign: TextAlign.left, textDirection: TextDirection.ltr); + _label!.text = TextSpan(text: value, style: _labelStyle); + _label!.layout(); + _markViewNeedsPaint(); + } + + final Map<int, _LiveTestPointerRecord> _pointerIdToPointerRecord = <int, _LiveTestPointerRecord>{}; + + void _handleRenderViewPaint(PaintingContext context, Offset offset, RenderView renderView) { + assert(offset == Offset.zero); + + if (_pointerIdToPointerRecord.isNotEmpty) { + final double radius = renderView.configuration.size.shortestSide * 0.05; + final Path path = Path() + ..addOval(Rect.fromCircle(center: Offset.zero, radius: radius)) + ..moveTo(0.0, -radius * 2.0) + ..lineTo(0.0, radius * 2.0) + ..moveTo(-radius * 2.0, 0.0) + ..lineTo(radius * 2.0, 0.0); + final Canvas canvas = context.canvas; + final Paint paint = Paint() + ..strokeWidth = radius / 10.0 + ..style = PaintingStyle.stroke; + bool dirty = false; + for (final _LiveTestPointerRecord record in _pointerIdToPointerRecord.values) { + paint.color = record.color.withOpacity(record.decay < 0 ? (record.decay / (_kPointerDecay - 1)) : 1.0); + canvas.drawPath(path.shift(record.position), paint); + if (record.decay < 0) { + dirty = true; + } + record.decay += 1; + } + _pointerIdToPointerRecord + .keys + .where((int pointer) => _pointerIdToPointerRecord[pointer]!.decay == 0) + .toList() + .forEach(_pointerIdToPointerRecord.remove); + if (dirty) { + scheduleMicrotask(() { + _markViewNeedsPaint(); + }); + } + } + + _label?.paint(context.canvas, offset - const Offset(0.0, 10.0)); } /// An object to which real device events should be routed. @@ -1820,19 +1846,19 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { void handlePointerEvent(PointerEvent event) { switch (pointerEventSource) { case TestBindingEventSource.test: - final _LiveTestPointerRecord? record = _liveTestRenderView._pointers[event.pointer]; + final _LiveTestPointerRecord? record = _pointerIdToPointerRecord[event.pointer]; if (record != null) { record.position = event.position; if (!event.down) { record.decay = _kPointerDecay; } - _handleViewNeedsPaint(); + _markViewNeedsPaint(); } else if (event.down) { - _liveTestRenderView._pointers[event.pointer] = _LiveTestPointerRecord( + _pointerIdToPointerRecord[event.pointer] = _LiveTestPointerRecord( event.pointer, event.position, ); - _handleViewNeedsPaint(); + _markViewNeedsPaint(); } super.handlePointerEvent(event); case TestBindingEventSource.device: @@ -1844,7 +1870,7 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { // The pointer events received with this source has a global position // (see [handlePointerEventForSource]). Transform it to the local // coordinate space used by the testing widgets. - final PointerEvent localEvent = event.copyWith(position: globalToLocal(event.position)); + final PointerEvent localEvent = event.copyWith(position: globalToLocal(event.position, renderView)); withPointerEventSource(TestBindingEventSource.device, () => super.handlePointerEvent(localEvent) ); @@ -1896,10 +1922,7 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { } @override - Future<T?> runAsync<T>( - Future<T> Function() callback, { - Duration additionalTime = const Duration(milliseconds: 1000), - }) async { + Future<T?> runAsync<T>(Future<T> Function() callback) async { assert(() { if (!_runningAsyncTasks) { return true; @@ -1933,17 +1956,10 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { Future<void> Function() testBody, VoidCallback invariantTester, { String description = '', - @Deprecated( - 'This parameter has no effect. Use the `timeout` parameter on `testWidgets` instead. ' - 'This feature was deprecated after v2.6.0-1.0.pre.' - ) - // TODO(pdblasi-google): Do not remove until https://github.com/flutter/flutter/issues/124346 - // is complete, as this removal will cascade into `integration_test` - Duration? timeout, }) { assert(!inTest); _inTest = true; - _liveTestRenderView._setDescription(description); + _setDescription(description); return _runTest(testBody, invariantTester, description); } @@ -1979,24 +1995,42 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { } @override - Offset globalToLocal(Offset point) { - final Matrix4 transform = _liveTestRenderView.configuration.toHitTestMatrix(); + Offset globalToLocal(Offset point, RenderView view) { + // The method is expected to translate the given point expressed in logical + // pixels in the global coordinate space to the local coordinate space (also + // expressed in logical pixels). + // The inverted transform translates from the global coordinate space in + // physical pixels to the local coordinate space in logical pixels. + final Matrix4 transform = view.configuration.toMatrix(); final double det = transform.invert(); assert(det != 0.0); - final Offset result = MatrixUtils.transformPoint(transform, point); - return result; + // In order to use the transform, we need to translate the point first into + // the physical coordinate space by applying the device pixel ratio. + return MatrixUtils.transformPoint( + transform, + point * view.configuration.devicePixelRatio, + ); } @override - Offset localToGlobal(Offset point) { - final Matrix4 transform = _liveTestRenderView.configuration.toHitTestMatrix(); - return MatrixUtils.transformPoint(transform, point); + Offset localToGlobal(Offset point, RenderView view) { + // The method is expected to translate the given point expressed in logical + // pixels in the local coordinate space to the global coordinate space (also + // expressed in logical pixels). + // The transform translates from the local coordinate space in logical + // pixels to the global coordinate space in physical pixels. + final Matrix4 transform = view.configuration.toMatrix(); + final Offset pointInPhysicalPixels = MatrixUtils.transformPoint(transform, point); + // We need to apply the device pixel ratio to get back to logical pixels. + return pointInPhysicalPixels / view.configuration.devicePixelRatio; } } -/// A [ViewConfiguration] that pretends the display is of a particular size. The -/// size is in logical pixels. The resulting ViewConfiguration maps the given -/// size onto the actual display using the [BoxFit.contain] algorithm. +/// A [ViewConfiguration] that pretends the display is of a particular size (in +/// logical pixels). +/// +/// The resulting ViewConfiguration maps the given size onto the actual display +/// using the [BoxFit.contain] algorithm. class TestViewConfiguration extends ViewConfiguration { /// Deprecated. Will be removed in a future version of Flutter. /// @@ -2021,7 +2055,6 @@ class TestViewConfiguration extends ViewConfiguration { /// The [size] defaults to 800x600. TestViewConfiguration.fromView({required ui.FlutterView view, super.size = _kDefaultTestViewportSize}) : _paintMatrix = _getMatrix(size, view.devicePixelRatio, view), - _hitTestMatrix = _getMatrix(size, 1.0, view), super(devicePixelRatio: view.devicePixelRatio); static Matrix4 _getMatrix(Size size, double devicePixelRatio, ui.FlutterView window) { @@ -2049,21 +2082,10 @@ class TestViewConfiguration extends ViewConfiguration { } final Matrix4 _paintMatrix; - final Matrix4 _hitTestMatrix; @override Matrix4 toMatrix() => _paintMatrix.clone(); - /// Provides the transformation matrix that converts coordinates in the test - /// coordinate space to coordinates in logical pixels on the real display. - /// - /// This is essentially the same as [toMatrix] but ignoring the device pixel - /// ratio. - /// - /// This is useful because pointers are described in logical pixels, as - /// opposed to graphics which are expressed in physical pixels. - Matrix4 toHitTestMatrix() => _hitTestMatrix.clone(); - @override String toString() => 'TestViewConfiguration'; } @@ -2081,75 +2103,3 @@ class _LiveTestPointerRecord { Offset position; int decay; // >0 means down, <0 means up, increases by one each time, removed at 0 } - -class _LiveTestRenderView extends RenderView { - _LiveTestRenderView({ - required super.configuration, - required this.onNeedPaint, - required super.view, - }); - - @override - TestViewConfiguration get configuration => super.configuration as TestViewConfiguration; - @override - set configuration(covariant TestViewConfiguration value) { super.configuration = value; } - - final VoidCallback onNeedPaint; - - final Map<int, _LiveTestPointerRecord> _pointers = <int, _LiveTestPointerRecord>{}; - - TextPainter? _label; - static const TextStyle _labelStyle = TextStyle( - fontFamily: 'sans-serif', - fontSize: 10.0, - ); - void _setDescription(String value) { - if (value.isEmpty) { - _label = null; - return; - } - // TODO(ianh): Figure out if the test name is actually RTL. - _label ??= TextPainter(textAlign: TextAlign.left, textDirection: TextDirection.ltr); - _label!.text = TextSpan(text: value, style: _labelStyle); - _label!.layout(); - onNeedPaint(); - } - - @override - void paint(PaintingContext context, Offset offset) { - assert(offset == Offset.zero); - super.paint(context, offset); - if (_pointers.isNotEmpty) { - final double radius = configuration.size.shortestSide * 0.05; - final Path path = Path() - ..addOval(Rect.fromCircle(center: Offset.zero, radius: radius)) - ..moveTo(0.0, -radius * 2.0) - ..lineTo(0.0, radius * 2.0) - ..moveTo(-radius * 2.0, 0.0) - ..lineTo(radius * 2.0, 0.0); - final Canvas canvas = context.canvas; - final Paint paint = Paint() - ..strokeWidth = radius / 10.0 - ..style = PaintingStyle.stroke; - bool dirty = false; - for (final int pointer in _pointers.keys) { - final _LiveTestPointerRecord record = _pointers[pointer]!; - paint.color = record.color.withOpacity(record.decay < 0 ? (record.decay / (_kPointerDecay - 1)) : 1.0); - canvas.drawPath(path.shift(record.position), paint); - if (record.decay < 0) { - dirty = true; - } - record.decay += 1; - } - _pointers - .keys - .where((int pointer) => _pointers[pointer]!.decay == 0) - .toList() - .forEach(_pointers.remove); - if (dirty) { - scheduleMicrotask(onNeedPaint); - } - } - _label?.paint(context.canvas, offset - const Offset(0.0, 10.0)); - } -} diff --git a/packages/flutter_test/lib/src/controller.dart b/packages/flutter_test/lib/src/controller.dart index 1d334300624c3..4033ba0ad872c 100644 --- a/packages/flutter_test/lib/src/controller.dart +++ b/packages/flutter_test/lib/src/controller.dart @@ -87,7 +87,7 @@ class SemanticsController { RenderObject? renderObject = element.findRenderObject(); SemanticsNode? result = renderObject?.debugSemantics; while (renderObject != null && (result == null || result.isMergedIntoParent)) { - renderObject = renderObject.parent as RenderObject?; + renderObject = renderObject.parent; result = renderObject?.debugSemantics; } if (result == null) { @@ -366,7 +366,7 @@ abstract class WidgetController { final RenderObject object = element.renderObject!; RenderObject current = object; while (current.debugLayer == null) { - current = current.parent! as RenderObject; + current = current.parent!; } final ContainerLayer layer = current.debugLayer!; return _walkLayers(layer); @@ -1192,7 +1192,8 @@ abstract class WidgetController { /// Forwards the given location to the binding's hitTest logic. HitTestResult hitTestOnBinding(Offset location) { final HitTestResult result = HitTestResult(); - binding.hitTest(result, location); + // TODO(goderbauer): Support multiple views in flutter_test pointer event handling, https://github.com/flutter/flutter/issues/128281 + binding.hitTest(result, location); // ignore: deprecated_member_use return result; } @@ -1313,7 +1314,8 @@ abstract class WidgetController { final Offset location = box.localToGlobal(sizeToPoint(box.size)); if (warnIfMissed) { final HitTestResult result = HitTestResult(); - binding.hitTest(result, location); + // TODO(goderbauer): Support multiple views in flutter_test pointer event handling, https://github.com/flutter/flutter/issues/128281 + binding.hitTest(result, location); // ignore: deprecated_member_use bool found = false; for (final HitTestEntry entry in result.path) { if (entry.target == box) { diff --git a/packages/flutter_test/lib/src/finders.dart b/packages/flutter_test/lib/src/finders.dart index 6a706318524d4..1e881489cb504 100644 --- a/packages/flutter_test/lib/src/finders.dart +++ b/packages/flutter_test/lib/src/finders.dart @@ -642,7 +642,8 @@ class _HitTestableFinder extends ChainedFinder { final RenderBox box = candidate.renderObject! as RenderBox; final Offset absoluteOffset = box.localToGlobal(alignment.alongSize(box.size)); final HitTestResult hitResult = HitTestResult(); - WidgetsBinding.instance.hitTest(hitResult, absoluteOffset); + // TODO(goderbauer): Support multiple views in flutter_test pointer event handling, https://github.com/flutter/flutter/issues/128281 + WidgetsBinding.instance.hitTest(hitResult, absoluteOffset); // ignore: deprecated_member_use for (final HitTestEntry entry in hitResult.path) { if (entry.target == candidate.renderObject) { yield candidate; diff --git a/packages/flutter_test/lib/src/matchers.dart b/packages/flutter_test/lib/src/matchers.dart index ed93e7145a9d6..3cc1bb6ce2e99 100644 --- a/packages/flutter_test/lib/src/matchers.dart +++ b/packages/flutter_test/lib/src/matchers.dart @@ -436,7 +436,7 @@ Matcher coversSameAreaAs(Path expectedPath, { required Rect areaToCompare, int s /// }); /// /// await testMain(); -/// }); +/// } /// ``` /// {@end-tool} /// {@endtemplate} @@ -2054,7 +2054,7 @@ class _MatchesReferenceImage extends AsyncMatcher { Uint8List.view(referenceBytes.buffer), ); return countDifferentPixels == 0 ? null : 'does not match on $countDifferentPixels pixels'; - }, additionalTime: const Duration(minutes: 1)); + }); } @override diff --git a/packages/flutter_test/lib/src/test_compat.dart b/packages/flutter_test/lib/src/test_compat.dart index bbf7506653aed..433f586cb3aaf 100644 --- a/packages/flutter_test/lib/src/test_compat.dart +++ b/packages/flutter_test/lib/src/test_compat.dart @@ -185,8 +185,8 @@ void test( /// should explain why the group is skipped; this reason will be printed instead /// of running the group's tests. @isTestGroup -void group(Object description, void Function() body, { dynamic skip }) { - _declarer.group(description.toString(), body, skip: skip); +void group(Object description, void Function() body, { dynamic skip, int? retry }) { + _declarer.group(description.toString(), body, skip: skip, retry: retry); } /// Registers a function to be run before tests. diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart index 6e6d7ab1322cc..8cd6d50cb0234 100644 --- a/packages/flutter_test/lib/src/widget_tester.dart +++ b/packages/flutter_test/lib/src/widget_tester.dart @@ -47,7 +47,20 @@ export 'package:matcher/expect.dart' hide expect, isInstanceOf; // that doesn't apply here. export 'package:test_api/hooks.dart' show TestFailure; export 'package:test_api/scaffolding.dart' - hide group, setUp, setUpAll, tearDown, tearDownAll, test; + show + OnPlatform, + Retry, + Skip, + Tags, + TestOn, + Timeout, + addTearDown, + markTestSkipped, + printOnFailure, + pumpEventQueue, + registerException, + spawnHybridCode, + spawnHybridUri; /// Signature for callback to [testWidgets] and [benchmarkWidgets]. typedef WidgetTesterCallback = Future<void> Function(WidgetTester widgetTester); @@ -80,9 +93,7 @@ E? _lastWhereOrNull<E>(Iterable<E> list, bool Function(E) test) { /// `test` package. If set, it should be relatively large (minutes). It defaults /// to ten minutes for tests run by `flutter test`, and is unlimited for tests /// run by `flutter run`; specifically, it defaults to -/// [TestWidgetsFlutterBinding.defaultTestTimeout]. (The `initialTimeout` -/// parameter has no effect. It was previously used with -/// [TestWidgetsFlutterBinding.addTime] but that feature was removed.) +/// [TestWidgetsFlutterBinding.defaultTestTimeout]. /// /// If the `semanticsEnabled` parameter is set to `true`, /// [WidgetTester.ensureSemantics] will have been called before the tester is @@ -102,11 +113,6 @@ E? _lastWhereOrNull<E>(Iterable<E> list, bool Function(E) test) { /// If the [tags] are passed, they declare user-defined tags that are implemented by /// the `test` package. /// -/// See also: -/// -/// * [AutomatedTestWidgetsFlutterBinding.addTime] to learn more about -/// timeout and how to manually increase timeouts. -/// /// ## Sample code /// /// ```dart @@ -122,14 +128,10 @@ void testWidgets( WidgetTesterCallback callback, { bool? skip, test_package.Timeout? timeout, - @Deprecated( - 'This parameter has no effect. Use `timeout` instead. ' - 'This feature was deprecated after v2.6.0-1.0.pre.' - ) - Duration? initialTimeout, bool semanticsEnabled = true, TestVariant<Object?> variant = const DefaultTestVariant(), dynamic tags, + int? retry, }) { assert(variant.values.isNotEmpty, 'There must be at least one value to test in the testing variant.'); final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized(); @@ -168,12 +170,12 @@ void testWidgets( }, tester._endOfTestVerifications, description: combinedDescription, - timeout: initialTimeout, ); }, skip: skip, timeout: timeout ?? binding.defaultTestTimeout, tags: tags, + retry: retry, ); } } @@ -811,8 +813,12 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker /// your widget tree, then await that future inside [callback]. Future<T?> runAsync<T>( Future<T> Function() callback, { + @Deprecated( + 'This is no longer supported and has no effect. ' + 'This feature was deprecated after v3.12.0-1.1.pre.' + ) Duration additionalTime = const Duration(milliseconds: 1000), - }) => binding.runAsync<T?>(callback, additionalTime: additionalTime); + }) => binding.runAsync<T?>(callback); /// Whether there are any transient callbacks scheduled. /// @@ -832,7 +838,7 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker @override HitTestResult hitTestOnBinding(Offset location) { - location = binding.localToGlobal(location); + location = binding.localToGlobal(location, binding.renderView); return super.hitTestOnBinding(location); } diff --git a/packages/flutter_test/lib/src/window.dart b/packages/flutter_test/lib/src/window.dart index c242c70d63669..9c379003e7d9f 100644 --- a/packages/flutter_test/lib/src/window.dart +++ b/packages/flutter_test/lib/src/window.dart @@ -153,7 +153,7 @@ class TestPlatformDispatcher implements PlatformDispatcher { TestPlatformDispatcher({ required PlatformDispatcher platformDispatcher, }) : _platformDispatcher = platformDispatcher { - _updateViews(); + _updateViewsAndDisplays(); _platformDispatcher.onMetricsChanged = _handleMetricsChanged; } @@ -167,7 +167,8 @@ class TestPlatformDispatcher implements PlatformDispatcher { : null; } - final Map<Object, TestFlutterView> _testViews = <Object, TestFlutterView>{}; + final Map<int, TestFlutterView> _testViews = <int, TestFlutterView>{}; + final Map<int, TestDisplay> _testDisplays = <int, TestDisplay>{}; @override VoidCallback? get onMetricsChanged => _platformDispatcher.onMetricsChanged; @@ -178,7 +179,7 @@ class TestPlatformDispatcher implements PlatformDispatcher { } void _handleMetricsChanged() { - _updateViews(); + _updateViewsAndDisplays(); _onMetricsChanged?.call(); } @@ -394,10 +395,10 @@ class TestPlatformDispatcher implements PlatformDispatcher { } @override - SemanticsActionCallback? get onSemanticsAction => _platformDispatcher.onSemanticsAction; + SemanticsActionEventCallback? get onSemanticsActionEvent => _platformDispatcher.onSemanticsActionEvent; @override - set onSemanticsAction(SemanticsActionCallback? callback) { - _platformDispatcher.onSemanticsAction = callback; + set onSemanticsActionEvent(SemanticsActionEventCallback? callback) { + _platformDispatcher.onSemanticsActionEvent = callback; } @override @@ -509,16 +510,53 @@ class TestPlatformDispatcher implements PlatformDispatcher { @override Iterable<TestFlutterView> get views => _testViews.values; - void _updateViews() { - final List<Object> extraKeys = <Object>[..._testViews.keys]; + @override + FlutterView? view({required int id}) => _testViews[id]; + + @override + Iterable<TestDisplay> get displays => _testDisplays.values; + + void _updateViewsAndDisplays() { + final List<Object> extraDisplayKeys = <Object>[..._testDisplays.keys]; + for (final Display display in _platformDispatcher.displays) { + extraDisplayKeys.remove(display.id); + if (!_testDisplays.containsKey(display.id)) { + _testDisplays[display.id] = TestDisplay(this, display); + } + } + extraDisplayKeys.forEach(_testDisplays.remove); + + final List<Object> extraViewKeys = <Object>[..._testViews.keys]; for (final FlutterView view in _platformDispatcher.views) { - extraKeys.remove(view.viewId); + // TODO(pdblasi-google): Remove this try-catch once the Display API is stable and supported on all platforms + late final TestDisplay display; + try { + final Display realDisplay = view.display; + if (_testDisplays.containsKey(realDisplay.id)) { + display = _testDisplays[view.display.id]!; + } else { + display = _UnsupportedDisplay( + this, + view, + 'PlatformDispatcher did not contain a Display with id ${realDisplay.id}, ' + 'which was expected by FlutterView ($view)', + ); + } + } catch (error){ + display = _UnsupportedDisplay(this, view, error); + } + + extraViewKeys.remove(view.viewId); if (!_testViews.containsKey(view.viewId)) { - _testViews[view.viewId] = TestFlutterView(view: view, platformDispatcher: this); + _testViews[view.viewId] = TestFlutterView( + view: view, + platformDispatcher: this, + display: display, + ); } } - extraKeys.forEach(_testViews.remove); + extraViewKeys.forEach(_testViews.remove); } @override @@ -625,7 +663,11 @@ class TestFlutterView implements FlutterView { TestFlutterView({ required FlutterView view, required TestPlatformDispatcher platformDispatcher, - }) : _view = view, _platformDispatcher = platformDispatcher; + required TestDisplay display, + }) : + _view = view, + _platformDispatcher = platformDispatcher, + _display = display; /// The [FlutterView] backing this [TestFlutterView]. final FlutterView _view; @@ -635,7 +677,11 @@ class TestFlutterView implements FlutterView { final TestPlatformDispatcher _platformDispatcher; @override - Object get viewId => _view.viewId; + TestDisplay get display => _display; + final TestDisplay _display; + + @override + int get viewId => _view.viewId; /// The device pixel ratio to use for this test. /// @@ -646,20 +692,21 @@ class TestFlutterView implements FlutterView { /// See also: /// /// * [FlutterView.devicePixelRatio] for the standard implementation + /// * [TestDisplay.devicePixelRatio] which will stay in sync with this value /// * [resetDevicePixelRatio] to reset this value specifically /// * [reset] to reset all test values for this view @override - double get devicePixelRatio => _devicePixelRatio ?? _view.devicePixelRatio; - double? _devicePixelRatio; + double get devicePixelRatio => _display._devicePixelRatio ?? _view.devicePixelRatio; set devicePixelRatio(double value) { - _devicePixelRatio = value; - platformDispatcher.onMetricsChanged?.call(); + _display.devicePixelRatio = value; } /// Resets [devicePixelRatio] for this test view to the default value for this view. + /// + /// This will also reset the [devicePixelRatio] for the [TestDisplay] + /// that is related to this view. void resetDevicePixelRatio() { - _devicePixelRatio = null; - platformDispatcher.onMetricsChanged?.call(); + _display.resetDevicePixelRatio(); } /// The display features to use for this test. @@ -947,6 +994,158 @@ class TestFlutterView implements FlutterView { } } +/// A version of [Display] that can be modified to allow for testing various +/// use cases. +/// +/// Updates to the [TestDisplay] will be surfaced through +/// [PlatformDispatcher.onMetricsChanged]. +class TestDisplay implements Display { + /// Creates a new [TestDisplay] backed by the given [Display]. + TestDisplay(TestPlatformDispatcher platformDispatcher, Display display) + : _platformDispatcher = platformDispatcher, _display = display; + + final Display _display; + final TestPlatformDispatcher _platformDispatcher; + + @override + int get id => _display.id; + + /// The device pixel ratio to use for this test. + /// + /// Defaults to the value provided by [Display.devicePixelRatio]. This + /// can only be set in a test environment to emulate different display + /// configurations. A standard [Display] is not mutable from the framework. + /// + /// See also: + /// + /// * [Display.devicePixelRatio] for the standard implementation + /// * [TestFlutterView.devicePixelRatio] which will stay in sync with this value + /// * [resetDevicePixelRatio] to reset this value specifically + /// * [reset] to reset all test values for this display + @override + double get devicePixelRatio => _devicePixelRatio ?? _display.devicePixelRatio; + double? _devicePixelRatio; + set devicePixelRatio(double value) { + _devicePixelRatio = value; + _platformDispatcher.onMetricsChanged?.call(); + } + + /// Resets [devicePixelRatio] to the default value for this display. + /// + /// This will also reset the [devicePixelRatio] for any [TestFlutterView]s + /// that are related to this display. + void resetDevicePixelRatio() { + _devicePixelRatio = null; + _platformDispatcher.onMetricsChanged?.call(); + } + + /// The refresh rate to use for this test. + /// + /// Defaults to the value provided by [Display.refreshRate]. This + /// can only be set in a test environment to emulate different display + /// configurations. A standard [Display] is not mutable from the framework. + /// + /// See also: + /// + /// * [Display.refreshRate] for the standard implementation + /// * [resetRefreshRate] to reset this value specifically + /// * [reset] to reset all test values for this display + @override + double get refreshRate => _refreshRate ?? _display.refreshRate; + double? _refreshRate; + set refreshRate(double value) { + _refreshRate = value; + _platformDispatcher.onMetricsChanged?.call(); + } + + /// Resets [refreshRate] to the default value for this display. + void resetRefreshRate() { + _refreshRate = null; + _platformDispatcher.onMetricsChanged?.call(); + } + + /// The size of the [Display] to use for this test. + /// + /// Defaults to the value provided by [Display.refreshRate]. This + /// can only be set in a test environment to emulate different display + /// configurations. A standard [Display] is not mutable from the framework. + /// + /// See also: + /// + /// * [Display.refreshRate] for the standard implementation + /// * [resetRefreshRate] to reset this value specifically + /// * [reset] to reset all test values for this display + @override + Size get size => _size ?? _display.size; + Size? _size; + set size(Size value) { + _size = value; + _platformDispatcher.onMetricsChanged?.call(); + } + + /// Resets [size] to the default value for this display. + void resetSize() { + _size = null; + _platformDispatcher.onMetricsChanged?.call(); + } + + /// Resets all values on this [TestDisplay]. + /// + /// See also: + /// * [resetDevicePixelRatio] to reset [devicePixelRatio] specifically + /// * [resetRefreshRate] to reset [refreshRate] specifically + /// * [resetSize] to reset [size] specifically + void reset() { + resetDevicePixelRatio(); + resetRefreshRate(); + resetSize(); + } + + /// This gives us some grace time when the dart:ui side adds something to + /// [Display], and makes things easier when we do rolls to give + /// us time to catch up. + @override + dynamic noSuchMethod(Invocation invocation) { + return null; + } +} + +// TODO(pdblasi-google): Remove this once the Display API is stable and supported on all platforms +class _UnsupportedDisplay implements TestDisplay { + _UnsupportedDisplay(this._platformDispatcher, this._view, this.error); + + final FlutterView _view; + final Object? error; + + @override + final TestPlatformDispatcher _platformDispatcher; + + @override + double get devicePixelRatio => _devicePixelRatio ?? _view.devicePixelRatio; + @override + double? _devicePixelRatio; + @override + set devicePixelRatio(double value) { + _devicePixelRatio = value; + _platformDispatcher.onMetricsChanged?.call(); + } + + @override + void resetDevicePixelRatio() { + _devicePixelRatio = null; + _platformDispatcher.onMetricsChanged?.call(); + } + + @override + dynamic noSuchMethod(Invocation invocation) { + throw UnsupportedError( + 'The Display API is unsupported in this context. ' + 'As of the last metrics change on PlatformDispatcher, this was the error ' + 'given when trying to prepare the display for testing: $error', + ); + } +} + /// Deprecated. Will be removed in a future version of Flutter. /// /// This class has been deprecated to prepare for Flutter's upcoming support @@ -1683,23 +1882,6 @@ class TestWindow implements SingletonFlutterWindow { platformDispatcher.onSemanticsEnabledChanged = callback; } - @Deprecated( - 'Use WidgetTester.platformDispatcher.onSemanticsAction instead. ' - 'Deprecated to prepare for the upcoming multi-window support. ' - 'This feature was deprecated after v3.9.0-0.1.pre.' - ) - @override - SemanticsActionCallback? get onSemanticsAction => platformDispatcher.onSemanticsAction; - @Deprecated( - 'Use WidgetTester.platformDispatcher.onSemanticsAction instead. ' - 'Deprecated to prepare for the upcoming multi-window support. ' - 'This feature was deprecated after v3.9.0-0.1.pre.' - ) - @override - set onSemanticsAction(SemanticsActionCallback? callback) { - platformDispatcher.onSemanticsAction = callback; - } - @Deprecated( 'Use WidgetTester.platformDispatcher.accessibilityFeatures instead. ' 'Deprecated to prepare for the upcoming multi-window support. ' @@ -1909,7 +2091,7 @@ class TestWindow implements SingletonFlutterWindow { 'This feature was deprecated after v3.9.0-0.1.pre.' ) @override - Object get viewId => _view.viewId; + int get viewId => _view.viewId; /// This gives us some grace time when the dart:ui side adds something to /// [SingletonFlutterWindow], and makes things easier when we do rolls to give diff --git a/packages/flutter_test/pubspec.yaml b/packages/flutter_test/pubspec.yaml index 989e27543b789..fe95841298336 100644 --- a/packages/flutter_test/pubspec.yaml +++ b/packages/flutter_test/pubspec.yaml @@ -12,7 +12,8 @@ dependencies: # We depend on very specific internal implementation details of the # 'test' package, which change between versions, so when upgrading # this, make sure the tests are still running correctly. - test_api: 0.5.2 + test_api: 0.6.0 + matcher: 0.12.16 # Used by golden file comparator path: 1.8.3 @@ -33,16 +34,15 @@ dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: file: 6.1.4 -# PUBSPEC CHECKSUM: db12 +# PUBSPEC CHECKSUM: 7736 diff --git a/packages/flutter_test/test/accessibility_test.dart b/packages/flutter_test/test/accessibility_test.dart index 59129ba47436c..2bf455c290f8c 100644 --- a/packages/flutter_test/test/accessibility_test.dart +++ b/packages/flutter_test/test/accessibility_test.dart @@ -275,11 +275,12 @@ void main() { handle.dispose(); }); - testWidgets('yellow text on yellow background fails with correct message', + testWidgets('Material2: yellow text on yellow background fails with correct message', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); await tester.pumpWidget( _boilerplate( + useMaterial3: false, Container( width: 200.0, height: 200.0, @@ -306,6 +307,38 @@ void main() { handle.dispose(); }); + testWidgets('Material3: yellow text on yellow background fails with correct message', + (WidgetTester tester) async { + final SemanticsHandle handle = tester.ensureSemantics(); + await tester.pumpWidget( + _boilerplate( + useMaterial3: true, + Container( + width: 200.0, + height: 200.0, + color: Colors.yellow, + child: const Text( + 'this is a test', + style: TextStyle(fontSize: 14.0, color: Colors.yellowAccent), + ), + ), + ), + ); + final Evaluation result = await textContrastGuideline.evaluate(tester); + expect(result.passed, false); + expect( + result.reason, + 'SemanticsNode#4(Rect.fromLTRB(300.0, 200.0, 500.0, 400.0), ' + 'label: "this is a test", textDirection: ltr):\n' + 'Expected contrast ratio of at least 4.5 but found 1.19 for a font ' + 'size of 14.0.\n' + 'The computed colors was:\n' + 'light - Color(0xfffffbfe), dark - Color(0xffffeb3b)\n' + 'See also: https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html', + ); + handle.dispose(); + }); + testWidgets('label without corresponding text is skipped', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); @@ -937,5 +970,11 @@ void main() { }); } -Widget _boilerplate(Widget child) => - MaterialApp(home: Scaffold(body: Center(child: child))); +Widget _boilerplate(Widget child, { bool? useMaterial3 }) { + return MaterialApp( + theme: ThemeData(useMaterial3: useMaterial3), + home: Scaffold( + body: Center(child: child), + ), + ); +} diff --git a/packages/flutter_test/test/coordinate_translation_test.dart b/packages/flutter_test/test/coordinate_translation_test.dart new file mode 100644 index 0000000000000..73cd70e07cedc --- /dev/null +++ b/packages/flutter_test/test/coordinate_translation_test.dart @@ -0,0 +1,70 @@ +// 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/rendering.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + final LiveTestWidgetsFlutterBinding binding = LiveTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('localToGlobal and globalToLocal calculate correct results', (WidgetTester tester) async { + tester.view.physicalSize = const Size(2400, 1800); + tester.view.devicePixelRatio = 3.0; + final RenderView renderView = RenderView( + view: tester.view, + configuration: TestViewConfiguration.fromView( + view: tester.view, + size: const Size(400, 200), + ) + ); + + // The configuration above defines a view with a resolution of 2400x1800 + // physical pixels. With a device pixel ratio of 3x, this yields a + // resolution of 800x600 logical pixels. In this view, a RenderView sized + // 400x200 (in logical pixels) is fitted using the BoxFit.contain + // algorithm (as documented on TestViewConfiguration. To fit 400x200 into + // 800x600 the RenderView is scaled up by 2 to fill the full width and then + // vertically positioned in the middle. The origin of the RenderView is + // located at (0, 100) in the logical coordinate space of the view: + // + // View: 800 logical pixels wide (or 2400 physical pixels) + // +---------------------------------------+ + // | | + // | 100px | + // | | + // +---------------------------------------+ + // | | + // | RenderView (400x200px) | + // | 400px scaled to 800x400px | View: 600 logical pixels high (or 1800 physical pixels) + // | | + // | | + // +---------------------------------------+ + // | | + // | 200px | + // | | + // +---------------------------------------+ + // + // All values in logical pixels until otherwise noted. + // + // A point can be translated from the local coordinate space of the + // RenderView (in logical pixels) to the global coordinate space of the View + // (in logical pixels) by multiplying each coordinate by 2 and adding 100 to + // the y coordinate. This is what the localToGlobal/globalToLocal methods + // do: + + expect(binding.localToGlobal(const Offset(0, -50), renderView), Offset.zero); + expect(binding.localToGlobal(Offset.zero, renderView), const Offset(0, 100)); + expect(binding.localToGlobal(const Offset(200, 100), renderView), const Offset(400, 300)); + expect(binding.localToGlobal(const Offset(150, 75), renderView), const Offset(300, 250)); + expect(binding.localToGlobal(const Offset(400, 200), renderView), const Offset(800, 500)); + expect(binding.localToGlobal(const Offset(400, 400), renderView), const Offset(800, 900)); + + expect(binding.globalToLocal(Offset.zero, renderView), const Offset(0, -50)); + expect(binding.globalToLocal(const Offset(0, 100), renderView), Offset.zero); + expect(binding.globalToLocal(const Offset(400, 300), renderView), const Offset(200, 100)); + expect(binding.globalToLocal(const Offset(300, 250), renderView), const Offset(150, 75)); + expect(binding.globalToLocal(const Offset(800, 500), renderView), const Offset(400, 200)); + expect(binding.globalToLocal(const Offset(800, 900), renderView), const Offset(400, 400)); + }); +} diff --git a/packages/flutter_test/test/display_test.dart b/packages/flutter_test/test/display_test.dart new file mode 100644 index 0000000000000..b6bdfe4f3de40 --- /dev/null +++ b/packages/flutter_test/test/display_test.dart @@ -0,0 +1,192 @@ +// 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:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils/fake_and_mock_utils.dart'; + +void main() { + group('TestDisplay', () { + Display trueDisplay() => PlatformDispatcher.instance.displays.single; + TestDisplay boundDisplay() => WidgetsBinding.instance.platformDispatcher.displays.single as TestDisplay; + + tearDown(() { + boundDisplay().reset(); + }); + + testWidgets('can handle new methods without breaking', (WidgetTester tester) async { + final dynamic testDisplay = tester.view.display; + //ignore: avoid_dynamic_calls + expect(testDisplay.someNewProperty, null); + }); + + testWidgets('can fake devicePixelRatio', (WidgetTester tester) async { + verifyPropertyFaked<double>( + tester: tester, + realValue: trueDisplay().devicePixelRatio, + fakeValue: trueDisplay().devicePixelRatio + 1, + propertyRetriever: () => boundDisplay().devicePixelRatio, + propertyFaker: (_, double fake) { + boundDisplay().devicePixelRatio = fake; + }, + ); + }); + + testWidgets('can reset devicePixelRatio', (WidgetTester tester) async { + verifyPropertyReset<double>( + tester: tester, + fakeValue: trueDisplay().devicePixelRatio + 1, + propertyRetriever: () => boundDisplay().devicePixelRatio, + propertyResetter: () => boundDisplay().resetDevicePixelRatio(), + propertyFaker: (double fake) { + boundDisplay().devicePixelRatio = fake; + }, + ); + }); + + testWidgets('resetting devicePixelRatio also resets view.devicePixelRatio', (WidgetTester tester) async { + verifyPropertyReset( + tester: tester, + fakeValue: trueDisplay().devicePixelRatio + 1, + propertyRetriever: () => tester.view.devicePixelRatio, + propertyResetter: () => boundDisplay().resetDevicePixelRatio(), + propertyFaker: (double dpr) => boundDisplay().devicePixelRatio = dpr, + ); + }); + + testWidgets('updating devicePixelRatio also updates view.devicePixelRatio', (WidgetTester tester) async { + tester.view.display.devicePixelRatio = tester.view.devicePixelRatio + 1; + + expect(tester.view.devicePixelRatio, tester.view.display.devicePixelRatio); + }); + + testWidgets('can fake refreshRate', (WidgetTester tester) async { + verifyPropertyFaked<double>( + tester: tester, + realValue: trueDisplay().refreshRate, + fakeValue: trueDisplay().refreshRate + 1, + propertyRetriever: () => boundDisplay().refreshRate, + propertyFaker: (_, double fake) { + boundDisplay().refreshRate = fake; + }, + ); + }); + + testWidgets('can reset refreshRate', (WidgetTester tester) async { + verifyPropertyReset<double>( + tester: tester, + fakeValue: trueDisplay().refreshRate + 1, + propertyRetriever: () => boundDisplay().refreshRate, + propertyResetter: () => boundDisplay().resetRefreshRate(), + propertyFaker: (double fake) { + boundDisplay().refreshRate = fake; + }, + ); + }); + + testWidgets('can fake size', (WidgetTester tester) async { + verifyPropertyFaked<Size>( + tester: tester, + realValue: trueDisplay().size, + fakeValue: const Size(354, 856), + propertyRetriever: () => boundDisplay().size, + propertyFaker: (_, Size fake) { + boundDisplay().size = fake; + }, + ); + }); + + testWidgets('can reset size', (WidgetTester tester) async { + verifyPropertyReset<Size>( + tester: tester, + fakeValue: const Size(465, 980), + propertyRetriever: () => boundDisplay().size, + propertyResetter: () => boundDisplay().resetSize(), + propertyFaker: (Size fake) { + boundDisplay().size = fake; + }, + ); + }); + + testWidgets('can reset all values', (WidgetTester tester) async { + final DisplaySnapshot initial = DisplaySnapshot(tester.view.display); + + tester.view.display.devicePixelRatio = 7; + tester.view.display.refreshRate = 40; + tester.view.display.size = const Size(476, 823); + + final DisplaySnapshot faked = DisplaySnapshot(tester.view.display); + + tester.view.display.reset(); + + final DisplaySnapshot reset = DisplaySnapshot(tester.view.display); + + expect(initial, isNot(matchesSnapshot(faked))); + expect(initial, matchesSnapshot(reset)); + }); + }); +} + +class DisplaySnapshot { + DisplaySnapshot(Display display) : + devicePixelRatio = display.devicePixelRatio, + refreshRate = display.refreshRate, + id = display.id, + size = display.size; + + final double devicePixelRatio; + final double refreshRate; + final int id; + final Size size; +} + +Matcher matchesSnapshot(DisplaySnapshot expected) => _DisplaySnapshotMatcher(expected); + +class _DisplaySnapshotMatcher extends Matcher { + _DisplaySnapshotMatcher(this.expected); + + final DisplaySnapshot expected; + + @override + Description describe(Description description) { + description.add('snapshot of a Display matches'); + return description; + } + + @override + Description describeMismatch(dynamic item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) { + assert(item is DisplaySnapshot, 'Can only match against snapshots of Display.'); + final DisplaySnapshot actual = item as DisplaySnapshot; + + if (actual.devicePixelRatio != expected.devicePixelRatio) { + mismatchDescription.add('actual.devicePixelRatio (${actual.devicePixelRatio}) did not match expected.devicePixelRatio (${expected.devicePixelRatio})'); + } + if (actual.refreshRate != expected.refreshRate) { + mismatchDescription.add('actual.refreshRate (${actual.refreshRate}) did not match expected.refreshRate (${expected.refreshRate})'); + } + if (actual.size != expected.size) { + mismatchDescription.add('actual.size (${actual.size}) did not match expected.size (${expected.size})'); + } + if (actual.id != expected.id) { + mismatchDescription.add('actual.id (${actual.id}) did not match expected.id (${expected.id})'); + } + + return mismatchDescription; + } + + @override + bool matches(dynamic item, Map<dynamic, dynamic> matchState) { + assert(item is DisplaySnapshot, 'Can only match against snapshots of Display.'); + final DisplaySnapshot actual = item as DisplaySnapshot; + + return actual.devicePixelRatio == expected.devicePixelRatio && + actual.refreshRate == expected.refreshRate && + actual.size == expected.size && + actual.id == expected.id; + } +} diff --git a/packages/flutter_test/test/platform_dispatcher_test.dart b/packages/flutter_test/test/platform_dispatcher_test.dart index 5940f3adf0aed..82544256e4dc0 100644 --- a/packages/flutter_test/test/platform_dispatcher_test.dart +++ b/packages/flutter_test/test/platform_dispatcher_test.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui' show AccessibilityFeatures, Brightness, Locale, PlatformDispatcher; +import 'dart:ui' show AccessibilityFeatures, Brightness, Display, FlutterView, Locale, PlatformDispatcher, VoidCallback; import 'package:flutter/widgets.dart' show WidgetsBinding, WidgetsBindingObserver; import 'package:flutter_test/flutter_test.dart'; @@ -152,6 +152,106 @@ void main() { expect(observer.locales, equals(expectedValue)); retrieveTestBinding(tester).platformDispatcher.localesTestValue = defaultLocales; }); + + testWidgets('TestPlatformDispatcher.view getter returns the implicit view', (WidgetTester tester) async { + expect(WidgetsBinding.instance.platformDispatcher.view(id: tester.view.viewId), same(tester.view)); + }); + + // TODO(pdblasi-google): Removed this group of tests when the Display API is stable and supported on all platforms. + group('TestPlatformDispatcher with unsupported Display API', () { + testWidgets('can initialize with empty displays', (WidgetTester tester) async { + expect(() { + TestPlatformDispatcher( + platformDispatcher: _FakePlatformDispatcher( + displays: <Display>[], + views: <FlutterView>[ + _FakeFlutterView(), + ], + ) + ); + }, isNot(throwsA(anything))); + }); + + testWidgets('can initialize with mismatched displays', (WidgetTester tester) async { + expect(() { + TestPlatformDispatcher( + platformDispatcher: _FakePlatformDispatcher( + displays: <Display>[ + _FakeDisplay(id: 2), + ], + views: <FlutterView>[ + _FakeFlutterView(display: _FakeDisplay(id: 1)), + ], + ) + ); + }, isNot(throwsA(anything))); + }); + + testWidgets('creates test views for all views', (WidgetTester tester) async { + final PlatformDispatcher backingDispatcher = _FakePlatformDispatcher( + displays: <Display>[], + views: <FlutterView>[ + _FakeFlutterView(), + ], + ); + final TestPlatformDispatcher testDispatcher = TestPlatformDispatcher( + platformDispatcher: backingDispatcher, + ); + + expect(testDispatcher.views.length, backingDispatcher.views.length); + }); + + group('creates TestFlutterViews', () { + testWidgets('that defaults to the correct devicePixelRatio', (WidgetTester tester) async { + const double expectedDpr = 2.5; + final TestPlatformDispatcher testDispatcher = TestPlatformDispatcher( + platformDispatcher: _FakePlatformDispatcher( + displays: <Display>[], + views: <FlutterView>[ + _FakeFlutterView(devicePixelRatio: expectedDpr), + ], + ) + ); + + expect(testDispatcher.views.single.devicePixelRatio, expectedDpr); + }); + + testWidgets('with working devicePixelRatio setter', (WidgetTester tester) async { + const double expectedDpr = 2.5; + const double defaultDpr = 4; + final TestPlatformDispatcher testDispatcher = TestPlatformDispatcher( + platformDispatcher: _FakePlatformDispatcher( + displays: <Display>[], + views: <FlutterView>[ + _FakeFlutterView(devicePixelRatio: defaultDpr), + ], + ) + ); + + testDispatcher.views.single.devicePixelRatio = expectedDpr; + + expect(testDispatcher.views.single.devicePixelRatio, expectedDpr); + }); + + testWidgets('with working resetDevicePixelRatio', (WidgetTester tester) async { + const double changedDpr = 2.5; + const double defaultDpr = 4; + final TestPlatformDispatcher testDispatcher = TestPlatformDispatcher( + platformDispatcher: _FakePlatformDispatcher( + displays: <Display>[], + views: <FlutterView>[ + _FakeFlutterView(devicePixelRatio: defaultDpr), + ], + ) + ); + + testDispatcher.views.single.devicePixelRatio = changedDpr; + testDispatcher.views.single.resetDevicePixelRatio(); + + expect(testDispatcher.views.single.devicePixelRatio, defaultDpr); + }); + }); + }); } class TestObserver with WidgetsBindingObserver { @@ -162,3 +262,45 @@ class TestObserver with WidgetsBindingObserver { this.locales = locales; } } + +class _FakeDisplay extends Fake implements Display { + _FakeDisplay({this.id = 0}); + + @override + final int id; +} + +class _FakeFlutterView extends Fake implements FlutterView { + _FakeFlutterView({ + this.devicePixelRatio = 1, + Display? display, + }) : _display = display; + + @override + final double devicePixelRatio; + + // This emulates the PlatformDispatcher not having a display on the engine + // side. We don't have access to the `_displayId` used in the engine to try + // to find it and can't directly extend `FlutterView` to emulate it closer. + @override + Display get display { + assert(_display != null); + return _display!; + } + final Display? _display; + + @override + final int viewId = 1; +} + +class _FakePlatformDispatcher extends Fake implements PlatformDispatcher { + _FakePlatformDispatcher({required this.displays, required this.views}); + @override + final Iterable<Display> displays; + + @override + final Iterable<FlutterView> views; + + @override + VoidCallback? onMetricsChanged; +} diff --git a/packages/flutter_test/test/view_test.dart b/packages/flutter_test/test/view_test.dart index c0834833eda74..e79a7aca4d09f 100644 --- a/packages/flutter_test/test/view_test.dart +++ b/packages/flutter_test/test/view_test.dart @@ -52,6 +52,12 @@ void main() { ); }); + testWidgets('updating devicePixelRatio also updates display.devicePixelRatio', (WidgetTester tester) async { + tester.view.devicePixelRatio = tester.view.devicePixelRatio + 1; + + expect(tester.view.display.devicePixelRatio, tester.view.devicePixelRatio); + }); + testWidgets('can fake displayFeatures', (WidgetTester tester) async { verifyPropertyFaked<List<DisplayFeature>>( tester: tester, @@ -288,6 +294,7 @@ void main() { final TestFlutterView view = TestFlutterView( view: backingView, platformDispatcher: tester.binding.platformDispatcher, + display: _FakeDisplay(), ); view.render(expectedScene); @@ -302,6 +309,7 @@ void main() { final TestFlutterView view = TestFlutterView( view: backingView, platformDispatcher: tester.binding.platformDispatcher, + display: _FakeDisplay(), ); view.updateSemantics(expectedUpdate); @@ -312,7 +320,6 @@ void main() { }); } - Matcher matchesSnapshot(FlutterViewSnapshot expected) => _FlutterViewSnapshotMatcher(expected); class _FlutterViewSnapshotMatcher extends Matcher { @@ -424,7 +431,7 @@ class FlutterViewSnapshot { final ViewPadding viewPadding; } -class _FakeFlutterView implements FlutterView { +class _FakeFlutterView extends Fake implements FlutterView { SemanticsUpdate? lastSemanticsUpdate; Scene? lastRenderedScene; @@ -437,9 +444,6 @@ class _FakeFlutterView implements FlutterView { void render(Scene scene) { lastRenderedScene = scene; } - - @override - dynamic noSuchMethod(Invocation invocation) { - return null; - } } + +class _FakeDisplay extends Fake implements TestDisplay { } diff --git a/packages/flutter_test/test/widget_tester_live_device_test.dart b/packages/flutter_test/test/widget_tester_live_device_test.dart index d2935cdd0ef51..4f9871011ecc6 100644 --- a/packages/flutter_test/test/widget_tester_live_device_test.dart +++ b/packages/flutter_test/test/widget_tester_live_device_test.dart @@ -126,7 +126,7 @@ class _MockLiveTestWidgetsFlutterBinding extends LiveTestWidgetsFlutterBinding { // real devices touches sends event in the global coordinate system. // See the documentation of [handlePointerEventForSource] for details. if (source == TestBindingEventSource.test) { - final PointerEvent globalEvent = event.copyWith(position: localToGlobal(event.position)); + final PointerEvent globalEvent = event.copyWith(position: localToGlobal(event.position, renderView)); return super.handlePointerEventForSource(globalEvent); } return super.handlePointerEventForSource(event, source: source); diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart index 153aed0bc238d..a168619b2e444 100644 --- a/packages/flutter_test/test/widget_tester_test.dart +++ b/packages/flutter_test/test/widget_tester_test.dart @@ -51,6 +51,24 @@ void main() { }); }); + group('group retry flag allows test to run multiple times', () { + bool retried = false; + group('the group with retry flag', () { + testWidgets('the test inside it', (WidgetTester tester) async { + addTearDown(() => retried = true); + expect(retried, isTrue); + }); + }, retry: 1); + }); + + group('testWidget retry flag allows test to run multiple times', () { + bool retried = false; + testWidgets('the test with retry flag', (WidgetTester tester) async { + addTearDown(() => retried = true); + expect(retried, isTrue); + }, retry: 1); + }); + group('respects the group skip flag', () { testWidgets('should be skipped', (WidgetTester tester) async { expect(false, true); diff --git a/packages/flutter_test/test/window_test.dart b/packages/flutter_test/test/window_test.dart index 495004da0d600..144890c744093 100644 --- a/packages/flutter_test/test/window_test.dart +++ b/packages/flutter_test/test/window_test.dart @@ -243,7 +243,6 @@ void main() { class TestObserver with WidgetsBindingObserver { List<Locale>? locales; - Locale? locale; @override void didChangeLocales(List<Locale>? locales) { diff --git a/packages/flutter_tools/bin/xcode_backend.dart b/packages/flutter_tools/bin/xcode_backend.dart index 9d4e46e3981bd..0486a8741c3dc 100644 --- a/packages/flutter_tools/bin/xcode_backend.dart +++ b/packages/flutter_tools/bin/xcode_backend.dart @@ -63,11 +63,6 @@ class Context { } } - bool existsDir(String path) { - final Directory dir = Directory(path); - return dir.existsSync(); - } - bool existsFile(String path) { final File file = File(path); return file.existsSync(); diff --git a/packages/flutter_tools/bin/xcode_debug.js b/packages/flutter_tools/bin/xcode_debug.js new file mode 100644 index 0000000000000..611ba1ba8fea4 --- /dev/null +++ b/packages/flutter_tools/bin/xcode_debug.js @@ -0,0 +1,663 @@ +// 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. + +/** + * @fileoverview OSA Script to interact with Xcode. Functionality includes + * checking if a given project is open in Xcode, starting a debug session for + * a given project, and stopping a debug session for a given project. + */ + +'use strict'; + +/** + * OSA Script `run` handler that is called when the script is run. When ran + * with `osascript`, arguments are passed from the command line to the direct + * parameter of the `run` handler as a list of strings. + * + * @param {?Array<string>=} args_array + * @returns {!RunJsonResponse} The validated command. + */ +function run(args_array = []) { + let args; + try { + args = new CommandArguments(args_array); + } catch (e) { + return new RunJsonResponse(false, `Failed to parse arguments: ${e}`).stringify(); + } + + const xcodeResult = getXcode(args); + if (xcodeResult.error != null) { + return new RunJsonResponse(false, xcodeResult.error).stringify(); + } + const xcode = xcodeResult.result; + + if (args.command === 'check-workspace-opened') { + const result = getWorkspaceDocument(xcode, args); + return new RunJsonResponse(result.error == null, result.error).stringify(); + } else if (args.command === 'debug') { + const result = debugApp(xcode, args); + return new RunJsonResponse(result.error == null, result.error, result.result).stringify(); + } else if (args.command === 'stop') { + const result = stopApp(xcode, args); + return new RunJsonResponse(result.error == null, result.error).stringify(); + } else { + return new RunJsonResponse(false, 'Unknown command').stringify(); + } +} + +/** + * Parsed and validated arguments passed from the command line. + */ +class CommandArguments { + /** + * + * @param {!Array<string>} args List of arguments passed from the command line. + */ + constructor(args) { + this.command = this.validatedCommand(args[0]); + + const parsedArguments = this.parseArguments(args); + + this.xcodePath = this.validatedStringArgument('--xcode-path', parsedArguments['--xcode-path']); + this.projectPath = this.validatedStringArgument('--project-path', parsedArguments['--project-path']); + this.projectName = this.validatedStringArgument('--project-name', parsedArguments['--project-name']); + this.expectedConfigurationBuildDir = this.validatedStringArgument( + '--expected-configuration-build-dir', + parsedArguments['--expected-configuration-build-dir'], + ); + this.workspacePath = this.validatedStringArgument('--workspace-path', parsedArguments['--workspace-path']); + this.targetDestinationId = this.validatedStringArgument('--device-id', parsedArguments['--device-id']); + this.targetSchemeName = this.validatedStringArgument('--scheme', parsedArguments['--scheme']); + this.skipBuilding = this.validatedBoolArgument('--skip-building', parsedArguments['--skip-building']); + this.launchArguments = this.validatedJsonArgument('--launch-args', parsedArguments['--launch-args']); + this.closeWindowOnStop = this.validatedBoolArgument('--close-window', parsedArguments['--close-window']); + this.promptToSaveBeforeClose = this.validatedBoolArgument('--prompt-to-save', parsedArguments['--prompt-to-save']); + this.verbose = this.validatedBoolArgument('--verbose', parsedArguments['--verbose']); + + if (this.verbose === true) { + console.log(JSON.stringify(this)); + } + } + + /** + * Validates the command is available. + * + * @param {?string} command + * @returns {!string} The validated command. + * @throws Will throw an error if command is not recognized. + */ + validatedCommand(command) { + const allowedCommands = ['check-workspace-opened', 'debug', 'stop']; + if (allowedCommands.includes(command) === false) { + throw `Unrecognized Command: ${command}`; + } + + return command; + } + + /** + * Returns map of commands to map of allowed arguments. For each command, if + * an argument flag is a key, than that flag is allowed for that command. If + * the value for the key is true, then it is required for the command. + * + * @returns {!string} Map of commands to allowed and optionally required + * arguments. + */ + argumentSettings() { + return { + 'check-workspace-opened': { + '--xcode-path': true, + '--project-path': true, + '--workspace-path': true, + '--verbose': false, + }, + 'debug': { + '--xcode-path': true, + '--project-path': true, + '--workspace-path': true, + '--project-name': true, + '--expected-configuration-build-dir': false, + '--device-id': true, + '--scheme': true, + '--skip-building': true, + '--launch-args': true, + '--verbose': false, + }, + 'stop': { + '--xcode-path': true, + '--project-path': true, + '--workspace-path': true, + '--close-window': true, + '--prompt-to-save': true, + '--verbose': false, + }, + }; + } + + /** + * Validates the flag is allowed for the current command. + * + * @param {!string} flag + * @param {?string} value + * @returns {!bool} + * @throws Will throw an error if the flag is not allowed for the current + * command and the value is not null, undefined, or empty. + */ + isArgumentAllowed(flag, value) { + const isAllowed = this.argumentSettings()[this.command].hasOwnProperty(flag); + if (isAllowed === false && (value != null && value !== '')) { + throw `The flag ${flag} is not allowed for the command ${this.command}.`; + } + return isAllowed; + } + + /** + * Validates required flag has a value. + * + * @param {!string} flag + * @param {?string} value + * @throws Will throw an error if the flag is required for the current + * command and the value is not null, undefined, or empty. + */ + validateRequiredArgument(flag, value) { + const isRequired = this.argumentSettings()[this.command][flag] === true; + if (isRequired === true && (value == null || value === '')) { + throw `Missing value for ${flag}`; + } + } + + /** + * Parses the command line arguments into an object. + * + * @param {!Array<string>} args List of arguments passed from the command line. + * @returns {!Object.<string, string>} Object mapping flag to value. + * @throws Will throw an error if flag does not begin with '--'. + */ + parseArguments(args) { + const valuesPerFlag = {}; + for (let index = 1; index < args.length; index++) { + const entry = args[index]; + let flag; + let value; + const splitIndex = entry.indexOf('='); + if (splitIndex === -1) { + flag = entry; + value = args[index + 1]; + + // If the flag is allowed for the command, and the next value in the + // array is null/undefined or also a flag, treat the flag like a boolean + // flag and set the value to 'true'. + if (this.isArgumentAllowed(flag) && (value == null || value.startsWith('--'))) { + value = 'true'; + } else { + index++; + } + } else { + flag = entry.substring(0, splitIndex); + value = entry.substring(splitIndex + 1, entry.length + 1); + } + if (flag.startsWith('--') === false) { + throw `Unrecognized Flag: ${flag}`; + } + + valuesPerFlag[flag] = value; + } + return valuesPerFlag; + } + + + /** + * Validates the flag is allowed and `value` is valid. If the flag is not + * allowed for the current command, return `null`. + * + * @param {!string} flag + * @param {?string} value + * @returns {!string} + * @throws Will throw an error if the flag is allowed and `value` is null, + * undefined, or empty. + */ + validatedStringArgument(flag, value) { + if (this.isArgumentAllowed(flag, value) === false) { + return null; + } + this.validateRequiredArgument(flag, value); + return value; + } + + /** + * Validates the flag is allowed, validates `value` is valid, and converts + * `value` to a boolean. A `value` of null, undefined, or empty, it will + * return true. If the flag is not allowed for the current command, will + * return `null`. + * + * @param {?string} value + * @returns {?boolean} + * @throws Will throw an error if the flag is allowed and `value` is not + * null, undefined, empty, 'true', or 'false'. + */ + validatedBoolArgument(flag, value) { + if (this.isArgumentAllowed(flag, value) === false) { + return null; + } + if (value == null || value === '') { + return false; + } + if (value !== 'true' && value !== 'false') { + throw `Invalid value for ${flag}`; + } + return value === 'true'; + } + + /** + * Validates the flag is allowed, `value` is valid, and parses `value` as JSON. + * If the flag is not allowed for the current command, will return `null`. + * + * @param {!string} flag + * @param {?string} value + * @returns {!Object} + * @throws Will throw an error if the flag is allowed and the value is + * null, undefined, or empty. Will also throw an error if parsing fails. + */ + validatedJsonArgument(flag, value) { + if (this.isArgumentAllowed(flag, value) === false) { + return null; + } + this.validateRequiredArgument(flag, value); + try { + return JSON.parse(value); + } catch (e) { + throw `Error parsing ${flag}: ${e}`; + } + } +} + +/** + * Response to return in `run` function. + */ +class RunJsonResponse { + /** + * + * @param {!bool} success Whether the command was successful. + * @param {?string=} errorMessage Defaults to null. + * @param {?DebugResult=} debugResult Curated results from Xcode's debug + * function. Defaults to null. + */ + constructor(success, errorMessage = null, debugResult = null) { + this.status = success; + this.errorMessage = errorMessage; + this.debugResult = debugResult; + } + + /** + * Converts this object to a JSON string. + * + * @returns {!string} + * @throws Throws an error if conversion fails. + */ + stringify() { + return JSON.stringify(this); + } +} + +/** + * Utility class to return a result along with a potential error. + */ +class FunctionResult { + /** + * + * @param {?Object} result + * @param {?string=} error Defaults to null. + */ + constructor(result, error = null) { + this.result = result; + this.error = error; + } +} + +/** + * Curated results from Xcode's debug function. Mirrors parts of + * `scheme action result` from Xcode's Script Editor dictionary. + */ +class DebugResult { + /** + * + * @param {!Object} result + */ + constructor(result) { + this.completed = result.completed(); + this.status = result.status(); + this.errorMessage = result.errorMessage(); + } +} + +/** + * Get the Xcode application from the given path. Since macs can have multiple + * Xcode version, we use the path to target the specific Xcode application. + * If the Xcode app is not running, return null with an error. + * + * @param {!CommandArguments} args + * @returns {!FunctionResult} Return either an `Application` (Mac Scripting class) + * or null as the `result`. + */ +function getXcode(args) { + try { + const xcode = Application(args.xcodePath); + const isXcodeRunning = xcode.running(); + + if (isXcodeRunning === false) { + return new FunctionResult(null, 'Xcode is not running'); + } + + return new FunctionResult(xcode); + } catch (e) { + return new FunctionResult(null, `Failed to get Xcode application: ${e}`); + } +} + +/** + * After setting the active run destination to the targeted device, uses Xcode + * debug function from Mac Scripting for Xcode to install the app on the device + * and start a debugging session using the 'run' or 'run without building' scheme + * action (depending on `args.skipBuilding`). Waits for the debugging session + * to start running. + * + * @param {!Application} xcode An `Application` (Mac Scripting class) for Xcode. + * @param {!CommandArguments} args + * @returns {!FunctionResult} Return either a `DebugResult` or null as the `result`. + */ +function debugApp(xcode, args) { + const workspaceResult = waitForWorkspaceToLoad(xcode, args); + if (workspaceResult.error != null) { + return new FunctionResult(null, workspaceResult.error); + } + const targetWorkspace = workspaceResult.result; + + const destinationResult = getTargetDestination( + targetWorkspace, + args.targetDestinationId, + args.verbose, + ); + if (destinationResult.error != null) { + return new FunctionResult(null, destinationResult.error) + } + + // If expectedConfigurationBuildDir is available, ensure that it matches the + // build settings. + if (args.expectedConfigurationBuildDir != null && args.expectedConfigurationBuildDir !== '') { + const updateResult = waitForConfigurationBuildDirToUpdate(targetWorkspace, args); + if (updateResult.error != null) { + return new FunctionResult(null, updateResult.error); + } + } + + try { + // Documentation from the Xcode Script Editor dictionary indicates that the + // `debug` function has a parameter called `runDestinationSpecifier` which + // is used to specify which device to debug the app on. It also states that + // it should be the same as the xcodebuild -destination specifier. It also + // states that if not specified, the `activeRunDestination` is used instead. + // + // Experimentation has shown that the `runDestinationSpecifier` does not work. + // It will always use the `activeRunDestination`. To mitigate this, we set + // the `activeRunDestination` to the targeted device prior to starting the debug. + targetWorkspace.activeRunDestination = destinationResult.result; + + const actionResult = targetWorkspace.debug({ + scheme: args.targetSchemeName, + skipBuilding: args.skipBuilding, + commandLineArguments: args.launchArguments, + }); + + // Wait until scheme action has started up to a max of 10 minutes. + // This does not wait for app to install, launch, or start debug session. + // Potential statuses include: not yet started/‌running/‌cancelled/‌failed/‌error occurred/‌succeeded. + const checkFrequencyInSeconds = 0.5; + const maxWaitInSeconds = 10 * 60; // 10 minutes + const iterations = maxWaitInSeconds * (1 / checkFrequencyInSeconds); + const verboseLogInterval = 10 * (1 / checkFrequencyInSeconds); + for (let i = 0; i < iterations; i++) { + if (actionResult.status() !== 'not yet started') { + break; + } + if (args.verbose === true && i % verboseLogInterval === 0) { + console.log(`Action result status: ${actionResult.status()}`); + } + delay(checkFrequencyInSeconds); + } + + return new FunctionResult(new DebugResult(actionResult)); + } catch (e) { + return new FunctionResult(null, `Failed to start debugging session: ${e}`); + } +} + +/** + * Iterates through available run destinations looking for one with a matching + * `deviceId`. If device is not found, return null with an error. + * + * @param {!WorkspaceDocument} targetWorkspace A `WorkspaceDocument` (Xcode Mac + * Scripting class). + * @param {!string} deviceId + * @param {?bool=} verbose Defaults to false. + * @returns {!FunctionResult} Return either a `RunDestination` (Xcode Mac + * Scripting class) or null as the `result`. + */ +function getTargetDestination(targetWorkspace, deviceId, verbose = false) { + try { + for (let destination of targetWorkspace.runDestinations()) { + const device = destination.device(); + if (verbose === true && device != null) { + console.log(`Device: ${device.name()} (${device.deviceIdentifier()})`); + } + if (device != null && device.deviceIdentifier() === deviceId) { + return new FunctionResult(destination); + } + } + return new FunctionResult( + null, + 'Unable to find target device. Ensure that the device is paired, ' + + 'unlocked, connected, and has an iOS version at least as high as the ' + + 'Minimum Deployment.', + ); + } catch (e) { + return new FunctionResult(null, `Failed to get target destination: ${e}`); + } +} + +/** + * Waits for the workspace to load. If the workspace is not loaded or in the + * process of opening, it will wait up to 10 minutes. + * + * @param {!Application} xcode An `Application` (Mac Scripting class) for Xcode. + * @param {!CommandArguments} args + * @returns {!FunctionResult} Return either a `WorkspaceDocument` (Xcode Mac + * Scripting class) or null as the `result`. + */ +function waitForWorkspaceToLoad(xcode, args) { + try { + const checkFrequencyInSeconds = 0.5; + const maxWaitInSeconds = 10 * 60; // 10 minutes + const verboseLogInterval = 10 * (1 / checkFrequencyInSeconds); + const iterations = maxWaitInSeconds * (1 / checkFrequencyInSeconds); + for (let i = 0; i < iterations; i++) { + // Every 10 seconds, print the list of workspaces if verbose + const verbose = args.verbose && i % verboseLogInterval === 0; + + const workspaceResult = getWorkspaceDocument(xcode, args, verbose); + if (workspaceResult.error == null) { + const document = workspaceResult.result; + if (document.loaded() === true) { + return new FunctionResult(document, null); + } + } else if (verbose === true) { + console.log(workspaceResult.error); + } + delay(checkFrequencyInSeconds); + } + return new FunctionResult(null, 'Timed out waiting for workspace to load'); + } catch (e) { + return new FunctionResult(null, `Failed to wait for workspace to load: ${e}`); + } +} + +/** + * Gets workspace opened in Xcode matching the projectPath or workspacePath + * from the command line arguments. If workspace is not found, return null with + * an error. + * + * @param {!Application} xcode An `Application` (Mac Scripting class) for Xcode. + * @param {!CommandArguments} args + * @param {?bool=} verbose Defaults to false. + * @returns {!FunctionResult} Return either a `WorkspaceDocument` (Xcode Mac + * Scripting class) or null as the `result`. + */ +function getWorkspaceDocument(xcode, args, verbose = false) { + const privatePrefix = '/private'; + + try { + const documents = xcode.workspaceDocuments(); + for (let document of documents) { + const filePath = document.file().toString(); + if (verbose === true) { + console.log(`Workspace: ${filePath}`); + } + if (filePath === args.projectPath || filePath === args.workspacePath) { + return new FunctionResult(document); + } + // Sometimes when the project is in a temporary directory, it'll be + // prefixed with `/private` but the args will not. Remove the + // prefix before matching. + if (filePath.startsWith(privatePrefix) === true) { + const filePathWithoutPrefix = filePath.slice(privatePrefix.length); + if (filePathWithoutPrefix === args.projectPath || filePathWithoutPrefix === args.workspacePath) { + return new FunctionResult(document); + } + } + } + } catch (e) { + return new FunctionResult(null, `Failed to get workspace: ${e}`); + } + return new FunctionResult(null, `Failed to get workspace.`); +} + +/** + * Stops all debug sessions in the target workspace. + * + * @param {!Application} xcode An `Application` (Mac Scripting class) for Xcode. + * @param {!CommandArguments} args + * @returns {!FunctionResult} Always returns null as the `result`. + */ +function stopApp(xcode, args) { + const workspaceResult = getWorkspaceDocument(xcode, args); + if (workspaceResult.error != null) { + return new FunctionResult(null, workspaceResult.error); + } + const targetDocument = workspaceResult.result; + + try { + targetDocument.stop(); + + if (args.closeWindowOnStop === true) { + // Wait a couple seconds before closing Xcode, otherwise it'll prompt the + // user to stop the app. + delay(2); + + targetDocument.close({ + saving: args.promptToSaveBeforeClose === true ? 'ask' : 'no', + }); + } + } catch (e) { + return new FunctionResult(null, `Failed to stop app: ${e}`); + } + return new FunctionResult(null, null); +} + +/** + * Gets resolved build setting for CONFIGURATION_BUILD_DIR and waits until its + * value matches the `--expected-configuration-build-dir` argument. Waits up to + * 2 minutes. + * + * @param {!WorkspaceDocument} targetWorkspace A `WorkspaceDocument` (Xcode Mac + * Scripting class). + * @param {!CommandArguments} args + * @returns {!FunctionResult} Always returns null as the `result`. + */ +function waitForConfigurationBuildDirToUpdate(targetWorkspace, args) { + // Get the project + let project; + try { + project = targetWorkspace.projects().find(x => x.name() == args.projectName); + } catch (e) { + return new FunctionResult(null, `Failed to find project ${args.projectName}: ${e}`); + } + if (project == null) { + return new FunctionResult(null, `Failed to find project ${args.projectName}.`); + } + + // Get the target + let target; + try { + // The target is probably named the same as the project, but if not, just use the first. + const targets = project.targets(); + target = targets.find(x => x.name() == args.projectName); + if (target == null && targets.length > 0) { + target = targets[0]; + if (args.verbose) { + console.log(`Failed to find target named ${args.projectName}, picking first target: ${target.name()}.`); + } + } + } catch (e) { + return new FunctionResult(null, `Failed to find target: ${e}`); + } + if (target == null) { + return new FunctionResult(null, `Failed to find target.`); + } + + try { + // Use the first build configuration (Debug). Any should do since they all + // include Generated.xcconfig. + const buildConfig = target.buildConfigurations()[0]; + const buildSettings = buildConfig.resolvedBuildSettings().reverse(); + + // CONFIGURATION_BUILD_DIR is often at (reverse) index 225 for Xcode + // projects, so check there first. If it's not there, search the build + // settings (which can be a little slow). + const defaultIndex = 225; + let configurationBuildDirSettings; + if (buildSettings[defaultIndex] != null && buildSettings[defaultIndex].name() === 'CONFIGURATION_BUILD_DIR') { + configurationBuildDirSettings = buildSettings[defaultIndex]; + } else { + configurationBuildDirSettings = buildSettings.find(x => x.name() === 'CONFIGURATION_BUILD_DIR'); + } + + if (configurationBuildDirSettings == null) { + // This should not happen, even if it's not set by Flutter, there should + // always be a resolved build setting for CONFIGURATION_BUILD_DIR. + return new FunctionResult(null, `Unable to find CONFIGURATION_BUILD_DIR.`); + } + + // Wait up to 2 minutes for the CONFIGURATION_BUILD_DIR to update to the + // expected value. + const checkFrequencyInSeconds = 0.5; + const maxWaitInSeconds = 2 * 60; // 2 minutes + const verboseLogInterval = 10 * (1 / checkFrequencyInSeconds); + const iterations = maxWaitInSeconds * (1 / checkFrequencyInSeconds); + for (let i = 0; i < iterations; i++) { + const verbose = args.verbose && i % verboseLogInterval === 0; + + const configurationBuildDir = configurationBuildDirSettings.value(); + if (configurationBuildDir === args.expectedConfigurationBuildDir) { + console.log(`CONFIGURATION_BUILD_DIR: ${configurationBuildDir}`); + return new FunctionResult(null, null); + } + if (verbose) { + console.log(`Current CONFIGURATION_BUILD_DIR: ${configurationBuildDir} while expecting ${args.expectedConfigurationBuildDir}`); + } + delay(checkFrequencyInSeconds); + } + return new FunctionResult(null, 'Timed out waiting for CONFIGURATION_BUILD_DIR to update.'); + } catch (e) { + return new FunctionResult(null, `Failed to get CONFIGURATION_BUILD_DIR: ${e}`); + } +} diff --git a/packages/flutter_tools/gradle/build.gradle.kts b/packages/flutter_tools/gradle/build.gradle.kts index 289693f9a478b..517a725f8cb94 100644 --- a/packages/flutter_tools/gradle/build.gradle.kts +++ b/packages/flutter_tools/gradle/build.gradle.kts @@ -31,4 +31,5 @@ dependencies { // * AGP version constants in packages/flutter_tools/lib/src/android/gradle_utils.dart // * AGP version in buildscript block in packages/flutter_tools/gradle/src/main/flutter.groovy compileOnly("com.android.tools.build:gradle:7.3.0") + implementation("org.yaml:snakeyaml:2.0") } diff --git a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy index f6ad3d9515a3d..31eefbeab31ff 100644 --- a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy +++ b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy @@ -31,6 +31,7 @@ import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.bundling.Jar import org.gradle.internal.os.OperatingSystem import org.gradle.util.VersionNumber +import org.yaml.snakeyaml.Yaml /** * For apps only. Provides the flutter extension used in app/build.gradle. @@ -84,6 +85,7 @@ buildscript { // * AGP version constants in packages/flutter_tools/lib/src/android/gradle_utils.dart // * AGP version in dependencies block in packages/flutter_tools/gradle/build.gradle.kts classpath 'com.android.tools.build:gradle:7.3.0' + classpath group: 'org.yaml', name: 'snakeyaml', version: '2.0' } } @@ -731,6 +733,85 @@ class FlutterPlugin implements Plugin<Project> { } } + // Add a task that can be called on Flutter projects that prints application id of a build + // variant. + // + // This task prints the application id in this format: + // + // ApplicationId: com.example.my_id + // + // Format of the output of this task is used by `AndroidProject.getApplicationIdForVariant`. + private static void addTasksForPrintApplicationId(Project project) { + project.android.applicationVariants.all { variant -> + // Warning: The name of this task is used by `AndroidProject.getApplicationIdForVariant`. + project.tasks.register("print${variant.name.capitalize()}ApplicationId") { + description "Prints out application id for the given build variant of this Android project" + doLast { + println "ApplicationId: ${variant.applicationId}"; + } + } + } + } + + // Add a task that can be called on Flutter projects that prints app link domains of a build + // variant. + // + // The app link domains refer to the host attributes of data tags in the apps' intent filters + // that support http/https schemes. See + // https://developer.android.com/guide/topics/manifest/intent-filter-element. + // + // This task prints app link domains in this format: + // + // Domain: domain.com + // Domain: another-domain.dev + // + // Format of the output of this task is used by `AndroidProject.getAppLinkDomainsForVariant`. + private static void addTasksForPrintAppLinkDomains(Project project) { + project.android.applicationVariants.all { variant -> + // Warning: The name of this task is used by `AndroidProject.getAppLinkDomainsForVariant`. + project.tasks.register("print${variant.name.capitalize()}AppLinkDomains") { + description "Prints out app links domain for the given build variant of this Android project" + variant.outputs.all { output -> + def processResources = output.hasProperty("processResourcesProvider") ? + output.processResourcesProvider.get() : output.processResources + dependsOn processResources.name + } + doLast { + variant.outputs.all { output -> + def processResources = output.hasProperty("processResourcesProvider") ? + output.processResourcesProvider.get() : output.processResources + def manifest = new XmlParser().parse(processResources.manifestFile) + manifest.application.activity.each { activity -> + // Find intent filters that have autoVerify = true and support http/https + // scheme. + activity.'intent-filter'.findAll { filter -> + def hasAutoVerify = filter.attributes().any { entry -> + return entry.key.getLocalPart() == "autoVerify" && entry.value + } + def hasHttpOrHttps = filter.data.any { data -> + data.attributes().any { entry -> + return entry.key.getLocalPart() == "scheme" && + (entry.value == "http" || entry.value == "https") + } + } + return hasAutoVerify && hasHttpOrHttps + }.each { appLinkIntent -> + // Print out the host attributes in data tags. + appLinkIntent.data.each { data -> + data.attributes().each { entry -> + if (entry.key.getLocalPart() == "host") { + println "Domain: ${entry.value}" + } + } + } + } + } + } + } + } + } + } + /** * Returns a Flutter build mode suitable for the specified Android buildType. * @@ -904,7 +985,11 @@ class FlutterPlugin implements Plugin<Project> { validateDeferredComponentsValue = project.property('validate-deferred-components').toBoolean() } addTaskForJavaVersion(project) - addTaskForPrintBuildVariants(project) + if(isFlutterAppProject()) { + addTaskForPrintBuildVariants(project) + addTasksForPrintApplicationId(project) + addTasksForPrintAppLinkDomains(project) + } def targetPlatforms = getTargetPlatforms() def addFlutterDeps = { variant -> if (shouldSplitPerAbi()) { @@ -1032,6 +1117,26 @@ class FlutterPlugin implements Plugin<Project> { return } Task copyFlutterAssetsTask = addFlutterDeps(variant) + copyFlutterAssetsTask.doLast { + if (variant.flavorName != null && !variant.flavorName.isEmpty()) { + def outputDir = copyFlutterAssetsTask.destinationDir + def shorebirdYamlFile = new File("${outputDir}/flutter_assets/shorebird.yaml") + def flavor = variant.flavorName + def shorebirdYaml = new Yaml().load(shorebirdYamlFile.text) + def flavorAppId = shorebirdYaml['flavors'][flavor] + if (flavorAppId == null) { + throw new GradleException("Cannot find app_id for ${flavor} in shorebird.yaml") + } + def content = 'app_id: ' + flavorAppId + '\n'; + if (shorebirdYaml.containsKey('base_url')) { + content += 'base_url: ' + shorebirdYaml['base_url'] + '\n'; + } + if (shorebirdYaml.containsKey('auto_update')) { + content += 'auto_update: ' + shorebirdYaml['auto_update'] + '\n'; + } + shorebirdYamlFile.write(content) + } + } def variantOutput = variant.outputs.first() def processResources = variantOutput.hasProperty("processResourcesProvider") ? variantOutput.processResourcesProvider.get() : variantOutput.processResources diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index ab819127598d2..2fb826f17d10f 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -28,7 +28,6 @@ import 'src/commands/doctor.dart'; import 'src/commands/downgrade.dart'; import 'src/commands/drive.dart'; import 'src/commands/emulators.dart'; -import 'src/commands/format.dart'; import 'src/commands/generate.dart'; import 'src/commands/generate_localizations.dart'; import 'src/commands/ide_config.dart'; @@ -200,7 +199,6 @@ List<FlutterCommand> generateCommands({ signals: globals.signals, ), EmulatorsCommand(), - FormatCommand(), GenerateCommand(), GenerateLocalizationsCommand( fileSystem: globals.fs, diff --git a/packages/flutter_tools/lib/src/android/android_app_link_settings.dart b/packages/flutter_tools/lib/src/android/android_app_link_settings.dart new file mode 100644 index 0000000000000..8769fd883f3c6 --- /dev/null +++ b/packages/flutter_tools/lib/src/android/android_app_link_settings.dart @@ -0,0 +1,22 @@ +// 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:meta/meta.dart'; + +/// A data class for app links related project settings. +/// +/// See https://developer.android.com/training/app-links. +@immutable +class AndroidAppLinkSettings { + const AndroidAppLinkSettings({ + required this.applicationId, + required this.domains, + }); + + /// The application id of the android sub-project. + final String applicationId; + + /// The associated web domains of the android sub-project. + final List<String> domains; +} diff --git a/packages/flutter_tools/lib/src/android/android_builder.dart b/packages/flutter_tools/lib/src/android/android_builder.dart index 199a21778db3b..70a7d9ff3bd22 100644 --- a/packages/flutter_tools/lib/src/android/android_builder.dart +++ b/packages/flutter_tools/lib/src/android/android_builder.dart @@ -42,4 +42,16 @@ abstract class AndroidBuilder { /// Returns a list of available build variant from the Android project. Future<List<String>> getBuildVariants({required FlutterProject project}); + + /// Returns the application id for the given build variant. + Future<String> getApplicationIdForVariant( + String buildVariant, { + required FlutterProject project, + }); + + /// Returns a list of app link domains for the given build variant. + Future<List<String>> getAppLinkDomainsForVariant( + String buildVariant, { + required FlutterProject project, + }); } diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart index f684844c64e44..2021b9b5a74ee 100644 --- a/packages/flutter_tools/lib/src/android/android_sdk.dart +++ b/packages/flutter_tools/lib/src/android/android_sdk.dart @@ -38,7 +38,6 @@ class AndroidSdk { reinitialize(); } - static const String javaHomeEnvironmentVariable = 'JAVA_HOME'; /// The Android SDK root directory. final Directory directory; diff --git a/packages/flutter_tools/lib/src/android/android_studio.dart b/packages/flutter_tools/lib/src/android/android_studio.dart index fb824e8819bd1..72d987583bdfe 100644 --- a/packages/flutter_tools/lib/src/android/android_studio.dart +++ b/packages/flutter_tools/lib/src/android/android_studio.dart @@ -28,18 +28,6 @@ import 'android_studio_validator.dart'; final RegExp _dotHomeStudioVersionMatcher = RegExp(r'^\.?(AndroidStudio[^\d]*)([\d.]+)'); -// TODO(andrewkolos): this global variable is used in several places to provide -// a java binary to multiple Java-dependent tools, including the Android SDK -// and Gradle. If this is null, these tools will implicitly fall back to current -// JAVA_HOME env variable and then to any java found on PATH. -// -// This logic is consistent with that used by flutter doctor to find a valid JDK, -// but this consistency is fragile--the implementations of this logic -// exist independently of each other. -// -// See https://github.com/flutter/flutter/issues/124252. -String? get javaPath => globals.androidStudio?.javaPath; - class AndroidStudio { /// A [version] value of null represents an unknown version. AndroidStudio( @@ -173,6 +161,9 @@ class AndroidStudio { /// The path of the JDK bundled with Android Studio. /// /// This will be null if the bundled JDK could not be found or run. + /// + /// If you looking to invoke the java binary or add it to the system + /// environment variables, consider using the [Java] class instead. String? get javaPath => _javaPath; bool get isValid => _isValid; @@ -245,16 +236,7 @@ class AndroidStudio { /// Android Studio found at that location is always returned, even if it is /// invalid. static AndroidStudio? latestValid() { - final String? configuredStudioPath = globals.config.getValue('android-studio-dir') as String?; - if (configuredStudioPath != null && !globals.fs.directory(configuredStudioPath).existsSync()) { - throwToolExit(''' -Could not find the Android Studio installation at the manually configured path "$configuredStudioPath". -Please verify that the path is correct and update it by running this command: flutter config --android-studio-dir '<path>' - -To have flutter search for Android Studio installations automatically, remove -the configured path by running this command: flutter config --android-studio-dir '' -'''); - } + final Directory? configuredStudioDir = _configuredDir(); // Find all available Studio installations. final List<AndroidStudio> studios = allInstalled(); @@ -264,8 +246,8 @@ the configured path by running this command: flutter config --android-studio-dir final AndroidStudio? manuallyConfigured = studios .where((AndroidStudio studio) => studio.configuredPath != null && - configuredStudioPath != null && - _pathsAreEqual(studio.configuredPath!, configuredStudioPath)) + configuredStudioDir != null && + _pathsAreEqual(studio.configuredPath!, configuredStudioDir.path)) .firstOrNull; if (manuallyConfigured != null) { @@ -332,16 +314,14 @@ the configured path by running this command: flutter config --android-studio-dir )); } - final String? configuredStudioDir = globals.config.getValue('android-studio-dir') as String?; - FileSystemEntity? configuredStudioDirAsEntity; + Directory? configuredStudioDir = _configuredDir(); if (configuredStudioDir != null) { - configuredStudioDirAsEntity = globals.fs.directory(configuredStudioDir); - if (configuredStudioDirAsEntity.basename == 'Contents') { - configuredStudioDirAsEntity = configuredStudioDirAsEntity.parent; + if (configuredStudioDir.basename == 'Contents') { + configuredStudioDir = configuredStudioDir.parent; } if (!candidatePaths - .any((FileSystemEntity e) => _pathsAreEqual(e.path, configuredStudioDirAsEntity!.path))) { - candidatePaths.add(configuredStudioDirAsEntity); + .any((FileSystemEntity e) => _pathsAreEqual(e.path, configuredStudioDir!.path))) { + candidatePaths.add(configuredStudioDir); } } @@ -366,13 +346,13 @@ the configured path by running this command: flutter config --android-studio-dir return candidatePaths .map<AndroidStudio?>((FileSystemEntity e) { - if (configuredStudioDirAsEntity == null) { + if (configuredStudioDir == null) { return AndroidStudio.fromMacOSBundle(e.path); } return AndroidStudio.fromMacOSBundle( e.path, - configuredPath: _pathsAreEqual(configuredStudioDirAsEntity.path, e.path) ? configuredStudioDir : null, + configuredPath: _pathsAreEqual(configuredStudioDir.path, e.path) ? configuredStudioDir.path : null, ); }) .whereType<AndroidStudio>() @@ -502,6 +482,38 @@ the configured path by running this command: flutter config --android-studio-dir return studios; } + /// Gets the Android Studio install directory set by the user, if it is configured. + /// + /// The returned [Directory], if not null, is guaranteed to have existed during + /// this function's execution. + static Directory? _configuredDir() { + final String? configuredPath = globals.config.getValue('android-studio-dir') as String?; + if (configuredPath == null) { + return null; + } + final Directory result = globals.fs.directory(configuredPath); + + bool? configuredStudioPathExists; + String? exceptionMessage; + try { + configuredStudioPathExists = result.existsSync(); + } on FileSystemException catch (e) { + exceptionMessage = e.toString(); + } + + if (configuredStudioPathExists == false || exceptionMessage != null) { + throwToolExit(''' +Could not find the Android Studio installation at the manually configured path "$configuredPath". +${exceptionMessage == null ? '' : 'Encountered exception: $exceptionMessage\n\n'} +Please verify that the path is correct and update it by running this command: flutter config --android-studio-dir '<path>' +To have flutter search for Android Studio installations automatically, remove +the configured path by running this command: flutter config --android-studio-dir +'''); + } + + return result; + } + static String? extractStudioPlistValueWithMatcher(String plistValue, RegExp keyMatcher) { return keyMatcher.stringMatch(plistValue)?.split('=').last.trim().replaceAll('"', ''); } diff --git a/packages/flutter_tools/lib/src/android/android_studio_validator.dart b/packages/flutter_tools/lib/src/android/android_studio_validator.dart index 6c8e4a82f5e9e..c35ee5ebb7c12 100644 --- a/packages/flutter_tools/lib/src/android/android_studio_validator.dart +++ b/packages/flutter_tools/lib/src/android/android_studio_validator.dart @@ -16,12 +16,17 @@ const String _androidStudioPreviewTitle = 'Android Studio Preview'; const String _androidStudioPreviewId = 'AndroidStudioPreview'; class AndroidStudioValidator extends DoctorValidator { - AndroidStudioValidator(this._studio, { required FileSystem fileSystem }) - : _fileSystem = fileSystem, + AndroidStudioValidator(this._studio, { + required FileSystem fileSystem, + required UserMessages userMessages, + }) + : _userMessages = userMessages, + _fileSystem = fileSystem, super('Android Studio'); final AndroidStudio _studio; final FileSystem _fileSystem; + final UserMessages _userMessages; static const Map<String, String> idToTitle = <String, String>{ _androidStudioId: _androidStudioTitle, @@ -35,7 +40,7 @@ class AndroidStudioValidator extends DoctorValidator { NoAndroidStudioValidator(config: config, platform: platform, userMessages: userMessages) else ...studios.map<DoctorValidator>( - (AndroidStudio studio) => AndroidStudioValidator(studio, fileSystem: fileSystem) + (AndroidStudio studio) => AndroidStudioValidator(studio, fileSystem: fileSystem, userMessages: userMessages) ), ]; } @@ -45,11 +50,11 @@ class AndroidStudioValidator extends DoctorValidator { final List<ValidationMessage> messages = <ValidationMessage>[]; ValidationType type = ValidationType.missing; - final String? studioVersionText = _studio.version == null - ? null - : userMessages.androidStudioVersion(_studio.version.toString()); + final String studioVersionText = _studio.version == null + ? _userMessages.androidStudioVersion('unknown') + : _userMessages.androidStudioVersion(_studio.version.toString()); messages.add(ValidationMessage( - userMessages.androidStudioLocation(_studio.directory), + _userMessages.androidStudioLocation(_studio.directory), )); if (_studio.pluginsPath != null) { @@ -69,6 +74,10 @@ class AndroidStudioValidator extends DoctorValidator { ); } + if (_studio.version == null) { + messages.add(const ValidationMessage.error('Unable to determine Android Studio version.')); + } + if (_studio.isValid) { type = _hasIssues(messages) ? ValidationType.partial @@ -81,9 +90,9 @@ class AndroidStudioValidator extends DoctorValidator { messages.addAll(_studio.validationMessages.map<ValidationMessage>( (String m) => ValidationMessage.error(m), )); - messages.add(ValidationMessage(userMessages.androidStudioNeedsUpdate)); + messages.add(ValidationMessage(_userMessages.androidStudioNeedsUpdate)); if (_studio.configuredPath != null) { - messages.add(ValidationMessage(userMessages.androidStudioResetDir)); + messages.add(ValidationMessage(_userMessages.androidStudioResetDir)); } } diff --git a/packages/flutter_tools/lib/src/android/android_workflow.dart b/packages/flutter_tools/lib/src/android/android_workflow.dart index daee70ab31c2d..40b96eb33056b 100644 --- a/packages/flutter_tools/lib/src/android/android_workflow.dart +++ b/packages/flutter_tools/lib/src/android/android_workflow.dart @@ -8,7 +8,6 @@ import 'package:process/process.dart'; import '../base/common.dart'; import '../base/context.dart'; -import '../base/file_system.dart'; import '../base/io.dart'; import '../base/logger.dart'; import '../base/platform.dart'; @@ -18,7 +17,6 @@ import '../convert.dart'; import '../doctor_validator.dart'; import '../features.dart'; import 'android_sdk.dart'; -import 'android_studio.dart'; import 'java.dart'; const int kAndroidSdkMinVersion = 29; @@ -75,84 +73,58 @@ class AndroidWorkflow implements Workflow { /// Android Studio. class AndroidValidator extends DoctorValidator { AndroidValidator({ + required Java? java, required AndroidSdk? androidSdk, - required AndroidStudio? androidStudio, - required FileSystem fileSystem, required Logger logger, required Platform platform, - required ProcessManager processManager, required UserMessages userMessages, - }) : _androidSdk = androidSdk, - _androidStudio = androidStudio, - _fileSystem = fileSystem, + }) : _java = java, + _androidSdk = androidSdk, _logger = logger, _platform = platform, - _processManager = processManager, _userMessages = userMessages, super('Android toolchain - develop for Android devices'); + final Java? _java; final AndroidSdk? _androidSdk; - final AndroidStudio? _androidStudio; - final FileSystem _fileSystem; final Logger _logger; final Platform _platform; - final ProcessManager _processManager; final UserMessages _userMessages; @override String get slowWarning => '${_task ?? 'This'} is taking a long time...'; String? _task; - /// Finds the semantic version anywhere in a text. - static final RegExp _javaVersionPattern = RegExp(r'(\d+)(\.(\d+)(\.(\d+))?)?'); - - /// `java -version` response is not only a number, but also includes other - /// information eg. `openjdk version "1.7.0_212"`. - /// This method extracts only the semantic version from that response. - static String? _extractJavaVersion(String? text) { - if (text == null || text.isEmpty) { - return null; - } - final Match? match = _javaVersionPattern.firstMatch(text); - if (match == null) { - return null; - } - return text.substring(match.start, match.end); - } - /// Returns false if we cannot determine the Java version or if the version /// is older that the minimum allowed version of 1.8. - Future<bool> _checkJavaVersion(String javaBinary, List<ValidationMessage> messages) async { + Future<bool> _checkJavaVersion(List<ValidationMessage> messages) async { _task = 'Checking Java status'; try { - if (!_processManager.canRun(javaBinary)) { - messages.add(ValidationMessage.error(_userMessages.androidCantRunJavaBinary(javaBinary))); + if (_java?.binaryPath == null) { + messages.add(ValidationMessage.error(_userMessages.androidMissingJdk)); + return false; + } + messages.add(ValidationMessage(_userMessages.androidJdkLocation(_java!.binaryPath))); + if (!_java!.canRun()) { + messages.add(ValidationMessage.error(_userMessages.androidCantRunJavaBinary(_java!.binaryPath))); return false; } - String? javaVersionText; + Version? javaVersion; try { - // TODO(andrewkolos): Use Java class to find version instead of using duplicate - // code. See https://github.com/flutter/flutter/issues/124252. - _logger.printTrace('java -version'); - final ProcessResult result = await _processManager.run(<String>[javaBinary, '-version']); - if (result.exitCode == 0) { - final List<String> versionLines = (result.stderr as String).split('\n'); - javaVersionText = versionLines.length >= 2 ? versionLines[1] : versionLines[0]; - } + javaVersion = _java!.version; } on Exception catch (error) { _logger.printTrace(error.toString()); } - final Version? javaVersion = Version.parse(_extractJavaVersion(javaVersionText)); - if (javaVersionText == null || javaVersionText.isEmpty || javaVersion == null) { + if (javaVersion == null) { // Could not determine the java version. messages.add(ValidationMessage.error(_userMessages.androidUnknownJavaVersion)); return false; } if (javaVersion < kAndroidJavaMinVersion) { - messages.add(ValidationMessage.error(_userMessages.androidJavaMinimumVersion(javaVersionText))); + messages.add(ValidationMessage.error(_userMessages.androidJavaMinimumVersion(javaVersion.toString()))); return false; } - messages.add(ValidationMessage(_userMessages.androidJavaVersion(javaVersionText))); + messages.add(ValidationMessage(_userMessages.androidJavaVersion(javaVersion.toString()))); return true; } finally { _task = null; @@ -234,22 +206,9 @@ class AndroidValidator extends DoctorValidator { } _task = 'Finding Java binary'; - // Now check for the JDK. - final String? javaBinary = Java.find( - logger: _logger, - androidStudio: _androidStudio, - fileSystem: _fileSystem, - platform: _platform, - processManager: _processManager, - )?.binaryPath; - if (javaBinary == null) { - messages.add(ValidationMessage.error(_userMessages.androidMissingJdk)); - return ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText); - } - messages.add(ValidationMessage(_userMessages.androidJdkLocation(javaBinary))); // Check JDK version. - if (!await _checkJavaVersion(javaBinary, messages)) { + if (!await _checkJavaVersion(messages)) { return ValidationResult(ValidationType.partial, messages, statusInfo: sdkVersionText); } @@ -265,29 +224,23 @@ class AndroidLicenseValidator extends DoctorValidator { required Java? java, required AndroidSdk? androidSdk, required Platform platform, - required FileSystem fileSystem, required ProcessManager processManager, required Logger logger, - required AndroidStudio? androidStudio, required Stdio stdio, required UserMessages userMessages, }) : _java = java, _androidSdk = androidSdk, _platform = platform, - _fileSystem = fileSystem, _processManager = processManager, _logger = logger, - _androidStudio = androidStudio, _stdio = stdio, _userMessages = userMessages, super('Android license subvalidator'); final Java? _java; final AndroidSdk? _androidSdk; - final AndroidStudio? _androidStudio; final Stdio _stdio; final Platform _platform; - final FileSystem _fileSystem; final ProcessManager _processManager; final Logger _logger; final UserMessages _userMessages; @@ -326,13 +279,8 @@ class AndroidLicenseValidator extends DoctorValidator { } Future<bool> _checkJavaVersionNoOutput() async { - final String? javaBinary = Java.find( - logger: _logger, - androidStudio: _androidStudio, - fileSystem: _fileSystem, - platform: _platform, - processManager: _processManager, - )?.binaryPath; + final String? javaBinary = _java?.binaryPath; + if (javaBinary == null) { return false; } diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index bc1f9c9957c2e..6d1e3e7345b12 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -30,17 +30,17 @@ import '../globals.dart' as globals; import '../project.dart'; import '../reporting/reporting.dart'; import 'android_builder.dart'; -import 'android_sdk.dart'; import 'android_studio.dart'; import 'gradle_errors.dart'; import 'gradle_utils.dart'; +import 'java.dart'; import 'migrations/android_studio_java_gradle_conflict_migration.dart'; import 'migrations/top_level_gradle_build_file_migration.dart'; import 'multidex.dart'; -/// The regex to grab variant names from printVariants gradle task +/// The regex to grab variant names from printBuildVariants gradle task /// -/// The task is defined in flutter/packages/flutter_tools/gradle/flutter.gradle. +/// The task is defined in flutter/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy /// /// The expected output from the task should be similar to: /// @@ -49,6 +49,34 @@ import 'multidex.dart'; /// BuildVariant: profile final RegExp _kBuildVariantRegex = RegExp('^BuildVariant: (?<$_kBuildVariantRegexGroupName>.*)\$'); const String _kBuildVariantRegexGroupName = 'variant'; +const String _kBuildVariantTaskName = 'printBuildVariants'; + +/// The regex to grab variant names from print${BuildVariant}ApplicationId gradle task +/// +/// The task is defined in flutter/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy +/// +/// The expected output from the task should be similar to: +/// +/// ApplicationId: com.example.my_id +final RegExp _kApplicationIdRegex = RegExp('^ApplicationId: (?<$_kApplicationIdRegexGroupName>.*)\$'); +const String _kApplicationIdRegexGroupName = 'applicationId'; +String _getPrintApplicationIdTaskFor(String buildVariant) { + return _taskForBuildVariant('print', buildVariant, 'ApplicationId'); +} + +/// The regex to grab app link domains from print${BuildVariant}AppLinkDomains gradle task +/// +/// The task is defined in flutter/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy +/// +/// The expected output from the task should be similar to: +/// +/// Domain: domain.com +/// Domain: another-domain.dev +final RegExp _kAppLinkDomainsRegex = RegExp('^Domain: (?<$_kAppLinkDomainsGroupName>.*)\$'); +const String _kAppLinkDomainsGroupName = 'domain'; +String _getPrintAppLinkDomainsTaskFor(String buildVariant) { + return _taskForBuildVariant('print', buildVariant, 'AppLinkDomains'); +} /// The directory where the APK artifact is generated. Directory getApkDirectory(FlutterProject project) { @@ -89,9 +117,14 @@ Directory getRepoDirectory(Directory buildDirectory) { String _taskFor(String prefix, BuildInfo buildInfo) { final String buildType = camelCase(buildInfo.modeName); final String productFlavor = buildInfo.flavor ?? ''; - return '$prefix${sentenceCase(productFlavor)}${sentenceCase(buildType)}'; + return _taskForBuildVariant(prefix, '$productFlavor${sentenceCase(buildType)}'); } +String _taskForBuildVariant(String prefix, String buildVariant, [String suffix = '']) { + return '$prefix${sentenceCase(buildVariant)}$suffix'; +} + + /// Returns the task to build an APK. @visibleForTesting String getAssembleTaskFor(BuildInfo buildInfo) { @@ -132,6 +165,7 @@ const Duration kMaxRetryTime = Duration(seconds: 10); /// An implementation of the [AndroidBuilder] that delegates to gradle. class AndroidGradleBuilder implements AndroidBuilder { AndroidGradleBuilder({ + required Java? java, required Logger logger, required ProcessManager processManager, required FileSystem fileSystem, @@ -140,7 +174,8 @@ class AndroidGradleBuilder implements AndroidBuilder { required GradleUtils gradleUtils, required Platform platform, required AndroidStudio? androidStudio, - }) : _logger = logger, + }) : _java = java, + _logger = logger, _fileSystem = fileSystem, _artifacts = artifacts, _usage = usage, @@ -149,6 +184,7 @@ class AndroidGradleBuilder implements AndroidBuilder { _fileSystemUtils = FileSystemUtils(fileSystem: fileSystem, platform: platform), _processUtils = ProcessUtils(logger: logger, processManager: processManager); + final Java? _java; final Logger _logger; final ProcessUtils _processUtils; final FileSystem _fileSystem; @@ -237,6 +273,34 @@ class AndroidGradleBuilder implements AndroidBuilder { ); } + Future<RunResult> _runGradleTask( + String taskName, { + List<String> options = const <String>[], + required FlutterProject project + }) async { + final Status status = _logger.startProgress( + "Running Gradle task '$taskName'...", + ); + final List<String> command = <String>[ + _gradleUtils.getExecutable(project), + ...options, // suppresses gradle output. + taskName, + ]; + + RunResult result; + try { + result = await _processUtils.run( + command, + workingDirectory: project.android.hostAppGradleRoot.path, + allowReentrantFlutter: true, + environment: _java?.environment, + ); + } finally { + status.stop(); + } + return result; + } + /// Builds an app. /// /// * [project] is typically [FlutterProject.current()]. @@ -422,15 +486,11 @@ class AndroidGradleBuilder implements AndroidBuilder { ..start(); int exitCode = 1; try { - final String? javaHome = globals.java?.javaHome; exitCode = await _processUtils.stream( command, workingDirectory: project.android.hostAppGradleRoot.path, allowReentrantFlutter: true, - environment: <String, String>{ - if (javaHome != null) - AndroidSdk.javaHomeEnvironmentVariable: javaHome, - }, + environment: _java?.environment, mapFunction: consumeLog, ); } on ProcessException catch (exception) { @@ -692,15 +752,11 @@ class AndroidGradleBuilder implements AndroidBuilder { ..start(); RunResult result; try { - final String? javaHome = globals.java?.javaHome; result = await _processUtils.run( command, workingDirectory: project.android.hostAppGradleRoot.path, allowReentrantFlutter: true, - environment: <String, String>{ - if (javaHome != null) - AndroidSdk.javaHomeEnvironmentVariable: javaHome, - }, + environment: _java?.environment, ); } finally { status.stop(); @@ -732,32 +788,14 @@ class AndroidGradleBuilder implements AndroidBuilder { @override Future<List<String>> getBuildVariants({required FlutterProject project}) async { - final Status status = _logger.startProgress( - "Running Gradle task 'printBuildVariants'...", - ); - final List<String> command = <String>[ - _gradleUtils.getExecutable(project), - '-q', // suppresses gradle output. - 'printBuildVariants', - ]; - final Stopwatch sw = Stopwatch() ..start(); - RunResult result; - try { - final String? javaHome = globals.java?.javaHome; - result = await _processUtils.run( - command, - workingDirectory: project.android.hostAppGradleRoot.path, - allowReentrantFlutter: true, - environment: <String, String>{ - if (javaHome != null) - AndroidSdk.javaHomeEnvironmentVariable: javaHome, - }, - ); - } finally { - status.stop(); - } + final RunResult result = await _runGradleTask( + _kBuildVariantTaskName, + options: const <String>['-q'], + project: project, + ); + _usage.sendTiming('print', 'android build variants', sw.elapsed); if (result.exitCode != 0) { @@ -774,6 +812,65 @@ class AndroidGradleBuilder implements AndroidBuilder { } return options; } + + @override + Future<String> getApplicationIdForVariant( + String buildVariant, { + required FlutterProject project, + }) async { + final String taskName = _getPrintApplicationIdTaskFor(buildVariant); + final Stopwatch sw = Stopwatch() + ..start(); + final RunResult result = await _runGradleTask( + taskName, + options: const <String>['-q'], + project: project, + ); + _usage.sendTiming('print', 'application id', sw.elapsed); + + if (result.exitCode != 0) { + _logger.printStatus(result.stdout, wrap: false); + _logger.printError(result.stderr, wrap: false); + return ''; + } + for (final String line in LineSplitter.split(result.stdout)) { + final RegExpMatch? match = _kApplicationIdRegex.firstMatch(line); + if (match != null) { + return match.namedGroup(_kApplicationIdRegexGroupName)!; + } + } + return ''; + } + + @override + Future<List<String>> getAppLinkDomainsForVariant( + String buildVariant, { + required FlutterProject project, + }) async { + final String taskName = _getPrintAppLinkDomainsTaskFor(buildVariant); + final Stopwatch sw = Stopwatch() + ..start(); + final RunResult result = await _runGradleTask( + taskName, + options: const <String>['-q'], + project: project, + ); + _usage.sendTiming('print', 'application id', sw.elapsed); + + if (result.exitCode != 0) { + _logger.printStatus(result.stdout, wrap: false); + _logger.printError(result.stderr, wrap: false); + return const <String>[]; + } + final List<String> domains = <String>[]; + for (final String line in LineSplitter.split(result.stdout)) { + final RegExpMatch? match = _kAppLinkDomainsRegex.firstMatch(line); + if (match != null) { + domains.add(match.namedGroup(_kAppLinkDomainsGroupName)!); + } + } + return domains; + } } /// Prints how to consume the AAR from a host app. @@ -930,7 +1027,7 @@ Iterable<String> listApkPaths( ]; if (androidBuildInfo.splitPerAbi) { return <String>[ - for (AndroidArch androidArch in androidBuildInfo.targetArchs) + for (final AndroidArch androidArch in androidBuildInfo.targetArchs) <String>[ 'app', androidArch.archName, diff --git a/packages/flutter_tools/lib/src/android/gradle_errors.dart b/packages/flutter_tools/lib/src/android/gradle_errors.dart index e10e433727399..4044baab666ef 100644 --- a/packages/flutter_tools/lib/src/android/gradle_errors.dart +++ b/packages/flutter_tools/lib/src/android/gradle_errors.dart @@ -11,8 +11,6 @@ import '../base/terminal.dart'; import '../globals.dart' as globals; import '../project.dart'; import '../reporting/reporting.dart'; -import 'android_sdk.dart'; -import 'android_studio.dart'; import 'gradle_utils.dart'; import 'multidex.dart'; @@ -85,6 +83,7 @@ final List<GradleHandledError> gradleErrors = <GradleHandledError>[ zipExceptionHandler, incompatibleJavaAndGradleVersionsHandler, remoteTerminatedHandshakeHandler, + couldNotOpenCacheDirectoryHandler, ]; const String _boxTitle = 'Flutter Fix'; @@ -379,10 +378,7 @@ final GradleHandledError flavorUndefinedHandler = GradleHandledError( ], throwOnError: true, workingDirectory: project.android.hostAppGradleRoot.path, - environment: <String, String>{ - if (javaPath != null) - AndroidSdk.javaHomeEnvironmentVariable: javaPath!, - }, + environment: globals.java?.environment, ); // Extract build types and product flavors. final Set<String> variants = <String>{}; @@ -720,3 +716,22 @@ final GradleHandledError remoteTerminatedHandshakeHandler = GradleHandledError( }, eventLabel: 'remote-terminated-handshake', ); + +@visibleForTesting +final GradleHandledError couldNotOpenCacheDirectoryHandler = GradleHandledError( + test: (String line) => line.contains('> Could not open cache directory '), + handler: ({ + required String line, + required FlutterProject project, + required bool usesAndroidX, + required bool multidexEnabled, + }) async { + globals.printError( + '${globals.logger.terminal.warningMark} ' + 'Gradle threw an error while resolving dependencies.' + ); + + return GradleBuildStatus.retry; + }, + eventLabel: 'could-not-open-cache-directory', +); diff --git a/packages/flutter_tools/lib/src/android/java.dart b/packages/flutter_tools/lib/src/android/java.dart index 365b1eff04e28..699fcfddfaaf8 100644 --- a/packages/flutter_tools/lib/src/android/java.dart +++ b/packages/flutter_tools/lib/src/android/java.dart @@ -4,18 +4,20 @@ import 'package:process/process.dart'; +import '../base/config.dart'; import '../base/file_system.dart'; import '../base/logger.dart'; import '../base/os.dart'; import '../base/platform.dart'; import '../base/process.dart'; +import '../base/version.dart'; import 'android_studio.dart'; -const String _javaHomeEnvironmentVariable = 'JAVA_HOME'; -const String _kJavaExecutable = 'java'; +const String _javaExecutable = 'java'; /// Represents an installation of Java. class Java { + Java({ required this.javaHome, required this.binaryPath, @@ -31,6 +33,15 @@ class Java { _processManager = processManager, _processUtils = ProcessUtils(processManager: processManager, logger: logger); + /// Within the Java ecosystem, this environment variable is typically set + /// the install location of a Java Runtime Environment (JRE) or Java + /// Development Kit (JDK). + /// + /// Tools that depend on Java and need to find it will often check this + /// variable. If you are looking to set `JAVA_HOME` when stating a process, + /// consider using the [environment] instance property instead. + static String javaHomeEnvironmentVariable = 'JAVA_HOME'; + /// Finds the Java runtime environment that should be used for all java-dependent /// operations across the tool. /// @@ -41,14 +52,8 @@ class Java { /// 3. the java binary found on PATH. /// /// Returns null if no java binary could be found. - // TODO(andrewkolos): To prevent confusion when debugging Android-related - // issues (see https://github.com/flutter/flutter/issues/122609 for an example), - // this logic should be consistently followed by any Java-dependent operation - // across the the tool (building Android apps, interacting with the Android SDK, etc.). - // Currently, this consistency is fragile since the logic used for building - // Android apps exists independently of this method. - // See https://github.com/flutter/flutter/issues/124252. static Java? find({ + required Config config, required AndroidStudio? androidStudio, required Logger logger, required FileSystem fileSystem, @@ -62,6 +67,7 @@ class Java { processManager: processManager ); final String? home = _findJavaHome( + config: config, logger: logger, androidStudio: androidStudio, platform: platform @@ -89,7 +95,7 @@ class Java { ); } - /// The path of the runtime's home directory. + /// The path of the runtime environments' home directory. /// /// This should only be used for logging and validation purposes. /// If you need to set JAVA_HOME when starting a process, consider @@ -98,7 +104,7 @@ class Java { /// a new method to this class instead. final String? javaHome; - /// The path of the runtime's java binary. + /// The path of the runtime environments' java binary. /// /// This should be only used for logging and validation purposes. /// If you need to invoke the binary directly, consider adding a new method @@ -120,7 +126,7 @@ class Java { /// processes, such as Gradle or Android SDK tools (avdmanager, sdkmanager, etc.) Map<String, String> get environment { return <String, String>{ - if (javaHome != null) _javaHomeEnvironmentVariable: javaHome!, + if (javaHome != null) javaHomeEnvironmentVariable: javaHome!, 'PATH': _fileSystem.path.dirname(binaryPath) + _os.pathVarSeparator + _platform.environment['PATH']!, @@ -129,7 +135,7 @@ class Java { /// Returns the version of java in the format \d(.\d)+(.\d)+ /// Returns null if version could not be determined. - late final JavaVersion? version = (() { + late final Version? version = (() { final RunResult result = _processUtils.runSync( <String>[binaryPath, '--version'], environment: environment, @@ -138,7 +144,38 @@ class Java { _logger.printTrace('java --version failed: exitCode: ${result.exitCode}' ' stdout: ${result.stdout} stderr: ${result.stderr}'); } - return JavaVersion.tryParseFromJavaOutput(result.stdout, logger: _logger); + final String rawVersionOutput = result.stdout; + final List<String> versionLines = rawVersionOutput.split('\n'); + // Should look something like 'openjdk 19.0.2 2023-01-17'. + final String longVersionText = versionLines.length >= 2 ? versionLines[1] : versionLines[0]; + + // The contents that matter come in the format '11.0.18' or '1.8.0_202'. + final RegExp jdkVersionRegex = RegExp(r'\d+\.\d+(\.\d+(?:_\d+)?)?'); + final Iterable<RegExpMatch> matches = + jdkVersionRegex.allMatches(rawVersionOutput); + if (matches.isEmpty) { + _logger.printWarning(_formatJavaVersionWarning(rawVersionOutput)); + return null; + } + final String? version = matches.first.group(0); + if (version == null || version.split('_').isEmpty) { + _logger.printWarning(_formatJavaVersionWarning(rawVersionOutput)); + return null; + } + + // Trim away _d+ from versions 1.8 and below. + final String versionWithoutBuildInfo = version.split('_').first; + + final Version? parsedVersion = Version.parse(versionWithoutBuildInfo); + if (parsedVersion == null) { + return null; + } + return Version.withText( + parsedVersion.major, + parsedVersion.minor, + parsedVersion.patch, + longVersionText, + ); })(); bool canRun() { @@ -147,16 +184,22 @@ class Java { } String? _findJavaHome({ + required Config config, required Logger logger, required AndroidStudio? androidStudio, required Platform platform, }) { + final Object? configured = config.getValue('jdk-dir'); + if (configured != null) { + return configured as String; + } + final String? androidStudioJavaPath = androidStudio?.javaPath; if (androidStudioJavaPath != null) { return androidStudioJavaPath; } - final String? javaHomeEnv = platform.environment[_javaHomeEnvironmentVariable]; + final String? javaHomeEnv = platform.environment[Java.javaHomeEnvironmentVariable]; if (javaHomeEnv != null) { return javaHomeEnv; } @@ -175,7 +218,7 @@ String? _findJavaBinary({ } // Fallback to PATH based lookup. - return operatingSystemUtils.which(_kJavaExecutable)?.path; + return operatingSystemUtils.which(_javaExecutable)?.path; } // Returns a user visible String that says the tool failed to parse @@ -187,44 +230,3 @@ String _formatJavaVersionWarning(String javaVersionRaw) { 'https://github.com/flutter/flutter/issues/ ' 'and if one does not exist file a new issue.'; } - -class JavaVersion { - JavaVersion({ - required this.longText, - required this.number - }); - - /// Typically the first line of the output from `java --version`. - /// For example, `"openjdk 19.0.2 2023-01-17"`. - final String longText; - - /// The version number. For example, `"19.0.2."`. - final String number; - - /// Extracts JDK version from the output of java --version. - static JavaVersion? tryParseFromJavaOutput(String rawVersionOutput, { - required Logger logger, - }) { - final List<String> versionLines = rawVersionOutput.split('\n'); - final String longText = versionLines.length >= 2 ? versionLines[1] : versionLines[0]; - - // The contents that matter come in the format '11.0.18' or '1.8.0_202'. - final RegExp jdkVersionRegex = RegExp(r'\d+\.\d+(\.\d+(?:_\d+)?)?'); - final Iterable<RegExpMatch> matches = - jdkVersionRegex.allMatches(rawVersionOutput); - if (matches.isEmpty) { - logger.printWarning(_formatJavaVersionWarning(rawVersionOutput)); - return null; - } - final String? rawShortText = matches.first.group(0); - if (rawShortText == null || rawShortText.split('_').isEmpty) { - logger.printWarning(_formatJavaVersionWarning(rawVersionOutput)); - return null; - } - - // Trim away _d+ from versions 1.8 and below. - final String shortText = rawShortText.split('_').first; - - return JavaVersion(longText: longText, number: shortText); - } -} diff --git a/packages/flutter_tools/lib/src/android/migrations/android_studio_java_gradle_conflict_migration.dart b/packages/flutter_tools/lib/src/android/migrations/android_studio_java_gradle_conflict_migration.dart index 63bb6c4940e5f..0a8307171336f 100644 --- a/packages/flutter_tools/lib/src/android/migrations/android_studio_java_gradle_conflict_migration.dart +++ b/packages/flutter_tools/lib/src/android/migrations/android_studio_java_gradle_conflict_migration.dart @@ -99,14 +99,12 @@ class AndroidStudioJavaGradleConflictMigration extends ProjectMigrator { return; } - final String? javaVersionString = _java?.version?.number; - final Version? javaVersion = Version.parse(javaVersionString); - if (javaVersion == null) { + if (_java?.version == null) { logger.printTrace(javaVersionNotFound); return; } - if (javaVersion.major != flamingoBundledJava.major) { + if (_java!.version!.major != flamingoBundledJava.major) { logger.printTrace(javaVersionNot17); return; } diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index 5ae97796840c7..7c8f054517cdb 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -166,9 +166,8 @@ class ManifestAssetBundle implements AssetBundle { DateTime? _lastBuildTimestamp; // We assume the main asset is designed for a device pixel ratio of 1.0. - static const double _defaultResolution = 1.0; static const String _kAssetManifestJsonFilename = 'AssetManifest.json'; - static const String _kAssetManifestBinFilename = 'AssetManifest.smcbin'; + static const String _kAssetManifestBinFilename = 'AssetManifest.bin'; static const String _kNoticeFile = 'NOTICES'; // Comically, this can't be name with the more common .gz file extension @@ -597,7 +596,7 @@ class ManifestAssetBundle implements AssetBundle { if (packageName == null) ...manifest.fontsDescriptor else - for (Font font in _parsePackageFonts( + for (final Font font in _parsePackageFonts( manifest, packageName, packageConfig, @@ -688,7 +687,7 @@ class ManifestAssetBundle implements AssetBundle { DevFSByteContent _createAssetManifestBinary( Map<String, List<String>> assetManifest ) { - double parseScale(String key) { + double? parseScale(String key) { final Uri assetUri = Uri.parse(key); String directoryPath = ''; if (assetUri.pathSegments.length > 1) { @@ -699,7 +698,8 @@ class ManifestAssetBundle implements AssetBundle { if (match != null && match.groupCount > 0) { return double.parse(match.group(1)!); } - return _defaultResolution; + + return null; } final Map<String, dynamic> result = <String, dynamic>{}; @@ -708,15 +708,12 @@ class ManifestAssetBundle implements AssetBundle { final List<dynamic> resultVariants = <dynamic>[]; final List<String> entries = (manifestEntry.value as List<dynamic>).cast<String>(); for (final String variant in entries) { - if (variant == manifestEntry.key) { - // With the newer binary format, don't include the main asset in it's - // list of variants. This reduces parsing time at runtime. - continue; - } final Map<String, dynamic> resultVariant = <String, dynamic>{}; - final double variantDevicePixelRatio = parseScale(variant); + final double? variantDevicePixelRatio = parseScale(variant); resultVariant['asset'] = variant; - resultVariant['dpr'] = variantDevicePixelRatio; + if (variantDevicePixelRatio != null) { + resultVariant['dpr'] = variantDevicePixelRatio; + } resultVariants.add(resultVariant); } result[manifestEntry.key] = resultVariants; diff --git a/packages/flutter_tools/lib/src/base/logger.dart b/packages/flutter_tools/lib/src/base/logger.dart index f8bac32f289d5..2c418a3b9dc0e 100644 --- a/packages/flutter_tools/lib/src/base/logger.dart +++ b/packages/flutter_tools/lib/src/base/logger.dart @@ -46,7 +46,8 @@ abstract class Logger { /// since the last time it was set to false. bool hadErrorOutput = false; - /// If true, then [printWarning] has been called at least once for this logger + /// If true, then [printWarning] has been called at least once with its + /// "fatal" argument true for this logger /// since the last time it was reset to false. bool hadWarningOutput = false; @@ -124,6 +125,7 @@ abstract class Logger { int? indent, int? hangingIndent, bool? wrap, + bool fatal = true, }); /// Display normal output of the command. This should be used for things like @@ -314,6 +316,7 @@ class DelegatingLogger implements Logger { int? indent, int? hangingIndent, bool? wrap, + bool fatal = true, }) { _delegate.printWarning( message, @@ -322,6 +325,7 @@ class DelegatingLogger implements Logger { indent: indent, hangingIndent: hangingIndent, wrap: wrap, + fatal: fatal, ); } @@ -481,8 +485,9 @@ class StdoutLogger extends Logger { int? indent, int? hangingIndent, bool? wrap, + bool fatal = true, }) { - hadWarningOutput = true; + hadWarningOutput = hadWarningOutput || fatal; _status?.pause(); message = wrapText(message, indent: indent, @@ -820,8 +825,9 @@ class BufferLogger extends Logger { int? indent, int? hangingIndent, bool? wrap, + bool fatal = true, }) { - hadWarningOutput = true; + hadWarningOutput = hadWarningOutput || fatal; _warning.writeln(terminal.color( wrapText(message, indent: indent, @@ -965,6 +971,7 @@ class VerboseLogger extends DelegatingLogger { int? indent, int? hangingIndent, bool? wrap, + bool fatal = true, }) { hadWarningOutput = true; _emit( diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart index 1aa91309852e2..46d0ef38fa2e5 100644 --- a/packages/flutter_tools/lib/src/base/utils.dart +++ b/packages/flutter_tools/lib/src/base/utils.dart @@ -59,7 +59,8 @@ String sentenceCase(String str, [String? locale]) { if (str.isEmpty) { return str; } - return toBeginningOfSentenceCase(str, locale)!; + // TODO(christopherfujino): Remove this check after the next release of intl + return ArgumentError.checkNotNull(toBeginningOfSentenceCase(str, locale)); } /// Converts `foo_bar` to `Foo Bar`. diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index 5c0f357f4d0c5..053197c8f1a17 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -38,7 +38,7 @@ class BuildInfo { this.webRenderer = WebRendererMode.auto, required this.treeShakeIcons, this.performanceMeasurementFile, - this.dartDefineConfigJsonMap, + this.dartDefineConfigJsonMap = const <String, Object?>{}, this.packagesPath = '.dart_tool/package_config.json', // TODO(zanderso): make this required and remove the default. this.nullSafetyMode = NullSafetyMode.sound, this.codeSizeDirectory, @@ -144,7 +144,7 @@ class BuildInfo { /// /// An additional field `dartDefineConfigJsonMap` is provided to represent the native JSON value of the configuration file /// - final Map<String, Object>? dartDefineConfigJsonMap; + final Map<String, Object?> dartDefineConfigJsonMap; /// If provided, an output directory where one or more v8-style heap snapshots /// will be written for code size profiling. @@ -267,7 +267,7 @@ class BuildInfo { /// Fields that are `null` are excluded from this configuration. Map<String, String> toEnvironmentConfig() { final Map<String, String> map = <String, String>{}; - dartDefineConfigJsonMap?.forEach((String key, Object value) { + dartDefineConfigJsonMap.forEach((String key, Object? value) { map[key] = '$value'; }); final Map<String, String> environmentMap = <String, String>{ @@ -324,20 +324,18 @@ class BuildInfo { '-Pbundle-sksl-path=$bundleSkSLPath', if (codeSizeDirectory != null) '-Pcode-size-directory=$codeSizeDirectory', - for (String projectArg in androidProjectArgs) + for (final String projectArg in androidProjectArgs) '-P$projectArg', ]; - if (dartDefineConfigJsonMap != null) { - final Iterable<String> gradleConfKeys = result.map((final String gradleConf) => gradleConf.split('=')[0].substring(2)); - dartDefineConfigJsonMap!.forEach((String key, Object value) { - if (gradleConfKeys.contains(key)) { - globals.printWarning( - 'The key: [$key] already exists, you cannot use gradle variables that have been used by the system!'); - } else { - result.add('-P$key=$value'); - } - }); - } + final Iterable<String> gradleConfKeys = result.map((final String gradleConf) => gradleConf.split('=')[0].substring(2)); + dartDefineConfigJsonMap.forEach((String key, Object? value) { + if (gradleConfKeys.contains(key)) { + globals.printWarning( + 'The key: [$key] already exists, you cannot use gradle variables that have been used by the system!'); + } else { + result.add('-P$key=$value'); + } + }); return result; } } diff --git a/packages/flutter_tools/lib/src/build_system/source.dart b/packages/flutter_tools/lib/src/build_system/source.dart index ffcc82321d06c..5233365b548fe 100644 --- a/packages/flutter_tools/lib/src/build_system/source.dart +++ b/packages/flutter_tools/lib/src/build_system/source.dart @@ -178,7 +178,7 @@ class SourceVisitor implements ResolvedFiles { .getArtifactPath(artifact, platform: platform, mode: mode); if (environment.fileSystem.isDirectorySync(path)) { sources.addAll(<File>[ - for (FileSystemEntity entity in environment.fileSystem.directory(path).listSync(recursive: true)) + for (final FileSystemEntity entity in environment.fileSystem.directory(path).listSync(recursive: true)) if (entity is File) entity, ]); @@ -206,7 +206,7 @@ class SourceVisitor implements ResolvedFiles { final FileSystemEntity entity = environment.artifacts.getHostArtifact(artifact); if (entity is Directory) { sources.addAll(<File>[ - for (FileSystemEntity entity in entity.listSync(recursive: true)) + for (final FileSystemEntity entity in entity.listSync(recursive: true)) if (entity is File) entity, ]); diff --git a/packages/flutter_tools/lib/src/build_system/targets/assets.dart b/packages/flutter_tools/lib/src/build_system/targets/assets.dart index e18234158524b..dc9c9aff91005 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/assets.dart @@ -78,6 +78,7 @@ Future<Depfile> copyAssets( logger: environment.logger, fileSystem: environment.fileSystem, artifacts: environment.artifacts, + targetPlatform: targetPlatform, ); final ShaderCompiler shaderCompiler = ShaderCompiler( processManager: environment.processManager, diff --git a/packages/flutter_tools/lib/src/build_system/targets/common.dart b/packages/flutter_tools/lib/src/build_system/targets/common.dart index 82eca5b9ba6e8..039bb5a7fdd6f 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart @@ -206,6 +206,21 @@ class KernelSnapshot extends Target { forceLinkPlatform = false; } + final String? targetOS = switch (targetPlatform) { + TargetPlatform.fuchsia_arm64 || TargetPlatform.fuchsia_x64 => 'fuchsia', + TargetPlatform.android || + TargetPlatform.android_arm || + TargetPlatform.android_arm64 || + TargetPlatform.android_x64 || + TargetPlatform.android_x86 => + 'android', + TargetPlatform.darwin => 'macos', + TargetPlatform.ios => 'ios', + TargetPlatform.linux_arm64 || TargetPlatform.linux_x64 => 'linux', + TargetPlatform.windows_x64 => 'windows', + TargetPlatform.tester || TargetPlatform.web_javascript => null, + }; + final PackageConfig packageConfig = await loadPackageConfigWithLogging( packagesFile, logger: environment.logger, @@ -234,6 +249,7 @@ class KernelSnapshot extends Target { dartDefines: decodeDartDefines(environment.defines, kDartDefines), packageConfig: packageConfig, buildDir: environment.buildDir, + targetOS: targetOS, checkDartPluginRegistry: environment.generateDartPluginRegistry, ); if (output == null || output.errorCount != 0) { diff --git a/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart b/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart index d8e557eff9d12..7f3b0dee8bd56 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/icon_tree_shaker.dart @@ -42,11 +42,13 @@ class IconTreeShaker { required Logger logger, required FileSystem fileSystem, required Artifacts artifacts, + required TargetPlatform targetPlatform, }) : _processManager = processManager, _logger = logger, _fs = fileSystem, _artifacts = artifacts, - _fontManifest = fontManifest?.string { + _fontManifest = fontManifest?.string, + _targetPlatform = targetPlatform { if (_environment.defines[kIconTreeShakerFlag] == 'true' && _environment.defines[kBuildMode] == 'debug') { logger.printError('Font subsetting is not supported in debug mode. The ' @@ -82,6 +84,7 @@ class IconTreeShaker { final Logger _logger; final FileSystem _fs; final Artifacts _artifacts; + final TargetPlatform _targetPlatform; /// Whether font subsetting should be used for this [Environment]. bool get enabled => _fontManifest != null @@ -129,15 +132,21 @@ class IconTreeShaker { } final Map<String, _IconTreeShakerData> result = <String, _IconTreeShakerData>{}; + const int kSpacePoint = 32; for (final MapEntry<String, String> entry in fonts.entries) { final List<int>? codePoints = iconData[entry.key]; if (codePoints == null) { throw IconTreeShakerException._('Expected to font code points for ${entry.key}, but none were found.'); } + + // Add space as an optional code point, as web uses it to measure the font height. + final List<int> optionalCodePoints = _targetPlatform == TargetPlatform.web_javascript + ? <int>[kSpacePoint] : <int>[]; result[entry.value] = _IconTreeShakerData( family: entry.key, relativePath: entry.value, codePoints: codePoints, + optionalCodePoints: optionalCodePoints, ); } _iconData = result; @@ -155,7 +164,6 @@ class IconTreeShaker { required String outputPath, required String relativePath, }) async { - if (!enabled) { return false; } @@ -189,12 +197,17 @@ class IconTreeShaker { outputPath, input.path, ]; - final String codePoints = iconTreeShakerData.codePoints.join(' '); + final Iterable<String> requiredCodePointStrings = iconTreeShakerData.codePoints + .map((int codePoint) => codePoint.toString()); + final Iterable<String> optionalCodePointStrings = iconTreeShakerData.optionalCodePoints + .map((int codePoint) => 'optional:$codePoint'); + final String codePointsString = requiredCodePointStrings + .followedBy(optionalCodePointStrings).join(' '); _logger.printTrace('Running font-subset: ${cmd.join(' ')}, ' - 'using codepoints $codePoints'); + 'using codepoints $codePointsString'); final Process fontSubsetProcess = await _processManager.start(cmd); try { - fontSubsetProcess.stdin.writeln(codePoints); + fontSubsetProcess.stdin.writeln(codePointsString); await fontSubsetProcess.stdin.flush(); await fontSubsetProcess.stdin.close(); } on Exception { @@ -361,6 +374,7 @@ class _IconTreeShakerData { required this.family, required this.relativePath, required this.codePoints, + required this.optionalCodePoints, }); /// The font family name, e.g. "MaterialIcons". @@ -372,6 +386,10 @@ class _IconTreeShakerData { /// The list of code points for the font. final List<int> codePoints; + /// The list of code points to be optionally added, if they exist in the + /// input font. Otherwise, the tool will silently omit them. + final List<int> optionalCodePoints; + @override String toString() => 'FontSubsetData($family, $relativePath, $codePoints)'; } diff --git a/packages/flutter_tools/lib/src/build_system/targets/ios.dart b/packages/flutter_tools/lib/src/build_system/targets/ios.dart index 3f57f85762643..2ced8b0164798 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -665,7 +665,7 @@ Future<void> _createStubAppFramework(File outputFile, Environment environment, await globals.xcode!.clang(<String>[ '-x', 'c', - for (String arch in iosArchNames ?? <String>{}) ...<String>['-arch', arch], + for (final String arch in iosArchNames ?? <String>{}) ...<String>['-arch', arch], stubSource.path, '-dynamiclib', // Keep version in sync with AOTSnapshotter flag diff --git a/packages/flutter_tools/lib/src/build_system/targets/localizations.dart b/packages/flutter_tools/lib/src/build_system/targets/localizations.dart index f2c083f98bf1e..01e4792a32dfb 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/localizations.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/localizations.dart @@ -57,12 +57,14 @@ class GenerateLocalizationsTarget extends Target { logger: environment.logger, defaultArbDir: defaultArbDir, ); - generateLocalizations( + await generateLocalizations( logger: environment.logger, options: options, projectDir: environment.projectDir, dependenciesDir: environment.buildDir, fileSystem: environment.fileSystem, + artifacts: environment.artifacts, + processManager: environment.processManager, ); final Map<String, Object?> dependencies = json.decode( @@ -74,12 +76,12 @@ class GenerateLocalizationsTarget extends Target { <File>[ configFile, if (inputs != null) - for (Object inputFile in inputs.whereType<Object>()) + for (final Object inputFile in inputs.whereType<Object>()) environment.fileSystem.file(inputFile), ], <File>[ if (outputs != null) - for (Object outputFile in outputs.whereType<Object>()) + for (final Object outputFile in outputs.whereType<Object>()) environment.fileSystem.file(outputFile), ], ); diff --git a/packages/flutter_tools/lib/src/bundle_builder.dart b/packages/flutter_tools/lib/src/bundle_builder.dart index afa1530fc0555..680eac7c67cca 100644 --- a/packages/flutter_tools/lib/src/bundle_builder.dart +++ b/packages/flutter_tools/lib/src/bundle_builder.dart @@ -21,7 +21,6 @@ import 'devfs.dart'; import 'globals.dart' as globals; import 'project.dart'; - /// Provides a `build` method that builds the bundle. class BundleBuilder { /// Builds the bundle for the given target platform. diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart index a8069426a8d8c..13efbde879dde 100644 --- a/packages/flutter_tools/lib/src/cache.dart +++ b/packages/flutter_tools/lib/src/cache.dart @@ -278,6 +278,9 @@ class Cache { // Whether to cache the unsigned mac binaries. Defaults to caching the signed binaries. bool useUnsignedMacBinaries = false; + // Whether the warning printed when a custom artifact URL is used is fatal. + bool fatalStorageWarning = true; + static RandomAccessFile? _lock; static bool _lockEnabled = true; @@ -340,12 +343,11 @@ class Cache { // version --machine"). It's not really a "warning" though, so print it // in grey. Also, make sure that it isn't counted as a warning for // Logger.warningsAreFatal. - final bool oldWarnings = _logger.hadWarningOutput; _logger.printWarning( 'Waiting for another flutter command to release the startup lock...', color: TerminalColor.grey, + fatal: false, ); - _logger.hadWarningOutput = oldWarnings; printed = true; } await Future<void>.delayed(const Duration(milliseconds: 50)); @@ -522,6 +524,7 @@ class Cache { _logger.printWarning( 'Flutter assets will be downloaded from $overrideUrl. Make sure you trust this source!', emphasis: true, + fatal: false, ); _hasWarnedAboutStorageOverride = true; } @@ -1290,7 +1293,7 @@ String flattenNameSubdirs(Uri url, FileSystem fileSystem) { /// something that doesn't. String _flattenNameNoSubdirs(String fileName) { final List<int> replacedCodeUnits = <int>[ - for (int codeUnit in fileName.codeUnits) + for (final int codeUnit in fileName.codeUnits) ..._flattenNameSubstitutions[codeUnit] ?? <int>[codeUnit], ]; return String.fromCharCodes(replacedCodeUnits); diff --git a/packages/flutter_tools/lib/src/commands/analyze.dart b/packages/flutter_tools/lib/src/commands/analyze.dart index a24f6bfb2d594..2c1c63798bf3c 100644 --- a/packages/flutter_tools/lib/src/commands/analyze.dart +++ b/packages/flutter_tools/lib/src/commands/analyze.dart @@ -176,7 +176,6 @@ class AnalyzeCommand extends FlutterCommand { } else if (boolArg('watch')) { await AnalyzeContinuously( argResults!, - runner!.getRepoRoots(), runner!.getRepoPackages(), fileSystem: _fileSystem, logger: _logger, @@ -189,7 +188,6 @@ class AnalyzeCommand extends FlutterCommand { } else { await AnalyzeOnce( argResults!, - runner!.getRepoRoots(), runner!.getRepoPackages(), workingDirectory: workingDirectory, fileSystem: _fileSystem, diff --git a/packages/flutter_tools/lib/src/commands/analyze_base.dart b/packages/flutter_tools/lib/src/commands/analyze_base.dart index e68597c081c2a..44dcf7cff7699 100644 --- a/packages/flutter_tools/lib/src/commands/analyze_base.dart +++ b/packages/flutter_tools/lib/src/commands/analyze_base.dart @@ -20,7 +20,6 @@ import '../globals.dart' as globals; /// Common behavior for `flutter analyze` and `flutter analyze --watch` abstract class AnalyzeBase { AnalyzeBase(this.argResults, { - required this.repoRoots, required this.repoPackages, required this.fileSystem, required this.logger, @@ -34,8 +33,6 @@ abstract class AnalyzeBase { /// The parsed argument results for execution. final ArgResults argResults; @protected - final List<String> repoRoots; - @protected final List<Directory> repoPackages; @protected final FileSystem fileSystem; @@ -52,6 +49,9 @@ abstract class AnalyzeBase { @protected final bool suppressAnalytics; + @protected + String get flutterRoot => globals.fs.path.absolute(Cache.flutterRoot!); + /// Called by [AnalyzeCommand] to start the analysis process. Future<void> analyze(); diff --git a/packages/flutter_tools/lib/src/commands/analyze_continuously.dart b/packages/flutter_tools/lib/src/commands/analyze_continuously.dart index ea9eb18febf2e..a6c3c2ececf70 100644 --- a/packages/flutter_tools/lib/src/commands/analyze_continuously.dart +++ b/packages/flutter_tools/lib/src/commands/analyze_continuously.dart @@ -13,7 +13,6 @@ import 'analyze_base.dart'; class AnalyzeContinuously extends AnalyzeBase { AnalyzeContinuously( super.argResults, - List<String> repoRoots, List<Directory> repoPackages, { required super.fileSystem, required super.logger, @@ -24,7 +23,6 @@ class AnalyzeContinuously extends AnalyzeBase { required super.suppressAnalytics, }) : super( repoPackages: repoPackages, - repoRoots: repoRoots, ); String? analysisTarget; @@ -43,13 +41,10 @@ class AnalyzeContinuously extends AnalyzeBase { final PackageDependencyTracker dependencies = PackageDependencyTracker(); dependencies.checkForConflictingDependencies(repoPackages, dependencies); - directories = repoRoots; + directories = <String>[flutterRoot]; analysisTarget = 'Flutter repository'; logger.printTrace('Analyzing Flutter repository:'); - for (final String projectPath in repoRoots) { - logger.printTrace(' ${fileSystem.path.relative(projectPath)}'); - } } else { directories = <String>[fileSystem.currentDirectory.path]; analysisTarget = fileSystem.currentDirectory.path; diff --git a/packages/flutter_tools/lib/src/commands/analyze_once.dart b/packages/flutter_tools/lib/src/commands/analyze_once.dart index 8e3fcac9ed0a1..6002761ef7404 100644 --- a/packages/flutter_tools/lib/src/commands/analyze_once.dart +++ b/packages/flutter_tools/lib/src/commands/analyze_once.dart @@ -4,7 +4,6 @@ import 'dart:async'; - import '../base/common.dart'; import '../base/file_system.dart'; import '../base/logger.dart'; @@ -14,7 +13,6 @@ import 'analyze_base.dart'; class AnalyzeOnce extends AnalyzeBase { AnalyzeOnce( super.argResults, - List<String> repoRoots, List<Directory> repoPackages, { required super.fileSystem, required super.logger, @@ -25,7 +23,6 @@ class AnalyzeOnce extends AnalyzeBase { required super.suppressAnalytics, this.workingDirectory, }) : super( - repoRoots: repoRoots, repoPackages: repoPackages, ); @@ -42,7 +39,7 @@ class AnalyzeOnce extends AnalyzeBase { // check for conflicting dependencies final PackageDependencyTracker dependencies = PackageDependencyTracker(); dependencies.checkForConflictingDependencies(repoPackages, dependencies); - items.addAll(repoRoots); + items.add(flutterRoot); if (argResults.wasParsed('current-package') && (argResults['current-package'] as bool)) { items.add(currentDirectory); } diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index ba2b528dd7e9b..1f9a212c07679 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart @@ -258,7 +258,7 @@ class AssembleCommand extends FlutterCommand { results[kExtraGenSnapshotOptions] = (argumentResults[FlutterOptions.kExtraGenSnapshotOptions] as List<String>).join(','); } - final Map<String, Object>? defineConfigJsonMap = extractDartDefineConfigJsonMap(); + final Map<String, Object?> defineConfigJsonMap = extractDartDefineConfigJsonMap(); final List<String> dartDefines = extractDartDefines(defineConfigJsonMap: defineConfigJsonMap); if (dartDefines.isNotEmpty){ results[kDartDefines] = dartDefines.join(','); diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart index 7e9fba29929a3..6bd6539181917 100644 --- a/packages/flutter_tools/lib/src/commands/attach.dart +++ b/packages/flutter_tools/lib/src/commands/attach.dart @@ -33,6 +33,7 @@ import '../resident_runner.dart'; import '../run_cold.dart'; import '../run_hot.dart'; import '../runner/flutter_command.dart'; +import '../runner/flutter_command_runner.dart'; import '../vmservice.dart'; /// A Flutter-command that attaches to applications that have been launched @@ -528,6 +529,7 @@ known, it can be explicitly provided to attach via the command-line, e.g. ddsPort: ddsPort, devToolsServerAddress: devToolsServerAddress, serveObservatory: serveObservatory, + usingCISystem: usingCISystem, ); return buildInfo.isDebug @@ -535,7 +537,7 @@ known, it can be explicitly provided to attach via the command-line, e.g. flutterDevices, target: targetFile, debuggingOptions: debuggingOptions, - packagesFilePath: globalResults!['packages'] as String?, + packagesFilePath: globalResults![FlutterGlobalOptions.kPackagesOption] as String?, projectRootPath: stringArg('project-root'), dillOutputPath: stringArg('output-dill'), ipv6: usesIpv6, diff --git a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart index 20b48db98bbfd..ea3c50bbfa9c1 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios_framework.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios_framework.dart @@ -147,7 +147,7 @@ abstract class BuildFrameworkCommand extends BuildSubCommand { 'xcrun', 'xcodebuild', '-create-xcframework', - for (Directory framework in frameworks) ...<String>[ + for (final Directory framework in frameworks) ...<String>[ '-framework', framework.path, ...framework.parent diff --git a/packages/flutter_tools/lib/src/commands/channel.dart b/packages/flutter_tools/lib/src/commands/channel.dart index 5a8695ed16438..1c54fa1513b3b 100644 --- a/packages/flutter_tools/lib/src/commands/channel.dart +++ b/packages/flutter_tools/lib/src/commands/channel.dart @@ -3,11 +3,15 @@ // found in the LICENSE file. import '../base/common.dart'; +import '../base/process.dart'; import '../cache.dart'; import '../globals.dart' as globals; import '../runner/flutter_command.dart'; +import '../runner/flutter_command_runner.dart'; import '../version.dart'; +import 'upgrade.dart' show precacheArtifacts; + class ChannelCommand extends FlutterCommand { ChannelCommand({ bool verboseHelp = false }) { argParser.addFlag( @@ -16,6 +20,12 @@ class ChannelCommand extends FlutterCommand { help: 'Include all the available branches (including local branches) when listing channels.', hide: !verboseHelp, ); + argParser.addFlag( + 'cache-artifacts', + help: 'After switching channels, download all required binary artifacts. ' + 'This is the equivalent of running "flutter precache" with the "--all-platforms" flag.', + defaultsTo: true, + ); } @override @@ -40,7 +50,7 @@ class ChannelCommand extends FlutterCommand { case 0: await _listChannels( showAll: boolArg('all'), - verbose: globalResults?['verbose'] == true, + verbose: globalResults?[FlutterGlobalOptions.kVerboseFlag] == true, ); return FlutterCommandResult.success(); case 1: @@ -133,6 +143,9 @@ class ChannelCommand extends FlutterCommand { globals.printStatus('This is not an official channel. For a list of available channels, try "flutter channel".'); } await _checkout(branchName); + if (boolArg('cache-artifacts')) { + await precacheArtifacts(Cache.flutterRoot); + } globals.printStatus("Successfully switched to flutter channel '$branchName'."); globals.printStatus("To ensure that you're on the latest build from this channel, run 'flutter upgrade'"); } @@ -148,36 +161,35 @@ class ChannelCommand extends FlutterCommand { static Future<void> _checkout(String branchName) async { // Get latest refs from upstream. - int result = await globals.processUtils.stream( + RunResult runResult = await globals.processUtils.run( <String>['git', 'fetch'], workingDirectory: Cache.flutterRoot, - prefix: 'git: ', ); - if (result == 0) { - result = await globals.processUtils.stream( + if (runResult.processResult.exitCode == 0) { + runResult = await globals.processUtils.run( <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/$branchName'], workingDirectory: Cache.flutterRoot, - prefix: 'git: ', ); - if (result == 0) { + if (runResult.processResult.exitCode == 0) { // branch already exists, try just switching to it - result = await globals.processUtils.stream( + runResult = await globals.processUtils.run( <String>['git', 'checkout', branchName, '--'], workingDirectory: Cache.flutterRoot, - prefix: 'git: ', ); } else { // branch does not exist, we have to create it - result = await globals.processUtils.stream( + runResult = await globals.processUtils.run( <String>['git', 'checkout', '--track', '-b', branchName, 'origin/$branchName'], workingDirectory: Cache.flutterRoot, - prefix: 'git: ', ); } } - if (result != 0) { - throwToolExit('Switching channels failed with error code $result.', exitCode: result); + if (runResult.processResult.exitCode != 0) { + throwToolExit( + 'Switching channels failed\n$runResult.', + exitCode: runResult.processResult.exitCode, + ); } else { // Remove the version check stamp, since it could contain out-of-date // information that pertains to the previous channel. diff --git a/packages/flutter_tools/lib/src/commands/config.dart b/packages/flutter_tools/lib/src/commands/config.dart index 4b4769897e21d..ae03cf38616b6 100644 --- a/packages/flutter_tools/lib/src/commands/config.dart +++ b/packages/flutter_tools/lib/src/commands/config.dart @@ -4,6 +4,7 @@ import '../../src/android/android_sdk.dart'; import '../../src/android/android_studio.dart'; +import '../android/java.dart'; import '../base/common.dart'; import '../convert.dart'; import '../features.dart'; @@ -19,7 +20,12 @@ class ConfigCommand extends FlutterCommand { negatable: false, help: 'Clear the saved development certificate choice used to sign apps for iOS device deployment.'); argParser.addOption('android-sdk', help: 'The Android SDK directory.'); - argParser.addOption('android-studio-dir', help: 'The Android Studio install directory. If unset, flutter will search for valid installs at well-known locations.'); + argParser.addOption('android-studio-dir', help: 'The Android Studio installation directory. If unset, flutter will search for valid installations at well-known locations.'); + argParser.addOption('jdk-dir', help: 'The Java Development Kit (JDK) installation directory. ' + 'If unset, flutter will search for one in the following order:\n' + ' 1) the JDK bundled with the latest installation of Android Studio,\n' + ' 2) the JDK found at the directory found in the JAVA_HOME environment variable, and\n' + " 3) the directory containing the java binary found in the user's path."); argParser.addOption('build-dir', help: 'The relative path to override a projects build directory.', valueHelp: 'out/'); argParser.addFlag('machine', @@ -101,7 +107,7 @@ class ConfigCommand extends FlutterCommand { @override Future<FlutterCommandResult> runCommand() async { - final List<String> rest = argResults?.rest ?? <String>[]; + final List<String> rest = argResults!.rest; if (rest.isNotEmpty) { throwToolExit(exitCode: 2, 'error: flutter config: Too many arguments.\n' @@ -126,7 +132,7 @@ class ConfigCommand extends FlutterCommand { return FlutterCommandResult.success(); } - if (argResults?.wasParsed('analytics') ?? false) { + if (argResults!.wasParsed('analytics')) { final bool value = boolArg('analytics'); // The tool sends the analytics event *before* toggling the flag // intentionally to be sure that opt-out events are sent correctly. @@ -146,19 +152,23 @@ class ConfigCommand extends FlutterCommand { await globals.analytics.setTelemetry(value); } - if (argResults?.wasParsed('android-sdk') ?? false) { + if (argResults!.wasParsed('android-sdk')) { _updateConfig('android-sdk', stringArg('android-sdk')!); } - if (argResults?.wasParsed('android-studio-dir') ?? false) { + if (argResults!.wasParsed('android-studio-dir')) { _updateConfig('android-studio-dir', stringArg('android-studio-dir')!); } - if (argResults?.wasParsed('clear-ios-signing-cert') ?? false) { + if (argResults!.wasParsed('jdk-dir')) { + _updateConfig('jdk-dir', stringArg('jdk-dir')!); + } + + if (argResults!.wasParsed('clear-ios-signing-cert')) { _updateConfig('ios-signing-cert', ''); } - if (argResults?.wasParsed('build-dir') ?? false) { + if (argResults!.wasParsed('build-dir')) { final String buildDir = stringArg('build-dir')!; if (globals.fs.path.isAbsolute(buildDir)) { throwToolExit('build-dir should be a relative path'); @@ -171,7 +181,7 @@ class ConfigCommand extends FlutterCommand { if (configSetting == null) { continue; } - if (argResults?.wasParsed(configSetting) ?? false) { + if (argResults!.wasParsed(configSetting)) { final bool keyValue = boolArg(configSetting); globals.config.setValue(configSetting, keyValue); globals.printStatus('Setting "$configSetting" value to "$keyValue".'); @@ -203,6 +213,10 @@ class ConfigCommand extends FlutterCommand { if (results['android-sdk'] == null && androidSdk != null) { results['android-sdk'] = androidSdk.directory.path; } + final Java? java = globals.java; + if (results['jdk-dir'] == null && java != null) { + results['jdk-dir'] = java.javaHome; + } globals.printStatus(const JsonEncoder.withIndent(' ').convert(results)); } diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index 9d84e81505499..66a9e1127e600 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -682,7 +682,7 @@ Your $application code is in $relativeAppMain. List<String> _getSupportedPlatformsFromTemplateContext(Map<String, Object?> templateContext) { return <String>[ - for (String platform in kAllCreatePlatforms) + for (final String platform in kAllCreatePlatforms) if (templateContext[platform] == true) platform, ]; } diff --git a/packages/flutter_tools/lib/src/commands/custom_devices.dart b/packages/flutter_tools/lib/src/commands/custom_devices.dart index dee0ee7745ce8..3f07e7ff734fe 100644 --- a/packages/flutter_tools/lib/src/commands/custom_devices.dart +++ b/packages/flutter_tools/lib/src/commands/custom_devices.dart @@ -25,6 +25,7 @@ import '../custom_devices/custom_devices_config.dart'; import '../device_port_forwarder.dart'; import '../features.dart'; import '../runner/flutter_command.dart'; +import '../runner/flutter_command_runner.dart'; /// just the function signature of the [print] function. /// The Object arg may be null. @@ -811,7 +812,7 @@ Delete a device from the config file. Future<FlutterCommandResult> runCommand() async { checkFeatureEnabled(); - final String? id = globalResults!['device-id'] as String?; + final String? id = globalResults![FlutterGlobalOptions.kDeviceIdOption] as String?; if (id == null || !customDevicesConfig.contains(id)) { throwToolExit('Couldn\'t find device with id "$id" in config at "${customDevicesConfig.configPath}"'); } diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index 5791cd0216e18..ab1659fecb95e 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -1250,6 +1250,7 @@ class NotifyingLogger extends DelegatingLogger { int? indent, int? hangingIndent, bool? wrap, + bool fatal = true, }) { _sendMessage(LogMessage('warning', message)); } diff --git a/packages/flutter_tools/lib/src/commands/doctor.dart b/packages/flutter_tools/lib/src/commands/doctor.dart index 43c1f313eaaae..7e7e3eaf8cf1f 100644 --- a/packages/flutter_tools/lib/src/commands/doctor.dart +++ b/packages/flutter_tools/lib/src/commands/doctor.dart @@ -33,7 +33,6 @@ class DoctorCommand extends FlutterCommand { @override Future<FlutterCommandResult> runCommand() async { - globals.flutterVersion.fetchTagsAndUpdate(); if (argResults?.wasParsed('check-for-remote-artifacts') ?? false) { final String engineRevision = stringArg('check-for-remote-artifacts')!; if (engineRevision.startsWith(RegExp(r'[a-f0-9]{1,40}'))) { diff --git a/packages/flutter_tools/lib/src/commands/downgrade.dart b/packages/flutter_tools/lib/src/commands/downgrade.dart index a27482aa87902..a58b75c009862 100644 --- a/packages/flutter_tools/lib/src/commands/downgrade.dart +++ b/packages/flutter_tools/lib/src/commands/downgrade.dart @@ -92,7 +92,10 @@ class DowngradeCommand extends FlutterCommand { String workingDirectory = Cache.flutterRoot!; if (argResults!.wasParsed('working-directory')) { workingDirectory = stringArg('working-directory')!; - _flutterVersion = FlutterVersion(workingDirectory: workingDirectory); + _flutterVersion = FlutterVersion( + fs: _fileSystem!, + flutterRoot: workingDirectory, + ); } final String currentChannel = _flutterVersion!.channel; diff --git a/packages/flutter_tools/lib/src/commands/format.dart b/packages/flutter_tools/lib/src/commands/format.dart deleted file mode 100644 index c4ea165d145b4..0000000000000 --- a/packages/flutter_tools/lib/src/commands/format.dart +++ /dev/null @@ -1,39 +0,0 @@ -// 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:args/args.dart'; - -import '../base/common.dart'; -import '../runner/flutter_command.dart'; - -class FormatCommand extends FlutterCommand { - FormatCommand(); - - @override - ArgParser argParser = ArgParser.allowAnything(); - - @override - final String name = 'format'; - - @override - List<String> get aliases => const <String>['dartfmt']; - - @override - String get description => deprecationWarning; - - @override - final bool hidden = true; - - @override - String get deprecationWarning { - return 'The "format" command is deprecated. Please use the "dart format" ' - 'sub-command instead, which has the same command-line usage as ' - '"flutter format".\n'; - } - - @override - Future<FlutterCommandResult> runCommand() async { - throwToolExit(deprecationWarning); - } -} diff --git a/packages/flutter_tools/lib/src/commands/generate_localizations.dart b/packages/flutter_tools/lib/src/commands/generate_localizations.dart index a51eaf78cd140..a7931f0618f5f 100644 --- a/packages/flutter_tools/lib/src/commands/generate_localizations.dart +++ b/packages/flutter_tools/lib/src/commands/generate_localizations.dart @@ -5,9 +5,7 @@ import 'package:process/process.dart'; import '../artifacts.dart'; -import '../base/common.dart'; import '../base/file_system.dart'; -import '../base/io.dart'; import '../base/logger.dart'; import '../localizations/gen_l10n.dart'; import '../localizations/localizations_utils.dart'; @@ -247,35 +245,14 @@ class GenerateLocalizationsCommand extends FlutterCommand { } // Run the localizations generator. - final LocalizationsGenerator generator = generateLocalizations( + await generateLocalizations( logger: _logger, options: options, projectDir: _fileSystem.currentDirectory, fileSystem: _fileSystem, + artifacts: _artifacts, + processManager: _processManager, ); - final List<String> outputFileList = generator.outputFileList; - final File? untranslatedMessagesFile = generator.untranslatedMessagesFile; - - // All other post processing. - if (options.format) { - if (outputFileList.isEmpty) { - return FlutterCommandResult.success(); - } - final List<String> formatFileList = outputFileList.toList(); - if (untranslatedMessagesFile != null) { - // Don't format the messages file using `dart format`. - formatFileList.remove(untranslatedMessagesFile.absolute.path); - } - if (formatFileList.isEmpty) { - return FlutterCommandResult.success(); - } - final String dartBinary = _artifacts.getArtifactPath(Artifact.engineDartBinary); - final List<String> command = <String>[dartBinary, 'format', ...formatFileList]; - final ProcessResult result = await _processManager.run(command); - if (result.exitCode != 0) { - throwToolExit('Formatting failed: $result', exitCode: result.exitCode); - } - } return FlutterCommandResult.success(); } diff --git a/packages/flutter_tools/lib/src/commands/precache.dart b/packages/flutter_tools/lib/src/commands/precache.dart index f0a2d16d3a313..a5d305eb130c5 100644 --- a/packages/flutter_tools/lib/src/commands/precache.dart +++ b/packages/flutter_tools/lib/src/commands/precache.dart @@ -22,8 +22,11 @@ class PrecacheCommand extends FlutterCommand { _platform = platform, _logger = logger, _featureFlags = featureFlags { - argParser.addFlag('all-platforms', abbr: 'a', negatable: false, - help: 'Precache artifacts for all host platforms.'); + argParser.addFlag('all-platforms', + abbr: 'a', + negatable: false, + help: 'Precache artifacts for all host platforms.', + aliases: const <String>['all']); argParser.addFlag('force', abbr: 'f', negatable: false, help: 'Force re-downloading of artifacts.'); argParser.addFlag('android', diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index 1cc7c3c828167..61829b16ebbe2 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -26,6 +26,7 @@ import '../resident_runner.dart'; import '../run_cold.dart'; import '../run_hot.dart'; import '../runner/flutter_command.dart'; +import '../runner/flutter_command_runner.dart'; import '../tracing.dart'; import '../vmservice.dart'; import '../web/web_runner.dart'; @@ -247,6 +248,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment uninstallFirst: uninstallFirst, enableDartProfiling: enableDartProfiling, enableEmbedderApi: enableEmbedderApi, + usingCISystem: usingCISystem, ); } else { return DebuggingOptions.enabled( @@ -298,6 +300,7 @@ abstract class RunCommandBase extends FlutterCommand with DeviceBasedDevelopment serveObservatory: boolArg('serve-observatory'), enableDartProfiling: enableDartProfiling, enableEmbedderApi: enableEmbedderApi, + usingCISystem: usingCISystem, ); } } @@ -643,7 +646,7 @@ class RunCommand extends RunCommandBase { : globals.fs.file(applicationBinaryPath), trackWidgetCreation: trackWidgetCreation, projectRootPath: stringArg('project-root'), - packagesFilePath: globalResults!['packages'] as String?, + packagesFilePath: globalResults![FlutterGlobalOptions.kPackagesOption] as String?, dillOutputPath: stringArg('output-dill'), ipv6: ipv6 ?? false, multidexEnabled: boolArg('multidex'), diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index fda2142294129..8bd076edcd5d1 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -422,6 +422,7 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { disablePortPublication: true, enableDds: enableDds, nullAssertions: boolArg(FlutterOptions.kNullAssertions), + usingCISystem: usingCISystem, ); Device? integrationTestDevice; @@ -540,7 +541,11 @@ class TestCommand extends FlutterCommand with DeviceBasedDevelopmentArtifacts { } bool _needRebuild(Map<String, DevFSContent> entries) { - final File manifest = globals.fs.file(globals.fs.path.join('build', 'unit_test_assets', 'AssetManifest.json')); + // TODO(andrewkolos): This logic might fail in the future if we change the + // schema of the contents of the asset manifest file and the user does not + // perform a `flutter clean` after upgrading. + // See https://github.com/flutter/flutter/issues/128563. + final File manifest = globals.fs.file(globals.fs.path.join('build', 'unit_test_assets', 'AssetManifest.bin')); if (!manifest.existsSync()) { return true; } diff --git a/packages/flutter_tools/lib/src/commands/update_packages.dart b/packages/flutter_tools/lib/src/commands/update_packages.dart index 006707faeece1..5d240051fba88 100644 --- a/packages/flutter_tools/lib/src/commands/update_packages.dart +++ b/packages/flutter_tools/lib/src/commands/update_packages.dart @@ -38,10 +38,6 @@ const Map<String, String> kManuallyPinnedDependencies = <String, String>{ 'url_launcher_android': '6.0.17', // https://github.com/flutter/flutter/issues/115660 'archive': '3.3.2', - // https://github.com/flutter/flutter/issues/116376 - 'path_provider_android': '2.0.21', - // https://github.com/flutter/flutter/issues/122039 - 'flutter_plugin_android_lifecycle': '2.0.8', }; class UpdatePackagesCommand extends FlutterCommand { @@ -53,6 +49,14 @@ class UpdatePackagesCommand extends FlutterCommand { 'This will actually modify the pubspec.yaml files in your checkout.', negatable: false, ) + ..addOption( + 'cherry-pick-package', + help: 'Attempt to update only the specified package. The "-cherry-pick-version" version must be specified also.', + ) + ..addOption( + 'cherry-pick-version', + help: 'Attempt to update the package to the specified version. The "--cherry-pick-package" option must be specified also.', + ) ..addFlag( 'paths', help: 'Finds paths in the dependency chain leading from package specified ' @@ -184,7 +188,8 @@ class UpdatePackagesCommand extends FlutterCommand { final bool isVerifyOnly = boolArg('verify-only'); final bool isConsumerOnly = boolArg('consumer-only'); final bool offline = boolArg('offline'); - final bool doUpgrade = forceUpgrade || isPrintPaths || isPrintTransitiveClosure; + final String? cherryPickPackage = stringArg('cherry-pick-package'); + final String? cherryPickVersion = stringArg('cherry-pick-version'); if (boolArg('crash')) { throw StateError('test crash please ignore.'); @@ -196,6 +201,54 @@ class UpdatePackagesCommand extends FlutterCommand { ); } + if (forceUpgrade && cherryPickPackage != null) { + throwToolExit( + '--force-upgrade cannot be used with the --cherry-pick-package flag' + ); + } + + if (forceUpgrade && isPrintPaths) { + throwToolExit( + '--force-upgrade cannot be used with the --paths flag' + ); + } + + if (forceUpgrade && isPrintTransitiveClosure) { + throwToolExit( + '--force-upgrade cannot be used with the --transitive-closure flag' + ); + } + + if (cherryPickPackage != null && offline) { + throwToolExit( + '--cherry-pick-package cannot be used with the --offline flag' + ); + } + + if (cherryPickPackage != null && cherryPickVersion == null) { + throwToolExit( + '--cherry-pick-version is required when using --cherry-pick-package flag' + ); + } + + if (isPrintPaths && (stringArg('from') == null || stringArg('to') == null)) { + throwToolExit( + 'The --from and --to flags are required when using the --paths flag' + ); + } + + if (!isPrintPaths && (stringArg('from') != null || stringArg('to') != null)) { + throwToolExit( + 'The --from and --to flags are only allowed when using the --paths flag' + ); + } + + if (isPrintTransitiveClosure && isPrintPaths) { + throwToolExit( + 'The --transitive-closure flag cannot be used with the --paths flag' + ); + } + // "consumer" packages are those that constitute our public API (e.g. flutter, flutter_test, flutter_driver, flutter_localizations, integration_test). if (isConsumerOnly) { if (!isPrintTransitiveClosure) { @@ -218,7 +271,7 @@ class UpdatePackagesCommand extends FlutterCommand { return FlutterCommandResult.success(); } - if (doUpgrade) { + if (forceUpgrade) { // This feature attempts to collect all the packages used across all the // pubspec.yamls in the repo (including via transitive dependencies), and // find the latest version of each that can be used while keeping each @@ -237,9 +290,38 @@ class UpdatePackagesCommand extends FlutterCommand { explicitDependencies: explicitDependencies, allDependencies: allDependencies, specialDependencies: specialDependencies, - doUpgrade: doUpgrade, + printPaths: forceUpgrade || isPrintPaths || isPrintTransitiveClosure || cherryPickPackage != null, ); + final Iterable<PubspecDependency> baseDependencies; + if (cherryPickPackage != null) { + if (!allDependencies.containsKey(cherryPickPackage)) { + throwToolExit( + 'Package "$cherryPickPackage" is not currently a dependency, and therefore cannot be upgraded.' + ); + } + if (cherryPickVersion != null) { + globals.printStatus('Pinning package "$cherryPickPackage" to version "$cherryPickVersion"...'); + } else { + globals.printStatus('Upgrading package "$cherryPickPackage"...'); + } + final List<PubspecDependency> adjustedDependencies = <PubspecDependency>[]; + for (final String package in allDependencies.keys) { + if (package == cherryPickPackage) { + assert(cherryPickVersion != null); + final PubspecDependency pubspec = allDependencies[cherryPickPackage]!; + adjustedDependencies.add(pubspec.copyWith(version: cherryPickVersion)); + } else { + adjustedDependencies.add(allDependencies[package]!); + } + } + baseDependencies = adjustedDependencies; + } else if (forceUpgrade) { + baseDependencies = explicitDependencies.values; + } else { + baseDependencies = allDependencies.values; + } + // Now that we have all the dependencies we care about, we are going to // create a fake package and then run either "pub upgrade", if requested, // followed by "pub get" on it. If upgrading, the pub tool will attempt to @@ -248,12 +330,14 @@ class UpdatePackagesCommand extends FlutterCommand { // attempt to download any necessary package versions to the pub cache to // warm the cache. final PubDependencyTree tree = PubDependencyTree(); // object to collect results - await _generateFakePackage( + await _pubGetAllDependencies( tempDir: _syntheticPackageDir, - dependencies: doUpgrade ? explicitDependencies.values : allDependencies.values, + dependencies: baseDependencies, pubspecs: pubspecs, tree: tree, - doUpgrade: doUpgrade, + doUpgrade: forceUpgrade, + isolateEnvironment: forceUpgrade || isPrintPaths || isPrintTransitiveClosure || cherryPickPackage != null, + reportDependenciesToTree: forceUpgrade || isPrintPaths || isPrintTransitiveClosure || cherryPickPackage != null, ); // Only delete the synthetic package if it was done in a temp directory @@ -261,18 +345,31 @@ class UpdatePackagesCommand extends FlutterCommand { _syntheticPackageDir.deleteSync(recursive: true); } - if (doUpgrade) { - final bool done = _upgradePubspecs( + if (forceUpgrade || isPrintTransitiveClosure || isPrintPaths || cherryPickPackage != null) { + _processPubspecs( tree: tree, pubspecs: pubspecs, - explicitDependencies: explicitDependencies, specialDependencies: specialDependencies, ); - if (done) { - // Complete early if we were just printing data. + if (isPrintTransitiveClosure) { + tree._dependencyTree.forEach((String from, Set<String> to) { + globals.printStatus('$from -> $to'); + }); + return FlutterCommandResult.success(); + } + + if (isPrintPaths) { + showDependencyPaths(from: stringArg('from')!, to: stringArg('to')!, tree: tree); return FlutterCommandResult.success(); } + + globals.printStatus('Updating workspace...'); + _updatePubspecs( + tree: tree, + pubspecs: pubspecs, + specialDependencies: specialDependencies, + ); } await _runPubGetOnPackages(packages); @@ -308,8 +405,9 @@ class UpdatePackagesCommand extends FlutterCommand { // we need to run update-packages to recapture the transitive deps. globals.printWarning( 'Warning: pubspec in ${directory.path} has updated or new dependencies. ' - 'Please run "flutter update-packages --force-upgrade" to update them correctly ' - '(checksum ${pubspec.checksum.value} != $checksum).' + 'Please run "flutter update-packages --force-upgrade" to update them correctly.' + // DO NOT PRINT THE CHECKSUM HERE. + // It causes people to ignore the requirement to actually run the script. ); needsUpdate = true; } else { @@ -333,11 +431,11 @@ class UpdatePackagesCommand extends FlutterCommand { required Set<String> specialDependencies, required Map<String, PubspecDependency> explicitDependencies, required Map<String, PubspecDependency> allDependencies, - required bool doUpgrade, + required bool printPaths, }) { // Visit all the directories with pubspec.yamls we care about. for (final Directory directory in packages) { - if (doUpgrade) { + if (printPaths) { globals.printTrace('Reading pubspec.yaml from: ${directory.path}'); } final PubspecYaml pubspec = PubspecYaml(directory); // this parses the pubspec.yaml @@ -406,12 +504,14 @@ class UpdatePackagesCommand extends FlutterCommand { } } - Future<void> _generateFakePackage({ + Future<void> _pubGetAllDependencies({ required Directory tempDir, required Iterable<PubspecDependency> dependencies, required List<PubspecYaml> pubspecs, required PubDependencyTree tree, required bool doUpgrade, + required bool isolateEnvironment, + required bool reportDependenciesToTree, }) async { Directory? temporaryFlutterSdk; final Directory syntheticPackageDir = tempDir.childDirectory('synthetic_package'); @@ -423,9 +523,10 @@ class UpdatePackagesCommand extends FlutterCommand { doUpgrade: doUpgrade, ), ); - // Create a synthetic flutter SDK so that transitive flutter SDK - // constraints are not affected by this upgrade. - if (doUpgrade) { + + if (isolateEnvironment) { + // Create a synthetic flutter SDK so that transitive flutter SDK + // constraints are not affected by this upgrade. temporaryFlutterSdk = createTemporaryFlutterSdk( globals.logger, globals.fs, @@ -435,8 +536,10 @@ class UpdatePackagesCommand extends FlutterCommand { ); } - // Next we run "pub get" on it in order to force the download of any + // Run "pub get" on it in order to force the download of any // needed packages to the pub cache, upgrading if requested. + // TODO(ianh): If this fails, the tool exits silently. + // It can fail, e.g., if --cherry-pick-version is invalid. await pub.get( context: PubContext.updatePackages, project: FlutterProject.fromDirectory(syntheticPackageDir), @@ -446,9 +549,9 @@ class UpdatePackagesCommand extends FlutterCommand { outputMode: PubOutputMode.none, ); - if (doUpgrade) { - // If upgrading, we run "pub deps --style=compact" on the result. We - // pipe all the output to tree.fill(), which parses it so that it can + if (reportDependenciesToTree) { + // Run "pub deps --style=compact" on the result. + // We pipe all the output to tree.fill(), which parses it so that it can // create a graph of all the dependencies so that we can figure out the // transitive dependencies later. It also remembers which version was // selected for each package. @@ -461,40 +564,29 @@ class UpdatePackagesCommand extends FlutterCommand { } } - bool _upgradePubspecs({ + void _processPubspecs({ required PubDependencyTree tree, required List<PubspecYaml> pubspecs, required Set<String> specialDependencies, - required Map<String, PubspecDependency> explicitDependencies, }) { - // The transitive dependency tree for the fake package does not contain - // dependencies between Flutter SDK packages and pub packages. We add them - // here. for (final PubspecYaml pubspec in pubspecs) { final String package = pubspec.name; specialDependencies.add(package); tree._versions[package] = pubspec.version; - assert(!tree._dependencyTree.containsKey(package)); - tree._dependencyTree[package] = <String>{}; for (final PubspecDependency dependency in pubspec.dependencies) { if (dependency.kind == DependencyKind.normal) { + tree._dependencyTree[package] ??= <String>{}; tree._dependencyTree[package]!.add(dependency.name); } } } + } - if (boolArg('transitive-closure')) { - tree._dependencyTree.forEach((String from, Set<String> to) { - globals.printStatus('$from -> $to'); - }); - return true; - } - - if (boolArg('paths')) { - showDependencyPaths(from: stringArg('from')!, to: stringArg('to')!, tree: tree); - return true; - } - + bool _updatePubspecs({ + required PubDependencyTree tree, + required List<PubspecYaml> pubspecs, + required Set<String> specialDependencies, + }) { // Now that we have collected all the data, we can apply our dependency // versions to each pubspec.yaml that we collected. This mutates the // pubspec.yaml files. @@ -664,7 +756,6 @@ enum DependencyKind { /// pubspec.yaml file. const String kTransitiveMagicString= '# THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"'; - /// This is the string output before a checksum of the packages used. const String kDependencyChecksum = '# PUBSPEC CHECKSUM: '; @@ -1290,6 +1381,28 @@ class PubspecDependency extends PubspecLine { static const String _sdkPrefix = ' sdk: '; static const String _gitPrefix = ' git:'; + PubspecDependency copyWith({ + String? line, + String? name, + String? suffix, + bool? isTransitive, + DependencyKind? kind, + String? version, + String? sourcePath, + bool? isDevDependency, + }) { + return PubspecDependency( + line ?? this.line, + name ?? this.name, + suffix ?? this.suffix, + isTransitive: isTransitive ?? this.isTransitive, + kind: kind ?? this.kind, + version: version ?? this.version, + sourcePath: sourcePath ?? this.sourcePath, + isDevDependency: isDevDependency ?? this.isDevDependency, + ); + } + /// Whether the dependency points to a package in the Flutter SDK. /// /// There are two ways one can point to a Flutter package: @@ -1355,16 +1468,16 @@ class PubspecDependency extends PubspecLine { /// This generates the entry for this dependency for the pubspec.yaml for the /// fake package that we'll use to get the version numbers figured out. /// - /// When called with [doUpgrade] as [true], the version constrains will be set - /// to >= whatever the previous version was. If [doUpgrade] is [false], then + /// When called with [allowUpgrade] as [true], the version constrains will be set + /// to >= whatever the previous version was. If [allowUpgrade] is [false], then /// the previous version is used again as an exact pin. - void describeForFakePubspec(StringBuffer dependencies, StringBuffer overrides, { bool doUpgrade = true }) { + void describeForFakePubspec(StringBuffer dependencies, StringBuffer overrides, { bool allowUpgrade = true }) { final String versionToUse; // This should only happen when manually adding new dependencies; otherwise // versions should always be pinned exactly if (version.isEmpty || version == 'any') { versionToUse = 'any'; - } else if (doUpgrade) { + } else if (allowUpgrade) { // Must wrap in quotes for Yaml parsing versionToUse = "'>= $version'"; } else { @@ -1447,7 +1560,7 @@ String generateFakePubspec( // Don't add pinned dependency if it is not in the set of all transitive dependencies. if (!allTransitive.contains(package)) { if (verbose) { - globals.printStatus('Skipping $package because it was not transitive'); + globals.printStatus(' - $package: $version (skipped because it was not a transitive dependency)'); } return; } @@ -1459,7 +1572,7 @@ String generateFakePubspec( } for (final PubspecDependency dependency in dependencies) { if (!dependency.pointsToSdk) { - dependency.describeForFakePubspec(result, overrides, doUpgrade: doUpgrade); + dependency.describeForFakePubspec(result, overrides, allowUpgrade: doUpgrade); } } result.write(overrides.toString()); diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart index e8f75bb5e5e8a..d9918e385efdc 100644 --- a/packages/flutter_tools/lib/src/commands/upgrade.dart +++ b/packages/flutter_tools/lib/src/commands/upgrade.dart @@ -77,10 +77,14 @@ class UpgradeCommand extends FlutterCommand { force: boolArg('force'), continueFlow: boolArg('continue'), testFlow: stringArg('working-directory') != null, - gitTagVersion: GitTagVersion.determine(globals.processUtils, globals.platform), + gitTagVersion: GitTagVersion.determine( + globals.processUtils, + globals.platform, + workingDirectory: _commandRunner.workingDirectory, + ), flutterVersion: stringArg('working-directory') == null ? globals.flutterVersion - : FlutterVersion(workingDirectory: _commandRunner.workingDirectory), + : FlutterVersion(flutterRoot: _commandRunner.workingDirectory!, fs: globals.fs), verifyOnly: boolArg('verify-only'), ); } @@ -88,7 +92,6 @@ class UpgradeCommand extends FlutterCommand { @visibleForTesting class UpgradeCommandRunner { - String? workingDirectory; // set in runCommand() above Future<FlutterCommandResult> runCommand({ @@ -205,7 +208,7 @@ class UpgradeCommandRunner { // Make sure the welcome message re-display is delayed until the end. final PersistentToolState persistentToolState = globals.persistentToolState!; persistentToolState.setShouldRedisplayWelcomeMessage(false); - await precacheArtifacts(); + await precacheArtifacts(workingDirectory); await updatePackages(flutterVersion); await runDoctor(); // Force the welcome message to re-display following the upgrade. @@ -298,7 +301,11 @@ class UpgradeCommandRunner { 'for instructions.' ); } - return FlutterVersion(workingDirectory: workingDirectory, frameworkRevision: revision); + return FlutterVersion.fromRevision( + flutterRoot: workingDirectory!, + frameworkRevision: revision, + fs: globals.fs, + ); } /// Attempts a hard reset to the given revision. @@ -318,27 +325,6 @@ class UpgradeCommandRunner { } } - /// Update the engine repository and precache all artifacts. - /// - /// Check for and download any engine and pkg/ updates. We run the 'flutter' - /// shell script reentrantly here so that it will download the updated - /// Dart and so forth if necessary. - Future<void> precacheArtifacts() async { - globals.printStatus(''); - globals.printStatus('Upgrading engine...'); - final int code = await globals.processUtils.stream( - <String>[ - globals.fs.path.join('bin', 'flutter'), '--no-color', '--no-version-check', 'precache', - ], - workingDirectory: workingDirectory, - allowReentrantFlutter: true, - environment: Map<String, String>.of(globals.platform.environment), - ); - if (code != 0) { - throwToolExit(null, exitCode: code); - } - } - /// Update the user's packages. Future<void> updatePackages(FlutterVersion flutterVersion) async { globals.printStatus(''); @@ -367,3 +353,24 @@ class UpgradeCommandRunner { ); } } + +/// Update the engine repository and precache all artifacts. +/// +/// Check for and download any engine and pkg/ updates. We run the 'flutter' +/// shell script reentrantly here so that it will download the updated +/// Dart and so forth if necessary. +Future<void> precacheArtifacts([String? workingDirectory]) async { + globals.printStatus(''); + globals.printStatus('Upgrading engine...'); + final int code = await globals.processUtils.stream( + <String>[ + globals.fs.path.join('bin', 'flutter'), '--no-color', '--no-version-check', 'precache', + ], + allowReentrantFlutter: true, + environment: Map<String, String>.of(globals.platform.environment), + workingDirectory: workingDirectory, + ); + if (code != 0) { + throwToolExit(null, exitCode: code); + } +} diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index 666a55f249805..6774080a54586 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart @@ -233,6 +233,7 @@ class KernelCompiler { String? initializeFromDill, String? platformDill, Directory? buildDir, + String? targetOS, bool checkDartPluginRegistry = false, required String? packagesPath, required BuildMode buildMode, @@ -293,6 +294,11 @@ class KernelCompiler { if (aot) ...<String>[ '--aot', '--tfa', + // The --target-os flag only makes sense for whole program compilation. + if (targetOS != null) ...<String>[ + '--target-os', + targetOS, + ], ], if (packagesPath != null) ...<String>[ '--packages', @@ -396,17 +402,25 @@ class _CompileExpressionRequest extends _CompilationRequest { super.completer, this.expression, this.definitions, + this.definitionTypes, this.typeDefinitions, + this.typeBounds, + this.typeDefaults, this.libraryUri, this.klass, + this.method, this.isStatic, ); String expression; List<String>? definitions; + List<String>? definitionTypes; List<String>? typeDefinitions; + List<String>? typeBounds; + List<String>? typeDefaults; String? libraryUri; String? klass; + String? method; bool isStatic; @override @@ -506,9 +520,13 @@ abstract class ResidentCompiler { Future<CompilerOutput?> compileExpression( String expression, List<String>? definitions, + List<String>? definitionTypes, List<String>? typeDefinitions, + List<String>? typeBounds, + List<String>? typeDefaults, String? libraryUri, String? klass, + String? method, bool isStatic, ); @@ -835,9 +853,13 @@ class DefaultResidentCompiler implements ResidentCompiler { Future<CompilerOutput?> compileExpression( String expression, List<String>? definitions, + List<String>? definitionTypes, List<String>? typeDefinitions, + List<String>? typeBounds, + List<String>? typeDefaults, String? libraryUri, String? klass, + String? method, bool isStatic, ) async { if (!_controller.hasListener) { @@ -846,7 +868,8 @@ class DefaultResidentCompiler implements ResidentCompiler { final Completer<CompilerOutput?> completer = Completer<CompilerOutput?>(); final _CompileExpressionRequest request = _CompileExpressionRequest( - completer, expression, definitions, typeDefinitions, libraryUri, klass, isStatic); + completer, expression, definitions, definitionTypes, typeDefinitions, + typeBounds, typeDefaults, libraryUri, klass, method, isStatic); _controller.add(request); return completer.future; } @@ -867,11 +890,18 @@ class DefaultResidentCompiler implements ResidentCompiler { ..writeln(request.expression); request.definitions?.forEach(server.stdin.writeln); server.stdin.writeln(inputKey); + request.definitionTypes?.forEach(server.stdin.writeln); + server.stdin.writeln(inputKey); request.typeDefinitions?.forEach(server.stdin.writeln); + server.stdin.writeln(inputKey); + request.typeBounds?.forEach(server.stdin.writeln); + server.stdin.writeln(inputKey); + request.typeDefaults?.forEach(server.stdin.writeln); server.stdin ..writeln(inputKey) ..writeln(request.libraryUri ?? '') ..writeln(request.klass ?? '') + ..writeln(request.method ?? '') ..writeln(request.isStatic); return _stdoutHandler.compilerOutput?.future; diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index 85e69809a0ee7..a5a4f92c25048 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:process/process.dart'; +import 'package:unified_analytics/unified_analytics.dart'; import 'android/android_builder.dart'; import 'android/android_sdk.dart'; @@ -87,7 +88,14 @@ Future<T> runInContext<T>( body: runnerWrapper, overrides: overrides, fallbacks: <Type, Generator>{ + Analytics: () => Analytics( + tool: DashTool.flutterTool, + flutterChannel: globals.flutterVersion.channel, + flutterVersion: globals.flutterVersion.frameworkVersion, + dartVersion: globals.flutterVersion.dartSdkVersion, + ), AndroidBuilder: () => AndroidGradleBuilder( + java: globals.java, logger: globals.logger, processManager: globals.processManager, fileSystem: globals.fs, @@ -101,22 +109,18 @@ Future<T> runInContext<T>( platform: globals.platform, userMessages: globals.userMessages, processManager: globals.processManager, - androidStudio: globals.androidStudio, java: globals.java, androidSdk: globals.androidSdk, logger: globals.logger, - fileSystem: globals.fs, stdio: globals.stdio, ), AndroidSdk: AndroidSdk.locateAndroidSdk, AndroidStudio: AndroidStudio.latestValid, AndroidValidator: () => AndroidValidator( - androidStudio: globals.androidStudio, + java: globals.java, androidSdk: globals.androidSdk, - fileSystem: globals.fs, logger: globals.logger, platform: globals.platform, - processManager: globals.processManager, userMessages: globals.userMessages, ), AndroidWorkflow: () => AndroidWorkflow( @@ -229,7 +233,10 @@ Future<T> runInContext<T>( config: globals.config, platform: globals.platform, ), - FlutterVersion: () => FlutterVersion(), + FlutterVersion: () => FlutterVersion( + fs: globals.fs, + flutterRoot: Cache.flutterRoot!, + ), FuchsiaArtifacts: () => FuchsiaArtifacts.find(), FuchsiaDeviceTools: () => FuchsiaDeviceTools(), FuchsiaSdk: () => FuchsiaSdk(), @@ -256,6 +263,7 @@ Future<T> runInContext<T>( platform: globals.platform, ), Java: () => Java.find( + config: globals.config, androidStudio: globals.androidStudio, logger: globals.logger, fileSystem: globals.fs, @@ -351,6 +359,7 @@ Future<T> runInContext<T>( platform: globals.platform, fileSystem: globals.fs, xcodeProjectInterpreter: globals.xcodeProjectInterpreter!, + userMessages: globals.userMessages, ), XCDevice: () => XCDevice( processManager: globals.processManager, @@ -367,6 +376,7 @@ Future<T> runInContext<T>( processManager: globals.processManager, dyLdLibEntry: globals.cache.dyLdLibEntry, ), + fileSystem: globals.fs, ), XcodeProjectInterpreter: () => XcodeProjectInterpreter( logger: globals.logger, diff --git a/packages/flutter_tools/lib/src/dart/pub.dart b/packages/flutter_tools/lib/src/dart/pub.dart index f9292c22ef9c5..40e60f9005acf 100644 --- a/packages/flutter_tools/lib/src/dart/pub.dart +++ b/packages/flutter_tools/lib/src/dart/pub.dart @@ -630,7 +630,7 @@ class _DefaultPub implements Pub { _logger.printStatus(''' Found an existing Pub cache at $pubCachePath. It can be repaired by running `dart pub cache repair`. -It can be reset by running `dart pub cache clear`.'''); +It can be reset by running `dart pub cache clean`.'''); } } final String? home = _platform.environment['HOME']; diff --git a/packages/flutter_tools/lib/src/debug_adapters/README.md b/packages/flutter_tools/lib/src/debug_adapters/README.md index 108ce14707a44..096a49d8efae4 100644 --- a/packages/flutter_tools/lib/src/debug_adapters/README.md +++ b/packages/flutter_tools/lib/src/debug_adapters/README.md @@ -40,7 +40,10 @@ Arguments specific to `launchRequest` are: Arguments specific to `attachRequest` are: -- `String? vmServiceUri` - the VM Service URI to attach to (if not supplied, Flutter will try to discover it from the device) +- `String? vmServiceInfoFile` - the file to read the VM Service info from \* +- `String? vmServiceUri` - the VM Service URI to attach to \* + +\* Only one of `vmServiceInfoFile` or `vmServiceUri` may be supplied. If neither are supplied, Flutter will try to discover it from the device. ## Custom Requests diff --git a/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart b/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart index 231c3f563bc14..e0e3402bb4466 100644 --- a/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart +++ b/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter.dart @@ -11,11 +11,12 @@ import 'package:vm_service/vm_service.dart' as vm; import '../base/io.dart'; import '../cache.dart'; import '../convert.dart'; +import '../globals.dart' as globals show fs; import 'flutter_adapter_args.dart'; import 'flutter_base_adapter.dart'; /// A DAP Debug Adapter for running and debugging Flutter applications. -class FlutterDebugAdapter extends FlutterBaseDebugAdapter { +class FlutterDebugAdapter extends FlutterBaseDebugAdapter with VmServiceInfoFileUtils { FlutterDebugAdapter( super.channel, { required super.fileSystem, @@ -120,6 +121,16 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { @override Future<void> attachImpl() async { final FlutterAttachRequestArguments args = this.args as FlutterAttachRequestArguments; + String? vmServiceUri = args.vmServiceUri; + final String? vmServiceInfoFile = args.vmServiceInfoFile; + + if (vmServiceUri != null && vmServiceInfoFile != null) { + sendConsoleOutput( + 'To attach, provide only one (or neither) of vmServiceUri/vmServiceInfoFile', + ); + handleSessionTerminate(); + return; + } launchProgress = startProgressNotification( 'launch', @@ -127,7 +138,11 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { message: 'Attaching…', ); - final String? vmServiceUri = args.vmServiceUri; + if (vmServiceUri == null && vmServiceInfoFile != null) { + final Uri uriFromFile = await waitForVmServiceInfoFile(logger, globals.fs.file(vmServiceInfoFile)); + vmServiceUri = uriFromFile.toString(); + } + final List<String> toolArgs = <String>[ 'attach', '--machine', @@ -170,6 +185,8 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { switch (request.command) { case 'hotRestart': case 'hotReload': + // This convention is for the internal IDE client. + case r'$/hotReload': final bool isFullRestart = request.command == 'hotRestart'; await _performRestart(isFullRestart, args?.args['reason'] as String?); sendResponse(null); @@ -663,6 +680,12 @@ class FlutterDebugAdapter extends FlutterBaseDebugAdapter { bool fullRestart, [ String? reason, ]) async { + // Don't do anything if the app hasn't started yet, as restarts and reloads + // can only operate on a running app. + if (_appId == null) { + return; + } + final String progressId = fullRestart ? 'hotRestart' : 'hotReload'; final String progressMessage = fullRestart ? 'Hot restarting…' : 'Hot reloading…'; final DapProgressReporter progress = startProgressNotification( diff --git a/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter_args.dart b/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter_args.dart index 55d1666dea128..8e90a6e9b6147 100644 --- a/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter_args.dart +++ b/packages/flutter_tools/lib/src/debug_adapters/flutter_adapter_args.dart @@ -17,6 +17,7 @@ class FlutterAttachRequestArguments this.customTool, this.customToolReplacesArgs, this.vmServiceUri, + this.vmServiceInfoFile, this.program, super.restart, super.name, @@ -36,6 +37,7 @@ class FlutterAttachRequestArguments customTool = obj['customTool'] as String?, customToolReplacesArgs = obj['customToolReplacesArgs'] as int?, vmServiceUri = obj['vmServiceUri'] as String?, + vmServiceInfoFile = obj['vmServiceInfoFile'] as String?, program = obj['program'] as String?, super.fromMap(); @@ -64,8 +66,15 @@ class FlutterAttachRequestArguments final int? customToolReplacesArgs; /// The VM Service URI of the running Flutter app to connect to. + /// + /// Only one of this or [vmServiceInfoFile] (or neither) can be supplied. final String? vmServiceUri; + /// The VM Service info file to extract the VM Service URI from to attach to. + /// + /// Only one of this or [vmServiceUri] (or neither) can be supplied. + final String? vmServiceInfoFile; + /// The program/Flutter app to be run. final String? program; diff --git a/packages/flutter_tools/lib/src/desktop_device.dart b/packages/flutter_tools/lib/src/desktop_device.dart index 02d7534a9f018..c8937258ddde4 100644 --- a/packages/flutter_tools/lib/src/desktop_device.dart +++ b/packages/flutter_tools/lib/src/desktop_device.dart @@ -209,6 +209,8 @@ abstract class DesktopDevice extends Device { /// steps to be run. void onAttached(ApplicationPackage package, BuildInfo buildInfo, Process process) {} + bool get supportsImpeller => false; + /// Computes a set of environment variables used to pass debugging information /// to the engine without interfering with application level command line /// arguments. @@ -266,6 +268,15 @@ abstract class DesktopDevice extends Device { if (debuggingOptions.purgePersistentCache) { addFlag('purge-persistent-cache=true'); } + if (supportsImpeller) { + switch (debuggingOptions.enableImpeller) { + case ImpellerStatus.enabled: + addFlag('enable-impeller=true'); + case ImpellerStatus.disabled: + case ImpellerStatus.platformDefault: + addFlag('enable-impeller=false'); + } + } // Options only supported when there is a VM Service connection between the // tool and the device, usually in debug or profile mode. if (debuggingOptions.debuggingEnabled) { diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 80d0fbd3dcc37..091bac0f48d0f 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -971,6 +971,7 @@ class DebuggingOptions { this.serveObservatory = false, this.enableDartProfiling = true, this.enableEmbedderApi = false, + this.usingCISystem = false, }) : debuggingEnabled = true; DebuggingOptions.disabled(this.buildInfo, { @@ -993,6 +994,7 @@ class DebuggingOptions { this.uninstallFirst = false, this.enableDartProfiling = true, this.enableEmbedderApi = false, + this.usingCISystem = false, }) : debuggingEnabled = false, useTestFonts = false, startPaused = false, @@ -1069,6 +1071,7 @@ class DebuggingOptions { required this.serveObservatory, required this.enableDartProfiling, required this.enableEmbedderApi, + required this.usingCISystem, }); final bool debuggingEnabled; @@ -1109,6 +1112,7 @@ class DebuggingOptions { final bool serveObservatory; final bool enableDartProfiling; final bool enableEmbedderApi; + final bool usingCISystem; /// Whether the tool should try to uninstall a previously installed version of the app. /// @@ -1152,6 +1156,7 @@ class DebuggingOptions { Map<String, Object?> platformArgs, { bool ipv6 = false, DeviceConnectionInterface interfaceType = DeviceConnectionInterface.attached, + bool isCoreDevice = false, }) { final String dartVmFlags = computeDartVmFlags(this); return <String>[ @@ -1165,7 +1170,10 @@ class DebuggingOptions { if (environmentType == EnvironmentType.simulator && dartVmFlags.isNotEmpty) '--dart-flags=$dartVmFlags', if (useTestFonts) '--use-test-fonts', - if (debuggingEnabled) ...<String>[ + // Core Devices (iOS 17 devices) are debugged through Xcode so don't + // include these flags, which are used to check if the app was launched + // via Flutter CLI and `ios-deploy`. + if (debuggingEnabled && !isCoreDevice) ...<String>[ '--enable-checked-mode', '--verify-entry-points', ], @@ -1243,6 +1251,7 @@ class DebuggingOptions { 'serveObservatory': serveObservatory, 'enableDartProfiling': enableDartProfiling, 'enableEmbedderApi': enableEmbedderApi, + 'usingCISystem': usingCISystem, }; static DebuggingOptions fromJson(Map<String, Object?> json, BuildInfo buildInfo) => @@ -1294,6 +1303,7 @@ class DebuggingOptions { serveObservatory: (json['serveObservatory'] as bool?) ?? false, enableDartProfiling: (json['enableDartProfiling'] as bool?) ?? true, enableEmbedderApi: (json['enableEmbedderApi'] as bool?) ?? false, + usingCISystem: (json['usingCISystem'] as bool?) ?? false, ); } diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index 3978d0d77654f..255e938ef8caa 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart @@ -123,7 +123,7 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider { FlutterValidator( fileSystem: globals.fs, platform: globals.platform, - flutterVersion: () => globals.flutterVersion, + flutterVersion: () => globals.flutterVersion.fetchTagsAndGetVersion(clock: globals.systemClock), devToolsVersion: () => globals.cache.devToolsVersion, processManager: globals.processManager, userMessages: userMessages, @@ -138,7 +138,14 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider { if (androidWorkflow!.appliesToHostPlatform) GroupedValidator(<DoctorValidator>[androidValidator!, androidLicenseValidator!]), if (globals.iosWorkflow!.appliesToHostPlatform || macOSWorkflow.appliesToHostPlatform) - GroupedValidator(<DoctorValidator>[XcodeValidator(xcode: globals.xcode!, userMessages: userMessages), globals.cocoapodsValidator!]), + GroupedValidator(<DoctorValidator>[ + XcodeValidator( + xcode: globals.xcode!, + userMessages: userMessages, + iosSimulatorUtils: globals.iosSimulatorUtils!, + ), + globals.cocoapodsValidator!, + ]), if (webWorkflow.appliesToHostPlatform) ChromeValidator( chromiumLauncher: ChromiumLauncher( diff --git a/packages/flutter_tools/lib/src/flutter_cache.dart b/packages/flutter_tools/lib/src/flutter_cache.dart index d28d2b853e11f..252021cf78e89 100644 --- a/packages/flutter_tools/lib/src/flutter_cache.dart +++ b/packages/flutter_tools/lib/src/flutter_cache.dart @@ -7,8 +7,7 @@ import 'dart:async'; import 'package:meta/meta.dart'; import 'package:package_config/package_config.dart'; -import 'android/android_sdk.dart'; -import 'android/android_studio.dart'; +import 'android/java.dart'; import 'base/common.dart'; import 'base/error_handling_io.dart'; import 'base/file_system.dart'; @@ -387,12 +386,17 @@ class AndroidGenSnapshotArtifacts extends EngineCachedArtifact { /// A cached artifact containing the Maven dependencies used to build Android projects. /// /// This is a no-op if the android SDK is not available. +/// +/// Set [Java] to `null` to indicate that no Java/JDK installation could be found. class AndroidMavenArtifacts extends ArtifactSet { AndroidMavenArtifacts(this.cache, { + required Java? java, required Platform platform, - }) : _platform = platform, + }) : _java = java, + _platform = platform, super(DevelopmentArtifact.androidMaven); + final Java? _java; final Platform _platform; final Cache cache; @@ -404,6 +408,8 @@ class AndroidMavenArtifacts extends ArtifactSet { OperatingSystemUtils operatingSystemUtils, {bool offline = false} ) async { + // TODO(andrewkolos): Should this really be no-op if the Android SDK + // is unavailable? https://github.com/flutter/flutter/issues/127848 if (globals.androidSdk == null) { return; } @@ -424,10 +430,7 @@ class AndroidMavenArtifacts extends ArtifactSet { '--project-cache-dir', tempDir.path, 'resolveDependencies', ], - environment: <String, String>{ - if (javaPath != null) - AndroidSdk.javaHomeEnvironmentVariable: javaPath!, - }, + environment: _java?.environment, ); if (processResult.exitCode != 0) { logger.printError('Failed to download the Android dependencies'); diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart index 0f239393c7299..59045aa442359 100644 --- a/packages/flutter_tools/lib/src/globals.dart +++ b/packages/flutter_tools/lib/src/globals.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:intl/date_symbol_data_local.dart'; import 'package:process/process.dart'; import 'package:unified_analytics/unified_analytics.dart'; @@ -91,19 +90,7 @@ Future<bool> get isRunningOnBot => botDetector.isRunningOnBot; // Analytics instance for package:unified_analytics for telemetry // reporting for all Flutter and Dart related tooling -Analytics get analytics => context.get<Analytics>() ?? getDefaultAnalytics(); -Analytics getDefaultAnalytics() { - - initializeDateFormatting(); - final Analytics defaultAnalytics = Analytics( - tool: DashTool.flutterTool, - flutterChannel: flutterVersion.channel, - flutterVersion: flutterVersion.frameworkVersion, - dartVersion: flutterVersion.dartSdkVersion, - ); - - return defaultAnalytics; -} +Analytics get analytics => context.get<Analytics>()!; /// Currently active implementation of the file system. /// diff --git a/packages/flutter_tools/lib/src/ios/core_devices.dart b/packages/flutter_tools/lib/src/ios/core_devices.dart new file mode 100644 index 0000000000000..aa39adbf66cb3 --- /dev/null +++ b/packages/flutter_tools/lib/src/ios/core_devices.dart @@ -0,0 +1,854 @@ +// 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:meta/meta.dart'; +import 'package:process/process.dart'; + +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/logger.dart'; +import '../base/process.dart'; +import '../convert.dart'; +import '../device.dart'; +import '../macos/xcode.dart'; + +/// A wrapper around the `devicectl` command line tool. +/// +/// CoreDevice is a device connectivity stack introduced in Xcode 15. Devices +/// with iOS 17 or greater are CoreDevices. +/// +/// `devicectl` (CoreDevice Device Control) is an Xcode CLI tool used for +/// interacting with CoreDevices. +class IOSCoreDeviceControl { + IOSCoreDeviceControl({ + required Logger logger, + required ProcessManager processManager, + required Xcode xcode, + required FileSystem fileSystem, + }) : _logger = logger, + _processUtils = ProcessUtils(logger: logger, processManager: processManager), + _xcode = xcode, + _fileSystem = fileSystem; + + final Logger _logger; + final ProcessUtils _processUtils; + final Xcode _xcode; + final FileSystem _fileSystem; + + /// When the `--timeout` flag is used with `devicectl`, it must be at + /// least 5 seconds. If lower than 5 seconds, `devicectl` will error and not + /// run the command. + static const int _minimumTimeoutInSeconds = 5; + + /// Executes `devicectl` command to get list of devices. The command will + /// likely complete before [timeout] is reached. If [timeout] is reached, + /// the command will be stopped as a failure. + Future<List<Object?>> _listCoreDevices({ + Duration timeout = const Duration(seconds: _minimumTimeoutInSeconds), + }) async { + if (!_xcode.isDevicectlInstalled) { + _logger.printError('devicectl is not installed.'); + return <Object?>[]; + } + + // Default to minimum timeout if needed to prevent error. + Duration validTimeout = timeout; + if (timeout.inSeconds < _minimumTimeoutInSeconds) { + _logger.printError( + 'Timeout of ${timeout.inSeconds} seconds is below the minimum timeout value ' + 'for devicectl. Changing the timeout to the minimum value of $_minimumTimeoutInSeconds.'); + validTimeout = const Duration(seconds: _minimumTimeoutInSeconds); + } + + final Directory tempDirectory = _fileSystem.systemTempDirectory.createTempSync('core_devices.'); + final File output = tempDirectory.childFile('core_device_list.json'); + output.createSync(); + + final List<String> command = <String>[ + ..._xcode.xcrunCommand(), + 'devicectl', + 'list', + 'devices', + '--timeout', + validTimeout.inSeconds.toString(), + '--json-output', + output.path, + ]; + + try { + await _processUtils.run(command, throwOnError: true); + + final String stringOutput = output.readAsStringSync(); + _logger.printTrace(stringOutput); + + try { + final Object? decodeResult = (json.decode(stringOutput) as Map<String, Object?>)['result']; + if (decodeResult is Map<String, Object?>) { + final Object? decodeDevices = decodeResult['devices']; + if (decodeDevices is List<Object?>) { + return decodeDevices; + } + } + _logger.printError('devicectl returned unexpected JSON response: $stringOutput'); + return <Object?>[]; + } on FormatException { + // We failed to parse the devicectl output, or it returned junk. + _logger.printError('devicectl returned non-JSON response: $stringOutput'); + return <Object?>[]; + } + } on ProcessException catch (err) { + _logger.printError('Error executing devicectl: $err'); + return <Object?>[]; + } finally { + tempDirectory.deleteSync(recursive: true); + } + } + + Future<List<IOSCoreDevice>> getCoreDevices({ + Duration timeout = const Duration(seconds: _minimumTimeoutInSeconds), + }) async { + final List<IOSCoreDevice> devices = <IOSCoreDevice>[]; + + final List<Object?> devicesSection = await _listCoreDevices(timeout: timeout); + for (final Object? deviceObject in devicesSection) { + if (deviceObject is Map<String, Object?>) { + devices.add(IOSCoreDevice.fromBetaJson(deviceObject, logger: _logger)); + } + } + return devices; + } + + /// Executes `devicectl` command to get list of apps installed on the device. + /// If [bundleId] is provided, it will only return apps matching the bundle + /// identifier exactly. + Future<List<Object?>> _listInstalledApps({ + required String deviceId, + String? bundleId, + }) async { + if (!_xcode.isDevicectlInstalled) { + _logger.printError('devicectl is not installed.'); + return <Object?>[]; + } + + final Directory tempDirectory = _fileSystem.systemTempDirectory.createTempSync('core_devices.'); + final File output = tempDirectory.childFile('core_device_app_list.json'); + output.createSync(); + + final List<String> command = <String>[ + ..._xcode.xcrunCommand(), + 'devicectl', + 'device', + 'info', + 'apps', + '--device', + deviceId, + if (bundleId != null) + '--bundle-id', + bundleId!, + '--json-output', + output.path, + ]; + + try { + await _processUtils.run(command, throwOnError: true); + + final String stringOutput = output.readAsStringSync(); + + try { + final Object? decodeResult = (json.decode(stringOutput) as Map<String, Object?>)['result']; + if (decodeResult is Map<String, Object?>) { + final Object? decodeApps = decodeResult['apps']; + if (decodeApps is List<Object?>) { + return decodeApps; + } + } + _logger.printError('devicectl returned unexpected JSON response: $stringOutput'); + return <Object?>[]; + } on FormatException { + // We failed to parse the devicectl output, or it returned junk. + _logger.printError('devicectl returned non-JSON response: $stringOutput'); + return <Object?>[]; + } + } on ProcessException catch (err) { + _logger.printError('Error executing devicectl: $err'); + return <Object?>[]; + } finally { + tempDirectory.deleteSync(recursive: true); + } + } + + @visibleForTesting + Future<List<IOSCoreDeviceInstalledApp>> getInstalledApps({ + required String deviceId, + String? bundleId, + }) async { + final List<IOSCoreDeviceInstalledApp> apps = <IOSCoreDeviceInstalledApp>[]; + + final List<Object?> appsData = await _listInstalledApps(deviceId: deviceId, bundleId: bundleId); + for (final Object? appObject in appsData) { + if (appObject is Map<String, Object?>) { + apps.add(IOSCoreDeviceInstalledApp.fromBetaJson(appObject)); + } + } + return apps; + } + + Future<bool> isAppInstalled({ + required String deviceId, + required String bundleId, + }) async { + final List<IOSCoreDeviceInstalledApp> apps = await getInstalledApps( + deviceId: deviceId, + bundleId: bundleId, + ); + if (apps.isNotEmpty) { + return true; + } + return false; + } + + Future<bool> installApp({ + required String deviceId, + required String bundlePath, + }) async { + if (!_xcode.isDevicectlInstalled) { + _logger.printError('devicectl is not installed.'); + return false; + } + + final Directory tempDirectory = _fileSystem.systemTempDirectory.createTempSync('core_devices.'); + final File output = tempDirectory.childFile('install_results.json'); + output.createSync(); + + final List<String> command = <String>[ + ..._xcode.xcrunCommand(), + 'devicectl', + 'device', + 'install', + 'app', + '--device', + deviceId, + bundlePath, + '--json-output', + output.path, + ]; + + try { + await _processUtils.run(command, throwOnError: true); + final String stringOutput = output.readAsStringSync(); + + try { + final Object? decodeResult = (json.decode(stringOutput) as Map<String, Object?>)['info']; + if (decodeResult is Map<String, Object?> && decodeResult['outcome'] == 'success') { + return true; + } + _logger.printError('devicectl returned unexpected JSON response: $stringOutput'); + return false; + } on FormatException { + // We failed to parse the devicectl output, or it returned junk. + _logger.printError('devicectl returned non-JSON response: $stringOutput'); + return false; + } + } on ProcessException catch (err) { + _logger.printError('Error executing devicectl: $err'); + return false; + } finally { + tempDirectory.deleteSync(recursive: true); + } + } + + /// Uninstalls the app from the device. Will succeed even if the app is not + /// currently installed on the device. + Future<bool> uninstallApp({ + required String deviceId, + required String bundleId, + }) async { + if (!_xcode.isDevicectlInstalled) { + _logger.printError('devicectl is not installed.'); + return false; + } + + final Directory tempDirectory = _fileSystem.systemTempDirectory.createTempSync('core_devices.'); + final File output = tempDirectory.childFile('uninstall_results.json'); + output.createSync(); + + final List<String> command = <String>[ + ..._xcode.xcrunCommand(), + 'devicectl', + 'device', + 'uninstall', + 'app', + '--device', + deviceId, + bundleId, + '--json-output', + output.path, + ]; + + try { + await _processUtils.run(command, throwOnError: true); + final String stringOutput = output.readAsStringSync(); + + try { + final Object? decodeResult = (json.decode(stringOutput) as Map<String, Object?>)['info']; + if (decodeResult is Map<String, Object?> && decodeResult['outcome'] == 'success') { + return true; + } + _logger.printError('devicectl returned unexpected JSON response: $stringOutput'); + return false; + } on FormatException { + // We failed to parse the devicectl output, or it returned junk. + _logger.printError('devicectl returned non-JSON response: $stringOutput'); + return false; + } + } on ProcessException catch (err) { + _logger.printError('Error executing devicectl: $err'); + return false; + } finally { + tempDirectory.deleteSync(recursive: true); + } + } + + Future<bool> launchApp({ + required String deviceId, + required String bundleId, + List<String> launchArguments = const <String>[], + }) async { + if (!_xcode.isDevicectlInstalled) { + _logger.printError('devicectl is not installed.'); + return false; + } + + final Directory tempDirectory = _fileSystem.systemTempDirectory.createTempSync('core_devices.'); + final File output = tempDirectory.childFile('launch_results.json'); + output.createSync(); + + final List<String> command = <String>[ + ..._xcode.xcrunCommand(), + 'devicectl', + 'device', + 'process', + 'launch', + '--device', + deviceId, + bundleId, + if (launchArguments.isNotEmpty) ...launchArguments, + '--json-output', + output.path, + ]; + + try { + await _processUtils.run(command, throwOnError: true); + final String stringOutput = output.readAsStringSync(); + + try { + final Object? decodeResult = (json.decode(stringOutput) as Map<String, Object?>)['info']; + if (decodeResult is Map<String, Object?> && decodeResult['outcome'] == 'success') { + return true; + } + _logger.printError('devicectl returned unexpected JSON response: $stringOutput'); + return false; + } on FormatException { + // We failed to parse the devicectl output, or it returned junk. + _logger.printError('devicectl returned non-JSON response: $stringOutput'); + return false; + } + } on ProcessException catch (err) { + _logger.printError('Error executing devicectl: $err'); + return false; + } finally { + tempDirectory.deleteSync(recursive: true); + } + } +} + +class IOSCoreDevice { + IOSCoreDevice._({ + required this.capabilities, + required this.connectionProperties, + required this.deviceProperties, + required this.hardwareProperties, + required this.coreDeviceIdentifer, + required this.visibilityClass, + }); + + /// Parse JSON from `devicectl list devices --json-output` while it's in beta preview mode. + /// + /// Example: + /// { + /// "capabilities" : [ + /// ], + /// "connectionProperties" : { + /// }, + /// "deviceProperties" : { + /// }, + /// "hardwareProperties" : { + /// }, + /// "identifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", + /// "visibilityClass" : "default" + /// } + factory IOSCoreDevice.fromBetaJson( + Map<String, Object?> data, { + required Logger logger, + }) { + final List<_IOSCoreDeviceCapability> capabilitiesList = <_IOSCoreDeviceCapability>[]; + if (data['capabilities'] is List<Object?>) { + final List<Object?> capabilitiesData = data['capabilities']! as List<Object?>; + for (final Object? capabilityData in capabilitiesData) { + if (capabilityData != null && capabilityData is Map<String, Object?>) { + capabilitiesList.add(_IOSCoreDeviceCapability.fromBetaJson(capabilityData)); + } + } + } + + _IOSCoreDeviceConnectionProperties? connectionProperties; + if (data['connectionProperties'] is Map<String, Object?>) { + final Map<String, Object?> connectionPropertiesData = data['connectionProperties']! as Map<String, Object?>; + connectionProperties = _IOSCoreDeviceConnectionProperties.fromBetaJson( + connectionPropertiesData, + logger: logger, + ); + } + + IOSCoreDeviceProperties? deviceProperties; + if (data['deviceProperties'] is Map<String, Object?>) { + final Map<String, Object?> devicePropertiesData = data['deviceProperties']! as Map<String, Object?>; + deviceProperties = IOSCoreDeviceProperties.fromBetaJson(devicePropertiesData); + } + + _IOSCoreDeviceHardwareProperties? hardwareProperties; + if (data['hardwareProperties'] is Map<String, Object?>) { + final Map<String, Object?> hardwarePropertiesData = data['hardwareProperties']! as Map<String, Object?>; + hardwareProperties = _IOSCoreDeviceHardwareProperties.fromBetaJson( + hardwarePropertiesData, + logger: logger, + ); + } + + return IOSCoreDevice._( + capabilities: capabilitiesList, + connectionProperties: connectionProperties, + deviceProperties: deviceProperties, + hardwareProperties: hardwareProperties, + coreDeviceIdentifer: data['identifier']?.toString(), + visibilityClass: data['visibilityClass']?.toString(), + ); + } + + String? get udid => hardwareProperties?.udid; + + DeviceConnectionInterface? get connectionInterface { + final String? transportType = connectionProperties?.transportType; + if (transportType != null) { + if (transportType.toLowerCase() == 'localnetwork') { + return DeviceConnectionInterface.wireless; + } else if (transportType.toLowerCase() == 'wired') { + return DeviceConnectionInterface.attached; + } + } + return null; + } + + @visibleForTesting + final List<_IOSCoreDeviceCapability> capabilities; + + @visibleForTesting + final _IOSCoreDeviceConnectionProperties? connectionProperties; + + final IOSCoreDeviceProperties? deviceProperties; + + @visibleForTesting + final _IOSCoreDeviceHardwareProperties? hardwareProperties; + + final String? coreDeviceIdentifer; + final String? visibilityClass; +} + + +class _IOSCoreDeviceCapability { + _IOSCoreDeviceCapability._({ + required this.featureIdentifier, + required this.name, + }); + + /// Parse `capabilities` section of JSON from `devicectl list devices --json-output` + /// while it's in beta preview mode. + /// + /// Example: + /// "capabilities" : [ + /// { + /// "featureIdentifier" : "com.apple.coredevice.feature.spawnexecutable", + /// "name" : "Spawn Executable" + /// }, + /// { + /// "featureIdentifier" : "com.apple.coredevice.feature.launchapplication", + /// "name" : "Launch Application" + /// } + /// ] + factory _IOSCoreDeviceCapability.fromBetaJson(Map<String, Object?> data) { + return _IOSCoreDeviceCapability._( + featureIdentifier: data['featureIdentifier']?.toString(), + name: data['name']?.toString(), + ); + } + + final String? featureIdentifier; + final String? name; +} + +class _IOSCoreDeviceConnectionProperties { + _IOSCoreDeviceConnectionProperties._({ + required this.authenticationType, + required this.isMobileDeviceOnly, + required this.lastConnectionDate, + required this.localHostnames, + required this.pairingState, + required this.potentialHostnames, + required this.transportType, + required this.tunnelIPAddress, + required this.tunnelState, + required this.tunnelTransportProtocol, + }); + + /// Parse `connectionProperties` section of JSON from `devicectl list devices --json-output` + /// while it's in beta preview mode. + /// + /// Example: + /// "connectionProperties" : { + /// "authenticationType" : "manualPairing", + /// "isMobileDeviceOnly" : false, + /// "lastConnectionDate" : "2023-06-15T15:29:00.082Z", + /// "localHostnames" : [ + /// "iPadName.coredevice.local", + /// "00001234-0001234A3C03401E.coredevice.local", + /// "12345BB5-AEDE-4A22-B653-6037262550DD.coredevice.local" + /// ], + /// "pairingState" : "paired", + /// "potentialHostnames" : [ + /// "00001234-0001234A3C03401E.coredevice.local", + /// "12345BB5-AEDE-4A22-B653-6037262550DD.coredevice.local" + /// ], + /// "transportType" : "wired", + /// "tunnelIPAddress" : "fdf1:23c4:cd56::1", + /// "tunnelState" : "connected", + /// "tunnelTransportProtocol" : "tcp" + /// } + factory _IOSCoreDeviceConnectionProperties.fromBetaJson( + Map<String, Object?> data, { + required Logger logger, + }) { + List<String>? localHostnames; + if (data['localHostnames'] is List<Object?>) { + final List<Object?> values = data['localHostnames']! as List<Object?>; + try { + localHostnames = List<String>.from(values); + } on TypeError { + logger.printTrace('Error parsing localHostnames value: $values'); + } + } + + List<String>? potentialHostnames; + if (data['potentialHostnames'] is List<Object?>) { + final List<Object?> values = data['potentialHostnames']! as List<Object?>; + try { + potentialHostnames = List<String>.from(values); + } on TypeError { + logger.printTrace('Error parsing potentialHostnames value: $values'); + } + } + return _IOSCoreDeviceConnectionProperties._( + authenticationType: data['authenticationType']?.toString(), + isMobileDeviceOnly: data['isMobileDeviceOnly'] is bool? ? data['isMobileDeviceOnly'] as bool? : null, + lastConnectionDate: data['lastConnectionDate']?.toString(), + localHostnames: localHostnames, + pairingState: data['pairingState']?.toString(), + potentialHostnames: potentialHostnames, + transportType: data['transportType']?.toString(), + tunnelIPAddress: data['tunnelIPAddress']?.toString(), + tunnelState: data['tunnelState']?.toString(), + tunnelTransportProtocol: data['tunnelTransportProtocol']?.toString(), + ); + } + + final String? authenticationType; + final bool? isMobileDeviceOnly; + final String? lastConnectionDate; + final List<String>? localHostnames; + final String? pairingState; + final List<String>? potentialHostnames; + final String? transportType; + final String? tunnelIPAddress; + final String? tunnelState; + final String? tunnelTransportProtocol; +} + +@visibleForTesting +class IOSCoreDeviceProperties { + IOSCoreDeviceProperties._({ + required this.bootedFromSnapshot, + required this.bootedSnapshotName, + required this.bootState, + required this.ddiServicesAvailable, + required this.developerModeStatus, + required this.hasInternalOSBuild, + required this.name, + required this.osBuildUpdate, + required this.osVersionNumber, + required this.rootFileSystemIsWritable, + required this.screenViewingURL, + }); + + /// Parse `deviceProperties` section of JSON from `devicectl list devices --json-output` + /// while it's in beta preview mode. + /// + /// Example: + /// "deviceProperties" : { + /// "bootedFromSnapshot" : true, + /// "bootedSnapshotName" : "com.apple.os.update-B5336980824124F599FD39FE91016493A74331B09F475250BB010B276FE2439E3DE3537349A3A957D3FF2A4B623B4ECC", + /// "bootState" : "booted", + /// "ddiServicesAvailable" : true, + /// "developerModeStatus" : "enabled", + /// "hasInternalOSBuild" : false, + /// "name" : "iPadName", + /// "osBuildUpdate" : "21A5248v", + /// "osVersionNumber" : "17.0", + /// "rootFileSystemIsWritable" : false, + /// "screenViewingURL" : "coredevice-devices:/viewDeviceByUUID?uuid=123456BB5-AEDE-7A22-B890-1234567890DD" + /// } + factory IOSCoreDeviceProperties.fromBetaJson(Map<String, Object?> data) { + return IOSCoreDeviceProperties._( + bootedFromSnapshot: data['bootedFromSnapshot'] is bool? ? data['bootedFromSnapshot'] as bool? : null, + bootedSnapshotName: data['bootedSnapshotName']?.toString(), + bootState: data['bootState']?.toString(), + ddiServicesAvailable: data['ddiServicesAvailable'] is bool? ? data['ddiServicesAvailable'] as bool? : null, + developerModeStatus: data['developerModeStatus']?.toString(), + hasInternalOSBuild: data['hasInternalOSBuild'] is bool? ? data['hasInternalOSBuild'] as bool? : null, + name: data['name']?.toString(), + osBuildUpdate: data['osBuildUpdate']?.toString(), + osVersionNumber: data['osVersionNumber']?.toString(), + rootFileSystemIsWritable: data['rootFileSystemIsWritable'] is bool? ? data['rootFileSystemIsWritable'] as bool? : null, + screenViewingURL: data['screenViewingURL']?.toString(), + ); + } + + final bool? bootedFromSnapshot; + final String? bootedSnapshotName; + final String? bootState; + final bool? ddiServicesAvailable; + final String? developerModeStatus; + final bool? hasInternalOSBuild; + final String? name; + final String? osBuildUpdate; + final String? osVersionNumber; + final bool? rootFileSystemIsWritable; + final String? screenViewingURL; +} + +class _IOSCoreDeviceHardwareProperties { + _IOSCoreDeviceHardwareProperties._({ + required this.cpuType, + required this.deviceType, + required this.ecid, + required this.hardwareModel, + required this.internalStorageCapacity, + required this.marketingName, + required this.platform, + required this.productType, + required this.serialNumber, + required this.supportedCPUTypes, + required this.supportedDeviceFamilies, + required this.thinningProductType, + required this.udid, + }); + + /// Parse `hardwareProperties` section of JSON from `devicectl list devices --json-output` + /// while it's in beta preview mode. + /// + /// Example: + /// "hardwareProperties" : { + /// "cpuType" : { + /// "name" : "arm64e", + /// "subType" : 2, + /// "type" : 16777228 + /// }, + /// "deviceType" : "iPad", + /// "ecid" : 12345678903408542, + /// "hardwareModel" : "J617AP", + /// "internalStorageCapacity" : 128000000000, + /// "marketingName" : "iPad Pro (11-inch) (4th generation)\"", + /// "platform" : "iOS", + /// "productType" : "iPad14,3", + /// "serialNumber" : "HC123DHCQV", + /// "supportedCPUTypes" : [ + /// { + /// "name" : "arm64e", + /// "subType" : 2, + /// "type" : 16777228 + /// }, + /// { + /// "name" : "arm64", + /// "subType" : 0, + /// "type" : 16777228 + /// } + /// ], + /// "supportedDeviceFamilies" : [ + /// 1, + /// 2 + /// ], + /// "thinningProductType" : "iPad14,3-A", + /// "udid" : "00001234-0001234A3C03401E" + /// } + factory _IOSCoreDeviceHardwareProperties.fromBetaJson( + Map<String, Object?> data, { + required Logger logger, + }) { + _IOSCoreDeviceCPUType? cpuType; + if (data['cpuType'] is Map<String, Object?>) { + cpuType = _IOSCoreDeviceCPUType.fromBetaJson(data['cpuType']! as Map<String, Object?>); + } + + List<_IOSCoreDeviceCPUType>? supportedCPUTypes; + if (data['supportedCPUTypes'] is List<Object?>) { + final List<Object?> values = data['supportedCPUTypes']! as List<Object?>; + final List<_IOSCoreDeviceCPUType> cpuTypes = <_IOSCoreDeviceCPUType>[]; + for (final Object? cpuTypeData in values) { + if (cpuTypeData is Map<String, Object?>) { + cpuTypes.add(_IOSCoreDeviceCPUType.fromBetaJson(cpuTypeData)); + } + } + supportedCPUTypes = cpuTypes; + } + + List<int>? supportedDeviceFamilies; + if (data['supportedDeviceFamilies'] is List<Object?>) { + final List<Object?> values = data['supportedDeviceFamilies']! as List<Object?>; + try { + supportedDeviceFamilies = List<int>.from(values); + } on TypeError { + logger.printTrace('Error parsing supportedDeviceFamilies value: $values'); + } + } + + return _IOSCoreDeviceHardwareProperties._( + cpuType: cpuType, + deviceType: data['deviceType']?.toString(), + ecid: data['ecid'] is int? ? data['ecid'] as int? : null, + hardwareModel: data['hardwareModel']?.toString(), + internalStorageCapacity: data['internalStorageCapacity'] is int? ? data['internalStorageCapacity'] as int? : null, + marketingName: data['marketingName']?.toString(), + platform: data['platform']?.toString(), + productType: data['productType']?.toString(), + serialNumber: data['serialNumber']?.toString(), + supportedCPUTypes: supportedCPUTypes, + supportedDeviceFamilies: supportedDeviceFamilies, + thinningProductType: data['thinningProductType']?.toString(), + udid: data['udid']?.toString(), + ); + } + + final _IOSCoreDeviceCPUType? cpuType; + final String? deviceType; + final int? ecid; + final String? hardwareModel; + final int? internalStorageCapacity; + final String? marketingName; + final String? platform; + final String? productType; + final String? serialNumber; + final List<_IOSCoreDeviceCPUType>? supportedCPUTypes; + final List<int>? supportedDeviceFamilies; + final String? thinningProductType; + final String? udid; +} + +class _IOSCoreDeviceCPUType { + _IOSCoreDeviceCPUType._({ + this.name, + this.subType, + this.cpuType, + }); + + /// Parse `hardwareProperties.cpuType` and `hardwareProperties.supportedCPUTypes` + /// sections of JSON from `devicectl list devices --json-output` while it's in beta preview mode. + /// + /// Example: + /// "cpuType" : { + /// "name" : "arm64e", + /// "subType" : 2, + /// "type" : 16777228 + /// } + factory _IOSCoreDeviceCPUType.fromBetaJson(Map<String, Object?> data) { + return _IOSCoreDeviceCPUType._( + name: data['name']?.toString(), + subType: data['subType'] is int? ? data['subType'] as int? : null, + cpuType: data['type'] is int? ? data['type'] as int? : null, + ); + } + + final String? name; + final int? subType; + final int? cpuType; +} + +@visibleForTesting +class IOSCoreDeviceInstalledApp { + IOSCoreDeviceInstalledApp._({ + required this.appClip, + required this.builtByDeveloper, + required this.bundleIdentifier, + required this.bundleVersion, + required this.defaultApp, + required this.hidden, + required this.internalApp, + required this.name, + required this.removable, + required this.url, + required this.version, + }); + + /// Parse JSON from `devicectl device info apps --json-output` while it's in + /// beta preview mode. + /// + /// Example: + /// { + /// "appClip" : false, + /// "builtByDeveloper" : true, + /// "bundleIdentifier" : "com.example.flutterApp", + /// "bundleVersion" : "1", + /// "defaultApp" : false, + /// "hidden" : false, + /// "internalApp" : false, + /// "name" : "Flutter App", + /// "removable" : true, + /// "url" : "file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/", + /// "version" : "1.0.0" + /// } + factory IOSCoreDeviceInstalledApp.fromBetaJson(Map<String, Object?> data) { + return IOSCoreDeviceInstalledApp._( + appClip: data['appClip'] is bool? ? data['appClip'] as bool? : null, + builtByDeveloper: data['builtByDeveloper'] is bool? ? data['builtByDeveloper'] as bool? : null, + bundleIdentifier: data['bundleIdentifier']?.toString(), + bundleVersion: data['bundleVersion']?.toString(), + defaultApp: data['defaultApp'] is bool? ? data['defaultApp'] as bool? : null, + hidden: data['hidden'] is bool? ? data['hidden'] as bool? : null, + internalApp: data['internalApp'] is bool? ? data['internalApp'] as bool? : null, + name: data['name']?.toString(), + removable: data['removable'] is bool? ? data['removable'] as bool? : null, + url: data['url']?.toString(), + version: data['version']?.toString(), + ); + } + + final bool? appClip; + final bool? builtByDeveloper; + final String? bundleIdentifier; + final String? bundleVersion; + final bool? defaultApp; + final bool? hidden; + final bool? internalApp; + final String? name; + final bool? removable; + final String? url; + final String? version; +} diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 494cfcfebd216..ca6e594885f8a 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -15,7 +15,9 @@ import '../base/io.dart'; import '../base/logger.dart'; import '../base/os.dart'; import '../base/platform.dart'; +import '../base/process.dart'; import '../base/utils.dart'; +import '../base/version.dart'; import '../build_info.dart'; import '../convert.dart'; import '../device.dart'; @@ -27,10 +29,14 @@ import '../project.dart'; import '../protocol_discovery.dart'; import '../vmservice.dart'; import 'application_package.dart'; +import 'core_devices.dart'; import 'ios_deploy.dart'; import 'ios_workflow.dart'; import 'iproxy.dart'; import 'mac.dart'; +import 'xcode_build_settings.dart'; +import 'xcode_debug.dart'; +import 'xcodeproj.dart'; class IOSDevices extends PollingDeviceDiscovery { IOSDevices({ @@ -262,16 +268,21 @@ class IOSDevice extends Device { required this.connectionInterface, required this.isConnected, required this.devModeEnabled, + required this.isCoreDevice, String? sdkVersion, required Platform platform, required IOSDeploy iosDeploy, required IMobileDevice iMobileDevice, + required IOSCoreDeviceControl coreDeviceControl, + required XcodeDebug xcodeDebug, required IProxy iProxy, required Logger logger, }) : _sdkVersion = sdkVersion, _iosDeploy = iosDeploy, _iMobileDevice = iMobileDevice, + _coreDeviceControl = coreDeviceControl, + _xcodeDebug = xcodeDebug, _iproxy = iProxy, _fileSystem = fileSystem, _logger = logger, @@ -293,12 +304,17 @@ class IOSDevice extends Device { final Logger _logger; final Platform _platform; final IMobileDevice _iMobileDevice; + final IOSCoreDeviceControl _coreDeviceControl; + final XcodeDebug _xcodeDebug; final IProxy _iproxy; + Version? get sdkVersion { + return Version.parse(_sdkVersion); + } + /// May be 0 if version cannot be parsed. int get majorSdkVersion { - final String? majorVersionString = _sdkVersion?.split('.').first.trim(); - return majorVersionString != null ? int.tryParse(majorVersionString) ?? 0 : 0; + return sdkVersion?.major ?? 0; } @override @@ -320,6 +336,10 @@ class IOSDevice extends Device { @override bool isConnected; + /// CoreDevice is a device connectivity stack introduced in Xcode 15. Devices + /// with iOS 17 or greater are CoreDevices. + final bool isCoreDevice; + final Map<IOSApp?, DeviceLogReader> _logReaders = <IOSApp?, DeviceLogReader>{}; DevicePortForwarder? _portForwarder; @@ -345,10 +365,17 @@ class IOSDevice extends Device { }) async { bool result; try { - result = await _iosDeploy.isAppInstalled( - bundleId: app.id, - deviceId: id, - ); + if (isCoreDevice) { + result = await _coreDeviceControl.isAppInstalled( + bundleId: app.id, + deviceId: id, + ); + } else { + result = await _iosDeploy.isAppInstalled( + bundleId: app.id, + deviceId: id, + ); + } } on ProcessException catch (e) { _logger.printError(e.message); return false; @@ -372,13 +399,20 @@ class IOSDevice extends Device { int installationResult; try { - installationResult = await _iosDeploy.installApp( - deviceId: id, - bundlePath: bundle.path, - appDeltaDirectory: app.appDeltaDirectory, - launchArguments: <String>[], - interfaceType: connectionInterface, - ); + if (isCoreDevice) { + installationResult = await _coreDeviceControl.installApp( + deviceId: id, + bundlePath: bundle.path, + ) ? 0 : 1; + } else { + installationResult = await _iosDeploy.installApp( + deviceId: id, + bundlePath: bundle.path, + appDeltaDirectory: app.appDeltaDirectory, + launchArguments: <String>[], + interfaceType: connectionInterface, + ); + } } on ProcessException catch (e) { _logger.printError(e.message); return false; @@ -400,10 +434,17 @@ class IOSDevice extends Device { }) async { int uninstallationResult; try { - uninstallationResult = await _iosDeploy.uninstallApp( - deviceId: id, - bundleId: app.id, - ); + if (isCoreDevice) { + uninstallationResult = await _coreDeviceControl.uninstallApp( + deviceId: id, + bundleId: app.id, + ) ? 0 : 1; + } else { + uninstallationResult = await _iosDeploy.uninstallApp( + deviceId: id, + bundleId: app.id, + ); + } } on ProcessException catch (e) { _logger.printError(e.message); return false; @@ -430,6 +471,7 @@ class IOSDevice extends Device { bool ipv6 = false, String? userIdentifier, @visibleForTesting Duration? discoveryTimeout, + @visibleForTesting ShutdownHooks? shutdownHooks, }) async { String? packageId; if (isWirelesslyConnected && @@ -437,6 +479,18 @@ class IOSDevice extends Device { debuggingOptions.disablePortPublication) { throwToolExit('Cannot start app on wirelessly tethered iOS device. Try running again with the --publish-port flag'); } + + // TODO(vashworth): Remove after Xcode 15 and iOS 17 are in CI (https://github.com/flutter/flutter/issues/132128) + // XcodeDebug workflow is used for CoreDevices (iOS 17+ and Xcode 15+). + // Force the use of XcodeDebug workflow in CI to test from older versions + // since devicelab has not yet been updated to iOS 17 and Xcode 15. + bool forceXcodeDebugWorkflow = false; + if (debuggingOptions.usingCISystem && + debuggingOptions.debuggingEnabled && + _platform.environment['FORCE_XCODE_DEBUG']?.toLowerCase() == 'true') { + forceXcodeDebugWorkflow = true; + } + if (!prebuiltApplication) { _logger.printTrace('Building ${package.name} for $id'); @@ -473,6 +527,7 @@ class IOSDevice extends Device { platformArgs, ipv6: ipv6, interfaceType: connectionInterface, + isCoreDevice: isCoreDevice, ); Status startAppStatus = _logger.startProgress( 'Installing and launching...', @@ -482,7 +537,10 @@ class IOSDevice extends Device { int installationResult = 1; if (debuggingOptions.debuggingEnabled) { _logger.printTrace('Debugging is enabled, connecting to vmService'); - final DeviceLogReader deviceLogReader = getLogReader(app: package); + final DeviceLogReader deviceLogReader = getLogReader( + app: package, + usingCISystem: debuggingOptions.usingCISystem, + ); // If the device supports syslog reading, prefer launching the app without // attaching the debugger to avoid the overhead of the unnecessary extra running process. @@ -509,7 +567,17 @@ class IOSDevice extends Device { logger: _logger, ); } - if (iosDeployDebugger == null) { + + if (isCoreDevice || forceXcodeDebugWorkflow) { + installationResult = await _startAppOnCoreDevice( + debuggingOptions: debuggingOptions, + package: package, + launchArguments: launchArguments, + mainPath: mainPath, + discoveryTimeout: discoveryTimeout, + shutdownHooks: shutdownHooks ?? globals.shutdownHooks, + ) ? 0 : 1; + } else if (iosDeployDebugger == null) { installationResult = await _iosDeploy.launchApp( deviceId: id, bundlePath: bundle.path, @@ -536,10 +604,26 @@ class IOSDevice extends Device { _logger.printTrace('Application launched on the device. Waiting for Dart VM Service url.'); - final int defaultTimeout = isWirelesslyConnected ? 45 : 30; + final int defaultTimeout; + if ((isCoreDevice || forceXcodeDebugWorkflow) && debuggingOptions.debuggingEnabled) { + // Core devices with debugging enabled takes longer because this + // includes time to install and launch the app on the device. + defaultTimeout = isWirelesslyConnected ? 75 : 60; + } else if (isWirelesslyConnected) { + defaultTimeout = 45; + } else { + defaultTimeout = 30; + } + final Timer timer = Timer(discoveryTimeout ?? Duration(seconds: defaultTimeout), () { _logger.printError('The Dart VM Service was not discovered after $defaultTimeout seconds. This is taking much longer than expected...'); - + if (isCoreDevice && debuggingOptions.debuggingEnabled) { + _logger.printError( + 'Open the Xcode window the project is opened in to ensure the app ' + 'is running. If the app is not running, try selecting "Product > Run" ' + 'to fix the problem.', + ); + } // If debugging with a wireless device and the timeout is reached, remind the // user to allow local network permissions. if (isWirelesslyConnected) { @@ -557,37 +641,71 @@ class IOSDevice extends Device { Uri? localUri; if (isWirelesslyConnected) { - // Wait for Dart VM Service to start up. - final Uri? serviceURL = await vmServiceDiscovery?.uri; - if (serviceURL == null) { - await iosDeployDebugger?.stopAndDumpBacktrace(); - await dispose(); - return LaunchResult.failed(); - } + // When using a CoreDevice, device logs are unavailable and therefore + // cannot be used to get the Dart VM url. Instead, get the Dart VM + // Service by finding services matching the app bundle id and the + // device name. + // + // If not using a CoreDevice, wait for the Dart VM url to be discovered + // via logs and then get the Dart VM Service by finding services matching + // the app bundle id and the Dart VM port. + // + // Then in both cases, get the device IP from the Dart VM Service to + // construct the Dart VM url using the device IP as the host. + if (isCoreDevice) { + localUri = await MDnsVmServiceDiscovery.instance!.getVMServiceUriForLaunch( + packageId, + this, + usesIpv6: ipv6, + useDeviceIPAsHost: true, + ); + } else { + // Wait for Dart VM Service to start up. + final Uri? serviceURL = await vmServiceDiscovery?.uri; + if (serviceURL == null) { + await iosDeployDebugger?.stopAndDumpBacktrace(); + await dispose(); + return LaunchResult.failed(); + } - // If Dart VM Service URL with the device IP is not found within 5 seconds, - // change the status message to prompt users to click Allow. Wait 5 seconds because it - // should only show this message if they have not already approved the permissions. - // MDnsVmServiceDiscovery usually takes less than 5 seconds to find it. - final Timer mDNSLookupTimer = Timer(const Duration(seconds: 5), () { - startAppStatus.stop(); - startAppStatus = _logger.startProgress( - 'Waiting for approval of local network permissions...', + // If Dart VM Service URL with the device IP is not found within 5 seconds, + // change the status message to prompt users to click Allow. Wait 5 seconds because it + // should only show this message if they have not already approved the permissions. + // MDnsVmServiceDiscovery usually takes less than 5 seconds to find it. + final Timer mDNSLookupTimer = Timer(const Duration(seconds: 5), () { + startAppStatus.stop(); + startAppStatus = _logger.startProgress( + 'Waiting for approval of local network permissions...', + ); + }); + + // Get Dart VM Service URL with the device IP as the host. + localUri = await MDnsVmServiceDiscovery.instance!.getVMServiceUriForLaunch( + packageId, + this, + usesIpv6: ipv6, + deviceVmservicePort: serviceURL.port, + useDeviceIPAsHost: true, ); - }); - - // Get Dart VM Service URL with the device IP as the host. - localUri = await MDnsVmServiceDiscovery.instance!.getVMServiceUriForLaunch( - packageId, - this, - usesIpv6: ipv6, - deviceVmservicePort: serviceURL.port, - useDeviceIPAsHost: true, - ); - mDNSLookupTimer.cancel(); + mDNSLookupTimer.cancel(); + } } else { - localUri = await vmServiceDiscovery?.uri; + if ((isCoreDevice || forceXcodeDebugWorkflow) && vmServiceDiscovery != null) { + // When searching for the Dart VM url, search for it via ProtocolDiscovery + // (device logs) and mDNS simultaneously, since both can be flaky at times. + final Future<Uri?> vmUrlFromMDns = MDnsVmServiceDiscovery.instance!.getVMServiceUriForLaunch( + packageId, + this, + usesIpv6: ipv6, + ); + final Future<Uri?> vmUrlFromLogs = vmServiceDiscovery.uri; + localUri = await Future.any( + <Future<Uri?>>[vmUrlFromMDns, vmUrlFromLogs] + ); + } else { + localUri = await vmServiceDiscovery?.uri; + } } timer.cancel(); if (localUri == null) { @@ -603,6 +721,139 @@ class IOSDevice extends Device { return LaunchResult.failed(); } finally { startAppStatus.stop(); + + if ((isCoreDevice || forceXcodeDebugWorkflow) && debuggingOptions.debuggingEnabled && package is BuildableIOSApp) { + // When debugging via Xcode, after the app launches, reset the Generated + // settings to not include the custom configuration build directory. + // This is to prevent confusion if the project is later ran via Xcode + // rather than the Flutter CLI. + await updateGeneratedXcodeProperties( + project: FlutterProject.current(), + buildInfo: debuggingOptions.buildInfo, + targetOverride: mainPath, + ); + } + } + } + + /// Starting with Xcode 15 and iOS 17, `ios-deploy` stopped working due to + /// the new CoreDevice connectivity stack. Previously, `ios-deploy` was used + /// to install the app, launch the app, and start `debugserver`. + /// Xcode 15 introduced a new command line tool called `devicectl` that + /// includes much of the functionality supplied by `ios-deploy`. However, + /// `devicectl` lacks the ability to start a `debugserver` and therefore `ptrace`, which are needed + /// for debug mode due to using a JIT Dart VM. + /// + /// Therefore, when starting an app on a CoreDevice, use `devicectl` when + /// debugging is not enabled. Otherwise, use Xcode automation. + Future<bool> _startAppOnCoreDevice({ + required DebuggingOptions debuggingOptions, + required IOSApp package, + required List<String> launchArguments, + required String? mainPath, + required ShutdownHooks shutdownHooks, + @visibleForTesting Duration? discoveryTimeout, + }) async { + if (!debuggingOptions.debuggingEnabled) { + // Release mode + + // Install app to device + final bool installSuccess = await _coreDeviceControl.installApp( + deviceId: id, + bundlePath: package.deviceBundlePath, + ); + if (!installSuccess) { + return installSuccess; + } + + // Launch app to device + final bool launchSuccess = await _coreDeviceControl.launchApp( + deviceId: id, + bundleId: package.id, + launchArguments: launchArguments, + ); + + return launchSuccess; + } else { + _logger.printStatus( + 'You may be prompted to give access to control Xcode. Flutter uses Xcode ' + 'to run your app. If access is not allowed, you can change this through ' + 'your Settings > Privacy & Security > Automation.', + ); + final int launchTimeout = isWirelesslyConnected ? 45 : 30; + final Timer timer = Timer(discoveryTimeout ?? Duration(seconds: launchTimeout), () { + _logger.printError( + 'Xcode is taking longer than expected to start debugging the app. ' + 'Ensure the project is opened in Xcode.', + ); + }); + + XcodeDebugProject debugProject; + final FlutterProject flutterProject = FlutterProject.current(); + + if (package is PrebuiltIOSApp) { + debugProject = await _xcodeDebug.createXcodeProjectWithCustomBundle( + package.deviceBundlePath, + templateRenderer: globals.templateRenderer, + verboseLogging: _logger.isVerbose, + ); + } else if (package is BuildableIOSApp) { + // Before installing/launching/debugging with Xcode, update the build + // settings to use a custom configuration build directory so Xcode + // knows where to find the app bundle to launch. + final Directory bundle = _fileSystem.directory( + package.deviceBundlePath, + ); + await updateGeneratedXcodeProperties( + project: flutterProject, + buildInfo: debuggingOptions.buildInfo, + targetOverride: mainPath, + configurationBuildDir: bundle.parent.absolute.path, + ); + + final IosProject project = package.project; + final XcodeProjectInfo? projectInfo = await project.projectInfo(); + if (projectInfo == null) { + globals.printError('Xcode project not found.'); + return false; + } + if (project.xcodeWorkspace == null) { + globals.printError('Unable to get Xcode workspace.'); + return false; + } + final String? scheme = projectInfo.schemeFor(debuggingOptions.buildInfo); + if (scheme == null) { + projectInfo.reportFlavorNotFoundAndExit(); + } + + debugProject = XcodeDebugProject( + scheme: scheme, + xcodeProject: project.xcodeProject, + xcodeWorkspace: project.xcodeWorkspace!, + hostAppProjectName: project.hostAppProjectName, + expectedConfigurationBuildDir: bundle.parent.absolute.path, + verboseLogging: _logger.isVerbose, + ); + } else { + // This should not happen. Currently, only PrebuiltIOSApp and + // BuildableIOSApp extend from IOSApp. + _logger.printError('IOSApp type ${package.runtimeType} is not recognized.'); + return false; + } + + final bool debugSuccess = await _xcodeDebug.debugApp( + project: debugProject, + deviceId: id, + launchArguments:launchArguments, + ); + timer.cancel(); + + // Kill Xcode on shutdown when running from CI + if (debuggingOptions.usingCISystem) { + shutdownHooks.addShutdownHook(() => _xcodeDebug.exit(force: true)); + } + + return debugSuccess; } } @@ -616,6 +867,9 @@ class IOSDevice extends Device { if (deployDebugger != null && deployDebugger.debuggerAttached) { return deployDebugger.exit(); } + if (_xcodeDebug.debugStarted) { + return _xcodeDebug.exit(); + } return false; } @@ -629,12 +883,14 @@ class IOSDevice extends Device { DeviceLogReader getLogReader({ covariant IOSApp? app, bool includePastLogs = false, + bool usingCISystem = false, }) { assert(!includePastLogs, 'Past log reading not supported on iOS devices.'); return _logReaders.putIfAbsent(app, () => IOSDeviceLogReader.create( device: this, app: app, iMobileDevice: _iMobileDevice, + usingCISystem: usingCISystem, )); } @@ -660,7 +916,14 @@ class IOSDevice extends Device { void clearLogs() { } @override - bool get supportsScreenshot => _iMobileDevice.isInstalled; + bool get supportsScreenshot { + if (isCoreDevice) { + // `idevicescreenshot` stopped working with iOS 17 / Xcode 15 + // (https://github.com/flutter/flutter/issues/128598). + return false; + } + return _iMobileDevice.isInstalled; + } @override Future<void> takeScreenshot(File outputFile) async { @@ -748,18 +1011,25 @@ class IOSDeviceLogReader extends DeviceLogReader { this._majorSdkVersion, this._deviceId, this.name, + this._isWirelesslyConnected, + this._isCoreDevice, String appName, - ) : // Match for lines for the runner in syslog. + bool usingCISystem, { + bool forceXcodeDebug = false, + }) : // Match for lines for the runner in syslog. // // iOS 9 format: Runner[297] <Notice>: // iOS 10 format: Runner(Flutter)[297] <Notice>: - _runnerLineRegex = RegExp(appName + r'(\(Flutter\))?\[[\d]+\] <[A-Za-z]+>: '); + _runnerLineRegex = RegExp(appName + r'(\(Flutter\))?\[[\d]+\] <[A-Za-z]+>: '), + _usingCISystem = usingCISystem, + _forceXcodeDebug = forceXcodeDebug; /// Create a new [IOSDeviceLogReader]. factory IOSDeviceLogReader.create({ required IOSDevice device, IOSApp? app, required IMobileDevice iMobileDevice, + bool usingCISystem = false, }) { final String appName = app?.name?.replaceAll('.app', '') ?? ''; return IOSDeviceLogReader._( @@ -767,7 +1037,11 @@ class IOSDeviceLogReader extends DeviceLogReader { device.majorSdkVersion, device.id, device.name, + device.isWirelesslyConnected, + device.isCoreDevice, appName, + usingCISystem, + forceXcodeDebug: device._platform.environment['FORCE_XCODE_DEBUG']?.toLowerCase() == 'true', ); } @@ -775,16 +1049,33 @@ class IOSDeviceLogReader extends DeviceLogReader { factory IOSDeviceLogReader.test({ required IMobileDevice iMobileDevice, bool useSyslog = true, + bool usingCISystem = false, + int? majorSdkVersion, + bool isWirelesslyConnected = false, + bool isCoreDevice = false, }) { + final int sdkVersion; + if (majorSdkVersion != null) { + sdkVersion = majorSdkVersion; + } else { + sdkVersion = useSyslog ? 12 : 13; + } return IOSDeviceLogReader._( - iMobileDevice, useSyslog ? 12 : 13, '1234', 'test', 'Runner'); + iMobileDevice, sdkVersion, '1234', 'test', isWirelesslyConnected, isCoreDevice, 'Runner', usingCISystem); } @override final String name; final int _majorSdkVersion; final String _deviceId; + final bool _isWirelesslyConnected; + final bool _isCoreDevice; final IMobileDevice _iMobileDevice; + final bool _usingCISystem; + + // TODO(vashworth): Remove after Xcode 15 and iOS 17 are in CI (https://github.com/flutter/flutter/issues/132128) + /// Whether XcodeDebug workflow is being forced. + final bool _forceXcodeDebug; // Matches a syslog line from the runner. RegExp _runnerLineRegex; @@ -810,12 +1101,66 @@ class IOSDeviceLogReader extends DeviceLogReader { // Sometimes (race condition?) we try to send a log after the controller has // been closed. See https://github.com/flutter/flutter/issues/99021 for more // context. - void _addToLinesController(String message) { + @visibleForTesting + void addToLinesController(String message, IOSDeviceLogSource source) { if (!linesController.isClosed) { + if (_excludeLog(message, source)) { + return; + } linesController.add(message); } } + /// Used to track messages prefixed with "flutter:" from the fallback log source. + final List<String> _fallbackStreamFlutterMessages = <String>[]; + + /// Used to track if a message prefixed with "flutter:" has been received from the primary log. + bool primarySourceFlutterLogReceived = false; + + /// There are three potential logging sources: `idevicesyslog`, `ios-deploy`, + /// and Unified Logging (Dart VM). When using more than one of these logging + /// sources at a time, prefer to use the primary source. However, if the + /// primary source is not working, use the fallback. + bool _excludeLog(String message, IOSDeviceLogSource source) { + // If no fallback, don't exclude any logs. + if (logSources.fallbackSource == null) { + return false; + } + + // If log is from primary source, don't exclude it unless the fallback was + // quicker and added the message first. + if (source == logSources.primarySource) { + if (!primarySourceFlutterLogReceived && message.startsWith('flutter:')) { + primarySourceFlutterLogReceived = true; + } + + // If the message was already added by the fallback, exclude it to + // prevent duplicates. + final bool foundAndRemoved = _fallbackStreamFlutterMessages.remove(message); + if (foundAndRemoved) { + return true; + } + return false; + } + + // If a flutter log was received from the primary source, that means it's + // working so don't use any messages from the fallback. + if (primarySourceFlutterLogReceived) { + return true; + } + + // When using logs from fallbacks, skip any logs not prefixed with "flutter:". + // This is done because different sources often have different prefixes for + // non-flutter messages, which makes duplicate matching difficult. Also, + // non-flutter messages are not critical for CI tests. + if (!message.startsWith('flutter:')) { + return true; + } + + _fallbackStreamFlutterMessages.add(message); + return false; + } + final List<StreamSubscription<void>> _loggingSubscriptions = <StreamSubscription<void>>[]; @override @@ -835,8 +1180,91 @@ class IOSDeviceLogReader extends DeviceLogReader { static const int minimumUniversalLoggingSdkVersion = 13; - Future<void> _listenToUnifiedLoggingEvents(FlutterVmService connectedVmService) async { + /// Determine the primary and fallback source for device logs. + /// + /// There are three potential logging sources: `idevicesyslog`, `ios-deploy`, + /// and Unified Logging (Dart VM). + @visibleForTesting + _IOSDeviceLogSources get logSources { + // `ios-deploy` stopped working with iOS 17 / Xcode 15, so use `idevicesyslog` instead. + // However, `idevicesyslog` is sometimes unreliable so use Dart VM as a fallback. + // Also, `idevicesyslog` does not work with iOS 17 wireless devices, so use the + // Dart VM for wireless devices. + if (_isCoreDevice || _forceXcodeDebug) { + if (_isWirelesslyConnected) { + return _IOSDeviceLogSources( + primarySource: IOSDeviceLogSource.unifiedLogging, + ); + } + return _IOSDeviceLogSources( + primarySource: IOSDeviceLogSource.idevicesyslog, + fallbackSource: IOSDeviceLogSource.unifiedLogging, + ); + } + + // Use `idevicesyslog` for iOS 12 or less. + // Syslog stopped working on iOS 13 (https://github.com/flutter/flutter/issues/41133). + // However, from at least iOS 16, it has began working again. It's unclear + // why it started working again. if (_majorSdkVersion < minimumUniversalLoggingSdkVersion) { + return _IOSDeviceLogSources( + primarySource: IOSDeviceLogSource.idevicesyslog, + ); + } + + // Use `idevicesyslog` as a fallback to `ios-deploy` when debugging from + // CI system since sometimes `ios-deploy` does not return the device logs: + // https://github.com/flutter/flutter/issues/121231 + if (_usingCISystem && _majorSdkVersion >= 16) { + return _IOSDeviceLogSources( + primarySource: IOSDeviceLogSource.iosDeploy, + fallbackSource: IOSDeviceLogSource.idevicesyslog, + ); + } + + // Use `ios-deploy` to stream logs from the device when the device is not a + // CoreDevice and has iOS 13 or greater. + // When using `ios-deploy` and the Dart VM, prefer the more complete logs + // from the attached debugger, if available. + if (connectedVMService != null && (_iosDeployDebugger == null || !_iosDeployDebugger!.debuggerAttached)) { + return _IOSDeviceLogSources( + primarySource: IOSDeviceLogSource.unifiedLogging, + fallbackSource: IOSDeviceLogSource.iosDeploy, + ); + } + return _IOSDeviceLogSources( + primarySource: IOSDeviceLogSource.iosDeploy, + fallbackSource: IOSDeviceLogSource.unifiedLogging, + ); + } + + /// Whether `idevicesyslog` is used as either the primary or fallback source for device logs. + @visibleForTesting + bool get useSyslogLogging { + return logSources.primarySource == IOSDeviceLogSource.idevicesyslog || + logSources.fallbackSource == IOSDeviceLogSource.idevicesyslog; + } + + /// Whether the Dart VM is used as either the primary or fallback source for device logs. + /// + /// Unified Logging only works after the Dart VM has been connected to. + @visibleForTesting + bool get useUnifiedLogging { + return logSources.primarySource == IOSDeviceLogSource.unifiedLogging || + logSources.fallbackSource == IOSDeviceLogSource.unifiedLogging; + } + + + /// Whether `ios-deploy` is used as either the primary or fallback source for device logs. + @visibleForTesting + bool get useIOSDeployLogging { + return logSources.primarySource == IOSDeviceLogSource.iosDeploy || + logSources.fallbackSource == IOSDeviceLogSource.iosDeploy; + } + + /// Listen to Dart VM for logs on iOS 13 or greater. + Future<void> _listenToUnifiedLoggingEvents(FlutterVmService connectedVmService) async { + if (!useUnifiedLogging) { return; } try { @@ -853,13 +1281,9 @@ class IOSDeviceLogReader extends DeviceLogReader { } void logMessage(vm_service.Event event) { - if (_iosDeployDebugger != null && _iosDeployDebugger!.debuggerAttached) { - // Prefer the more complete logs from the attached debugger. - return; - } final String message = processVmServiceMessage(event); if (message.isNotEmpty) { - _addToLinesController(message); + addToLinesController(message, IOSDeviceLogSource.unifiedLogging); } } @@ -871,9 +1295,11 @@ class IOSDeviceLogReader extends DeviceLogReader { /// Log reader will listen to [debugger.logLines] and will detach debugger on dispose. IOSDeployDebugger? get debuggerStream => _iosDeployDebugger; + + /// Send messages from ios-deploy debugger stream to device log reader stream. set debuggerStream(IOSDeployDebugger? debugger) { - // Logging is gathered from syslog on iOS 13 and earlier. - if (_majorSdkVersion < minimumUniversalLoggingSdkVersion) { + // Logging is gathered from syslog on iOS earlier than 13. + if (!useIOSDeployLogging) { return; } _iosDeployDebugger = debugger; @@ -882,7 +1308,10 @@ class IOSDeviceLogReader extends DeviceLogReader { } // Add the debugger logs to the controller created on initialization. _loggingSubscriptions.add(debugger.logLines.listen( - (String line) => _addToLinesController(_debuggerLineHandler(line)), + (String line) => addToLinesController( + _debuggerLineHandler(line), + IOSDeviceLogSource.iosDeploy, + ), onError: linesController.addError, onDone: linesController.close, cancelOnError: true, @@ -893,18 +1322,26 @@ class IOSDeviceLogReader extends DeviceLogReader { // Strip off the logging metadata (leave the category), or just echo the line. String _debuggerLineHandler(String line) => _debuggerLoggingRegex.firstMatch(line)?.group(1) ?? line; + /// Start and listen to idevicesyslog to get device logs for iOS versions + /// prior to 13 or if [useBothLogDeviceReaders] is true. void _listenToSysLog() { - // syslog is not written on iOS 13+. - if (_majorSdkVersion >= minimumUniversalLoggingSdkVersion) { + if (!useSyslogLogging) { return; } _iMobileDevice.startLogger(_deviceId).then<void>((Process process) { process.stdout.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(_newSyslogLineHandler()); process.stderr.transform<String>(utf8.decoder).transform<String>(const LineSplitter()).listen(_newSyslogLineHandler()); process.exitCode.whenComplete(() { - if (linesController.hasListener) { - linesController.close(); + if (!linesController.hasListener) { + return; } + // When using both log readers, do not close the stream on exit. + // This is to allow ios-deploy to be the source of authority to close + // the stream. + if (useSyslogLogging && useIOSDeployLogging && debuggerStream != null) { + return; + } + linesController.close(); }); assert(idevicesyslogProcess == null); idevicesyslogProcess = process; @@ -926,7 +1363,7 @@ class IOSDeviceLogReader extends DeviceLogReader { return (String line) { if (printing) { if (!_anyLineRegex.hasMatch(line)) { - _addToLinesController(decodeSyslog(line)); + addToLinesController(decodeSyslog(line), IOSDeviceLogSource.idevicesyslog); return; } @@ -938,8 +1375,7 @@ class IOSDeviceLogReader extends DeviceLogReader { if (match != null) { final String logLine = line.substring(match.end); // Only display the log line after the initial device and executable information. - _addToLinesController(decodeSyslog(logLine)); - + addToLinesController(decodeSyslog(logLine), IOSDeviceLogSource.idevicesyslog); printing = true; } }; @@ -955,6 +1391,25 @@ class IOSDeviceLogReader extends DeviceLogReader { } } +enum IOSDeviceLogSource { + /// Gets logs from ios-deploy debugger. + iosDeploy, + /// Gets logs from idevicesyslog. + idevicesyslog, + /// Gets logs from the Dart VM Service. + unifiedLogging, +} + +class _IOSDeviceLogSources { + _IOSDeviceLogSources({ + required this.primarySource, + this.fallbackSource, + }); + + final IOSDeviceLogSource primarySource; + final IOSDeviceLogSource? fallbackSource; +} + /// A [DevicePortForwarder] specialized for iOS usage with iproxy. class IOSDevicePortForwarder extends DevicePortForwarder { diff --git a/packages/flutter_tools/lib/src/ios/ios_deploy.dart b/packages/flutter_tools/lib/src/ios/ios_deploy.dart index 8e0063f997e8b..2d1f7d6ffbe53 100644 --- a/packages/flutter_tools/lib/src/ios/ios_deploy.dart +++ b/packages/flutter_tools/lib/src/ios/ios_deploy.dart @@ -348,12 +348,6 @@ class IOSDeployDebugger { .transform<String>(utf8.decoder) .transform<String>(const LineSplitter()) .listen((String line) { - - // TODO(vashworth): Revert after https://github.com/flutter/flutter/issues/121231 is resolved. - if (line.isNotEmpty) { - _logger.printTrace(line); - } - _monitorIOSDeployFailure(line, _logger); // (lldb) platform select remote-'ios' --sysroot @@ -371,6 +365,7 @@ class IOSDeployDebugger { } final String prompt = line.substring(0, promptEndIndex); lldbRun = RegExp(RegExp.escape(prompt) + r'\s*run'); + _logger.printTrace(line); return; } @@ -389,6 +384,7 @@ class IOSDeployDebugger { // success // 2020-09-15 13:42:25.185474-0700 Runner[477:181141] flutter: The Dart VM service is listening on http://127.0.0.1:57782/ if (lldbRun.hasMatch(line)) { + _logger.printTrace(line); _debuggerState = _IOSDeployDebuggerState.launching; // TODO(vashworth): Remove all debugger state comments when https://github.com/flutter/flutter/issues/126412 is resolved. _logger.printTrace('Debugger state set to launching.'); @@ -397,6 +393,7 @@ class IOSDeployDebugger { // Next line after "run" must be "success", or the attach failed. // Example: "error: process launch failed" if (_debuggerState == _IOSDeployDebuggerState.launching) { + _logger.printTrace(line); final bool attachSuccess = line == 'success'; _debuggerState = attachSuccess ? _IOSDeployDebuggerState.attached : _IOSDeployDebuggerState.detached; _logger.printTrace('Debugger state set to ${attachSuccess ? 'attached' : 'detached'}.'); @@ -411,6 +408,7 @@ class IOSDeployDebugger { // process signal SIGSTOP if (line.contains(_signalStop)) { // The app is about to be stopped. Only show in verbose mode. + _logger.printTrace(line); return; } @@ -423,6 +421,7 @@ class IOSDeployDebugger { if (line == _backTraceAll) { // The app is stopped and the backtrace for all threads will be printed. + _logger.printTrace(line); // Even though we're not "detached", just stopped, mark as detached so the backtrace // is only show in verbose. _debuggerState = _IOSDeployDebuggerState.detached; @@ -439,6 +438,7 @@ class IOSDeployDebugger { if (line.contains('PROCESS_STOPPED') || _lldbProcessStopped.hasMatch(line)) { // The app has been stopped. Dump the backtrace, and detach. + _logger.printTrace(line); _iosDeployProcess?.stdin.writeln(_backTraceAll); if (_processResumeCompleter == null) { detach(); @@ -449,17 +449,20 @@ class IOSDeployDebugger { if (line.contains('PROCESS_EXITED') || _lldbProcessExit.hasMatch(line)) { // The app exited or crashed, so exit. Continue passing debugging // messages to the log reader until it exits to capture crash dumps. + _logger.printTrace(line); exit(); return; } if (_lldbProcessDetached.hasMatch(line)) { // The debugger has detached from the app, and there will be no more debugging messages. // Kill the ios-deploy process. + _logger.printTrace(line); exit(); return; } if (_lldbProcessResuming.hasMatch(line)) { + _logger.printTrace(line); // we marked this detached when we received [_backTraceAll] _debuggerState = _IOSDeployDebuggerState.attached; _logger.printTrace('Debugger state set to attached.'); @@ -467,6 +470,7 @@ class IOSDeployDebugger { } if (_debuggerState != _IOSDeployDebuggerState.attached) { + _logger.printTrace(line); return; } if (lastLineFromDebugger != null && lastLineFromDebugger!.isNotEmpty && line.isEmpty) { @@ -484,7 +488,7 @@ class IOSDeployDebugger { .transform<String>(const LineSplitter()) .listen((String line) { _monitorIOSDeployFailure(line, _logger); - _logger.printTrace('error: $line'); + _logger.printTrace(line); }); unawaited(_iosDeployProcess!.exitCode.then((int status) async { _logger.printTrace('ios-deploy exited with code $exitCode'); diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index cbd1f89c38aec..541c89443a558 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -4,7 +4,9 @@ import 'dart:async'; +import 'package:meta/meta.dart'; import 'package:process/process.dart'; +import 'package:yaml/yaml.dart'; import '../artifacts.dart'; import '../base/file_system.dart'; @@ -41,6 +43,21 @@ import 'xcresult.dart'; const String kConcurrentRunFailureMessage1 = 'database is locked'; const String kConcurrentRunFailureMessage2 = 'there are two concurrent builds running'; +/// User message when missing platform required to use Xcode. +/// +/// Starting with Xcode 15, the simulator is no longer downloaded with Xcode +/// and must be downloaded and installed separately. +@visibleForTesting +String missingPlatformInstructions(String simulatorVersion) => ''' +════════════════════════════════════════════════════════════════════════════════ +$simulatorVersion is not installed. To download and install the platform, open +Xcode, select Xcode > Settings > Platforms, and click the GET button for the +required platform. + +For more information, please visit: + https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes +════════════════════════════════════════════════════════════════════════════════'''; + class IMobileDevice { IMobileDevice({ required Artifacts artifacts, @@ -480,6 +497,14 @@ Future<XcodeBuildResult> buildXcodeProject({ globals.printError('Archive succeeded but the expected xcarchive at $outputDir not found'); } } + + try { + updateShorebirdYaml(buildInfo, app); + } on Exception catch (error) { + globals.printError('[shorebird] failed to generate shorebird configuration.\n$error'); + return XcodeBuildResult(success: false); + } + return XcodeBuildResult( success: true, output: outputDir, @@ -494,6 +519,59 @@ Future<XcodeBuildResult> buildXcodeProject({ } } +void updateShorebirdYaml(BuildInfo buildInfo, BuildableIOSApp app) { + final File shorebirdYaml = globals.fs.file( + globals.fs.path.join( + app.archiveBundleOutputPath, + 'Products', + 'Applications', + app.name ?? 'Runner.app', + 'Frameworks', + 'App.framework', + 'flutter_assets', + 'shorebird.yaml', + ), + ); + if (!shorebirdYaml.existsSync()) { + throw Exception(''' +Cannot find shorebird.yaml in ${shorebirdYaml.absolute.path}. +Please file an issue at: https://github.com/shorebirdtech/shorebird/issues/new +'''); + } + final YamlDocument yaml = loadYamlDocument(shorebirdYaml.readAsStringSync()); + final YamlMap yamlMap = yaml.contents as YamlMap; + final String? flavor = buildInfo.flavor; + String appId = ''; + if (flavor == null) { + final String? defaultAppId = yamlMap['app_id'] as String?; + if (defaultAppId == null || defaultAppId.isEmpty) { + throw Exception('Cannot find "app_id" in shorebird.yaml'); + } + appId = defaultAppId; + } else { + final YamlMap? yamlFlavors = yamlMap['flavors'] as YamlMap?; + if (yamlFlavors == null) { + throw Exception('Cannot find "flavors" in shorebird.yaml.'); + } + final String? flavorAppId = yamlFlavors[flavor] as String?; + if (flavorAppId == null || flavorAppId.isEmpty) { + throw Exception('Cannot find "app_id" for $flavor in shorebird.yaml'); + } + appId = flavorAppId; + } + final StringBuffer yamlContent = StringBuffer(); + final String? baseUrl = yamlMap['base_url'] as String?; + yamlContent.writeln('app_id: $appId'); + if (baseUrl != null) { + yamlContent.writeln('base_url: $baseUrl'); + } + final bool? autoUpdate = yamlMap['auto_update'] as bool?; + if (autoUpdate != null) { + yamlContent.writeln('auto_update: $autoUpdate'); + } + shorebirdYaml.writeAsStringSync(yamlContent.toString(), flush: true); +} + /// Extended attributes applied by Finder can cause code signing errors. Remove them. /// https://developer.apple.com/library/archive/qa/qa1940/_index.html Future<void> removeFinderExtendedAttributes(FileSystemEntity projectDirectory, ProcessUtils processUtils, Logger logger) async { @@ -700,6 +778,11 @@ _XCResultIssueHandlingResult _handleXCResultIssue({required XCResultIssue issue, return _XCResultIssueHandlingResult(requiresProvisioningProfile: true, hasProvisioningProfileIssue: true); } else if (message.toLowerCase().contains('provisioning profile')) { return _XCResultIssueHandlingResult(requiresProvisioningProfile: false, hasProvisioningProfileIssue: true); + } else if (message.toLowerCase().contains('ineligible destinations')) { + final String? missingPlatform = _parseMissingPlatform(message); + if (missingPlatform != null) { + return _XCResultIssueHandlingResult(requiresProvisioningProfile: false, hasProvisioningProfileIssue: false, missingPlatform: missingPlatform); + } } return _XCResultIssueHandlingResult(requiresProvisioningProfile: false, hasProvisioningProfileIssue: false); } @@ -709,6 +792,7 @@ bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcode bool requiresProvisioningProfile = false; bool hasProvisioningProfileIssue = false; bool issueDetected = false; + String? missingPlatform; if (xcResult != null && xcResult.parseSuccess) { for (final XCResultIssue issue in xcResult.issues) { @@ -719,6 +803,7 @@ bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcode if (handlingResult.requiresProvisioningProfile) { requiresProvisioningProfile = true; } + missingPlatform = handlingResult.missingPlatform; issueDetected = true; } } else if (xcResult != null) { @@ -738,6 +823,8 @@ bool _handleIssues(XCResult? xcResult, Logger logger, XcodeBuildExecution? xcode logger.printError(' open ios/Runner.xcworkspace'); logger.printError(''); logger.printError("Also try selecting 'Product > Build' to fix the problem."); + } else if (missingPlatform != null) { + logger.printError(missingPlatformInstructions(missingPlatform), emphasis: true); } return issueDetected; @@ -773,18 +860,41 @@ void _parseIssueInStdout(XcodeBuildExecution xcodeBuildExecution, Logger logger, && (result.stdout?.contains('requires a provisioning profile. Select a provisioning profile in the Signing & Capabilities editor') ?? false)) { logger.printError(noProvisioningProfileInstruction, emphasis: true); } + + if (stderr != null && stderr.contains('Ineligible destinations')) { + final String? version = _parseMissingPlatform(stderr); + if (version != null) { + logger.printError(missingPlatformInstructions(version), emphasis: true); + } + } +} + +String? _parseMissingPlatform(String message) { + final RegExp pattern = RegExp(r'error:(.*?) is not installed\. To use with Xcode, first download and install the platform'); + final RegExpMatch? match = pattern.firstMatch(message); + if (match != null) { + final String? version = match.group(1); + return version; + } + return null; } // The result of [_handleXCResultIssue]. class _XCResultIssueHandlingResult { - _XCResultIssueHandlingResult({required this.requiresProvisioningProfile, required this.hasProvisioningProfileIssue}); + _XCResultIssueHandlingResult({ + required this.requiresProvisioningProfile, + required this.hasProvisioningProfileIssue, + this.missingPlatform, + }); // An issue indicates that user didn't provide the provisioning profile. final bool requiresProvisioningProfile; // An issue indicates that there is a provisioning profile issue. final bool hasProvisioningProfileIssue; + + final String? missingPlatform; } const String _kResultBundlePath = 'temporary_xcresult_bundle'; diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart index 06211c67a07a2..49a6bad2b4627 100644 --- a/packages/flutter_tools/lib/src/ios/simulators.dart +++ b/packages/flutter_tools/lib/src/ios/simulators.dart @@ -15,6 +15,7 @@ import '../base/io.dart'; import '../base/logger.dart'; import '../base/process.dart'; import '../base/utils.dart'; +import '../base/version.dart'; import '../build_info.dart'; import '../convert.dart'; import '../devfs.dart'; @@ -91,6 +92,14 @@ class IOSSimulatorUtils { ); }).whereType<IOSSimulator>().toList(); } + + Future<List<IOSSimulatorRuntime>> getAvailableIOSRuntimes() async { + if (!_xcode.isInstalledAndMeetsVersionCheck) { + return <IOSSimulatorRuntime>[]; + } + + return _simControl.listAvailableIOSRuntimes(); + } } /// A wrapper around the `simctl` command line tool. @@ -293,6 +302,46 @@ class SimControl { _logger.printError('Unable to take screenshot of $deviceId:\n$exception'); } } + + /// Runs `simctl list runtimes available iOS --json` and returns all available iOS simulator runtimes. + Future<List<IOSSimulatorRuntime>> listAvailableIOSRuntimes() async { + final List<IOSSimulatorRuntime> runtimes = <IOSSimulatorRuntime>[]; + final RunResult results = await _processUtils.run( + <String>[ + ..._xcode.xcrunCommand(), + 'simctl', + 'list', + 'runtimes', + 'available', + 'iOS', + '--json', + ], + ); + + if (results.exitCode != 0) { + _logger.printError('Error executing simctl: ${results.exitCode}\n${results.stderr}'); + return runtimes; + } + + try { + final Object? decodeResult = (json.decode(results.stdout) as Map<String, Object?>)['runtimes']; + if (decodeResult is List<Object?>) { + for (final Object? runtimeData in decodeResult) { + if (runtimeData is Map<String, Object?>) { + runtimes.add(IOSSimulatorRuntime.fromJson(runtimeData)); + } + } + } + + return runtimes; + } on FormatException { + // We failed to parse the simctl output, or it returned junk. + // One known message is "Install Started" isn't valid JSON but is + // returned sometimes. + _logger.printError('simctl returned non-JSON response: ${results.stdout}'); + return runtimes; + } + } } @@ -624,6 +673,64 @@ class IOSSimulator extends Device { } } +class IOSSimulatorRuntime { + IOSSimulatorRuntime._({ + this.bundlePath, + this.buildVersion, + this.platform, + this.runtimeRoot, + this.identifier, + this.version, + this.isInternal, + this.isAvailable, + this.name, + }); + + // Example: + // { + // "bundlePath" : "\/Library\/Developer\/CoreSimulator\/Volumes\/iOS_21A5277g\/Library\/Developer\/CoreSimulator\/Profiles\/Runtimes\/iOS 17.0.simruntime", + // "buildversion" : "21A5277g", + // "platform" : "iOS", + // "runtimeRoot" : "\/Library\/Developer\/CoreSimulator\/Volumes\/iOS_21A5277g\/Library\/Developer\/CoreSimulator\/Profiles\/Runtimes\/iOS 17.0.simruntime\/Contents\/Resources\/RuntimeRoot", + // "identifier" : "com.apple.CoreSimulator.SimRuntime.iOS-17-0", + // "version" : "17.0", + // "isInternal" : false, + // "isAvailable" : true, + // "name" : "iOS 17.0", + // "supportedDeviceTypes" : [ + // { + // "bundlePath" : "\/Applications\/Xcode.app\/Contents\/Developer\/Platforms\/iPhoneOS.platform\/Library\/Developer\/CoreSimulator\/Profiles\/DeviceTypes\/iPhone 8.simdevicetype", + // "name" : "iPhone 8", + // "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-8", + // "productFamily" : "iPhone" + // } + // ] + // }, + factory IOSSimulatorRuntime.fromJson(Map<String, Object?> data) { + return IOSSimulatorRuntime._( + bundlePath: data['bundlePath']?.toString(), + buildVersion: data['buildversion']?.toString(), + platform: data['platform']?.toString(), + runtimeRoot: data['runtimeRoot']?.toString(), + identifier: data['identifier']?.toString(), + version: Version.parse(data['version']?.toString()), + isInternal: data['isInternal'] is bool? ? data['isInternal'] as bool? : null, + isAvailable: data['isAvailable'] is bool? ? data['isAvailable'] as bool? : null, + name: data['name']?.toString(), + ); + } + + final String? bundlePath; + final String? buildVersion; + final String? platform; + final String? runtimeRoot; + final String? identifier; + final Version? version; + final bool? isInternal; + final bool? isAvailable; + final String? name; +} + /// Launches the device log reader process on the host and parses the syslog. @visibleForTesting Future<Process> launchDeviceSystemLogTool(IOSSimulator device) async { diff --git a/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart b/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart index 2ef75a209de2a..eeda85a63bf9f 100644 --- a/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart +++ b/packages/flutter_tools/lib/src/ios/xcode_build_settings.dart @@ -35,6 +35,7 @@ Future<void> updateGeneratedXcodeProperties({ String? targetOverride, bool useMacOSConfig = false, String? buildDirOverride, + String? configurationBuildDir, }) async { final List<String> xcodeBuildSettings = await _xcodeBuildSettingsLines( project: project, @@ -42,6 +43,7 @@ Future<void> updateGeneratedXcodeProperties({ targetOverride: targetOverride, useMacOSConfig: useMacOSConfig, buildDirOverride: buildDirOverride, + configurationBuildDir: configurationBuildDir, ); _updateGeneratedXcodePropertiesFile( @@ -143,6 +145,7 @@ Future<List<String>> _xcodeBuildSettingsLines({ String? targetOverride, bool useMacOSConfig = false, String? buildDirOverride, + String? configurationBuildDir, }) async { final List<String> xcodeBuildSettings = <String>[]; @@ -170,6 +173,13 @@ Future<List<String>> _xcodeBuildSettingsLines({ final String buildNumber = parsedBuildNumber(manifest: project.manifest, buildInfo: buildInfo) ?? '1'; xcodeBuildSettings.add('FLUTTER_BUILD_NUMBER=$buildNumber'); + // CoreDevices in debug and profile mode are launched, but not built, via Xcode. + // Set the CONFIGURATION_BUILD_DIR so Xcode knows where to find the app + // bundle to launch. + if (configurationBuildDir != null) { + xcodeBuildSettings.add('CONFIGURATION_BUILD_DIR=$configurationBuildDir'); + } + final LocalEngineInfo? localEngineInfo = globals.artifacts?.localEngineInfo; if (localEngineInfo != null) { final String engineOutPath = localEngineInfo.engineOutPath; diff --git a/packages/flutter_tools/lib/src/ios/xcode_debug.dart b/packages/flutter_tools/lib/src/ios/xcode_debug.dart new file mode 100644 index 0000000000000..563ec9d8e3444 --- /dev/null +++ b/packages/flutter_tools/lib/src/ios/xcode_debug.dart @@ -0,0 +1,498 @@ +// 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:meta/meta.dart'; +import 'package:process/process.dart'; + +import '../base/error_handling_io.dart'; +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/logger.dart'; +import '../base/process.dart'; +import '../base/template.dart'; +import '../convert.dart'; +import '../macos/xcode.dart'; +import '../template.dart'; + +/// A class to handle interacting with Xcode via OSA (Open Scripting Architecture) +/// Scripting to debug Flutter applications. +class XcodeDebug { + XcodeDebug({ + required Logger logger, + required ProcessManager processManager, + required Xcode xcode, + required FileSystem fileSystem, + }) : _logger = logger, + _processUtils = ProcessUtils(logger: logger, processManager: processManager), + _xcode = xcode, + _fileSystem = fileSystem; + + final ProcessUtils _processUtils; + final Logger _logger; + final Xcode _xcode; + final FileSystem _fileSystem; + + /// Process to start Xcode's debug action. + @visibleForTesting + Process? startDebugActionProcess; + + /// Information about the project that is currently being debugged. + @visibleForTesting + XcodeDebugProject? currentDebuggingProject; + + /// Whether the debug action has been started. + bool get debugStarted => currentDebuggingProject != null; + + /// Install, launch, and start a debug session for app through Xcode interface, + /// automated by OSA scripting. First checks if the project is opened in + /// Xcode. If it isn't, open it with the `open` command. + /// + /// The OSA script waits until the project is opened and the debug action + /// has started. It does not wait for the app to install, launch, or start + /// the debug session. + Future<bool> debugApp({ + required XcodeDebugProject project, + required String deviceId, + required List<String> launchArguments, + }) async { + + // If project is not already opened in Xcode, open it. + if (!await _isProjectOpenInXcode(project: project)) { + final bool openResult = await _openProjectInXcode(xcodeWorkspace: project.xcodeWorkspace); + if (!openResult) { + return openResult; + } + } + + currentDebuggingProject = project; + StreamSubscription<String>? stdoutSubscription; + StreamSubscription<String>? stderrSubscription; + try { + startDebugActionProcess = await _processUtils.start( + <String>[ + ..._xcode.xcrunCommand(), + 'osascript', + '-l', + 'JavaScript', + _xcode.xcodeAutomationScriptPath, + 'debug', + '--xcode-path', + _xcode.xcodeAppPath, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + '--project-name', + project.hostAppProjectName, + if (project.expectedConfigurationBuildDir != null) + ...<String>[ + '--expected-configuration-build-dir', + project.expectedConfigurationBuildDir!, + ], + '--device-id', + deviceId, + '--scheme', + project.scheme, + '--skip-building', + '--launch-args', + json.encode(launchArguments), + if (project.verboseLogging) '--verbose', + ], + ); + + final StringBuffer stdoutBuffer = StringBuffer(); + stdoutSubscription = startDebugActionProcess!.stdout + .transform<String>(utf8.decoder) + .transform<String>(const LineSplitter()) + .listen((String line) { + _logger.printTrace(line); + stdoutBuffer.write(line); + }); + + final StringBuffer stderrBuffer = StringBuffer(); + bool permissionWarningPrinted = false; + // console.log from the script are found in the stderr + stderrSubscription = startDebugActionProcess!.stderr + .transform<String>(utf8.decoder) + .transform<String>(const LineSplitter()) + .listen((String line) { + _logger.printTrace('stderr: $line'); + stderrBuffer.write(line); + + // This error may occur if Xcode automation has not been allowed. + // Example: Failed to get workspace: Error: An error occurred. + if (!permissionWarningPrinted && line.contains('Failed to get workspace') && line.contains('An error occurred')) { + _logger.printError( + 'There was an error finding the project in Xcode. Ensure permission ' + 'has been given to control Xcode in Settings > Privacy & Security > Automation.', + ); + permissionWarningPrinted = true; + } + }); + + final int exitCode = await startDebugActionProcess!.exitCode.whenComplete(() async { + await stdoutSubscription?.cancel(); + await stderrSubscription?.cancel(); + startDebugActionProcess = null; + }); + + if (exitCode != 0) { + _logger.printError('Error executing osascript: $exitCode\n$stderrBuffer'); + return false; + } + + final XcodeAutomationScriptResponse? response = parseScriptResponse( + stdoutBuffer.toString(), + ); + if (response == null) { + return false; + } + if (response.status == false) { + _logger.printError('Error starting debug session in Xcode: ${response.errorMessage}'); + return false; + } + if (response.debugResult == null) { + _logger.printError('Unable to get debug results from response: $stdoutBuffer'); + return false; + } + if (response.debugResult?.status != 'running') { + _logger.printError( + 'Unexpected debug results: \n' + ' Status: ${response.debugResult?.status}\n' + ' Completed: ${response.debugResult?.completed}\n' + ' Error Message: ${response.debugResult?.errorMessage}\n' + ); + return false; + } + return true; + } on ProcessException catch (exception) { + _logger.printError('Error executing osascript: $exitCode\n$exception'); + await stdoutSubscription?.cancel(); + await stderrSubscription?.cancel(); + startDebugActionProcess = null; + + return false; + } + } + + /// Kills [startDebugActionProcess] if it's still running. If [force] is true, it + /// will kill all Xcode app processes. Otherwise, it will stop the debug + /// session in Xcode. If the project is temporary, it will close the Xcode + /// window of the project and then delete the project. + Future<bool> exit({ + bool force = false, + @visibleForTesting + bool skipDelay = false, + }) async { + final bool success = (startDebugActionProcess == null) || startDebugActionProcess!.kill(); + + if (force) { + await _forceExitXcode(); + if (currentDebuggingProject != null) { + final XcodeDebugProject project = currentDebuggingProject!; + if (project.isTemporaryProject) { + // Only delete if it exists. This is to prevent crashes when racing + // with shutdown hooks to delete temporary files. + ErrorHandlingFileSystem.deleteIfExists( + project.xcodeProject.parent, + recursive: true, + ); + } + currentDebuggingProject = null; + } + } + + if (currentDebuggingProject != null) { + final XcodeDebugProject project = currentDebuggingProject!; + await stopDebuggingApp( + project: project, + closeXcode: project.isTemporaryProject, + ); + + if (project.isTemporaryProject) { + // Wait a couple seconds before deleting the project. If project is + // still opened in Xcode and it's deleted, it will prompt the user to + // restore it. + if (!skipDelay) { + await Future<void>.delayed(const Duration(seconds: 2)); + } + + try { + project.xcodeProject.parent.deleteSync(recursive: true); + } on FileSystemException { + _logger.printError('Failed to delete temporary Xcode project: ${project.xcodeProject.parent.path}'); + } + } + currentDebuggingProject = null; + } + + return success; + } + + /// Kill all opened Xcode applications. + Future<bool> _forceExitXcode() async { + final RunResult result = await _processUtils.run( + <String>[ + 'killall', + '-9', + 'Xcode', + ], + ); + + if (result.exitCode != 0) { + _logger.printError('Error killing Xcode: ${result.exitCode}\n${result.stderr}'); + return false; + } + return true; + } + + Future<bool> _isProjectOpenInXcode({ + required XcodeDebugProject project, + }) async { + + final RunResult result = await _processUtils.run( + <String>[ + ..._xcode.xcrunCommand(), + 'osascript', + '-l', + 'JavaScript', + _xcode.xcodeAutomationScriptPath, + 'check-workspace-opened', + '--xcode-path', + _xcode.xcodeAppPath, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + if (project.verboseLogging) '--verbose', + ], + ); + + if (result.exitCode != 0) { + _logger.printError('Error executing osascript: ${result.exitCode}\n${result.stderr}'); + return false; + } + + final XcodeAutomationScriptResponse? response = parseScriptResponse(result.stdout); + if (response == null) { + return false; + } + if (response.status == false) { + _logger.printTrace('Error checking if project opened in Xcode: ${response.errorMessage}'); + return false; + } + return true; + } + + @visibleForTesting + XcodeAutomationScriptResponse? parseScriptResponse(String results) { + try { + final Object decodeResult = json.decode(results) as Object; + if (decodeResult is Map<String, Object?>) { + final XcodeAutomationScriptResponse response = XcodeAutomationScriptResponse.fromJson(decodeResult); + // Status should always be found + if (response.status != null) { + return response; + } + } + _logger.printError('osascript returned unexpected JSON response: $results'); + return null; + } on FormatException { + _logger.printError('osascript returned non-JSON response: $results'); + return null; + } + } + + Future<bool> _openProjectInXcode({ + required Directory xcodeWorkspace, + }) async { + try { + await _processUtils.run( + <String>[ + 'open', + '-a', + _xcode.xcodeAppPath, + '-g', // Do not bring the application to the foreground. + '-j', // Launches the app hidden. + '-F', // Open "fresh", without restoring windows. + xcodeWorkspace.path + ], + throwOnError: true, + ); + return true; + } on ProcessException catch (error, stackTrace) { + _logger.printError('$error', stackTrace: stackTrace); + } + return false; + } + + /// Using OSA Scripting, stop the debug session in Xcode. + /// + /// If [closeXcode] is true, it will close the Xcode window that has the + /// project opened. If [promptToSaveOnClose] is true, it will ask the user if + /// they want to save any changes before it closes. + Future<bool> stopDebuggingApp({ + required XcodeDebugProject project, + bool closeXcode = false, + bool promptToSaveOnClose = false, + }) async { + final RunResult result = await _processUtils.run( + <String>[ + ..._xcode.xcrunCommand(), + 'osascript', + '-l', + 'JavaScript', + _xcode.xcodeAutomationScriptPath, + 'stop', + '--xcode-path', + _xcode.xcodeAppPath, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + if (closeXcode) '--close-window', + if (promptToSaveOnClose) '--prompt-to-save', + if (project.verboseLogging) '--verbose', + ], + ); + + if (result.exitCode != 0) { + _logger.printError('Error executing osascript: ${result.exitCode}\n${result.stderr}'); + return false; + } + + final XcodeAutomationScriptResponse? response = parseScriptResponse(result.stdout); + if (response == null) { + return false; + } + if (response.status == false) { + _logger.printError('Error stopping app in Xcode: ${response.errorMessage}'); + return false; + } + return true; + } + + /// Create a temporary empty Xcode project with the application bundle + /// location explicitly set. + Future<XcodeDebugProject> createXcodeProjectWithCustomBundle( + String deviceBundlePath, { + required TemplateRenderer templateRenderer, + @visibleForTesting + Directory? projectDestination, + bool verboseLogging = false, + }) async { + final Directory tempXcodeProject = projectDestination ?? _fileSystem.systemTempDirectory.createTempSync('flutter_empty_xcode.'); + + final Template template = await Template.fromName( + _fileSystem.path.join('xcode', 'ios', 'custom_application_bundle'), + fileSystem: _fileSystem, + templateManifest: null, + logger: _logger, + templateRenderer: templateRenderer, + ); + + template.render( + tempXcodeProject, + <String, Object>{ + 'applicationBundlePath': deviceBundlePath + }, + printStatusWhenWriting: false, + ); + + return XcodeDebugProject( + scheme: 'Runner', + hostAppProjectName: 'Runner', + xcodeProject: tempXcodeProject.childDirectory('Runner.xcodeproj'), + xcodeWorkspace: tempXcodeProject.childDirectory('Runner.xcworkspace'), + isTemporaryProject: true, + verboseLogging: verboseLogging, + ); + } +} + +@visibleForTesting +class XcodeAutomationScriptResponse { + XcodeAutomationScriptResponse._({ + this.status, + this.errorMessage, + this.debugResult, + }); + + factory XcodeAutomationScriptResponse.fromJson(Map<String, Object?> data) { + XcodeAutomationScriptDebugResult? debugResult; + if (data['debugResult'] != null && data['debugResult'] is Map<String, Object?>) { + debugResult = XcodeAutomationScriptDebugResult.fromJson( + data['debugResult']! as Map<String, Object?>, + ); + } + return XcodeAutomationScriptResponse._( + status: data['status'] is bool? ? data['status'] as bool? : null, + errorMessage: data['errorMessage']?.toString(), + debugResult: debugResult, + ); + } + + final bool? status; + final String? errorMessage; + final XcodeAutomationScriptDebugResult? debugResult; +} + +@visibleForTesting +class XcodeAutomationScriptDebugResult { + XcodeAutomationScriptDebugResult._({ + required this.completed, + required this.status, + required this.errorMessage, + }); + + factory XcodeAutomationScriptDebugResult.fromJson(Map<String, Object?> data) { + return XcodeAutomationScriptDebugResult._( + completed: data['completed'] is bool? ? data['completed'] as bool? : null, + status: data['status']?.toString(), + errorMessage: data['errorMessage']?.toString(), + ); + } + + /// Whether this scheme action has completed (sucessfully or otherwise). Will + /// be false if still running. + final bool? completed; + + /// The status of the debug action. Potential statuses include: + /// `not yet started`, `‌running`, `‌cancelled`, `‌failed`, `‌error occurred`, + /// and `‌succeeded`. + /// + /// Only the status of `‌running` indicates the debug action has started successfully. + /// For example, `‌succeeded` often does not indicate success as if the action fails, + /// it will sometimes return `‌succeeded`. + final String? status; + + /// When [status] is `‌error occurred`, an error message is provided. + /// Otherwise, this will be null. + final String? errorMessage; +} + +class XcodeDebugProject { + XcodeDebugProject({ + required this.scheme, + required this.xcodeWorkspace, + required this.xcodeProject, + required this.hostAppProjectName, + this.expectedConfigurationBuildDir, + this.isTemporaryProject = false, + this.verboseLogging = false, + }); + + final String scheme; + final Directory xcodeWorkspace; + final Directory xcodeProject; + final String hostAppProjectName; + final String? expectedConfigurationBuildDir; + final bool isTemporaryProject; + + /// When [verboseLogging] is true, the xcode_debug.js script will log + /// additional information via console.log, which is sent to stderr. + final bool verboseLogging; +} diff --git a/packages/flutter_tools/lib/src/ios/xcresult.dart b/packages/flutter_tools/lib/src/ios/xcresult.dart index 5bb520e0a5046..1329d6b3bd754 100644 --- a/packages/flutter_tools/lib/src/ios/xcresult.dart +++ b/packages/flutter_tools/lib/src/ios/xcresult.dart @@ -104,6 +104,13 @@ class XCResult { issueDiscarder: issueDiscarders, )); } + + final Object? actionsMap = resultJson['actions']; + if (actionsMap is Map<String, Object?>) { + final List<XCResultIssue> actionIssues = _parseActionIssues(actionsMap, issueDiscarders: issueDiscarders); + issues.addAll(actionIssues); + } + return XCResult._(issues: issues); } @@ -383,3 +390,84 @@ List<XCResultIssue> _parseIssuesFromIssueSummariesJson({ } return issues; } + +List<XCResultIssue> _parseActionIssues( + Map<String, Object?> actionsMap, { + required List<XCResultIssueDiscarder> issueDiscarders, +}) { + // Example of json: + // { + // "actions" : { + // "_values" : [ + // { + // "actionResult" : { + // "_type" : { + // "_name" : "ActionResult" + // }, + // "issues" : { + // "_type" : { + // "_name" : "ResultIssueSummaries" + // }, + // "testFailureSummaries" : { + // "_type" : { + // "_name" : "Array" + // }, + // "_values" : [ + // { + // "_type" : { + // "_name" : "TestFailureIssueSummary", + // "_supertype" : { + // "_name" : "IssueSummary" + // } + // }, + // "issueType" : { + // "_type" : { + // "_name" : "String" + // }, + // "_value" : "Uncategorized" + // }, + // "message" : { + // "_type" : { + // "_name" : "String" + // }, + // "_value" : "Unable to find a destination matching the provided destination specifier:\n\t\t{ id:1234D567-890C-1DA2-34E5-F6789A0123C4 }\n\n\tIneligible destinations for the \"Runner\" scheme:\n\t\t{ platform:iOS, id:dvtdevice-DVTiPhonePlaceholder-iphoneos:placeholder, name:Any iOS Device, error:iOS 17.0 is not installed. To use with Xcode, first download and install the platform }" + // } + // } + // ] + // } + // } + // } + // } + // ] + // } + // } + final List<XCResultIssue> issues = <XCResultIssue>[]; + final Object? actionsValues = actionsMap['_values']; + if (actionsValues is! List<Object?>) { + return issues; + } + + for (final Object? actionValue in actionsValues) { + if (actionValue is!Map<String, Object?>) { + continue; + } + final Object? actionResult = actionValue['actionResult']; + if (actionResult is! Map<String, Object?>) { + continue; + } + final Object? actionResultIssues = actionResult['issues']; + if (actionResultIssues is! Map<String, Object?>) { + continue; + } + final Object? testFailureSummaries = actionResultIssues['testFailureSummaries']; + if (testFailureSummaries is Map<String, Object?>) { + issues.addAll(_parseIssuesFromIssueSummariesJson( + type: XCResultIssueType.error, + issueSummariesJson: testFailureSummaries, + issueDiscarder: issueDiscarders, + )); + } + } + + return issues; + } diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index f4d4f706e7193..903eae94f6bfc 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart @@ -493,7 +493,7 @@ class WebAssetServer implements AssetReader { /// Write a single file into the in-memory cache. void writeFile(String filePath, String contents) { - writeBytes(filePath, utf8.encode(contents) as Uint8List); + writeBytes(filePath, const Utf8Encoder().convert(contents)); } void writeBytes(String filePath, Uint8List contents) { diff --git a/packages/flutter_tools/lib/src/license_collector.dart b/packages/flutter_tools/lib/src/license_collector.dart index 041467b4d27d3..289962a40e564 100644 --- a/packages/flutter_tools/lib/src/license_collector.dart +++ b/packages/flutter_tools/lib/src/license_collector.dart @@ -82,11 +82,10 @@ class LicenseCollector { } } - final List<String> combinedLicensesList = packageLicenses.keys - .map<String>((String license) { - final List<String> packageNames = packageLicenses[license]!.toList() - ..sort(); - return '${packageNames.join('\n')}\n\n$license'; + final List<String> combinedLicensesList = packageLicenses.entries + .map<String>((MapEntry<String, Set<String>> entry) { + final List<String> packageNames = entry.value.toList()..sort(); + return '${packageNames.join('\n')}\n\n${entry.key}'; }).toList(); combinedLicensesList.sort(); diff --git a/packages/flutter_tools/lib/src/linux/linux_doctor.dart b/packages/flutter_tools/lib/src/linux/linux_doctor.dart index 39b88e6cae97f..b5af1d5fedb75 100644 --- a/packages/flutter_tools/lib/src/linux/linux_doctor.dart +++ b/packages/flutter_tools/lib/src/linux/linux_doctor.dart @@ -64,7 +64,7 @@ class LinuxDoctorValidator extends DoctorValidator { final Map<String, _VersionInfo?> installedVersions = <String, _VersionInfo?>{ // Sort the check to make the call order predictable for unit tests. - for (String binary in _requiredBinaryVersions.keys.toList()..sort()) + for (final String binary in _requiredBinaryVersions.keys.toList()..sort()) binary: await _getBinaryVersion(binary), }; diff --git a/packages/flutter_tools/lib/src/localizations/gen_l10n.dart b/packages/flutter_tools/lib/src/localizations/gen_l10n.dart index bd8ea9f03c44f..17bc3641a2ebe 100644 --- a/packages/flutter_tools/lib/src/localizations/gen_l10n.dart +++ b/packages/flutter_tools/lib/src/localizations/gen_l10n.dart @@ -3,26 +3,30 @@ // found in the LICENSE file. import 'package:meta/meta.dart'; +import 'package:process/process.dart'; +import '../artifacts.dart'; import '../base/common.dart'; import '../base/file_system.dart'; +import '../base/io.dart'; import '../base/logger.dart'; import '../convert.dart'; import '../flutter_manifest.dart'; - import 'gen_l10n_templates.dart'; import 'gen_l10n_types.dart'; import 'localizations_utils.dart'; import 'message_parser.dart'; /// Run the localizations generation script with the configuration [options]. -LocalizationsGenerator generateLocalizations({ +Future<LocalizationsGenerator> generateLocalizations({ required Directory projectDir, Directory? dependenciesDir, required LocalizationOptions options, required Logger logger, required FileSystem fileSystem, -}) { + required Artifacts artifacts, + required ProcessManager processManager, +}) async { // If generating a synthetic package, generate a warning if // flutter: generate is not set. final FlutterManifest? flutterManifest = FlutterManifest.createFromPath( @@ -71,6 +75,37 @@ LocalizationsGenerator generateLocalizations({ } on L10nException catch (e) { throwToolExit(e.message); } + + final List<String> outputFileList = generator.outputFileList; + final File? untranslatedMessagesFile = generator.untranslatedMessagesFile; + + // All other post processing. + if (options.format) { + final List<String> formatFileList = outputFileList.toList(); + if (untranslatedMessagesFile != null) { + // Don't format the messages file using `dart format`. + formatFileList.remove(untranslatedMessagesFile.absolute.path); + } + if (formatFileList.isEmpty) { + return generator; + } + final String dartBinary = artifacts.getArtifactPath(Artifact.engineDartBinary); + final List<String> command = <String>[dartBinary, 'format', ...formatFileList]; + final ProcessResult result = await processManager.run(command); + if (result.exitCode != 0) { + throw ProcessException( + dartBinary, + command, + ''' +`dart format` failed with exit code ${result.exitCode} + +stdout:\n${result.stdout}\n +stderr:\n${result.stderr}''', + result.exitCode, + ); + } + } + return generator; } @@ -1122,6 +1157,7 @@ class LocalizationsGenerator { // When traversing through a placeholderExpr node, return "$placeholderName". // When traversing through a pluralExpr node, return "$tempVarN" and add variable declaration in "tempVariables". // When traversing through a selectExpr node, return "$tempVarN" and add variable declaration in "tempVariables". + // When traversing through an argumentExpr node, return "$tempVarN" and add variable declaration in "tempVariables". // When traversing through a message node, return concatenation of all of "generateVariables(child)" for each child. String generateVariables(Node node, { bool isRoot = false }) { switch (node.type) { @@ -1224,6 +1260,34 @@ The plural cases must be one of "=0", "=1", "=2", "zero", "one", "two", "few", " .replaceAll('@(selectCases)', selectLogicArgs.join('\n')) ); return '\$$tempVarName'; + case ST.argumentExpr: + requiresIntlImport = true; + assert(node.children[1].type == ST.identifier); + assert(node.children[3].type == ST.argType); + assert(node.children[7].type == ST.identifier); + final String identifierName = node.children[1].value!; + final Node formatType = node.children[7]; + // Check that formatType is a valid intl.DateFormat. + if (!validDateFormats.contains(formatType.value)) { + throw L10nParserException( + 'Date format "${formatType.value!}" for placeholder ' + '$identifierName does not have a corresponding DateFormat ' + "constructor\n. Check the intl library's DateFormat class " + 'constructors for allowed date formats, or set "isCustomDateFormat" attribute ' + 'to "true".', + _inputFileNames[locale]!, + message.resourceId, + translationForMessage, + formatType.positionInMessage, + ); + } + final String tempVarName = getTempVariableName(); + tempVariables.add(dateVariableTemplate + .replaceAll('@(varName)', tempVarName) + .replaceAll('@(formatType)', formatType.value!) + .replaceAll('@(argument)', identifierName) + ); + return '\$$tempVarName'; // ignore: no_default_cases default: throw Exception('Cannot call "generateHelperMethod" on node type ${node.type}'); diff --git a/packages/flutter_tools/lib/src/localizations/gen_l10n_templates.dart b/packages/flutter_tools/lib/src/localizations/gen_l10n_templates.dart index 022e4962e3a0a..15e9cd6b4aa87 100644 --- a/packages/flutter_tools/lib/src/localizations/gen_l10n_templates.dart +++ b/packages/flutter_tools/lib/src/localizations/gen_l10n_templates.dart @@ -157,6 +157,9 @@ const String selectVariableTemplate = ''' }, );'''; +const String dateVariableTemplate = ''' + String @(varName) = intl.DateFormat.@(formatType)(localeName).format(@(argument));'''; + const String classFileTemplate = ''' @(header)@(requiresIntlImport)import '@(fileName)'; diff --git a/packages/flutter_tools/lib/src/localizations/gen_l10n_types.dart b/packages/flutter_tools/lib/src/localizations/gen_l10n_types.dart index 7aad6d99134f7..a978e43794343 100644 --- a/packages/flutter_tools/lib/src/localizations/gen_l10n_types.dart +++ b/packages/flutter_tools/lib/src/localizations/gen_l10n_types.dart @@ -27,7 +27,7 @@ import 'message_parser.dart'; // * <https://pub.dev/packages/intl> // * <https://pub.dev/documentation/intl/latest/intl/DateFormat-class.html> // * <https://api.dartlang.org/stable/2.7.0/dart-core/DateTime-class.html> -const Set<String> _validDateFormats = <String>{ +const Set<String> validDateFormats = <String>{ 'd', 'E', 'EEEE', @@ -244,13 +244,14 @@ class Placeholder { String? type; bool isPlural = false; bool isSelect = false; + bool isDateTime = false; + bool requiresDateFormatting = false; bool get requiresFormatting => requiresDateFormatting || requiresNumFormatting; - bool get requiresDateFormatting => type == 'DateTime'; bool get requiresNumFormatting => <String>['int', 'num', 'double'].contains(type) && format != null; bool get hasValidNumberFormat => _validNumberFormats.contains(format); bool get hasNumberFormatWithParameters => _numberFormatsWithNamedParameters.contains(format); - bool get hasValidDateFormat => _validDateFormats.contains(format); + bool get hasValidDateFormat => validDateFormats.contains(format); static String? _stringAttribute( String resourceId, @@ -488,7 +489,12 @@ class Message { final List<Node> traversalStack = <Node>[parsedMessages[locale]!]; while (traversalStack.isNotEmpty) { final Node node = traversalStack.removeLast(); - if (<ST>[ST.placeholderExpr, ST.pluralExpr, ST.selectExpr].contains(node.type)) { + if (<ST>[ + ST.placeholderExpr, + ST.pluralExpr, + ST.selectExpr, + ST.argumentExpr + ].contains(node.type)) { final String identifier = node.children[1].value!; Placeholder? placeholder = getPlaceholder(identifier); if (placeholder == null) { @@ -499,6 +505,14 @@ class Message { placeholder.isPlural = true; } else if (node.type == ST.selectExpr) { placeholder.isSelect = true; + } else if (node.type == ST.argumentExpr) { + placeholder.isDateTime = true; + } else { + // Here the node type must be ST.placeholderExpr. + // A DateTime placeholder must require date formatting. + if (placeholder.type == 'DateTime') { + placeholder.requiresDateFormatting = true; + } } } traversalStack.addAll(node.children); @@ -510,9 +524,16 @@ class Message { ..sort((MapEntry<String, Placeholder> p1, MapEntry<String, Placeholder> p2) => p1.key.compareTo(p2.key)) ); + bool atMostOneOf(bool x, bool y, bool z) { + return x && !y && !z + || !x && y && !z + || !x && !y && z + || !x && !y && !z; + } + for (final Placeholder placeholder in placeholders.values) { - if (placeholder.isPlural && placeholder.isSelect) { - throw L10nException('Placeholder is used as both a plural and select in certain languages.'); + if (!atMostOneOf(placeholder.isPlural, placeholder.isDateTime, placeholder.isSelect)) { + throw L10nException('Placeholder is used as plural/select/datetime in certain languages.'); } else if (placeholder.isPlural) { if (placeholder.type == null) { placeholder.type = 'num'; @@ -526,6 +547,12 @@ class Message { } else if (placeholder.type != 'String') { throw L10nException("Placeholders used in selects must be of type 'String'"); } + } else if (placeholder.isDateTime) { + if (placeholder.type == null) { + placeholder.type = 'DateTime'; + } else if (placeholder.type != 'DateTime') { + throw L10nException("Placeholders used in datetime expressions much be of type 'DateTime'"); + } } placeholder.type ??= 'Object'; } diff --git a/packages/flutter_tools/lib/src/localizations/message_parser.dart b/packages/flutter_tools/lib/src/localizations/message_parser.dart index dc05744f160d1..49a04fff4858d 100644 --- a/packages/flutter_tools/lib/src/localizations/message_parser.dart +++ b/packages/flutter_tools/lib/src/localizations/message_parser.dart @@ -22,11 +22,16 @@ enum ST { number, identifier, empty, + colon, + date, + time, // Nonterminal Types message, placeholderExpr, + argumentExpr, + pluralExpr, pluralParts, pluralPart, @@ -34,6 +39,8 @@ enum ST { selectExpr, selectParts, selectPart, + + argType, } // The grammar of the syntax. @@ -43,6 +50,7 @@ Map<ST, List<List<ST>>> grammar = <ST, List<List<ST>>>{ <ST>[ST.placeholderExpr, ST.message], <ST>[ST.pluralExpr, ST.message], <ST>[ST.selectExpr, ST.message], + <ST>[ST.argumentExpr, ST.message], <ST>[ST.empty], ], ST.placeholderExpr: <List<ST>>[ @@ -73,6 +81,13 @@ Map<ST, List<List<ST>>> grammar = <ST, List<List<ST>>>{ <ST>[ST.number, ST.openBrace, ST.message, ST.closeBrace], <ST>[ST.other, ST.openBrace, ST.message, ST.closeBrace], ], + ST.argumentExpr: <List<ST>>[ + <ST>[ST.openBrace, ST.identifier, ST.comma, ST.argType, ST.comma, ST.colon, ST.colon, ST.identifier, ST.closeBrace], + ], + ST.argType: <List<ST>>[ + <ST>[ST.date], + <ST>[ST.time], + ], }; class Node { @@ -100,6 +115,8 @@ class Node { Node.selectKeyword(this.positionInMessage): type = ST.select, value = 'select'; Node.otherKeyword(this.positionInMessage): type = ST.other, value = 'other'; Node.empty(this.positionInMessage): type = ST.empty, value = ''; + Node.dateKeyword(this.positionInMessage): type = ST.date, value = 'date'; + Node.timeKeyword(this.positionInMessage): type = ST.time, value = 'time'; String? value; late ST type; @@ -162,6 +179,7 @@ RegExp numeric = RegExp(r'[0-9]+'); RegExp alphanumeric = RegExp(r'[a-zA-Z0-9|_]+'); RegExp comma = RegExp(r','); RegExp equalSign = RegExp(r'='); +RegExp colon = RegExp(r':'); // List of token matchers ordered by precedence Map<ST, RegExp> matchers = <ST, RegExp>{ @@ -169,6 +187,7 @@ Map<ST, RegExp> matchers = <ST, RegExp>{ ST.number: numeric, ST.comma: comma, ST.equalSign: equalSign, + ST.colon: colon, ST.identifier: alphanumeric, }; @@ -312,6 +331,10 @@ class Parser { matchedType = ST.select; case 'other': matchedType = ST.other; + case 'date': + matchedType = ST.date; + case 'time': + matchedType = ST.time; } tokens.add(Node(matchedType!, startIndex, value: match.group(0))); startIndex = match.end; @@ -354,9 +377,9 @@ class Parser { switch (symbol) { case ST.message: if (tokens.isEmpty) { - parseAndConstructNode(ST.message, 4); + parseAndConstructNode(ST.message, 5); } else if (tokens[0].type == ST.closeBrace) { - parseAndConstructNode(ST.message, 4); + parseAndConstructNode(ST.message, 5); } else if (tokens[0].type == ST.string) { parseAndConstructNode(ST.message, 0); } else if (tokens[0].type == ST.openBrace) { @@ -364,6 +387,8 @@ class Parser { parseAndConstructNode(ST.message, 2); } else if (3 < tokens.length && tokens[3].type == ST.select) { parseAndConstructNode(ST.message, 3); + } else if (3 < tokens.length && (tokens[3].type == ST.date || tokens[3].type == ST.time)) { + parseAndConstructNode(ST.message, 4); } else { parseAndConstructNode(ST.message, 1); } @@ -373,6 +398,16 @@ class Parser { } case ST.placeholderExpr: parseAndConstructNode(ST.placeholderExpr, 0); + case ST.argumentExpr: + parseAndConstructNode(ST.argumentExpr, 0); + case ST.argType: + if (tokens.isNotEmpty && tokens[0].type == ST.date) { + parseAndConstructNode(ST.argType, 0); + } else if (tokens.isNotEmpty && tokens[0].type == ST.time) { + parseAndConstructNode(ST.argType, 1); + } else { + throw L10nException('ICU Syntax Error. Found unknown argument type.'); + } case ST.pluralExpr: parseAndConstructNode(ST.pluralExpr, 0); case ST.pluralParts: diff --git a/packages/flutter_tools/lib/src/macos/application_package.dart b/packages/flutter_tools/lib/src/macos/application_package.dart index b2b993476f463..036de96de6551 100644 --- a/packages/flutter_tools/lib/src/macos/application_package.dart +++ b/packages/flutter_tools/lib/src/macos/application_package.dart @@ -173,7 +173,7 @@ class BuildableMacOSApp extends MacOSApp { String bundleDirectory(BuildInfo buildInfo) { return sentenceCase(buildInfo.mode.cliName) + (buildInfo.flavor != null - ? ' ${sentenceCase(buildInfo.flavor!)}' + ? '-${buildInfo.flavor!}' : ''); } diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart index 95a967e450619..e675b50c5efa8 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapods.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart @@ -19,6 +19,7 @@ import '../build_info.dart'; import '../cache.dart'; import '../ios/xcodeproj.dart'; import '../migrations/cocoapods_script_symlink.dart'; +import '../migrations/cocoapods_toolchain_directory_migration.dart'; import '../reporting/reporting.dart'; import '../xcode_project.dart'; @@ -172,6 +173,11 @@ class CocoaPods { // This migrator works around a CocoaPods bug, and should be run after `pod install` is run. final ProjectMigration postPodMigration = ProjectMigration(<ProjectMigrator>[ CocoaPodsScriptReadlink(xcodeProject, _xcodeProjectInterpreter, _logger), + CocoaPodsToolchainDirectoryMigration( + xcodeProject, + _xcodeProjectInterpreter, + _logger, + ), ]); postPodMigration.run(); diff --git a/packages/flutter_tools/lib/src/macos/macos_device.dart b/packages/flutter_tools/lib/src/macos/macos_device.dart index 1de3ad9ce5d43..478c8ac27c125 100644 --- a/packages/flutter_tools/lib/src/macos/macos_device.dart +++ b/packages/flutter_tools/lib/src/macos/macos_device.dart @@ -47,6 +47,9 @@ class MacOSDevice extends DesktopDevice { @override String get name => 'macOS'; + @override + bool get supportsImpeller => true; + @override Future<TargetPlatform> get targetPlatform async => TargetPlatform.darwin; @@ -54,9 +57,8 @@ class MacOSDevice extends DesktopDevice { Future<String> get targetPlatformDisplayName async { if (_operatingSystemUtils.hostPlatform == HostPlatform.darwin_arm64) { return 'darwin-arm64'; - } else { - return 'darwin-x64'; } + return 'darwin-x64'; } @override diff --git a/packages/flutter_tools/lib/src/macos/xcdevice.dart b/packages/flutter_tools/lib/src/macos/xcdevice.dart index 1af4c7686887d..a2c66eea776d1 100644 --- a/packages/flutter_tools/lib/src/macos/xcdevice.dart +++ b/packages/flutter_tools/lib/src/macos/xcdevice.dart @@ -8,19 +8,23 @@ import 'package:meta/meta.dart'; import 'package:process/process.dart'; import '../artifacts.dart'; +import '../base/file_system.dart'; import '../base/io.dart'; import '../base/logger.dart'; import '../base/platform.dart'; import '../base/process.dart'; +import '../base/version.dart'; import '../build_info.dart'; import '../cache.dart'; import '../convert.dart'; import '../device.dart'; import '../globals.dart' as globals; +import '../ios/core_devices.dart'; import '../ios/devices.dart'; import '../ios/ios_deploy.dart'; import '../ios/iproxy.dart'; import '../ios/mac.dart'; +import '../ios/xcode_debug.dart'; import '../reporting/reporting.dart'; import 'xcode.dart'; @@ -64,6 +68,10 @@ class XCDevice { required Xcode xcode, required Platform platform, required IProxy iproxy, + required FileSystem fileSystem, + @visibleForTesting + IOSCoreDeviceControl? coreDeviceControl, + XcodeDebug? xcodeDebug, }) : _processUtils = ProcessUtils(logger: logger, processManager: processManager), _logger = logger, _iMobileDevice = IMobileDevice( @@ -79,6 +87,18 @@ class XCDevice { platform: platform, processManager: processManager, ), + _coreDeviceControl = coreDeviceControl ?? IOSCoreDeviceControl( + logger: logger, + processManager: processManager, + xcode: xcode, + fileSystem: fileSystem, + ), + _xcodeDebug = xcodeDebug ?? XcodeDebug( + logger: logger, + processManager: processManager, + xcode: xcode, + fileSystem: fileSystem, + ), _iProxy = iproxy, _xcode = xcode { @@ -98,6 +118,8 @@ class XCDevice { final IOSDeploy _iosDeploy; final Xcode _xcode; final IProxy _iProxy; + final IOSCoreDeviceControl _coreDeviceControl; + final XcodeDebug _xcodeDebug; List<Object>? _cachedListResults; @@ -456,6 +478,17 @@ class XCDevice { return const <IOSDevice>[]; } + final Map<String, IOSCoreDevice> coreDeviceMap = <String, IOSCoreDevice>{}; + if (_xcode.isDevicectlInstalled) { + final List<IOSCoreDevice> coreDevices = await _coreDeviceControl.getCoreDevices(); + for (final IOSCoreDevice device in coreDevices) { + if (device.udid == null) { + continue; + } + coreDeviceMap[device.udid!] = device; + } + } + // [ // { // "simulator" : true, @@ -493,7 +526,7 @@ class XCDevice { // }, // ... - final List<IOSDevice> devices = <IOSDevice>[]; + final Map<String, IOSDevice> deviceMap = <String, IOSDevice>{}; for (final Object device in allAvailableDevices) { if (device is Map<String, Object?>) { // Only include iPhone, iPad, iPod, or other iOS devices. @@ -531,33 +564,76 @@ class XCDevice { } } - String? sdkVersion = _sdkVersion(device); + String? sdkVersionString = _sdkVersion(device); - if (sdkVersion != null) { + if (sdkVersionString != null) { final String? buildVersion = _buildVersion(device); if (buildVersion != null) { - sdkVersion = '$sdkVersion $buildVersion'; + sdkVersionString = '$sdkVersionString $buildVersion'; + } + } + + // Duplicate entries started appearing in Xcode 15, possibly due to + // Xcode's new device connectivity stack. + // If a duplicate entry is found in `xcdevice list`, don't overwrite + // existing entry when the existing entry indicates the device is + // connected and the current entry indicates the device is not connected. + // Don't overwrite if current entry's sdkVersion is null. + // Don't overwrite if both entries indicate the device is not + // connected and the existing entry has a higher sdkVersion. + if (deviceMap.containsKey(identifier)) { + final IOSDevice deviceInMap = deviceMap[identifier]!; + if ((deviceInMap.isConnected && !isConnected) || sdkVersionString == null) { + continue; + } + + final Version? sdkVersion = Version.parse(sdkVersionString); + if (!deviceInMap.isConnected && + !isConnected && + sdkVersion != null && + deviceInMap.sdkVersion != null && + deviceInMap.sdkVersion!.compareTo(sdkVersion) > 0) { + continue; + } + } + + DeviceConnectionInterface connectionInterface = _interfaceType(device); + + // CoreDevices (devices with iOS 17 and greater) no longer reflect the + // correct connection interface or developer mode status in `xcdevice`. + // Use `devicectl` to get that information for CoreDevices. + final IOSCoreDevice? coreDevice = coreDeviceMap[identifier]; + if (coreDevice != null) { + if (coreDevice.connectionInterface != null) { + connectionInterface = coreDevice.connectionInterface!; + } + + if (coreDevice.deviceProperties?.developerModeStatus != 'enabled') { + devModeEnabled = false; } } - devices.add(IOSDevice( + deviceMap[identifier] = IOSDevice( identifier, name: name, cpuArchitecture: _cpuArchitecture(device), - connectionInterface: _interfaceType(device), + connectionInterface: connectionInterface, isConnected: isConnected, - sdkVersion: sdkVersion, + sdkVersion: sdkVersionString, iProxy: _iProxy, fileSystem: globals.fs, logger: _logger, iosDeploy: _iosDeploy, iMobileDevice: _iMobileDevice, + coreDeviceControl: _coreDeviceControl, + xcodeDebug: _xcodeDebug, platform: globals.platform, - devModeEnabled: devModeEnabled - )); + devModeEnabled: devModeEnabled, + isCoreDevice: coreDevice != null, + ); } } - return devices; + return deviceMap.values.toList(); } /// Despite the name, com.apple.platform.iphoneos includes iPhone, iPads, and all iOS devices. diff --git a/packages/flutter_tools/lib/src/macos/xcode.dart b/packages/flutter_tools/lib/src/macos/xcode.dart index ff0b89e7e7f9a..540f08d5f3b38 100644 --- a/packages/flutter_tools/lib/src/macos/xcode.dart +++ b/packages/flutter_tools/lib/src/macos/xcode.dart @@ -14,8 +14,10 @@ import '../base/io.dart'; import '../base/logger.dart'; import '../base/platform.dart'; import '../base/process.dart'; +import '../base/user_messages.dart'; import '../base/version.dart'; import '../build_info.dart'; +import '../cache.dart'; import '../ios/xcodeproj.dart'; Version get xcodeRequiredVersion => Version(14, null, null); @@ -44,11 +46,16 @@ class Xcode { required Logger logger, required FileSystem fileSystem, required XcodeProjectInterpreter xcodeProjectInterpreter, + required UserMessages userMessages, + String? flutterRoot, }) : _platform = platform, _fileSystem = fileSystem, _xcodeProjectInterpreter = xcodeProjectInterpreter, + _userMessage = userMessages, + _flutterRoot = flutterRoot, _processUtils = - ProcessUtils(logger: logger, processManager: processManager); + ProcessUtils(logger: logger, processManager: processManager), + _logger = logger; /// Create an [Xcode] for testing. /// @@ -60,16 +67,21 @@ class Xcode { XcodeProjectInterpreter? xcodeProjectInterpreter, Platform? platform, FileSystem? fileSystem, + String? flutterRoot, + Logger? logger, }) { platform ??= FakePlatform( operatingSystem: 'macos', environment: <String, String>{}, ); + logger ??= BufferLogger.test(); return Xcode( platform: platform, processManager: processManager, fileSystem: fileSystem ?? MemoryFileSystem.test(), - logger: BufferLogger.test(), + userMessages: UserMessages(), + flutterRoot: flutterRoot, + logger: logger, xcodeProjectInterpreter: xcodeProjectInterpreter ?? XcodeProjectInterpreter.test(processManager: processManager), ); } @@ -78,6 +90,9 @@ class Xcode { final ProcessUtils _processUtils; final FileSystem _fileSystem; final XcodeProjectInterpreter _xcodeProjectInterpreter; + final UserMessages _userMessage; + final String? _flutterRoot; + final Logger _logger; bool get isInstalledAndMeetsVersionCheck => _platform.isMacOS && isInstalled && isRequiredVersionSatisfactory; @@ -97,6 +112,38 @@ class Xcode { return _xcodeSelectPath; } + String get xcodeAppPath { + // If the Xcode Select Path is /Applications/Xcode.app/Contents/Developer, + // the path to Xcode App is /Applications/Xcode.app + + final String? pathToXcode = xcodeSelectPath; + if (pathToXcode == null || pathToXcode.isEmpty) { + throwToolExit(_userMessage.xcodeMissing); + } + final int index = pathToXcode.indexOf('.app'); + if (index == -1) { + throwToolExit(_userMessage.xcodeMissing); + } + return pathToXcode.substring(0, index + 4); + } + + /// Path to script to automate debugging through Xcode. Used in xcode_debug.dart. + /// Located in this file to make it easily overrideable in google3. + String get xcodeAutomationScriptPath { + final String flutterRoot = _flutterRoot ?? Cache.flutterRoot!; + final String flutterToolsAbsolutePath = _fileSystem.path.join( + flutterRoot, + 'packages', + 'flutter_tools', + ); + + final String filePath = '$flutterToolsAbsolutePath/bin/xcode_debug.js'; + if (!_fileSystem.file(filePath).existsSync()) { + throwToolExit('Unable to find Xcode automation script at $filePath'); + } + return filePath; + } + bool get isInstalled => _xcodeProjectInterpreter.isInstalled; Version? get currentVersion => _xcodeProjectInterpreter.version; @@ -146,6 +193,28 @@ class Xcode { return _isSimctlInstalled ?? false; } + bool? _isDevicectlInstalled; + + /// Verifies that `devicectl` is installed by checking Xcode version and trying + /// to run it. `devicectl` is made available in Xcode 15. + bool get isDevicectlInstalled { + if (_isDevicectlInstalled == null) { + try { + if (currentVersion == null || currentVersion!.major < 15) { + _isDevicectlInstalled = false; + return _isDevicectlInstalled!; + } + final RunResult result = _processUtils.runSync( + <String>[...xcrunCommand(), 'devicectl', '--version'], + ); + _isDevicectlInstalled = result.exitCode == 0; + } on ProcessException { + _isDevicectlInstalled = false; + } + } + return _isDevicectlInstalled ?? false; + } + bool get isRequiredVersionSatisfactory { final Version? version = currentVersion; if (version == null) { @@ -198,6 +267,19 @@ class Xcode { final String appPath = _fileSystem.path.join(selectPath, 'Applications', 'Simulator.app'); return _fileSystem.directory(appPath).existsSync() ? appPath : null; } + + /// Gets the version number of the platform for the selected SDK. + Future<Version?> sdkPlatformVersion(EnvironmentType environmentType) async { + final RunResult runResult = await _processUtils.run( + <String>[...xcrunCommand(), '--sdk', getSDKNameForIOSEnvironmentType(environmentType), '--show-sdk-platform-version'], + ); + if (runResult.exitCode != 0) { + _logger.printError('Could not find SDK Platform Version: ${runResult.stderr}'); + return null; + } + final String versionString = runResult.stdout.trim(); + return Version.parse(versionString); + } } EnvironmentType? environmentTypeFromSdkroot(String sdkroot, FileSystem fileSystem) { diff --git a/packages/flutter_tools/lib/src/macos/xcode_validator.dart b/packages/flutter_tools/lib/src/macos/xcode_validator.dart index c45351c6728b3..1604876fb9c0c 100644 --- a/packages/flutter_tools/lib/src/macos/xcode_validator.dart +++ b/packages/flutter_tools/lib/src/macos/xcode_validator.dart @@ -3,18 +3,32 @@ // found in the LICENSE file. import '../base/user_messages.dart'; +import '../base/version.dart'; +import '../build_info.dart'; import '../doctor_validator.dart'; +import '../ios/simulators.dart'; import 'xcode.dart'; +String _iOSSimulatorMissing(String version) => ''' +iOS $version Simulator not installed; this may be necessary for iOS and macOS development. +To download and install the platform, open Xcode, select Xcode > Settings > Platforms, +and click the GET button for the required platform. + +For more information, please visit: + https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes'''; + class XcodeValidator extends DoctorValidator { XcodeValidator({ required Xcode xcode, + required IOSSimulatorUtils iosSimulatorUtils, required UserMessages userMessages, - }) : _xcode = xcode, - _userMessages = userMessages, - super('Xcode - develop for iOS and macOS'); + }) : _xcode = xcode, + _iosSimulatorUtils = iosSimulatorUtils, + _userMessages = userMessages, + super('Xcode - develop for iOS and macOS'); final Xcode _xcode; + final IOSSimulatorUtils _iosSimulatorUtils; final UserMessages _userMessages; @override @@ -57,6 +71,11 @@ class XcodeValidator extends DoctorValidator { messages.add(ValidationMessage.error(_userMessages.xcodeMissingSimct)); } + final ValidationMessage? missingSimulatorMessage = await _validateSimulatorRuntimeInstalled(); + if (missingSimulatorMessage != null) { + xcodeStatus = ValidationType.partial; + messages.add(missingSimulatorMessage); + } } else { xcodeStatus = ValidationType.missing; if (xcodeSelectPath == null || xcodeSelectPath.isEmpty) { @@ -68,4 +87,45 @@ class XcodeValidator extends DoctorValidator { return ValidationResult(xcodeStatus, messages, statusInfo: xcodeVersionInfo); } + + /// Validate the Xcode-installed iOS simulator SDK has a corresponding iOS + /// simulator runtime installed. + /// + /// Starting with Xcode 15, the iOS simulator runtime is no longer downloaded + /// with Xcode and must be downloaded and installed separately. + /// iOS applications cannot be run without it. + Future<ValidationMessage?> _validateSimulatorRuntimeInstalled() async { + // Skip this validation if Xcode is not installed, Xcode is a version less + // than 15, simctl is not installed, or if the EULA is not signed. + if (!_xcode.isInstalled || + _xcode.currentVersion == null || + _xcode.currentVersion!.major < 15 || + !_xcode.isSimctlInstalled || + !_xcode.eulaSigned) { + return null; + } + + final Version? platformSDKVersion = await _xcode.sdkPlatformVersion(EnvironmentType.simulator); + if (platformSDKVersion == null) { + return const ValidationMessage.error('Unable to find the iPhone Simulator SDK.'); + } + + final List<IOSSimulatorRuntime> runtimes = await _iosSimulatorUtils.getAvailableIOSRuntimes(); + if (runtimes.isEmpty) { + return const ValidationMessage.error('Unable to get list of installed Simulator runtimes.'); + } + + // Verify there is a simulator runtime installed matching the + // iphonesimulator SDK major version. + try { + runtimes.firstWhere( + (IOSSimulatorRuntime runtime) => + runtime.version?.major == platformSDKVersion.major, + ); + } on StateError { + return ValidationMessage.hint(_iOSSimulatorMissing(platformSDKVersion.toString())); + } + + return null; + } } diff --git a/packages/flutter_tools/lib/src/mdns_discovery.dart b/packages/flutter_tools/lib/src/mdns_discovery.dart index 82196118afc00..da6527596caa3 100644 --- a/packages/flutter_tools/lib/src/mdns_discovery.dart +++ b/packages/flutter_tools/lib/src/mdns_discovery.dart @@ -130,9 +130,9 @@ class MDnsVmServiceDiscovery { /// The [deviceVmservicePort] parameter must be set to specify which port /// to find. /// - /// [applicationId] and [deviceVmservicePort] are required for launch so that - /// if multiple flutter apps are running on different devices, it will - /// only match with the device running the desired app. + /// [applicationId] and either [deviceVmservicePort] or [deviceName] are + /// required for launch so that if multiple flutter apps are running on + /// different devices, it will only match with the device running the desired app. /// /// The [useDeviceIPAsHost] parameter flags whether to get the device IP /// and the [ipv6] parameter flags whether to get an iPv6 address @@ -141,21 +141,27 @@ class MDnsVmServiceDiscovery { /// The [timeout] parameter determines how long to continue to wait for /// services to become active. /// - /// If a Dart VM Service matching the [applicationId] and [deviceVmservicePort] - /// cannot be found after the [timeout], it will call [throwToolExit]. + /// If a Dart VM Service matching the [applicationId] and + /// [deviceVmservicePort]/[deviceName] cannot be found before the [timeout] + /// is reached, it will call [throwToolExit]. @visibleForTesting Future<MDnsVmServiceDiscoveryResult?> queryForLaunch({ required String applicationId, - required int deviceVmservicePort, + int? deviceVmservicePort, + String? deviceName, bool ipv6 = false, bool useDeviceIPAsHost = false, Duration timeout = const Duration(minutes: 10), }) async { - // Query for a specific application and device port. + // Either the device port or the device name must be provided. + assert(deviceVmservicePort != null || deviceName != null); + + // Query for a specific application matching on either device port or device name. return firstMatchingVmService( _client, applicationId: applicationId, deviceVmservicePort: deviceVmservicePort, + deviceName: deviceName, ipv6: ipv6, useDeviceIPAsHost: useDeviceIPAsHost, timeout: timeout, @@ -170,6 +176,7 @@ class MDnsVmServiceDiscovery { MDnsClient client, { String? applicationId, int? deviceVmservicePort, + String? deviceName, bool ipv6 = false, bool useDeviceIPAsHost = false, Duration timeout = const Duration(minutes: 10), @@ -178,6 +185,7 @@ class MDnsVmServiceDiscovery { client, applicationId: applicationId, deviceVmservicePort: deviceVmservicePort, + deviceName: deviceName, ipv6: ipv6, useDeviceIPAsHost: useDeviceIPAsHost, timeout: timeout, @@ -193,6 +201,7 @@ class MDnsVmServiceDiscovery { MDnsClient client, { String? applicationId, int? deviceVmservicePort, + String? deviceName, bool ipv6 = false, bool useDeviceIPAsHost = false, required Duration timeout, @@ -263,6 +272,11 @@ class MDnsVmServiceDiscovery { continue; } + // If deviceName is set, only use records that match it + if (deviceName != null && !deviceNameMatchesTargetName(deviceName, srvRecord.target)) { + continue; + } + // Get the IP address of the device if using the IP as the host. InternetAddress? ipAddress; if (useDeviceIPAsHost) { @@ -332,6 +346,15 @@ class MDnsVmServiceDiscovery { } } + @visibleForTesting + bool deviceNameMatchesTargetName(String deviceName, String targetName) { + // Remove `.local` from the name along with any non-word, non-digit characters. + final RegExp cleanedNameRegex = RegExp(r'\.local|\W'); + final String cleanedDeviceName = deviceName.trim().toLowerCase().replaceAll(cleanedNameRegex, ''); + final String cleanedTargetName = targetName.toLowerCase().replaceAll(cleanedNameRegex, ''); + return cleanedDeviceName == cleanedTargetName; + } + String _getAuthCode(String txtRecord) { const String authCodePrefix = 'authCode='; final Iterable<String> matchingRecords = @@ -354,7 +377,7 @@ class MDnsVmServiceDiscovery { /// When [useDeviceIPAsHost] is true, it will use the device's IP as the /// host and will not forward the port. /// - /// Differs from `getVMServiceUriForLaunch` because it can search for any available Dart VM Service. + /// Differs from [getVMServiceUriForLaunch] because it can search for any available Dart VM Service. /// Since [applicationId] and [deviceVmservicePort] are optional, it can either look for any service /// or a specific service matching [applicationId]/[deviceVmservicePort]. /// It may find more than one service, which will throw an error listing the found services. @@ -391,20 +414,22 @@ class MDnsVmServiceDiscovery { /// When [useDeviceIPAsHost] is true, it will use the device's IP as the /// host and will not forward the port. /// - /// Differs from `getVMServiceUriForAttach` because it only searches for a specific service. - /// This is enforced by [applicationId] and [deviceVmservicePort] being required. + /// Differs from [getVMServiceUriForAttach] because it only searches for a specific service. + /// This is enforced by [applicationId] being required and using either the + /// [deviceVmservicePort] or the [device]'s name to query. Future<Uri?> getVMServiceUriForLaunch( String applicationId, Device device, { bool usesIpv6 = false, int? hostVmservicePort, - required int deviceVmservicePort, + int? deviceVmservicePort, bool useDeviceIPAsHost = false, Duration timeout = const Duration(minutes: 10), }) async { final MDnsVmServiceDiscoveryResult? result = await queryForLaunch( applicationId: applicationId, deviceVmservicePort: deviceVmservicePort, + deviceName: deviceVmservicePort == null ? device.name : null, ipv6: usesIpv6, useDeviceIPAsHost: useDeviceIPAsHost, timeout: timeout, diff --git a/packages/flutter_tools/lib/src/migrations/cocoapods_toolchain_directory_migration.dart b/packages/flutter_tools/lib/src/migrations/cocoapods_toolchain_directory_migration.dart new file mode 100644 index 0000000000000..4ab406bda6819 --- /dev/null +++ b/packages/flutter_tools/lib/src/migrations/cocoapods_toolchain_directory_migration.dart @@ -0,0 +1,62 @@ +// 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 '../base/file_system.dart'; +import '../base/project_migrator.dart'; +import '../base/version.dart'; +import '../ios/xcodeproj.dart'; +import '../xcode_project.dart'; + +/// Starting in Xcode 15, when building macOS, DT_TOOLCHAIN_DIR cannot be used +/// to evaluate LD_RUNPATH_SEARCH_PATHS or LIBRARY_SEARCH_PATHS. `xcodebuild` +/// error message recommend using TOOLCHAIN_DIR instead. +/// +/// This has been fixed upstream in CocoaPods, but migrate a copy of their +/// workaround so users don't need to update. +class CocoaPodsToolchainDirectoryMigration extends ProjectMigrator { + CocoaPodsToolchainDirectoryMigration( + XcodeBasedProject project, + XcodeProjectInterpreter xcodeProjectInterpreter, + super.logger, + ) : _podRunnerTargetSupportFiles = project.podRunnerTargetSupportFiles, + _xcodeProjectInterpreter = xcodeProjectInterpreter; + + final Directory _podRunnerTargetSupportFiles; + final XcodeProjectInterpreter _xcodeProjectInterpreter; + + @override + void migrate() { + if (!_podRunnerTargetSupportFiles.existsSync()) { + logger.printTrace('CocoaPods Pods-Runner Target Support Files not found, skipping TOOLCHAIN_DIR workaround.'); + return; + } + + final Version? version = _xcodeProjectInterpreter.version; + + // If Xcode not installed or less than 15, skip this migration. + if (version == null || version < Version(15, 0, 0)) { + logger.printTrace('Detected Xcode version is $version, below 15.0, skipping TOOLCHAIN_DIR workaround.'); + return; + } + + final List<FileSystemEntity> files = _podRunnerTargetSupportFiles.listSync(); + for (final FileSystemEntity file in files) { + if (file.basename.endsWith('xcconfig') && file is File) { + processFileLines(file); + } + } + } + + @override + String? migrateLine(String line) { + final String trimmedString = line.trim(); + if (trimmedString.startsWith('LD_RUNPATH_SEARCH_PATHS') || trimmedString.startsWith('LIBRARY_SEARCH_PATHS')) { + const String originalReadLinkLine = r'{DT_TOOLCHAIN_DIR}'; + const String replacementReadLinkLine = r'{TOOLCHAIN_DIR}'; + + return line.replaceAll(originalReadLinkLine, replacementReadLinkLine); + } + return line; + } +} diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index d726192f5384b..38a63ff55df1b 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -7,6 +7,7 @@ import 'package:xml/xml.dart'; import 'package:yaml/yaml.dart'; import '../src/convert.dart'; +import 'android/android_app_link_settings.dart'; import 'android/android_builder.dart'; import 'android/gradle_utils.dart' as gradle; import 'base/common.dart'; @@ -14,6 +15,7 @@ import 'base/error_handling_io.dart'; import 'base/file_system.dart'; import 'base/logger.dart'; import 'base/utils.dart'; +import 'base/version.dart'; import 'bundle.dart' as bundle; import 'cmake_project.dart'; import 'features.dart'; @@ -476,6 +478,7 @@ class AndroidProject extends FlutterProjectPlatform { /// Returns true if the current version of the Gradle plugin is supported. late final bool isSupportedVersion = _computeSupportedVersion(); + /// Gets all build variants of this project. Future<List<String>> getBuildVariants() async { if (!existsSync() || androidBuilder == null) { return const <String>[]; @@ -483,6 +486,22 @@ class AndroidProject extends FlutterProjectPlatform { return androidBuilder!.getBuildVariants(project: parent); } + /// Returns app link related project settings for a given build variant. + /// + /// Use [getBuildVariants] to get all of the available build variants. + Future<AndroidAppLinkSettings> getAppLinksSettings({required String variant}) async { + if (!existsSync() || androidBuilder == null) { + return const AndroidAppLinkSettings( + applicationId: '', + domains: <String>[], + ); + } + return AndroidAppLinkSettings( + applicationId: await androidBuilder!.getApplicationIdForVariant(variant, project: parent), + domains: await androidBuilder!.getAppLinkDomainsForVariant(variant, project: parent), + ); + } + bool _computeSupportedVersion() { final FileSystem fileSystem = hostAppGradleRoot.fileSystem; final File plugin = hostAppGradleRoot.childFile( @@ -568,7 +587,7 @@ class AndroidProject extends FlutterProjectPlatform { hostAppGradleRoot, globals.logger, globals.processManager); final String? agpVersion = gradle.getAgpVersion(hostAppGradleRoot, globals.logger); - final String? javaVersion = globals.java?.version?.number; + final String? javaVersion = _versionToParsableString(globals.java?.version); // Assume valid configuration. String description = validJavaGradleAgpString; @@ -893,3 +912,12 @@ class CompatibilityResult { final bool success; final String description; } + +/// Converts a [Version] to a string that can be parsed by [Version.parse]. +String? _versionToParsableString(Version? version) { + if (version == null) { + return null; + } + + return '${version.major}.${version.minor}.${version.patch}'; +} diff --git a/packages/flutter_tools/lib/src/project_validator.dart b/packages/flutter_tools/lib/src/project_validator.dart index 791642a19bf8c..57c3f8dd4fe3a 100644 --- a/packages/flutter_tools/lib/src/project_validator.dart +++ b/packages/flutter_tools/lib/src/project_validator.dart @@ -136,7 +136,10 @@ class VariableDumpMachineProjectValidator extends MachineProjectValidator { )); // FlutterVersion - final FlutterVersion version = FlutterVersion(workingDirectory: project.directory.absolute.path); + final FlutterVersion version = FlutterVersion( + flutterRoot: Cache.flutterRoot!, + fs: fileSystem, + ); result.add(ProjectValidatorResult( name: 'FlutterVersion.frameworkRevision', value: _toJsonValue(version.frameworkRevision), diff --git a/packages/flutter_tools/lib/src/protocol_discovery.dart b/packages/flutter_tools/lib/src/protocol_discovery.dart index d21e602d2d6c0..578052bfb7888 100644 --- a/packages/flutter_tools/lib/src/protocol_discovery.dart +++ b/packages/flutter_tools/lib/src/protocol_discovery.dart @@ -167,7 +167,6 @@ class _BufferedStreamController<T> { final StreamController<T> streamControllerInstance = StreamController<T>.broadcast(); streamControllerInstance.onListen = () { for (final dynamic event in _events) { - assert(T is! List); if (event is T) { streamControllerInstance.add(event); } else { diff --git a/packages/flutter_tools/lib/src/resident_devtools_handler.dart b/packages/flutter_tools/lib/src/resident_devtools_handler.dart index 2dc0aaef1aea5..3b7521480fa83 100644 --- a/packages/flutter_tools/lib/src/resident_devtools_handler.dart +++ b/packages/flutter_tools/lib/src/resident_devtools_handler.dart @@ -35,6 +35,7 @@ abstract class ResidentDevtoolsHandler { Future<void> serveAndAnnounceDevTools({ Uri? devToolsServerAddress, required List<FlutterDevice?> flutterDevices, + bool isStartPaused = false, }); bool launchDevToolsInBrowser({required List<FlutterDevice?> flutterDevices}); @@ -71,6 +72,7 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { Future<void> serveAndAnnounceDevTools({ Uri? devToolsServerAddress, required List<FlutterDevice?> flutterDevices, + bool isStartPaused = false, }) async { assert(!_readyToAnnounce); if (!_residentRunner.supportsServiceProtocol || _devToolsLauncher == null) { @@ -88,20 +90,10 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { assert(!_readyToAnnounce); return; } - final List<FlutterDevice?> devicesWithExtension = await _devicesWithExtensions(flutterDevices); - await _maybeCallDevToolsUriServiceExtension(devicesWithExtension); - await _callConnectedVmServiceUriExtension(devicesWithExtension); - - if (_shutdown) { - // If we're shutting down, no point reporting the debugger list. - return; - } - _readyToAnnounce = true; - assert(_devToolsLauncher!.activeDevToolsServer != null); final Uri? devToolsUrl = _devToolsLauncher!.devToolsUrl; if (devToolsUrl != null) { - for (final FlutterDevice? device in devicesWithExtension) { + for (final FlutterDevice? device in flutterDevices) { if (device == null) { continue; } @@ -111,11 +103,43 @@ class FlutterResidentDevtoolsHandler implements ResidentDevtoolsHandler { } } + Future<void> callServiceExtensions() async { + final List<FlutterDevice?> devicesWithExtension = await _devicesWithExtensions(flutterDevices); + await Future.wait( + <Future<void>>[ + _maybeCallDevToolsUriServiceExtension(devicesWithExtension), + _callConnectedVmServiceUriExtension(devicesWithExtension) + ] + ); + } + + // If the application is starting paused, we can't invoke service extensions + // as they're handled on the target app's paused isolate. Since invoking + // service extensions will block in this situation, we should wait to invoke + // them until after we've output the DevTools connection details. + if (!isStartPaused) { + await callServiceExtensions(); + } + + // This check needs to happen after the possible asynchronous call above, + // otherwise a shutdown event might be missed and the DevTools launcher may + // no longer be initialized. + if (_shutdown) { + // If we're shutting down, no point reporting the debugger list. + return; + } + + _readyToAnnounce = true; + assert(_devToolsLauncher!.activeDevToolsServer != null); if (_residentRunner.reportedDebuggers) { // Since the DevTools only just became available, we haven't had a chance to // report their URLs yet. Do so now. _residentRunner.printDebuggerList(includeVmService: false); } + + if (isStartPaused) { + await callServiceExtensions(); + } } // This must be guaranteed not to return a Future that fails. @@ -295,7 +319,11 @@ class NoOpDevtoolsHandler implements ResidentDevtoolsHandler { } @override - Future<void> serveAndAnnounceDevTools({Uri? devToolsServerAddress, List<FlutterDevice?>? flutterDevices}) async { + Future<void> serveAndAnnounceDevTools({ + Uri? devToolsServerAddress, + List<FlutterDevice?>? flutterDevices, + bool isStartPaused = false, + }) async { return; } diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 92b8b3c302039..1d55e0d5673dc 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -36,6 +36,7 @@ import 'devfs.dart'; import 'device.dart'; import 'features.dart'; import 'globals.dart' as globals; +import 'ios/application_package.dart'; import 'ios/devices.dart'; import 'project.dart'; import 'resident_devtools_handler.dart'; @@ -229,9 +230,8 @@ class FlutterDevice { FlutterVmService? vmService; DevFS? devFS; ApplicationPackage? package; - @visibleForTesting // ignore: cancel_subscriptions - StreamSubscription<String>? loggingSubscription; + StreamSubscription<String>? _loggingSubscription; bool? _isListeningForVmServiceUri; /// Whether the stream [vmServiceUris] is still open. @@ -393,27 +393,32 @@ class FlutterDevice { return devFS!.create(); } - Future<void> startEchoingDeviceLog() async { - if (loggingSubscription != null) { + Future<void> startEchoingDeviceLog(DebuggingOptions debuggingOptions) async { + if (_loggingSubscription != null) { return; } - final DeviceLogReader logReader = await device!.getLogReader(app: package); - final Stream<String> logStream = logReader.logLines; - // TODO(vashworth): Remove check for IOSDeviceLogReader after - // https://github.com/flutter/flutter/issues/121231 is resolved. - loggingSubscription = logStream.listen((String line) { - if (logReader is! IOSDeviceLogReader && !line.contains(globals.kVMServiceMessageRegExp)) { + final Stream<String> logStream; + if (device is IOSDevice) { + logStream = (device! as IOSDevice).getLogReader( + app: package as IOSApp?, + usingCISystem: debuggingOptions.usingCISystem, + ).logLines; + } else { + logStream = (await device!.getLogReader(app: package)).logLines; + } + _loggingSubscription = logStream.listen((String line) { + if (!line.contains(globals.kVMServiceMessageRegExp)) { globals.printStatus(line, wrap: false); } }); } Future<void> stopEchoingDeviceLog() async { - if (loggingSubscription == null) { + if (_loggingSubscription == null) { return; } - await loggingSubscription!.cancel(); - loggingSubscription = null; + await _loggingSubscription!.cancel(); + _loggingSubscription = null; } Future<void> initLogReader() async { @@ -456,7 +461,7 @@ class FlutterDevice { 'multidex': hotRunner.multidexEnabled, }; - await startEchoingDeviceLog(); + await startEchoingDeviceLog(hotRunner.debuggingOptions); // Start the application. final Future<LaunchResult> futureResult = device!.startApp( @@ -524,7 +529,7 @@ class FlutterDevice { platformArgs['trace-startup'] = coldRunner.traceStartup; platformArgs['multidex'] = coldRunner.multidexEnabled; - await startEchoingDeviceLog(); + await startEchoingDeviceLog(coldRunner.debuggingOptions); final LaunchResult result = await device!.startApp( applicationPackage, diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart index 88c3fd68e9cb3..cbd83bed033c1 100644 --- a/packages/flutter_tools/lib/src/run_cold.dart +++ b/packages/flutter_tools/lib/src/run_cold.dart @@ -86,6 +86,7 @@ class ColdRunner extends ResidentRunner { unawaited(residentDevtoolsHandler!.serveAndAnnounceDevTools( devToolsServerAddress: debuggingOptions.devToolsServerAddress, flutterDevices: flutterDevices, + isStartPaused: debuggingOptions.startPaused, )); } if (debuggingOptions.serveObservatory) { @@ -173,6 +174,7 @@ class ColdRunner extends ResidentRunner { unawaited(residentDevtoolsHandler!.serveAndAnnounceDevTools( devToolsServerAddress: debuggingOptions.devToolsServerAddress, flutterDevices: flutterDevices, + isStartPaused: debuggingOptions.startPaused, )); } if (debuggingOptions.serveObservatory) { diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index 9dcf7e9ded6f6..7d8c77ac02b26 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -2,8 +2,6 @@ // 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:meta/meta.dart'; @@ -192,16 +190,21 @@ class HotRunner extends ResidentRunner { String isolateId, String expression, List<String> definitions, + List<String> definitionTypes, List<String> typeDefinitions, + List<String> typeBounds, + List<String> typeDefaults, String libraryUri, String? klass, + String? method, bool isStatic, ) async { for (final FlutterDevice? device in flutterDevices) { if (device!.generator != null) { final CompilerOutput? compilerOutput = await device.generator!.compileExpression(expression, definitions, - typeDefinitions, libraryUri, klass, isStatic); + definitionTypes, typeDefinitions, typeBounds, typeDefaults, + libraryUri, klass, method, isStatic); if (compilerOutput != null && compilerOutput.expressionData != null) { return base64.encode(compilerOutput.expressionData!); } @@ -246,6 +249,7 @@ class HotRunner extends ResidentRunner { unawaited(residentDevtoolsHandler!.serveAndAnnounceDevTools( devToolsServerAddress: debuggingOptions.devToolsServerAddress, flutterDevices: flutterDevices, + isStartPaused: debuggingOptions.startPaused, )); } @@ -1295,9 +1299,8 @@ Future<ReassembleResult> _defaultReassembleHelper( for (final FlutterView view in views) { // Check if the isolate is paused, and if so, don't reassemble. Ignore the // PostPauseEvent event - the client requesting the pause will resume the app. - final vm_service.Isolate? isolate = await device!.vmService! - .getIsolateOrNull(view.uiIsolate!.id!); - final vm_service.Event? pauseEvent = isolate?.pauseEvent; + final vm_service.Event? pauseEvent = await device!.vmService! + .getIsolatePauseEventOrNull(view.uiIsolate!.id!); if (pauseEvent != null && isPauseEvent(pauseEvent.kind!) && pauseEvent.kind != vm_service.EventKind.kPausePostRequest) { @@ -1361,16 +1364,13 @@ Future<ReassembleResult> _defaultReassembleHelper( int postReloadPausedIsolatesFound = 0; String? serviceEventKind; for (final FlutterView view in reassembleViews.keys) { - final vm_service.Isolate? isolate = await reassembleViews[view]! - .getIsolateOrNull(view.uiIsolate!.id!); - if (isolate == null) { - continue; - } - if (isolate.pauseEvent != null && isPauseEvent(isolate.pauseEvent!.kind!)) { + final vm_service.Event? pauseEvent = await reassembleViews[view]! + .getIsolatePauseEventOrNull(view.uiIsolate!.id!); + if (pauseEvent != null && isPauseEvent(pauseEvent.kind!)) { postReloadPausedIsolatesFound += 1; if (serviceEventKind == null) { - serviceEventKind = isolate.pauseEvent!.kind; - } else if (serviceEventKind != isolate.pauseEvent!.kind) { + serviceEventKind = pauseEvent.kind; + } else if (serviceEventKind != pauseEvent.kind) { serviceEventKind = ''; // many kinds } } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 94586ff0ff876..b7e624b4e2589 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -304,7 +304,10 @@ abstract class FlutterCommand extends Command<void> { /// Path to the Dart's package config file. /// /// This can be overridden by some of its subclasses. - String? get packagesPath => globalResults?['packages'] as String?; + String? get packagesPath => stringArg(FlutterGlobalOptions.kPackagesOption, global: true); + + /// Whether flutter is being run from our CI. + bool get usingCISystem => boolArg(FlutterGlobalOptions.kContinuousIntegrationFlag, global: true); /// The value of the `--filesystem-scheme` argument. /// @@ -609,17 +612,17 @@ abstract class FlutterCommand extends Command<void> { valueHelp: 'foo=bar', splitCommas: false, ); - useDartDefineConfigJsonFileOption(); + useDartDefineFromFileOption(); } - void useDartDefineConfigJsonFileOption() { + void useDartDefineFromFileOption() { argParser.addMultiOption( FlutterOptions.kDartDefineFromFileOption, - help: 'The path of a json format file where flutter define a global constant pool. ' - 'Json entry will be available as constants from the String.fromEnvironment, bool.fromEnvironment, ' - 'and int.fromEnvironment constructors; the key and field are json values.\n' + help: + 'The path of a .json or .env file containing key-value pairs that will be available as environment variables.\n' + 'These can be accessed using the String.fromEnvironment, bool.fromEnvironment, and int.fromEnvironment constructors.\n' 'Multiple defines can be passed by repeating "--${FlutterOptions.kDartDefineFromFileOption}" multiple times.', - valueHelp: 'use-define-config.json', + valueHelp: 'use-define-config.json|.env', splitCommas: false, ); } @@ -1187,7 +1190,7 @@ abstract class FlutterCommand extends Command<void> { ? stringArg(FlutterOptions.kPerformanceMeasurementFile) : null; - final Map<String, Object>? defineConfigJsonMap = extractDartDefineConfigJsonMap(); + final Map<String, Object?> defineConfigJsonMap = extractDartDefineConfigJsonMap(); List<String> dartDefines = extractDartDefines(defineConfigJsonMap: defineConfigJsonMap); WebRendererMode webRenderer = WebRendererMode.auto; @@ -1320,44 +1323,52 @@ abstract class FlutterCommand extends Command<void> { } } - List<String> extractDartDefines({Map<String, Object>? defineConfigJsonMap}) { + List<String> extractDartDefines({required Map<String, Object?> defineConfigJsonMap}) { final List<String> dartDefines = <String>[]; if (argParser.options.containsKey(FlutterOptions.kDartDefinesOption)) { dartDefines.addAll(stringsArg(FlutterOptions.kDartDefinesOption)); } - if (defineConfigJsonMap == null) { - return dartDefines; - } - defineConfigJsonMap.forEach((String key, Object value) { + defineConfigJsonMap.forEach((String key, Object? value) { dartDefines.add('$key=$value'); }); return dartDefines; } - Map<String, Object>? extractDartDefineConfigJsonMap() { - final Map<String, Object> dartDefineConfigJsonMap = <String, Object>{}; + Map<String, Object?> extractDartDefineConfigJsonMap() { + final Map<String, Object?> dartDefineConfigJsonMap = <String, Object?>{}; if (argParser.options.containsKey(FlutterOptions.kDartDefineFromFileOption)) { - final List<String> configJsonPaths = stringsArg( - FlutterOptions.kDartDefineFromFileOption, + final List<String> configFilePaths = stringsArg( + FlutterOptions.kDartDefineFromFileOption, ); - for (final String path in configJsonPaths) { + for (final String path in configFilePaths) { if (!globals.fs.isFileSync(path)) { throwToolExit('Json config define file "--${FlutterOptions .kDartDefineFromFileOption}=$path" is not a file, ' 'please fix first!'); } - final String configJsonRaw = globals.fs.file(path).readAsStringSync(); + final String configRaw = globals.fs.file(path).readAsStringSync(); + + // Determine whether the file content is JSON or .env format. + String configJsonRaw; + if (configRaw.trim().startsWith('{')) { + configJsonRaw = configRaw; + } else { + + // Convert env file to JSON. + configJsonRaw = convertEnvFileToJsonRaw(configRaw); + } + try { // Fix json convert Object value :type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Map<String, Object>' in type cast (json.decode(configJsonRaw) as Map<String, dynamic>) - .forEach((String key, dynamic value) { - dartDefineConfigJsonMap[key] = value as Object; + .forEach((String key, Object? value) { + dartDefineConfigJsonMap[key] = value; }); } on FormatException catch (err) { throwToolExit('Json config define file "--${FlutterOptions @@ -1370,6 +1381,88 @@ abstract class FlutterCommand extends Command<void> { return dartDefineConfigJsonMap; } + /// Parse a property line from an env file. + /// Supposed property structure should be: + /// key=value + /// + /// Where: key is a string without spaces and value is a string. + /// Value can also contain '=' char. + /// + /// Returns a record of key and value as strings. + MapEntry<String, String> _parseProperty(String line) { + final RegExp blockRegExp = RegExp(r'^\s*([a-zA-Z_]+[a-zA-Z0-9_]*)\s*=\s*"""\s*(.*)$'); + if (blockRegExp.hasMatch(line)) { + throwToolExit('Multi-line value is not supported: $line'); + } + + final RegExp propertyRegExp = RegExp(r'^\s*([a-zA-Z_]+[a-zA-Z0-9_]*)\s*=\s*(.*)?$'); + final Match? match = propertyRegExp.firstMatch(line); + if (match == null) { + throwToolExit('Unable to parse file provided for ' + '--${FlutterOptions.kDartDefineFromFileOption}.\n' + 'Invalid property line: $line'); + } + + final String key = match.group(1)!; + final String value = match.group(2) ?? ''; + + // Remove wrapping quotes and trailing line comment. + final RegExp doubleQuoteValueRegExp = RegExp(r'^"(.*)"\s*(\#\s*.*)?$'); + final Match? doubleQuoteValue = doubleQuoteValueRegExp.firstMatch(value); + if (doubleQuoteValue != null) { + return MapEntry<String, String>(key, doubleQuoteValue.group(1)!); + } + + final RegExp quoteValueRegExp = RegExp(r"^'(.*)'\s*(\#\s*.*)?$"); + final Match? quoteValue = quoteValueRegExp.firstMatch(value); + if (quoteValue != null) { + return MapEntry<String, String>(key, quoteValue.group(1)!); + } + + final RegExp backQuoteValueRegExp = RegExp(r'^`(.*)`\s*(\#\s*.*)?$'); + final Match? backQuoteValue = backQuoteValueRegExp.firstMatch(value); + if (backQuoteValue != null) { + return MapEntry<String, String>(key, backQuoteValue.group(1)!); + } + + final RegExp noQuoteValueRegExp = RegExp(r'^([^#\n\s]*)\s*(?:\s*#\s*(.*))?$'); + final Match? noQuoteValue = noQuoteValueRegExp.firstMatch(value); + if (noQuoteValue != null) { + return MapEntry<String, String>(key, noQuoteValue.group(1)!); + } + + return MapEntry<String, String>(key, value); + } + + /// Converts an .env file string to its equivalent JSON string. + /// + /// For example, the .env file string + /// key=value # comment + /// complexKey="foo#bar=baz" + /// would be converted to a JSON string equivalent to: + /// { + /// "key": "value", + /// "complexKey": "foo#bar=baz" + /// } + /// + /// Multiline values are not supported. + String convertEnvFileToJsonRaw(String configRaw) { + final List<String> lines = configRaw + .split('\n') + .map((String line) => line.trim()) + .where((String line) => line.isNotEmpty) + .where((String line) => !line.startsWith('#')) // Remove comment lines. + .toList(); + + final Map<String, String> propertyMap = <String, String>{}; + for (final String line in lines) { + final MapEntry<String, String> property = _parseProperty(line); + propertyMap[property.key] = property.value; + } + + return jsonEncode(propertyMap); + } + /// Updates dart-defines based on [webRenderer]. @visibleForTesting static List<String> updateDartDefines(List<String> dartDefines, WebRendererMode webRenderer) { @@ -1634,17 +1727,30 @@ Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and /// /// If no flag named [name] was added to the [ArgParser], an [ArgumentError] /// will be thrown. - bool boolArg(String name) => argResults![name] as bool; + bool boolArg(String name, {bool global = false}) { + if (global) { + return globalResults![name] as bool; + } + return argResults![name] as bool; + } /// Gets the parsed command-line option named [name] as a `String`. /// /// If no option named [name] was added to the [ArgParser], an [ArgumentError] /// will be thrown. - String? stringArg(String name) => argResults![name] as String?; + String? stringArg(String name, {bool global = false}) { + if (global) { + return globalResults![name] as String?; + } + return argResults![name] as String?; + } /// Gets the parsed command-line option named [name] as `List<String>`. - List<String> stringsArg(String name) { - return argResults![name]! as List<String>? ?? <String>[]; + List<String> stringsArg(String name, {bool global = false}) { + if (global) { + return globalResults![name] as List<String>; + } + return argResults![name] as List<String>; } } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart index ea84a7db40a60..5d6d78639fb66 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart @@ -18,8 +18,33 @@ import '../cache.dart'; import '../convert.dart'; import '../globals.dart' as globals; import '../tester/flutter_tester.dart'; +import '../version.dart'; import '../web/web_device.dart'; +/// Common flutter command line options. +abstract final class FlutterGlobalOptions { + static const String kColorFlag = 'color'; + static const String kContinuousIntegrationFlag = 'ci'; + static const String kDeviceIdOption = 'device-id'; + static const String kDisableTelemetryFlag = 'disable-telemetry'; + static const String kEnableTelemetryFlag = 'enable-telemetry'; + static const String kLocalEngineOption = 'local-engine'; + static const String kLocalEngineSrcPathOption = 'local-engine-src-path'; + static const String kLocalWebSDKOption = 'local-web-sdk'; + static const String kMachineFlag = 'machine'; + static const String kPackagesOption = 'packages'; + static const String kPrefixedErrorsFlag = 'prefixed-errors'; + static const String kQuietFlag = 'quiet'; + static const String kShowTestDeviceFlag = 'show-test-device'; + static const String kShowWebServerDeviceFlag = 'show-web-server-device'; + static const String kSuppressAnalyticsFlag = 'suppress-analytics'; + static const String kVerboseFlag = 'verbose'; + static const String kVersionCheckFlag = 'version-check'; + static const String kVersionFlag = 'version'; + static const String kWrapColumnOption = 'wrap-column'; + static const String kWrapFlag = 'wrap'; +} + class FlutterCommandRunner extends CommandRunner<void> { FlutterCommandRunner({ bool verboseHelp = false }) : super( 'flutter', @@ -33,80 +58,80 @@ class FlutterCommandRunner extends CommandRunner<void> { ' flutter run [options]\n' ' Run your Flutter application on an attached device or in an emulator.', ) { - argParser.addFlag('verbose', + argParser.addFlag(FlutterGlobalOptions.kVerboseFlag, abbr: 'v', negatable: false, help: 'Noisy logging, including all shell commands executed.\n' 'If used with "--help", shows hidden options. ' 'If used with "flutter doctor", shows additional diagnostic information. ' '(Use "-vv" to force verbose logging in those cases.)'); - argParser.addFlag('prefixed-errors', + argParser.addFlag(FlutterGlobalOptions.kPrefixedErrorsFlag, negatable: false, help: 'Causes lines sent to stderr to be prefixed with "ERROR:".', hide: !verboseHelp); - argParser.addFlag('quiet', + argParser.addFlag(FlutterGlobalOptions.kQuietFlag, negatable: false, hide: !verboseHelp, help: 'Reduce the amount of output from some commands.'); - argParser.addFlag('wrap', + argParser.addFlag(FlutterGlobalOptions.kWrapFlag, hide: !verboseHelp, help: 'Toggles output word wrapping, regardless of whether or not the output is a terminal.', defaultsTo: true); - argParser.addOption('wrap-column', + argParser.addOption(FlutterGlobalOptions.kWrapColumnOption, hide: !verboseHelp, help: 'Sets the output wrap column. If not set, uses the width of the terminal. No ' 'wrapping occurs if not writing to a terminal. Use "--no-wrap" to turn off wrapping ' 'when connected to a terminal.'); - argParser.addOption('device-id', + argParser.addOption(FlutterGlobalOptions.kDeviceIdOption, abbr: 'd', help: 'Target device id or name (prefixes allowed).'); - argParser.addFlag('version', + argParser.addFlag(FlutterGlobalOptions.kVersionFlag, negatable: false, help: 'Reports the version of this tool.'); - argParser.addFlag('machine', + argParser.addFlag(FlutterGlobalOptions.kMachineFlag, negatable: false, hide: !verboseHelp, help: 'When used with the "--version" flag, outputs the information using JSON.'); - argParser.addFlag('color', + argParser.addFlag(FlutterGlobalOptions.kColorFlag, hide: !verboseHelp, help: 'Whether to use terminal colors (requires support for ANSI escape sequences).', defaultsTo: true); - argParser.addFlag('version-check', + argParser.addFlag(FlutterGlobalOptions.kVersionCheckFlag, defaultsTo: true, hide: !verboseHelp, help: 'Allow Flutter to check for updates when this command runs.'); - argParser.addFlag('suppress-analytics', + argParser.addFlag(FlutterGlobalOptions.kSuppressAnalyticsFlag, negatable: false, help: 'Suppress analytics reporting for the current CLI invocation.'); - argParser.addFlag('disable-telemetry', + argParser.addFlag(FlutterGlobalOptions.kDisableTelemetryFlag, negatable: false, help: 'Disable telemetry reporting each time a flutter or dart ' 'command runs, until it is re-enabled.'); - argParser.addFlag('enable-telemetry', + argParser.addFlag(FlutterGlobalOptions.kEnableTelemetryFlag, negatable: false, help: 'Enable telemetry reporting each time a flutter or dart ' 'command runs.'); - argParser.addOption('packages', + argParser.addOption(FlutterGlobalOptions.kPackagesOption, hide: !verboseHelp, help: 'Path to your "package_config.json" file.'); if (verboseHelp) { argParser.addSeparator('Local build selection options (not normally required):'); } - argParser.addOption('local-engine-src-path', + argParser.addOption(FlutterGlobalOptions.kLocalEngineSrcPathOption, hide: !verboseHelp, help: 'Path to your engine src directory, if you are building Flutter locally.\n' 'Defaults to \$$kFlutterEngineEnvironmentVariableName if set, otherwise defaults to ' 'the path given in your pubspec.yaml dependency_overrides for $kFlutterEnginePackageName, ' 'if any.'); - argParser.addOption('local-engine', + argParser.addOption(FlutterGlobalOptions.kLocalEngineOption, hide: !verboseHelp, help: 'Name of a build output within the engine out directory, if you are building Flutter locally.\n' 'Use this to select a specific version of the engine if you have built multiple engine targets.\n' 'This path is relative to "--local-engine-src-path" (see above).'); - argParser.addOption('local-web-sdk', + argParser.addOption(FlutterGlobalOptions.kLocalWebSDKOption, hide: !verboseHelp, help: 'Name of a build output within the engine out directory, if you are building Flutter locally.\n' 'Use this to select a specific version of the web sdk if you have built multiple engine targets.\n' @@ -115,16 +140,22 @@ class FlutterCommandRunner extends CommandRunner<void> { if (verboseHelp) { argParser.addSeparator('Options for testing the "flutter" tool itself:'); } - argParser.addFlag('show-test-device', + argParser.addFlag(FlutterGlobalOptions.kShowTestDeviceFlag, negatable: false, hide: !verboseHelp, help: 'List the special "flutter-tester" device in device listings. ' 'This headless device is used to test Flutter tooling.'); - argParser.addFlag('show-web-server-device', + argParser.addFlag(FlutterGlobalOptions.kShowWebServerDeviceFlag, negatable: false, hide: !verboseHelp, help: 'List the special "web-server" device in device listings.', ); + argParser.addFlag( + FlutterGlobalOptions.kContinuousIntegrationFlag, + negatable: false, + help: 'Enable a set of CI-specific test debug settings.', + hide: !verboseHelp, + ); } @override @@ -198,8 +229,8 @@ class FlutterCommandRunner extends CommandRunner<void> { // If the flag for enabling or disabling telemetry is passed in, // we will return out - if (topLevelResults.wasParsed('disable-telemetry') || - topLevelResults.wasParsed('enable-telemetry')) { + if (topLevelResults.wasParsed(FlutterGlobalOptions.kDisableTelemetryFlag) || + topLevelResults.wasParsed(FlutterGlobalOptions.kEnableTelemetryFlag)) { return; } @@ -207,43 +238,43 @@ class FlutterCommandRunner extends CommandRunner<void> { // wrapping will occur at this width explicitly, and won't adapt if the // terminal size changes during a run. int? wrapColumn; - if (topLevelResults.wasParsed('wrap-column')) { + if (topLevelResults.wasParsed(FlutterGlobalOptions.kWrapColumnOption)) { try { - wrapColumn = int.parse(topLevelResults['wrap-column'] as String); + wrapColumn = int.parse(topLevelResults[FlutterGlobalOptions.kWrapColumnOption] as String); if (wrapColumn < 0) { - throwToolExit(userMessages.runnerWrapColumnInvalid(topLevelResults['wrap-column'])); + throwToolExit(userMessages.runnerWrapColumnInvalid(topLevelResults[FlutterGlobalOptions.kWrapColumnOption])); } } on FormatException { - throwToolExit(userMessages.runnerWrapColumnParseError(topLevelResults['wrap-column'])); + throwToolExit(userMessages.runnerWrapColumnParseError(topLevelResults[FlutterGlobalOptions.kWrapColumnOption])); } } // If we're not writing to a terminal with a defined width, then don't wrap // anything, unless the user explicitly said to. - final bool useWrapping = topLevelResults.wasParsed('wrap') - ? topLevelResults['wrap'] as bool - : globals.stdio.terminalColumns != null && topLevelResults['wrap'] as bool; + final bool useWrapping = topLevelResults.wasParsed(FlutterGlobalOptions.kWrapFlag) + ? topLevelResults[FlutterGlobalOptions.kWrapFlag] as bool + : globals.stdio.terminalColumns != null && topLevelResults[FlutterGlobalOptions.kWrapFlag] as bool; contextOverrides[OutputPreferences] = OutputPreferences( wrapText: useWrapping, - showColor: topLevelResults['color'] as bool?, + showColor: topLevelResults[FlutterGlobalOptions.kColorFlag] as bool?, wrapColumn: wrapColumn, ); - if (((topLevelResults['show-test-device'] as bool?) ?? false) - || topLevelResults['device-id'] == FlutterTesterDevices.kTesterDeviceId) { + if (((topLevelResults[FlutterGlobalOptions.kShowTestDeviceFlag] as bool?) ?? false) + || topLevelResults[FlutterGlobalOptions.kDeviceIdOption] == FlutterTesterDevices.kTesterDeviceId) { FlutterTesterDevices.showFlutterTesterDevice = true; } - if (((topLevelResults['show-web-server-device'] as bool?) ?? false) - || topLevelResults['device-id'] == WebServerDevice.kWebServerDeviceId) { + if (((topLevelResults[FlutterGlobalOptions.kShowWebServerDeviceFlag] as bool?) ?? false) + || topLevelResults[FlutterGlobalOptions.kDeviceIdOption] == WebServerDevice.kWebServerDeviceId) { WebServerDevice.showWebServerDevice = true; } // Set up the tooling configuration. final EngineBuildPaths? engineBuildPaths = await globals.localEngineLocator?.findEnginePath( - engineSourcePath: topLevelResults['local-engine-src-path'] as String?, - localEngine: topLevelResults['local-engine'] as String?, - localWebSdk: topLevelResults['local-web-sdk'] as String?, - packagePath: topLevelResults['packages'] as String?, + engineSourcePath: topLevelResults[FlutterGlobalOptions.kLocalEngineSrcPathOption] as String?, + localEngine: topLevelResults[FlutterGlobalOptions.kLocalEngineOption] as String?, + localWebSdk: topLevelResults[FlutterGlobalOptions.kLocalWebSDKOption] as String?, + packagePath: topLevelResults[FlutterGlobalOptions.kPackagesOption] as String?, ); if (engineBuildPaths != null) { contextOverrides.addAll(<Type, Object?>{ @@ -256,24 +287,24 @@ class FlutterCommandRunner extends CommandRunner<void> { return MapEntry<Type, Generator>(type, () => value); }), body: () async { - globals.logger.quiet = (topLevelResults['quiet'] as bool?) ?? false; + globals.logger.quiet = (topLevelResults[FlutterGlobalOptions.kQuietFlag] as bool?) ?? false; if (globals.platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true') { await globals.cache.lock(); } - if ((topLevelResults['suppress-analytics'] as bool?) ?? false) { + if ((topLevelResults[FlutterGlobalOptions.kSuppressAnalyticsFlag] as bool?) ?? false) { globals.flutterUsage.suppressAnalytics = true; } globals.flutterVersion.ensureVersionFile(); - final bool machineFlag = topLevelResults['machine'] as bool? ?? false; + final bool machineFlag = topLevelResults[FlutterGlobalOptions.kMachineFlag] as bool? ?? false; final bool ci = await globals.botDetector.isRunningOnBot; final bool redirectedCompletion = !globals.stdio.hasTerminal && (topLevelResults.command?.name ?? '').endsWith('-completion'); final bool isMachine = machineFlag || ci || redirectedCompletion; - final bool versionCheckFlag = topLevelResults['version-check'] as bool? ?? false; - final bool explicitVersionCheckPassed = topLevelResults.wasParsed('version-check') && versionCheckFlag; + final bool versionCheckFlag = topLevelResults[FlutterGlobalOptions.kVersionCheckFlag] as bool? ?? false; + final bool explicitVersionCheckPassed = topLevelResults.wasParsed(FlutterGlobalOptions.kVersionCheckFlag) && versionCheckFlag; if (topLevelResults.command?.name != 'upgrade' && (explicitVersionCheckPassed || (versionCheckFlag && !isMachine))) { @@ -281,21 +312,23 @@ class FlutterCommandRunner extends CommandRunner<void> { } // See if the user specified a specific device. - final String? specifiedDeviceId = topLevelResults['device-id'] as String?; + final String? specifiedDeviceId = topLevelResults[FlutterGlobalOptions.kDeviceIdOption] as String?; if (specifiedDeviceId != null) { globals.deviceManager?.specifiedDeviceId = specifiedDeviceId; } - if ((topLevelResults['version'] as bool?) ?? false) { - globals.flutterUsage.sendCommand('version'); - globals.flutterVersion.fetchTagsAndUpdate(); - String status; + if ((topLevelResults[FlutterGlobalOptions.kVersionFlag] as bool?) ?? false) { + globals.flutterUsage.sendCommand(FlutterGlobalOptions.kVersionFlag); + final FlutterVersion version = globals.flutterVersion.fetchTagsAndGetVersion( + clock: globals.systemClock, + ); + final String status; if (machineFlag) { - final Map<String, Object> jsonOut = globals.flutterVersion.toJson(); + final Map<String, Object> jsonOut = version.toJson(); jsonOut['flutterRoot'] = Cache.flutterRoot!; status = const JsonEncoder.withIndent(' ').convert(jsonOut); } else { - status = globals.flutterVersion.toString(); + status = version.toString(); } globals.printStatus(status); return; diff --git a/packages/flutter_tools/lib/src/runner/target_devices.dart b/packages/flutter_tools/lib/src/runner/target_devices.dart index b0697d0c9dea0..202ac9075432b 100644 --- a/packages/flutter_tools/lib/src/runner/target_devices.dart +++ b/packages/flutter_tools/lib/src/runner/target_devices.dart @@ -774,7 +774,7 @@ class TargetDeviceSelection { throwToolExit(''); } final int deviceIndex = int.parse(userInputString) - 1; - if (deviceIndex < devices.length) { + if (deviceIndex > -1 && deviceIndex < devices.length) { chosenDevice = devices[deviceIndex]; } } diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart index a9bf867f4826d..1ae2178014b47 100644 --- a/packages/flutter_tools/lib/src/test/flutter_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart @@ -161,7 +161,7 @@ import 'dart:developer' as developer; '''); } buffer.write(''' -import 'package:test_api/src/remote_listener.dart'; +import 'package:test_api/backend.dart'; import 'package:stream_channel/stream_channel.dart'; import 'package:stack_trace/stack_trace.dart'; @@ -392,9 +392,13 @@ class FlutterPlatform extends PlatformPlugin { String isolateId, String expression, List<String> definitions, + List<String> definitionTypes, List<String> typeDefinitions, + List<String> typeBounds, + List<String> typeDefaults, String libraryUri, String? klass, + String? method, bool isStatic, ) async { if (compiler == null || compiler!.compiler == null) { @@ -402,7 +406,8 @@ class FlutterPlatform extends PlatformPlugin { } final CompilerOutput? compilerOutput = await compiler!.compiler!.compileExpression(expression, definitions, - typeDefinitions, libraryUri, klass, isStatic); + definitionTypes, typeDefinitions, typeBounds, typeDefaults, libraryUri, + klass, method, isStatic); if (compilerOutput != null && compilerOutput.expressionData != null) { return base64.encode(compilerOutput.expressionData!); } diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart index bae196f14b0fe..0702b35e7ec51 100644 --- a/packages/flutter_tools/lib/src/version.dart +++ b/packages/flutter_tools/lib/src/version.dart @@ -79,103 +79,152 @@ Channel? getChannelForName(String name) { return null; } -class FlutterVersion { +abstract class FlutterVersion { /// Parses the Flutter version from currently available tags in the local /// repo. - /// - /// Call [fetchTagsAndUpdate] to update the version based on the latest tags - /// available upstream. - FlutterVersion({ + factory FlutterVersion({ SystemClock clock = const SystemClock(), - String? workingDirectory, - String? frameworkRevision, - }) : _clock = clock, - _workingDirectory = workingDirectory { - _frameworkRevision = frameworkRevision ?? _runGit( + required FileSystem fs, + required String flutterRoot, + @protected + bool fetchTags = false, + }) { + final File versionFile = getVersionFile(fs, flutterRoot); + + if (!fetchTags && versionFile.existsSync()) { + final _FlutterVersionFromFile? version = _FlutterVersionFromFile.tryParseFromFile( + versionFile, + flutterRoot: flutterRoot, + ); + if (version != null) { + return version; + } + } + + // if we are fetching tags, ignore cached versionFile + if (fetchTags && versionFile.existsSync()) { + versionFile.deleteSync(); + final File legacyVersionFile = fs.file(fs.path.join(flutterRoot, 'version')); + if (legacyVersionFile.existsSync()) { + legacyVersionFile.deleteSync(); + } + } + + final String frameworkRevision = _runGit( gitLog(<String>['-n', '1', '--pretty=format:%H']).join(' '), globals.processUtils, - _workingDirectory, + flutterRoot, + ); + + return FlutterVersion.fromRevision( + clock: clock, + frameworkRevision: frameworkRevision, + fs: fs, + flutterRoot: flutterRoot, + fetchTags: fetchTags, ); - _gitTagVersion = GitTagVersion.determine(globals.processUtils, globals.platform, workingDirectory: _workingDirectory, gitRef: _frameworkRevision); - _frameworkVersion = gitTagVersion.frameworkVersionFor(_frameworkRevision); } - final SystemClock _clock; - final String? _workingDirectory; + FlutterVersion._({ + required SystemClock clock, + required this.flutterRoot, + required this.fs, + }) : _clock = clock; - /// Fetches tags from the upstream Flutter repository and re-calculates the - /// version. - /// - /// This carries a performance penalty, and should only be called when the - /// user explicitly wants to get the version, e.g. for `flutter --version` or - /// `flutter doctor`. - void fetchTagsAndUpdate() { - _gitTagVersion = GitTagVersion.determine(globals.processUtils, globals.platform, workingDirectory: _workingDirectory, fetchTags: true); - _frameworkVersion = gitTagVersion.frameworkVersionFor(_frameworkRevision); + factory FlutterVersion.fromRevision({ + SystemClock clock = const SystemClock(), + required String flutterRoot, + required String frameworkRevision, + required FileSystem fs, + bool fetchTags = false, + }) { + final GitTagVersion gitTagVersion = GitTagVersion.determine( + globals.processUtils, + globals.platform, + gitRef: frameworkRevision, + workingDirectory: flutterRoot, + fetchTags: fetchTags, + ); + final String frameworkVersion = gitTagVersion.frameworkVersionFor(frameworkRevision); + return _FlutterVersionGit._( + clock: clock, + flutterRoot: flutterRoot, + frameworkRevision: frameworkRevision, + frameworkVersion: frameworkVersion, + gitTagVersion: gitTagVersion, + fs: fs, + ); } - String? _repositoryUrl; - String? get repositoryUrl { - if (_repositoryUrl == null) { - final String gitChannel = _runGit( - 'git rev-parse --abbrev-ref --symbolic $kGitTrackingUpstream', - globals.processUtils, - _workingDirectory, - ); - final int slash = gitChannel.indexOf('/'); - if (slash != -1) { - final String remote = gitChannel.substring(0, slash); - _repositoryUrl = _runGit( - 'git ls-remote --get-url $remote', - globals.processUtils, - _workingDirectory, - ); - } + /// Ensure the latest git tags are fetched and recalculate [FlutterVersion]. + /// + /// This is only required when not on beta or stable and we need to calculate + /// the current version relative to upstream tags. + /// + /// This is a method and not a factory constructor so that test classes can + /// override it. + FlutterVersion fetchTagsAndGetVersion({ + SystemClock clock = const SystemClock(), + }) { + // We don't need to fetch tags on beta and stable to calculate the version, + // we should already exactly be on a tag that was pushed when this release + // was published. + if (channel != 'master' && channel != 'main') { + return this; } - return _repositoryUrl; + return FlutterVersion( + clock: clock, + flutterRoot: flutterRoot, + fs: fs, + fetchTags: true, + ); } - /// The channel is the current branch if we recognize it, or "[user-branch]" (kUserBranch). - /// `master`, `beta`, `stable`; or old ones, like `alpha`, `hackathon`, `dev`, ... - String get channel { - final String channel = getBranchName(redactUnknownBranches: true); - assert(kOfficialChannels.contains(channel) || kObsoleteBranches.containsKey(channel) || channel == kUserBranch, 'Potential PII leak in channel name: "$channel"'); - return channel; - } + final FileSystem fs; - late GitTagVersion _gitTagVersion; - GitTagVersion get gitTagVersion => _gitTagVersion; + final SystemClock _clock; - /// The name of the local branch. - /// Use getBranchName() to read this. - String? _branch; + String? get repositoryUrl; + + GitTagVersion get gitTagVersion; + + /// The channel is the upstream branch. + /// + /// `master`, `dev`, `beta`, `stable`; or old ones, like `alpha`, `hackathon`, ... + String get channel; - late String _frameworkRevision; - String get frameworkRevision => _frameworkRevision; + String get frameworkRevision; String get frameworkRevisionShort => _shortGitRevision(frameworkRevision); + String get frameworkVersion; + + String get devToolsVersion; + + String get dartSdkVersion; + + String get engineRevision; + String get engineRevisionShort => _shortGitRevision(engineRevision); + + // This is static as it is called from a constructor. + static File getVersionFile(FileSystem fs, String flutterRoot) { + return fs.file(fs.path.join(flutterRoot, 'bin', 'cache', 'flutter.version.json')); + } + + final String flutterRoot; + String? _frameworkAge; + + // TODO(fujino): calculate this relative to frameworkCommitDate for + // _FlutterVersionFromFile so we don't need a git call. String get frameworkAge { return _frameworkAge ??= _runGit( - gitLog(<String>['-n', '1', '--pretty=format:%ar']).join(' '), + FlutterVersion.gitLog(<String>['-n', '1', '--pretty=format:%ar']).join(' '), globals.processUtils, - _workingDirectory, + flutterRoot, ); } - late String _frameworkVersion; - String get frameworkVersion => _frameworkVersion; - - String get devToolsVersion => globals.cache.devToolsVersion; - - String get dartSdkVersion => globals.cache.dartSdkVersion; - - String get engineRevision => globals.cache.engineRevision; - String get engineRevisionShort => _shortGitRevision(engineRevision); - - void ensureVersionFile() { - globals.fs.file(globals.fs.path.join(Cache.flutterRoot!, 'version')).writeAsStringSync(_frameworkVersion); - } + void ensureVersionFile(); @override String toString() { @@ -202,47 +251,13 @@ class FlutterVersion { 'engineRevision': engineRevision, 'dartSdkVersion': dartSdkVersion, 'devToolsVersion': devToolsVersion, + 'flutterVersion': frameworkVersion, }; - String get frameworkDate => frameworkCommitDate; - /// A date String describing the last framework commit. /// /// If a git command fails, this will return a placeholder date. - String get frameworkCommitDate => _gitCommitDate(lenient: true); - - // The date of the given commit hash as [gitRef]. If no hash is specified, - // then it is the HEAD of the current local branch. - // - // If lenient is true, and the git command fails, a placeholder date is - // returned. Otherwise, the VersionCheckError exception is propagated. - static String _gitCommitDate({ - String gitRef = 'HEAD', - bool lenient = false, - }) { - final List<String> args = gitLog(<String>[ - gitRef, - '-n', - '1', - '--pretty=format:%ad', - '--date=iso', - ]); - try { - // Don't plumb 'lenient' through directly so that we can print an error - // if something goes wrong. - return _runSync(args, lenient: false); - } on VersionCheckError catch (e) { - if (lenient) { - final DateTime dummyDate = DateTime.fromMillisecondsSinceEpoch(0); - globals.printError('Failed to find the latest git commit date: $e\n' - 'Returning $dummyDate instead.'); - // Return something that DateTime.parse() can parse. - return dummyDate.toString(); - } else { - rethrow; - } - } - } + String get frameworkCommitDate; /// Checks if the currently installed version of Flutter is up-to-date, and /// warns the user if it isn't. @@ -261,7 +276,7 @@ class FlutterVersion { DateTime localFrameworkCommitDate; try { // Don't perform the update check if fetching the latest local commit failed. - localFrameworkCommitDate = DateTime.parse(_gitCommitDate()); + localFrameworkCommitDate = DateTime.parse(_gitCommitDate(workingDirectory: flutterRoot)); } on VersionCheckError { return; } @@ -278,6 +293,53 @@ class FlutterVersion { ).run(); } + /// Gets the release date of the latest available Flutter version. + /// + /// This method sends a server request if it's been more than + /// [checkAgeConsideredUpToDate] since the last version check. + /// + /// Returns null if the cached version is out-of-date or missing, and we are + /// unable to reach the server to get the latest version. + Future<DateTime?> _getLatestAvailableFlutterDate() async { + globals.cache.checkLockAcquired(); + final VersionCheckStamp versionCheckStamp = await VersionCheckStamp.load(globals.cache, globals.logger); + + final DateTime now = _clock.now(); + if (versionCheckStamp.lastTimeVersionWasChecked != null) { + final Duration timeSinceLastCheck = now.difference( + versionCheckStamp.lastTimeVersionWasChecked!, + ); + + // Don't ping the server too often. Return cached value if it's fresh. + if (timeSinceLastCheck < VersionFreshnessValidator.checkAgeConsideredUpToDate) { + return versionCheckStamp.lastKnownRemoteVersion; + } + } + + // Cache is empty or it's been a while since the last server ping. Ping the server. + try { + final DateTime remoteFrameworkCommitDate = DateTime.parse( + await fetchRemoteFrameworkCommitDate(), + ); + await versionCheckStamp.store( + newTimeVersionWasChecked: now, + newKnownRemoteVersion: remoteFrameworkCommitDate, + ); + return remoteFrameworkCommitDate; + } on VersionCheckError catch (error) { + // This happens when any of the git commands fails, which can happen when + // there's no Internet connectivity. Remote version check is best effort + // only. We do not prevent the command from running when it fails. + globals.printTrace('Failed to check Flutter version in the remote repository: $error'); + // Still update the timestamp to avoid us hitting the server on every single + // command if for some reason we cannot connect (eg. we may be offline). + await versionCheckStamp.store( + newTimeVersionWasChecked: now, + ); + return null; + } + } + /// The date of the latest framework commit in the remote repository. /// /// Throws [VersionCheckError] if a git command fails, for example, when the @@ -286,7 +348,7 @@ class FlutterVersion { try { // Fetch upstream branch's commit and tags await _run(<String>['git', 'fetch', '--tags']); - return _gitCommitDate(gitRef: kGitTrackingUpstream); + return _gitCommitDate(gitRef: kGitTrackingUpstream, workingDirectory: Cache.flutterRoot); } on VersionCheckError catch (error) { globals.printError(error.message); rethrow; @@ -301,13 +363,18 @@ class FlutterVersion { return '${getBranchName(redactUnknownBranches: redactUnknownBranches)}/$frameworkRevisionShort'; } + /// The name of the local branch. + /// + /// Use getBranchName() to read this. + String? _branch; + /// Return the branch name. /// /// If [redactUnknownBranches] is true and the branch is unknown, /// the branch name will be returned as `'[user-branch]'` ([kUserBranch]). String getBranchName({ bool redactUnknownBranches = false }) { _branch ??= () { - final String branch = _runGit('git symbolic-ref --short HEAD', globals.processUtils, _workingDirectory); + final String branch = _runGit('git symbolic-ref --short HEAD', globals.processUtils, flutterRoot); return branch == 'HEAD' ? '' : branch; }(); if (redactUnknownBranches || _branch!.isEmpty) { @@ -342,51 +409,205 @@ class FlutterVersion { static List<String> gitLog(List<String> args) { return <String>['git', '-c', 'log.showSignature=false', 'log'] + args; } +} - /// Gets the release date of the latest available Flutter version. - /// - /// This method sends a server request if it's been more than - /// [checkAgeConsideredUpToDate] since the last version check. - /// - /// Returns null if the cached version is out-of-date or missing, and we are - /// unable to reach the server to get the latest version. - Future<DateTime?> _getLatestAvailableFlutterDate() async { - globals.cache.checkLockAcquired(); - final VersionCheckStamp versionCheckStamp = await VersionCheckStamp.load(globals.cache, globals.logger); +// The date of the given commit hash as [gitRef]. If no hash is specified, +// then it is the HEAD of the current local branch. +// +// If lenient is true, and the git command fails, a placeholder date is +// returned. Otherwise, the VersionCheckError exception is propagated. +String _gitCommitDate({ + String gitRef = 'HEAD', + bool lenient = false, + required String? workingDirectory, +}) { + final List<String> args = FlutterVersion.gitLog(<String>[ + gitRef, + '-n', + '1', + '--pretty=format:%ad', + '--date=iso', + ]); + try { + // Don't plumb 'lenient' through directly so that we can print an error + // if something goes wrong. + return _runSync( + args, + lenient: false, + workingDirectory: workingDirectory, + ); + } on VersionCheckError catch (e) { + if (lenient) { + final DateTime dummyDate = DateTime.fromMillisecondsSinceEpoch(0); + globals.printError('Failed to find the latest git commit date: $e\n' + 'Returning $dummyDate instead.'); + // Return something that DateTime.parse() can parse. + return dummyDate.toString(); + } else { + rethrow; + } + } +} - final DateTime now = _clock.now(); - if (versionCheckStamp.lastTimeVersionWasChecked != null) { - final Duration timeSinceLastCheck = now.difference( - versionCheckStamp.lastTimeVersionWasChecked!, +class _FlutterVersionFromFile extends FlutterVersion { + _FlutterVersionFromFile._({ + required super.clock, + required this.frameworkVersion, + required this.channel, + required this.repositoryUrl, + required this.frameworkRevision, + required this.frameworkCommitDate, + required this.engineRevision, + required this.dartSdkVersion, + required this.devToolsVersion, + required this.gitTagVersion, + required super.flutterRoot, + required super.fs, + }) : super._(); + + static _FlutterVersionFromFile? tryParseFromFile( + File jsonFile, { + required String flutterRoot, + SystemClock clock = const SystemClock(), + }) { + try { + final String jsonContents = jsonFile.readAsStringSync(); + final Map<String, Object?> manifest = jsonDecode(jsonContents) as Map<String, Object?>; + + return _FlutterVersionFromFile._( + clock: clock, + frameworkVersion: manifest['frameworkVersion']! as String, + channel: manifest['channel']! as String, + repositoryUrl: manifest['repositoryUrl']! as String, + frameworkRevision: manifest['frameworkRevision']! as String, + frameworkCommitDate: manifest['frameworkCommitDate']! as String, + engineRevision: manifest['engineRevision']! as String, + dartSdkVersion: manifest['dartSdkVersion']! as String, + devToolsVersion: manifest['devToolsVersion']! as String, + gitTagVersion: GitTagVersion.parse(manifest['flutterVersion']! as String), + flutterRoot: flutterRoot, + fs: jsonFile.fileSystem, ); - - // Don't ping the server too often. Return cached value if it's fresh. - if (timeSinceLastCheck < VersionFreshnessValidator.checkAgeConsideredUpToDate) { - return versionCheckStamp.lastKnownRemoteVersion; + // ignore: avoid_catches_without_on_clauses + } catch (err) { + globals.printTrace('Failed to parse ${jsonFile.path} with $err'); + try { + jsonFile.deleteSync(); + } on FileSystemException { + globals.printTrace('Failed to delete ${jsonFile.path}'); } + // Returning null means fallback to git implementation. + return null; } + } - // Cache is empty or it's been a while since the last server ping. Ping the server. - try { - final DateTime remoteFrameworkCommitDate = DateTime.parse( - await FlutterVersion.fetchRemoteFrameworkCommitDate(), - ); - await versionCheckStamp.store( - newTimeVersionWasChecked: now, - newKnownRemoteVersion: remoteFrameworkCommitDate, - ); - return remoteFrameworkCommitDate; - } on VersionCheckError catch (error) { - // This happens when any of the git commands fails, which can happen when - // there's no Internet connectivity. Remote version check is best effort - // only. We do not prevent the command from running when it fails. - globals.printTrace('Failed to check Flutter version in the remote repository: $error'); - // Still update the timestamp to avoid us hitting the server on every single - // command if for some reason we cannot connect (eg. we may be offline). - await versionCheckStamp.store( - newTimeVersionWasChecked: now, + @override + final GitTagVersion gitTagVersion; + + @override + final String frameworkVersion; + + @override + final String channel; + + @override + String getBranchName({bool redactUnknownBranches = false}) => channel; + + @override + final String repositoryUrl; + + @override + final String frameworkRevision; + + @override + final String frameworkCommitDate; + + @override + final String engineRevision; + + @override + final String dartSdkVersion; + + @override + final String devToolsVersion; + + @override + void ensureVersionFile() {} +} + +class _FlutterVersionGit extends FlutterVersion { + _FlutterVersionGit._({ + required super.clock, + required super.flutterRoot, + required this.frameworkRevision, + required this.frameworkVersion, + required this.gitTagVersion, + required super.fs, + }) : super._(); + + @override + final GitTagVersion gitTagVersion; + + @override + final String frameworkRevision; + + @override + String get frameworkCommitDate => _gitCommitDate(lenient: true, workingDirectory: flutterRoot); + + String? _repositoryUrl; + @override + String? get repositoryUrl { + if (_repositoryUrl == null) { + final String gitChannel = _runGit( + 'git rev-parse --abbrev-ref --symbolic $kGitTrackingUpstream', + globals.processUtils, + flutterRoot, ); - return null; + final int slash = gitChannel.indexOf('/'); + if (slash != -1) { + final String remote = gitChannel.substring(0, slash); + _repositoryUrl = _runGit( + 'git ls-remote --get-url $remote', + globals.processUtils, + flutterRoot, + ); + } + } + return _repositoryUrl; + } + + @override + String get devToolsVersion => globals.cache.devToolsVersion; + + @override + String get dartSdkVersion => globals.cache.dartSdkVersion; + + @override + String get engineRevision => globals.cache.engineRevision; + + @override + final String frameworkVersion; + + /// The channel is the current branch if we recognize it, or "[user-branch]" (kUserBranch). + /// `master`, `beta`, `stable`; or old ones, like `alpha`, `hackathon`, `dev`, ... + @override + String get channel { + final String channel = getBranchName(redactUnknownBranches: true); + assert(kOfficialChannels.contains(channel) || kObsoleteBranches.containsKey(channel) || channel == kUserBranch, 'Potential PII leak in channel name: "$channel"'); + return channel; + } + + @override + void ensureVersionFile() { + final File legacyVersionFile = fs.file(fs.path.join(flutterRoot, 'version')); + if (!legacyVersionFile.existsSync()) { + legacyVersionFile.writeAsStringSync(frameworkVersion); + } + + const JsonEncoder encoder = JsonEncoder.withIndent(' '); + final File newVersionFile = FlutterVersion.getVersionFile(fs, flutterRoot); + if (!newVersionFile.existsSync()) { + newVersionFile.writeAsStringSync(encoder.convert(toJson())); } } } @@ -606,10 +827,14 @@ class VersionCheckError implements Exception { /// /// If [lenient] is true and the command fails, returns an empty string. /// Otherwise, throws a [ToolExit] exception. -String _runSync(List<String> command, { bool lenient = true }) { +String _runSync( + List<String> command, { + bool lenient = true, + required String? workingDirectory, +}) { final ProcessResult results = globals.processManager.runSync( command, - workingDirectory: Cache.flutterRoot, + workingDirectory: workingDirectory, ); if (results.exitCode == 0) { @@ -630,7 +855,7 @@ String _runSync(List<String> command, { bool lenient = true }) { String _runGit(String command, ProcessUtils processUtils, String? workingDirectory) { return processUtils.runSync( command.split(' '), - workingDirectory: workingDirectory ?? Cache.flutterRoot, + workingDirectory: workingDirectory, ).stdout.trim(); } diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 7f276be8c421e..6da8494d24de6 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -7,13 +7,16 @@ import 'dart:async'; import 'package:meta/meta.dart' show visibleForTesting; import 'package:vm_service/vm_service.dart' as vm_service; +import 'android/android_app_link_settings.dart'; import 'base/common.dart'; import 'base/context.dart'; import 'base/io.dart' as io; import 'base/logger.dart'; import 'base/utils.dart'; +import 'cache.dart'; import 'convert.dart'; import 'device.dart'; +import 'globals.dart' as globals; import 'ios/xcodeproj.dart'; import 'project.dart'; import 'version.dart'; @@ -42,6 +45,7 @@ const String kFlutterGetSkSLServiceName = 'flutterGetSkSL'; const String kFlutterGetIOSBuildOptionsServiceName = 'flutterGetIOSBuildOptions'; const String kFlutterGetAndroidBuildVariantsServiceName = 'flutterGetAndroidBuildVariants'; const String kFlutterGetIOSUniversalLinkSettingsServiceName = 'flutterGetIOSUniversalLinkSettings'; +const String kFlutterGetAndroidAppLinkSettingsName = 'flutterGetAndroidAppLinkSettings'; /// The error response code from an unrecoverable compilation failure. const int kIsolateReloadBarred = 1005; @@ -108,9 +112,13 @@ typedef CompileExpression = Future<String> Function( String isolateId, String expression, List<String> definitions, + List<String> definitionTypes, List<String> typeDefinitions, + List<String> typeBounds, + List<String> typeDefaults, String libraryUri, String? klass, + String? method, bool isStatic, ); @@ -238,7 +246,10 @@ Future<vm_service.VmService> setUpVmService({ } vmService.registerServiceCallback(kFlutterVersionServiceName, (Map<String, Object?> params) async { - final FlutterVersion version = context.get<FlutterVersion>() ?? FlutterVersion(); + final FlutterVersion version = context.get<FlutterVersion>() ?? FlutterVersion( + fs: globals.fs, + flutterRoot: Cache.flutterRoot!, + ); final Map<String, Object> versionJson = version.toJson(); versionJson['frameworkRevisionShort'] = version.frameworkRevisionShort; versionJson['engineRevisionShort'] = version.engineRevisionShort; @@ -256,14 +267,18 @@ Future<vm_service.VmService> setUpVmService({ final String isolateId = _validateRpcStringParam('compileExpression', params, 'isolateId'); final String expression = _validateRpcStringParam('compileExpression', params, 'expression'); final List<String> definitions = List<String>.from(params['definitions']! as List<Object?>); + final List<String> definitionTypes = List<String>.from(params['definitionTypes']! as List<Object?>); final List<String> typeDefinitions = List<String>.from(params['typeDefinitions']! as List<Object?>); + final List<String> typeBounds = List<String>.from(params['typeBounds']! as List<Object?>); + final List<String> typeDefaults = List<String>.from(params['typeDefaults']! as List<Object?>); final String libraryUri = params['libraryUri']! as String; final String? klass = params['klass'] as String?; + final String? method = params['method'] as String?; final bool isStatic = _validateRpcBoolParam('compileExpression', params, 'isStatic'); final String kernelBytesBase64 = await compileExpression(isolateId, - expression, definitions, typeDefinitions, libraryUri, klass, - isStatic); + expression, definitions, definitionTypes, typeDefinitions, typeBounds, typeDefaults, + libraryUri, klass, method, isStatic); return <String, Object>{ kResultType: kResultTypeSuccess, 'result': <String, String>{'kernelBytes': kernelBytesBase64}, @@ -357,6 +372,21 @@ Future<vm_service.VmService> setUpVmService({ registrationRequests.add( vmService.registerService(kFlutterGetIOSUniversalLinkSettingsServiceName, kFlutterToolAlias), ); + + vmService.registerServiceCallback(kFlutterGetAndroidAppLinkSettingsName, (Map<String, Object?> params) async { + final String variant = params['variant']! as String; + final AndroidAppLinkSettings settings = await flutterProject.android.getAppLinksSettings(variant: variant); + return <String, Object>{ + 'result': <String, Object>{ + kResultType: kResultTypeSuccess, + 'applicationId': settings.applicationId, + 'domains': settings.domains, + }, + }; + }); + registrationRequests.add( + vmService.registerService(kFlutterGetAndroidAppLinkSettingsName, kFlutterToolAlias), + ); } if (printStructuredErrorLogMethod != null) { @@ -1080,6 +1110,22 @@ class FlutterVmService { }); } + /// Attempt to retrieve the isolate pause event with id [isolateId], or `null` if it has + /// been collected. + Future<vm_service.Event?> getIsolatePauseEventOrNull(String isolateId) async { + return service.getIsolatePauseEvent(isolateId) + .then<vm_service.Event?>( + (vm_service.Event event) => event, + onError: (Object? error, StackTrace stackTrace) { + if (error is vm_service.SentinelException || + error == null || + (error is vm_service.RPCError && error.code == RPCErrorCodes.kServiceDisappeared)) { + return null; + } + return Future<vm_service.Event?>.error(error, stackTrace); + }); + } + /// Create a new development file system on the device. Future<vm_service.Response> createDevFS(String fsName) { // Call the unchecked version of `callServiceExtension` because the caller diff --git a/packages/flutter_tools/lib/src/vscode/vscode_validator.dart b/packages/flutter_tools/lib/src/vscode/vscode_validator.dart index d216536231ca1..7c5a7f485f20c 100644 --- a/packages/flutter_tools/lib/src/vscode/vscode_validator.dart +++ b/packages/flutter_tools/lib/src/vscode/vscode_validator.dart @@ -23,13 +23,20 @@ class VsCodeValidator extends DoctorValidator { @override Future<ValidationResult> validate() async { - final String? vsCodeVersionText = _vsCode.version == null - ? null + final List<ValidationMessage> validationMessages = + List<ValidationMessage>.from(_vsCode.validationMessages); + + final String vsCodeVersionText = _vsCode.version == null + ? userMessages.vsCodeVersion('unknown') : userMessages.vsCodeVersion(_vsCode.version.toString()); + if (_vsCode.version == null) { + validationMessages.add(const ValidationMessage.error('Unable to determine VS Code version.')); + } + return ValidationResult( ValidationType.success, - _vsCode.validationMessages.toList(), + validationMessages, statusInfo: vsCodeVersionText, ); } diff --git a/packages/flutter_tools/lib/src/web/bootstrap.dart b/packages/flutter_tools/lib/src/web/bootstrap.dart index f05428a25dfca..de08019febe0c 100644 --- a/packages/flutter_tools/lib/src/web/bootstrap.dart +++ b/packages/flutter_tools/lib/src/web/bootstrap.dart @@ -196,6 +196,12 @@ define("$bootstrapModule", ["$entrypoint", "dart_sdk"], function(app, dart_sdk) return dart.getSourceMap(url); }); } + // Prevent DDC's requireJS to interfere with modern bundling. + if (typeof define === 'function' && define.amd) { + // Preserve a copy just in case... + define._amd = define.amd; + delete define.amd; + } }); '''; } @@ -213,17 +219,16 @@ String generateTestEntrypoint({ // @dart = ${languageVersion.major}.${languageVersion.minor} import 'org-dartlang-app:///$relativeTestPath' as test; import 'dart:ui' as ui; + import 'dart:ui_web' as ui_web; import 'dart:html'; import 'dart:js'; ${testConfigPath != null ? "import '${Uri.file(testConfigPath)}' as test_config;" : ""} import 'package:stream_channel/stream_channel.dart'; import 'package:flutter_test/flutter_test.dart'; - import 'package:test_api/src/backend/stack_trace_formatter.dart'; // ignore: implementation_imports - import 'package:test_api/src/remote_listener.dart'; // ignore: implementation_imports - import 'package:test_api/src/backend/suite_channel_manager.dart'; // ignore: implementation_imports + import 'package:test_api/backend.dart'; Future<void> main() async { - ui.debugEmulateFlutterTesterEnvironment = true; + ui_web.debugEmulateFlutterTesterEnvironment = true; await ui.webOnlyInitializePlatform(); webGoldenComparator = DefaultWebGoldenComparator(Uri.parse('${Uri.file(absolutePath)}')); (ui.window as dynamic).debugOverrideDevicePixelRatio(3.0); diff --git a/packages/flutter_tools/lib/src/web/file_generators/main_dart.dart b/packages/flutter_tools/lib/src/web/file_generators/main_dart.dart index 9d87fe1a3e72a..040e4097ab796 100644 --- a/packages/flutter_tools/lib/src/web/file_generators/main_dart.dart +++ b/packages/flutter_tools/lib/src/web/file_generators/main_dart.dart @@ -19,7 +19,7 @@ String generateMainDartFile(String appEntrypoint, { '', '// ignore_for_file: type=lint', '', - "import 'dart:ui' as ui;", + "import 'dart:ui_web' as ui_web;", "import 'dart:async';", '', "import '$appEntrypoint' as entrypoint;", @@ -29,7 +29,7 @@ String generateMainDartFile(String appEntrypoint, { 'typedef _NullaryFunction = dynamic Function();', '', 'Future<void> main() async {', - ' await ui.webOnlyWarmupEngine(', + ' await ui_web.bootstrapEngine(', ' runApp: () {', ' if (entrypoint.main is _UnaryFunction) {', ' return (entrypoint.main as _UnaryFunction)(<String>[]);', diff --git a/packages/flutter_tools/lib/src/windows/migrations/show_window_migration.dart b/packages/flutter_tools/lib/src/windows/migrations/show_window_migration.dart index 0e888b695f690..bebb5ecaad252 100644 --- a/packages/flutter_tools/lib/src/windows/migrations/show_window_migration.dart +++ b/packages/flutter_tools/lib/src/windows/migrations/show_window_migration.dart @@ -20,8 +20,8 @@ const String _after = r''' }); // Flutter can complete the first frame before the "show window" callback is - // registered. Ensure a frame is pending to ensure the window is shown. - // This no-ops if the first frame hasn't completed yet. + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; diff --git a/packages/flutter_tools/lib/src/xcode_project.dart b/packages/flutter_tools/lib/src/xcode_project.dart index 0e658a410d1ab..add59a60b1b28 100644 --- a/packages/flutter_tools/lib/src/xcode_project.dart +++ b/packages/flutter_tools/lib/src/xcode_project.dart @@ -104,11 +104,14 @@ abstract class XcodeBasedProject extends FlutterProjectPlatform { File get podManifestLock => hostAppRoot.childDirectory('Pods').childFile('Manifest.lock'); /// The CocoaPods generated 'Pods-Runner-frameworks.sh'. - File get podRunnerFrameworksScript => hostAppRoot + File get podRunnerFrameworksScript => podRunnerTargetSupportFiles + .childFile('Pods-Runner-frameworks.sh'); + + /// The CocoaPods generated directory 'Pods-Runner'. + Directory get podRunnerTargetSupportFiles => hostAppRoot .childDirectory('Pods') .childDirectory('Target Support Files') - .childDirectory('Pods-Runner') - .childFile('Pods-Runner-frameworks.sh'); + .childDirectory('Pods-Runner'); } /// Represents the iOS sub-project of a Flutter project. diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index 1db7ed1b0a5b1..d658af9149643 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -8,16 +8,16 @@ environment: dependencies: # To update these, use "flutter update-packages --force-upgrade". archive: 3.3.2 - args: 2.4.1 + args: 2.4.2 browser_launcher: 1.1.1 - dds: 2.8.1 - dwds: 19.0.0 + dds: 2.9.0+hotfix + dwds: 19.0.1+1 completion: 1.0.1 coverage: 1.6.3 crypto: 3.0.3 file: 6.1.4 flutter_template_images: 4.2.0 - html: 0.15.3 + html: 0.15.4 http: 0.13.6 intl: 0.18.1 meta: 1.9.1 @@ -32,9 +32,9 @@ dependencies: webkit_inspection_protocol: 1.2.0 xml: 6.3.0 yaml: 3.1.2 - native_stack_traces: 0.5.5 + native_stack_traces: 0.5.6 shelf: 1.4.1 - vm_snapshot_analysis: 0.7.2 + vm_snapshot_analysis: 0.7.6 uuid: 3.0.7 web_socket_channel: 2.4.0 stream_channel: 2.1.1 @@ -44,43 +44,44 @@ dependencies: pool: 1.5.1 path: 1.8.3 mime: 1.0.4 - logging: 1.1.1 + logging: 1.2.0 http_multi_server: 3.2.1 convert: 3.1.1 async: 2.11.0 - unified_analytics: 1.1.0 + unified_analytics: 2.0.0 # We depend on very specific internal implementation details of the # 'test' package, which change between versions, so when upgrading # this, make sure the tests are still running correctly. - test_api: 0.5.2 - test_core: 0.5.2 + test_api: 0.6.0 + test_core: 0.5.3 - vm_service: 11.6.0 + vm_service: 11.7.1 standard_message_codec: 0.0.1+3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" built_collection: 5.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - built_value: 8.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + built_value: 8.6.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - csslib: 0.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - dds_service_extensions: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - devtools_shared: 2.23.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + csslib: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + dap: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + dds_service_extensions: 1.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + devtools_shared: 2.24.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fixnum: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" json_rpc_2: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" petitparser: 5.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" platform: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" shelf_packages_handler: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - shelf_proxy: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + shelf_proxy: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_map_stack_trace: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_maps: 0.10.12 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -99,10 +100,10 @@ dev_dependencies: checked_yaml: 2.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" json_annotation: 4.8.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test: 1.24.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test: 1.24.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 2042 +# PUBSPEC CHECKSUM: bab7 diff --git a/packages/flutter_tools/templates/app_shared/analysis_options.yaml.tmpl b/packages/flutter_tools/templates/app_shared/analysis_options.yaml.tmpl index d78ad1b07aaa8..7a98d8c1854bd 100644 --- a/packages/flutter_tools/templates/app_shared/analysis_options.yaml.tmpl +++ b/packages/flutter_tools/templates/app_shared/analysis_options.yaml.tmpl @@ -16,8 +16,7 @@ linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. + # and their documentation is published at https://dart.dev/lints. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code diff --git a/packages/flutter_tools/templates/app_shared/ios.tmpl/Runner/Info.plist.tmpl b/packages/flutter_tools/templates/app_shared/ios.tmpl/Runner/Info.plist.tmpl index 3d4696c092bf6..bb6ce2f4c44a4 100644 --- a/packages/flutter_tools/templates/app_shared/ios.tmpl/Runner/Info.plist.tmpl +++ b/packages/flutter_tools/templates/app_shared/ios.tmpl/Runner/Info.plist.tmpl @@ -41,8 +41,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/flutter_window.cpp b/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/flutter_window.cpp index 46c3e636f1e8b..955ee3038f988 100644 --- a/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/flutter_window.cpp +++ b/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/flutter_window.cpp @@ -32,8 +32,8 @@ bool FlutterWindow::OnCreate() { }); // Flutter can complete the first frame before the "show window" callback is - // registered. Ensure a frame is pending to ensure the window is shown. - // This no-ops if the first frame hasn't completed yet. + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. flutter_controller_->ForceRedraw(); return true; diff --git a/packages/flutter_tools/templates/module/android/deferred_component/build.gradle.tmpl b/packages/flutter_tools/templates/module/android/deferred_component/build.gradle.tmpl index bde8ac7833dce..502aa70449aaa 100644 --- a/packages/flutter_tools/templates/module/android/deferred_component/build.gradle.tmpl +++ b/packages/flutter_tools/templates/module/android/deferred_component/build.gradle.tmpl @@ -19,7 +19,7 @@ if (flutterVersionName == null) { apply plugin: "com.android.dynamic-feature" android { - compileSdkVersion 31 + compileSdkVersion flutter.compileSdkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Info.plist.tmpl b/packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Info.plist.tmpl index 5c9b9128027ff..a4388efddd1b1 100644 --- a/packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Info.plist.tmpl +++ b/packages/flutter_tools/templates/module/ios/host_app_ephemeral/Runner.tmpl/Info.plist.tmpl @@ -41,8 +41,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl index b466c607eeda8..fbde25d5c0985 100644 --- a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl +++ b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl @@ -29,7 +29,7 @@ android { namespace '{{androidIdentifier}}' } - compileSdkVersion 31 + compileSdkVersion {{compileSdkVersion}} compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl index efab097ee1c93..3281a438ca461 100644 --- a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl +++ b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/src/main/kotlin/androidIdentifier/pluginClass.kt.tmpl @@ -16,12 +16,12 @@ class {{pluginClass}}: FlutterPlugin, MethodCallHandler { /// when the Flutter Engine is detached from the Activity private lateinit var channel : MethodChannel - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.binaryMessenger, "{{projectName}}") channel.setMethodCallHandler(this) } - override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + override fun onMethodCall(call: MethodCall, result: Result) { if (call.method == "getPlatformVersion") { result.success("Android ${android.os.Build.VERSION.RELEASE}") } else { @@ -29,7 +29,7 @@ class {{pluginClass}}: FlutterPlugin, MethodCallHandler { } } - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) } } diff --git a/packages/flutter_tools/templates/plugin_shared/pubspec.yaml.tmpl b/packages/flutter_tools/templates/plugin_shared/pubspec.yaml.tmpl index 8641c2f35cc5c..5b0d6b2967ac5 100644 --- a/packages/flutter_tools/templates/plugin_shared/pubspec.yaml.tmpl +++ b/packages/flutter_tools/templates/plugin_shared/pubspec.yaml.tmpl @@ -5,7 +5,7 @@ homepage: environment: sdk: {{dartSdkVersionBounds}} - flutter: ">=3.3.0" + flutter: '>=3.3.0' dependencies: flutter: diff --git a/packages/flutter_tools/templates/skeleton/lib/src/app.dart.tmpl b/packages/flutter_tools/templates/skeleton/lib/src/app.dart.tmpl index 6f7ad23e37d1a..7ba2c2f39dd46 100644 --- a/packages/flutter_tools/templates/skeleton/lib/src/app.dart.tmpl +++ b/packages/flutter_tools/templates/skeleton/lib/src/app.dart.tmpl @@ -20,10 +20,10 @@ class MyApp extends StatelessWidget { Widget build(BuildContext context) { // Glue the SettingsController to the MaterialApp. // - // The AnimatedBuilder Widget listens to the SettingsController for changes. + // The ListenableBuilder Widget listens to the SettingsController for changes. // Whenever the user updates their settings, the MaterialApp is rebuilt. - return AnimatedBuilder( - animation: settingsController, + return ListenableBuilder( + listenable: settingsController, builder: (BuildContext context, Widget? child) { return MaterialApp( // Providing a restorationScopeId allows the Navigator built by the diff --git a/packages/flutter_tools/templates/template_manifest.json b/packages/flutter_tools/templates/template_manifest.json index d0902d5edc286..3729d8909fa75 100644 --- a/packages/flutter_tools/templates/template_manifest.json +++ b/packages/flutter_tools/templates/template_manifest.json @@ -339,6 +339,15 @@ "templates/skeleton/README.md.tmpl", "templates/skeleton/test/implementation_test.dart.test.tmpl", "templates/skeleton/test/unit_test.dart.tmpl", - "templates/skeleton/test/widget_test.dart.tmpl" + "templates/skeleton/test/widget_test.dart.tmpl", + + "templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/contents.xcworkspacedata", + "templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/xcshareddata/IDEWorkspaceChecks.plist", + "templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/xcshareddata/WorkspaceSettings.xcsettings", + "templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.pbxproj", + "templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/contents.xcworkspacedata", + "templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist", + "templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings", + "templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/xcshareddata/xcschemes/Runner.xcscheme.tmpl" ] } diff --git a/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/README.md b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/README.md new file mode 100644 index 0000000000000..2b2b69707db5d --- /dev/null +++ b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/README.md @@ -0,0 +1,5 @@ +# Template Xcode project with a custom application bundle + +This template is an empty Xcode project with a settable application bundle path +within the `xcscheme`. It is used when debugging a project on a physical iOS 17+ +device via Xcode 15+ when `--use-application-binary` is used. \ No newline at end of file diff --git a/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.pbxproj b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.pbxproj new file mode 100644 index 0000000000000..8f544ef77eb2b --- /dev/null +++ b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.pbxproj @@ -0,0 +1,297 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXFileReference section */ + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = "<group>"; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/contents.xcworkspacedata b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000..919434a6254f0 --- /dev/null +++ b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Workspace + version = "1.0"> + <FileRef + location = "self:"> + </FileRef> +</Workspace> diff --git a/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000..18d981003d68d --- /dev/null +++ b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IDEDidComputeMac32BitWarning</key> + <true/> +</dict> +</plist> diff --git a/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000000..f9b0d7c5ea15f --- /dev/null +++ b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>PreviewsEnabled</key> + <false/> +</dict> +</plist> diff --git a/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/xcshareddata/xcschemes/Runner.xcscheme.tmpl b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/xcshareddata/xcschemes/Runner.xcscheme.tmpl new file mode 100644 index 0000000000000..bcca935dea1c6 --- /dev/null +++ b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcodeproj.tmpl/xcshareddata/xcschemes/Runner.xcscheme.tmpl @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1430" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "97C146ED1CF9000F007C117D" + BuildableName = "Runner.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES"> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + <PathRunnable + runnableDebuggingMode = "0" + FilePath = "{{applicationBundlePath}}"> + </PathRunnable> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "97C146ED1CF9000F007C117D" + BuildableName = "Runner.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> + </BuildableReference> + </MacroExpansion> + </LaunchAction> + <ProfileAction + buildConfiguration = "Profile" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <PathRunnable + runnableDebuggingMode = "0" + FilePath = "{{applicationBundlePath}}"> + </PathRunnable> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "97C146ED1CF9000F007C117D" + BuildableName = "Runner.app" + BlueprintName = "Runner" + ReferencedContainer = "container:Runner.xcodeproj"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/contents.xcworkspacedata b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/contents.xcworkspacedata new file mode 100644 index 0000000000000..1d526a16ed0f1 --- /dev/null +++ b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/contents.xcworkspacedata @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Workspace + version = "1.0"> + <FileRef + location = "group:Runner.xcodeproj"> + </FileRef> +</Workspace> diff --git a/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/xcshareddata/IDEWorkspaceChecks.plist b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000..18d981003d68d --- /dev/null +++ b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IDEDidComputeMac32BitWarning</key> + <true/> +</dict> +</plist> diff --git a/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/xcshareddata/WorkspaceSettings.xcsettings b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000000..f9b0d7c5ea15f --- /dev/null +++ b/packages/flutter_tools/templates/xcode/ios/custom_application_bundle/Runner.xcworkspace.tmpl/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>PreviewsEnabled</key> + <false/> +</dict> +</plist> diff --git a/packages/flutter_tools/test/commands.shard/hermetic/analyze_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/analyze_test.dart index 533cca6ba1268..2ce416c9f3956 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/analyze_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/analyze_test.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; +import 'dart:convert'; + import 'package:args/command_runner.dart'; import 'package:file/file.dart'; import 'package:file/memory.dart'; @@ -118,6 +121,40 @@ void main() { FileSystem: () => fileSystem, ProcessManager: () => processManager, }); + + testUsingContext('--flutter-repo analyzes everything in the flutterRoot', () async { + final StreamController<List<int>> streamController = StreamController<List<int>>(); + final IOSink sink = IOSink(streamController.sink); + processManager.addCommands( + <FakeCommand>[ + FakeCommand( + // artifact paths are from Artifacts.test() and stable + command: const <String>[ + 'Artifact.engineDartSdkPath/bin/dart', + '--disable-dart-dev', + 'Artifact.engineDartSdkPath/bin/snapshots/analysis_server.dart.snapshot', + '--disable-server-feature-completion', + '--disable-server-feature-search', + '--sdk', + 'Artifact.engineDartSdkPath', + '--suppress-analytics', + ], + stdin: sink, + stdout: '{"event":"server.status","params":{"analysis":{"isAnalyzing":false}}}', + ), + ], + ); + await runner.run(<String>['analyze', '--flutter-repo']); + final Map<String, Object?> setAnalysisRootsCommand = jsonDecode(await streamController.stream.transform(utf8.decoder).elementAt(2)) as Map<String, Object?>; + expect(setAnalysisRootsCommand['method'], 'analysis.setAnalysisRoots'); + final Map<String, Object?> params = setAnalysisRootsCommand['params']! as Map<String, Object?>; + expect(params['included'], <String?>[Cache.flutterRoot]); + expect(params['excluded'], isEmpty); + }, + overrides: <Type, Generator>{ + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + }); }); testWithoutContext('analyze inRepo', () { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart index 9a3f6b1f2d87b..dd2de79fcb539 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/attach_test.dart @@ -105,7 +105,6 @@ void main() { testUsingContext('succeeds with iOS device with protocol discovery', () async { final FakeIOSDevice device = FakeIOSDevice( - logReader: fakeLogReader, portForwarder: portForwarder, majorSdkVersion: 12, onGetLogReader: () { @@ -167,7 +166,6 @@ void main() { testUsingContext('succeeds with iOS device with mDNS', () async { final FakeIOSDevice device = FakeIOSDevice( - logReader: fakeLogReader, portForwarder: portForwarder, majorSdkVersion: 16, onGetLogReader: () { @@ -237,7 +235,6 @@ void main() { testUsingContext('succeeds with iOS device with mDNS wireless device', () async { final FakeIOSDevice device = FakeIOSDevice( - logReader: fakeLogReader, portForwarder: portForwarder, majorSdkVersion: 16, connectionInterface: DeviceConnectionInterface.wireless, @@ -309,7 +306,6 @@ void main() { testUsingContext('succeeds with iOS device with mDNS wireless device with debug-port', () async { final FakeIOSDevice device = FakeIOSDevice( - logReader: fakeLogReader, portForwarder: portForwarder, majorSdkVersion: 16, connectionInterface: DeviceConnectionInterface.wireless, @@ -385,7 +381,6 @@ void main() { testUsingContext('succeeds with iOS device with mDNS wireless device with debug-url', () async { final FakeIOSDevice device = FakeIOSDevice( - logReader: fakeLogReader, portForwarder: portForwarder, majorSdkVersion: 16, connectionInterface: DeviceConnectionInterface.wireless, @@ -619,7 +614,6 @@ void main() { testUsingContext('succeeds when ipv6 is specified and debug-port is not on iOS device', () async { final FakeIOSDevice device = FakeIOSDevice( - logReader: fakeLogReader, portForwarder: portForwarder, majorSdkVersion: 12, onGetLogReader: () { @@ -1138,8 +1132,9 @@ class StreamLogger extends Logger { int? indent, int? hangingIndent, bool? wrap, + bool fatal = true, }) { - hadWarningOutput = true; + hadWarningOutput = hadWarningOutput || fatal; _log('[stderr] $message'); } @@ -1349,11 +1344,10 @@ class FakeAndroidDevice extends Fake implements AndroidDevice { class FakeIOSDevice extends Fake implements IOSDevice { FakeIOSDevice({ DevicePortForwarder? portForwarder, - DeviceLogReader? logReader, this.onGetLogReader, this.connectionInterface = DeviceConnectionInterface.attached, this.majorSdkVersion = 0, - }) : _portForwarder = portForwarder, _logReader = logReader; + }) : _portForwarder = portForwarder; final DevicePortForwarder? _portForwarder; @override @@ -1372,15 +1366,13 @@ class FakeIOSDevice extends Fake implements IOSDevice { @override DartDevelopmentService get dds => throw UnimplementedError('getter dds not implemented'); - final DeviceLogReader? _logReader; - DeviceLogReader get logReader => _logReader!; - final DeviceLogReader Function()? onGetLogReader; @override DeviceLogReader getLogReader({ IOSApp? app, bool includePastLogs = false, + bool usingCISystem = false, }) { if (onGetLogReader == null) { throw UnimplementedError( @@ -1468,11 +1460,9 @@ class FakeMDnsClient extends Fake implements MDnsClient { } class TestDeviceManager extends DeviceManager { - TestDeviceManager({required this.logger}) : super(logger: logger); + TestDeviceManager({required super.logger}); List<Device> devices = <Device>[]; - final BufferLogger logger; - @override List<DeviceDiscovery> get deviceDiscoverers { final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery(); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart index aaf95811246c6..83756298bbaa3 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart @@ -638,6 +638,37 @@ void main() { XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), }); + testUsingContext('Extra error message for missing simulator platform in xcresult bundle.', () async { + final BuildCommand command = BuildCommand( + androidSdk: FakeAndroidSdk(), + buildSystem: TestBuildSystem.all(BuildResult(success: true)), + fileSystem: MemoryFileSystem.test(), + logger: BufferLogger.test(), + osUtils: FakeOperatingSystemUtils(), + ); + + createMinimalMockProjectFiles(); + + await expectLater( + createTestCommandRunner(command).run(const <String>['build', 'ios', '--no-pub']), + throwsToolExit(), + ); + + expect(testLogger.errorText, contains(missingPlatformInstructions('iOS 17.0'))); + }, overrides: <Type, Generator>{ + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.list(<FakeCommand>[ + xattrCommand, + setUpFakeXcodeBuildHandler(exitCode: 1, onRun: () { + fileSystem.systemTempDirectory.childDirectory(_xcBundleFilePath).createSync(); + }), + setUpXCResultCommand(stdout: kSampleResultJsonWithActionIssues), + setUpRsyncCommand(), + ]), + Platform: () => macosPlatform, + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), + }); + testUsingContext('Delete xcresult bundle before each xcodebuild command.', () async { final BuildCommand command = BuildCommand( androidSdk: FakeAndroidSdk(), diff --git a/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart index 98f40c48f0b21..d3e86de9695e1 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/config_test.dart @@ -7,6 +7,7 @@ import 'dart:convert'; import 'package:args/command_runner.dart'; import 'package:flutter_tools/src/android/android_sdk.dart'; import 'package:flutter_tools/src/android/android_studio.dart'; +import 'package:flutter_tools/src/android/java.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; @@ -18,9 +19,11 @@ import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart'; +import '../../src/fakes.dart'; import '../../src/test_flutter_command_runner.dart'; void main() { + late Java fakeJava; late FakeAndroidStudio fakeAndroidStudio; late FakeAndroidSdk fakeAndroidSdk; late FakeFlutterVersion fakeFlutterVersion; @@ -31,6 +34,7 @@ void main() { }); setUp(() { + fakeJava = FakeJava(); fakeAndroidStudio = FakeAndroidStudio(); fakeAndroidSdk = FakeAndroidSdk(); fakeFlutterVersion = FakeFlutterVersion(); @@ -65,16 +69,15 @@ void main() { final dynamic jsonObject = json.decode(testLogger.statusText); expect(jsonObject, const TypeMatcher<Map<String, dynamic>>()); if (jsonObject is Map<String, dynamic>) { - expect(jsonObject.containsKey('android-studio-dir'), true); - expect(jsonObject['android-studio-dir'], isNotNull); - - expect(jsonObject.containsKey('android-sdk'), true); - expect(jsonObject['android-sdk'], isNotNull); + expect(jsonObject['android-studio-dir'], fakeAndroidStudio.directory); + expect(jsonObject['android-sdk'], fakeAndroidSdk.directory.path); + expect(jsonObject['jdk-dir'], fakeJava.javaHome); } verifyNoAnalytics(); }, overrides: <Type, Generator>{ AndroidStudio: () => fakeAndroidStudio, AndroidSdk: () => fakeAndroidSdk, + Java: () => fakeJava, Usage: () => testUsage, }); @@ -289,7 +292,10 @@ void main() { class FakeAndroidStudio extends Fake implements AndroidStudio, Comparable<AndroidStudio> { @override - String get directory => 'path/to/android/stdio'; + String get directory => 'path/to/android/studio'; + + @override + String? get javaPath => 'path/to/android/studio/jbr'; } class FakeAndroidSdk extends Fake implements AndroidSdk { diff --git a/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart index 82491edf819bd..088caf9e754a0 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/create_usage_test.dart @@ -19,9 +19,7 @@ import '../../src/test_flutter_command_runner.dart'; import '../../src/testbed.dart'; class FakePub extends Fake implements Pub { - FakePub(this.fs); - final FileSystem fs; int calledGetOffline = 0; int calledOnline = 0; @@ -59,7 +57,7 @@ void main() { setUp(() { testbed = Testbed(setup: () { - fakePub = FakePub(globals.fs); + fakePub = FakePub(); Cache.flutterRoot = 'flutter'; final List<String> filePaths = <String>[ globals.fs.path.join('flutter', 'packages', 'flutter', 'pubspec.yaml'), diff --git a/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart index cd569c54a7a30..9d42023235ab7 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/doctor_test.dart @@ -4,7 +4,6 @@ import 'dart:async'; -import 'package:args/command_runner.dart'; import 'package:fake_async/fake_async.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_studio_validator.dart'; @@ -16,7 +15,6 @@ import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; -import 'package:flutter_tools/src/commands/doctor.dart'; import 'package:flutter_tools/src/custom_devices/custom_device_workflow.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/doctor.dart'; @@ -32,17 +30,16 @@ import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/fakes.dart'; -import '../../src/test_flutter_command_runner.dart'; void main() { - late FakeFlutterVersion flutterVersion; late BufferLogger logger; late FakeProcessManager fakeProcessManager; + late MemoryFileSystem fs; setUp(() { - flutterVersion = FakeFlutterVersion(); logger = BufferLogger.test(); fakeProcessManager = FakeProcessManager.empty(); + fs = MemoryFileSystem.test(); }); testWithoutContext('ValidationMessage equality and hashCode includes contextUrl', () { @@ -761,27 +758,55 @@ void main() { contains(isA<CustomDeviceWorkflow>()), ); }, overrides: <Type, Generator>{ - FileSystem: () => MemoryFileSystem.test(), + FileSystem: () => fs, ProcessManager: () => fakeProcessManager, }); - testUsingContext('Fetches tags to get the right version', () async { - Cache.disableLocking(); - - final DoctorCommand doctorCommand = DoctorCommand(); - final CommandRunner<void> commandRunner = createTestCommandRunner(doctorCommand); - - await commandRunner.run(<String>['doctor']); + group('FlutterValidator', () { + late FakeFlutterVersion initialVersion; + late FakeFlutterVersion secondVersion; + late TestFeatureFlags featureFlags; - expect(flutterVersion.didFetchTagsAndUpdate, true); - Cache.enableLocking(); - }, overrides: <Type, Generator>{ - ProcessManager: () => FakeProcessManager.any(), - FileSystem: () => MemoryFileSystem.test(), - FlutterVersion: () => flutterVersion, - Doctor: () => NoOpDoctor(), - }, initializeFlutterRoot: false); + setUp(() { + secondVersion = FakeFlutterVersion(frameworkRevisionShort: '222'); + initialVersion = FakeFlutterVersion( + frameworkRevisionShort: '111', + nextFlutterVersion: secondVersion, + ); + featureFlags = TestFeatureFlags(); + }); + testUsingContext('FlutterValidator fetches tags and gets fresh version', () async { + final Directory devtoolsDir = fs.directory('/path/to/flutter/bin/cache/dart-sdk/bin/resources/devtools') + ..createSync(recursive: true); + fs.directory('/path/to/flutter/bin/cache/artifacts').createSync(recursive: true); + devtoolsDir.childFile('version.json').writeAsStringSync('{"version": "123"}'); + fakeProcessManager.addCommands(const <FakeCommand>[ + FakeCommand(command: <String>['which', 'java']), + ]); + final List<DoctorValidator> validators = DoctorValidatorsProvider.test( + featureFlags: featureFlags, + platform: FakePlatform(), + ).validators; + final FlutterValidator flutterValidator = validators.whereType<FlutterValidator>().first; + final ValidationResult result = await flutterValidator.validate(); + expect( + result.messages.map((ValidationMessage msg) => msg.message), + contains(contains('Framework revision 222')), + ); + }, overrides: <Type, Generator>{ + Cache: () => Cache.test( + rootOverride: fs.directory('/path/to/flutter'), + fileSystem: fs, + processManager: fakeProcessManager, + ), + FileSystem: () => fs, + FlutterVersion: () => initialVersion, + Platform: () => FakePlatform(), + ProcessManager: () => fakeProcessManager, + TestFeatureFlags: () => featureFlags, + }); + }); testUsingContext('If android workflow is disabled, AndroidStudio validator is not included', () { final DoctorValidatorsProvider provider = DoctorValidatorsProvider.test( featureFlags: TestFeatureFlags(isAndroidEnabled: false), @@ -806,41 +831,6 @@ class FakeAndroidWorkflow extends Fake implements AndroidWorkflow { final bool appliesToHostPlatform; } - -class NoOpDoctor implements Doctor { - @override - bool get canLaunchAnything => true; - - @override - bool get canListAnything => true; - - @override - Future<bool> checkRemoteArtifacts(String engineRevision) async => true; - - @override - Future<bool> diagnose({ - bool androidLicenses = false, - bool verbose = true, - bool showColor = true, - AndroidLicenseValidator? androidLicenseValidator, - bool showPii = true, - List<ValidatorTask>? startedValidatorTasks, - bool sendEvent = true, - }) async => true; - - @override - List<ValidatorTask> startValidatorTasks() => <ValidatorTask>[]; - - @override - Future<void> summary() async { } - - @override - List<DoctorValidator> get validators => <DoctorValidator>[]; - - @override - List<Workflow> get workflows => <Workflow>[]; -} - class PassingValidator extends DoctorValidator { PassingValidator(super.title); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart index 82d71c8061800..f3df962855f1d 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/drive_test.dart @@ -425,6 +425,7 @@ void main() { '--enable-software-rendering', '--skia-deterministic-rendering', '--enable-embedder-api', + '--ci', ]), throwsToolExit()); final DebuggingOptions options = await command.createDebuggingOptions(false); @@ -440,6 +441,7 @@ void main() { expect(options.traceSystrace, true); expect(options.enableSoftwareRendering, true); expect(options.skiaDeterministicRendering, true); + expect(options.usingCISystem, true); }, overrides: <Type, Generator>{ Cache: () => Cache.test(processManager: FakeProcessManager.any()), FileSystem: () => MemoryFileSystem.test(), diff --git a/packages/flutter_tools/test/commands.shard/hermetic/generate_localizations_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/generate_localizations_test.dart index a1ac916a80e93..552dd37470a8b 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/generate_localizations_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/generate_localizations_test.dart @@ -6,6 +6,8 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/build_system/build_system.dart'; +import 'package:flutter_tools/src/build_system/targets/localizations.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/generate_localizations.dart'; @@ -324,6 +326,50 @@ untranslated-messages-file: lib/l10n/untranslated.json ProcessManager: () => FakeProcessManager.any(), }); + // Regression test for https://github.com/flutter/flutter/issues/120530. + testWithoutContext('dart format is run when generateLocalizations is called through build target', () async { + final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb')) + ..createSync(recursive: true); + arbFile.writeAsStringSync(''' +{ + "helloWorld": "Hello, World!", + "@helloWorld": { + "description": "Sample description" + } +}'''); + final File configFile = fileSystem.file('l10n.yaml')..createSync(); + configFile.writeAsStringSync(''' +format: true +'''); + const Target buildTarget = GenerateLocalizationsTarget(); + final File pubspecFile = fileSystem.file('pubspec.yaml')..createSync(); + pubspecFile.writeAsStringSync(BasicProjectWithFlutterGen().pubspec); + processManager.addCommand( + const FakeCommand( + command: <String>[ + 'Artifact.engineDartBinary', + 'format', + '/.dart_tool/flutter_gen/gen_l10n/app_localizations_en.dart', + '/.dart_tool/flutter_gen/gen_l10n/app_localizations.dart', + ] + ) + ); + final Environment environment = Environment.test( + fileSystem.currentDirectory, + artifacts: artifacts, + processManager: processManager, + fileSystem: fileSystem, + logger: BufferLogger.test(), + ); + await buildTarget.build(environment); + + final Directory outputDirectory = fileSystem.directory(fileSystem.path.join('.dart_tool', 'flutter_gen', 'gen_l10n')); + expect(outputDirectory.existsSync(), true); + expect(outputDirectory.childFile('app_localizations_en.dart').existsSync(), true); + expect(outputDirectory.childFile('app_localizations.dart').existsSync(), true); + expect(processManager, hasNoRemainingExpectations); + }); + testUsingContext('nullable-getter defaults to true', () async { final File arbFile = fileSystem.file(fileSystem.path.join('lib', 'l10n', 'app_en.arb')) ..createSync(recursive: true); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart index 2ffedf4927357..ca7564e59e9b6 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/run_test.dart @@ -1096,6 +1096,7 @@ void main() { '--enable-software-rendering', '--skia-deterministic-rendering', '--enable-embedder-api', + '--ci', ]), throwsToolExit()); final DebuggingOptions options = await command.createDebuggingOptions(false); @@ -1114,6 +1115,7 @@ void main() { expect(options.impellerForceGL, true); expect(options.enableSoftwareRendering, true); expect(options.skiaDeterministicRendering, true); + expect(options.usingCISystem, true); }, overrides: <Type, Generator>{ Cache: () => Cache.test(processManager: FakeProcessManager.any()), FileSystem: () => MemoryFileSystem.test(), @@ -1146,11 +1148,9 @@ void main() { } class TestDeviceManager extends DeviceManager { - TestDeviceManager({required this.logger}) : super(logger: logger); + TestDeviceManager({required super.logger}); List<Device> devices = <Device>[]; - final Logger logger; - @override List<DeviceDiscovery> get deviceDiscoverers { final FakePollingDeviceDiscovery discoverer = FakePollingDeviceDiscovery(); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/symbolize_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/symbolize_test.dart index fb5d1810db604..44096a775ec8d 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/symbolize_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/symbolize_test.dart @@ -39,7 +39,7 @@ void main() { unawaited(symbolizationService.decode( input: Stream<Uint8List>.fromIterable(<Uint8List>[ - utf8.encode('Hello, World\n') as Uint8List, + const Utf8Encoder().convert('Hello, World\n'), ]), symbols: Uint8List(0), output: IOSink(output.sink), diff --git a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart index 764695d17e9ed..2c8d81e6e47aa 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/test_test.dart @@ -743,7 +743,7 @@ dev_dependencies: '--no-pub', ]); - final bool fileExists = await fs.isFile(globals.fs.path.join('build', 'unit_test_assets', 'AssetManifest.json')); + final bool fileExists = await fs.isFile(globals.fs.path.join('build', 'unit_test_assets', 'AssetManifest.bin')); expect(fileExists, true); }, overrides: <Type, Generator>{ @@ -764,7 +764,7 @@ dev_dependencies: '--no-test-assets', ]); - final bool fileExists = await fs.isFile(globals.fs.path.join('build', 'unit_test_assets', 'AssetManifest.json')); + final bool fileExists = await fs.isFile(globals.fs.path.join('build', 'unit_test_assets', 'AssetManifest.bin')); expect(fileExists, false); }, overrides: <Type, Generator>{ diff --git a/packages/flutter_tools/test/commands.shard/hermetic/update_packages_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/update_packages_test.dart index 971afe76fe16f..7e75ca9bfbb0a 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/update_packages_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/update_packages_test.dart @@ -197,6 +197,73 @@ void main() { Logger: () => logger, }); + testUsingContext('--cherry-pick-package', () async { + final UpdatePackagesCommand command = UpdatePackagesCommand(); + await createTestCommandRunner(command).run(<String>[ + 'update-packages', + '--cherry-pick-package=vector_math', + '--cherry-pick-version=2.0.9', + ]); + expect(pub.pubGetDirectories, equals(<String>[ + '/.tmp_rand0/flutter_update_packages.rand0/synthetic_package', + '/flutter/examples', + '/flutter/packages/flutter', + ])); + expect(pub.pubBatchDirectories, equals(<String>[ + '/.tmp_rand0/flutter_update_packages.rand0/synthetic_package', + ])); + expect(pub.pubspecYamls, hasLength(3)); + final String output = pub.pubspecYamls.first; + expect(output, isNotNull); + expect(output, contains('collection: 1.14.11\n')); + expect(output, contains('meta: 1.1.8\n')); + expect(output, contains('typed_data: 1.1.6\n')); + expect(output, contains('vector_math: 2.0.9\n')); + expect(output, isNot(contains('vector_math: 2.0.8'))); + expect(output, isNot(contains('vector_math: ">= 2.0.8"'))); + expect(output, isNot(contains("vector_math: '>= 2.0.8'"))); + }, overrides: <Type, Generator>{ + Pub: () => pub, + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + Cache: () => Cache.test( + processManager: processManager, + ), + Logger: () => logger, + }); + + testUsingContext('--force-upgrade', () async { + final UpdatePackagesCommand command = UpdatePackagesCommand(); + await createTestCommandRunner(command).run(<String>[ + 'update-packages', + '--force-upgrade', + ]); + expect(pub.pubGetDirectories, equals(<String>[ + '/.tmp_rand0/flutter_update_packages.rand0/synthetic_package', + '/flutter/examples', + '/flutter/packages/flutter', + ])); + expect(pub.pubBatchDirectories, equals(<String>[ + '/.tmp_rand0/flutter_update_packages.rand0/synthetic_package', + ])); + expect(pub.pubspecYamls, hasLength(3)); + final String output = pub.pubspecYamls.first; + expect(output, isNotNull); + expect(output, contains("collection: '>= 1.14.11'\n")); + expect(output, contains("meta: '>= 1.1.8'\n")); + expect(output, contains("typed_data: '>= 1.1.6'\n")); + expect(output, contains("vector_math: '>= 2.0.8'\n")); + expect(output, isNot(contains('vector_math: 2.0.8'))); + }, overrides: <Type, Generator>{ + Pub: () => pub, + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + Cache: () => Cache.test( + processManager: processManager, + ), + Logger: () => logger, + }); + testUsingContext('force updates packages --synthetic-package-path', () async { final UpdatePackagesCommand command = UpdatePackagesCommand(); const String dir = '/path/to/synthetic/package'; @@ -272,6 +339,7 @@ class FakePub extends Fake implements Pub { final FileSystem fileSystem; final List<String> pubGetDirectories = <String>[]; final List<String> pubBatchDirectories = <String>[]; + final List<String> pubspecYamls = <String>[]; @override Future<void> get({ @@ -287,6 +355,7 @@ class FakePub extends Fake implements Pub { PubOutputMode outputMode = PubOutputMode.all, }) async { pubGetDirectories.add(project.directory.path); + pubspecYamls.add(project.directory.childFile('pubspec.yaml').readAsStringSync()); project.directory.childFile('pubspec.lock') ..createSync(recursive: true) ..writeAsStringSync(''' diff --git a/packages/flutter_tools/test/commands.shard/hermetic/upgrade_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/upgrade_test.dart index 2a1db24d57a75..3b7c116fc6eae 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/upgrade_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/upgrade_test.dart @@ -23,9 +23,11 @@ void main() { late FakeProcessManager processManager; UpgradeCommand command; late CommandRunner<void> runner; + const String flutterRoot = '/path/to/flutter'; setUpAll(() { Cache.disableLocking(); + Cache.flutterRoot = flutterRoot; }); setUp(() { @@ -214,28 +216,35 @@ void main() { const FakeCommand( command: <String>['git', 'tag', '--points-at', 'HEAD'], stdout: startingTag, + workingDirectory: flutterRoot, ), const FakeCommand( command: <String>['git', 'fetch', '--tags'], + workingDirectory: flutterRoot, ), const FakeCommand( command: <String>['git', 'rev-parse', '--verify', '@{upstream}'], stdout: upstreamHeadRevision, + workingDirectory: flutterRoot, ), const FakeCommand( command: <String>['git', 'tag', '--points-at', upstreamHeadRevision], stdout: latestUpstreamTag, + workingDirectory: flutterRoot, ), const FakeCommand( command: <String>['git', 'status', '-s'], + workingDirectory: flutterRoot, ), const FakeCommand( command: <String>['git', 'reset', '--hard', upstreamHeadRevision], + workingDirectory: flutterRoot, ), FakeCommand( command: const <String>['bin/flutter', 'upgrade', '--continue', '--no-version-check'], onRun: reEnterTool, completer: reEntryCompleter, + workingDirectory: flutterRoot, ), // commands following this are from the re-entrant `flutter upgrade --continue` call @@ -243,12 +252,15 @@ void main() { const FakeCommand( command: <String>['git', 'tag', '--points-at', 'HEAD'], stdout: latestUpstreamTag, + workingDirectory: flutterRoot, ), const FakeCommand( command: <String>['bin/flutter', '--no-color', '--no-version-check', 'precache'], + workingDirectory: flutterRoot, ), const FakeCommand( command: <String>['bin/flutter', '--no-version-check', 'doctor'], + workingDirectory: flutterRoot, ), ]); await runner.run(<String>['upgrade']); diff --git a/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart b/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart index db99b2b75ef92..c4a501897f551 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/build_bundle_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:typed_data'; + import 'package:args/command_runner.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/file_system.dart'; @@ -12,6 +14,7 @@ import 'package:flutter_tools/src/bundle.dart'; import 'package:flutter_tools/src/bundle_builder.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/build_bundle.dart'; +import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/project.dart'; @@ -504,7 +507,7 @@ void main() { ProcessManager: () => FakeProcessManager.any(), }); - testUsingContext('test --dart-define-from-file option', () async { + testUsingContext('--dart-define-from-file successfully forwards values to build env', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); @@ -514,7 +517,9 @@ void main() { "kInt": 1, "kDouble": 1.1, "name": "denghaizhu", - "title": "this is title from config json file" + "title": "this is title from config json file", + "nullValue": null, + "containEqual": "sfadsfv=432f" } ''' ); @@ -537,12 +542,172 @@ void main() { ]); }, overrides: <Type, Generator>{ BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { - expect(environment.defines[kDartDefines], 'a0ludD0x,a0RvdWJsZT0xLjE=,bmFtZT1kZW5naGFpemh1,dGl0bGU9dGhpcyBpcyB0aXRsZSBmcm9tIGNvbmZpZyBqc29uIGZpbGU=,Ym9keT10aGlzIGlzIGJvZHkgZnJvbSBjb25maWcganNvbiBmaWxl'); + expect( + _decodeDartDefines(environment), + containsAllInOrder(const <String>[ + 'kInt=1', + 'kDouble=1.1', + 'name=denghaizhu', + 'title=this is title from config json file', + 'nullValue=null', + 'containEqual=sfadsfv=432f', + 'body=this is body from config json file', + ]), + ); }), FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), }); + testUsingContext('--dart-define-from-file correctly parses a valid env file', () async { + globals.fs + .file(globals.fs.path.join('lib', 'main.dart')) + .createSync(recursive: true); + globals.fs.file('pubspec.yaml').createSync(); + globals.fs.file('.packages').createSync(); + await globals.fs.file('.env').writeAsString(''' + # comment + kInt=1 + kDouble=1.1 # should be double + + name=piotrfleury + title=this is title from config env file + empty= + + doubleQuotes="double quotes 'value'#=" # double quotes + singleQuotes='single quotes "value"#=' # single quotes + backQuotes=`back quotes "value" '#=` # back quotes + + hashString="some-#-hash-string-value" + + # Play around with spaces around the equals sign. + spaceBeforeEqual =value + spaceAroundEqual = value + spaceAfterEqual= value + + '''); + await globals.fs.file('.env2').writeAsString(''' + # second comment + + body=this is body from config env file + '''); + final CommandRunner<void> runner = + createTestCommandRunner(BuildBundleCommand( + logger: BufferLogger.test(), + )); + + await runner.run(<String>[ + 'bundle', + '--no-pub', + '--dart-define-from-file=.env', + '--dart-define-from-file=.env2', + ]); + }, overrides: <Type, Generator>{ + BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), + (Target target, Environment environment) { + expect( + _decodeDartDefines(environment), + containsAllInOrder(const <String>[ + 'kInt=1', + 'kDouble=1.1', + 'name=piotrfleury', + 'title=this is title from config env file', + 'empty=', + "doubleQuotes=double quotes 'value'#=", + 'singleQuotes=single quotes "value"#=', + 'backQuotes=back quotes "value" \'#=', + 'hashString=some-#-hash-string-value', + 'spaceBeforeEqual=value', + 'spaceAroundEqual=value', + 'spaceAfterEqual=value', + 'body=this is body from config env file' + ]), + ); + }), + FileSystem: fsFactory, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('--dart-define-from-file option env file throws a ToolExit when .env file contains a multiline value', () async { + globals.fs + .file(globals.fs.path.join('lib', 'main.dart')) + .createSync(recursive: true); + globals.fs.file('pubspec.yaml').createSync(); + globals.fs.file('.packages').createSync(); + await globals.fs.file('.env').writeAsString(''' + # single line value + name=piotrfleury + + # multi-line value + multiline = """ Welcome to .env demo + a simple counter app with .env file support + for more info, check out the README.md file + Thanks! """ # This is the welcome message that will be displayed on the counter app + + '''); + final CommandRunner<void> runner = + createTestCommandRunner(BuildBundleCommand( + logger: BufferLogger.test(), + )); + + expect(() => runner.run(<String>[ + 'bundle', + '--no-pub', + '--dart-define-from-file=.env', + ]), throwsToolExit(message: 'Multi-line value is not supported: multiline = """ Welcome to .env demo')); + }, overrides: <Type, Generator>{ + BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)), + FileSystem: fsFactory, + ProcessManager: () => FakeProcessManager.any(), + }); + + testUsingContext('--dart-define-from-file option works with mixed file formats', + () async { + globals.fs + .file(globals.fs.path.join('lib', 'main.dart')) + .createSync(recursive: true); + globals.fs.file('pubspec.yaml').createSync(); + globals.fs.file('.packages').createSync(); + await globals.fs.file('.env').writeAsString(''' + kInt=1 + kDouble=1.1 + name=piotrfleury + title=this is title from config env file + '''); + await globals.fs.file('config.json').writeAsString(''' + { + "body": "this is body from config json file" + } + '''); + final CommandRunner<void> runner = + createTestCommandRunner(BuildBundleCommand( + logger: BufferLogger.test(), + )); + + await runner.run(<String>[ + 'bundle', + '--no-pub', + '--dart-define-from-file=.env', + '--dart-define-from-file=config.json', + ]); + }, overrides: <Type, Generator>{ + BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), + (Target target, Environment environment) { + expect( + _decodeDartDefines(environment), + containsAllInOrder(const <String>[ + 'kInt=1', + 'kDouble=1.1', + 'name=piotrfleury', + 'title=this is title from config env file', + 'body=this is body from config json file', + ]), + ); + }), + FileSystem: fsFactory, + ProcessManager: () => FakeProcessManager.any(), + }); + testUsingContext('test --dart-define-from-file option if conflict', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); @@ -576,7 +741,10 @@ void main() { ]); }, overrides: <Type, Generator>{ BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { - expect(environment.defines[kDartDefines], 'a0ludD0y,a0RvdWJsZT0xLjE=,bmFtZT1kZW5naGFpemh1,dGl0bGU9dGhpcyBpcyB0aXRsZSBmcm9tIGNvbmZpZyBqc29uIGZpbGU='); + expect( + _decodeDartDefines(environment), + containsAllInOrder(<String>['kInt=2', 'kDouble=1.1', 'name=denghaizhu', 'title=this is title from config json file']), + ); }), FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), @@ -632,6 +800,15 @@ void main() { }); } +Iterable<String> _decodeDartDefines(Environment environment) { + final String encodedDefines = environment.defines[kDartDefines]!; + const Utf8Decoder byteDecoder = Utf8Decoder(); + return encodedDefines + .split(',') + .map<Uint8List>(base64.decode) + .map<String>(byteDecoder.convert); +} + class FakeBundleBuilder extends Fake implements BundleBuilder { @override Future<void> build({ diff --git a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart index bddf42d0b3623..cc1c8bc566be0 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/upgrade_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/platform.dart'; @@ -36,7 +37,8 @@ void main() { setUp(() { fakeCommandRunner = FakeUpgradeCommandRunner(); - realCommandRunner = UpgradeCommandRunner(); + realCommandRunner = UpgradeCommandRunner() + ..workingDirectory = getFlutterRoot(); processManager = FakeProcessManager.empty(); fakeCommandRunner.willHaveUncommittedChanges = false; fakePlatform = FakePlatform()..environment = Map<String, String>.unmodifiable(<String, String>{ @@ -327,7 +329,7 @@ void main() { environment: <String, String>{'FLUTTER_ALREADY_LOCKED': 'true', ...fakePlatform.environment} ), ); - await realCommandRunner.precacheArtifacts(); + await precacheArtifacts(); expect(processManager, hasNoRemainingExpectations); }, overrides: <Type, Generator>{ ProcessManager: () => processManager, @@ -408,6 +410,7 @@ void main() { late FakeProcessManager fakeProcessManager; late Directory tempDir; late File flutterToolState; + late FileSystem fs; setUp(() { Cache.disableLocking(); @@ -424,7 +427,8 @@ void main() { stdout: 'v1.12.16-19-gb45b676af', ), ]); - tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_upgrade_test.'); + fs = MemoryFileSystem.test(); + tempDir = fs.systemTempDirectory.createTempSync('flutter_upgrade_test.'); flutterToolState = tempDir.childFile('.flutter_tool_state'); }); @@ -434,6 +438,7 @@ void main() { }); testUsingContext('upgrade continue prints welcome message', () async { + fakeProcessManager = FakeProcessManager.any(); final UpgradeCommand upgradeCommand = UpgradeCommand( verboseHelp: false, commandRunner: fakeCommandRunner, @@ -451,6 +456,7 @@ void main() { containsPair('redisplay-welcome-message', true), ); }, overrides: <Type, Generator>{ + FileSystem: () => fs, FlutterVersion: () => FakeFlutterVersion(), ProcessManager: () => fakeProcessManager, PersistentToolState: () => PersistentToolState.test( @@ -479,9 +485,6 @@ class FakeUpgradeCommandRunner extends UpgradeCommandRunner { @override Future<void> attemptReset(String newRevision) async {} - @override - Future<void> precacheArtifacts() async {} - @override Future<void> updatePackages(FlutterVersion flutterVersion) async {} diff --git a/packages/flutter_tools/test/general.shard/analytics_test.dart b/packages/flutter_tools/test/general.shard/analytics_test.dart index b8bfd57a47c7a..c6cdf97745b98 100644 --- a/packages/flutter_tools/test/general.shard/analytics_test.dart +++ b/packages/flutter_tools/test/general.shard/analytics_test.dart @@ -41,11 +41,14 @@ void main() { group('analytics', () { late Directory tempDir; late Config testConfig; + late FileSystem fs; + const String flutterRoot = '/path/to/flutter'; setUp(() { - Cache.flutterRoot = '../..'; + Cache.flutterRoot = flutterRoot; tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_analytics_test.'); testConfig = Config.test(); + fs = MemoryFileSystem.test(); }); tearDown(() { @@ -77,7 +80,7 @@ void main() { expect(count, 0); }, overrides: <Type, Generator>{ - FlutterVersion: () => FlutterVersion(), + FlutterVersion: () => FakeFlutterVersion(), Usage: () => Usage( configDirOverride: tempDir.path, logFile: tempDir.childFile('analytics.log').path, @@ -101,7 +104,7 @@ void main() { expect(count, 0); }, overrides: <Type, Generator>{ - FlutterVersion: () => FlutterVersion(), + FlutterVersion: () => FakeFlutterVersion(), Usage: () => Usage( configDirOverride: tempDir.path, logFile: tempDir.childFile('analytics.log').path, @@ -118,12 +121,12 @@ void main() { expect(globals.fs.file('test').readAsStringSync(), contains('$featuresKey: enable-web')); }, overrides: <Type, Generator>{ - FlutterVersion: () => FlutterVersion(), + FlutterVersion: () => FakeFlutterVersion(), Config: () => testConfig, Platform: () => FakePlatform(environment: <String, String>{ 'FLUTTER_ANALYTICS_LOG_FILE': 'test', }), - FileSystem: () => MemoryFileSystem.test(), + FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), }); @@ -141,12 +144,12 @@ void main() { contains('$featuresKey: enable-web,enable-linux-desktop,enable-macos-desktop'), ); }, overrides: <Type, Generator>{ - FlutterVersion: () => FlutterVersion(), + FlutterVersion: () => FakeFlutterVersion(), Config: () => testConfig, Platform: () => FakePlatform(environment: <String, String>{ 'FLUTTER_ANALYTICS_LOG_FILE': 'test', }), - FileSystem: () => MemoryFileSystem.test(), + FileSystem: () => fs, ProcessManager: () => FakeProcessManager.any(), }); }); @@ -384,6 +387,7 @@ class FakeDoctor extends Fake implements Doctor { bool showPii = true, List<ValidatorTask>? startedValidatorTasks, bool sendEvent = true, + FlutterVersion? version, }) async { return diagnoseSucceeds; } diff --git a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart index 38b19f74f62d7..b989d371f4615 100644 --- a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart @@ -46,6 +46,7 @@ void main() { testUsingContext('Can immediately tool exit on recognized exit code/stderr', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -138,6 +139,7 @@ void main() { testUsingContext('Verbose mode for APKs includes Gradle stacktrace and sets debug log level', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: BufferLogger.test(verbose: true), processManager: processManager, fileSystem: fileSystem, @@ -205,6 +207,7 @@ void main() { testUsingContext('Can retry build on recognized exit code/stderr', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -310,6 +313,7 @@ void main() { testUsingContext('Converts recognized ProcessExceptions into tools exits', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -402,6 +406,7 @@ void main() { testUsingContext('rethrows unrecognized ProcessException', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -466,6 +471,7 @@ void main() { testUsingContext('logs success event after a successful retry', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -569,6 +575,7 @@ void main() { testUsingContext('performs code size analysis and sends analytics', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -670,6 +677,7 @@ void main() { testUsingContext('indicates that an APK has been built successfully', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -797,6 +805,7 @@ android { testUsingContext('can call custom gradle task getBuildOptions and parse the result', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -831,6 +840,7 @@ BuildVariant: paidProfile testUsingContext('getBuildOptions returns empty list if gradle returns error', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -859,8 +869,136 @@ Gradle Crashed AndroidStudio: () => FakeAndroidStudio(), }); + testUsingContext('can call custom gradle task getApplicationIdForVariant and parse the result', () async { + final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), + logger: logger, + processManager: processManager, + fileSystem: fileSystem, + artifacts: Artifacts.test(), + usage: testUsage, + gradleUtils: FakeGradleUtils(), + platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), + ); + processManager.addCommand(const FakeCommand( + command: <String>[ + 'gradlew', + '-q', + 'printFreeDebugApplicationId', + ], + stdout: ''' +ApplicationId: com.example.id + ''', + )); + final String actual = await builder.getApplicationIdForVariant( + 'freeDebug', + project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), + ); + expect(actual, 'com.example.id'); + }, overrides: <Type, Generator>{ + AndroidStudio: () => FakeAndroidStudio(), + }); + + testUsingContext('can call custom gradle task getApplicationIdForVariant with unknown crash', () async { + final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), + logger: logger, + processManager: processManager, + fileSystem: fileSystem, + artifacts: Artifacts.test(), + usage: testUsage, + gradleUtils: FakeGradleUtils(), + platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), + ); + processManager.addCommand(const FakeCommand( + command: <String>[ + 'gradlew', + '-q', + 'printFreeDebugApplicationId', + ], + stdout: ''' +unknown crash + ''', + )); + final String actual = await builder.getApplicationIdForVariant( + 'freeDebug', + project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), + ); + expect(actual, ''); + }, overrides: <Type, Generator>{ + AndroidStudio: () => FakeAndroidStudio(), + }); + + testUsingContext('can call custom gradle task getAppLinkDomainsForVariant and parse the result', () async { + final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), + logger: logger, + processManager: processManager, + fileSystem: fileSystem, + artifacts: Artifacts.test(), + usage: testUsage, + gradleUtils: FakeGradleUtils(), + platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), + ); + + processManager.addCommand(const FakeCommand( + command: <String>[ + 'gradlew', + '-q', + 'printFreeDebugAppLinkDomains', + ], + stdout: ''' +Domain: example.com +Domain: example2.com + ''', + )); + final List<String> actual = await builder.getAppLinkDomainsForVariant( + 'freeDebug', + project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), + ); + expect(actual, <String>['example.com', 'example2.com']); + }, overrides: <Type, Generator>{ + AndroidStudio: () => FakeAndroidStudio(), + }); + + testUsingContext('can call custom gradle task getAppLinkDomainsForVariant with unknown crash', () async { + final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), + logger: logger, + processManager: processManager, + fileSystem: fileSystem, + artifacts: Artifacts.test(), + usage: testUsage, + gradleUtils: FakeGradleUtils(), + platform: FakePlatform(), + androidStudio: FakeAndroidStudio(), + ); + + processManager.addCommand(const FakeCommand( + command: <String>[ + 'gradlew', + '-q', + 'printFreeDebugAppLinkDomains', + ], + stdout: ''' +unknown crash + ''', + )); + final List<String> actual = await builder.getAppLinkDomainsForVariant( + 'freeDebug', + project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), + ); + expect(actual.isEmpty, isTrue); + }, overrides: <Type, Generator>{ + AndroidStudio: () => FakeAndroidStudio(), + }); + testUsingContext("doesn't indicate how to consume an AAR when printHowToConsumeAar is false", () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -926,6 +1064,7 @@ Gradle Crashed testUsingContext('Verbose mode for AARs includes Gradle stacktrace and sets debug log level', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: BufferLogger.test(verbose: true), processManager: processManager, fileSystem: fileSystem, @@ -984,6 +1123,7 @@ Gradle Crashed testUsingContext('gradle exit code and stderr is forwarded to tool exit', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -1043,6 +1183,7 @@ Gradle Crashed testUsingContext('build apk uses selected local engine with arm32 ABI', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -1121,6 +1262,7 @@ Gradle Crashed testUsingContext('build apk uses selected local engine with arm64 ABI', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -1199,6 +1341,7 @@ Gradle Crashed testUsingContext('build apk uses selected local engine with x86 ABI', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -1277,6 +1420,7 @@ Gradle Crashed testUsingContext('build apk uses selected local engine with x64 ABI', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -1356,6 +1500,7 @@ Gradle Crashed testUsingContext('honors --no-android-gradle-daemon setting', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -1416,6 +1561,7 @@ Gradle Crashed testUsingContext('build aar uses selected local engine with arm32 ABI', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -1503,6 +1649,7 @@ Gradle Crashed testUsingContext('build aar uses selected local engine with x64 ABI', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -1590,6 +1737,7 @@ Gradle Crashed testUsingContext('build aar uses selected local engine with x86 ABI', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -1677,6 +1825,7 @@ Gradle Crashed testUsingContext('build aar uses selected local engine on x64 ABI', () async { final AndroidGradleBuilder builder = AndroidGradleBuilder( + java: FakeJava(), logger: logger, processManager: processManager, fileSystem: fileSystem, @@ -1773,5 +1922,5 @@ class FakeGradleUtils extends Fake implements GradleUtils { class FakeAndroidStudio extends Fake implements AndroidStudio { @override - String get javaPath => 'java'; + String get javaPath => '/android-studio/jbr'; } diff --git a/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart b/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart index c83a2b12a8629..8722774497cce 100644 --- a/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_project_migration_test.dart @@ -6,13 +6,9 @@ import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_studio.dart'; import 'package:flutter_tools/src/android/gradle_utils.dart'; -import 'package:flutter_tools/src/android/java.dart'; import 'package:flutter_tools/src/android/migrations/android_studio_java_gradle_conflict_migration.dart'; import 'package:flutter_tools/src/android/migrations/top_level_gradle_build_file_migration.dart'; import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/base/os.dart'; -import 'package:flutter_tools/src/base/platform.dart'; -import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:test/fake.dart'; @@ -47,8 +43,8 @@ zipStorePath=wrapper/dists final Version androidStudioDolphin = Version(2021, 3, 1); -final JavaVersion _javaVersion17 = JavaVersion(longText: 'openjdk 17.0.2', number: '17.0.2'); -final JavaVersion _javaVersion16 = JavaVersion(longText: 'openjdk 16.0.2', number: '16.0.2'); +const Version _javaVersion17 = Version.withText(17, 0, 2, 'openjdk 17.0.2'); +const Version _javaVersion16 = Version.withText(16, 0, 2, 'openjdk 16.0.2'); void main() { group('Android migration', () { @@ -284,12 +280,7 @@ class FakeAndroidStudio extends Fake implements AndroidStudio { class FakeErroringJava extends FakeJava { @override - JavaVersion get version { + Version get version { throw Exception('How did this happen?'); } } - -class FakeFileSystem extends Fake implements FileSystem {} -class FakeProcessUtils extends Fake implements ProcessUtils {} -class FakePlatform extends Fake implements Platform {} -class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {} diff --git a/packages/flutter_tools/test/general.shard/android/android_sdk_test.dart b/packages/flutter_tools/test/general.shard/android/android_sdk_test.dart index a82e62d32ba4f..9e7ed95ea2396 100644 --- a/packages/flutter_tools/test/general.shard/android/android_sdk_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_sdk_test.dart @@ -4,14 +4,10 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_sdk.dart'; -import 'package:flutter_tools/src/android/android_studio.dart'; -import 'package:flutter_tools/src/android/java.dart'; import 'package:flutter_tools/src/base/config.dart'; import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -345,99 +341,6 @@ void main() { Platform: () => FakePlatform(operatingSystem: 'windows'), Config: () => config, }); - - group('findJavaBinary', () { - testUsingContext('returns the path of the JDK bundled with Android Studio, if it exists', () { - final String androidStudioBundledJdkHome = globals.androidStudio!.javaPath!; - final String expectedJavaBinaryPath = globals.fs.path.join(androidStudioBundledJdkHome, 'bin', 'java'); - - final String? foundJavaBinaryPath = Java.find( - logger: globals.logger, - androidStudio: globals.androidStudio, - fileSystem: globals.fs, - platform: globals.platform, - processManager: globals.processManager, - )?.binaryPath; - - expect(foundJavaBinaryPath, expectedJavaBinaryPath); - }, overrides: <Type, Generator>{ - FileSystem: () => MemoryFileSystem.test(), - ProcessManager: () => FakeProcessManager.any(), - Platform: () => FakePlatform(), - Config: () => Config, - AndroidStudio: () => FakeAndroidStudioWithJdk(), - }); - - testUsingContext('returns the current value of JAVA_HOME if it is set and the JDK bundled with Android Studio could not be found', () { - final String expectedJavaBinaryPath = globals.fs.path.join('java-home-path', 'bin', 'java'); - - final String? foundJavaBinaryPath = Java.find( - logger: globals.logger, - androidStudio: globals.androidStudio, - fileSystem: globals.fs, - platform: globals.platform, - processManager: globals.processManager, - )?.binaryPath; - - expect(foundJavaBinaryPath, expectedJavaBinaryPath); - }, overrides: <Type, Generator>{ - FileSystem: () => MemoryFileSystem.test(), - ProcessManager: () => FakeProcessManager.empty(), - Platform: () => FakePlatform(environment: <String, String>{ - AndroidSdk.javaHomeEnvironmentVariable: 'java-home-path', - }), - Config: () => Config, - AndroidStudio: () => FakeAndroidStudioWithoutJdk(), - }); - - testUsingContext('returns the java binary found on PATH if no other can be found', () { - final String? foundJavaBinaryPath = Java.find( - logger: globals.logger, - androidStudio: globals.androidStudio, - fileSystem: globals.fs, - platform: globals.platform, - processManager: globals.processManager, - )?.binaryPath; - - expect(foundJavaBinaryPath, 'java'); - }, overrides: <Type, Generator>{ - Logger: () => BufferLogger.test(), - FileSystem: () => MemoryFileSystem.test(), - ProcessManager: () => FakeProcessManager.list(<FakeCommand>[ - const FakeCommand( - command: <String>['which', 'java'], - stdout: 'java', - ), - ]), - Platform: () => FakePlatform(), - Config: () => Config, - AndroidStudio: () => FakeAndroidStudioWithoutJdk(), - }); - - testUsingContext('returns null if no java binary could be found', () { - final String? foundJavaBinaryPath = Java.find( - logger: globals.logger, - androidStudio: globals.androidStudio, - fileSystem: globals.fs, - platform: globals.platform, - processManager: globals.processManager, - )?.binaryPath; - - expect(foundJavaBinaryPath, null); - }, overrides: <Type, Generator>{ - Logger: () => BufferLogger.test(), - FileSystem: () => MemoryFileSystem.test(), - ProcessManager: () => FakeProcessManager.list(<FakeCommand>[ - const FakeCommand( - command: <String>['which', 'java'], - exitCode: 1, - ), - ]), - Platform: () => FakePlatform(), - Config: () => Config, - AndroidStudio: () => FakeAndroidStudioWithoutJdk(), - }); - }); }); } @@ -519,13 +422,3 @@ ro.build.version.incremental=1624448 ro.build.version.sdk=24 ro.build.version.codename=REL '''; - -class FakeAndroidStudioWithJdk extends Fake implements AndroidStudio { - @override - String? get javaPath => '/fake/android_studio/java/path/'; -} - -class FakeAndroidStudioWithoutJdk extends Fake implements AndroidStudio { - @override - String? get javaPath => null; -} diff --git a/packages/flutter_tools/test/general.shard/android/android_studio_test.dart b/packages/flutter_tools/test/general.shard/android/android_studio_test.dart index ee018bcc363ed..cc8d8c2f36481 100644 --- a/packages/flutter_tools/test/general.shard/android/android_studio_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_studio_test.dart @@ -11,6 +11,7 @@ import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/ios/plist_parser.dart'; +import 'package:path/path.dart' show Context; // flutter_ignore: package_path_import -- We only use Context as an interface. import 'package:test/fake.dart'; import '../../src/common.dart'; @@ -1287,6 +1288,20 @@ void main() { Platform: () => platform, ProcessManager: () => FakeProcessManager.any(), }); + + testUsingContext('handles file system exception when checking for explicitly configured Android Studio install', () { + const String androidStudioDir = '/Users/Dash/Desktop/android-studio'; + config.setValue('android-studio-dir', androidStudioDir); + + expect(() => AndroidStudio.latestValid(), + throwsToolExit(message: RegExp(r'[.\s\S]*Could not find[.\s\S]*FileSystemException[.\s\S]*'))); + }, overrides: <Type, Generator>{ + Config: () => config, + Platform: () => platform, + FileSystem: () => _FakeFileSystem(), + FileSystemUtils: () => _FakeFsUtils(), + ProcessManager: () => FakeProcessManager.any(), + }); }); } @@ -1298,3 +1313,33 @@ class FakePlistUtils extends Fake implements PlistParser { return fileContents[plistFilePath]!; } } + +class _FakeFileSystem extends Fake implements FileSystem { + @override + Directory directory(dynamic path) { + return _NonExistentDirectory(); + } + + @override + Context get path { + return MemoryFileSystem.test().path; + } +} + +class _NonExistentDirectory extends Fake implements Directory { + @override + bool existsSync() { + throw const FileSystemException('OS Error: Filename, directory name, or volume label syntax is incorrect.'); + } + + @override + String get path => ''; + + @override + Directory get parent => _NonExistentDirectory(); +} + +class _FakeFsUtils extends Fake implements FileSystemUtils { + @override + String get homeDirPath => '/home/'; +} diff --git a/packages/flutter_tools/test/general.shard/android/android_studio_validator_test.dart b/packages/flutter_tools/test/general.shard/android/android_studio_validator_test.dart index e06a9f5afbb9b..a969081d3833c 100644 --- a/packages/flutter_tools/test/general.shard/android/android_studio_validator_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_studio_validator_test.dart @@ -3,14 +3,17 @@ // found in the LICENSE file. import 'package:file/memory.dart'; +import 'package:flutter_tools/src/android/android_studio.dart'; import 'package:flutter_tools/src/android/android_studio_validator.dart'; import 'package:flutter_tools/src/base/config.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/user_messages.dart'; +import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/doctor_validator.dart'; import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -23,6 +26,7 @@ final Platform linuxPlatform = FakePlatform( ); void main() { + late FileSystem fileSystem; late FakeProcessManager fakeProcessManager; @@ -31,49 +35,76 @@ void main() { fakeProcessManager = FakeProcessManager.empty(); }); - testWithoutContext('NoAndroidStudioValidator shows Android Studio as "not available" when not available.', () async { - final Config config = Config.test(); - final NoAndroidStudioValidator validator = NoAndroidStudioValidator( - config: config, - platform: linuxPlatform, - userMessages: UserMessages(), - ); + group(NoAndroidStudioValidator, () { + testWithoutContext('shows Android Studio as "not available" when not available.', () async { + final Config config = Config.test(); + final NoAndroidStudioValidator validator = NoAndroidStudioValidator( + config: config, + platform: linuxPlatform, + userMessages: UserMessages(), + ); - expect((await validator.validate()).type, equals(ValidationType.notAvailable)); + expect((await validator.validate()).type, equals(ValidationType.notAvailable)); + }); }); - testUsingContext('AndroidStudioValidator gives doctor error on java crash', () async { - fakeProcessManager.addCommand(const FakeCommand( - command: <String>[ - '/opt/android-studio-with-cheese-5.0/jre/bin/java', - '-version', - ], - exception: ProcessException('java', <String>['-version']), - )); - const String installPath = '/opt/android-studio-with-cheese-5.0'; - const String studioHome = '$home/.AndroidStudioWithCheese5.0'; - const String homeFile = '$studioHome/system/.home'; - globals.fs.directory(installPath).createSync(recursive: true); - globals.fs.file(homeFile).createSync(recursive: true); - globals.fs.file(homeFile).writeAsStringSync(installPath); + group(AndroidStudioValidator, () { + testUsingContext('gives doctor error on java crash', () async { + fakeProcessManager.addCommand(const FakeCommand( + command: <String>[ + '/opt/android-studio-with-cheese-5.0/jre/bin/java', + '-version', + ], + exception: ProcessException('java', <String>['-version']), + )); + const String installPath = '/opt/android-studio-with-cheese-5.0'; + const String studioHome = '$home/.AndroidStudioWithCheese5.0'; + const String homeFile = '$studioHome/system/.home'; + globals.fs.directory(installPath).createSync(recursive: true); + globals.fs.file(homeFile).createSync(recursive: true); + globals.fs.file(homeFile).writeAsStringSync(installPath); + + // This checks that running the validator doesn't throw an unhandled + // exception and that the ProcessException makes it into the error + // message list. + for (final DoctorValidator validator in AndroidStudioValidator.allValidators(globals.config, globals.platform, globals.fs, globals.userMessages)) { + final ValidationResult result = await validator.validate(); + expect(result.messages.where((ValidationMessage message) { + return message.isError && message.message.contains('ProcessException'); + }).isNotEmpty, true); + } + expect(fakeProcessManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + FileSystem: () => fileSystem, + ProcessManager: () => fakeProcessManager, + Platform: () => linuxPlatform, + FileSystemUtils: () => FileSystemUtils( + fileSystem: fileSystem, + platform: linuxPlatform, + ), + }); - // This checks that running the validator doesn't throw an unhandled - // exception and that the ProcessException makes it into the error - // message list. - for (final DoctorValidator validator in AndroidStudioValidator.allValidators(globals.config, globals.platform, globals.fs, globals.userMessages)) { + testWithoutContext('warns if version of Android Studio could not be determined', () async { + final AndroidStudio studio = _FakeAndroidStudio(); + final AndroidStudioValidator validator = AndroidStudioValidator(studio, fileSystem: fileSystem, userMessages: UserMessages()); final ValidationResult result = await validator.validate(); - expect(result.messages.where((ValidationMessage message) { - return message.isError && message.message.contains('ProcessException'); - }).isNotEmpty, true); - } - expect(fakeProcessManager, hasNoRemainingExpectations); - }, overrides: <Type, Generator>{ - FileSystem: () => fileSystem, - ProcessManager: () => fakeProcessManager, - Platform: () => linuxPlatform, - FileSystemUtils: () => FileSystemUtils( - fileSystem: fileSystem, - platform: linuxPlatform, - ), + expect(result.messages, contains(const ValidationMessage.error('Unable to determine Android Studio version.'))); + expect(result.statusInfo, 'version unknown'); + }); }); } + +class _FakeAndroidStudio extends Fake implements AndroidStudio { + @override + List<String> get validationMessages => <String>[]; + @override + bool get isValid => true; + @override + String? get pluginsPath => null; + @override + String get directory => 'android-studio'; + @override + Version? get version => null; + @override + String get javaPath => 'android-studio/jbr/bin/java'; +} diff --git a/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart b/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart index d916258f44ca7..d138215e77416 100644 --- a/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart @@ -4,7 +4,6 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_sdk.dart'; -import 'package:flutter_tools/src/android/android_studio.dart'; import 'package:flutter_tools/src/android/android_workflow.dart'; import 'package:flutter_tools/src/android/java.dart'; import 'package:flutter_tools/src/base/file_system.dart'; @@ -125,13 +124,11 @@ void main() { final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( java: FakeJava(), androidSdk: sdk, - fileSystem: fileSystem, processManager: processManager, platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), stdio: stdio, logger: BufferLogger.test(), userMessages: UserMessages(), - androidStudio: FakeAndroidStudio(), ); final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted; @@ -144,13 +141,11 @@ void main() { final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( java: FakeJava(), androidSdk: sdk, - fileSystem: fileSystem, processManager: processManager, platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), stdio: stdio, logger: BufferLogger.test(), userMessages: UserMessages(), - androidStudio: FakeAndroidStudio(), ); final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted; @@ -168,13 +163,11 @@ void main() { final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( java: FakeJava(), androidSdk: sdk, - fileSystem: fileSystem, processManager: processManager, platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), stdio: stdio, logger: BufferLogger.test(), userMessages: UserMessages(), - androidStudio: FakeAndroidStudio(), ); final LicensesAccepted result = await licenseValidator.licensesAccepted; @@ -197,13 +190,11 @@ All SDK package licenses accepted. final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( java: FakeJava(), androidSdk: sdk, - fileSystem: fileSystem, processManager: processManager, platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), stdio: stdio, logger: BufferLogger.test(), userMessages: UserMessages(), - androidStudio: FakeAndroidStudio(), ); final LicensesAccepted result = await licenseValidator.licensesAccepted; @@ -226,13 +217,11 @@ All SDK package licenses accepted. final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( java: java, androidSdk: sdk, - fileSystem: fileSystem, processManager: processManager, platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), stdio: stdio, logger: BufferLogger.test(), userMessages: UserMessages(), - androidStudio: FakeAndroidStudio(), ); final LicensesAccepted licenseStatus = await licenseValidator.licensesAccepted; @@ -256,13 +245,11 @@ Review licenses that have not been accepted (y/N)? final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( java: FakeJava(), androidSdk: sdk, - fileSystem: fileSystem, processManager: processManager, platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), stdio: stdio, logger: BufferLogger.test(), userMessages: UserMessages(), - androidStudio: FakeAndroidStudio(), ); final LicensesAccepted result = await licenseValidator.licensesAccepted; @@ -286,13 +273,11 @@ Review licenses that have not been accepted (y/N)? final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( java: FakeJava(), androidSdk: sdk, - fileSystem: fileSystem, processManager: processManager, platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), stdio: stdio, logger: BufferLogger.test(), userMessages: UserMessages(), - androidStudio: FakeAndroidStudio(), ); final LicensesAccepted result = await licenseValidator.licensesAccepted; @@ -312,13 +297,11 @@ Review licenses that have not been accepted (y/N)? final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( java: FakeJava(), androidSdk: sdk, - fileSystem: fileSystem, processManager: processManager, platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), stdio: stdio, logger: BufferLogger.test(), userMessages: UserMessages(), - androidStudio: FakeAndroidStudio(), ); expect(await licenseValidator.runLicenseManager(), isTrue); @@ -331,13 +314,11 @@ Review licenses that have not been accepted (y/N)? final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( java: FakeJava(), androidSdk: sdk, - fileSystem: fileSystem, processManager: processManager, platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), stdio: stdio, logger: BufferLogger.test(), userMessages: UserMessages(), - androidStudio: FakeAndroidStudio(), ); expect(licenseValidator.runLicenseManager(), throwsToolExit()); @@ -360,13 +341,11 @@ Review licenses that have not been accepted (y/N)? final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( java: FakeJava(), androidSdk: sdk, - fileSystem: fileSystem, processManager: processManager, platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), stdio: stdio, logger: logger, userMessages: UserMessages(), - androidStudio: FakeAndroidStudio(), ); await licenseValidator.runLicenseManager(); @@ -381,13 +360,11 @@ Review licenses that have not been accepted (y/N)? final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( java: FakeJava(), androidSdk: sdk, - fileSystem: fileSystem, processManager: processManager, platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), stdio: stdio, logger: BufferLogger.test(), userMessages: UserMessages(), - androidStudio: FakeAndroidStudio(), ); expect(licenseValidator.runLicenseManager(), throwsToolExit()); @@ -408,13 +385,11 @@ Review licenses that have not been accepted (y/N)? final AndroidLicenseValidator licenseValidator = AndroidLicenseValidator( java: FakeJava(), androidSdk: sdk, - fileSystem: fileSystem, processManager: processManager, platform: FakePlatform(environment: <String, String>{'HOME': '/home/me'}), stdio: stdio, logger: logger, userMessages: UserMessages(), - androidStudio: FakeAndroidStudio(), ); await expectLater( @@ -436,11 +411,9 @@ Review licenses that have not been accepted (y/N)? ..cmdlineToolsAvailable = true ..directory = fileSystem.directory('/foo/bar'); final ValidationResult validationResult = await AndroidValidator( - androidStudio: FakeAndroidStudio(), + java: FakeJava(), androidSdk: sdk, - fileSystem: fileSystem, logger: logger, - processManager: processManager, platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, userMessages: UserMessages(), ).validate(); @@ -457,12 +430,6 @@ Review licenses that have not been accepted (y/N)? }); testUsingContext('detects minimum required SDK and buildtools', () async { - processManager.addCommand(const FakeCommand( - command: <String>[ - 'which', - 'java', - ], exitCode: 1, - )); final FakeAndroidSdkVersion sdkVersion = FakeAndroidSdkVersion() ..sdkLevel = 28 ..buildToolsVersion = Version(26, 0, 3); @@ -483,15 +450,14 @@ Review licenses that have not been accepted (y/N)? ); final AndroidValidator androidValidator = AndroidValidator( - androidStudio: null, + java: null, androidSdk: sdk, - fileSystem: fileSystem, logger: logger, - processManager: processManager, platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, userMessages: UserMessages(), ); + ValidationResult validationResult = await androidValidator.validate(); expect(validationResult.type, ValidationType.missing); expect( @@ -531,15 +497,14 @@ Review licenses that have not been accepted (y/N)? ..directory = fileSystem.directory('/foo/bar'); final AndroidValidator androidValidator = AndroidValidator( - androidStudio: null, + java: FakeJava(), androidSdk: sdk, - fileSystem: fileSystem, logger: logger, - processManager: processManager, platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me'}, userMessages: UserMessages(), ); + final String errorMessage = UserMessages().androidMissingCmdTools; final ValidationResult validationResult = await androidValidator.validate(); @@ -556,13 +521,11 @@ Review licenses that have not been accepted (y/N)? testUsingContext('detects minimum required java version', () async { // Test with older version of JDK - const String javaVersionText = 'openjdk version "1.7.0_212"'; - processManager.addCommand(const FakeCommand( - command: <String>[ - 'home/java/bin/java', - '-version', - ], stderr: javaVersionText, - )); + final Platform platform = FakePlatform()..environment = <String, String>{ + 'HOME': '/home/me', + Java.javaHomeEnvironmentVariable: 'home/java', + 'PATH': '', + }; final FakeAndroidSdkVersion sdkVersion = FakeAndroidSdkVersion() ..sdkLevel = 29 ..buildToolsVersion = Version(28, 0, 3); @@ -576,15 +539,14 @@ Review licenses that have not been accepted (y/N)? ..sdkManagerPath = '/foo/bar/sdkmanager'; sdk.latestVersion = sdkVersion; + const String javaVersionText = 'openjdk version "1.7.0_212"'; final String errorMessage = UserMessages().androidJavaMinimumVersion(javaVersionText); final ValidationResult validationResult = await AndroidValidator( + java: FakeJava(version: const Version.withText(1, 7, 0, javaVersionText)), androidSdk: sdk, - androidStudio: null, - fileSystem: fileSystem, logger: logger, - platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me', AndroidSdk.javaHomeEnvironmentVariable: 'home/java'}, - processManager: processManager, + platform: platform, userMessages: UserMessages(), ).validate(); expect(validationResult.type, ValidationType.partial); @@ -602,12 +564,10 @@ Review licenses that have not been accepted (y/N)? testWithoutContext('Mentions `flutter config --android-sdk if user has no AndroidSdk`', () async { final ValidationResult validationResult = await AndroidValidator( + java: FakeJava(), androidSdk: null, - androidStudio: null, - fileSystem: fileSystem, logger: logger, - platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me', AndroidSdk.javaHomeEnvironmentVariable: 'home/java'}, - processManager: processManager, + platform: FakePlatform()..environment = <String, String>{'HOME': '/home/me', Java.javaHomeEnvironmentVariable: 'home/java'}, userMessages: UserMessages(), ).validate(); @@ -666,11 +626,6 @@ class FakeAndroidSdkVersion extends Fake implements AndroidSdkVersion { String get platformName => ''; } -class FakeAndroidStudio extends Fake implements AndroidStudio { - @override - String get javaPath => 'java'; -} - class ThrowingStdin<T> extends Fake implements IOSink { ThrowingStdin(this.exception); diff --git a/packages/flutter_tools/test/general.shard/android/gradle_errors_test.dart b/packages/flutter_tools/test/general.shard/android/gradle_errors_test.dart index 015897e18f7f0..28cdf71e6f900 100644 --- a/packages/flutter_tools/test/general.shard/android/gradle_errors_test.dart +++ b/packages/flutter_tools/test/general.shard/android/gradle_errors_test.dart @@ -6,6 +6,7 @@ import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/android/gradle_errors.dart'; import 'package:flutter_tools/src/android/gradle_utils.dart'; +import 'package:flutter_tools/src/android/java.dart'; import 'package:flutter_tools/src/base/bot_detector.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; @@ -50,6 +51,7 @@ void main() { zipExceptionHandler, incompatibleJavaAndGradleVersionsHandler, remoteTerminatedHandshakeHandler, + couldNotOpenCacheDirectoryHandler, ]) ); }); @@ -133,6 +135,7 @@ Caused by: java.io.EOFException: SSL peer shut down incorrectly ) ); }); + testUsingContext('retries if gradle fails downloading with proxy error', () async { const String errorMessage = r''' Exception in thread "main" java.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.1 400 Bad Request" @@ -626,9 +629,7 @@ Command: /home/android/gradlew assembleRelease ) ); }); - }); - group('permission errors', () { testUsingContext('pattern', () async { const String errorMessage = ''' Permission denied @@ -774,6 +775,7 @@ assembleFooTest ); expect(processManager, hasNoRemainingExpectations); }, overrides: <Type, Generator>{ + Java: () => FakeJava(), GradleUtils: () => FakeGradleUtils(), Platform: () => fakePlatform('android'), FileSystem: () => fileSystem, @@ -816,6 +818,7 @@ assembleProfile ); expect(processManager, hasNoRemainingExpectations); }, overrides: <Type, Generator>{ + Java: () => FakeJava(), GradleUtils: () => FakeGradleUtils(), Platform: () => fakePlatform('android'), FileSystem: () => fileSystem, @@ -1383,6 +1386,32 @@ Could not compile build file '…/example/android/build.gradle'. ProcessManager: () => processManager, }); }); + + testUsingContext('couldNotOpenCacheDirectoryHandler', () async { + final GradleBuildStatus status = await couldNotOpenCacheDirectoryHandler.handler( + line: ''' +FAILURE: Build failed with an exception. + +* Where: +Script '/Volumes/Work/s/w/ir/x/w/flutter/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy' line: 276 + +* What went wrong: +A problem occurred evaluating script. +> Failed to apply plugin class 'FlutterPlugin'. + > Could not open cache directory 41rl0ui7kgmsyfwn97o2jypl6 (/Volumes/Work/s/w/ir/cache/gradle/caches/6.7/gradle-kotlin-dsl/41rl0ui7kgmsyfwn97o2jypl6). + > Failed to create Jar file /Volumes/Work/s/w/ir/cache/gradle/caches/6.7/generated-gradle-jars/gradle-api-6.7.jar.''', + project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory), + usesAndroidX: true, + multidexEnabled: true, + ); + expect(testLogger.errorText, contains('Gradle threw an error while resolving dependencies')); + expect(status, GradleBuildStatus.retry); + }, overrides: <Type, Generator>{ + GradleUtils: () => FakeGradleUtils(), + Platform: () => fakePlatform('android'), + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + }); } bool formatTestErrorMessage(String errorMessage, GradleHandledError error) { diff --git a/packages/flutter_tools/test/general.shard/android/java_test.dart b/packages/flutter_tools/test/general.shard/android/java_test.dart index b9c4b42f969ba..c7ff6786dae02 100644 --- a/packages/flutter_tools/test/general.shard/android/java_test.dart +++ b/packages/flutter_tools/test/general.shard/android/java_test.dart @@ -5,10 +5,12 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/android/android_studio.dart'; import 'package:flutter_tools/src/android/java.dart'; +import 'package:flutter_tools/src/base/config.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/base/version.dart'; import 'package:test/fake.dart'; import 'package:webdriver/async_io.dart'; @@ -19,12 +21,14 @@ import '../../src/fakes.dart'; void main() { + late Config config; late Logger logger; late FileSystem fs; late Platform platform; late FakeProcessManager processManager; setUp(() { + config = Config.test(); logger = BufferLogger.test(); fs = MemoryFileSystem.test(); platform = FakePlatform(environment: <String, String>{ @@ -53,19 +57,19 @@ OpenJDK 64-Bit Server VM Zulu19.32+15-CA (build 19.0.2+7, mixed mode, sharing) ''' )); final Java java = Java.find( + config: config, androidStudio: androidStudio, logger: logger, fileSystem: fs, platform: platform, processManager: processManager, )!; - final JavaVersion version = java.version!; expect(java.javaHome, androidStudioBundledJdkHome); expect(java.binaryPath, expectedJavaBinaryPath); - expect(version.longText, 'OpenJDK Runtime Environment Zulu19.32+15-CA (build 19.0.2+7)'); - expect(version.number, '19.0.2'); + expect(java.version!.toString(), 'OpenJDK Runtime Environment Zulu19.32+15-CA (build 19.0.2+7)'); + expect(java.version, equals(Version(19, 0, 2))); }); testWithoutContext('finds JAVA_HOME if it is set and the JDK bundled with Android Studio could not be found', () { @@ -74,11 +78,12 @@ OpenJDK 64-Bit Server VM Zulu19.32+15-CA (build 19.0.2+7, mixed mode, sharing) final String expectedJavaBinaryPath = fs.path.join(javaHome, 'bin', 'java'); final Java java = Java.find( + config: config, androidStudio: androidStudio, logger: logger, fileSystem: fs, platform: FakePlatform(environment: <String, String>{ - 'JAVA_HOME': javaHome, + Java.javaHomeEnvironmentVariable: javaHome, }), processManager: processManager, )!; @@ -99,6 +104,7 @@ OpenJDK 64-Bit Server VM Zulu19.32+15-CA (build 19.0.2+7, mixed mode, sharing) ); final Java java = Java.find( + config: config, androidStudio: androidStudio, logger: logger, fileSystem: fs, @@ -119,6 +125,7 @@ OpenJDK 64-Bit Server VM Zulu19.32+15-CA (build 19.0.2+7, mixed mode, sharing) ), ); final Java? java = Java.find( + config: config, androidStudio: androidStudio, logger: logger, fileSystem: fs, @@ -127,6 +134,53 @@ OpenJDK 64-Bit Server VM Zulu19.32+15-CA (build 19.0.2+7, mixed mode, sharing) ); expect(java, isNull); }); + + testWithoutContext('finds and prefers JDK found at config item "jdk-dir" if it is set', () { + const String configuredJdkPath = '/jdk'; + config.setValue('jdk-dir', configuredJdkPath); + + processManager.addCommand( + const FakeCommand( + command: <String>['which', 'java'], + stdout: '/fake/which/java/path', + ), + ); + + final _FakeAndroidStudioWithJdk androidStudio = _FakeAndroidStudioWithJdk(); + final FakePlatform platformWithJavaHome = FakePlatform( + environment: <String, String>{ + 'JAVA_HOME': '/old/jdk' + }, + ); + Java? java = Java.find( + config: config, + androidStudio: androidStudio, + logger: logger, + fileSystem: fs, + platform: platformWithJavaHome, + processManager: processManager, + ); + + expect(java, isNotNull); + expect(java!.javaHome, configuredJdkPath); + expect(java.binaryPath, fs.path.join(configuredJdkPath, 'bin', 'java')); + + config.removeValue('jdk-dir'); + + java = Java.find( + config: config, + androidStudio: androidStudio, + logger: logger, + fileSystem: fs, + platform: platformWithJavaHome, + processManager: processManager, + ); + + expect(java, isNotNull); + assert(androidStudio.javaPath != configuredJdkPath); + expect(java!.javaHome, androidStudio.javaPath); + expect(java.binaryPath, fs.path.join(androidStudio.javaPath!, 'bin', 'java')); + }); }); group('getVersionString', () { @@ -160,9 +214,9 @@ java version "1.8.0_202" Java(TM) SE Runtime Environment (build 1.8.0_202-b10) Java HotSpot(TM) 64-Bit Server VM (build 25.202-b10, mixed mode) '''); - final JavaVersion version = java.version!; - expect(version.longText, 'Java(TM) SE Runtime Environment (build 1.8.0_202-b10)'); - expect(version.number, '1.8.0'); + final Version version = java.version!; + expect(version.toString(), 'Java(TM) SE Runtime Environment (build 1.8.0_202-b10)'); + expect(version, equals(Version(1, 8, 0))); }); testWithoutContext('parses jdk 11 windows', () { addJavaVersionCommand(''' @@ -170,9 +224,9 @@ java version "11.0.14" Java(TM) SE Runtime Environment (build 11.0.14+10-b13) Java HotSpot(TM) 64-Bit Server VM (build 11.0.14+10-b13, mixed mode) '''); - final JavaVersion version = java.version!; - expect(version.longText, 'Java(TM) SE Runtime Environment (build 11.0.14+10-b13)'); - expect(version.number, '11.0.14'); + final Version version = java.version!; + expect(version.toString(), 'Java(TM) SE Runtime Environment (build 11.0.14+10-b13)'); + expect(version, equals(Version(11, 0, 14))); }); testWithoutContext('parses jdk 11 mac/linux', () { @@ -181,9 +235,9 @@ openjdk version "11.0.18" 2023-01-17 LTS OpenJDK Runtime Environment Zulu11.62+17-CA (build 11.0.18+10-LTS) OpenJDK 64-Bit Server VM Zulu11.62+17-CA (build 11.0.18+10-LTS, mixed mode) '''); - final JavaVersion version = java.version!; - expect(version.longText, 'OpenJDK Runtime Environment Zulu11.62+17-CA (build 11.0.18+10-LTS)'); - expect(version.number, '11.0.18'); + final Version version = java.version!; + expect(version.toString(), 'OpenJDK Runtime Environment Zulu11.62+17-CA (build 11.0.18+10-LTS)'); + expect(version, equals(Version(11, 0, 18))); }); testWithoutContext('parses jdk 17', () { @@ -192,9 +246,9 @@ openjdk 17.0.6 2023-01-17 OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694) OpenJDK 64-Bit Server VM (build 17.0.6+0-17.0.6b802.4-9586694, mixed mode) '''); - final JavaVersion version = java.version!; - expect(version.longText, 'OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694)'); - expect(version.number, '17.0.6'); + final Version version = java.version!; + expect(version.toString(), 'OpenJDK Runtime Environment (build 17.0.6+0-17.0.6b802.4-9586694)'); + expect(version, equals(Version(17, 0, 6))); }); testWithoutContext('parses jdk 19', () { @@ -203,9 +257,9 @@ openjdk 19.0.2 2023-01-17 OpenJDK Runtime Environment Homebrew (build 19.0.2) OpenJDK 64-Bit Server VM Homebrew (build 19.0.2, mixed mode, sharing) '''); - final JavaVersion version = java.version!; - expect(version.longText, 'OpenJDK Runtime Environment Homebrew (build 19.0.2)'); - expect(version.number, '19.0.2'); + final Version version = java.version!; + expect(version.toString(), 'OpenJDK Runtime Environment Homebrew (build 19.0.2)'); + expect(version, equals(Version(19, 0, 2))); }); // https://chrome-infra-packages.appspot.com/p/flutter/java/openjdk/ @@ -215,16 +269,16 @@ openjdk 11.0.2 2019-01-15 OpenJDK Runtime Environment 18.9 (build 11.0.2+9) OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode) '''); - final JavaVersion version = java.version!; - expect(version.longText, 'OpenJDK Runtime Environment 18.9 (build 11.0.2+9)'); - expect(version.number, '11.0.2'); + final Version version = java.version!; + expect(version.toString(), 'OpenJDK Runtime Environment 18.9 (build 11.0.2+9)'); + expect(version, equals(Version(11, 0, 2))); }); testWithoutContext('parses jdk two number versions', () { addJavaVersionCommand('openjdk 19.0 2023-01-17'); - final JavaVersion version = java.version!; - expect(version.longText, 'openjdk 19.0 2023-01-17'); - expect(version.number, '19.0'); + final Version version = java.version!; + expect(version.toString(), 'openjdk 19.0 2023-01-17'); + expect(version, equals(Version(19, 0, null))); }); }); }); diff --git a/packages/flutter_tools/test/general.shard/args_test.dart b/packages/flutter_tools/test/general.shard/args_test.dart index a6f207d78454e..bb04cc8a86ffd 100644 --- a/packages/flutter_tools/test/general.shard/args_test.dart +++ b/packages/flutter_tools/test/general.shard/args_test.dart @@ -5,6 +5,7 @@ import 'package:args/args.dart'; import 'package:args/command_runner.dart'; import 'package:flutter_tools/executable.dart' as executable; +import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/analyze.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/runner/flutter_command_runner.dart'; @@ -15,6 +16,14 @@ import '../src/testbed.dart'; import 'runner/utils.dart'; void main() { + setUpAll(() { + Cache.disableLocking(); + }); + + tearDownAll(() { + Cache.enableLocking(); + }); + test('Help for command line arguments is consistently styled and complete', () => Testbed().run(() { final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true); executable.generateCommands( @@ -30,11 +39,53 @@ void main() { } })); + testUsingContext('Global arg results are available in FlutterCommands', () async { + final DummyFlutterCommand command = DummyFlutterCommand( + commandFunction: () async { + return const FlutterCommandResult(ExitStatus.success); + }, + ); + + final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true); + + runner.addCommand(command); + await runner.run(<String>['dummy', '--${FlutterGlobalOptions.kContinuousIntegrationFlag}']); + + expect(command.globalResults, isNotNull); + expect(command.boolArg(FlutterGlobalOptions.kContinuousIntegrationFlag, global: true), true); + }); + + testUsingContext('Global arg results are available in FlutterCommands sub commands', () async { + final DummyFlutterCommand command = DummyFlutterCommand( + commandFunction: () async { + return const FlutterCommandResult(ExitStatus.success); + }, + ); + + final DummyFlutterCommand subcommand = DummyFlutterCommand( + name: 'sub', + commandFunction: () async { + return const FlutterCommandResult(ExitStatus.success); + }, + ); + + command.addSubcommand(subcommand); + + final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true); + + runner.addCommand(command); + runner.addCommand(subcommand); + await runner.run(<String>['dummy', 'sub', '--${FlutterGlobalOptions.kContinuousIntegrationFlag}']); + + expect(subcommand.globalResults, isNotNull); + expect(subcommand.boolArg(FlutterGlobalOptions.kContinuousIntegrationFlag, global: true), true); + }); + testUsingContext('bool? safe argResults', () async { final DummyFlutterCommand command = DummyFlutterCommand( - commandFunction: () async { - return const FlutterCommandResult(ExitStatus.success); - } + commandFunction: () async { + return const FlutterCommandResult(ExitStatus.success); + }, ); final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true); command.argParser.addFlag('key'); @@ -58,9 +109,9 @@ void main() { testUsingContext('String? safe argResults', () async { final DummyFlutterCommand command = DummyFlutterCommand( - commandFunction: () async { - return const FlutterCommandResult(ExitStatus.success); - } + commandFunction: () async { + return const FlutterCommandResult(ExitStatus.success); + }, ); final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true); command.argParser.addOption('key'); @@ -80,9 +131,9 @@ void main() { testUsingContext('List<String> safe argResults', () async { final DummyFlutterCommand command = DummyFlutterCommand( - commandFunction: () async { - return const FlutterCommandResult(ExitStatus.success); - } + commandFunction: () async { + return const FlutterCommandResult(ExitStatus.success); + }, ); final FlutterCommandRunner runner = FlutterCommandRunner(verboseHelp: true); command.argParser.addMultiOption( diff --git a/packages/flutter_tools/test/general.shard/artifact_updater_test.dart b/packages/flutter_tools/test/general.shard/artifact_updater_test.dart index 2fc87d47efb94..96780ec396101 100644 --- a/packages/flutter_tools/test/general.shard/artifact_updater_test.dart +++ b/packages/flutter_tools/test/general.shard/artifact_updater_test.dart @@ -371,7 +371,7 @@ void main() { }); testWithoutContext('ArtifactUpdater will de-download a file if unzipping fails on windows', () async { - final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils(windows: true); + final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils(); final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final BufferLogger logger = BufferLogger.test(); final ArtifactUpdater artifactUpdater = ArtifactUpdater( @@ -421,7 +421,7 @@ void main() { }); testWithoutContext('ArtifactUpdater will bail if unzipping fails more than twice on Windows', () async { - final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils(windows: true); + final FakeOperatingSystemUtils operatingSystemUtils = FakeOperatingSystemUtils(); final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final BufferLogger logger = BufferLogger.test(); final ArtifactUpdater artifactUpdater = ArtifactUpdater( @@ -529,10 +529,7 @@ void main() { } class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils { - FakeOperatingSystemUtils({this.windows = false}); - int failures = 0; - final bool windows; /// A mapping of zip [file] paths to callbacks that receive the [targetDirectory]. /// diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_package_fonts_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_package_fonts_test.dart index de7afe549e662..febaa31742e02 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_package_fonts_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_package_fonts_test.dart @@ -111,7 +111,7 @@ $fontsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build(packagesPath: '.packages'); - expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.smcbin', + expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.bin', 'AssetManifest.json', 'FontManifest.json', 'NOTICES.Z'])); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart index c8bb1339efa1b..cc7f68a0a0ed8 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_package_test.dart @@ -26,6 +26,7 @@ void main() { // rolls into Flutter. return path.replaceAll('/', globals.fs.path.separator); } + void writePubspecFile(String path, String name, { List<String>? assets }) { String assetsSection; if (assets == null) { @@ -62,19 +63,19 @@ $assetsSection ..writeAsStringSync(packages); } + Map<Object, Object> assetManifestBinToJson(Map<Object, Object> manifest) { + List<Object> convertList(List<Object> variants) => variants + .map((Object variant) => (variant as Map<Object?, Object?>)['asset']!) + .toList(); + + return manifest.map((Object key, Object value) => MapEntry<Object, Object>(key, convertList(value as List<Object>))); + } + Future<void> buildAndVerifyAssets( List<String> assets, List<String> packages, - String? expectedJsonAssetManifest, - String? expectedBinAssetManifestAsJson, { - bool expectExists = true, - }) async { - Future<String> extractAssetManifestBinFromBundleAsJson(AssetBundle bundle) async { - final List<int> manifestBytes = await bundle.entries['AssetManifest.smcbin']!.contentsAsBytes(); - return json.encode(const StandardMessageCodec().decodeMessage( - ByteData.sublistView(Uint8List.fromList(manifestBytes)) - )); - } + Map<Object,Object> expectedAssetManifest + ) async { final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build(packagesPath: '.packages'); @@ -82,27 +83,31 @@ $assetsSection for (final String packageName in packages) { for (final String asset in assets) { final String entryKey = Uri.encodeFull('packages/$packageName/$asset'); - expect(bundle.entries.containsKey(entryKey), expectExists, + expect(bundle.entries, contains(entryKey), reason: 'Cannot find key on bundle: $entryKey'); - if (expectExists) { - expect( - utf8.decode(await bundle.entries[entryKey]!.contentsAsBytes()), - asset, - ); - } + expect( + utf8.decode(await bundle.entries[entryKey]!.contentsAsBytes()), + asset, + ); } } - if (expectExists) { - expect( - utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes()), - expectedJsonAssetManifest, - ); - expect( - await extractAssetManifestBinFromBundleAsJson(bundle), - expectedBinAssetManifestAsJson - ); - } + final Map<Object?, Object?> assetManifest = const StandardMessageCodec().decodeMessage( + ByteData.sublistView( + Uint8List.fromList( + await bundle.entries['AssetManifest.bin']!.contentsAsBytes() + ) + ) + ) as Map<Object?, Object?>; + + expect( + json.decode(utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes())), + assetManifestBinToJson(expectedAssetManifest), + ); + expect( + assetManifest, + expectedAssetManifest + ); } void writeAssets(String path, List<String> assets) { @@ -135,7 +140,7 @@ $assetsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build(packagesPath: '.packages'); expect(bundle.entries.keys, unorderedEquals( - <String>['NOTICES.Z', 'AssetManifest.json', 'AssetManifest.smcbin', 'FontManifest.json'])); + <String>['NOTICES.Z', 'AssetManifest.json', 'AssetManifest.bin', 'FontManifest.json'])); const String expectedAssetManifest = '{}'; expect( utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes()), @@ -161,7 +166,7 @@ $assetsSection final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build(packagesPath: '.packages'); expect(bundle.entries.keys, unorderedEquals( - <String>['NOTICES.Z', 'AssetManifest.json', 'AssetManifest.smcbin', 'FontManifest.json'])); + <String>['NOTICES.Z', 'AssetManifest.json', 'AssetManifest.bin', 'FontManifest.json'])); const String expectedAssetManifest = '{}'; expect( utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes()), @@ -190,14 +195,18 @@ $assetsSection writeAssets('p/p/', assets); - const String expectedJsonAssetManifest = '{"packages/test_package/a/foo":' - '["packages/test_package/a/foo"]}'; - const String expectedBinAssetManifest = '{"packages/test_package/a/foo":[]}'; + final Map<Object, Object> expectedAssetManifest = <Object, Object>{ + 'packages/test_package/a/foo': <Map<Object, Object>>[ + <Object, Object>{ + 'asset': 'packages/test_package/a/foo', + } + ] + }; + await buildAndVerifyAssets( assets, <String>['test_package'], - expectedJsonAssetManifest, - expectedBinAssetManifest + expectedAssetManifest, ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -218,14 +227,16 @@ $assetsSection final List<String> assets = <String>['a/foo']; writeAssets('p/p/lib/', assets); - const String expectedAssetManifest = '{"packages/test_package/a/foo":' - '["packages/test_package/a/foo"]}'; - const String expectedBinAssetManifest = '{"packages/test_package/a/foo":[]}'; + + const Map<Object, Object> expectedAssetManifest = <Object, Object>{ + 'packages/test_package/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/foo'} + ] + }; await buildAndVerifyAssets( assets, <String>['test_package'], - expectedAssetManifest, - expectedBinAssetManifest + expectedAssetManifest ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -245,25 +256,20 @@ $assetsSection final List<String> assets = <String>['a/foo', 'a/2x/foo', 'a/bar']; writeAssets('p/p/', assets); - const String expectedManifest = '{' - '"packages/test_package/a/bar":' - '["packages/test_package/a/bar"],' - '"packages/test_package/a/foo":' - '["packages/test_package/a/foo","packages/test_package/a/2x/foo"]' - '}'; - - const String expectedBinManifest = '{' - '"packages/test_package/a/bar":[],' - '"packages/test_package/a/foo":' - '[{"asset":"packages/test_package/a/2x/foo","dpr":2.0}]' - '}'; - + const Map<Object, Object> expectedManifest = <Object, Object>{ + 'packages/test_package/a/bar': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/bar'} + ], + 'packages/test_package/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/foo'}, + <String, Object>{'asset': 'packages/test_package/a/2x/foo', 'dpr': 2.0} + ] + }; await buildAndVerifyAssets( assets, <String>['test_package'], expectedManifest, - expectedBinManifest ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -286,17 +292,17 @@ $assetsSection final List<String> assets = <String>['a/foo', 'a/2x/foo']; writeAssets('p/p/lib/', assets); - const String expectedManifest = '{"packages/test_package/a/foo":' - '["packages/test_package/a/foo","packages/test_package/a/2x/foo"]}'; - - const String expectedBinManifest = '{"packages/test_package/a/foo":' - '[{"asset":"packages/test_package/a/2x/foo","dpr":2.0}]}'; + const Map<Object, Object> expectedManifest = <Object, Object>{ + 'packages/test_package/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/foo'}, + <String, Object>{'asset': 'packages/test_package/a/2x/foo', 'dpr': 2.0} + ] + }; await buildAndVerifyAssets( assets, <String>['test_package'], expectedManifest, - expectedBinManifest ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -316,19 +322,19 @@ $assetsSection ); writeAssets('p/p/', assets); - const String expectedAssetManifest = - '{"packages/test_package/a/bar":["packages/test_package/a/bar"],' - '"packages/test_package/a/foo":["packages/test_package/a/foo"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/bar":[],' - '"packages/test_package/a/foo":[]}'; - + const Map<Object, Object> expectedAssetManifest = <Object, Object>{ + 'packages/test_package/a/bar': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/bar'} + ], + 'packages/test_package/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/foo'} + ] + }; await buildAndVerifyAssets( assets, <String>['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -354,18 +360,19 @@ $assetsSection ); writeAssets('p/p/lib/', assets); - const String expectedAssetManifest = - '{"packages/test_package/a/bar":["packages/test_package/a/bar"],' - '"packages/test_package/a/foo":["packages/test_package/a/foo"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/bar":[],' - '"packages/test_package/a/foo":[]}'; + const Map<Object, Object> expectedAssetManifest = <Object, Object>{ + 'packages/test_package/a/bar': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/bar'} + ], + 'packages/test_package/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/foo'} + ] + }; await buildAndVerifyAssets( assets, <String>['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -393,22 +400,21 @@ $assetsSection writeAssets('p/p/', assets); writeAssets('p2/p/', assets); - const String expectedAssetManifest = - '{"packages/test_package/a/foo":' - '["packages/test_package/a/foo","packages/test_package/a/2x/foo"],' - '"packages/test_package2/a/foo":' - '["packages/test_package2/a/foo","packages/test_package2/a/2x/foo"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/foo":' - '[{"asset":"packages/test_package/a/2x/foo","dpr":2.0}],' - '"packages/test_package2/a/foo":' - '[{"asset":"packages/test_package2/a/2x/foo","dpr":2.0}]}'; + const Map<Object, Object> expectedAssetManifest = <Object, Object>{ + 'packages/test_package/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/foo'}, + <String, Object>{'asset': 'packages/test_package/a/2x/foo', 'dpr': 2.0} + ], + 'packages/test_package2/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package2/a/foo'}, + <String, Object>{'asset': 'packages/test_package2/a/2x/foo', 'dpr': 2.0} + ] + }; await buildAndVerifyAssets( assets, <String>['test_package', 'test_package2'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -439,23 +445,21 @@ $assetsSection writeAssets('p/p/lib/', assets); writeAssets('p2/p/lib/', assets); - const String expectedAssetManifest = - '{"packages/test_package/a/foo":' - '["packages/test_package/a/foo","packages/test_package/a/2x/foo"],' - '"packages/test_package2/a/foo":' - '["packages/test_package2/a/foo","packages/test_package2/a/2x/foo"]}'; - - const String expectedBinAssetManifest = - '{"packages/test_package/a/foo":' - '[{"asset":"packages/test_package/a/2x/foo","dpr":2.0}],' - '"packages/test_package2/a/foo":' - '[{"asset":"packages/test_package2/a/2x/foo","dpr":2.0}]}'; + const Map<Object, Object> expectedAssetManifest = <Object, Object>{ + 'packages/test_package/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/foo'}, + <String, Object>{'asset': 'packages/test_package/a/2x/foo', 'dpr': 2.0} + ], + 'packages/test_package2/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package2/a/foo'}, + <String, Object>{'asset': 'packages/test_package2/a/2x/foo', 'dpr': 2.0} + ] + }; await buildAndVerifyAssets( assets, <String>['test_package', 'test_package2'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -482,18 +486,17 @@ $assetsSection final List<String> assets = <String>['a/foo', 'a/2x/foo']; writeAssets('p2/p/lib/', assets); - const String expectedAssetManifest = - '{"packages/test_package2/a/foo":' - '["packages/test_package2/a/foo","packages/test_package2/a/2x/foo"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package2/a/foo":' - '[{"asset":"packages/test_package2/a/2x/foo","dpr":2.0}]}'; + const Map<Object, Object> expectedAssetManifest = <Object, Object>{ + 'packages/test_package2/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package2/a/foo'}, + <String, Object>{'asset': 'packages/test_package2/a/2x/foo', 'dpr': 2.0} + ] + }; await buildAndVerifyAssets( assets, <String>['test_package2'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -513,18 +516,19 @@ $assetsSection ); writeAssets('p/p/', assets); - const String expectedAssetManifest = - '{"packages/test_package/a/foo":["packages/test_package/a/foo"],' - '"packages/test_package/a/foo [x]":["packages/test_package/a/foo [x]"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/foo":[],' - '"packages/test_package/a/foo [x]":[]}'; + const Map<Object, Object> expectedAssetManifest = <Object, Object>{ + 'packages/test_package/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/foo'} + ], + 'packages/test_package/a/foo [x]': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/foo [x]'} + ] + }; await buildAndVerifyAssets( assets, <String>['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -546,18 +550,19 @@ $assetsSection ); writeAssets('p/p/', assetsOnDisk); - const String expectedAssetManifest = - '{"packages/test_package/a/bar":["packages/test_package/a/bar"],' - '"packages/test_package/a/foo":["packages/test_package/a/foo"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/bar":[],' - '"packages/test_package/a/foo":[]}'; + const Map<Object, Object> expectedAssetManifest = <Object, Object>{ + 'packages/test_package/a/bar': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/bar'} + ], + 'packages/test_package/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/foo'} + ] + }; await buildAndVerifyAssets( assetsOnDisk, <String>['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -578,18 +583,19 @@ $assetsSection ); writeAssets('p/p/', assetsOnDisk); - const String expectedAssetManifest = - '{"packages/test_package/a/foo":["packages/test_package/a/foo"],' - '"packages/test_package/abc/bar":["packages/test_package/abc/bar"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/foo":[],' - '"packages/test_package/abc/bar":[]}'; + const Map<Object, Object> expectedAssetManifest = <Object, Object>{ + 'packages/test_package/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/foo'} + ], + 'packages/test_package/abc/bar': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/abc/bar'} + ] + }; await buildAndVerifyAssets( assetsOnDisk, <String>['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -637,16 +643,16 @@ $assetsSection ); writeAssets('p/p/', assetsOnDisk); - const String expectedAssetManifest = - '{"packages/test_package/a/foo":["packages/test_package/a/foo","packages/test_package/a/2x/foo"]}'; - const String expectedBinAssetManifest = - '{"packages/test_package/a/foo":[{"asset":"packages/test_package/a/2x/foo","dpr":2.0}]}'; - + const Map<Object, Object> expectedAssetManifest = <Object, Object>{ + 'packages/test_package/a/foo': <Map<String, Object>>[ + <String, Object>{'asset': 'packages/test_package/a/foo'}, + <String, Object>{'asset': 'packages/test_package/a/2x/foo', 'dpr': 2.0} + ] + }; await buildAndVerifyAssets( assetsOnDisk, <String>['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -667,15 +673,12 @@ $assetsSection ); writeAssets('p/p/', assetsOnDisk); - const String expectedAssetManifest = '{}'; - const String expectedBinAssetManifest = '{}'; - + const Map<Object, Object> expectedAssetManifest = <Object, Object>{}; await buildAndVerifyAssets( assetOnManifest, <String>['test_package'], expectedAssetManifest, - expectedBinAssetManifest ); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -694,13 +697,8 @@ $assetsSection assets: assetOnManifest, ); - await buildAndVerifyAssets( - assetOnManifest, - <String>['test_package'], - null, - null, - expectExists: false, - ); + final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); + await bundle.build(packagesPath: '.packages'); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart index 77b4129749aa2..11837c7fb2572 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_test.dart @@ -51,7 +51,7 @@ void main() { final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build(packagesPath: '.packages'); expect(bundle.entries.keys, - unorderedEquals(<String>['AssetManifest.json', 'AssetManifest.smcbin']) + unorderedEquals(<String>['AssetManifest.json', 'AssetManifest.bin']) ); const String expectedJsonAssetManifest = '{}'; const Map<Object, Object> expectedBinAssetManifest = <Object, Object>{}; @@ -60,7 +60,7 @@ void main() { expectedJsonAssetManifest, ); expect( - const StandardMessageCodec().decodeMessage(ByteData.sublistView(Uint8List.fromList(await bundle.entries['AssetManifest.smcbin']!.contentsAsBytes()))), + const StandardMessageCodec().decodeMessage(ByteData.sublistView(Uint8List.fromList(await bundle.entries['AssetManifest.bin']!.contentsAsBytes()))), expectedBinAssetManifest ); @@ -103,7 +103,7 @@ flutter: expect(bundle.entries.keys, unorderedEquals(<String>[ 'AssetManifest.json', - 'AssetManifest.smcbin', + 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/dog.png', @@ -128,7 +128,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build(packagesPath: '.packages'); expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.json', - 'AssetManifest.smcbin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt'])); + 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt'])); // Simulate modifying the files by updating the filestat time manually. globals.fs.file(globals.fs.path.join('assets', 'foo', 'fizz.txt')) ..createSync(recursive: true) @@ -137,7 +137,7 @@ flutter: expect(bundle.needsBuild(), true); await bundle.build(packagesPath: '.packages'); expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.json', - 'AssetManifest.smcbin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt', + 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt', 'assets/foo/fizz.txt'])); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -158,7 +158,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build(packagesPath: '.packages'); expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.json', - 'AssetManifest.smcbin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt'])); + 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt'])); expect(bundle.needsBuild(), false); // Delete the wildcard directory and update pubspec file. @@ -180,7 +180,7 @@ name: example''') expect(bundle.needsBuild(), true); await bundle.build(packagesPath: '.packages'); expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.json', - 'AssetManifest.smcbin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt'])); + 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt'])); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, ProcessManager: () => FakeProcessManager.any(), @@ -204,7 +204,7 @@ flutter: final AssetBundle bundle = AssetBundleFactory.instance.createBundle(); await bundle.build(packagesPath: '.packages'); expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.json', - 'AssetManifest.smcbin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt'])); + 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt'])); expect(bundle.needsBuild(), false); }, overrides: <Type, Generator>{ FileSystem: () => testFileSystem, @@ -237,7 +237,7 @@ flutter: ).createBundle(); await bundle.build(packagesPath: '.packages', deferredComponentsEnabled: true); expect(bundle.entries.keys, unorderedEquals(<String>['AssetManifest.json', - 'AssetManifest.smcbin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt'])); + 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z', 'assets/foo/bar.txt'])); expect(bundle.deferredComponentsEntries.length, 1); expect(bundle.deferredComponentsEntries['component1']!.length, 2); expect(bundle.needsBuild(), false); @@ -268,7 +268,7 @@ flutter: await bundle.build(packagesPath: '.packages'); expect(bundle.entries.keys, unorderedEquals(<String>['assets/foo/bar.txt', 'assets/bar/barbie.txt', 'assets/wild/dash.txt', 'AssetManifest.json', - 'AssetManifest.smcbin', 'FontManifest.json', 'NOTICES.Z'])); + 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z'])); expect(bundle.deferredComponentsEntries.isEmpty, true); expect(bundle.needsBuild(), false); }, overrides: <Type, Generator>{ @@ -302,7 +302,7 @@ flutter: ).createBundle(); await bundle.build(packagesPath: '.packages', deferredComponentsEnabled: true); expect(bundle.entries.keys, unorderedEquals(<String>['assets/foo/bar.txt', - 'AssetManifest.json', 'AssetManifest.smcbin', 'FontManifest.json', 'NOTICES.Z'])); + 'AssetManifest.json', 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z'])); expect(bundle.deferredComponentsEntries.length, 1); expect(bundle.deferredComponentsEntries['component1']!.length, 2); expect(bundle.needsBuild(), false); @@ -316,7 +316,7 @@ flutter: await bundle.build(packagesPath: '.packages', deferredComponentsEnabled: true); expect(bundle.entries.keys, unorderedEquals(<String>['assets/foo/bar.txt', - 'AssetManifest.json', 'AssetManifest.smcbin', 'FontManifest.json', 'NOTICES.Z'])); + 'AssetManifest.json', 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z'])); expect(bundle.deferredComponentsEntries.length, 1); expect(bundle.deferredComponentsEntries['component1']!.length, 3); }, overrides: <Type, Generator>{ @@ -663,7 +663,7 @@ flutter: await bundle.build(packagesPath: '.packages'); expect(bundle.entries.keys, unorderedEquals(<String>['packages/foo/bar/fizz.txt', - 'AssetManifest.json', 'AssetManifest.smcbin', 'FontManifest.json', 'NOTICES.Z'])); + 'AssetManifest.json', 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z'])); expect(bundle.needsBuild(), false); // Does not track dependency's wildcard directories. @@ -799,7 +799,7 @@ flutter: expect(await bundle.build(packagesPath: '.packages'), 0); expect(bundle.entries.keys, unorderedEquals(<String>['assets/foo.txt', - 'AssetManifest.json', 'AssetManifest.smcbin', 'FontManifest.json', 'NOTICES.Z'])); + 'AssetManifest.json', 'AssetManifest.bin', 'FontManifest.json', 'NOTICES.Z'])); }, overrides: <Type, Generator>{ FileSystem: () => MemoryFileSystem.test(), ProcessManager: () => FakeProcessManager.any(), diff --git a/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart b/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart index 46c7caef6ca87..8136ea1b31d57 100644 --- a/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart +++ b/packages/flutter_tools/test/general.shard/asset_bundle_variant_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'dart:convert'; +import 'dart:typed_data'; import 'package:file/file.dart'; import 'package:file/memory.dart'; @@ -15,12 +16,13 @@ import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/project.dart'; +import 'package:standard_message_codec/standard_message_codec.dart'; import '../src/common.dart'; void main() { - Future<Map<String, List<String>>> extractAssetManifestFromBundle(ManifestAssetBundle bundle) async { + Future<Map<String, List<String>>> extractAssetManifestJsonFromBundle(ManifestAssetBundle bundle) async { final String manifestJson = utf8.decode(await bundle.entries['AssetManifest.json']!.contentsAsBytes()); final Map<String, dynamic> parsedJson = json.decode(manifestJson) as Map<String, dynamic>; final Iterable<String> keys = parsedJson.keys; @@ -30,6 +32,13 @@ void main() { return parsedManifest; } + Future<Map<Object?, Object?>> extractAssetManifestSmcBinFromBundle(ManifestAssetBundle bundle) async { + final List<int> manifest = await bundle.entries['AssetManifest.bin']!.contentsAsBytes(); + final ByteData asByteData = ByteData.view(Uint8List.fromList(manifest).buffer); + final Map<Object?, Object?> decoded = const StandardMessageCodec().decodeMessage(asByteData)! as Map<Object?, Object?>; + return decoded; + } + group('AssetBundle asset variants (with Unix-style paths)', () { late Platform platform; late FileSystem fs; @@ -44,7 +53,11 @@ void main() { ); fs.file('.packages').createSync(); + }); + void createPubspec({ + required List<String> assets, + }) { fs.file('pubspec.yaml').writeAsStringSync( ''' name: test @@ -53,15 +66,14 @@ dependencies: sdk: flutter flutter: assets: - - assets/ - - assets/notAVariant/ - - assets/folder/ - - assets/normalFolder/ +${assets.map((String entry) => ' - $entry').join('\n')} ''' ); - }); + } testWithoutContext('Only images in folders named with device pixel ratios (e.g. 2x, 3.0x) should be considered as variants of other images', () async { + createPubspec(assets: <String>['assets/', 'assets/notAVariant/']); + const String image = 'assets/image.jpg'; const String image2xVariant = 'assets/2x/image.jpg'; const String imageNonVariant = 'assets/notAVariant/image.jpg'; @@ -89,14 +101,33 @@ flutter: flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), ); - final Map<String, List<String>> manifest = await extractAssetManifestFromBundle(bundle); - - expect(manifest, hasLength(2)); - expect(manifest[image], equals(<String>[image, image2xVariant])); - expect(manifest[imageNonVariant], equals(<String>[imageNonVariant])); + final Map<String, List<String>> jsonManifest = await extractAssetManifestJsonFromBundle(bundle); + final Map<Object?, Object?> smcBinManifest = await extractAssetManifestSmcBinFromBundle(bundle); + + final Map<String, List<Map<String, Object>>> expectedAssetManifest = <String, List<Map<String, Object>>>{ + image: <Map<String, Object>>[ + <String, String>{ + 'asset': image, + }, + <String, Object>{ + 'asset': image2xVariant, + 'dpr': 2.0, + } + ], + imageNonVariant: <Map<String, String>>[ + <String, String>{ + 'asset': imageNonVariant, + } + ], + }; + + expect(smcBinManifest, equals(expectedAssetManifest)); + expect(jsonManifest, equals(_assetManifestBinToJson(expectedAssetManifest))); }); - testWithoutContext('Asset directories are recursively searched for assets', () async { + testWithoutContext('Asset directories have their subdirectories searched for asset variants', () async { + createPubspec(assets: <String>['assets/', 'assets/folder/']); + const String topLevelImage = 'assets/image.jpg'; const String secondLevelImage = 'assets/folder/secondLevel.jpg'; const String secondLevel2xVariant = 'assets/folder/2x/secondLevel.jpg'; @@ -124,13 +155,36 @@ flutter: flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), ); - final Map<String, List<String>> manifest = await extractAssetManifestFromBundle(bundle); - expect(manifest, hasLength(2)); - expect(manifest[topLevelImage], equals(<String>[topLevelImage])); - expect(manifest[secondLevelImage], equals(<String>[secondLevelImage, secondLevel2xVariant])); + final Map<String, List<String>> jsonManifest = await extractAssetManifestJsonFromBundle(bundle); + expect(jsonManifest, hasLength(2)); + expect(jsonManifest[topLevelImage], equals(<String>[topLevelImage])); + expect(jsonManifest[secondLevelImage], equals(<String>[secondLevelImage, secondLevel2xVariant])); + + final Map<Object?, Object?> smcBinManifest = await extractAssetManifestSmcBinFromBundle(bundle); + + final Map<String, List<Map<String, Object>>> expectedAssetManifest = <String, List<Map<String, Object>>>{ + topLevelImage: <Map<String, Object>>[ + <String, String>{ + 'asset': topLevelImage, + }, + ], + secondLevelImage: <Map<String, Object>>[ + <String, String>{ + 'asset': secondLevelImage, + }, + <String, Object>{ + 'asset': secondLevel2xVariant, + 'dpr': 2.0, + }, + ], + }; + expect(jsonManifest, equals(_assetManifestBinToJson(expectedAssetManifest))); + expect(smcBinManifest, equals(expectedAssetManifest)); }); testWithoutContext('Asset paths should never be URI-encoded', () async { + createPubspec(assets: <String>['assets/normalFolder/']); + const String image = 'assets/normalFolder/i have URI-reserved_characters.jpg'; const String imageVariant = 'assets/normalFolder/3x/i have URI-reserved_characters.jpg'; @@ -156,12 +210,66 @@ flutter: flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), ); - final Map<String, List<String>> manifest = await extractAssetManifestFromBundle(bundle); - expect(manifest, hasLength(1)); - expect(manifest[image], equals(<String>[image, imageVariant])); + final Map<String, List<String>> jsonManifest = await extractAssetManifestJsonFromBundle(bundle); + final Map<Object?, Object?> smcBinManifest = await extractAssetManifestSmcBinFromBundle(bundle); + + final Map<String, List<Map<String, Object>>> expectedAssetManifest = <String, List<Map<String, Object>>>{ + image: <Map<String, Object>>[ + <String, Object>{ + 'asset': image, + }, + <String, Object>{ + 'asset': imageVariant, + 'dpr': 3.0 + }, + ], + }; + + expect(jsonManifest, equals(_assetManifestBinToJson(expectedAssetManifest))); + expect(smcBinManifest, equals(expectedAssetManifest)); }); - }); + testWithoutContext('Main assets are not included if the file does not exist', () async { + createPubspec(assets: <String>['assets/image.png']); + + // We intentionally do not add a 'assets/image.png'. + const String imageVariant = 'assets/2x/image.png'; + final List<String> assets = <String>[ + imageVariant, + ]; + + for (final String asset in assets) { + final File assetFile = fs.file(asset); + assetFile.createSync(recursive: true); + assetFile.writeAsStringSync(asset); + } + + final ManifestAssetBundle bundle = ManifestAssetBundle( + logger: BufferLogger.test(), + fileSystem: fs, + platform: platform, + ); + + await bundle.build( + packagesPath: '.packages', + flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), + ); + + final Map<String, List<Map<String, Object>>> expectedManifest = <String, List<Map<String, Object>>>{ + 'assets/image.png': <Map<String, Object>>[ + <String, Object>{ + 'asset': imageVariant, + 'dpr': 2.0 + }, + ], + }; + final Map<String, List<String>> jsonManifest = await extractAssetManifestJsonFromBundle(bundle); + final Map<Object?, Object?> smcBinManifest = await extractAssetManifestSmcBinFromBundle(bundle); + + expect(jsonManifest, equals(_assetManifestBinToJson(expectedManifest))); + expect(smcBinManifest, equals(expectedManifest)); + }); + }); group('AssetBundle asset variants (with Windows-style filepaths)', () { late final Platform platform; @@ -217,11 +325,40 @@ flutter: flutterProject: FlutterProject.fromDirectoryTest(fs.currentDirectory), ); - final Map<String, List<String>> manifest = await extractAssetManifestFromBundle(bundle); - - expect(manifest, hasLength(2)); - expect(manifest['assets/foo.jpg'], equals(<String>['assets/foo.jpg', 'assets/2x/foo.jpg'])); - expect(manifest['assets/somewhereElse/bar.jpg'], equals(<String>['assets/somewhereElse/bar.jpg', 'assets/somewhereElse/2x/bar.jpg'])); + final Map<String, List<Map<String, Object>>> expectedAssetManifest = <String, List<Map<String, Object>>>{ + 'assets/foo.jpg': <Map<String, Object>>[ + <String, Object>{ + 'asset': 'assets/foo.jpg', + }, + <String, Object>{ + 'asset': 'assets/2x/foo.jpg', + 'dpr': 2.0, + }, + ], + 'assets/somewhereElse/bar.jpg': <Map<String, Object>>[ + <String, Object>{ + 'asset': 'assets/somewhereElse/bar.jpg', + }, + <String, Object>{ + 'asset': 'assets/somewhereElse/2x/bar.jpg', + 'dpr': 2.0, + }, + ], + }; + + final Map<String, List<String>> jsonManifest = await extractAssetManifestJsonFromBundle(bundle); + final Map<Object?, Object?> smcBinManifest = await extractAssetManifestSmcBinFromBundle(bundle); + + expect(jsonManifest, equals(_assetManifestBinToJson(expectedAssetManifest))); + expect(smcBinManifest, equals(expectedAssetManifest)); }); }); } + +Map<Object, Object> _assetManifestBinToJson(Map<Object, Object> manifest) { + List<Object> convertList(List<Object> variants) => variants + .map((Object variant) => (variant as Map<Object?, Object?>)['asset']!) + .toList(); + + return manifest.map((Object key, Object value) => MapEntry<Object, Object>(key, convertList(value as List<Object>))); +} diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart index 6adbd025df737..5aab1be653bf3 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/common_test.dart @@ -94,6 +94,8 @@ void main() { '--track-widget-creation', '--aot', '--tfa', + '--target-os', + 'android', '--packages', '/.dart_tool/package_config.json', '--output-dill', @@ -132,6 +134,8 @@ void main() { '--track-widget-creation', '--aot', '--tfa', + '--target-os', + 'android', '--packages', '/.dart_tool/package_config.json', '--output-dill', @@ -171,6 +175,8 @@ void main() { '--track-widget-creation', '--aot', '--tfa', + '--target-os', + 'android', '--packages', '/.dart_tool/package_config.json', '--output-dill', @@ -211,6 +217,8 @@ void main() { '--track-widget-creation', '--aot', '--tfa', + '--target-os', + 'android', '--packages', '/.dart_tool/package_config.json', '--output-dill', diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/icon_tree_shaker_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/icon_tree_shaker_test.dart index dac8013dfe657..cb493e7cec765 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/icon_tree_shaker_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/icon_tree_shaker_test.dart @@ -21,6 +21,8 @@ const String inputPath = '/input/fonts/MaterialIcons-Regular.otf'; const String outputPath = '/output/fonts/MaterialIcons-Regular.otf'; const String relativePath = 'fonts/MaterialIcons-Regular.otf'; +final RegExp whitespace = RegExp(r'\s+'); + void main() { late BufferLogger logger; late MemoryFileSystem fileSystem; @@ -122,6 +124,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, + targetPlatform: TargetPlatform.android, ); expect( @@ -153,6 +156,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, + targetPlatform: TargetPlatform.android, ); expect( @@ -176,6 +180,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, + targetPlatform: TargetPlatform.android, ); expect( @@ -199,6 +204,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, + targetPlatform: TargetPlatform.android, ); expect( @@ -227,6 +233,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, + targetPlatform: TargetPlatform.android, ); final CompleterIOSink stdinSink = CompleterIOSink(); addConstFinderInvocation(appDill.path, stdout: validConstFinderResult); @@ -276,6 +283,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, + targetPlatform: TargetPlatform.android, ); final CompleterIOSink stdinSink = CompleterIOSink(); @@ -308,6 +316,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, + targetPlatform: TargetPlatform.android, ); final CompleterIOSink stdinSink = CompleterIOSink(); @@ -341,6 +350,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, + targetPlatform: platform, ); addConstFinderInvocation(appDill.path, stdout: constFinderResultWithInvalid); @@ -360,6 +370,97 @@ void main() { expect(processManager, hasNoRemainingExpectations); }); } + + testWithoutContext('Does not add 0x32 for non-web builds', () async { + final Environment environment = createEnvironment(<String, String>{ + kIconTreeShakerFlag: 'true', + kBuildMode: 'release', + }); + final File appDill = environment.buildDir.childFile('app.dill') + ..createSync(recursive: true); + + final IconTreeShaker iconTreeShaker = IconTreeShaker( + environment, + fontManifestContent, + logger: logger, + processManager: processManager, + fileSystem: fileSystem, + artifacts: artifacts, + targetPlatform: TargetPlatform.android_arm64, + ); + + addConstFinderInvocation( + appDill.path, + // Does not contain space char + stdout: validConstFinderResult, + ); + final CompleterIOSink stdinSink = CompleterIOSink(); + resetFontSubsetInvocation(stdinSink: stdinSink); + expect(processManager.hasRemainingExpectations, isTrue); + final File inputFont = fileSystem.file(inputPath) + ..writeAsBytesSync(List<int>.filled(2500, 0)); + fileSystem.file(outputPath) + ..createSync(recursive: true) + ..writeAsBytesSync(List<int>.filled(1200, 0)); + + final bool result = await iconTreeShaker.subsetFont( + input: inputFont, + outputPath: outputPath, + relativePath: relativePath, + ); + + expect(result, isTrue); + final List<String> codePoints = stdinSink.getAndClear().trim().split(whitespace); + expect(codePoints, isNot(contains('optional:32'))); + + expect(processManager, hasNoRemainingExpectations); + }); + + testWithoutContext('Ensures 0x32 is included for web builds', () async { + final Environment environment = createEnvironment(<String, String>{ + kIconTreeShakerFlag: 'true', + kBuildMode: 'release', + }); + final File appDill = environment.buildDir.childFile('app.dill') + ..createSync(recursive: true); + + final IconTreeShaker iconTreeShaker = IconTreeShaker( + environment, + fontManifestContent, + logger: logger, + processManager: processManager, + fileSystem: fileSystem, + artifacts: artifacts, + targetPlatform: TargetPlatform.web_javascript, + ); + + addConstFinderInvocation( + appDill.path, + // Does not contain space char + stdout: validConstFinderResult, + ); + final CompleterIOSink stdinSink = CompleterIOSink(); + resetFontSubsetInvocation(stdinSink: stdinSink); + expect(processManager.hasRemainingExpectations, isTrue); + final File inputFont = fileSystem.file(inputPath) + ..writeAsBytesSync(List<int>.filled(2500, 0)); + fileSystem.file(outputPath) + ..createSync(recursive: true) + ..writeAsBytesSync(List<int>.filled(1200, 0)); + + final bool result = await iconTreeShaker.subsetFont( + input: inputFont, + outputPath: outputPath, + relativePath: relativePath, + ); + + expect(result, isTrue); + final List<String> codePoints = stdinSink.getAndClear().trim().split(whitespace); + expect(codePoints, containsAllInOrder(const <String>['59470', 'optional:32'])); + + expect(processManager, hasNoRemainingExpectations); + }); + testWithoutContext('Non-zero font-subset exit code', () async { final Environment environment = createEnvironment(<String, String>{ kIconTreeShakerFlag: 'true', @@ -376,6 +477,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, + targetPlatform: TargetPlatform.android, ); final CompleterIOSink stdinSink = CompleterIOSink(); @@ -408,6 +510,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, + targetPlatform: TargetPlatform.android, ); final CompleterIOSink stdinSink = CompleterIOSink(throwOnAdd: true); @@ -442,6 +545,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, + targetPlatform: TargetPlatform.android, ); addConstFinderInvocation(appDill.path, stdout: validConstFinderResult); @@ -474,6 +578,7 @@ void main() { processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, + targetPlatform: TargetPlatform.android, ); addConstFinderInvocation(appDill.path, exitCode: -1); diff --git a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart index d2b79401eaa96..8e47324eda371 100644 --- a/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart +++ b/packages/flutter_tools/test/general.shard/build_system/targets/web_test.dart @@ -118,7 +118,7 @@ void main() { expect(generated, contains("import 'package:foo/main.dart' as entrypoint;")); // Main - expect(generated, contains('ui.webOnlyWarmupEngine(')); + expect(generated, contains('ui_web.bootstrapEngine(')); expect(generated, contains('entrypoint.main as _')); }, overrides: <Type, Generator>{ TemplateRenderer: () => const MustacheTemplateRenderer(), @@ -270,7 +270,7 @@ void main() { expect(generated, contains("import 'package:foo/main.dart' as entrypoint;")); // Main - expect(generated, contains('ui.webOnlyWarmupEngine(')); + expect(generated, contains('ui_web.bootstrapEngine(')); expect(generated, contains('entrypoint.main as _')); }, overrides: <Type, Generator>{ Platform: () => windows, @@ -295,7 +295,7 @@ void main() { expect(generated, contains("import 'package:foo/main.dart' as entrypoint;")); // Main - expect(generated, contains('ui.webOnlyWarmupEngine(')); + expect(generated, contains('ui_web.bootstrapEngine(')); expect(generated, contains('entrypoint.main as _')); }, overrides: <Type, Generator>{ TemplateRenderer: () => const MustacheTemplateRenderer(), @@ -351,7 +351,7 @@ void main() { expect(generated, contains("import 'package:foo/main.dart' as entrypoint;")); // Main - expect(generated, contains('ui.webOnlyWarmupEngine(')); + expect(generated, contains('ui_web.bootstrapEngine(')); expect(generated, contains('entrypoint.main as _')); }, overrides: <Type, Generator>{ TemplateRenderer: () => const MustacheTemplateRenderer(), diff --git a/packages/flutter_tools/test/general.shard/cache_test.dart b/packages/flutter_tools/test/general.shard/cache_test.dart index e4abb8edc7804..da9f6e10c8a9e 100644 --- a/packages/flutter_tools/test/general.shard/cache_test.dart +++ b/packages/flutter_tools/test/general.shard/cache_test.dart @@ -1042,7 +1042,11 @@ void main() { }); testWithoutContext('AndroidMavenArtifacts has a specified development artifact', () async { - final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(cache!, platform: FakePlatform()); + final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts( + cache!, + java: FakeJava(), + platform: FakePlatform(), + ); expect(mavenArtifacts.developmentArtifact, DevelopmentArtifact.androidMaven); }); @@ -1050,7 +1054,11 @@ void main() { final String? oldRoot = Cache.flutterRoot; Cache.flutterRoot = ''; try { - final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(cache!, platform: FakePlatform()); + final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts( + cache!, + java: FakeJava(), + platform: FakePlatform(), + ); expect(await mavenArtifacts.isUpToDate(memoryFileSystem!), isFalse); final Directory gradleWrapperDir = cache!.getArtifactDirectory('gradle_wrapper')..createSync(recursive: true); @@ -1082,7 +1090,11 @@ void main() { }); testUsingContext('AndroidMavenArtifacts is a no-op if the Android SDK is absent', () async { - final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts(cache!, platform: FakePlatform()); + final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts( + cache!, + java: FakeJava(), + platform: FakePlatform(), + ); expect(await mavenArtifacts.isUpToDate(memoryFileSystem!), isFalse); await mavenArtifacts.update(FakeArtifactUpdater(), BufferLogger.test(), memoryFileSystem!, FakeOperatingSystemUtils()); diff --git a/packages/flutter_tools/test/general.shard/channel_test.dart b/packages/flutter_tools/test/general.shard/channel_test.dart index f75c92fff2315..d973a2e180b5a 100644 --- a/packages/flutter_tools/test/general.shard/channel_test.dart +++ b/packages/flutter_tools/test/general.shard/channel_test.dart @@ -242,15 +242,18 @@ void main() { }); testUsingContext('can switch channels', () async { - fakeProcessManager.addCommands(<FakeCommand>[ - const FakeCommand( + fakeProcessManager.addCommands(const <FakeCommand>[ + FakeCommand( command: <String>['git', 'fetch'], ), - const FakeCommand( + FakeCommand( command: <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/beta'], ), - const FakeCommand( - command: <String>['git', 'checkout', 'beta', '--'] + FakeCommand( + command: <String>['git', 'checkout', 'beta', '--'] + ), + FakeCommand( + command: <String>['bin/flutter', '--no-color', '--no-version-check', 'precache'], ), ]); @@ -265,15 +268,18 @@ void main() { ); expect(testLogger.errorText, hasLength(0)); - fakeProcessManager.addCommands(<FakeCommand>[ - const FakeCommand( + fakeProcessManager.addCommands(const <FakeCommand>[ + FakeCommand( command: <String>['git', 'fetch'], ), - const FakeCommand( + FakeCommand( command: <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/stable'], ), - const FakeCommand( - command: <String>['git', 'checkout', 'stable', '--'] + FakeCommand( + command: <String>['git', 'checkout', 'stable', '--'], + ), + FakeCommand( + command: <String>['bin/flutter', '--no-color', '--no-version-check', 'precache'], ), ]); @@ -286,16 +292,19 @@ void main() { }); testUsingContext('switching channels prompts to run flutter upgrade', () async { - fakeProcessManager.addCommands(<FakeCommand>[ - const FakeCommand( + fakeProcessManager.addCommands(const <FakeCommand>[ + FakeCommand( command: <String>['git', 'fetch'], ), - const FakeCommand( + FakeCommand( command: <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/beta'], ), - const FakeCommand( + FakeCommand( command: <String>['git', 'checkout', 'beta', '--'] ), + FakeCommand( + command: <String>['bin/flutter', '--no-color', '--no-version-check', 'precache'], + ), ]); final ChannelCommand command = ChannelCommand(); @@ -322,16 +331,19 @@ void main() { // This verifies that bug https://github.com/flutter/flutter/issues/21134 // doesn't return. testUsingContext('removes version stamp file when switching channels', () async { - fakeProcessManager.addCommands(<FakeCommand>[ - const FakeCommand( + fakeProcessManager.addCommands(const <FakeCommand>[ + FakeCommand( command: <String>['git', 'fetch'], ), - const FakeCommand( + FakeCommand( command: <String>['git', 'show-ref', '--verify', '--quiet', 'refs/heads/beta'], ), - const FakeCommand( + FakeCommand( command: <String>['git', 'checkout', 'beta', '--'] ), + FakeCommand( + command: <String>['bin/flutter', '--no-color', '--no-version-check', 'precache'], + ), ]); final File versionCheckFile = globals.cache.getStampFileFor( diff --git a/packages/flutter_tools/test/general.shard/compile_expression_test.dart b/packages/flutter_tools/test/general.shard/compile_expression_test.dart index d081214abcd1d..ac35313bfafc9 100644 --- a/packages/flutter_tools/test/general.shard/compile_expression_test.dart +++ b/packages/flutter_tools/test/general.shard/compile_expression_test.dart @@ -50,7 +50,7 @@ void main() { testWithoutContext('compile expression fails if not previously compiled', () async { final CompilerOutput? result = await generator.compileExpression( - '2+2', null, null, null, null, false); + '2+2', null, null, null, null, null, null, null, null, false); expect(result, isNull); }); @@ -93,7 +93,7 @@ void main() { 'result def\nline1\nline2\ndef\ndef /path/to/main.dart.dill.incremental 0\n' ))); generator.compileExpression( - '2+2', null, null, null, null, false).then( + '2+2', null, null, null, null, null, null, null, null, false).then( (CompilerOutput? outputExpression) { expect(outputExpression, isNotNull); expect(outputExpression!.expressionData, <int>[1, 2, 3, 4]); @@ -142,7 +142,8 @@ void main() { // The test manages timing via completers. final Completer<bool> lastExpressionCompleted = Completer<bool>(); unawaited( - generator.compileExpression('0+1', null, null, null, null, false).then( + generator.compileExpression('0+1', null, null, null, null, null, null, + null, null, false).then( (CompilerOutput? outputExpression) { expect(outputExpression, isNotNull); expect(outputExpression!.expressionData, <int>[0, 1, 2, 3]); @@ -159,7 +160,8 @@ void main() { // The test manages timing via completers. unawaited( - generator.compileExpression('1+1', null, null, null, null, false).then( + generator.compileExpression('1+1', null, null, null, null, null, null, + null, null, false).then( (CompilerOutput? outputExpression) { expect(outputExpression, isNotNull); expect(outputExpression!.expressionData, <int>[4, 5, 6, 7]); diff --git a/packages/flutter_tools/test/general.shard/dap/flutter_adapter_test.dart b/packages/flutter_tools/test/general.shard/dap/flutter_adapter_test.dart index c6d347d28c917..d5b958ae23958 100644 --- a/packages/flutter_tools/test/general.shard/dap/flutter_adapter_test.dart +++ b/packages/flutter_tools/test/general.shard/dap/flutter_adapter_test.dart @@ -11,7 +11,7 @@ import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/debug_adapters/flutter_adapter.dart'; import 'package:flutter_tools/src/debug_adapters/flutter_adapter_args.dart'; -import 'package:flutter_tools/src/globals.dart' as globals show platform; +import 'package:flutter_tools/src/globals.dart' as globals show fs, platform; import 'package:test/fake.dart'; import 'package:test/test.dart'; import 'package:vm_service/vm_service.dart'; @@ -186,6 +186,30 @@ void main() { expect(adapter.dapToFlutterRequests, isNot(contains('app.stop'))); }); + test('does not call "app.restart" before app has been started', () async { + final MockFlutterDebugAdapter adapter = MockFlutterDebugAdapter( + fileSystem: MemoryFileSystem.test(style: fsStyle), + platform: platform, + simulateAppStarted: false, + ); + + final Completer<void> launchCompleter = Completer<void>(); + final FlutterLaunchRequestArguments launchArgs = FlutterLaunchRequestArguments( + cwd: '/project', + program: 'foo.dart', + ); + final Completer<void> restartCompleter = Completer<void>(); + final RestartArguments restartArgs = RestartArguments(); + + await adapter.configurationDoneRequest(MockRequest(), null, () {}); + await adapter.launchRequest(MockRequest(), launchArgs, launchCompleter.complete); + await launchCompleter.future; + await adapter.restartRequest(MockRequest(), restartArgs, restartCompleter.complete); + await restartCompleter.future; + + expect(adapter.dapToFlutterRequests, isNot(contains('app.restart'))); + }); + test('includes Dart Debug extension progress update', () async { final MockFlutterDebugAdapter adapter = MockFlutterDebugAdapter( fileSystem: MemoryFileSystem.test(style: fsStyle), @@ -275,6 +299,107 @@ void main() { ])); }); + test('runs "flutter attach" with --debug-uri if vmServiceUri is passed', () async { + final MockFlutterDebugAdapter adapter = MockFlutterDebugAdapter( + fileSystem: MemoryFileSystem.test(style: fsStyle), + platform: platform, + ); + final Completer<void> responseCompleter = Completer<void>(); + + final FlutterAttachRequestArguments args = + FlutterAttachRequestArguments( + cwd: '/project', + program: 'program/main.dart', + vmServiceUri: 'ws://1.2.3.4/ws' + ); + + await adapter.configurationDoneRequest(MockRequest(), null, () {}); + await adapter.attachRequest( + MockRequest(), args, responseCompleter.complete); + await responseCompleter.future; + + expect( + adapter.processArgs, + containsAllInOrder(<String>[ + 'attach', + '--machine', + '--debug-uri', + 'ws://1.2.3.4/ws', + '--target', + 'program/main.dart', + ])); + }); + + test('runs "flutter attach" with --debug-uri if vmServiceInfoFile exists', () async { + final MockFlutterDebugAdapter adapter = MockFlutterDebugAdapter( + fileSystem: MemoryFileSystem.test(style: fsStyle), + platform: platform, + ); + final Completer<void> responseCompleter = Completer<void>(); + final File serviceInfoFile = globals.fs.systemTempDirectory.createTempSync('dap_flutter_attach_vmServiceInfoFile').childFile('vmServiceInfo.json'); + + final FlutterAttachRequestArguments args = + FlutterAttachRequestArguments( + cwd: '/project', + program: 'program/main.dart', + vmServiceInfoFile: serviceInfoFile.path, + ); + + // Write the service info file before trying to attach: + serviceInfoFile.writeAsStringSync('{ "uri": "ws://1.2.3.4/ws" }'); + + await adapter.configurationDoneRequest(MockRequest(), null, () {}); + await adapter.attachRequest(MockRequest(), args, responseCompleter.complete); + await responseCompleter.future; + + expect( + adapter.processArgs, + containsAllInOrder(<String>[ + 'attach', + '--machine', + '--debug-uri', + 'ws://1.2.3.4/ws', + '--target', + 'program/main.dart', + ])); + }); + + test('runs "flutter attach" with --debug-uri if vmServiceInfoFile is created later', () async { + final MockFlutterDebugAdapter adapter = MockFlutterDebugAdapter( + fileSystem: MemoryFileSystem.test(style: fsStyle), + platform: platform, + ); + final Completer<void> responseCompleter = Completer<void>(); + final File serviceInfoFile = globals.fs.systemTempDirectory.createTempSync('dap_flutter_attach_vmServiceInfoFile').childFile('vmServiceInfo.json'); + + final FlutterAttachRequestArguments args = + FlutterAttachRequestArguments( + cwd: '/project', + program: 'program/main.dart', + vmServiceInfoFile: serviceInfoFile.path, + ); + + + await adapter.configurationDoneRequest(MockRequest(), null, () {}); + final Future<void> attachResponseFuture = adapter.attachRequest(MockRequest(), args, responseCompleter.complete); + // Write the service info file a little later to ensure we detect it: + await pumpEventQueue(times:5000); + serviceInfoFile.writeAsStringSync('{ "uri": "ws://1.2.3.4/ws" }'); + await attachResponseFuture; + await responseCompleter.future; + + expect( + adapter.processArgs, + containsAllInOrder(<String>[ + 'attach', + '--machine', + '--debug-uri', + 'ws://1.2.3.4/ws', + '--target', + 'program/main.dart', + ])); + }); + test('does not record the VMs PID for terminating', () async { final MockFlutterDebugAdapter adapter = MockFlutterDebugAdapter( fileSystem: MemoryFileSystem.test(style: fsStyle), diff --git a/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart b/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart index 135792521f3ba..53091963bbe4d 100644 --- a/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart +++ b/packages/flutter_tools/test/general.shard/dart/pub_get_test.dart @@ -940,6 +940,8 @@ exit code: 66 context: PubContext.flutterTests); expect(logger.statusText, contains('Found an existing Pub cache at /global/.pub-cache')); + expect(logger.statusText, + contains('It can be reset by running `dart pub cache clean`')); expect( logger.statusText, contains( diff --git a/packages/flutter_tools/test/general.shard/desktop_device_test.dart b/packages/flutter_tools/test/general.shard/desktop_device_test.dart index 8967bd00a5aed..57631ac8fe70c 100644 --- a/packages/flutter_tools/test/general.shard/desktop_device_test.dart +++ b/packages/flutter_tools/test/general.shard/desktop_device_test.dart @@ -300,6 +300,66 @@ void main() { ), ); }); + + testWithoutContext('Desktop devices that support impeller pass through the enable-impeller flag', () async { + final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ + const FakeCommand( + command: <String>['debug'], + exitCode: -1, + environment: <String, String>{ + 'FLUTTER_ENGINE_SWITCH_1': 'enable-dart-profiling=true', + 'FLUTTER_ENGINE_SWITCH_2': 'enable-impeller=true', + 'FLUTTER_ENGINE_SWITCH_3': 'enable-checked-mode=true', + 'FLUTTER_ENGINE_SWITCH_4': 'verify-entry-points=true', + 'FLUTTER_ENGINE_SWITCHES': '4' + } + ), + ]); + final FakeDesktopDevice device = setUpDesktopDevice( + processManager: processManager, + supportsImpeller: true, + ); + + final FakeApplicationPackage package = FakeApplicationPackage(); + await device.startApp( + package, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.enabled( + BuildInfo.debug, + enableImpeller: ImpellerStatus.enabled, + dartEntrypointArgs: <String>[], + ), + ); + }); + + testWithoutContext('Desktop devices that do not support impeller ignore the enable-impeller flag', () async { + final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ + const FakeCommand( + command: <String>['debug'], + exitCode: -1, + environment: <String, String>{ + 'FLUTTER_ENGINE_SWITCH_1': 'enable-dart-profiling=true', + 'FLUTTER_ENGINE_SWITCH_2': 'enable-checked-mode=true', + 'FLUTTER_ENGINE_SWITCH_3': 'verify-entry-points=true', + 'FLUTTER_ENGINE_SWITCHES': '3' + } + ), + ]); + final FakeDesktopDevice device = setUpDesktopDevice( + processManager: processManager, + ); + + final FakeApplicationPackage package = FakeApplicationPackage(); + await device.startApp( + package, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.enabled( + BuildInfo.debug, + enableImpeller: ImpellerStatus.enabled, + dartEntrypointArgs: <String>[], + ), + ); + }); } FakeDesktopDevice setUpDesktopDevice({ @@ -308,6 +368,7 @@ FakeDesktopDevice setUpDesktopDevice({ ProcessManager? processManager, OperatingSystemUtils? operatingSystemUtils, bool nullExecutablePathForDevice = false, + bool supportsImpeller = false, }) { return FakeDesktopDevice( fileSystem: fileSystem ?? MemoryFileSystem.test(), @@ -315,6 +376,7 @@ FakeDesktopDevice setUpDesktopDevice({ processManager: processManager ?? FakeProcessManager.any(), operatingSystemUtils: operatingSystemUtils ?? FakeOperatingSystemUtils(), nullExecutablePathForDevice: nullExecutablePathForDevice, + supportsImpeller: supportsImpeller, ); } @@ -326,6 +388,7 @@ class FakeDesktopDevice extends DesktopDevice { required FileSystem fileSystem, required OperatingSystemUtils operatingSystemUtils, this.nullExecutablePathForDevice = false, + this.supportsImpeller = false, }) : super( 'dummy', platformType: PlatformType.linux, @@ -344,6 +407,9 @@ class FakeDesktopDevice extends DesktopDevice { final bool nullExecutablePathForDevice; + @override + final bool supportsImpeller; + @override String get name => 'dummy'; diff --git a/packages/flutter_tools/test/general.shard/devfs_test.dart b/packages/flutter_tools/test/general.shard/devfs_test.dart index bffa996c96cf7..1f02688181abe 100644 --- a/packages/flutter_tools/test/general.shard/devfs_test.dart +++ b/packages/flutter_tools/test/general.shard/devfs_test.dart @@ -211,7 +211,7 @@ void main() { FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, responseError: const OSError('Connection Reset by peer')), FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, responseError: const OSError('Connection Reset by peer')), // This is the value of `<int>[1, 2, 3, 4, 5]` run through `osUtils.gzipLevel1Stream`. - FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, body: <int>[for (List<int> chunk in expectedEncoded) ...chunk]), + FakeRequest(Uri.parse('http://localhost'), method: HttpMethod.put, body: <int>[for (final List<int> chunk in expectedEncoded) ...chunk]), ]), uploadRetryThrottle: Duration.zero, ); diff --git a/packages/flutter_tools/test/general.shard/device_test.dart b/packages/flutter_tools/test/general.shard/device_test.dart index 2774536308214..860a58ff687ca 100644 --- a/packages/flutter_tools/test/general.shard/device_test.dart +++ b/packages/flutter_tools/test/general.shard/device_test.dart @@ -885,6 +885,26 @@ void main() { ); }); + testWithoutContext('Get launch arguments for physical CoreDevice with debugging enabled with no launch arguments', () { + final DebuggingOptions original = DebuggingOptions.enabled( + BuildInfo.debug, + ); + + final List<String> launchArguments = original.getIOSLaunchArguments( + EnvironmentType.physical, + null, + <String, Object?>{}, + isCoreDevice: true, + ); + + expect( + launchArguments.join(' '), + <String>[ + '--enable-dart-profiling', + ].join(' '), + ); + }); + testWithoutContext('Get launch arguments for physical device with iPv4 network connection', () { final DebuggingOptions original = DebuggingOptions.enabled( BuildInfo.debug, diff --git a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart index d19002de036be..c439a0787e9b2 100644 --- a/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart +++ b/packages/flutter_tools/test/general.shard/fuchsia/fuchsia_device_test.dart @@ -737,7 +737,7 @@ void main() { method: kListViewsMethod, jsonResponse: <String, Object>{ 'views': <Object>[ - for (FlutterView view in views) view.toJson(), + for (final FlutterView view in views) view.toJson(), ], }, ), diff --git a/packages/flutter_tools/test/general.shard/generate_localizations_test.dart b/packages/flutter_tools/test/general.shard/generate_localizations_test.dart index 340cca2f830fb..ba35df50a8805 100644 --- a/packages/flutter_tools/test/general.shard/generate_localizations_test.dart +++ b/packages/flutter_tools/test/general.shard/generate_localizations_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:file/memory.dart'; +import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/convert.dart'; @@ -12,6 +13,7 @@ import 'package:flutter_tools/src/localizations/localizations_utils.dart'; import 'package:yaml/yaml.dart'; import '../src/common.dart'; +import '../src/fake_process_manager.dart'; const String defaultTemplateArbFileName = 'app_en.arb'; const String defaultOutputFileString = 'output-localization-file.dart'; @@ -62,13 +64,78 @@ void _standardFlutterDirectoryL10nSetup(FileSystem fs) { void main() { late MemoryFileSystem fs; late BufferLogger logger; + late Artifacts artifacts; + late ProcessManager processManager; late String defaultL10nPathString; late String syntheticPackagePath; late String syntheticL10nPackagePath; + LocalizationsGenerator setupLocalizations( + Map<String, String> localeToArbFile, + { + String? yamlFile, + String? outputPathString, + String? outputFileString, + String? headerString, + String? headerFile, + String? untranslatedMessagesFile, + bool useSyntheticPackage = true, + bool isFromYaml = false, + bool usesNullableGetter = true, + String? inputsAndOutputsListPath, + List<String>? preferredSupportedLocales, + bool useDeferredLoading = false, + bool useEscaping = false, + bool areResourceAttributeRequired = false, + bool suppressWarnings = false, + void Function(Directory)? setup, + } + ) { + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + for (final String locale in localeToArbFile.keys) { + l10nDirectory.childFile('app_$locale.arb') + .writeAsStringSync(localeToArbFile[locale]!); + } + if (setup != null) { + setup(l10nDirectory); + } + return LocalizationsGenerator( + fileSystem: fs, + inputPathString: l10nDirectory.path, + outputPathString: outputPathString ?? l10nDirectory.path, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: outputFileString ?? defaultOutputFileString, + classNameString: defaultClassNameString, + headerString: headerString, + headerFile: headerFile, + logger: logger, + untranslatedMessagesFile: untranslatedMessagesFile, + useSyntheticPackage: useSyntheticPackage, + inputsAndOutputsListPath: inputsAndOutputsListPath, + usesNullableGetter: usesNullableGetter, + preferredSupportedLocales: preferredSupportedLocales, + useDeferredLoading: useDeferredLoading, + useEscaping: useEscaping, + areResourceAttributesRequired: areResourceAttributeRequired, + suppressWarnings: suppressWarnings, + ) + ..loadResources() + ..writeOutputFiles(isFromYaml: isFromYaml); + } + + String getGeneratedFileContent({String? locale}) { + final String fileName = locale == null ? 'output-localization-file.dart' : 'output-localization-file_$locale.dart'; + return fs.file( + fs.path.join(syntheticL10nPackagePath, fileName) + ).readAsStringSync(); + } + setUp(() { fs = MemoryFileSystem.test(); logger = BufferLogger.test(); + artifacts = Artifacts.test(); + processManager = FakeProcessManager.empty(); defaultL10nPathString = fs.path.join('lib', 'l10n'); syntheticPackagePath = fs.path.join('.dart_tool', 'flutter_gen'); @@ -263,61 +330,28 @@ void main() { }); testWithoutContext('correctly adds a headerString when it is set', () { - _standardFlutterDirectoryL10nSetup(fs); - - final LocalizationsGenerator generator = LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - headerString: '/// Sample header', - logger: logger, - ); - + final LocalizationsGenerator generator = setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'es': singleEsMessageArbFileString, + }, headerString: '/// Sample header'); expect(generator.header, '/// Sample header'); }); testWithoutContext('correctly adds a headerFile when it is set', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString) - ..childFile(esArbFileName).writeAsStringSync(singleEsMessageArbFileString) - ..childFile('header.txt').writeAsStringSync('/// Sample header in a text file'); - - final LocalizationsGenerator generator = LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - headerFile: 'header.txt', - logger: logger, - ); - + final LocalizationsGenerator generator = setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'es': singleEsMessageArbFileString, + }, headerFile: 'header.txt', setup: (Directory l10nDirectory) { + l10nDirectory.childFile('header.txt').writeAsStringSync('/// Sample header in a text file'); + }); expect(generator.header, '/// Sample header in a text file'); }); testWithoutContext('sets templateArbFileName with more than one underscore correctly', () { - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile('app_localizations_en.arb') - .writeAsStringSync(singleMessageArbFileString); - l10nDirectory.childFile('app_localizations_es.arb') - .writeAsStringSync(singleEsMessageArbFileString); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: 'app_localizations_en.arb', - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'es': singleEsMessageArbFileString, + }); final Directory outputDirectory = fs.directory(syntheticL10nPackagePath); expect(outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue); expect(outputDirectory.childFile('output-localization-file_en.dart').existsSync(), isTrue); @@ -325,22 +359,13 @@ void main() { }); testWithoutContext('filenames with invalid locales should not be recognized', () { - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile('app_localizations_en.arb') - .writeAsStringSync(singleMessageArbFileString); - l10nDirectory.childFile('app_localizations_en_CA_foo.arb') - .writeAsStringSync(singleMessageArbFileString); expect( () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: 'app_localizations_en.arb', - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ).loadResources(); + // This attempts to create 'app_localizations_en_CA_foo.arb'. + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'en_CA_foo': singleMessageArbFileString, + }); }, throwsA(isA<L10nException>().having( (L10nException e) => e.message, @@ -351,28 +376,12 @@ void main() { }); testWithoutContext('correctly creates an untranslated messages file (useSyntheticPackage = true)', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(twoMessageArbFileString) - ..childFile(esArbFileName).writeAsStringSync(singleEsMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - untranslatedMessagesFile: fs.path.join('lib', 'l10n', 'unimplemented_message_translations.json'), - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final File unimplementedOutputFile = fs.file( - fs.path.join('lib', 'l10n', 'unimplemented_message_translations.json'), - ); - final String unimplementedOutputString = unimplementedOutputFile.readAsStringSync(); + final String untranslatedMessagesFilePath = fs.path.join('lib', 'l10n', 'unimplemented_message_translations.json'); + setupLocalizations(<String, String>{ + 'en': twoMessageArbFileString, + 'es': singleEsMessageArbFileString, + }, untranslatedMessagesFile: untranslatedMessagesFilePath); + final String unimplementedOutputString = fs.file(untranslatedMessagesFilePath).readAsStringSync(); try { // Since ARB file is essentially JSON, decoding it should not fail. json.decode(unimplementedOutputString); @@ -384,29 +393,12 @@ void main() { }); testWithoutContext('correctly creates an untranslated messages file (useSyntheticPackage = false)', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(twoMessageArbFileString) - ..childFile(esArbFileName).writeAsStringSync(singleEsMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - useSyntheticPackage: false, - untranslatedMessagesFile: fs.path.join('lib', 'l10n', 'unimplemented_message_translations.json'), - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final File unimplementedOutputFile = fs.file( - fs.path.join('lib', 'l10n', 'unimplemented_message_translations.json'), - ); - final String unimplementedOutputString = unimplementedOutputFile.readAsStringSync(); + final String untranslatedMessagesFilePath = fs.path.join('lib', 'l10n', 'unimplemented_message_translations.json'); + setupLocalizations(<String, String>{ + 'en': twoMessageArbFileString, + 'es': singleMessageArbFileString, + }, useSyntheticPackage: false, untranslatedMessagesFile: untranslatedMessagesFilePath); + final String unimplementedOutputString = fs.file(untranslatedMessagesFilePath).readAsStringSync(); try { // Since ARB file is essentially JSON, decoding it should not fail. json.decode(unimplementedOutputString); @@ -421,24 +413,10 @@ void main() { 'untranslated messages suggestion is printed when translation is missing: ' 'command line message', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(twoMessageArbFileString) - ..childFile(esArbFileName).writeAsStringSync(singleEsMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - useSyntheticPackage: false, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - + setupLocalizations(<String, String>{ + 'en': twoMessageArbFileString, + 'es': singleEsMessageArbFileString, + }); expect( logger.statusText, contains('To see a detailed report, use the --untranslated-messages-file'), @@ -454,22 +432,10 @@ void main() { 'untranslated messages suggestion is printed when translation is missing: ' 'l10n.yaml message', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(twoMessageArbFileString) - ..childFile(esArbFileName).writeAsStringSync(singleEsMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(isFromYaml: true); - + setupLocalizations(<String, String>{ + 'en': twoMessageArbFileString, + 'es': singleEsMessageArbFileString, + }, isFromYaml: true); expect( logger.statusText, contains('To see a detailed report, use the untranslated-messages-file'), @@ -485,45 +451,26 @@ void main() { 'unimplemented messages suggestion is not printed when all messages ' 'are fully translated', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(twoMessageArbFileString) - ..childFile(esArbFileName).writeAsStringSync(twoMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - expect(logger.statusText, ''); + setupLocalizations(<String, String>{ + 'en': twoMessageArbFileString, + 'es': twoMessageArbFileString, + }); + expect(logger.statusText, equals('')); }, ); testWithoutContext('untranslated messages file included in generated JSON list of outputs', () { - _standardFlutterDirectoryL10nSetup(fs); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, + final String untranslatedMessagesFilePath = fs.path.join('lib', 'l10n', 'unimplemented_message_translations.json'); + setupLocalizations( + <String, String>{ + 'en': twoMessageArbFileString, + 'es': singleEsMessageArbFileString, + }, + untranslatedMessagesFile: untranslatedMessagesFilePath, inputsAndOutputsListPath: syntheticL10nPackagePath, - untranslatedMessagesFile: fs.path.join('lib', 'l10n', 'unimplemented_message_translations.json'), - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - + ); final File inputsAndOutputsList = fs.file( - fs.path.join(syntheticL10nPackagePath, 'gen_l10n_inputs_and_outputs.json'), + fs.path.join(syntheticL10nPackagePath, 'gen_l10n_inputs_and_outputs.json') ); expect(inputsAndOutputsList.existsSync(), isTrue); final Map<String, dynamic> jsonResult = json.decode( @@ -629,30 +576,18 @@ void main() { 'generates nullable localizations class getter via static `of` method ' 'by default', () { - _standardFlutterDirectoryL10nSetup(fs); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: fs.path.join('lib', 'l10n', 'output'), - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - useSyntheticPackage: false, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final Directory outputDirectory = fs.directory('lib').childDirectory('l10n').childDirectory('output'); - expect(outputDirectory.existsSync(), isTrue); - expect(outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue); + final LocalizationsGenerator generator = setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'es': singleEsMessageArbFileString, + }); + expect(generator.outputDirectory.existsSync(), isTrue); + expect(generator.outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue); expect( - outputDirectory.childFile('output-localization-file.dart').readAsStringSync(), + generator.outputDirectory.childFile('output-localization-file.dart').readAsStringSync(), contains('static AppLocalizations? of(BuildContext context)'), ); expect( - outputDirectory.childFile('output-localization-file.dart').readAsStringSync(), + generator.outputDirectory.childFile('output-localization-file.dart').readAsStringSync(), contains('return Localizations.of<AppLocalizations>(context, AppLocalizations);'), ); }, @@ -661,51 +596,28 @@ void main() { testWithoutContext( 'can generate non-nullable localizations class getter via static `of` method ', () { - _standardFlutterDirectoryL10nSetup(fs); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: fs.path.join('lib', 'l10n', 'output'), - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - useSyntheticPackage: false, - usesNullableGetter: false, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final Directory outputDirectory = fs.directory('lib').childDirectory('l10n').childDirectory('output'); - expect(outputDirectory.existsSync(), isTrue); - expect(outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue); + final LocalizationsGenerator generator = setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'es': singleEsMessageArbFileString, + }, usesNullableGetter: false); + expect(generator.outputDirectory.existsSync(), isTrue); + expect(generator.outputDirectory.childFile('output-localization-file.dart').existsSync(), isTrue); expect( - outputDirectory.childFile('output-localization-file.dart').readAsStringSync(), + generator.outputDirectory.childFile('output-localization-file.dart').readAsStringSync(), contains('static AppLocalizations of(BuildContext context)'), ); expect( - outputDirectory.childFile('output-localization-file.dart').readAsStringSync(), + generator.outputDirectory.childFile('output-localization-file.dart').readAsStringSync(), contains('return Localizations.of<AppLocalizations>(context, AppLocalizations)!;'), ); }, ); testWithoutContext('creates list of inputs and outputs when file path is specified', () { - _standardFlutterDirectoryL10nSetup(fs); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - inputsAndOutputsListPath: syntheticL10nPackagePath, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'es': singleEsMessageArbFileString, + }, inputsAndOutputsListPath: syntheticL10nPackagePath); final File inputsAndOutputsList = fs.file( fs.path.join(syntheticL10nPackagePath, 'gen_l10n_inputs_and_outputs.json'), ); @@ -725,24 +637,18 @@ void main() { }); testWithoutContext('setting both a headerString and a headerFile should fail', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString) - ..childFile(esArbFileName).writeAsStringSync(singleEsMessageArbFileString) - ..childFile('header.txt').writeAsStringSync('/// Sample header in a text file'); - expect( () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - headerString: '/// Sample header for localizations file.', + setupLocalizations( + <String, String>{ + 'en': singleMessageArbFileString, + 'es': singleEsMessageArbFileString, + }, + headerString: '/// Sample header in a text file', headerFile: 'header.txt', - logger: logger, + setup: (Directory l10nDirectory) { + l10nDirectory.childFile('header.txt').writeAsStringSync('/// Sample header in a text file'); + }, ); }, throwsA(isA<L10nException>().having( @@ -754,27 +660,12 @@ void main() { }); testWithoutContext('setting a headerFile that does not exist should fail', () { - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleMessageArbFileString); - l10nDirectory.childFile(esArbFileName) - .writeAsStringSync(singleEsMessageArbFileString); - l10nDirectory.childFile('header.txt') - .writeAsStringSync('/// Sample header in a text file'); - expect( () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - headerFile: 'header.tx', // Intentionally spelled incorrectly - logger: logger, - ); + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'es': singleEsMessageArbFileString, + }, headerFile: 'header.txt'); }, throwsA(isA<L10nException>().having( (L10nException e) => e.message, @@ -793,7 +684,7 @@ void main() { logger.printError('An error output from a different tool in flutter_tools'); // Should run without error. - generateLocalizations( + await generateLocalizations( fileSystem: fs, options: LocalizationOptions( arbDir: Uri.directory(defaultL10nPathString).path, @@ -804,6 +695,8 @@ void main() { logger: logger, projectDir: fs.currentDirectory, dependenciesDir: fs.currentDirectory, + artifacts: artifacts, + processManager: processManager, ); }); @@ -825,12 +718,14 @@ void main() { ); // Verify that values are correctly passed through the localizations target. - final LocalizationsGenerator generator = generateLocalizations( + final LocalizationsGenerator generator = await generateLocalizations( fileSystem: fs, options: options, logger: logger, projectDir: fs.currentDirectory, dependenciesDir: fs.currentDirectory, + artifacts: artifacts, + processManager: processManager, ); expect(generator.inputDirectory.path, '/lib/l10n/'); @@ -894,6 +789,8 @@ flutter: logger: BufferLogger.test(), projectDir: fs.currentDirectory, dependenciesDir: fs.currentDirectory, + artifacts: artifacts, + processManager: processManager, ), throwsToolExit( message: 'Attempted to generate localizations code without having the ' @@ -906,7 +803,7 @@ flutter: _standardFlutterDirectoryL10nSetup(fs); // Test without headers. - generateLocalizations( + await generateLocalizations( fileSystem: fs, options: LocalizationOptions( arbDir: Uri.directory(defaultL10nPathString).path, @@ -917,6 +814,8 @@ flutter: logger: BufferLogger.test(), projectDir: fs.currentDirectory, dependenciesDir: fs.currentDirectory, + artifacts: artifacts, + processManager: processManager, ); expect(fs.file('/lib/l10n/app_localizations_en.dart').readAsStringSync(), ''' @@ -932,7 +831,7 @@ class AppLocalizationsEn extends AppLocalizations { '''); // Test with headers. - generateLocalizations( + await generateLocalizations( fileSystem: fs, options: LocalizationOptions( header: 'HEADER', @@ -944,6 +843,8 @@ class AppLocalizationsEn extends AppLocalizations { logger: logger, projectDir: fs.currentDirectory, dependenciesDir: fs.currentDirectory, + artifacts: artifacts, + processManager: processManager, ); expect(fs.file('/lib/l10n/app_localizations_en.dart').readAsStringSync(), ''' @@ -1307,28 +1208,13 @@ class AppLocalizationsEn extends AppLocalizations { group('writeOutputFiles', () { testWithoutContext('multiple messages with syntax error all log their errors', () { - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(r''' + try { + setupLocalizations(<String, String>{ + 'en': r''' { "msg1": "{", "msg2": "{ {" -}'''); - l10nDirectory.childFile(esArbFileName) - .writeAsStringSync(singleEsMessageArbFileString); - try { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); +}'''}); } on L10nException catch (error) { expect(error.message, equals('Found syntax errors.')); expect(logger.errorText, contains(''' @@ -1342,147 +1228,62 @@ class AppLocalizationsEn extends AppLocalizations { }); testWithoutContext('no description generates generic comment', () { - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(r''' + setupLocalizations(<String, String>{ + 'en': r''' { "helloWorld": "Hello world!" -}'''); - l10nDirectory.childFile(esArbFileName) - .writeAsStringSync(singleEsMessageArbFileString); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - final File baseLocalizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') - ); - expect(baseLocalizationsFile.existsSync(), isTrue); - - final String baseLocalizationsFileContents = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') - ).readAsStringSync(); - expect(baseLocalizationsFileContents, contains('/// No description provided for @helloWorld.')); +}''' + }); + expect(getGeneratedFileContent(), contains('/// No description provided for @helloWorld.')); }); - testWithoutContext('multiline descriptions are correctly formatted as comments', () { - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(r''' + testWithoutContext('multiline descriptions are correctly formatted as comments', () { + setupLocalizations(<String, String>{ + 'en': r''' { "helloWorld": "Hello world!", "@helloWorld": { "description": "The generic example string in every language.\nUse this for tests!" } -}'''); - l10nDirectory.childFile(esArbFileName) - .writeAsStringSync(singleEsMessageArbFileString); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - final File baseLocalizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') - ); - expect(baseLocalizationsFile.existsSync(), isTrue); - - final String baseLocalizationsFileContents = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') - ).readAsStringSync(); - expect(baseLocalizationsFileContents, contains(''' +}'''}); + expect(getGeneratedFileContent(), contains(''' /// The generic example string in every language. /// Use this for tests!''')); }); testWithoutContext('message without placeholders - should generate code comment with description and template message translation', () { - _standardFlutterDirectoryL10nSetup(fs); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final File baseLocalizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') - ); - expect(baseLocalizationsFile.existsSync(), isTrue); - - final String baseLocalizationsFileContents = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') - ).readAsStringSync(); - expect(baseLocalizationsFileContents, contains('/// Title for the application.')); - expect(baseLocalizationsFileContents, contains(''' + setupLocalizations(<String, String> { + 'en': singleMessageArbFileString, + 'es': singleEsMessageArbFileString, + }); + final String content = getGeneratedFileContent(); + expect(content, contains('/// Title for the application.')); + expect(content, contains(''' /// In en, this message translates to: /// **'Title'**''')); }); testWithoutContext('template message translation handles newline characters', () { - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(r''' + setupLocalizations(<String, String>{ + 'en': r''' { "title": "Title \n of the application", "@title": { "description": "Title for the application." } -}'''); - l10nDirectory.childFile(esArbFileName) - .writeAsStringSync(singleEsMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final File baseLocalizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') - ); - expect(baseLocalizationsFile.existsSync(), isTrue); - - final String baseLocalizationsFileContents = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') - ).readAsStringSync(); - expect(baseLocalizationsFileContents, contains('/// Title for the application.')); - expect(baseLocalizationsFileContents, contains(r''' +}''', + 'es': singleEsMessageArbFileString + }); + final String content = getGeneratedFileContent(); + expect(content, contains('/// Title for the application.')); + expect(content, contains(r''' /// In en, this message translates to: /// **'Title \n of the application'**''')); }); testWithoutContext('message with placeholders - should generate code comment with description and template message translation', () { - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(r''' + setupLocalizations(<String, String>{ + 'en': r''' { "price": "The price of this item is: ${price}", "@price": { @@ -1494,94 +1295,41 @@ class AppLocalizationsEn extends AppLocalizations { } } } -}'''); - l10nDirectory.childFile(esArbFileName) - .writeAsStringSync(r''' +}''', + 'es': r''' { - "price": "el precio de este artículo es: ${price}" -}'''); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final File baseLocalizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') - ); - expect(baseLocalizationsFile.existsSync(), isTrue); - - final String baseLocalizationsFileContents = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart') - ).readAsStringSync(); - expect(baseLocalizationsFileContents, contains('/// The price of an online shopping cart item.')); - expect(baseLocalizationsFileContents, contains(r''' + "price": "El precio de este artículo es: ${price}" +}''' + }); + final String content = getGeneratedFileContent(); + expect(content, contains('/// The price of an online shopping cart item.')); + expect(content, contains(r''' /// In en, this message translates to: /// **'The price of this item is: \${price}'**''')); }); testWithoutContext('should generate a file per language', () { - const String singleEnCaMessageArbFileString = ''' + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'en_CA': ''' { "title": "Canadian Title" -}'''; - fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString) - ..childFile('app_en_CA.arb').writeAsStringSync(singleEnCaMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - expect(fs.isFileSync(fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart')), true); - expect(fs.isFileSync(fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en_US.dart')), false); - - final String englishLocalizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart') - ).readAsStringSync(); - expect(englishLocalizationsFile, contains('class AppLocalizationsEnCa extends AppLocalizationsEn')); - expect(englishLocalizationsFile, contains('class AppLocalizationsEn extends AppLocalizations')); +}''' + }); + expect(getGeneratedFileContent(locale: 'en'), contains('class AppLocalizationsEn extends AppLocalizations')); + expect(getGeneratedFileContent(locale: 'en'), contains('class AppLocalizationsEnCa extends AppLocalizationsEn')); + expect(() => getGeneratedFileContent(locale: 'en_US'), throwsException); }); testWithoutContext('language imports are sorted when preferredSupportedLocaleString is given', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString) - ..childFile('app_zh.arb').writeAsStringSync(singleZhMessageArbFileString) - ..childFile('app_es.arb').writeAsStringSync(singleEsMessageArbFileString); - - const List<String> preferredSupportedLocale = <String>['zh']; - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - preferredSupportedLocales: preferredSupportedLocale, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, defaultOutputFileString), - ).readAsStringSync(); - expect(localizationsFile, contains( + const List<String> preferredSupportedLocales = <String>['zh']; + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'zh': singleZhMessageArbFileString, + 'es': singleEsMessageArbFileString, + }, preferredSupportedLocales: preferredSupportedLocales); + final String content = getGeneratedFileContent(); + expect(content, contains( ''' import 'output-localization-file_en.dart'; import 'output-localization-file_es.dart'; @@ -1591,21 +1339,9 @@ import 'output-localization-file_zh.dart'; // Regression test for https://github.com/flutter/flutter/issues/88356 testWithoutContext('full output file suffix is retained', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: 'output-localization-file.g.dart', - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + }, outputFileString: 'output-localization-file.g.dart'); final String baseLocalizationsFile = fs.file( fs.path.join(syntheticL10nPackagePath, 'output-localization-file.g.dart'), ).readAsStringSync(); @@ -1624,118 +1360,69 @@ import 'output-localization-file.g.dart'; }); testWithoutContext('throws an exception when invalid output file name is passed in', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString); - - expect( - () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: 'asdf', - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - }, - throwsA(isA<L10nException>().having( - (L10nException e) => e.message, - 'message', - allOf( - contains('output-localization-file'), - contains('asdf'), - contains('is invalid'), - contains('The file name must have a .dart extension.'), - ), - )), - ); - - expect( - () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: '.g.dart', - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - }, - throwsA(isA<L10nException>().having( - (L10nException e) => e.message, - 'message', - allOf( - contains('output-localization-file'), - contains('.g.dart'), - contains('is invalid'), - contains('The base name cannot be empty.'), - ), - )), - ); - }); + expect( + () { + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + }, outputFileString: 'asdf'); + }, + throwsA(isA<L10nException>().having( + (L10nException e) => e.message, + 'message', + allOf( + contains('output-localization-file'), + contains('asdf'), + contains('is invalid'), + contains('The file name must have a .dart extension.'), + ), + )), + ); + expect( + () { + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + }, outputFileString: '.g.dart'); + }, + throwsA(isA<L10nException>().having( + (L10nException e) => e.message, + 'message', + allOf( + contains('output-localization-file'), + contains('.g.dart'), + contains('is invalid'), + contains('The base name cannot be empty.'), + ), + )), + ); + }); testWithoutContext('imports are deferred and loaded when useDeferredImports are set', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - useDeferredLoading: true, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, defaultOutputFileString), - ).readAsStringSync(); - expect(localizationsFile, contains( + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + }, useDeferredLoading: true); + final String content = getGeneratedFileContent(); + expect(content, contains( ''' import 'output-localization-file_en.dart' deferred as output-localization-file_en; ''')); - expect(localizationsFile, contains('output-localization-file_en.loadLibrary()')); + expect(content, contains('output-localization-file_en.loadLibrary()')); }); group('placeholder tests', () { testWithoutContext('should automatically infer placeholders that are not explicitly defined', () { - const String messageWithoutDefinedPlaceholder = ''' + setupLocalizations(<String, String>{ + 'en': ''' { "helloWorld": "Hello {name}" -}'''; - - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(messageWithoutDefinedPlaceholder); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'), - ).readAsStringSync(); - expect(localizationsFile, contains('String helloWorld(Object name) {')); +}''' + }); + final String content = getGeneratedFileContent(locale: 'en'); + expect(content, contains('String helloWorld(Object name) {')); }); testWithoutContext('placeholder parameter list should be consistent between languages', () { - const String messageEn = ''' + setupLocalizations(<String, String>{ + 'en': ''' { "helloWorld": "Hello {name}", "@helloWorld": { @@ -1743,42 +1430,22 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e "name": {} } } -}'''; - const String messageEs = ''' +}''', + 'es': ''' { "helloWorld": "Hola" } -'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(messageEn); - l10nDirectory.childFile('app_es.arb') - .writeAsStringSync(messageEs); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - final String localizationsFileEn = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'), - ).readAsStringSync(); - final String localizationsFileEs = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_es.dart'), - ).readAsStringSync(); - expect(localizationsFileEn, contains('String helloWorld(Object name) {')); - expect(localizationsFileEs, contains('String helloWorld(Object name) {')); +''', + }); + expect(getGeneratedFileContent(locale: 'en'), contains('String helloWorld(Object name) {')); + expect(getGeneratedFileContent(locale: 'es'), contains('String helloWorld(Object name) {')); }); }); group('DateTime tests', () { testWithoutContext('imports package:intl', () { - const String singleDateMessageArbFileString = ''' + setupLocalizations(<String, String>{ + 'en': ''' { "@@locale": "en", "springBegins": "Spring begins on {springStartDate}", @@ -1791,35 +1458,19 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e } } } -}'''; - fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleDateMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'), - ).readAsStringSync(); - expect(localizationsFile, contains(intlImportDartCode)); +}''' + }); + expect(getGeneratedFileContent(locale: 'en'), contains(intlImportDartCode)); }); testWithoutContext('throws an exception when improperly formatted date is passed in', () { - const String singleDateMessageArbFileString = ''' + expect( + () { + setupLocalizations(<String, String>{ + 'en': ''' { - "@@locale": "en", "springBegins": "Spring begins on {springStartDate}", "@springBegins": { - "description": "The first day of spring", "placeholders": { "springStartDate": { "type": "DateTime", @@ -1827,24 +1478,8 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e } } } -}'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleDateMessageArbFileString); - - expect( - () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); +}''' + }); }, throwsA(isA<L10nException>().having( (L10nException e) => e.message, @@ -1859,12 +1494,11 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e }); testWithoutContext('use standard date format whenever possible', () { - const String singleDateMessageArbFileString = ''' + setupLocalizations(<String, String>{ + 'en': ''' { - "@@locale": "en", "springBegins": "Spring begins on {springStartDate}", "@springBegins": { - "description": "The first day of spring", "placeholders": { "springStartDate": { "type": "DateTime", @@ -1873,31 +1507,15 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e } } } -}'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleDateMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'), - ).readAsStringSync(); - expect(localizationsFile, contains('DateFormat.yMd(localeName)')); +}''' + }); + final String content = getGeneratedFileContent(locale: 'en'); + expect(content, contains('DateFormat.yMd(localeName)')); }); testWithoutContext('handle arbitrary formatted date', () { - const String singleDateMessageArbFileString = ''' + setupLocalizations(<String, String>{ + 'en': ''' { "@@locale": "en", "springBegins": "Spring begins on {springStartDate}", @@ -1911,31 +1529,17 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e } } } -}'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleDateMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'), - ).readAsStringSync(); - expect(localizationsFile, contains(r"DateFormat('asdf o\'clock', localeName)")); +}''' + }); + final String content = getGeneratedFileContent(locale: 'en'); + expect(content, contains(r"DateFormat('asdf o\'clock', localeName)")); }); testWithoutContext('throws an exception when no format attribute is passed in', () { - const String singleDateMessageArbFileString = ''' + expect( + () { + setupLocalizations(<String, String>{ + 'en': ''' { "springBegins": "Spring begins on {springStartDate}", "@springBegins": { @@ -1946,25 +1550,8 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e } } } -}'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleDateMessageArbFileString); - - expect( - () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); +}''' + }); }, throwsA(isA<L10nException>().having( (L10nException e) => e.message, @@ -1977,7 +1564,8 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e group('NumberFormat tests', () { testWithoutContext('imports package:intl', () { - const String singleDateMessageArbFileString = ''' + setupLocalizations(<String, String>{ + 'en': ''' { "courseCompletion": "You have completed {progress} of the course.", "@courseCompletion": { @@ -1989,32 +1577,17 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e } } } -}'''; - fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync( - singleDateMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'), - ).readAsStringSync(); - expect(localizationsFile, contains(intlImportDartCode)); +}''' + }); + final String content = getGeneratedFileContent(locale: 'en'); + expect(content, contains(intlImportDartCode)); }); testWithoutContext('throws an exception when improperly formatted number is passed in', () { - const String singleDateMessageArbFileString = ''' + expect( + () { + setupLocalizations(<String, String>{ + 'en': ''' { "courseCompletion": "You have completed {progress} of the course.", "@courseCompletion": { @@ -2026,25 +1599,8 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e } } } -}'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleDateMessageArbFileString); - - expect( - () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); +}''' + }); }, throwsA(isA<L10nException>().having( (L10nException e) => e.message, @@ -2060,29 +1616,24 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e }); group('plural messages', () { + testWithoutContext('intl package import should be omitted in subclass files when no plurals are included', () { + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'es': singleEsMessageArbFileString, + }); + expect(getGeneratedFileContent(locale: 'es'), isNot(contains(intlImportDartCode))); + }); + testWithoutContext('warnings are generated when plural parts are repeated', () { - const String pluralMessageWithOverriddenParts = ''' + setupLocalizations(<String, String>{ + 'en': ''' { "helloWorlds": "{count,plural, =0{Hello}zero{hello} other{hi}}", "@helloWorlds": { "description": "Properly formatted but has redundant zero cases." } -}'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(pluralMessageWithOverriddenParts); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); +}''' + }); expect(logger.hadWarningOutput, isTrue); expect(logger.warningText, contains(''' [app_en.arb:helloWorlds] ICU Syntax Warning: The plural part specified below is overridden by a later plural part. @@ -2091,26 +1642,13 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e }); testWithoutContext('undefined plural cases throws syntax error', () { - const String pluralMessageWithUndefinedParts = ''' + try { + setupLocalizations(<String, String>{ + 'en': ''' { "count": "{count,plural, =0{None} =1{One} =2{Two} =3{Undefined Behavior!} other{Hmm...}}" -}'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(pluralMessageWithUndefinedParts); - try { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); +}''' + }); } on L10nException catch (error) { expect(error.message, contains('Found syntax errors.')); expect(logger.hadErrorOutput, isTrue); @@ -2123,62 +1661,30 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e }); testWithoutContext('should automatically infer plural placeholders that are not explicitly defined', () { - const String pluralMessageWithoutPlaceholdersAttribute = ''' + setupLocalizations(<String, String>{ + 'en': ''' { "helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}", "@helloWorlds": { "description": "Improperly formatted since it has no placeholder attribute." } -}'''; - - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(pluralMessageWithoutPlaceholdersAttribute); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'), - ).readAsStringSync(); - expect(localizationsFile, contains('String helloWorlds(num count) {')); +}''' + }); + expect(getGeneratedFileContent(locale: 'en'), contains('String helloWorlds(num count) {')); }); testWithoutContext('should throw attempting to generate a plural message with incorrect format for placeholders', () { - const String pluralMessageWithIncorrectPlaceholderFormat = ''' + expect( + () { + setupLocalizations(<String, String>{ + 'en': ''' { "helloWorlds": "{count,plural, =0{Hello}=1{Hello World}=2{Hello two worlds}few{Hello {count} worlds}many{Hello all {count} worlds}other{Hello other {count} worlds}}", "@helloWorlds": { "placeholders": "Incorrectly a string, should be a map." } -}'''; - - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(pluralMessageWithIncorrectPlaceholderFormat); - - expect( - () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); +}''' + }); }, throwsA(isA<L10nException>().having( (L10nException e) => e.message, @@ -2194,62 +1700,30 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e group('select messages', () { testWithoutContext('should automatically infer select placeholders that are not explicitly defined', () { - const String selectMessageWithoutPlaceholdersAttribute = ''' + setupLocalizations(<String, String>{ + 'en': ''' { "genderSelect": "{gender, select, female {She} male {He} other {they} }", "@genderSelect": { "description": "Improperly formatted since it has no placeholder attribute." } -}'''; - - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(selectMessageWithoutPlaceholdersAttribute); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'), - ).readAsStringSync(); - expect(localizationsFile, contains('String genderSelect(String gender) {')); +}''' + }); + expect(getGeneratedFileContent(locale: 'en'), contains('String genderSelect(String gender) {')); }); testWithoutContext('should throw attempting to generate a select message with incorrect format for placeholders', () { - const String selectMessageWithIncorrectPlaceholderFormat = ''' + expect( + () { + setupLocalizations(<String, String>{ + 'en': ''' { "genderSelect": "{gender, select, female {She} male {He} other {they} }", "@genderSelect": { "placeholders": "Incorrectly a string, should be a map." } -}'''; - - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(selectMessageWithIncorrectPlaceholderFormat); - - expect( - () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); +}''' + }); }, throwsA(isA<L10nException>().having( (L10nException e) => e.message, @@ -2263,7 +1737,9 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e }); testWithoutContext('should throw attempting to generate a select message with an incorrect message', () { - const String selectMessageWithoutPlaceholdersAttribute = ''' + try { + setupLocalizations(<String, String>{ + 'en': ''' { "genderSelect": "{gender, select,}", "@genderSelect": { @@ -2271,24 +1747,8 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e "gender": {} } } -}'''; - - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(selectMessageWithoutPlaceholdersAttribute); - try { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); +}''' + }); } on L10nException { expect(logger.errorText, contains(''' [app_en.arb:genderSelect] ICU Syntax Error: Select expressions must have an "other" case. @@ -2299,36 +1759,83 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e }); }); + group('argument messages', () { + testWithoutContext('should generate proper calls to intl.DateFormat', () { + setupLocalizations(<String, String>{ + 'en': ''' +{ + "datetime": "{today, date, ::yMd}" +}''' + }); + expect(getGeneratedFileContent(locale: 'en'), contains('intl.DateFormat.yMd(localeName).format(today)')); + }); + + testWithoutContext('should generate proper calls to intl.DateFormat when using time', () { + setupLocalizations(<String, String>{ + 'en': ''' +{ + "datetime": "{current, time, ::jms}" +}''' + }); + expect(getGeneratedFileContent(locale: 'en'), contains('intl.DateFormat.jms(localeName).format(current)')); + }); + + testWithoutContext('should not complain when placeholders are explicitly typed to DateTime', () { + setupLocalizations(<String, String>{ + 'en': ''' +{ + "datetime": "{today, date, ::yMd}", + "@datetime": { + "placeholders": { + "today": { "type": "DateTime" } + } + } +}''' + }); + expect(getGeneratedFileContent(locale: 'en'), contains('String datetime(DateTime today) {')); + }); + + testWithoutContext('should automatically infer date time placeholders that are not explicitly defined', () { + setupLocalizations(<String, String>{ + 'en': ''' +{ + "datetime": "{today, date, ::yMd}" +}''' + }); + expect(getGeneratedFileContent(locale: 'en'), contains('String datetime(DateTime today) {')); + }); + + testWithoutContext('should throw on invalid DateFormat', () { + try { + setupLocalizations(<String, String>{ + 'en': ''' +{ + "datetime": "{today, date, ::yMMMMMd}" +}''' + }); + assert(false); + } on L10nException { + expect(logger.errorText, contains('Date format "yMMMMMd" for placeholder today does not have a corresponding DateFormat constructor')); + } + }); + }); + // All error handling for messages should collect errors on a per-error // basis and log them out individually. Then, it will throw an L10nException. group('error handling tests', () { testWithoutContext('syntax/code-gen errors properly logs errors per message', () { // TODO(thkim1011): Fix error handling so that long indents don't get truncated. // See https://github.com/flutter/flutter/issues/120490. - const String messagesWithSyntaxErrors = ''' + try { + setupLocalizations(<String, String>{ + 'en': ''' { "hello": "Hello { name", "plural": "This is an incorrectly formatted plural: { count, plural, zero{No frog} one{One frog} other{{count} frogs}", "explanationWithLexingError": "The 'string above is incorrect as it forgets to close the brace", "pluralWithInvalidCase": "{ count, plural, woohoo{huh?} other{lol} }" -}'''; - try { - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(messagesWithSyntaxErrors); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - useEscaping: true, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); +}''' + }, useEscaping: true); } on L10nException { expect(logger.errorText, contains(''' [app_en.arb:hello] ICU Syntax Error: Expected "}" but found no tokens. @@ -2347,33 +1854,11 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e }); testWithoutContext('errors thrown in multiple languages are all shown', () { - const String messageEn = ''' -{ - "hello": "Hello { name" -}'''; - const String messageEs = ''' -{ - "hello": "Hola { name" -}'''; try { - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(messageEn); - l10nDirectory.childFile('app_es.arb') - .writeAsStringSync(messageEs); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - useEscaping: true, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); + setupLocalizations(<String, String>{ + 'en': '{ "hello": "Hello { name" }', + 'es': '{ "hello": "Hola { name" }', + }); } on L10nException { expect(logger.errorText, contains(''' [app_en.arb:hello] ICU Syntax Error: Expected "}" but found no tokens. @@ -2386,28 +1871,6 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e }); }); - testWithoutContext('intl package import should be omitted in subclass files when no plurals are included', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString) - ..childFile('app_es.arb').writeAsStringSync(singleEsMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_es.dart'), - ).readAsStringSync(); - expect(localizationsFile, isNot(contains(intlImportDartCode))); - }); testWithoutContext('intl package import should be kept in subclass files when plurals are included', () { const String pluralMessageArb = ''' @@ -2421,96 +1884,52 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e } } '''; - const String pluralMessageEsArb = ''' { "helloWorlds": "{count,plural, =0{ES - Hello} =1{ES - Hello World} =2{ES - Hello two worlds} few{ES - Hello {count} worlds} many{ES - Hello all {count} worlds} other{ES - Hello other {count} worlds}}" } '''; - - fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(pluralMessageArb) - ..childFile('app_es.arb').writeAsStringSync(pluralMessageEsArb); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_es.dart'), - ).readAsStringSync(); - expect(localizationsFile, contains(intlImportDartCode)); + setupLocalizations(<String, String>{ + 'en': pluralMessageArb, + 'es': pluralMessageEsArb, + }); + expect(getGeneratedFileContent(locale: 'en'), contains(intlImportDartCode)); + expect(getGeneratedFileContent(locale: 'es'), contains(intlImportDartCode)); }); testWithoutContext('intl package import should be kept in subclass files when select is included', () { const String selectMessageArb = ''' { - "genderSelect": "{gender, select, female {She} male {He} other {they} }", - "@genderSelect": { - "description": "A select message", - "placeholders": { - "gender": {} - } - } -} -'''; - - const String selectMessageEsArb = ''' -{ - "genderSelect": "{gender, select, female {ES - She} male {ES - He} other {ES - they} }" -} -'''; - - fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(selectMessageArb) - ..childFile('app_es.arb').writeAsStringSync(selectMessageEsArb); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_es.dart'), - ).readAsStringSync(); - expect(localizationsFile, contains(intlImportDartCode)); + "genderSelect": "{gender, select, female {She} male {He} other {they} }", + "@genderSelect": { + "description": "A select message", + "placeholders": { + "gender": {} + } + } +} +'''; + const String selectMessageEsArb = ''' +{ + "genderSelect": "{gender, select, female {ES - She} male {ES - He} other {ES - they} }" +} +'''; + setupLocalizations(<String, String>{ + 'en': selectMessageArb, + 'es': selectMessageEsArb, + }); + expect(getGeneratedFileContent(locale: 'en'), contains(intlImportDartCode)); + expect(getGeneratedFileContent(locale: 'es'), contains(intlImportDartCode)); }); testWithoutContext('check indentation on generated files', () { - _standardFlutterDirectoryL10nSetup(fs); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart'), - ).readAsStringSync(); + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'es': singleEsMessageArbFileString, + }); // Tests a few of the lines in the generated code. // Localizations lookup code + final String localizationsFile = getGeneratedFileContent(); expect(localizationsFile.contains(' switch (locale.languageCode) {'), true); expect(localizationsFile.contains(" case 'en': return AppLocalizationsEn();"), true); expect(localizationsFile.contains(" case 'es': return AppLocalizationsEs();"), true); @@ -2524,50 +1943,19 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e }); testWithoutContext('foundation package import should be omitted from file template when deferred loading = true', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString) - ..childFile('app_es.arb').writeAsStringSync(singleEsMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - useDeferredLoading: true, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart'), - ).readAsStringSync(); - expect(localizationsFile, isNot(contains(foundationImportDartCode))); + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'es': singleEsMessageArbFileString, + }, useDeferredLoading: true); + expect(getGeneratedFileContent(), isNot(contains(foundationImportDartCode))); }); testWithoutContext('foundation package import should be kept in file template when deferred loading = false', () { - fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(singleMessageArbFileString) - ..childFile('app_es.arb').writeAsStringSync(singleEsMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart'), - ).readAsStringSync(); - expect(localizationsFile, contains(foundationImportDartCode)); + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + 'es': singleEsMessageArbFileString, + }); + expect(getGeneratedFileContent(), contains(foundationImportDartCode)); }); testWithoutContext('check for string interpolation rules', () { @@ -2672,27 +2060,11 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e "nine": "m{nine}" } '''; - - fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(enArbCheckList) - ..childFile('app_es.arb').writeAsStringSync(esArbCheckList); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_es.dart'), - ).readAsStringSync(); - + setupLocalizations(<String, String>{ + 'en': enArbCheckList, + 'es': esArbCheckList, + }); + final String localizationsFile = getGeneratedFileContent(locale: 'es'); expect(localizationsFile, contains(r'$one')); expect(localizationsFile, contains(r'$two')); expect(localizationsFile, contains(r'${three}')); @@ -2742,27 +2114,11 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e "second": "{count,plural, =0{test {count}} other{ {count}}}" } '''; - - fs.currentDirectory.childDirectory('lib').childDirectory('l10n')..createSync(recursive: true) - ..childFile(defaultTemplateArbFileName).writeAsStringSync(enArbCheckList) - ..childFile('app_es.arb').writeAsStringSync(esArbCheckList); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_es.dart'), - ).readAsStringSync(); - + setupLocalizations(<String, String>{ + 'en': enArbCheckList, + 'es': esArbCheckList, + }); + final String localizationsFile = getGeneratedFileContent(locale: 'es'); expect(localizationsFile, contains(r'test $count test')); expect(localizationsFile, contains(r'哈$count哈')); expect(localizationsFile, contains(r'm${count}m')); @@ -2787,24 +2143,9 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e "description": "Title for the Stocks application" }, }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(arbFileWithTrailingComma); - expect( () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); + setupLocalizations(<String, String>{ 'en': arbFileWithTrailingComma }); }, throwsA(isA<L10nException>().having( (L10nException e) => e.message, @@ -2824,25 +2165,12 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e { "title": "Stocks" }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(arbFileWithMissingResourceAttribute); - expect( () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - areResourceAttributesRequired: true, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); + setupLocalizations( + <String, String>{ 'en': arbFileWithMissingResourceAttribute }, + areResourceAttributeRequired: true, + ); }, throwsA(isA<L10nException>().having( (L10nException e) => e.message, @@ -2861,25 +2189,8 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e "description": "Title for the Stocks application" } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(nonAlphaNumericArbFile); - expect( - () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - }, + () => setupLocalizations(<String, String>{ 'en': nonAlphaNumericArbFile }), throwsA(isA<L10nException>().having( (L10nException e) => e.message, 'message', @@ -2896,25 +2207,8 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e "description": "Title for the Stocks application" } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(nonAlphaNumericArbFile); - expect( - () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - }, + () => setupLocalizations(<String, String>{ 'en': nonAlphaNumericArbFile }), throwsA(isA<L10nException>().having( (L10nException e) => e.message, 'message', @@ -2931,24 +2225,8 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e "description": "Title for the Stocks application" } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(nonAlphaNumericArbFile); - expect( - () { - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - }, + () => setupLocalizations(<String, String>{ 'en': nonAlphaNumericArbFile }), throwsA(isA<L10nException>().having( (L10nException e) => e.message, 'message', @@ -2965,21 +2243,7 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e "description": "Title for the application" } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(dollarArbFile); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); + setupLocalizations(<String, String>{ 'en': dollarArbFile }); }); }); @@ -3019,18 +2283,9 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e }); testWithoutContext('should generate a valid pubspec.yaml file when using synthetic package if it does not already exist', () { - _standardFlutterDirectoryL10nSetup(fs); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + }); final Directory outputDirectory = fs.directory(syntheticPackagePath); final File pubspecFile = outputDirectory.childFile('pubspec.yaml'); expect(pubspecFile.existsSync(), isTrue); @@ -3046,22 +2301,12 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e }); testWithoutContext('should not overwrite existing pubspec.yaml file when using synthetic package', () { - _standardFlutterDirectoryL10nSetup(fs); final File pubspecFile = fs.file(fs.path.join(syntheticPackagePath, 'pubspec.yaml')) ..createSync(recursive: true) ..writeAsStringSync('abcd'); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - + setupLocalizations(<String, String>{ + 'en': singleMessageArbFileString, + }); // The original pubspec file should not be overwritten. expect(pubspecFile.readAsStringSync(), 'abcd'); }); @@ -3079,57 +2324,20 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e } } }'''; - - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(arbFile); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'), - ).readAsStringSync(); - expect(localizationsFile, containsIgnoringWhitespace(r''' + setupLocalizations(<String, String>{ + 'en': arbFile, + }); + expect(getGeneratedFileContent(locale: 'en'), containsIgnoringWhitespace(r''' String orderNumber(int number) { return 'This is order #$number.'; } ''')); - expect(localizationsFile, isNot(contains(intlImportDartCode))); + expect(getGeneratedFileContent(locale: 'en'), isNot(contains(intlImportDartCode))); }); testWithoutContext('app localizations lookup is a public method', () { - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(singleMessageArbFileString); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file.dart'), - ).readAsStringSync(); - expect(localizationsFile, containsIgnoringWhitespace(r''' + setupLocalizations(<String, String>{ 'en': singleMessageArbFileString }); + expect(getGeneratedFileContent(), containsIgnoringWhitespace(r''' AppLocalizations lookupAppLocalizations(Locale locale) { ''')); }); @@ -3142,29 +2350,8 @@ AppLocalizations lookupAppLocalizations(Locale locale) { "description": "A message with a single quote." } }'''; - - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(arbFile); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - useEscaping: true, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'), - ).readAsStringSync(); - expect(localizationsFile, contains(r"Flutter\'s amazing")); + setupLocalizations(<String, String>{ 'en': arbFile }, useEscaping: true); + expect(getGeneratedFileContent(locale: 'en'), contains(r"Flutter\'s amazing")); }); testWithoutContext('suppress warnings flag actually suppresses warnings', () { @@ -3178,22 +2365,10 @@ AppLocalizations lookupAppLocalizations(Locale locale) { } } }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(pluralMessageWithOverriddenParts); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, + setupLocalizations( + <String, String>{ 'en': pluralMessageWithOverriddenParts }, suppressWarnings: true, - ) - ..loadResources() - ..writeOutputFiles(); + ); expect(logger.hadWarningOutput, isFalse); }); @@ -3213,27 +2388,8 @@ AppLocalizations lookupAppLocalizations(Locale locale) { } } }'''; - - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(arbFile); - - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - ) - ..loadResources() - ..writeOutputFiles(); - - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'), - ).readAsStringSync(); + setupLocalizations(<String, String>{ 'en': arbFile }); + final String localizationsFile = getGeneratedFileContent(locale: 'en'); expect(localizationsFile, containsIgnoringWhitespace(r''' String treeHeight(double height) { ''')); @@ -3251,25 +2407,7 @@ NumberFormat.decimalPatternDigits( { "dollarSignWithSelect": "$nice_bug\nHello Bug! Manifistation #1 {selectPlaceholder, select, case{message} other{messageOther}}" }'''; - final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') - ..createSync(recursive: true); - l10nDirectory.childFile(defaultTemplateArbFileName) - .writeAsStringSync(dollarSignWithSelect); - LocalizationsGenerator( - fileSystem: fs, - inputPathString: defaultL10nPathString, - outputPathString: defaultL10nPathString, - templateArbFileName: defaultTemplateArbFileName, - outputFileString: defaultOutputFileString, - classNameString: defaultClassNameString, - logger: logger, - suppressWarnings: true, - ) - ..loadResources() - ..writeOutputFiles(); - final String localizationsFile = fs.file( - fs.path.join(syntheticL10nPackagePath, 'output-localization-file_en.dart'), - ).readAsStringSync(); - expect(localizationsFile, contains(r'\$nice_bug\nHello Bug! Manifistation #1 $_temp0')); + setupLocalizations(<String, String>{ 'en': dollarSignWithSelect }); + expect(getGeneratedFileContent(locale: 'en'), contains(r'\$nice_bug\nHello Bug! Manifistation #1 $_temp0')); }); } diff --git a/packages/flutter_tools/test/general.shard/ios/core_devices_test.dart b/packages/flutter_tools/test/general.shard/ios/core_devices_test.dart new file mode 100644 index 0000000000000..d820e1a13bcb2 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/ios/core_devices_test.dart @@ -0,0 +1,1949 @@ +// 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:file/memory.dart'; +import 'package:file_testing/file_testing.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/version.dart'; +import 'package:flutter_tools/src/ios/core_devices.dart'; +import 'package:flutter_tools/src/ios/xcodeproj.dart'; +import 'package:flutter_tools/src/macos/xcode.dart'; + +import '../../src/common.dart'; +import '../../src/fake_process_manager.dart'; + +void main() { + late MemoryFileSystem fileSystem; + + setUp(() { + fileSystem = MemoryFileSystem.test(); + }); + + group('Xcode prior to Core Device Control/Xcode 15', () { + late BufferLogger logger; + late FakeProcessManager fakeProcessManager; + late Xcode xcode; + late IOSCoreDeviceControl deviceControl; + + setUp(() { + logger = BufferLogger.test(); + fakeProcessManager = FakeProcessManager.empty(); + final XcodeProjectInterpreter xcodeProjectInterpreter = XcodeProjectInterpreter.test( + processManager: fakeProcessManager, + version: Version(14, 0, 0), + ); + xcode = Xcode.test( + processManager: FakeProcessManager.any(), + xcodeProjectInterpreter: xcodeProjectInterpreter, + ); + deviceControl = IOSCoreDeviceControl( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + }); + + group('devicectl is not installed', () { + testWithoutContext('fails to get device list', () async { + final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl is not installed.')); + expect(devices.isEmpty, isTrue); + }); + + testWithoutContext('fails to install app', () async { + final bool status = await deviceControl.installApp(deviceId: 'device-id', bundlePath: '/path/to/bundle'); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl is not installed.')); + expect(status, isFalse); + }); + + testWithoutContext('fails to launch app', () async { + final bool status = await deviceControl.launchApp(deviceId: 'device-id', bundleId: 'com.example.flutterApp'); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl is not installed.')); + expect(status, isFalse); + }); + + testWithoutContext('fails to check if app is installed', () async { + final bool status = await deviceControl.isAppInstalled(deviceId: 'device-id', bundleId: 'com.example.flutterApp'); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl is not installed.')); + expect(status, isFalse); + }); + }); + }); + + group('Core Device Control', () { + late BufferLogger logger; + late FakeProcessManager fakeProcessManager; + late Xcode xcode; + late IOSCoreDeviceControl deviceControl; + + setUp(() { + logger = BufferLogger.test(); + fakeProcessManager = FakeProcessManager.empty(); + xcode = Xcode.test(processManager: FakeProcessManager.any()); + deviceControl = IOSCoreDeviceControl( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + }); + + group('install app', () { + const String deviceId = 'device-id'; + const String bundlePath = '/path/to/com.example.flutterApp'; + + testWithoutContext('Successful install', () async { + const String deviceControlOutput = ''' +{ + "info" : { + "arguments" : [ + "devicectl", + "device", + "install", + "app", + "--device", + "00001234-0001234A3C03401E", + "build/ios/iphoneos/Runner.app", + "--json-output", + "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/install_results.json" + ], + "commandType" : "devicectl.device.install.app", + "environment" : { + + }, + "outcome" : "success", + "version" : "341" + }, + "result" : { + "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", + "installedApplications" : [ + { + "bundleID" : "com.example.bundle", + "databaseSequenceNumber" : 1230, + "databaseUUID" : "1234A567-D890-1B23-BCF4-D5D67A8D901E", + "installationURL" : "file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/", + "launchServicesIdentifier" : "unknown", + "options" : { + + } + } + ] + } +} +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('install_results.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'install', + 'app', + '--device', + deviceId, + bundlePath, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final bool status = await deviceControl.installApp( + deviceId: deviceId, + bundlePath: bundlePath, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, isEmpty); + expect(tempFile, isNot(exists)); + expect(status, true); + }); + + testWithoutContext('devicectl fails install', () async { + const String deviceControlOutput = ''' +{ + "error" : { + "code" : 1005, + "domain" : "com.apple.dt.CoreDeviceError", + "userInfo" : { + "NSLocalizedDescription" : { + "string" : "Could not obtain access to one or more requested file system resources because CoreDevice was unable to create bookmark data." + }, + "NSUnderlyingError" : { + "error" : { + "code" : 260, + "domain" : "NSCocoaErrorDomain", + "userInfo" : { + + } + } + } + } + }, + "info" : { + "arguments" : [ + "devicectl", + "device", + "install", + "app", + "--device", + "00001234-0001234A3C03401E", + "/path/to/app", + "--json-output", + "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/install_results.json" + ], + "commandType" : "devicectl.device.install.app", + "environment" : { + "TERM" : "xterm-256color" + }, + "outcome" : "failed", + "version" : "341" + } +} +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('install_results.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'install', + 'app', + '--device', + deviceId, + bundlePath, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + exitCode: 1, + stderr: ''' +ERROR: Could not obtain access to one or more requested file system resources because CoreDevice was unable to create bookmark data. (com.apple.dt.CoreDeviceError error 1005.) + NSURL = file:///path/to/app +-------------------------------------------------------------------------------- +ERROR: The file couldn’t be opened because it doesn’t exist. (NSCocoaErrorDomain error 260.) +''' + )); + + final bool status = await deviceControl.installApp( + deviceId: deviceId, + bundlePath: bundlePath, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('ERROR: Could not obtain access to one or more requested file system')); + expect(tempFile, isNot(exists)); + expect(status, false); + }); + + testWithoutContext('fails install because of unexpected JSON', () async { + const String deviceControlOutput = ''' +{ + "valid_unexpected_json": true +} +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('install_results.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'install', + 'app', + '--device', + deviceId, + bundlePath, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final bool status = await deviceControl.installApp( + deviceId: deviceId, + bundlePath: bundlePath, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl returned unexpected JSON response')); + expect(tempFile, isNot(exists)); + expect(status, false); + }); + + testWithoutContext('fails install because of invalid JSON', () async { + const String deviceControlOutput = ''' +invalid JSON +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('install_results.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'install', + 'app', + '--device', + deviceId, + bundlePath, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final bool status = await deviceControl.installApp( + deviceId: deviceId, + bundlePath: bundlePath, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl returned non-JSON response')); + expect(tempFile, isNot(exists)); + expect(status, false); + }); + }); + + group('uninstall app', () { + const String deviceId = 'device-id'; + const String bundleId = 'com.example.flutterApp'; + + testWithoutContext('Successful uninstall', () async { + const String deviceControlOutput = ''' +{ + "info" : { + "arguments" : [ + "devicectl", + "device", + "uninstall", + "app", + "--device", + "00001234-0001234A3C03401E", + "build/ios/iphoneos/Runner.app", + "--json-output", + "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/uninstall_results.json" + ], + "commandType" : "devicectl.device.uninstall.app", + "environment" : { + + }, + "outcome" : "success", + "version" : "341" + }, + "result" : { + "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", + "uninstalledApplications" : [ + { + "bundleID" : "com.example.bundle" + } + ] + } +} +'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('uninstall_results.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'uninstall', + 'app', + '--device', + deviceId, + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final bool status = await deviceControl.uninstallApp( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, isEmpty); + expect(tempFile, isNot(exists)); + expect(status, true); + }); + + testWithoutContext('devicectl fails uninstall', () async { + const String deviceControlOutput = ''' +{ + "error" : { + "code" : 1005, + "domain" : "com.apple.dt.CoreDeviceError", + "userInfo" : { + "NSLocalizedDescription" : { + "string" : "Could not obtain access to one or more requested file system resources because CoreDevice was unable to create bookmark data." + }, + "NSUnderlyingError" : { + "error" : { + "code" : 260, + "domain" : "NSCocoaErrorDomain", + "userInfo" : { + + } + } + } + } + }, + "info" : { + "arguments" : [ + "devicectl", + "device", + "uninstall", + "app", + "--device", + "00001234-0001234A3C03401E", + "com.example.flutterApp", + "--json-output", + "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/uninstall_results.json" + ], + "commandType" : "devicectl.device.uninstall.app", + "environment" : { + "TERM" : "xterm-256color" + }, + "outcome" : "failed", + "version" : "341" + } +} +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('uninstall_results.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'uninstall', + 'app', + '--device', + deviceId, + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + exitCode: 1, + stderr: ''' +ERROR: Could not obtain access to one or more requested file system resources because CoreDevice was unable to create bookmark data. (com.apple.dt.CoreDeviceError error 1005.) + NSURL = file:///path/to/app +-------------------------------------------------------------------------------- +ERROR: The file couldn’t be opened because it doesn’t exist. (NSCocoaErrorDomain error 260.) +''' + )); + + final bool status = await deviceControl.uninstallApp( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('ERROR: Could not obtain access to one or more requested file system')); + expect(tempFile, isNot(exists)); + expect(status, false); + }); + + testWithoutContext('fails uninstall because of unexpected JSON', () async { + const String deviceControlOutput = ''' +{ + "valid_unexpected_json": true +} +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('uninstall_results.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'uninstall', + 'app', + '--device', + deviceId, + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final bool status = await deviceControl.uninstallApp( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl returned unexpected JSON response')); + expect(tempFile, isNot(exists)); + expect(status, false); + }); + + testWithoutContext('fails uninstall because of invalid JSON', () async { + const String deviceControlOutput = ''' +invalid JSON +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('uninstall_results.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'uninstall', + 'app', + '--device', + deviceId, + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final bool status = await deviceControl.uninstallApp( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl returned non-JSON response')); + expect(tempFile, isNot(exists)); + expect(status, false); + }); + }); + + group('launch app', () { + const String deviceId = 'device-id'; + const String bundleId = 'com.example.flutterApp'; + + testWithoutContext('Successful launch without launch args', () async { + const String deviceControlOutput = ''' +{ + "info" : { + "arguments" : [ + "devicectl", + "device", + "process", + "launch", + "--device", + "00001234-0001234A3C03401E", + "com.example.flutterApp", + "--json-output", + "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/install_results.json" + ], + "commandType" : "devicectl.device.process.launch", + "environment" : { + + }, + "outcome" : "success", + "version" : "341" + }, + "result" : { + "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", + "launchOptions" : { + "activatedWhenStarted" : true, + "arguments" : [ + + ], + "environmentVariables" : { + "TERM" : "vt100" + }, + "platformSpecificOptions" : { + + }, + "startStopped" : false, + "terminateExistingInstances" : false, + "user" : { + "active" : true + } + }, + "process" : { + "auditToken" : [ + 12345, + 678 + ], + "executable" : "file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/Runner", + "processIdentifier" : 1234 + } + } +} +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('launch_results.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'process', + 'launch', + '--device', + deviceId, + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final bool status = await deviceControl.launchApp( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, isEmpty); + expect(tempFile, isNot(exists)); + expect(status, true); + }); + + testWithoutContext('Successful launch with launch args', () async { + const String deviceControlOutput = ''' +{ + "info" : { + "arguments" : [ + "devicectl", + "device", + "process", + "launch", + "--device", + "00001234-0001234A3C03401E", + "com.example.flutterApp", + "--arg1", + "--arg2", + "--json-output", + "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/install_results.json" + ], + "commandType" : "devicectl.device.process.launch", + "environment" : { + + }, + "outcome" : "success", + "version" : "341" + }, + "result" : { + "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", + "launchOptions" : { + "activatedWhenStarted" : true, + "arguments" : [ + + ], + "environmentVariables" : { + "TERM" : "vt100" + }, + "platformSpecificOptions" : { + + }, + "startStopped" : false, + "terminateExistingInstances" : false, + "user" : { + "active" : true + } + }, + "process" : { + "auditToken" : [ + 12345, + 678 + ], + "executable" : "file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/Runner", + "processIdentifier" : 1234 + } + } +} +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('launch_results.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'process', + 'launch', + '--device', + deviceId, + bundleId, + '--arg1', + '--arg2', + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final bool status = await deviceControl.launchApp( + deviceId: deviceId, + bundleId: bundleId, + launchArguments: <String>['--arg1', '--arg2'], + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, isEmpty); + expect(tempFile, isNot(exists)); + expect(status, true); + }); + + testWithoutContext('devicectl fails install', () async { + const String deviceControlOutput = ''' +{ + "error" : { + "code" : -10814, + "domain" : "NSOSStatusErrorDomain", + "userInfo" : { + "_LSFunction" : { + "string" : "runEvaluator" + }, + "_LSLine" : { + "int" : 1608 + } + } + }, + "info" : { + "arguments" : [ + "devicectl", + "device", + "process", + "launch", + "--device", + "00001234-0001234A3C03401E", + "com.example.flutterApp", + "--json-output", + "/var/folders/wq/randompath/T/flutter_tools.rand0/core_devices.rand0/install_results.json" + ], + "commandType" : "devicectl.device.process.launch", + "environment" : { + + }, + "outcome" : "failed", + "version" : "341" + } +} +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('launch_results.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'process', + 'launch', + '--device', + deviceId, + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + exitCode: 1, + stderr: ''' +ERROR: The operation couldn?t be completed. (OSStatus error -10814.) (NSOSStatusErrorDomain error -10814.) + _LSFunction = runEvaluator + _LSLine = 1608 +''' + )); + + final bool status = await deviceControl.launchApp( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('ERROR: The operation couldn?t be completed.')); + expect(tempFile, isNot(exists)); + expect(status, false); + }); + + testWithoutContext('fails launch because of unexpected JSON', () async { + const String deviceControlOutput = ''' +{ + "valid_unexpected_json": true +} +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('launch_results.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'process', + 'launch', + '--device', + deviceId, + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + + final bool status = await deviceControl.launchApp( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl returned unexpected JSON response')); + expect(tempFile, isNot(exists)); + expect(status, false); + }); + + testWithoutContext('fails launch because of invalid JSON', () async { + const String deviceControlOutput = ''' +invalid JSON +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('launch_results.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'process', + 'launch', + '--device', + deviceId, + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final bool status = await deviceControl.launchApp( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl returned non-JSON response')); + expect(tempFile, isNot(exists)); + expect(status, false); + }); + }); + + group('list apps', () { + const String deviceId = 'device-id'; + const String bundleId = 'com.example.flutterApp'; + + testWithoutContext('Successfully parses apps', () async { + const String deviceControlOutput = ''' +{ + "info" : { + "arguments" : [ + "devicectl", + "device", + "info", + "apps", + "--device", + "00001234-0001234A3C03401E", + "--bundle-id", + "com.example.flutterApp", + "--json-output", + "apps.txt" + ], + "commandType" : "devicectl.device.info.apps", + "environment" : { + "TERM" : "xterm-256color" + }, + "outcome" : "success", + "version" : "341" + }, + "result" : { + "apps" : [ + { + "appClip" : false, + "builtByDeveloper" : true, + "bundleIdentifier" : "com.example.flutterApp", + "bundleVersion" : "1", + "defaultApp" : false, + "hidden" : false, + "internalApp" : false, + "name" : "Bundle", + "removable" : true, + "url" : "file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/", + "version" : "1.0.0" + }, + { + "appClip" : true, + "builtByDeveloper" : false, + "bundleIdentifier" : "com.example.flutterApp2", + "bundleVersion" : "2", + "defaultApp" : true, + "hidden" : true, + "internalApp" : true, + "name" : "Bundle 2", + "removable" : false, + "url" : "file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/", + "version" : "1.0.0" + } + ], + "defaultAppsIncluded" : false, + "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", + "hiddenAppsIncluded" : false, + "internalAppsIncluded" : false, + "matchingBundleIdentifier" : "com.example.flutterApp", + "removableAppsIncluded" : true + } +} +'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_app_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'info', + 'apps', + '--device', + deviceId, + '--bundle-id', + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final List<IOSCoreDeviceInstalledApp> apps = await deviceControl.getInstalledApps( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, isEmpty); + expect(tempFile, isNot(exists)); + expect(apps.length, 2); + + expect(apps[0].appClip, isFalse); + expect(apps[0].builtByDeveloper, isTrue); + expect(apps[0].bundleIdentifier, 'com.example.flutterApp'); + expect(apps[0].bundleVersion, '1'); + expect(apps[0].defaultApp, isFalse); + expect(apps[0].hidden, isFalse); + expect(apps[0].internalApp, isFalse); + expect(apps[0].name, 'Bundle'); + expect(apps[0].removable, isTrue); + expect(apps[0].url, 'file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/'); + expect(apps[0].version, '1.0.0'); + + expect(apps[1].appClip, isTrue); + expect(apps[1].builtByDeveloper, isFalse); + expect(apps[1].bundleIdentifier, 'com.example.flutterApp2'); + expect(apps[1].bundleVersion, '2'); + expect(apps[1].defaultApp, isTrue); + expect(apps[1].hidden, isTrue); + expect(apps[1].internalApp, isTrue); + expect(apps[1].name, 'Bundle 2'); + expect(apps[1].removable, isFalse); + expect(apps[1].url, 'file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/'); + expect(apps[1].version, '1.0.0'); + }); + + + testWithoutContext('Successfully find installed app', () async { + const String deviceControlOutput = ''' +{ + "info" : { + "arguments" : [ + "devicectl", + "device", + "info", + "apps", + "--device", + "00001234-0001234A3C03401E", + "--bundle-id", + "com.example.flutterApp", + "--json-output", + "apps.txt" + ], + "commandType" : "devicectl.device.info.apps", + "environment" : { + "TERM" : "xterm-256color" + }, + "outcome" : "success", + "version" : "341" + }, + "result" : { + "apps" : [ + { + "appClip" : false, + "builtByDeveloper" : true, + "bundleIdentifier" : "com.example.flutterApp", + "bundleVersion" : "1", + "defaultApp" : false, + "hidden" : false, + "internalApp" : false, + "name" : "Bundle", + "removable" : true, + "url" : "file:///private/var/containers/Bundle/Application/12345E6A-7F89-0C12-345E-F6A7E890CFF1/Runner.app/", + "version" : "1.0.0" + } + ], + "defaultAppsIncluded" : false, + "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", + "hiddenAppsIncluded" : false, + "internalAppsIncluded" : false, + "matchingBundleIdentifier" : "com.example.flutterApp", + "removableAppsIncluded" : true + } +} +'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_app_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'info', + 'apps', + '--device', + deviceId, + '--bundle-id', + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final bool status = await deviceControl.isAppInstalled( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, isEmpty); + expect(tempFile, isNot(exists)); + expect(status, true); + }); + + testWithoutContext('Succeeds but does not find app', () async { + const String deviceControlOutput = ''' +{ + "info" : { + "arguments" : [ + "devicectl", + "device", + "info", + "apps", + "--device", + "00001234-0001234A3C03401E", + "--bundle-id", + "com.example.flutterApp", + "--json-output", + "apps.txt" + ], + "commandType" : "devicectl.device.info.apps", + "environment" : { + "TERM" : "xterm-256color" + }, + "outcome" : "success", + "version" : "341" + }, + "result" : { + "apps" : [ + ], + "defaultAppsIncluded" : false, + "deviceIdentifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", + "hiddenAppsIncluded" : false, + "internalAppsIncluded" : false, + "matchingBundleIdentifier" : "com.example.flutterApp", + "removableAppsIncluded" : true + } +} +'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_app_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'info', + 'apps', + '--device', + deviceId, + '--bundle-id', + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final bool status = await deviceControl.isAppInstalled( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, isEmpty); + expect(tempFile, isNot(exists)); + expect(status, false); + }); + + testWithoutContext('devicectl fails to get apps', () async { + const String deviceControlOutput = ''' +{ + "error" : { + "code" : 1000, + "domain" : "com.apple.dt.CoreDeviceError", + "userInfo" : { + "NSLocalizedDescription" : { + "string" : "The specified device was not found." + } + } + }, + "info" : { + "arguments" : [ + "devicectl", + "device", + "info", + "apps", + "--device", + "00001234-0001234A3C03401E", + "--bundle-id", + "com.example.flutterApp", + "--json-output", + "apps.txt" + ], + "commandType" : "devicectl.device.info.apps", + "environment" : { + "TERM" : "xterm-256color" + }, + "outcome" : "failed", + "version" : "341" + } +} +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_app_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'info', + 'apps', + '--device', + deviceId, + '--bundle-id', + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + exitCode: 1, + stderr: ''' +ERROR: The specified device was not found. (com.apple.dt.CoreDeviceError error 1000.) +''' + )); + + final bool status = await deviceControl.isAppInstalled( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('ERROR: The specified device was not found.')); + expect(tempFile, isNot(exists)); + expect(status, false); + }); + + testWithoutContext('fails launch because of unexpected JSON', () async { + const String deviceControlOutput = ''' +{ + "valid_unexpected_json": true +} +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_app_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'info', + 'apps', + '--device', + deviceId, + '--bundle-id', + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + + final bool status = await deviceControl.isAppInstalled( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl returned unexpected JSON response')); + expect(tempFile, isNot(exists)); + expect(status, false); + }); + + testWithoutContext('fails launch because of invalid JSON', () async { + const String deviceControlOutput = ''' +invalid JSON +'''; + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_app_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'device', + 'info', + 'apps', + '--device', + deviceId, + '--bundle-id', + bundleId, + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final bool status = await deviceControl.isAppInstalled( + deviceId: deviceId, + bundleId: bundleId, + ); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl returned non-JSON response')); + expect(tempFile, isNot(exists)); + expect(status, false); + }); + }); + + group('list devices', () { + testWithoutContext('No devices', () async { + const String deviceControlOutput = ''' +{ + "info" : { + "arguments" : [ + "devicectl", + "list", + "devices", + "--json-output", + "core_device_list.json" + ], + "commandType" : "devicectl.list.devices", + "environment" : { + "TERM" : "xterm-256color" + }, + "outcome" : "success", + "version" : "325.3" + }, + "result" : { + "devices" : [ + + ] + } +} +'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'list', + 'devices', + '--timeout', + '5', + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(devices.isEmpty, isTrue); + }); + + testWithoutContext('All sections parsed', () async { + const String deviceControlOutput = ''' +{ + "info" : { + "arguments" : [ + "devicectl", + "list", + "devices", + "--json-output", + "core_device_list.json" + ], + "commandType" : "devicectl.list.devices", + "environment" : { + "TERM" : "xterm-256color" + }, + "outcome" : "success", + "version" : "325.3" + }, + "result" : { + "devices" : [ + { + "capabilities" : [ + ], + "connectionProperties" : { + }, + "deviceProperties" : { + }, + "hardwareProperties" : { + }, + "identifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", + "visibilityClass" : "default" + } + ] + } +} +'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'list', + 'devices', + '--timeout', + '5', + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); + expect(devices.length, 1); + + expect(devices[0].capabilities, isNotNull); + expect(devices[0].connectionProperties, isNotNull); + expect(devices[0].deviceProperties, isNotNull); + expect(devices[0].hardwareProperties, isNotNull); + expect(devices[0].coreDeviceIdentifer, '123456BB5-AEDE-7A22-B890-1234567890DD'); + expect(devices[0].visibilityClass, 'default'); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(tempFile, isNot(exists)); + }); + + testWithoutContext('All sections parsed, device missing sections', () async { + const String deviceControlOutput = ''' +{ + "info" : { + "arguments" : [ + "devicectl", + "list", + "devices", + "--json-output", + "core_device_list.json" + ], + "commandType" : "devicectl.list.devices", + "environment" : { + "TERM" : "xterm-256color" + }, + "outcome" : "success", + "version" : "325.3" + }, + "result" : { + "devices" : [ + { + "identifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", + "visibilityClass" : "default" + } + ] + } +} +'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'list', + 'devices', + '--timeout', + '5', + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); + expect(devices.length, 1); + + expect(devices[0].capabilities, isEmpty); + expect(devices[0].connectionProperties, isNull); + expect(devices[0].deviceProperties, isNull); + expect(devices[0].hardwareProperties, isNull); + expect(devices[0].coreDeviceIdentifer, '123456BB5-AEDE-7A22-B890-1234567890DD'); + expect(devices[0].visibilityClass, 'default'); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(tempFile, isNot(exists)); + }); + + testWithoutContext('capabilities parsed', () async { + const String deviceControlOutput = ''' +{ + "result" : { + "devices" : [ + { + "capabilities" : [ + { + "featureIdentifier" : "com.apple.coredevice.feature.spawnexecutable", + "name" : "Spawn Executable" + }, + { + "featureIdentifier" : "com.apple.coredevice.feature.launchapplication", + "name" : "Launch Application" + } + ] + } + ] + } +} +'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'list', + 'devices', + '--timeout', + '5', + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); + expect(devices.length, 1); + + expect(devices[0].capabilities.length, 2); + expect(devices[0].capabilities[0].featureIdentifier, 'com.apple.coredevice.feature.spawnexecutable'); + expect(devices[0].capabilities[0].name, 'Spawn Executable'); + expect(devices[0].capabilities[1].featureIdentifier, 'com.apple.coredevice.feature.launchapplication'); + expect(devices[0].capabilities[1].name, 'Launch Application'); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(tempFile, isNot(exists)); + }); + + testWithoutContext('connectionProperties parsed', () async { + const String deviceControlOutput = ''' +{ + "result" : { + "devices" : [ + { + "connectionProperties" : { + "authenticationType" : "manualPairing", + "isMobileDeviceOnly" : false, + "lastConnectionDate" : "2023-06-15T15:29:00.082Z", + "localHostnames" : [ + "Victorias-iPad.coredevice.local", + "00001234-0001234A3C03401E.coredevice.local", + "123456BB5-AEDE-7A22-B890-1234567890DD.coredevice.local" + ], + "pairingState" : "paired", + "potentialHostnames" : [ + "00001234-0001234A3C03401E.coredevice.local", + "123456BB5-AEDE-7A22-B890-1234567890DD.coredevice.local" + ], + "transportType" : "wired", + "tunnelIPAddress" : "fdf1:23c4:cd56::1", + "tunnelState" : "connected", + "tunnelTransportProtocol" : "tcp" + } + } + ] + } +} +'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'list', + 'devices', + '--timeout', + '5', + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); + expect(devices.length, 1); + + expect(devices[0].connectionProperties?.authenticationType, 'manualPairing'); + expect(devices[0].connectionProperties?.isMobileDeviceOnly, false); + expect(devices[0].connectionProperties?.lastConnectionDate, '2023-06-15T15:29:00.082Z'); + expect( + devices[0].connectionProperties?.localHostnames, + <String>[ + 'Victorias-iPad.coredevice.local', + '00001234-0001234A3C03401E.coredevice.local', + '123456BB5-AEDE-7A22-B890-1234567890DD.coredevice.local', + ], + ); + expect(devices[0].connectionProperties?.pairingState, 'paired'); + expect(devices[0].connectionProperties?.potentialHostnames, <String>[ + '00001234-0001234A3C03401E.coredevice.local', + '123456BB5-AEDE-7A22-B890-1234567890DD.coredevice.local', + ]); + expect(devices[0].connectionProperties?.transportType, 'wired'); + expect(devices[0].connectionProperties?.tunnelIPAddress, 'fdf1:23c4:cd56::1'); + expect(devices[0].connectionProperties?.tunnelState, 'connected'); + expect(devices[0].connectionProperties?.tunnelTransportProtocol, 'tcp'); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(tempFile, isNot(exists)); + }); + + testWithoutContext('deviceProperties parsed', () async { + const String deviceControlOutput = ''' +{ + "result" : { + "devices" : [ + { + "deviceProperties" : { + "bootedFromSnapshot" : true, + "bootedSnapshotName" : "com.apple.os.update-123456", + "bootState" : "booted", + "ddiServicesAvailable" : true, + "developerModeStatus" : "enabled", + "hasInternalOSBuild" : false, + "name" : "iPadName", + "osBuildUpdate" : "21A5248v", + "osVersionNumber" : "17.0", + "rootFileSystemIsWritable" : false, + "screenViewingURL" : "coredevice-devices:/viewDeviceByUUID?uuid=123456BB5-AEDE-7A22-B890-1234567890DD" + } + } + ] + } +} +'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'list', + 'devices', + '--timeout', + '5', + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); + expect(devices.length, 1); + + expect(devices[0].deviceProperties?.bootedFromSnapshot, true); + expect(devices[0].deviceProperties?.bootedSnapshotName, 'com.apple.os.update-123456'); + expect(devices[0].deviceProperties?.bootState, 'booted'); + expect(devices[0].deviceProperties?.ddiServicesAvailable, true); + expect(devices[0].deviceProperties?.developerModeStatus, 'enabled'); + expect(devices[0].deviceProperties?.hasInternalOSBuild, false); + expect(devices[0].deviceProperties?.name, 'iPadName'); + expect(devices[0].deviceProperties?.osBuildUpdate, '21A5248v'); + expect(devices[0].deviceProperties?.osVersionNumber, '17.0'); + expect(devices[0].deviceProperties?.rootFileSystemIsWritable, false); + expect(devices[0].deviceProperties?.screenViewingURL, 'coredevice-devices:/viewDeviceByUUID?uuid=123456BB5-AEDE-7A22-B890-1234567890DD'); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(tempFile, isNot(exists)); + }); + + testWithoutContext('hardwareProperties parsed', () async { + const String deviceControlOutput = r''' +{ + "result" : { + "devices" : [ + { + "hardwareProperties" : { + "cpuType" : { + "name" : "arm64e", + "subType" : 2, + "type" : 16777228 + }, + "deviceType" : "iPad", + "ecid" : 12345678903408542, + "hardwareModel" : "J617AP", + "internalStorageCapacity" : 128000000000, + "marketingName" : "iPad Pro (11-inch) (4th generation)\"", + "platform" : "iOS", + "productType" : "iPad14,3", + "serialNumber" : "HC123DHCQV", + "supportedCPUTypes" : [ + { + "name" : "arm64e", + "subType" : 2, + "type" : 16777228 + }, + { + "name" : "arm64", + "subType" : 0, + "type" : 16777228 + } + ], + "supportedDeviceFamilies" : [ + 1, + 2 + ], + "thinningProductType" : "iPad14,3-A", + "udid" : "00001234-0001234A3C03401E" + } + } + ] + } +} +'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'list', + 'devices', + '--timeout', + '5', + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); + expect(devices.length, 1); + + expect(devices[0].hardwareProperties?.cpuType, isNotNull); + expect(devices[0].hardwareProperties?.cpuType?.name, 'arm64e'); + expect(devices[0].hardwareProperties?.cpuType?.subType, 2); + expect(devices[0].hardwareProperties?.cpuType?.cpuType, 16777228); + expect(devices[0].hardwareProperties?.deviceType, 'iPad'); + expect(devices[0].hardwareProperties?.ecid, 12345678903408542); + expect(devices[0].hardwareProperties?.hardwareModel, 'J617AP'); + expect(devices[0].hardwareProperties?.internalStorageCapacity, 128000000000); + expect(devices[0].hardwareProperties?.marketingName, 'iPad Pro (11-inch) (4th generation)"'); + expect(devices[0].hardwareProperties?.platform, 'iOS'); + expect(devices[0].hardwareProperties?.productType, 'iPad14,3'); + expect(devices[0].hardwareProperties?.serialNumber, 'HC123DHCQV'); + expect(devices[0].hardwareProperties?.supportedCPUTypes, isNotNull); + expect(devices[0].hardwareProperties?.supportedCPUTypes?[0].name, 'arm64e'); + expect(devices[0].hardwareProperties?.supportedCPUTypes?[0].subType, 2); + expect(devices[0].hardwareProperties?.supportedCPUTypes?[0].cpuType, 16777228); + expect(devices[0].hardwareProperties?.supportedCPUTypes?[1].name, 'arm64'); + expect(devices[0].hardwareProperties?.supportedCPUTypes?[1].subType, 0); + expect(devices[0].hardwareProperties?.supportedCPUTypes?[1].cpuType, 16777228); + expect(devices[0].hardwareProperties?.supportedDeviceFamilies, <int>[1, 2]); + expect(devices[0].hardwareProperties?.thinningProductType, 'iPad14,3-A'); + + expect(devices[0].hardwareProperties?.udid, '00001234-0001234A3C03401E'); + + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(tempFile, isNot(exists)); + }); + + group('Handles errors', () { + testWithoutContext('invalid json', () async { + const String deviceControlOutput = '''Invalid JSON'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'list', + 'devices', + '--timeout', + '5', + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); + expect(devices.isEmpty, isTrue); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl returned non-JSON response: Invalid JSON')); + }); + + testWithoutContext('unexpected json', () async { + const String deviceControlOutput = ''' +{ + "info" : { + "arguments" : [ + "devicectl", + "list", + "devices", + "--json-output", + "device_list.json" + ], + "commandType" : "devicectl.list.devices", + "environment" : { + "TERM" : "xterm-256color" + }, + "outcome" : "success", + "version" : "325.3" + }, + "result" : [ + + ] +} +'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'list', + 'devices', + '--timeout', + '5', + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices(); + expect(devices.isEmpty, isTrue); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('devicectl returned unexpected JSON response:')); + }); + + testWithoutContext('When timeout is below minimum, default to minimum', () async { + const String deviceControlOutput = ''' +{ + "info" : { + "arguments" : [ + "devicectl", + "list", + "devices", + "--json-output", + "core_device_list.json" + ], + "commandType" : "devicectl.list.devices", + "environment" : { + "TERM" : "xterm-256color" + }, + "outcome" : "success", + "version" : "325.3" + }, + "result" : { + "devices" : [ + { + "identifier" : "123456BB5-AEDE-7A22-B890-1234567890DD", + "visibilityClass" : "default" + } + ] + } +} +'''; + + final File tempFile = fileSystem.systemTempDirectory + .childDirectory('core_devices.rand0') + .childFile('core_device_list.json'); + fakeProcessManager.addCommand(FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + 'list', + 'devices', + '--timeout', + '5', + '--json-output', + tempFile.path, + ], + onRun: () { + expect(tempFile, exists); + tempFile.writeAsStringSync(deviceControlOutput); + }, + )); + + final List<IOSCoreDevice> devices = await deviceControl.getCoreDevices( + timeout: const Duration(seconds: 2), + ); + expect(devices.isNotEmpty, isTrue); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect( + logger.errorText, + contains('Timeout of 2 seconds is below the minimum timeout value ' + 'for devicectl. Changing the timeout to the minimum value of 5.'), + ); + }); + }); + }); + + + }); +} diff --git a/packages/flutter_tools/test/general.shard/ios/devices_test.dart b/packages/flutter_tools/test/general.shard/ios/devices_test.dart index 73105f780485d..146e05b2b70b7 100644 --- a/packages/flutter_tools/test/general.shard/ios/devices_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/devices_test.dart @@ -13,16 +13,19 @@ import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device_port_forwarder.dart'; import 'package:flutter_tools/src/ios/application_package.dart'; +import 'package:flutter_tools/src/ios/core_devices.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/ios_workflow.dart'; import 'package:flutter_tools/src/ios/iproxy.dart'; import 'package:flutter_tools/src/ios/mac.dart'; +import 'package:flutter_tools/src/ios/xcode_debug.dart'; import 'package:flutter_tools/src/macos/xcdevice.dart'; import 'package:test/fake.dart'; @@ -41,6 +44,8 @@ void main() { late IOSDeploy iosDeploy; late IMobileDevice iMobileDevice; late FileSystem fileSystem; + late IOSCoreDeviceControl coreDeviceControl; + late XcodeDebug xcodeDebug; setUp(() { final Artifacts artifacts = Artifacts.test(); @@ -60,6 +65,8 @@ void main() { logger: logger, processManager: FakeProcessManager.any(), ); + coreDeviceControl = FakeIOSCoreDeviceControl(); + xcodeDebug = FakeXcodeDebug(); }); testWithoutContext('successfully instantiates on Mac OS', () { @@ -71,12 +78,15 @@ void main() { platform: macPlatform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ); expect(device.isSupported(), isTrue); }); @@ -90,11 +100,14 @@ void main() { platform: macPlatform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, name: 'iPhone 1', cpuArchitecture: DarwinArch.armv7, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ); expect(device.isSupported(), isFalse); }); @@ -108,12 +121,15 @@ void main() { platform: macPlatform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '1.0.0', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ).majorSdkVersion, 1); expect(IOSDevice( 'device-123', @@ -123,12 +139,15 @@ void main() { platform: macPlatform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '13.1.1', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ).majorSdkVersion, 13); expect(IOSDevice( 'device-123', @@ -138,12 +157,15 @@ void main() { platform: macPlatform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '10', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ).majorSdkVersion, 10); expect(IOSDevice( 'device-123', @@ -153,12 +175,15 @@ void main() { platform: macPlatform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: '0', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ).majorSdkVersion, 0); expect(IOSDevice( 'device-123', @@ -168,15 +193,151 @@ void main() { platform: macPlatform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, name: 'iPhone 1', cpuArchitecture: DarwinArch.arm64, sdkVersion: 'bogus', connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ).majorSdkVersion, 0); }); + testWithoutContext('parses sdk version', () { + Version? sdkVersion = IOSDevice( + 'device-123', + iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), + fileSystem: fileSystem, + logger: logger, + platform: macPlatform, + iosDeploy: iosDeploy, + iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, + name: 'iPhone 1', + cpuArchitecture: DarwinArch.arm64, + sdkVersion: '13.3.1', + connectionInterface: DeviceConnectionInterface.attached, + isConnected: true, + devModeEnabled: true, + isCoreDevice: false, + ).sdkVersion; + Version expectedVersion = Version(13, 3, 1, text: '13.3.1'); + expect(sdkVersion, isNotNull); + expect(sdkVersion!.toString(), expectedVersion.toString()); + expect(sdkVersion.compareTo(expectedVersion), 0); + + sdkVersion = IOSDevice( + 'device-123', + iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), + fileSystem: fileSystem, + logger: logger, + platform: macPlatform, + iosDeploy: iosDeploy, + iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, + name: 'iPhone 1', + cpuArchitecture: DarwinArch.arm64, + sdkVersion: '13.3.1 (20ADBC)', + connectionInterface: DeviceConnectionInterface.attached, + isConnected: true, + devModeEnabled: true, + isCoreDevice: false, + ).sdkVersion; + expectedVersion = Version(13, 3, 1, text: '13.3.1 (20ADBC)'); + expect(sdkVersion, isNotNull); + expect(sdkVersion!.toString(), expectedVersion.toString()); + expect(sdkVersion.compareTo(expectedVersion), 0); + + sdkVersion = IOSDevice( + 'device-123', + iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), + fileSystem: fileSystem, + logger: logger, + platform: macPlatform, + iosDeploy: iosDeploy, + iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, + name: 'iPhone 1', + cpuArchitecture: DarwinArch.arm64, + sdkVersion: '16.4.1(a) (20ADBC)', + connectionInterface: DeviceConnectionInterface.attached, + isConnected: true, + devModeEnabled: true, + isCoreDevice: false, + ).sdkVersion; + expectedVersion = Version(16, 4, 1, text: '16.4.1(a) (20ADBC)'); + expect(sdkVersion, isNotNull); + expect(sdkVersion!.toString(), expectedVersion.toString()); + expect(sdkVersion.compareTo(expectedVersion), 0); + + sdkVersion = IOSDevice( + 'device-123', + iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), + fileSystem: fileSystem, + logger: logger, + platform: macPlatform, + iosDeploy: iosDeploy, + iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, + name: 'iPhone 1', + cpuArchitecture: DarwinArch.arm64, + sdkVersion: '0', + connectionInterface: DeviceConnectionInterface.attached, + isConnected: true, + devModeEnabled: true, + isCoreDevice: false, + ).sdkVersion; + expectedVersion = Version(0, 0, 0, text: '0'); + expect(sdkVersion, isNotNull); + expect(sdkVersion!.toString(), expectedVersion.toString()); + expect(sdkVersion.compareTo(expectedVersion), 0); + + sdkVersion = IOSDevice( + 'device-123', + iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), + fileSystem: fileSystem, + logger: logger, + platform: macPlatform, + iosDeploy: iosDeploy, + iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, + name: 'iPhone 1', + cpuArchitecture: DarwinArch.arm64, + connectionInterface: DeviceConnectionInterface.attached, + isConnected: true, + devModeEnabled: true, + isCoreDevice: false, + ).sdkVersion; + expect(sdkVersion, isNull); + + sdkVersion = IOSDevice( + 'device-123', + iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), + fileSystem: fileSystem, + logger: logger, + platform: macPlatform, + iosDeploy: iosDeploy, + iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, + name: 'iPhone 1', + cpuArchitecture: DarwinArch.arm64, + sdkVersion: 'bogus', + connectionInterface: DeviceConnectionInterface.attached, + isConnected: true, + devModeEnabled: true, + isCoreDevice: false, + ).sdkVersion; + expect(sdkVersion, isNull); + }); + testWithoutContext('has build number in sdkNameAndVersion', () async { final IOSDevice device = IOSDevice( 'device-123', @@ -186,12 +347,15 @@ void main() { platform: macPlatform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, name: 'iPhone 1', sdkVersion: '13.3 17C54', cpuArchitecture: DarwinArch.arm64, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ); expect(await device.sdkNameAndVersion,'iOS 13.3 17C54'); @@ -206,12 +370,15 @@ void main() { platform: macPlatform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ); expect(device.supportsRuntimeMode(BuildMode.debug), true); @@ -232,12 +399,15 @@ void main() { platform: platform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ); }, throwsAssertionError, @@ -324,12 +494,15 @@ void main() { platform: macPlatform, iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, name: 'iPhone 1', sdkVersion: '13.3', cpuArchitecture: DarwinArch.arm64, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ); logReader1 = createLogReader(device, appPackage1, process1); logReader2 = createLogReader(device, appPackage2, process2); @@ -355,6 +528,8 @@ void main() { late IOSDeploy iosDeploy; late IMobileDevice iMobileDevice; late IOSWorkflow iosWorkflow; + late IOSCoreDeviceControl coreDeviceControl; + late XcodeDebug xcodeDebug; late IOSDevice device1; late IOSDevice device2; @@ -378,6 +553,8 @@ void main() { processManager: fakeProcessManager, logger: logger, ); + coreDeviceControl = FakeIOSCoreDeviceControl(); + xcodeDebug = FakeXcodeDebug(); device1 = IOSDevice( 'd83d5bc53967baa0ee18626ba87b6254b2ab5418', @@ -387,12 +564,15 @@ void main() { iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, logger: logger, platform: macPlatform, fileSystem: MemoryFileSystem.test(), connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ); device2 = IOSDevice( @@ -403,12 +583,15 @@ void main() { iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, logger: logger, platform: macPlatform, fileSystem: MemoryFileSystem.test(), connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ); }); @@ -665,6 +848,8 @@ void main() { late IOSDeploy iosDeploy; late IMobileDevice iMobileDevice; late IOSWorkflow iosWorkflow; + late IOSCoreDeviceControl coreDeviceControl; + late XcodeDebug xcodeDebug; late IOSDevice notConnected1; setUp(() { @@ -687,6 +872,8 @@ void main() { processManager: fakeProcessManager, logger: logger, ); + coreDeviceControl = FakeIOSCoreDeviceControl(); + xcodeDebug = FakeXcodeDebug(); notConnected1 = IOSDevice( '00000001-0000000000000000', name: 'iPad', @@ -695,12 +882,15 @@ void main() { iProxy: IProxy.test(logger: logger, processManager: FakeProcessManager.any()), iosDeploy: iosDeploy, iMobileDevice: iMobileDevice, + coreDeviceControl: coreDeviceControl, + xcodeDebug: xcodeDebug, logger: logger, platform: macPlatform, fileSystem: MemoryFileSystem.test(), connectionInterface: DeviceConnectionInterface.attached, isConnected: false, devModeEnabled: true, + isCoreDevice: false, ); }); @@ -849,3 +1039,10 @@ class FakeProcess extends Fake implements Process { return true; } } + +class FakeXcodeDebug extends Fake implements XcodeDebug { + @override + bool get debugStarted => false; +} + +class FakeIOSCoreDeviceControl extends Fake implements IOSCoreDeviceControl {} diff --git a/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart index ac6372fe1c89b..72e453823fcb7 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_deploy_test.dart @@ -94,26 +94,6 @@ void main () { logger = BufferLogger.test(); }); - testWithoutContext('print all lines', () async { - final StreamController<List<int>> stdin = StreamController<List<int>>(); - final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ - FakeCommand( - command: const <String>['ios-deploy'], - stdout: "(mylldb) platform select remote-'ios' --sysroot\r\n(mylldb) run\r\nsuccess\r\nrandom string\r\n", - stdin: IOSink(stdin.sink), - ), - ]); - final IOSDeployDebugger iosDeployDebugger = IOSDeployDebugger.test( - processManager: processManager, - logger: logger, - ); - expect(await iosDeployDebugger.launchAndAttach(), isTrue); - expect(logger.traceText, contains("(mylldb) platform select remote-'ios' --sysroot")); - expect(logger.traceText, contains('(mylldb) run')); - expect(logger.traceText, contains('success')); - expect(logger.traceText, contains('random string')); - }); - testWithoutContext('custom lldb prompt', () async { final StreamController<List<int>> stdin = StreamController<List<int>>(); final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart index 8c93d8fb4f81d..6a5f5226fdba9 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_install_test.dart @@ -12,10 +12,13 @@ import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/ios/application_package.dart'; +import 'package:flutter_tools/src/ios/core_devices.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/iproxy.dart'; import 'package:flutter_tools/src/ios/mac.dart'; +import 'package:flutter_tools/src/ios/xcode_debug.dart'; +import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/fake_process_manager.dart'; @@ -105,6 +108,28 @@ void main() { expect(processManager, hasNoRemainingExpectations); }); + testWithoutContext('IOSDevice.installApp uses devicectl for CoreDevices', () async { + final IOSApp iosApp = PrebuiltIOSApp( + projectBundleId: 'app', + uncompressedBundle: fileSystem.currentDirectory, + applicationPackage: bundleDirectory, + ); + + final FakeProcessManager processManager = FakeProcessManager.empty(); + + final IOSDevice device = setUpIOSDevice( + processManager: processManager, + fileSystem: fileSystem, + interfaceType: DeviceConnectionInterface.attached, + artifacts: artifacts, + isCoreDevice: true, + ); + final bool wasInstalled = await device.installApp(iosApp); + + expect(wasInstalled, true); + expect(processManager, hasNoRemainingExpectations); + }); + testWithoutContext('IOSDevice.uninstallApp calls ios-deploy correctly', () async { final IOSApp iosApp = PrebuiltIOSApp( projectBundleId: 'app', @@ -134,6 +159,28 @@ void main() { expect(processManager, hasNoRemainingExpectations); }); + testWithoutContext('IOSDevice.uninstallApp uses devicectl for CoreDevices', () async { + final IOSApp iosApp = PrebuiltIOSApp( + projectBundleId: 'app', + uncompressedBundle: fileSystem.currentDirectory, + applicationPackage: bundleDirectory, + ); + + final FakeProcessManager processManager = FakeProcessManager.empty(); + + final IOSDevice device = setUpIOSDevice( + processManager: processManager, + fileSystem: fileSystem, + interfaceType: DeviceConnectionInterface.attached, + artifacts: artifacts, + isCoreDevice: true, + ); + final bool wasUninstalled = await device.uninstallApp(iosApp); + + expect(wasUninstalled, true); + expect(processManager, hasNoRemainingExpectations); + }); + group('isAppInstalled', () { testWithoutContext('catches ProcessException from ios-deploy', () async { final IOSApp iosApp = PrebuiltIOSApp( @@ -263,6 +310,28 @@ void main() { expect(processManager, hasNoRemainingExpectations); expect(logger.traceText, contains(stderr)); }); + + testWithoutContext('uses devicectl for CoreDevices', () async { + final IOSApp iosApp = PrebuiltIOSApp( + projectBundleId: 'app', + uncompressedBundle: fileSystem.currentDirectory, + applicationPackage: bundleDirectory, + ); + + final FakeProcessManager processManager = FakeProcessManager.empty(); + + final IOSDevice device = setUpIOSDevice( + processManager: processManager, + fileSystem: fileSystem, + interfaceType: DeviceConnectionInterface.attached, + artifacts: artifacts, + isCoreDevice: true, + ); + final bool wasInstalled = await device.isAppInstalled(iosApp); + + expect(wasInstalled, true); + expect(processManager, hasNoRemainingExpectations); + }); }); testWithoutContext('IOSDevice.installApp catches ProcessException from ios-deploy', () async { @@ -314,6 +383,8 @@ void main() { expect(wasAppUninstalled, false); }); + + } IOSDevice setUpIOSDevice({ @@ -322,6 +393,7 @@ IOSDevice setUpIOSDevice({ Logger? logger, DeviceConnectionInterface? interfaceType, Artifacts? artifacts, + bool isCoreDevice = false, }) { logger ??= BufferLogger.test(); final FakePlatform platform = FakePlatform( @@ -357,9 +429,42 @@ IOSDevice setUpIOSDevice({ artifacts: artifacts, cache: cache, ), + coreDeviceControl: FakeIOSCoreDeviceControl(), + xcodeDebug: FakeXcodeDebug(), iProxy: IProxy.test(logger: logger, processManager: processManager), connectionInterface: interfaceType ?? DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: isCoreDevice, ); } + +class FakeXcodeDebug extends Fake implements XcodeDebug {} + +class FakeIOSCoreDeviceControl extends Fake implements IOSCoreDeviceControl { + @override + Future<bool> installApp({ + required String deviceId, + required String bundlePath, + }) async { + + return true; + } + + @override + Future<bool> uninstallApp({ + required String deviceId, + required String bundleId, + }) async { + + return true; + } + + @override + Future<bool> isAppInstalled({ + required String deviceId, + required String bundleId, + }) async { + return true; + } +} diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart index 07ea502346953..01506cd99dfca 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_logger_test.dart @@ -190,7 +190,7 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt ])); }); - testWithoutContext('IOSDeviceLogReader ignores VM Service logs when attached to debugger', () async { + testWithoutContext('IOSDeviceLogReader ignores VM Service logs when attached to and received flutter logs from debugger', () async { final Event stdoutEvent = Event( kind: 'Stdout', timestamp: 0, @@ -229,14 +229,14 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt iosDeployDebugger.debuggerAttached = true; final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[ - 'Message from debugger', + 'flutter: Message from debugger', ]); iosDeployDebugger.logLines = debuggingLogs; logReader.debuggerStream = iosDeployDebugger; // Wait for stream listeners to fire. await expectLater(logReader.logLines, emitsInAnyOrder(<Matcher>[ - equals('Message from debugger'), + equals('flutter: Message from debugger'), ])); }); }); @@ -348,6 +348,647 @@ Runner(libsystem_asl.dylib)[297] <Notice>: libMobileGestalt ); }); }); + + group('Determine which loggers to use', () { + testWithoutContext('for physically attached CoreDevice', () { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: processManager, + cache: fakeCache, + logger: logger, + ), + majorSdkVersion: 17, + isCoreDevice: true, + ); + + expect(logReader.useSyslogLogging, isTrue); + expect(logReader.useUnifiedLogging, isTrue); + expect(logReader.useIOSDeployLogging, isFalse); + expect(logReader.logSources.primarySource, IOSDeviceLogSource.idevicesyslog); + expect(logReader.logSources.fallbackSource, IOSDeviceLogSource.unifiedLogging); + }); + + testWithoutContext('for wirelessly attached CoreDevice', () { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: processManager, + cache: fakeCache, + logger: logger, + ), + majorSdkVersion: 17, + isCoreDevice: true, + isWirelesslyConnected: true, + ); + + expect(logReader.useSyslogLogging, isFalse); + expect(logReader.useUnifiedLogging, isTrue); + expect(logReader.useIOSDeployLogging, isFalse); + expect(logReader.logSources.primarySource, IOSDeviceLogSource.unifiedLogging); + expect(logReader.logSources.fallbackSource, isNull); + }); + + testWithoutContext('for iOS 12 or less device', () { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: processManager, + cache: fakeCache, + logger: logger, + ), + majorSdkVersion: 12, + ); + + expect(logReader.useSyslogLogging, isTrue); + expect(logReader.useUnifiedLogging, isFalse); + expect(logReader.useIOSDeployLogging, isFalse); + expect(logReader.logSources.primarySource, IOSDeviceLogSource.idevicesyslog); + expect(logReader.logSources.fallbackSource, isNull); + }); + + testWithoutContext('for iOS 13 or greater non-CoreDevice and _iosDeployDebugger not attached', () { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: processManager, + cache: fakeCache, + logger: logger, + ), + majorSdkVersion: 13, + ); + + expect(logReader.useSyslogLogging, isFalse); + expect(logReader.useUnifiedLogging, isTrue); + expect(logReader.useIOSDeployLogging, isTrue); + expect(logReader.logSources.primarySource, IOSDeviceLogSource.iosDeploy); + expect(logReader.logSources.fallbackSource, IOSDeviceLogSource.unifiedLogging); + }); + + testWithoutContext('for iOS 13 or greater non-CoreDevice, _iosDeployDebugger not attached, and VM is connected', () { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: processManager, + cache: fakeCache, + logger: logger, + ), + majorSdkVersion: 13, + ); + + final FlutterVmService vmService = FakeVmServiceHost(requests: <VmServiceExpectation>[ + const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{ + 'streamId': 'Debug', + }), + const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{ + 'streamId': 'Stdout', + }), + const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{ + 'streamId': 'Stderr', + }), + ]).vmService; + + logReader.connectedVMService = vmService; + + expect(logReader.useSyslogLogging, isFalse); + expect(logReader.useUnifiedLogging, isTrue); + expect(logReader.useIOSDeployLogging, isTrue); + expect(logReader.logSources.primarySource, IOSDeviceLogSource.unifiedLogging); + expect(logReader.logSources.fallbackSource, IOSDeviceLogSource.iosDeploy); + }); + + testWithoutContext('for iOS 13 or greater non-CoreDevice and _iosDeployDebugger is attached', () { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: processManager, + cache: fakeCache, + logger: logger, + ), + majorSdkVersion: 13, + ); + + final FakeIOSDeployDebugger iosDeployDebugger = FakeIOSDeployDebugger(); + iosDeployDebugger.debuggerAttached = true; + logReader.debuggerStream = iosDeployDebugger; + + final FlutterVmService vmService = FakeVmServiceHost(requests: <VmServiceExpectation>[ + const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{ + 'streamId': 'Debug', + }), + const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{ + 'streamId': 'Stdout', + }), + const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{ + 'streamId': 'Stderr', + }), + ]).vmService; + + logReader.connectedVMService = vmService; + + expect(logReader.useSyslogLogging, isFalse); + expect(logReader.useUnifiedLogging, isTrue); + expect(logReader.useIOSDeployLogging, isTrue); + expect(logReader.logSources.primarySource, IOSDeviceLogSource.iosDeploy); + expect(logReader.logSources.fallbackSource, IOSDeviceLogSource.unifiedLogging); + }); + + testWithoutContext('for iOS 16 or greater non-CoreDevice', () { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: processManager, + cache: fakeCache, + logger: logger, + ), + majorSdkVersion: 16, + ); + + final FakeIOSDeployDebugger iosDeployDebugger = FakeIOSDeployDebugger(); + iosDeployDebugger.debuggerAttached = true; + logReader.debuggerStream = iosDeployDebugger; + + expect(logReader.useSyslogLogging, isFalse); + expect(logReader.useUnifiedLogging, isTrue); + expect(logReader.useIOSDeployLogging, isTrue); + expect(logReader.logSources.primarySource, IOSDeviceLogSource.iosDeploy); + expect(logReader.logSources.fallbackSource, IOSDeviceLogSource.unifiedLogging); + }); + + testWithoutContext('for iOS 16 or greater non-CoreDevice in CI', () { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: processManager, + cache: fakeCache, + logger: logger, + ), + usingCISystem: true, + majorSdkVersion: 16, + ); + + expect(logReader.useSyslogLogging, isTrue); + expect(logReader.useUnifiedLogging, isFalse); + expect(logReader.useIOSDeployLogging, isTrue); + expect(logReader.logSources.primarySource, IOSDeviceLogSource.iosDeploy); + expect(logReader.logSources.fallbackSource, IOSDeviceLogSource.idevicesyslog); + }); + + group('when useSyslogLogging', () { + + testWithoutContext('is true syslog sends flutter messages to stream', () async { + processManager.addCommand( + FakeCommand( + command: <String>[ + ideviceSyslogPath, '-u', '1234', + ], + stdout: ''' + Runner(Flutter)[297] <Notice>: A is for ari + Runner(Flutter)[297] <Notice>: I is for ichigo + May 30 13:56:28 Runner(Flutter)[2037] <Notice>: flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/ + May 30 13:56:28 Runner(Flutter)[2037] <Notice>: flutter: This is a test + May 30 13:56:28 Runner(Flutter)[2037] <Notice>: [VERBOSE-2:FlutterDarwinContextMetalImpeller.mm(39)] Using the Impeller rendering backend. + ''' + ), + ); + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: processManager, + cache: fakeCache, + logger: logger, + ), + usingCISystem: true, + majorSdkVersion: 16, + ); + final List<String> lines = await logReader.logLines.toList(); + + expect(logReader.useSyslogLogging, isTrue); + expect(processManager, hasNoRemainingExpectations); + expect(lines, <String>[ + 'flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/', + 'flutter: This is a test' + ]); + }); + + testWithoutContext('is false syslog does not send flutter messages to stream', () async { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: processManager, + cache: fakeCache, + logger: logger, + ), + majorSdkVersion: 16, + ); + + final FakeIOSDeployDebugger iosDeployDebugger = FakeIOSDeployDebugger(); + iosDeployDebugger.logLines = Stream<String>.fromIterable(<String>[]); + logReader.debuggerStream = iosDeployDebugger; + + final List<String> lines = await logReader.logLines.toList(); + + expect(logReader.useSyslogLogging, isFalse); + expect(processManager, hasNoRemainingExpectations); + expect(lines, isEmpty); + }); + }); + + group('when useIOSDeployLogging', () { + + testWithoutContext('is true ios-deploy sends flutter messages to stream', () async { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: processManager, + cache: fakeCache, + logger: logger, + ), + majorSdkVersion: 16, + ); + + final FakeIOSDeployDebugger iosDeployDebugger = FakeIOSDeployDebugger(); + final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[ + 'flutter: Message from debugger', + ]); + iosDeployDebugger.logLines = debuggingLogs; + logReader.debuggerStream = iosDeployDebugger; + + final List<String> lines = await logReader.logLines.toList(); + + expect(logReader.useIOSDeployLogging, isTrue); + expect(processManager, hasNoRemainingExpectations); + expect(lines, <String>[ + 'flutter: Message from debugger', + ]); + }); + + testWithoutContext('is false ios-deploy does not send flutter messages to stream', () async { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: FakeProcessManager.any(), + cache: fakeCache, + logger: logger, + ), + majorSdkVersion: 12, + ); + + final FakeIOSDeployDebugger iosDeployDebugger = FakeIOSDeployDebugger(); + final Stream<String> debuggingLogs = Stream<String>.fromIterable(<String>[ + 'flutter: Message from debugger', + ]); + iosDeployDebugger.logLines = debuggingLogs; + logReader.debuggerStream = iosDeployDebugger; + + final List<String> lines = await logReader.logLines.toList(); + + expect(logReader.useIOSDeployLogging, isFalse); + expect(processManager, hasNoRemainingExpectations); + expect(lines, isEmpty); + }); + }); + + group('when useUnifiedLogging', () { + + + testWithoutContext('is true Dart VM sends flutter messages to stream', () async { + final Event stdoutEvent = Event( + kind: 'Stdout', + timestamp: 0, + bytes: base64.encode(utf8.encode('flutter: A flutter message')), + ); + final Event stderrEvent = Event( + kind: 'Stderr', + timestamp: 0, + bytes: base64.encode(utf8.encode('flutter: A second flutter message')), + ); + final FlutterVmService vmService = FakeVmServiceHost(requests: <VmServiceExpectation>[ + const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{ + 'streamId': 'Debug', + }), + const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{ + 'streamId': 'Stdout', + }), + const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{ + 'streamId': 'Stderr', + }), + FakeVmServiceStreamResponse(event: stdoutEvent, streamId: 'Stdout'), + FakeVmServiceStreamResponse(event: stderrEvent, streamId: 'Stderr'), + ]).vmService; + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + useSyslog: false, + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: processManager, + cache: fakeCache, + logger: logger, + ), + ); + logReader.connectedVMService = vmService; + + // Wait for stream listeners to fire. + expect(logReader.useUnifiedLogging, isTrue); + expect(processManager, hasNoRemainingExpectations); + await expectLater(logReader.logLines, emitsInAnyOrder(<Matcher>[ + equals('flutter: A flutter message'), + equals('flutter: A second flutter message'), + ])); + }); + + testWithoutContext('is false Dart VM does not send flutter messages to stream', () async { + final Event stdoutEvent = Event( + kind: 'Stdout', + timestamp: 0, + bytes: base64.encode(utf8.encode('flutter: A flutter message')), + ); + final Event stderrEvent = Event( + kind: 'Stderr', + timestamp: 0, + bytes: base64.encode(utf8.encode('flutter: A second flutter message')), + ); + final FlutterVmService vmService = FakeVmServiceHost(requests: <VmServiceExpectation>[ + const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{ + 'streamId': 'Debug', + }), + const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{ + 'streamId': 'Stdout', + }), + const FakeVmServiceRequest(method: 'streamListen', args: <String, Object>{ + 'streamId': 'Stderr', + }), + FakeVmServiceStreamResponse(event: stdoutEvent, streamId: 'Stdout'), + FakeVmServiceStreamResponse(event: stderrEvent, streamId: 'Stderr'), + ]).vmService; + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: FakeProcessManager.any(), + cache: fakeCache, + logger: logger, + ), + majorSdkVersion: 12, + ); + logReader.connectedVMService = vmService; + + final List<String> lines = await logReader.logLines.toList(); + + // Wait for stream listeners to fire. + expect(logReader.useUnifiedLogging, isFalse); + expect(processManager, hasNoRemainingExpectations); + expect(lines, isEmpty); + }); + }); + + group('and when to exclude logs:', () { + + testWithoutContext('all primary messages are included except if fallback sent flutter message first', () async { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: FakeProcessManager.any(), + cache: fakeCache, + logger: logger, + ), + usingCISystem: true, + majorSdkVersion: 16, + ); + + expect(logReader.useSyslogLogging, isTrue); + expect(logReader.useIOSDeployLogging, isTrue); + expect(logReader.logSources.primarySource, IOSDeviceLogSource.iosDeploy); + expect(logReader.logSources.fallbackSource, IOSDeviceLogSource.idevicesyslog); + + final Future<List<String>> logLines = logReader.logLines.toList(); + + logReader.addToLinesController( + 'flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/', + IOSDeviceLogSource.idevicesyslog, + ); + // Will be excluded because was already added by fallback. + logReader.addToLinesController( + 'flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/', + IOSDeviceLogSource.iosDeploy, + ); + logReader.addToLinesController( + 'A second non-flutter message', + IOSDeviceLogSource.iosDeploy, + ); + logReader.addToLinesController( + 'flutter: Another flutter message', + IOSDeviceLogSource.iosDeploy, + ); + final List<String> lines = await logLines; + + expect(lines, containsAllInOrder(<String>[ + 'flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/', // from idevicesyslog + 'A second non-flutter message', // from iosDeploy + 'flutter: Another flutter message', // from iosDeploy + ])); + }); + + testWithoutContext('all primary messages are included when there is no fallback', () async { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: FakeProcessManager.any(), + cache: fakeCache, + logger: logger, + ), + majorSdkVersion: 12, + ); + + expect(logReader.useSyslogLogging, isTrue); + expect(logReader.logSources.primarySource, IOSDeviceLogSource.idevicesyslog); + expect(logReader.logSources.fallbackSource, isNull); + + final Future<List<String>> logLines = logReader.logLines.toList(); + + logReader.addToLinesController( + 'flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/', + IOSDeviceLogSource.idevicesyslog, + ); + logReader.addToLinesController( + 'A non-flutter message', + IOSDeviceLogSource.idevicesyslog, + ); + logReader.addToLinesController( + 'A non-flutter message', + IOSDeviceLogSource.idevicesyslog, + ); + logReader.addToLinesController( + 'flutter: A flutter message', + IOSDeviceLogSource.idevicesyslog, + ); + logReader.addToLinesController( + 'flutter: A flutter message', + IOSDeviceLogSource.idevicesyslog, + ); + final List<String> lines = await logLines; + + expect(lines, containsAllInOrder(<String>[ + 'flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/', + 'A non-flutter message', + 'A non-flutter message', + 'flutter: A flutter message', + 'flutter: A flutter message', + ])); + }); + + testWithoutContext('primary messages are not added if fallback already added them, otherwise duplicates are allowed', () async { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: FakeProcessManager.any(), + cache: fakeCache, + logger: logger, + ), + usingCISystem: true, + majorSdkVersion: 16, + ); + + expect(logReader.useSyslogLogging, isTrue); + expect(logReader.useIOSDeployLogging, isTrue); + expect(logReader.logSources.primarySource, IOSDeviceLogSource.iosDeploy); + expect(logReader.logSources.fallbackSource, IOSDeviceLogSource.idevicesyslog); + + final Future<List<String>> logLines = logReader.logLines.toList(); + + logReader.addToLinesController( + 'flutter: A flutter message', + IOSDeviceLogSource.idevicesyslog, + ); + logReader.addToLinesController( + 'flutter: A flutter message', + IOSDeviceLogSource.idevicesyslog, + ); + logReader.addToLinesController( + 'A non-flutter message', + IOSDeviceLogSource.iosDeploy, + ); + logReader.addToLinesController( + 'A non-flutter message', + IOSDeviceLogSource.iosDeploy, + ); + // Will be excluded because was already added by fallback. + logReader.addToLinesController( + 'flutter: A flutter message', + IOSDeviceLogSource.iosDeploy, + ); + // Will be excluded because was already added by fallback. + logReader.addToLinesController( + 'flutter: A flutter message', + IOSDeviceLogSource.iosDeploy, + ); + // Will be included because, although the message is the same, the + // fallback only added it twice so this third one is considered new. + logReader.addToLinesController( + 'flutter: A flutter message', + IOSDeviceLogSource.iosDeploy, + ); + + final List<String> lines = await logLines; + + expect(lines, containsAllInOrder(<String>[ + 'flutter: A flutter message', // from idevicesyslog + 'flutter: A flutter message', // from idevicesyslog + 'A non-flutter message', // from iosDeploy + 'A non-flutter message', // from iosDeploy + 'flutter: A flutter message', // from iosDeploy + ])); + }); + + testWithoutContext('flutter fallback messages are included until a primary flutter message is received', () async { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: FakeProcessManager.any(), + cache: fakeCache, + logger: logger, + ), + usingCISystem: true, + majorSdkVersion: 16, + ); + + expect(logReader.useSyslogLogging, isTrue); + expect(logReader.useIOSDeployLogging, isTrue); + expect(logReader.logSources.primarySource, IOSDeviceLogSource.iosDeploy); + expect(logReader.logSources.fallbackSource, IOSDeviceLogSource.idevicesyslog); + + final Future<List<String>> logLines = logReader.logLines.toList(); + + logReader.addToLinesController( + 'flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/', + IOSDeviceLogSource.idevicesyslog, + ); + logReader.addToLinesController( + 'A second non-flutter message', + IOSDeviceLogSource.iosDeploy, + ); + // Will be included because the first log from primary source wasn't a + // flutter log. + logReader.addToLinesController( + 'flutter: A flutter message', + IOSDeviceLogSource.idevicesyslog, + ); + // Will be excluded because was already added by fallback, however, it + // will be used to determine a flutter log was received by the primary source. + logReader.addToLinesController( + 'flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/', + IOSDeviceLogSource.iosDeploy, + ); + // Will be excluded because flutter log from primary was received. + logReader.addToLinesController( + 'flutter: A third flutter message', + IOSDeviceLogSource.idevicesyslog, + ); + + final List<String> lines = await logLines; + + expect(lines, containsAllInOrder(<String>[ + 'flutter: The Dart VM service is listening on http://127.0.0.1:63098/35ZezGIQLnw=/', // from idevicesyslog + 'A second non-flutter message', // from iosDeploy + 'flutter: A flutter message', // from idevicesyslog + ])); + }); + + testWithoutContext('non-flutter fallback messages are not included', () async { + final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( + iMobileDevice: IMobileDevice( + artifacts: artifacts, + processManager: FakeProcessManager.any(), + cache: fakeCache, + logger: logger, + ), + usingCISystem: true, + majorSdkVersion: 16, + ); + + expect(logReader.useSyslogLogging, isTrue); + expect(logReader.useIOSDeployLogging, isTrue); + expect(logReader.logSources.primarySource, IOSDeviceLogSource.iosDeploy); + expect(logReader.logSources.fallbackSource, IOSDeviceLogSource.idevicesyslog); + + final Future<List<String>> logLines = logReader.logLines.toList(); + + logReader.addToLinesController( + 'flutter: A flutter message', + IOSDeviceLogSource.idevicesyslog, + ); + // Will be excluded because it's from fallback and not a flutter message. + logReader.addToLinesController( + 'A non-flutter message', + IOSDeviceLogSource.idevicesyslog, + ); + + final List<String> lines = await logLines; + + expect(lines, containsAllInOrder(<String>[ + 'flutter: A flutter message', + ])); + }); + }); + }); } class FakeIOSDeployDebugger extends Fake implements IOSDeployDebugger { diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart index 082d70b77beee..abe1e850a4a1a 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_project_test.dart @@ -10,11 +10,14 @@ import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/device.dart'; +import 'package:flutter_tools/src/ios/core_devices.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/iproxy.dart'; import 'package:flutter_tools/src/ios/mac.dart'; +import 'package:flutter_tools/src/ios/xcode_debug.dart'; import 'package:flutter_tools/src/project.dart'; +import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart'; @@ -94,6 +97,8 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) { cache: Cache.test(processManager: processManager), ), iMobileDevice: IMobileDevice.test(processManager: processManager), + coreDeviceControl: FakeIOSCoreDeviceControl(), + xcodeDebug: FakeXcodeDebug(), platform: platform, name: 'iPhone 1', sdkVersion: '13.3', @@ -102,5 +107,10 @@ IOSDevice setUpIOSDevice(FileSystem fileSystem) { connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: false, ); } + +class FakeXcodeDebug extends Fake implements XcodeDebug {} + +class FakeIOSCoreDeviceControl extends Fake implements IOSCoreDeviceControl {} diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart index be0d51a3438ad..68d78e5f9b346 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_nonprebuilt_test.dart @@ -2,6 +2,8 @@ // 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:fake_async/fake_async.dart'; import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; @@ -13,11 +15,14 @@ import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/device.dart'; +import 'package:flutter_tools/src/device_port_forwarder.dart'; import 'package:flutter_tools/src/ios/application_package.dart'; +import 'package:flutter_tools/src/ios/core_devices.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/iproxy.dart'; import 'package:flutter_tools/src/ios/mac.dart'; +import 'package:flutter_tools/src/ios/xcode_debug.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/macos/xcode.dart'; import 'package:flutter_tools/src/project.dart'; @@ -25,6 +30,7 @@ import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart' hide FakeXcodeProjectInterpreter; +import '../../src/fake_devices.dart'; import '../../src/fake_process_manager.dart'; import '../../src/fakes.dart'; @@ -287,13 +293,442 @@ void main() { Xcode: () => xcode, }, skip: true); // TODO(zanderso): clean up with https://github.com/flutter/flutter/issues/60675 }); + + group('IOSDevice.startApp for CoreDevice', () { + late FileSystem fileSystem; + late FakeProcessManager processManager; + late BufferLogger logger; + late Xcode xcode; + late FakeXcodeProjectInterpreter fakeXcodeProjectInterpreter; + late XcodeProjectInfo projectInfo; + + setUp(() { + logger = BufferLogger.test(); + fileSystem = MemoryFileSystem.test(); + processManager = FakeProcessManager.empty(); + projectInfo = XcodeProjectInfo( + <String>['Runner'], + <String>['Debug', 'Release'], + <String>['Runner'], + logger, + ); + fakeXcodeProjectInterpreter = FakeXcodeProjectInterpreter(projectInfo: projectInfo); + xcode = Xcode.test(processManager: FakeProcessManager.any(), xcodeProjectInterpreter: fakeXcodeProjectInterpreter); + fileSystem.file('foo/.packages') + ..createSync(recursive: true) + ..writeAsStringSync('\n'); + }); + + group('in release mode', () { + testUsingContext('suceeds when install and launch succeed', () async { + final IOSDevice iosDevice = setUpIOSDevice( + fileSystem: fileSystem, + processManager: FakeProcessManager.any(), + logger: logger, + artifacts: artifacts, + isCoreDevice: true, + coreDeviceControl: FakeIOSCoreDeviceControl(), + ); + setUpIOSProject(fileSystem); + final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); + final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app'); + fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true); + + final LaunchResult launchResult = await iosDevice.startApp( + buildableIOSApp, + debuggingOptions: DebuggingOptions.disabled(BuildInfo.release), + platformArgs: <String, Object>{}, + ); + + expect(fileSystem.directory('build/ios/iphoneos'), exists); + expect(launchResult.started, true); + expect(processManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + ProcessManager: () => FakeProcessManager.any(), + FileSystem: () => fileSystem, + Logger: () => logger, + Platform: () => macPlatform, + XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter, + Xcode: () => xcode, + }); + + testUsingContext('fails when install fails', () async { + final IOSDevice iosDevice = setUpIOSDevice( + fileSystem: fileSystem, + processManager: FakeProcessManager.any(), + logger: logger, + artifacts: artifacts, + isCoreDevice: true, + coreDeviceControl: FakeIOSCoreDeviceControl( + installSuccess: false, + ), + ); + setUpIOSProject(fileSystem); + final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); + final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app'); + fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true); + + final LaunchResult launchResult = await iosDevice.startApp( + buildableIOSApp, + debuggingOptions: DebuggingOptions.disabled(BuildInfo.release), + platformArgs: <String, Object>{}, + ); + + expect(fileSystem.directory('build/ios/iphoneos'), exists); + expect(launchResult.started, false); + expect(processManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + ProcessManager: () => FakeProcessManager.any(), + FileSystem: () => fileSystem, + Logger: () => logger, + Platform: () => macPlatform, + XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter, + Xcode: () => xcode, + }); + + testUsingContext('fails when launch fails', () async { + final IOSDevice iosDevice = setUpIOSDevice( + fileSystem: fileSystem, + processManager: FakeProcessManager.any(), + logger: logger, + artifacts: artifacts, + isCoreDevice: true, + coreDeviceControl: FakeIOSCoreDeviceControl( + launchSuccess: false, + ), + ); + setUpIOSProject(fileSystem); + final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); + final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app'); + fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true); + + final LaunchResult launchResult = await iosDevice.startApp( + buildableIOSApp, + debuggingOptions: DebuggingOptions.disabled(BuildInfo.release), + platformArgs: <String, Object>{}, + ); + + expect(fileSystem.directory('build/ios/iphoneos'), exists); + expect(launchResult.started, false); + expect(processManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + ProcessManager: () => FakeProcessManager.any(), + FileSystem: () => fileSystem, + Logger: () => logger, + Platform: () => macPlatform, + XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter, + Xcode: () => xcode, + }); + + testUsingContext('ensure arguments passed to launch', () async { + final FakeIOSCoreDeviceControl coreDeviceControl = FakeIOSCoreDeviceControl(); + final IOSDevice iosDevice = setUpIOSDevice( + fileSystem: fileSystem, + processManager: FakeProcessManager.any(), + logger: logger, + artifacts: artifacts, + isCoreDevice: true, + coreDeviceControl: coreDeviceControl, + ); + setUpIOSProject(fileSystem); + final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); + final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app'); + fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true); + + final LaunchResult launchResult = await iosDevice.startApp( + buildableIOSApp, + debuggingOptions: DebuggingOptions.disabled(BuildInfo.release), + platformArgs: <String, Object>{}, + ); + + expect(fileSystem.directory('build/ios/iphoneos'), exists); + expect(launchResult.started, true); + expect(processManager, hasNoRemainingExpectations); + expect(coreDeviceControl.argumentsUsedForLaunch, isNotNull); + expect(coreDeviceControl.argumentsUsedForLaunch, contains('--enable-dart-profiling')); + }, overrides: <Type, Generator>{ + ProcessManager: () => FakeProcessManager.any(), + FileSystem: () => fileSystem, + Logger: () => logger, + Platform: () => macPlatform, + XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter, + Xcode: () => xcode, + }); + + }); + + group('in debug mode', () { + + testUsingContext('succeeds', () async { + final IOSDevice iosDevice = setUpIOSDevice( + fileSystem: fileSystem, + processManager: FakeProcessManager.any(), + logger: logger, + artifacts: artifacts, + isCoreDevice: true, + coreDeviceControl: FakeIOSCoreDeviceControl(), + xcodeDebug: FakeXcodeDebug( + expectedProject: XcodeDebugProject( + scheme: 'Runner', + xcodeWorkspace: fileSystem.directory('/ios/Runner.xcworkspace'), + xcodeProject: fileSystem.directory('/ios/Runner.xcodeproj'), + hostAppProjectName: 'Runner', + ), + expectedDeviceId: '123', + expectedLaunchArguments: <String>['--enable-dart-profiling'], + ), + ); + + setUpIOSProject(fileSystem); + final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); + final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app'); + fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true); + + final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader(); + + iosDevice.portForwarder = const NoOpDevicePortForwarder(); + iosDevice.setLogReader(buildableIOSApp, deviceLogReader); + + // Start writing messages to the log reader. + Timer.run(() { + deviceLogReader.addLine('Foo'); + deviceLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:456'); + }); + + final LaunchResult launchResult = await iosDevice.startApp( + buildableIOSApp, + debuggingOptions: DebuggingOptions.enabled(const BuildInfo( + BuildMode.debug, + null, + buildName: '1.2.3', + buildNumber: '4', + treeShakeIcons: false, + )), + platformArgs: <String, Object>{}, + ); + + expect(logger.errorText, isEmpty); + expect(fileSystem.directory('build/ios/iphoneos'), exists); + expect(launchResult.started, true); + expect(processManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + ProcessManager: () => FakeProcessManager.any(), + FileSystem: () => fileSystem, + Logger: () => logger, + Platform: () => macPlatform, + XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter, + Xcode: () => xcode, + }); + + testUsingContext('updates Generated.xcconfig before and after launch', () async { + final Completer<void> debugStartedCompleter = Completer<void>(); + final Completer<void> debugEndedCompleter = Completer<void>(); + final IOSDevice iosDevice = setUpIOSDevice( + fileSystem: fileSystem, + processManager: FakeProcessManager.any(), + logger: logger, + artifacts: artifacts, + isCoreDevice: true, + coreDeviceControl: FakeIOSCoreDeviceControl(), + xcodeDebug: FakeXcodeDebug( + expectedProject: XcodeDebugProject( + scheme: 'Runner', + xcodeWorkspace: fileSystem.directory('/ios/Runner.xcworkspace'), + xcodeProject: fileSystem.directory('/ios/Runner.xcodeproj'), + hostAppProjectName: 'Runner', + expectedConfigurationBuildDir: '/build/ios/iphoneos', + ), + expectedDeviceId: '123', + expectedLaunchArguments: <String>['--enable-dart-profiling'], + debugStartedCompleter: debugStartedCompleter, + debugEndedCompleter: debugEndedCompleter, + ), + ); + + setUpIOSProject(fileSystem); + final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); + final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app'); + fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true); + + final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader(); + + iosDevice.portForwarder = const NoOpDevicePortForwarder(); + iosDevice.setLogReader(buildableIOSApp, deviceLogReader); + + // Start writing messages to the log reader. + Timer.run(() { + deviceLogReader.addLine('Foo'); + deviceLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:456'); + }); + + final Future<LaunchResult> futureLaunchResult = iosDevice.startApp( + buildableIOSApp, + debuggingOptions: DebuggingOptions.enabled(const BuildInfo( + BuildMode.debug, + null, + buildName: '1.2.3', + buildNumber: '4', + treeShakeIcons: false, + )), + platformArgs: <String, Object>{}, + ); + + await debugStartedCompleter.future; + + // Validate CoreDevice build settings were used + final File config = fileSystem.directory('ios').childFile('Flutter/Generated.xcconfig'); + expect(config.existsSync(), isTrue); + + String contents = config.readAsStringSync(); + expect(contents, contains('CONFIGURATION_BUILD_DIR=/build/ios/iphoneos')); + + debugEndedCompleter.complete(); + + await futureLaunchResult; + + // Validate CoreDevice build settings were removed after launch + contents = config.readAsStringSync(); + expect(contents.contains('CONFIGURATION_BUILD_DIR'), isFalse); + }, overrides: <Type, Generator>{ + ProcessManager: () => FakeProcessManager.any(), + FileSystem: () => fileSystem, + Logger: () => logger, + Platform: () => macPlatform, + XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter, + Xcode: () => xcode, + }); + + testUsingContext('fails when Xcode project is not found', () async { + final IOSDevice iosDevice = setUpIOSDevice( + fileSystem: fileSystem, + processManager: FakeProcessManager.any(), + logger: logger, + artifacts: artifacts, + isCoreDevice: true, + coreDeviceControl: FakeIOSCoreDeviceControl() + ); + setUpIOSProject(fileSystem); + final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); + final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app'); + fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true); + + final LaunchResult launchResult = await iosDevice.startApp( + buildableIOSApp, + debuggingOptions: DebuggingOptions.enabled(const BuildInfo( + BuildMode.debug, + null, + buildName: '1.2.3', + buildNumber: '4', + treeShakeIcons: false, + )), + platformArgs: <String, Object>{}, + ); + expect(logger.errorText, contains('Xcode project not found')); + expect(fileSystem.directory('build/ios/iphoneos'), exists); + expect(launchResult.started, false); + expect(processManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + ProcessManager: () => FakeProcessManager.any(), + FileSystem: () => fileSystem, + Logger: () => logger, + Platform: () => macPlatform, + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreter(), + Xcode: () => xcode, + }); + + testUsingContext('fails when Xcode workspace is not found', () async { + final IOSDevice iosDevice = setUpIOSDevice( + fileSystem: fileSystem, + processManager: FakeProcessManager.any(), + logger: logger, + artifacts: artifacts, + isCoreDevice: true, + coreDeviceControl: FakeIOSCoreDeviceControl() + ); + setUpIOSProject(fileSystem, createWorkspace: false); + final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); + final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app'); + fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true); + + final LaunchResult launchResult = await iosDevice.startApp( + buildableIOSApp, + debuggingOptions: DebuggingOptions.enabled(const BuildInfo( + BuildMode.debug, + null, + buildName: '1.2.3', + buildNumber: '4', + treeShakeIcons: false, + )), + platformArgs: <String, Object>{}, + ); + expect(logger.errorText, contains('Unable to get Xcode workspace')); + expect(fileSystem.directory('build/ios/iphoneos'), exists); + expect(launchResult.started, false); + expect(processManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + ProcessManager: () => FakeProcessManager.any(), + FileSystem: () => fileSystem, + Logger: () => logger, + Platform: () => macPlatform, + XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter, + Xcode: () => xcode, + }); + + testUsingContext('fails when scheme is not found', () async { + final IOSDevice iosDevice = setUpIOSDevice( + fileSystem: fileSystem, + processManager: FakeProcessManager.any(), + logger: logger, + artifacts: artifacts, + isCoreDevice: true, + coreDeviceControl: FakeIOSCoreDeviceControl() + ); + setUpIOSProject(fileSystem); + final FlutterProject flutterProject = FlutterProject.fromDirectory(fileSystem.currentDirectory); + final BuildableIOSApp buildableIOSApp = BuildableIOSApp(flutterProject.ios, 'flutter', 'My Super Awesome App.app'); + fileSystem.directory('build/ios/Release-iphoneos/My Super Awesome App.app').createSync(recursive: true); + + final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader(); + + iosDevice.portForwarder = const NoOpDevicePortForwarder(); + iosDevice.setLogReader(buildableIOSApp, deviceLogReader); + + // Start writing messages to the log reader. + Timer.run(() { + deviceLogReader.addLine('Foo'); + deviceLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:456'); + }); + + expect(() async => iosDevice.startApp( + buildableIOSApp, + debuggingOptions: DebuggingOptions.enabled(const BuildInfo( + BuildMode.debug, + 'Flavor', + buildName: '1.2.3', + buildNumber: '4', + treeShakeIcons: false, + )), + platformArgs: <String, Object>{}, + ), throwsToolExit()); + }, overrides: <Type, Generator>{ + ProcessManager: () => FakeProcessManager.any(), + FileSystem: () => fileSystem, + Logger: () => logger, + Platform: () => macPlatform, + XcodeProjectInterpreter: () => fakeXcodeProjectInterpreter, + Xcode: () => xcode, + }); + }); + }); } -void setUpIOSProject(FileSystem fileSystem) { +void setUpIOSProject(FileSystem fileSystem, {bool createWorkspace = true}) { fileSystem.file('pubspec.yaml').createSync(); fileSystem.file('.packages').writeAsStringSync('\n'); fileSystem.directory('ios').createSync(); - fileSystem.directory('ios/Runner.xcworkspace').createSync(); + if (createWorkspace) { + fileSystem.directory('ios/Runner.xcworkspace').createSync(); + } fileSystem.file('ios/Runner.xcodeproj/project.pbxproj').createSync(recursive: true); // This is the expected output directory. fileSystem.directory('build/ios/iphoneos/My Super Awesome App.app').createSync(recursive: true); @@ -305,6 +740,9 @@ IOSDevice setUpIOSDevice({ Logger? logger, ProcessManager? processManager, Artifacts? artifacts, + bool isCoreDevice = false, + IOSCoreDeviceControl? coreDeviceControl, + FakeXcodeDebug? xcodeDebug, }) { artifacts ??= Artifacts.test(); final Cache cache = Cache.test( @@ -336,10 +774,13 @@ IOSDevice setUpIOSDevice({ artifacts: artifacts, cache: cache, ), + coreDeviceControl: coreDeviceControl ?? FakeIOSCoreDeviceControl(), + xcodeDebug: xcodeDebug ?? FakeXcodeDebug(), cpuArchitecture: DarwinArch.arm64, connectionInterface: DeviceConnectionInterface.attached, isConnected: true, devModeEnabled: true, + isCoreDevice: isCoreDevice, ); } @@ -381,3 +822,76 @@ class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterprete Duration timeout = const Duration(minutes: 1), }) async => buildSettings; } + +class FakeXcodeDebug extends Fake implements XcodeDebug { + FakeXcodeDebug({ + this.debugSuccess = true, + this.expectedProject, + this.expectedDeviceId, + this.expectedLaunchArguments, + this.debugStartedCompleter, + this.debugEndedCompleter, + }); + + final bool debugSuccess; + + final XcodeDebugProject? expectedProject; + final String? expectedDeviceId; + final List<String>? expectedLaunchArguments; + final Completer<void>? debugStartedCompleter; + final Completer<void>? debugEndedCompleter; + + @override + Future<bool> debugApp({ + required XcodeDebugProject project, + required String deviceId, + required List<String> launchArguments, + }) async { + debugStartedCompleter?.complete(); + if (expectedProject != null) { + expect(project.scheme, expectedProject!.scheme); + expect(project.xcodeWorkspace.path, expectedProject!.xcodeWorkspace.path); + expect(project.xcodeProject.path, expectedProject!.xcodeProject.path); + expect(project.isTemporaryProject, expectedProject!.isTemporaryProject); + } + if (expectedDeviceId != null) { + expect(deviceId, expectedDeviceId); + } + if (expectedLaunchArguments != null) { + expect(expectedLaunchArguments, launchArguments); + } + await debugEndedCompleter?.future; + return debugSuccess; + } +} + +class FakeIOSCoreDeviceControl extends Fake implements IOSCoreDeviceControl { + FakeIOSCoreDeviceControl({ + this.installSuccess = true, + this.launchSuccess = true + }); + + final bool installSuccess; + final bool launchSuccess; + List<String>? _launchArguments; + + List<String>? get argumentsUsedForLaunch => _launchArguments; + + @override + Future<bool> installApp({ + required String deviceId, + required String bundlePath, + }) async { + return installSuccess; + } + + @override + Future<bool> launchApp({ + required String deviceId, + required String bundleId, + List<String> launchArguments = const <String>[], + }) async { + _launchArguments = launchArguments; + return launchSuccess; + } +} diff --git a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart index 5d01888b0c471..9ca0aceca475e 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_device_start_prebuilt_test.dart @@ -5,20 +5,25 @@ import 'dart:async'; import 'dart:convert'; +import 'package:fake_async/fake_async.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/base/process.dart'; +import 'package:flutter_tools/src/base/template.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device_port_forwarder.dart'; import 'package:flutter_tools/src/ios/application_package.dart'; +import 'package:flutter_tools/src/ios/core_devices.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/ios_deploy.dart'; import 'package:flutter_tools/src/ios/iproxy.dart'; import 'package:flutter_tools/src/ios/mac.dart'; +import 'package:flutter_tools/src/ios/xcode_debug.dart'; import 'package:flutter_tools/src/mdns_discovery.dart'; import 'package:test/fake.dart'; @@ -601,6 +606,216 @@ void main() { expect(await device.stopApp(iosApp), false); expect(processManager, hasNoRemainingExpectations); }); + + group('IOSDevice.startApp for CoreDevice', () { + group('in debug mode', () { + testUsingContext('succeeds', () async { + final FileSystem fileSystem = MemoryFileSystem.test(); + final FakeProcessManager processManager = FakeProcessManager.empty(); + + final Directory temporaryXcodeProjectDirectory = fileSystem.systemTempDirectory.childDirectory('flutter_empty_xcode.rand0'); + final Directory bundleLocation = fileSystem.currentDirectory; + final IOSDevice device = setUpIOSDevice( + processManager: processManager, + fileSystem: fileSystem, + isCoreDevice: true, + coreDeviceControl: FakeIOSCoreDeviceControl(), + xcodeDebug: FakeXcodeDebug( + expectedProject: XcodeDebugProject( + scheme: 'Runner', + xcodeWorkspace: temporaryXcodeProjectDirectory.childDirectory('Runner.xcworkspace'), + xcodeProject: temporaryXcodeProjectDirectory.childDirectory('Runner.xcodeproj'), + hostAppProjectName: 'Runner', + ), + expectedDeviceId: '123', + expectedLaunchArguments: <String>['--enable-dart-profiling'], + expectedBundlePath: bundleLocation.path, + ) + ); + final IOSApp iosApp = PrebuiltIOSApp( + projectBundleId: 'app', + bundleName: 'Runner', + uncompressedBundle: bundleLocation, + applicationPackage: bundleLocation, + ); + final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader(); + + device.portForwarder = const NoOpDevicePortForwarder(); + device.setLogReader(iosApp, deviceLogReader); + + // Start writing messages to the log reader. + Timer.run(() { + deviceLogReader.addLine('Foo'); + deviceLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:456'); + }); + + final LaunchResult launchResult = await device.startApp(iosApp, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), + platformArgs: <String, dynamic>{}, + ); + + expect(launchResult.started, true); + }); + + testUsingContext('prints warning message if it takes too long to start debugging', () async { + final FileSystem fileSystem = MemoryFileSystem.test(); + final FakeProcessManager processManager = FakeProcessManager.empty(); + final BufferLogger logger = BufferLogger.test(); + final Directory temporaryXcodeProjectDirectory = fileSystem.systemTempDirectory.childDirectory('flutter_empty_xcode.rand0'); + final Directory bundleLocation = fileSystem.currentDirectory; + final Completer<void> completer = Completer<void>(); + final FakeXcodeDebug xcodeDebug = FakeXcodeDebug( + expectedProject: XcodeDebugProject( + scheme: 'Runner', + xcodeWorkspace: temporaryXcodeProjectDirectory.childDirectory('Runner.xcworkspace'), + xcodeProject: temporaryXcodeProjectDirectory.childDirectory('Runner.xcodeproj'), + hostAppProjectName: 'Runner', + ), + expectedDeviceId: '123', + expectedLaunchArguments: <String>['--enable-dart-profiling'], + expectedBundlePath: bundleLocation.path, + completer: completer, + ); + final IOSDevice device = setUpIOSDevice( + processManager: processManager, + fileSystem: fileSystem, + logger: logger, + isCoreDevice: true, + coreDeviceControl: FakeIOSCoreDeviceControl(), + xcodeDebug: xcodeDebug, + ); + final IOSApp iosApp = PrebuiltIOSApp( + projectBundleId: 'app', + bundleName: 'Runner', + uncompressedBundle: bundleLocation, + applicationPackage: bundleLocation, + ); + final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader(); + + device.portForwarder = const NoOpDevicePortForwarder(); + device.setLogReader(iosApp, deviceLogReader); + + // Start writing messages to the log reader. + Timer.run(() { + deviceLogReader.addLine('Foo'); + deviceLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:456'); + }); + + FakeAsync().run((FakeAsync fakeAsync) { + device.startApp(iosApp, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), + platformArgs: <String, dynamic>{}, + ); + + fakeAsync.flushTimers(); + expect(logger.errorText, contains('Xcode is taking longer than expected to start debugging the app. Ensure the project is opened in Xcode.')); + completer.complete(); + }); + }); + + testUsingContext('succeeds with shutdown hook added when running from CI', () async { + final FileSystem fileSystem = MemoryFileSystem.test(); + final FakeProcessManager processManager = FakeProcessManager.empty(); + + final Directory temporaryXcodeProjectDirectory = fileSystem.systemTempDirectory.childDirectory('flutter_empty_xcode.rand0'); + final Directory bundleLocation = fileSystem.currentDirectory; + final IOSDevice device = setUpIOSDevice( + processManager: processManager, + fileSystem: fileSystem, + isCoreDevice: true, + coreDeviceControl: FakeIOSCoreDeviceControl(), + xcodeDebug: FakeXcodeDebug( + expectedProject: XcodeDebugProject( + scheme: 'Runner', + xcodeWorkspace: temporaryXcodeProjectDirectory.childDirectory('Runner.xcworkspace'), + xcodeProject: temporaryXcodeProjectDirectory.childDirectory('Runner.xcodeproj'), + hostAppProjectName: 'Runner', + ), + expectedDeviceId: '123', + expectedLaunchArguments: <String>['--enable-dart-profiling'], + expectedBundlePath: bundleLocation.path, + ) + ); + final IOSApp iosApp = PrebuiltIOSApp( + projectBundleId: 'app', + bundleName: 'Runner', + uncompressedBundle: bundleLocation, + applicationPackage: bundleLocation, + ); + final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader(); + + device.portForwarder = const NoOpDevicePortForwarder(); + device.setLogReader(iosApp, deviceLogReader); + + // Start writing messages to the log reader. + Timer.run(() { + deviceLogReader.addLine('Foo'); + deviceLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:456'); + }); + + final FakeShutDownHooks shutDownHooks = FakeShutDownHooks(); + + final LaunchResult launchResult = await device.startApp(iosApp, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug, usingCISystem: true), + platformArgs: <String, dynamic>{}, + shutdownHooks: shutDownHooks, + ); + + expect(launchResult.started, true); + expect(shutDownHooks.hooks.length, 1); + }); + + testUsingContext('IOSDevice.startApp attaches in debug mode via mDNS when device logging fails', () async { + final FileSystem fileSystem = MemoryFileSystem.test(); + final FakeProcessManager processManager = FakeProcessManager.empty(); + + final Directory temporaryXcodeProjectDirectory = fileSystem.systemTempDirectory.childDirectory('flutter_empty_xcode.rand0'); + final Directory bundleLocation = fileSystem.currentDirectory; + final IOSDevice device = setUpIOSDevice( + processManager: processManager, + fileSystem: fileSystem, + isCoreDevice: true, + coreDeviceControl: FakeIOSCoreDeviceControl(), + xcodeDebug: FakeXcodeDebug( + expectedProject: XcodeDebugProject( + scheme: 'Runner', + xcodeWorkspace: temporaryXcodeProjectDirectory.childDirectory('Runner.xcworkspace'), + xcodeProject: temporaryXcodeProjectDirectory.childDirectory('Runner.xcodeproj'), + hostAppProjectName: 'Runner', + ), + expectedDeviceId: '123', + expectedLaunchArguments: <String>['--enable-dart-profiling'], + expectedBundlePath: bundleLocation.path, + ) + ); + final IOSApp iosApp = PrebuiltIOSApp( + projectBundleId: 'app', + bundleName: 'Runner', + uncompressedBundle: bundleLocation, + applicationPackage: bundleLocation, + ); + final FakeDeviceLogReader deviceLogReader = FakeDeviceLogReader(); + + device.portForwarder = const NoOpDevicePortForwarder(); + device.setLogReader(iosApp, deviceLogReader); + + final LaunchResult launchResult = await device.startApp(iosApp, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), + platformArgs: <String, dynamic>{}, + ); + + expect(launchResult.started, true); + expect(launchResult.hasVmService, true); + expect(await device.stopApp(iosApp), true); + }, overrides: <Type, Generator>{ + MDnsVmServiceDiscovery: () => FakeMDnsVmServiceDiscovery(), + }); + }); + }); } IOSDevice setUpIOSDevice({ @@ -610,6 +825,9 @@ IOSDevice setUpIOSDevice({ ProcessManager? processManager, IOSDeploy? iosDeploy, DeviceConnectionInterface interfaceType = DeviceConnectionInterface.attached, + bool isCoreDevice = false, + IOSCoreDeviceControl? coreDeviceControl, + FakeXcodeDebug? xcodeDebug, }) { final Artifacts artifacts = Artifacts.test(); final FakePlatform macPlatform = FakePlatform( @@ -646,10 +864,13 @@ IOSDevice setUpIOSDevice({ artifacts: artifacts, cache: cache, ), + coreDeviceControl: coreDeviceControl ?? FakeIOSCoreDeviceControl(), + xcodeDebug: xcodeDebug ?? FakeXcodeDebug(), cpuArchitecture: DarwinArch.arm64, connectionInterface: interfaceType, isConnected: true, devModeEnabled: true, + isCoreDevice: isCoreDevice, ); } @@ -669,10 +890,88 @@ class FakeMDnsVmServiceDiscovery extends Fake implements MDnsVmServiceDiscovery Device device, { bool usesIpv6 = false, int? hostVmservicePort, - required int deviceVmservicePort, + int? deviceVmservicePort, bool useDeviceIPAsHost = false, Duration timeout = Duration.zero, }) async { return Uri.tryParse('http://0.0.0.0:1234'); } } + +class FakeXcodeDebug extends Fake implements XcodeDebug { + FakeXcodeDebug({ + this.debugSuccess = true, + this.expectedProject, + this.expectedDeviceId, + this.expectedLaunchArguments, + this.expectedBundlePath, + this.completer, + }); + + final bool debugSuccess; + final XcodeDebugProject? expectedProject; + final String? expectedDeviceId; + final List<String>? expectedLaunchArguments; + final String? expectedBundlePath; + final Completer<void>? completer; + + @override + bool debugStarted = false; + + @override + Future<XcodeDebugProject> createXcodeProjectWithCustomBundle( + String deviceBundlePath, { + required TemplateRenderer templateRenderer, + Directory? projectDestination, + bool verboseLogging = false, + }) async { + if (expectedBundlePath != null) { + expect(expectedBundlePath, deviceBundlePath); + } + return expectedProject!; + } + + @override + Future<bool> debugApp({ + required XcodeDebugProject project, + required String deviceId, + required List<String> launchArguments, + }) async { + if (expectedProject != null) { + expect(project.scheme, expectedProject!.scheme); + expect(project.xcodeWorkspace.path, expectedProject!.xcodeWorkspace.path); + expect(project.xcodeProject.path, expectedProject!.xcodeProject.path); + expect(project.isTemporaryProject, expectedProject!.isTemporaryProject); + } + if (expectedDeviceId != null) { + expect(deviceId, expectedDeviceId); + } + if (expectedLaunchArguments != null) { + expect(expectedLaunchArguments, launchArguments); + } + debugStarted = debugSuccess; + + if (completer != null) { + await completer!.future; + } + return debugSuccess; + } + + @override + Future<bool> exit({ + bool force = false, + bool skipDelay = false, + }) async { + return true; + } +} + +class FakeIOSCoreDeviceControl extends Fake implements IOSCoreDeviceControl {} + +class FakeShutDownHooks extends Fake implements ShutdownHooks { + List<ShutdownHook> hooks = <ShutdownHook>[]; + @override + void addShutdownHook(ShutdownHook shutdownHook) { + hooks.add(shutdownHook); + } +} diff --git a/packages/flutter_tools/test/general.shard/ios/ios_project_migration_test.dart b/packages/flutter_tools/test/general.shard/ios/ios_project_migration_test.dart index c5ed8b619cca7..3f2277a22c83b 100644 --- a/packages/flutter_tools/test/general.shard/ios/ios_project_migration_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/ios_project_migration_test.dart @@ -16,6 +16,7 @@ import 'package:flutter_tools/src/ios/migrations/remove_framework_link_and_embed import 'package:flutter_tools/src/ios/migrations/xcode_build_system_migration.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/migrations/cocoapods_script_symlink.dart'; +import 'package:flutter_tools/src/migrations/cocoapods_toolchain_directory_migration.dart'; import 'package:flutter_tools/src/migrations/xcode_project_object_version_migration.dart'; import 'package:flutter_tools/src/migrations/xcode_script_build_phase_migration.dart'; import 'package:flutter_tools/src/migrations/xcode_thin_binary_build_phase_input_paths_migration.dart'; @@ -1003,6 +1004,150 @@ platform :ios, '11.0' expect(testLogger.statusText, contains('Upgrading Pods-Runner-frameworks.sh')); }); }); + + group('Cocoapods migrate toolchain directory', () { + late MemoryFileSystem memoryFileSystem; + late BufferLogger testLogger; + late FakeIosProject project; + late Directory podRunnerTargetSupportFiles; + late ProcessManager processManager; + late XcodeProjectInterpreter xcode15ProjectInterpreter; + + setUp(() { + memoryFileSystem = MemoryFileSystem(); + podRunnerTargetSupportFiles = memoryFileSystem.directory('Pods-Runner'); + testLogger = BufferLogger.test(); + project = FakeIosProject(); + processManager = FakeProcessManager.any(); + xcode15ProjectInterpreter = XcodeProjectInterpreter.test(processManager: processManager, version: Version(15, 0, 0)); + project.podRunnerTargetSupportFiles = podRunnerTargetSupportFiles; + }); + + testWithoutContext('skip if directory is missing', () { + final CocoaPodsToolchainDirectoryMigration iosProjectMigration = CocoaPodsToolchainDirectoryMigration( + project, + xcode15ProjectInterpreter, + testLogger, + ); + iosProjectMigration.migrate(); + expect(podRunnerTargetSupportFiles.existsSync(), isFalse); + + expect(testLogger.traceText, contains('CocoaPods Pods-Runner Target Support Files not found')); + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('skip if xcconfig files are missing', () { + podRunnerTargetSupportFiles.createSync(); + final CocoaPodsToolchainDirectoryMigration iosProjectMigration = CocoaPodsToolchainDirectoryMigration( + project, + xcode15ProjectInterpreter, + testLogger, + ); + iosProjectMigration.migrate(); + expect(podRunnerTargetSupportFiles.existsSync(), isTrue); + expect(testLogger.traceText, isEmpty); + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('skip if nothing to upgrade', () { + podRunnerTargetSupportFiles.createSync(); + final File debugConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.debug.xcconfig'); + const String contents = r''' +LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frameworks' '@loader_path/Frameworks' "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" +LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +'''; + debugConfig.writeAsStringSync(contents); + + final File profileConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.profile.xcconfig'); + profileConfig.writeAsStringSync(contents); + + final File releaseConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.release.xcconfig'); + releaseConfig.writeAsStringSync(contents); + + final CocoaPodsToolchainDirectoryMigration iosProjectMigration = CocoaPodsToolchainDirectoryMigration( + project, + xcode15ProjectInterpreter, + testLogger, + ); + iosProjectMigration.migrate(); + expect(debugConfig.existsSync(), isTrue); + expect(testLogger.traceText, isEmpty); + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('skipped if Xcode version below 15', () { + podRunnerTargetSupportFiles.createSync(); + final File debugConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.debug.xcconfig'); + const String contents = r''' +LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frameworks' '@loader_path/Frameworks' "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" +LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +'''; + debugConfig.writeAsStringSync(contents); + + final File profileConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.profile.xcconfig'); + profileConfig.writeAsStringSync(contents); + + final File releaseConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.release.xcconfig'); + releaseConfig.writeAsStringSync(contents); + + final XcodeProjectInterpreter xcode14ProjectInterpreter = XcodeProjectInterpreter.test( + processManager: processManager, + version: Version(14, 0, 0), + ); + + final CocoaPodsToolchainDirectoryMigration iosProjectMigration = CocoaPodsToolchainDirectoryMigration( + project, + xcode14ProjectInterpreter, + testLogger, + ); + iosProjectMigration.migrate(); + expect(debugConfig.existsSync(), isTrue); + expect(testLogger.traceText, contains('Detected Xcode version is 14.0.0, below 15.0')); + expect(testLogger.statusText, isEmpty); + }); + + testWithoutContext('Xcode project is migrated and ignores leading whitespace', () { + podRunnerTargetSupportFiles.createSync(); + final File debugConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.debug.xcconfig'); + const String contents = r''' +LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frameworks' '@loader_path/Frameworks' "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + LIBRARY_SEARCH_PATHS = $(inherited) "${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +'''; + debugConfig.writeAsStringSync(contents); + + final File profileConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.profile.xcconfig'); + profileConfig.writeAsStringSync(contents); + + final File releaseConfig = podRunnerTargetSupportFiles.childFile('Pods-Runner.release.xcconfig'); + releaseConfig.writeAsStringSync(contents); + + final CocoaPodsToolchainDirectoryMigration iosProjectMigration = CocoaPodsToolchainDirectoryMigration( + project, + xcode15ProjectInterpreter, + testLogger, + ); + iosProjectMigration.migrate(); + + expect(debugConfig.existsSync(), isTrue); + expect(debugConfig.readAsStringSync(), r''' +LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frameworks' '@loader_path/Frameworks' "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +'''); + expect(profileConfig.existsSync(), isTrue); + expect(profileConfig.readAsStringSync(), r''' +LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frameworks' '@loader_path/Frameworks' "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +'''); + expect(releaseConfig.existsSync(), isTrue); + expect(releaseConfig.readAsStringSync(), r''' +LD_RUNPATH_SEARCH_PATHS = $(inherited) /usr/lib/swift '@executable_path/../Frameworks' '@loader_path/Frameworks' "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" + LIBRARY_SEARCH_PATHS = $(inherited) "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift +'''); + expect(testLogger.statusText, contains('Upgrading Pods-Runner.debug.xcconfig')); + expect(testLogger.statusText, contains('Upgrading Pods-Runner.profile.xcconfig')); + expect(testLogger.statusText, contains('Upgrading Pods-Runner.release.xcconfig')); + }); + }); }); group('update Xcode script build phase', () { @@ -1239,6 +1384,9 @@ class FakeIosProject extends Fake implements IosProject { @override File podRunnerFrameworksScript = MemoryFileSystem.test().file('podRunnerFrameworksScript'); + + @override + Directory podRunnerTargetSupportFiles = MemoryFileSystem.test().directory('Pods-Runner'); } class FakeIOSMigrator extends ProjectMigrator { diff --git a/packages/flutter_tools/test/general.shard/ios/mac_test.dart b/packages/flutter_tools/test/general.shard/ios/mac_test.dart index e7fef592be5f1..303d261273ee7 100644 --- a/packages/flutter_tools/test/general.shard/ios/mac_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/mac_test.dart @@ -245,6 +245,44 @@ Error launching application on iPhone.''', ); }); + testWithoutContext('fallback to stdout: Ineligible destinations', () async { + final Map<String, String> buildSettingsWithDevTeam = <String, String>{ + 'PRODUCT_BUNDLE_IDENTIFIER': 'test.app', + 'DEVELOPMENT_TEAM': 'a team', + }; + final XcodeBuildResult buildResult = XcodeBuildResult( + success: false, + stderr: ''' +Launching lib/main.dart on iPhone in debug mode... +Signing iOS app for device deployment using developer identity: "iPhone Developer: test@flutter.io (1122334455)" +Running Xcode build... 1.3s +Failed to build iOS app +Error output from Xcode build: +↳ + xcodebuild: error: Unable to find a destination matching the provided destination specifier: + { id:1234D567-890C-1DA2-34E5-F6789A0123C4 } + + Ineligible destinations for the "Runner" scheme: + { platform:iOS, id:dvtdevice-DVTiPhonePlaceholder-iphoneos:placeholder, name:Any iOS Device, error:iOS 17.0 is not installed. To use with Xcode, first download and install the platform } + +Could not build the precompiled application for the device. + +Error launching application on iPhone.''', + xcodeBuildExecution: XcodeBuildExecution( + buildCommands: <String>['xcrun', 'xcodebuild', 'blah'], + appDirectory: '/blah/blah', + environmentType: EnvironmentType.physical, + buildSettings: buildSettingsWithDevTeam, + ), + ); + + await diagnoseXcodeBuildFailure(buildResult, testUsage, logger); + expect( + logger.errorText, + contains(missingPlatformInstructions('iOS 17.0')), + ); + }); + testWithoutContext('No development team shows message', () async { final XcodeBuildResult buildResult = XcodeBuildResult( success: false, diff --git a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart index cefad2011d65f..f3830d0a09005 100644 --- a/packages/flutter_tools/test/general.shard/ios/simulators_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/simulators_test.dart @@ -8,6 +8,7 @@ import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/process.dart'; +import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/device.dart'; @@ -771,14 +772,16 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''' late FakeProcessManager fakeProcessManager; Xcode xcode; late SimControl simControl; + late BufferLogger logger; const String deviceId = 'smart-phone'; const String appId = 'flutterApp'; setUp(() { fakeProcessManager = FakeProcessManager.empty(); xcode = Xcode.test(processManager: FakeProcessManager.any()); + logger = BufferLogger.test(); simControl = SimControl( - logger: BufferLogger.test(), + logger: logger, processManager: fakeProcessManager, xcode: xcode, ); @@ -931,6 +934,159 @@ Dec 20 17:04:32 md32-11-vm1 Another App[88374]: Ignore this text''' expect(await iosSimulator.stopApp(null), isFalse); }); + + testWithoutContext('listAvailableIOSRuntimes succeeds', () async { + const String validRuntimesOutput = ''' +{ + "runtimes" : [ + { + "bundlePath" : "/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 15.4.simruntime", + "buildversion" : "19E240", + "platform" : "iOS", + "runtimeRoot" : "/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 15.4.simruntime/Contents/Resources/RuntimeRoot", + "identifier" : "com.apple.CoreSimulator.SimRuntime.iOS-15-4", + "version" : "15.4", + "isInternal" : false, + "isAvailable" : true, + "name" : "iOS 15.4", + "supportedDeviceTypes" : [ + { + "bundlePath" : "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/DeviceTypes/iPhone 6s.simdevicetype", + "name" : "iPhone 6s", + "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-6s", + "productFamily" : "iPhone" + }, + { + "bundlePath" : "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/DeviceTypes/iPhone 6s Plus.simdevicetype", + "name" : "iPhone 6s Plus", + "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-6s-Plus", + "productFamily" : "iPhone" + } + ] + }, + { + "bundlePath" : "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime", + "buildversion" : "20E247", + "platform" : "iOS", + "runtimeRoot" : "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot", + "identifier" : "com.apple.CoreSimulator.SimRuntime.iOS-16-4", + "version" : "16.4", + "isInternal" : false, + "isAvailable" : true, + "name" : "iOS 16.4", + "supportedDeviceTypes" : [ + { + "bundlePath" : "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/DeviceTypes/iPhone 8.simdevicetype", + "name" : "iPhone 8", + "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-8", + "productFamily" : "iPhone" + }, + { + "bundlePath" : "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/DeviceTypes/iPhone 8 Plus.simdevicetype", + "name" : "iPhone 8 Plus", + "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus", + "productFamily" : "iPhone" + } + ] + }, + { + "bundlePath" : "/Library/Developer/CoreSimulator/Volumes/iOS_21A5268h/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 17.0.simruntime", + "buildversion" : "21A5268h", + "platform" : "iOS", + "runtimeRoot" : "/Library/Developer/CoreSimulator/Volumes/iOS_21A5268h/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 17.0.simruntime/Contents/Resources/RuntimeRoot", + "identifier" : "com.apple.CoreSimulator.SimRuntime.iOS-17-0", + "version" : "17.0", + "isInternal" : false, + "isAvailable" : true, + "name" : "iOS 17.0", + "supportedDeviceTypes" : [ + { + "bundlePath" : "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/DeviceTypes/iPhone 8.simdevicetype", + "name" : "iPhone 8", + "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-8", + "productFamily" : "iPhone" + }, + { + "bundlePath" : "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/DeviceTypes/iPhone 8 Plus.simdevicetype", + "name" : "iPhone 8 Plus", + "identifier" : "com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus", + "productFamily" : "iPhone" + } + ] + } + ] +} + +'''; + fakeProcessManager.addCommand(const FakeCommand( + command: <String>[ + 'xcrun', + 'simctl', + 'list', + 'runtimes', + 'available', + 'iOS', + '--json', + ], + stdout: validRuntimesOutput, + )); + + final List<IOSSimulatorRuntime> runtimes = await simControl.listAvailableIOSRuntimes(); + + final IOSSimulatorRuntime runtime1 = runtimes[0]; + expect(runtime1.bundlePath, '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 15.4.simruntime'); + expect(runtime1.buildVersion, '19E240'); + expect(runtime1.platform, 'iOS'); + expect(runtime1.runtimeRoot, '/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 15.4.simruntime/Contents/Resources/RuntimeRoot'); + expect(runtime1.identifier, 'com.apple.CoreSimulator.SimRuntime.iOS-15-4'); + expect(runtime1.version, Version(15, 4, null)); + expect(runtime1.isInternal, false); + expect(runtime1.isAvailable, true); + expect(runtime1.name, 'iOS 15.4'); + + final IOSSimulatorRuntime runtime2 = runtimes[1]; + expect(runtime2.bundlePath, '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime'); + expect(runtime2.buildVersion, '20E247'); + expect(runtime2.platform, 'iOS'); + expect(runtime2.runtimeRoot, '/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot'); + expect(runtime2.identifier, 'com.apple.CoreSimulator.SimRuntime.iOS-16-4'); + expect(runtime2.version, Version(16, 4, null)); + expect(runtime2.isInternal, false); + expect(runtime2.isAvailable, true); + expect(runtime2.name, 'iOS 16.4'); + + final IOSSimulatorRuntime runtime3 = runtimes[2]; + expect(runtime3.bundlePath, '/Library/Developer/CoreSimulator/Volumes/iOS_21A5268h/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 17.0.simruntime'); + expect(runtime3.buildVersion, '21A5268h'); + expect(runtime3.platform, 'iOS'); + expect(runtime3.runtimeRoot, '/Library/Developer/CoreSimulator/Volumes/iOS_21A5268h/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS 17.0.simruntime/Contents/Resources/RuntimeRoot'); + expect(runtime3.identifier, 'com.apple.CoreSimulator.SimRuntime.iOS-17-0'); + expect(runtime3.version, Version(17, 0, null)); + expect(runtime3.isInternal, false); + expect(runtime3.isAvailable, true); + expect(runtime3.name, 'iOS 17.0'); + }); + + testWithoutContext('listAvailableIOSRuntimes handles bad simctl output', () async { + fakeProcessManager.addCommand(const FakeCommand( + command: <String>[ + 'xcrun', + 'simctl', + 'list', + 'runtimes', + 'available', + 'iOS', + '--json', + ], + stdout: 'Install Started', + )); + + final List<IOSSimulatorRuntime> runtimes = await simControl.listAvailableIOSRuntimes(); + + expect(runtimes, isEmpty); + expect(logger.errorText, contains('simctl returned non-JSON response:')); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); }); group('startApp', () { @@ -1204,7 +1360,7 @@ class FakeSimControl extends Fake implements SimControl { @override Future<RunResult> launch(String deviceId, String appIdentifier, [ List<String>? launchArgs ]) async { - requests.add(LaunchRequest(deviceId, appIdentifier, launchArgs)); + requests.add(LaunchRequest(appIdentifier, launchArgs)); return RunResult(ProcessResult(0, 0, '', ''), <String>['test']); } @@ -1215,9 +1371,8 @@ class FakeSimControl extends Fake implements SimControl { } class LaunchRequest { - const LaunchRequest(this.deviceId, this.appIdentifier, this.launchArgs); + const LaunchRequest(this.appIdentifier, this.launchArgs); - final String deviceId; final String appIdentifier; final List<String>? launchArgs; } diff --git a/packages/flutter_tools/test/general.shard/ios/xcode_debug_test.dart b/packages/flutter_tools/test/general.shard/ios/xcode_debug_test.dart new file mode 100644 index 0000000000000..0fe3a8aa9bdae --- /dev/null +++ b/packages/flutter_tools/test/general.shard/ios/xcode_debug_test.dart @@ -0,0 +1,1163 @@ +// 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:io' as io; + +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/version.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/ios/xcode_debug.dart'; +import 'package:flutter_tools/src/ios/xcodeproj.dart'; +import 'package:flutter_tools/src/macos/xcode.dart'; +import 'package:test/fake.dart'; + +import '../../src/common.dart'; +import '../../src/context.dart'; +import '../../src/fake_process_manager.dart'; + +void main() { + group('Debug project through Xcode', () { + late MemoryFileSystem fileSystem; + late BufferLogger logger; + late FakeProcessManager fakeProcessManager; + + const String flutterRoot = '/path/to/flutter'; + const String pathToXcodeAutomationScript = '$flutterRoot/packages/flutter_tools/bin/xcode_debug.js'; + + setUp(() { + fileSystem = MemoryFileSystem.test(); + logger = BufferLogger.test(); + fakeProcessManager = FakeProcessManager.empty(); + }); + + group('debugApp', () { + const String pathToXcodeApp = '/Applications/Xcode.app'; + const String deviceId = '0000001234'; + + late Xcode xcode; + late Directory xcodeproj; + late Directory xcworkspace; + late XcodeDebugProject project; + + setUp(() { + xcode = setupXcode( + fakeProcessManager: fakeProcessManager, + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); + + xcodeproj = fileSystem.directory('Runner.xcodeproj'); + xcworkspace = fileSystem.directory('Runner.xcworkspace'); + project = XcodeDebugProject( + scheme: 'Runner', + xcodeProject: xcodeproj, + xcodeWorkspace: xcworkspace, + hostAppProjectName: 'Runner', + ); + }); + + testWithoutContext('succeeds in opening and debugging with launch options, expectedConfigurationBuildDir, and verbose logging', () async { + fakeProcessManager.addCommands(<FakeCommand>[ + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'check-workspace-opened', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + '--verbose', + ], + stdout: ''' + {"status":false,"errorMessage":"Xcode is not running","debugResult":null} + ''', + ), + FakeCommand( + command: <String>[ + 'open', + '-a', + pathToXcodeApp, + '-g', + '-j', + '-F', + xcworkspace.path + ], + ), + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'debug', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + '--project-name', + project.hostAppProjectName, + '--expected-configuration-build-dir', + '/build/ios/iphoneos', + '--device-id', + deviceId, + '--scheme', + project.scheme, + '--skip-building', + '--launch-args', + r'["--enable-dart-profiling","--trace-allowlist=\"foo,bar\""]', + '--verbose', + ], + stdout: ''' + {"status":true,"errorMessage":null,"debugResult":{"completed":false,"status":"running","errorMessage":null}} + ''', + ), + ]); + + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + + project = XcodeDebugProject( + scheme: 'Runner', + xcodeProject: xcodeproj, + xcodeWorkspace: xcworkspace, + hostAppProjectName: 'Runner', + expectedConfigurationBuildDir: '/build/ios/iphoneos', + verboseLogging: true, + ); + + final bool status = await xcodeDebug.debugApp( + project: project, + deviceId: deviceId, + launchArguments: <String>[ + '--enable-dart-profiling', + '--trace-allowlist="foo,bar"' + ], + ); + + expect(logger.errorText, isEmpty); + expect(logger.traceText, contains('Error checking if project opened in Xcode')); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(xcodeDebug.startDebugActionProcess, isNull); + expect(status, true); + }); + + testWithoutContext('succeeds in opening and debugging without launch options, expectedConfigurationBuildDir, and verbose logging', () async { + fakeProcessManager.addCommands(<FakeCommand>[ + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'check-workspace-opened', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + ], + stdout: ''' + {"status":false,"errorMessage":"Xcode is not running","debugResult":null} + ''', + ), + FakeCommand( + command: <String>[ + 'open', + '-a', + pathToXcodeApp, + '-g', + '-j', + '-F', + xcworkspace.path + ], + ), + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'debug', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + '--project-name', + project.hostAppProjectName, + '--device-id', + deviceId, + '--scheme', + project.scheme, + '--skip-building', + '--launch-args', + '[]' + ], + stdout: ''' + {"status":true,"errorMessage":null,"debugResult":{"completed":false,"status":"running","errorMessage":null}} + ''', + ), + ]); + + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + + final bool status = await xcodeDebug.debugApp( + project: project, + deviceId: deviceId, + launchArguments: <String>[], + ); + + expect(logger.errorText, isEmpty); + expect(logger.traceText, contains('Error checking if project opened in Xcode')); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(xcodeDebug.startDebugActionProcess, isNull); + expect(status, true); + }); + + testWithoutContext('fails if project fails to open', () async { + fakeProcessManager.addCommands(<FakeCommand>[ + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'check-workspace-opened', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + ], + stdout: ''' + {"status":false,"errorMessage":"Xcode is not running","debugResult":null} + ''', + ), + FakeCommand( + command: <String>[ + 'open', + '-a', + pathToXcodeApp, + '-g', + '-j', + '-F', + xcworkspace.path + ], + exception: ProcessException( + 'open', + <String>[ + '-a', + '/non_existant_path', + '-g', + '-j', + '-F', + xcworkspace.path, + ], + 'The application /non_existant_path cannot be opened for an unexpected reason', + ), + ), + ]); + + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + + final bool status = await xcodeDebug.debugApp( + project: project, + deviceId: deviceId, + launchArguments: <String>[ + '--enable-dart-profiling', + '--trace-allowlist="foo,bar"', + ], + ); + + expect( + logger.errorText, + contains('The application /non_existant_path cannot be opened for an unexpected reason'), + ); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(status, false); + }); + + testWithoutContext('fails if osascript errors', () async { + fakeProcessManager.addCommands(<FakeCommand>[ + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'check-workspace-opened', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + ], + stdout: ''' + {"status":true,"errorMessage":"","debugResult":null} + ''', + ), + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'debug', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + '--project-name', + project.hostAppProjectName, + '--device-id', + deviceId, + '--scheme', + project.scheme, + '--skip-building', + '--launch-args', + r'["--enable-dart-profiling","--trace-allowlist=\"foo,bar\""]' + ], + exitCode: 1, + stderr: "/flutter/packages/flutter_tools/bin/xcode_debug.js: execution error: Error: ReferenceError: Can't find variable: y (-2700)", + ), + ]); + + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + + final bool status = await xcodeDebug.debugApp( + project: project, + deviceId: deviceId, + launchArguments: <String>[ + '--enable-dart-profiling', + '--trace-allowlist="foo,bar"', + ], + ); + + expect(logger.errorText, contains('Error executing osascript')); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(status, false); + }); + + testWithoutContext('fails if osascript output returns false status', () async { + fakeProcessManager.addCommands(<FakeCommand>[ + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'check-workspace-opened', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + ], + stdout: ''' + {"status":true,"errorMessage":null,"debugResult":null} + ''', + ), + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'debug', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + '--project-name', + project.hostAppProjectName, + '--device-id', + deviceId, + '--scheme', + project.scheme, + '--skip-building', + '--launch-args', + r'["--enable-dart-profiling","--trace-allowlist=\"foo,bar\""]' + ], + stdout: ''' + {"status":false,"errorMessage":"Unable to find target device.","debugResult":null} + ''', + ), + ]); + + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + + final bool status = await xcodeDebug.debugApp( + project: project, + deviceId: deviceId, + launchArguments: <String>[ + '--enable-dart-profiling', + '--trace-allowlist="foo,bar"', + ], + ); + + expect( + logger.errorText, + contains('Error starting debug session in Xcode'), + ); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(status, false); + }); + + testWithoutContext('fails if missing debug results', () async { + fakeProcessManager.addCommands(<FakeCommand>[ + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'check-workspace-opened', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + ], + stdout: ''' + {"status":true,"errorMessage":null,"debugResult":null} + ''', + ), + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'debug', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + '--project-name', + project.hostAppProjectName, + '--device-id', + deviceId, + '--scheme', + project.scheme, + '--skip-building', + '--launch-args', + r'["--enable-dart-profiling","--trace-allowlist=\"foo,bar\""]' + ], + stdout: ''' + {"status":true,"errorMessage":null,"debugResult":null} + ''', + ), + ]); + + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + + final bool status = await xcodeDebug.debugApp( + project: project, + deviceId: deviceId, + launchArguments: <String>[ + '--enable-dart-profiling', + '--trace-allowlist="foo,bar"' + ], + ); + + expect( + logger.errorText, + contains('Unable to get debug results from response'), + ); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(status, false); + }); + + testWithoutContext('fails if debug results status is not running', () async { + fakeProcessManager.addCommands(<FakeCommand>[ + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'check-workspace-opened', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + ], + stdout: ''' + {"status":true,"errorMessage":null,"debugResult":null} + ''', + ), + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'debug', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + '--project-name', + project.hostAppProjectName, + '--device-id', + deviceId, + '--scheme', + project.scheme, + '--skip-building', + '--launch-args', + r'["--enable-dart-profiling","--trace-allowlist=\"foo,bar\""]' + ], + stdout: ''' + {"status":true,"errorMessage":null,"debugResult":{"completed":false,"status":"not yet started","errorMessage":null}} + ''', + ), + ]); + + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + + final bool status = await xcodeDebug.debugApp( + project: project, + deviceId: deviceId, + launchArguments: <String>[ + '--enable-dart-profiling', + '--trace-allowlist="foo,bar"', + ], + ); + + expect(logger.errorText, contains('Unexpected debug results')); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(status, false); + }); + }); + + group('parse script response', () { + testWithoutContext('fails if osascript output returns non-json output', () async { + final Xcode xcode = setupXcode( + fakeProcessManager: FakeProcessManager.any(), + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + + final XcodeAutomationScriptResponse? response = xcodeDebug.parseScriptResponse('not json'); + + expect( + logger.errorText, + contains('osascript returned non-JSON response'), + ); + expect(response, isNull); + }); + + testWithoutContext('fails if osascript output returns unexpected json', () async { + final Xcode xcode = setupXcode( + fakeProcessManager: FakeProcessManager.any(), + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + + final XcodeAutomationScriptResponse? response = xcodeDebug.parseScriptResponse('[]'); + + expect( + logger.errorText, + contains('osascript returned unexpected JSON response'), + ); + expect(response, isNull); + }); + + testWithoutContext('fails if osascript output is missing status field', () async { + final Xcode xcode = setupXcode( + fakeProcessManager: FakeProcessManager.any(), + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + + final XcodeAutomationScriptResponse? response = xcodeDebug.parseScriptResponse('{}'); + + expect( + logger.errorText, + contains('osascript returned unexpected JSON response'), + ); + expect(response, isNull); + }); + }); + + group('exit', () { + const String pathToXcodeApp = '/Applications/Xcode.app'; + + late Directory projectDirectory; + late Directory xcodeproj; + late Directory xcworkspace; + + setUp(() { + projectDirectory = fileSystem.directory('FlutterApp'); + xcodeproj = projectDirectory.childDirectory('Runner.xcodeproj'); + xcworkspace = projectDirectory.childDirectory('Runner.xcworkspace'); + }); + + testWithoutContext('exits when waiting for debug session to start', () async { + final Xcode xcode = setupXcode( + fakeProcessManager: fakeProcessManager, + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); + final XcodeDebugProject project = XcodeDebugProject( + scheme: 'Runner', + xcodeProject: xcodeproj, + xcodeWorkspace: xcworkspace, + hostAppProjectName: 'Runner', + ); + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + fakeProcessManager.addCommands(<FakeCommand>[ + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'stop', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + ], + stdout: ''' + {"status":true,"errorMessage":null,"debugResult":null} + ''', + ), + ]); + + xcodeDebug.startDebugActionProcess = FakeProcess(); + xcodeDebug.currentDebuggingProject = project; + + expect(xcodeDebug.startDebugActionProcess, isNotNull); + expect(xcodeDebug.currentDebuggingProject, isNotNull); + + final bool exitStatus = await xcodeDebug.exit(); + + expect((xcodeDebug.startDebugActionProcess! as FakeProcess).killed, isTrue); + expect(xcodeDebug.currentDebuggingProject, isNull); + expect(logger.errorText, isEmpty); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(exitStatus, isTrue); + }); + + testWithoutContext('exits and deletes temporary directory', () async { + final Xcode xcode = setupXcode( + fakeProcessManager: fakeProcessManager, + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); + xcodeproj.createSync(recursive: true); + xcworkspace.createSync(recursive: true); + + final XcodeDebugProject project = XcodeDebugProject( + scheme: 'Runner', + xcodeProject: xcodeproj, + xcodeWorkspace: xcworkspace, + hostAppProjectName: 'Runner', + isTemporaryProject: true, + ); + + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + fakeProcessManager.addCommands(<FakeCommand>[ + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'stop', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + '--close-window' + ], + stdout: ''' + {"status":true,"errorMessage":null,"debugResult":null} + ''', + ), + ]); + + xcodeDebug.startDebugActionProcess = FakeProcess(); + xcodeDebug.currentDebuggingProject = project; + + expect(xcodeDebug.startDebugActionProcess, isNotNull); + expect(xcodeDebug.currentDebuggingProject, isNotNull); + expect(projectDirectory.existsSync(), isTrue); + expect(xcodeproj.existsSync(), isTrue); + expect(xcworkspace.existsSync(), isTrue); + + final bool status = await xcodeDebug.exit(skipDelay: true); + + expect((xcodeDebug.startDebugActionProcess! as FakeProcess).killed, isTrue); + expect(xcodeDebug.currentDebuggingProject, isNull); + expect(projectDirectory.existsSync(), isFalse); + expect(xcodeproj.existsSync(), isFalse); + expect(xcworkspace.existsSync(), isFalse); + expect(logger.errorText, isEmpty); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(status, isTrue); + }); + + testWithoutContext('prints error message when deleting temporary directory that is nonexistant', () async { + final Xcode xcode = setupXcode( + fakeProcessManager: fakeProcessManager, + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); + final XcodeDebugProject project = XcodeDebugProject( + scheme: 'Runner', + xcodeProject: xcodeproj, + xcodeWorkspace: xcworkspace, + hostAppProjectName: 'Runner', + isTemporaryProject: true, + ); + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + + fakeProcessManager.addCommands(<FakeCommand>[ + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'stop', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + '--close-window' + ], + stdout: ''' + {"status":true,"errorMessage":null,"debugResult":null} + ''', + ), + ]); + + xcodeDebug.startDebugActionProcess = FakeProcess(); + xcodeDebug.currentDebuggingProject = project; + + expect(xcodeDebug.startDebugActionProcess, isNotNull); + expect(xcodeDebug.currentDebuggingProject, isNotNull); + expect(projectDirectory.existsSync(), isFalse); + expect(xcodeproj.existsSync(), isFalse); + expect(xcworkspace.existsSync(), isFalse); + + final bool status = await xcodeDebug.exit(skipDelay: true); + + expect((xcodeDebug.startDebugActionProcess! as FakeProcess).killed, isTrue); + expect(xcodeDebug.currentDebuggingProject, isNull); + expect(projectDirectory.existsSync(), isFalse); + expect(xcodeproj.existsSync(), isFalse); + expect(xcworkspace.existsSync(), isFalse); + expect(logger.errorText, contains('Failed to delete temporary Xcode project')); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(status, isTrue); + }); + + testWithoutContext('kill Xcode when force exit', () async { + final Xcode xcode = setupXcode( + fakeProcessManager: FakeProcessManager.any(), + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); + final XcodeDebugProject project = XcodeDebugProject( + scheme: 'Runner', + xcodeProject: xcodeproj, + xcodeWorkspace: xcworkspace, + hostAppProjectName: 'Runner', + ); + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + fakeProcessManager.addCommands(<FakeCommand>[ + const FakeCommand( + command: <String>[ + 'killall', + '-9', + 'Xcode', + ], + ), + ]); + + xcodeDebug.startDebugActionProcess = FakeProcess(); + xcodeDebug.currentDebuggingProject = project; + + expect(xcodeDebug.startDebugActionProcess, isNotNull); + expect(xcodeDebug.currentDebuggingProject, isNotNull); + + final bool exitStatus = await xcodeDebug.exit(force: true); + + expect((xcodeDebug.startDebugActionProcess! as FakeProcess).killed, isTrue); + expect(xcodeDebug.currentDebuggingProject, isNull); + expect(logger.errorText, isEmpty); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(exitStatus, isTrue); + }); + + testWithoutContext('does not crash when deleting temporary directory that is nonexistant when force exiting', () async { + final Xcode xcode = setupXcode( + fakeProcessManager: FakeProcessManager.any(), + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); + final XcodeDebugProject project = XcodeDebugProject( + scheme: 'Runner', + xcodeProject: xcodeproj, + xcodeWorkspace: xcworkspace, + hostAppProjectName: 'Runner', + isTemporaryProject: true, + ); + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager:FakeProcessManager.any(), + xcode: xcode, + fileSystem: fileSystem, + ); + + xcodeDebug.startDebugActionProcess = FakeProcess(); + xcodeDebug.currentDebuggingProject = project; + + expect(xcodeDebug.startDebugActionProcess, isNotNull); + expect(xcodeDebug.currentDebuggingProject, isNotNull); + expect(projectDirectory.existsSync(), isFalse); + expect(xcodeproj.existsSync(), isFalse); + expect(xcworkspace.existsSync(), isFalse); + + final bool status = await xcodeDebug.exit(force: true); + + expect((xcodeDebug.startDebugActionProcess! as FakeProcess).killed, isTrue); + expect(xcodeDebug.currentDebuggingProject, isNull); + expect(projectDirectory.existsSync(), isFalse); + expect(xcodeproj.existsSync(), isFalse); + expect(xcworkspace.existsSync(), isFalse); + expect(logger.errorText, isEmpty); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(status, isTrue); + }); + }); + + group('stop app', () { + const String pathToXcodeApp = '/Applications/Xcode.app'; + + late Xcode xcode; + late Directory xcodeproj; + late Directory xcworkspace; + late XcodeDebugProject project; + + setUp(() { + xcode = setupXcode( + fakeProcessManager: fakeProcessManager, + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); + xcodeproj = fileSystem.directory('Runner.xcodeproj'); + xcworkspace = fileSystem.directory('Runner.xcworkspace'); + project = XcodeDebugProject( + scheme: 'Runner', + xcodeProject: xcodeproj, + xcodeWorkspace: xcworkspace, + hostAppProjectName: 'Runner', + ); + }); + + testWithoutContext('succeeds with all optional flags', () async { + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + fakeProcessManager.addCommands(<FakeCommand>[ + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'stop', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + '--close-window', + '--prompt-to-save' + ], + stdout: ''' + {"status":true,"errorMessage":null,"debugResult":null} + ''', + ), + ]); + + final bool status = await xcodeDebug.stopDebuggingApp( + project: project, + closeXcode: true, + promptToSaveOnClose: true, + ); + + expect(logger.errorText, isEmpty); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(status, isTrue); + }); + + testWithoutContext('fails if osascript output returns false status', () async { + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: fileSystem, + ); + fakeProcessManager.addCommands(<FakeCommand>[ + FakeCommand( + command: <String>[ + 'xcrun', + 'osascript', + '-l', + 'JavaScript', + pathToXcodeAutomationScript, + 'stop', + '--xcode-path', + pathToXcodeApp, + '--project-path', + project.xcodeProject.path, + '--workspace-path', + project.xcodeWorkspace.path, + '--close-window', + '--prompt-to-save' + ], + stdout: ''' + {"status":false,"errorMessage":"Failed to stop app","debugResult":null} + ''', + ), + ]); + + final bool status = await xcodeDebug.stopDebuggingApp( + project: project, + closeXcode: true, + promptToSaveOnClose: true, + ); + + expect(logger.errorText, contains('Error stopping app in Xcode')); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(status, isFalse); + }); + }); + }); + + group('Debug project through Xcode with app bundle', () { + late BufferLogger logger; + late FakeProcessManager fakeProcessManager; + late MemoryFileSystem fileSystem; + + const String flutterRoot = '/path/to/flutter'; + + setUp(() { + logger = BufferLogger.test(); + fakeProcessManager = FakeProcessManager.empty(); + fileSystem = MemoryFileSystem.test(); + }); + + testUsingContext('creates temporary xcode project', () async { + final Xcode xcode = setupXcode( + fakeProcessManager: fakeProcessManager, + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); + + final XcodeDebug xcodeDebug = XcodeDebug( + logger: logger, + processManager: fakeProcessManager, + xcode: xcode, + fileSystem: globals.fs, + ); + + final Directory projectDirectory = globals.fs.systemTempDirectory.createTempSync('flutter_empty_xcode.'); + + try { + final XcodeDebugProject project = await xcodeDebug.createXcodeProjectWithCustomBundle( + '/path/to/bundle', + templateRenderer: globals.templateRenderer, + projectDestination: projectDirectory, + ); + + final File schemeFile = projectDirectory + .childDirectory('Runner.xcodeproj') + .childDirectory('xcshareddata') + .childDirectory('xcschemes') + .childFile('Runner.xcscheme'); + + expect(project.scheme, 'Runner'); + expect(project.xcodeProject.existsSync(), isTrue); + expect(project.xcodeWorkspace.existsSync(), isTrue); + expect(project.isTemporaryProject, isTrue); + expect(projectDirectory.childDirectory('Runner.xcodeproj').existsSync(), isTrue); + expect(projectDirectory.childDirectory('Runner.xcworkspace').existsSync(), isTrue); + expect(schemeFile.existsSync(), isTrue); + expect(schemeFile.readAsStringSync(), contains('FilePath = "/path/to/bundle"')); + + } catch (err) { // ignore: avoid_catches_without_on_clauses + fail(err.toString()); + } finally { + projectDirectory.deleteSync(recursive: true); + } + }); + }); +} + +Xcode setupXcode({ + required FakeProcessManager fakeProcessManager, + required FileSystem fileSystem, + required String flutterRoot, + bool xcodeSelect = true, +}) { + fakeProcessManager.addCommand(const FakeCommand( + command: <String>['/usr/bin/xcode-select', '--print-path'], + stdout: '/Applications/Xcode.app/Contents/Developer', + )); + + fileSystem.file('$flutterRoot/packages/flutter_tools/bin/xcode_debug.js').createSync(recursive: true); + + final XcodeProjectInterpreter xcodeProjectInterpreter = XcodeProjectInterpreter.test( + processManager: FakeProcessManager.any(), + version: Version(14, 0, 0), + ); + + return Xcode.test( + processManager: fakeProcessManager, + xcodeProjectInterpreter: xcodeProjectInterpreter, + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); +} + +class FakeProcess extends Fake implements Process { + bool killed = false; + + @override + bool kill([io.ProcessSignal signal = io.ProcessSignal.sigterm]) { + killed = true; + return true; + } +} diff --git a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart index 90210c3255604..b2f79385050dd 100644 --- a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart @@ -1306,5 +1306,50 @@ flutter: expectedBuildNumber: '1', ); }); + + group('CoreDevice', () { + testUsingContext('sets CONFIGURATION_BUILD_DIR when configurationBuildDir is set', () async { + const BuildInfo buildInfo = BuildInfo.debug; + final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project')); + await updateGeneratedXcodeProperties( + project: project, + buildInfo: buildInfo, + configurationBuildDir: 'path/to/project/build/ios/iphoneos' + ); + + final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig'); + expect(config.existsSync(), isTrue); + + final String contents = config.readAsStringSync(); + expect(contents, contains('CONFIGURATION_BUILD_DIR=path/to/project/build/ios/iphoneos')); + }, overrides: <Type, Generator>{ + Artifacts: () => localIosArtifacts, + // Platform: () => macOS, + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + XcodeProjectInterpreter: () => xcodeProjectInterpreter, + }); + + testUsingContext('does not set CONFIGURATION_BUILD_DIR when configurationBuildDir is not set', () async { + const BuildInfo buildInfo = BuildInfo.debug; + final FlutterProject project = FlutterProject.fromDirectoryTest(fs.directory('path/to/project')); + await updateGeneratedXcodeProperties( + project: project, + buildInfo: buildInfo, + ); + + final File config = fs.file('path/to/project/ios/Flutter/Generated.xcconfig'); + expect(config.existsSync(), isTrue); + + final String contents = config.readAsStringSync(); + expect(contents.contains('CONFIGURATION_BUILD_DIR'), isFalse); + }, overrides: <Type, Generator>{ + Artifacts: () => localIosArtifacts, + Platform: () => macOS, + FileSystem: () => fs, + ProcessManager: () => FakeProcessManager.any(), + XcodeProjectInterpreter: () => xcodeProjectInterpreter, + }); + }); }); } diff --git a/packages/flutter_tools/test/general.shard/ios/xcresult_test.dart b/packages/flutter_tools/test/general.shard/ios/xcresult_test.dart index 72b78681c838f..19f6944b0895a 100644 --- a/packages/flutter_tools/test/general.shard/ios/xcresult_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/xcresult_test.dart @@ -204,6 +204,19 @@ void main() { expect(result.parsingErrorMessage, isNull); }); + testWithoutContext( + 'correctly parse sample result json with action issues.', () async { + final XCResultGenerator generator = setupGenerator(resultJson: kSampleResultJsonWithActionIssues); + final XCResultIssueDiscarder discarder = XCResultIssueDiscarder(typeMatcher: XCResultIssueType.warning); + final XCResult result = await generator.generate(issueDiscarders: <XCResultIssueDiscarder>[discarder]); + expect(result.issues.length, 1); + expect(result.issues.first.type, XCResultIssueType.error); + expect(result.issues.first.subType, 'Uncategorized'); + expect(result.issues.first.message, contains('Unable to find a destination matching the provided destination specifier')); + expect(result.parseSuccess, isTrue); + expect(result.parsingErrorMessage, isNull); + }); + testWithoutContext( 'error: `xcresulttool get` process fail should return an `XCResult` with stderr as `parsingErrorMessage`.', () async { diff --git a/packages/flutter_tools/test/general.shard/ios/xcresult_test_data.dart b/packages/flutter_tools/test/general.shard/ios/xcresult_test_data.dart index 645afd1e876ee..5845262c21851 100644 --- a/packages/flutter_tools/test/general.shard/ios/xcresult_test_data.dart +++ b/packages/flutter_tools/test/general.shard/ios/xcresult_test_data.dart @@ -378,3 +378,252 @@ const String kSampleResultJsonWithProvisionIssue = r''' } } '''; + + +/// An example xcresult bundle json that contains action issues. +const String kSampleResultJsonWithActionIssues = r''' +{ + "_type" : { + "_name" : "ActionsInvocationRecord" + }, + "actions" : { + "_type" : { + "_name" : "Array" + }, + "_values" : [ + { + "_type" : { + "_name" : "ActionRecord" + }, + "actionResult" : { + "_type" : { + "_name" : "ActionResult" + }, + "coverage" : { + "_type" : { + "_name" : "CodeCoverageInfo" + } + }, + "issues" : { + "_type" : { + "_name" : "ResultIssueSummaries" + }, + "testFailureSummaries" : { + "_type" : { + "_name" : "Array" + }, + "_values" : [ + { + "_type" : { + "_name" : "TestFailureIssueSummary", + "_supertype" : { + "_name" : "IssueSummary" + } + }, + "issueType" : { + "_type" : { + "_name" : "String" + }, + "_value" : "Uncategorized" + }, + "message" : { + "_type" : { + "_name" : "String" + }, + "_value" : "Unable to find a destination matching the provided destination specifier:\n\t\t{ id:1234D567-890C-1DA2-34E5-F6789A0123C4 }\n\n\tIneligible destinations for the \"Runner\" scheme:\n\t\t{ platform:iOS, id:dvtdevice-DVTiPhonePlaceholder-iphoneos:placeholder, name:Any iOS Device, error:iOS 17.0 is not installed. To use with Xcode, first download and install the platform }" + } + } + ] + } + }, + "logRef" : { + "_type" : { + "_name" : "Reference" + }, + "id" : { + "_type" : { + "_name" : "String" + }, + "_value" : "0~5X-qvql8_ppq0bj9taBMeZd4L2lXQagy1twsFRWwc06r42obpBZfP87uKnGO98mp5CUz1Ppr1knHiTMH9tOuwQ==" + }, + "targetType" : { + "_type" : { + "_name" : "TypeDefinition" + }, + "name" : { + "_type" : { + "_name" : "String" + }, + "_value" : "ActivityLogSection" + } + } + }, + "metrics" : { + "_type" : { + "_name" : "ResultMetrics" + } + }, + "resultName" : { + "_type" : { + "_name" : "String" + }, + "_value" : "All Tests" + }, + "status" : { + "_type" : { + "_name" : "String" + }, + "_value" : "failedToStart" + }, + "testsRef" : { + "_type" : { + "_name" : "Reference" + }, + "id" : { + "_type" : { + "_name" : "String" + }, + "_value" : "0~Dmuz8-g6YRb8HPVbTUXJD21oy3r5jxIGi-njd2Lc43yR5JlJf7D78HtNn2BsrF5iw1uYMnsuJ9xFDV7ZAmwhGg==" + }, + "targetType" : { + "_type" : { + "_name" : "TypeDefinition" + }, + "name" : { + "_type" : { + "_name" : "String" + }, + "_value" : "ActionTestPlanRunSummaries" + } + } + } + }, + "buildResult" : { + "_type" : { + "_name" : "ActionResult" + }, + "coverage" : { + "_type" : { + "_name" : "CodeCoverageInfo" + } + }, + "issues" : { + "_type" : { + "_name" : "ResultIssueSummaries" + } + }, + "metrics" : { + "_type" : { + "_name" : "ResultMetrics" + } + }, + "resultName" : { + "_type" : { + "_name" : "String" + }, + "_value" : "Build Succeeded" + }, + "status" : { + "_type" : { + "_name" : "String" + }, + "_value" : "succeeded" + } + }, + "endedTime" : { + "_type" : { + "_name" : "Date" + }, + "_value" : "2023-07-10T12:52:22.592-0500" + }, + "runDestination" : { + "_type" : { + "_name" : "ActionRunDestinationRecord" + }, + "localComputerRecord" : { + "_type" : { + "_name" : "ActionDeviceRecord" + }, + "platformRecord" : { + "_type" : { + "_name" : "ActionPlatformRecord" + } + } + }, + "targetDeviceRecord" : { + "_type" : { + "_name" : "ActionDeviceRecord" + }, + "platformRecord" : { + "_type" : { + "_name" : "ActionPlatformRecord" + } + } + }, + "targetSDKRecord" : { + "_type" : { + "_name" : "ActionSDKRecord" + } + } + }, + "schemeCommandName" : { + "_type" : { + "_name" : "String" + }, + "_value" : "Test" + }, + "schemeTaskName" : { + "_type" : { + "_name" : "String" + }, + "_value" : "BuildAndAction" + }, + "startedTime" : { + "_type" : { + "_name" : "Date" + }, + "_value" : "2023-07-10T12:52:22.592-0500" + }, + "title" : { + "_type" : { + "_name" : "String" + }, + "_value" : "RunnerTests.xctest" + } + } + ] + }, + "issues" : { + "_type" : { + "_name" : "ResultIssueSummaries" + } + }, + "metadataRef" : { + "_type" : { + "_name" : "Reference" + }, + "id" : { + "_type" : { + "_name" : "String" + }, + "_value" : "0~pY0GqmiVE6Q3qlWdLJDp_PnrsUKsJ7KKM1zKGnvEZOWGdBeGNArjjU62kgF2UBFdQLdRmf5SGpImQfJB6e7vDQ==" + }, + "targetType" : { + "_type" : { + "_name" : "TypeDefinition" + }, + "name" : { + "_type" : { + "_name" : "String" + }, + "_value" : "ActionsInvocationMetadata" + } + } + }, + "metrics" : { + "_type" : { + "_name" : "ResultMetrics" + } + } +} +'''; diff --git a/packages/flutter_tools/test/general.shard/macos/application_package_test.dart b/packages/flutter_tools/test/general.shard/macos/application_package_test.dart index 40652afe0f70a..1edfa137af27e 100644 --- a/packages/flutter_tools/test/general.shard/macos/application_package_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/application_package_test.dart @@ -167,7 +167,7 @@ group('PrebuiltMacOSApp', () { const BuildInfo flavoredApp = BuildInfo(BuildMode.release, 'flavor', treeShakeIcons: false); applicationBundle = macosApp.bundleDirectory(flavoredApp); - expect(applicationBundle, 'Release Flavor'); + expect(applicationBundle, 'Release-flavor'); }, overrides: overrides); }); diff --git a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart index b7fd7ff9c1162..6df6b82b6d751 100644 --- a/packages/flutter_tools/test/general.shard/macos/xcode_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/xcode_test.dart @@ -4,16 +4,20 @@ import 'dart:async'; +import 'package:file/memory.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/io.dart' show ProcessException; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/device.dart'; +import 'package:flutter_tools/src/ios/core_devices.dart'; import 'package:flutter_tools/src/ios/devices.dart'; import 'package:flutter_tools/src/ios/iproxy.dart'; +import 'package:flutter_tools/src/ios/xcode_debug.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/macos/xcdevice.dart'; import 'package:flutter_tools/src/macos/xcode.dart'; @@ -75,7 +79,7 @@ void main() { expect(fakeProcessManager, hasNoRemainingExpectations); }); - testWithoutContext('isSimctlInstalled is true when simctl list fails', () { + testWithoutContext('isSimctlInstalled is false when simctl list fails', () { fakeProcessManager.addCommand( const FakeCommand( command: <String>[ @@ -97,14 +101,167 @@ void main() { expect(fakeProcessManager, hasNoRemainingExpectations); }); + group('isDevicectlInstalled', () { + testWithoutContext('is true when Xcode is 15+ and devicectl succeeds', () { + fakeProcessManager.addCommand( + const FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + '--version', + ], + ), + ); + xcodeProjectInterpreter.version = Version(15, 0, 0); + final Xcode xcode = Xcode.test( + processManager: fakeProcessManager, + xcodeProjectInterpreter: xcodeProjectInterpreter, + ); + + expect(xcode.isDevicectlInstalled, isTrue); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); + + testWithoutContext('is false when devicectl fails', () { + fakeProcessManager.addCommand( + const FakeCommand( + command: <String>[ + 'xcrun', + 'devicectl', + '--version', + ], + exitCode: 1, + ), + ); + xcodeProjectInterpreter.version = Version(15, 0, 0); + final Xcode xcode = Xcode.test( + processManager: fakeProcessManager, + xcodeProjectInterpreter: xcodeProjectInterpreter, + ); + + expect(xcode.isDevicectlInstalled, isFalse); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); + + testWithoutContext('is false when Xcode is less than 15', () { + xcodeProjectInterpreter.version = Version(14, 0, 0); + final Xcode xcode = Xcode.test( + processManager: fakeProcessManager, + xcodeProjectInterpreter: xcodeProjectInterpreter, + ); + + expect(xcode.isDevicectlInstalled, isFalse); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); + }); + + group('pathToXcodeApp', () { + late UserMessages userMessages; + + setUp(() { + userMessages = UserMessages(); + }); + + testWithoutContext('parses correctly', () { + final Xcode xcode = Xcode.test( + processManager: fakeProcessManager, + xcodeProjectInterpreter: xcodeProjectInterpreter, + ); + + fakeProcessManager.addCommand(const FakeCommand( + command: <String>['/usr/bin/xcode-select', '--print-path'], + stdout: '/Applications/Xcode.app/Contents/Developer', + )); + + expect(xcode.xcodeAppPath, '/Applications/Xcode.app'); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); + + testWithoutContext('throws error if not found', () { + final Xcode xcode = Xcode.test( + processManager: FakeProcessManager.any(), + xcodeProjectInterpreter: xcodeProjectInterpreter, + ); + + expect( + () => xcode.xcodeAppPath, + throwsToolExit(message: userMessages.xcodeMissing), + ); + }); + + testWithoutContext('throws error with unexpected outcome', () { + final Xcode xcode = Xcode.test( + processManager: fakeProcessManager, + xcodeProjectInterpreter: xcodeProjectInterpreter, + ); + + fakeProcessManager.addCommand(const FakeCommand( + command: <String>[ + '/usr/bin/xcode-select', + '--print-path', + ], + stdout: '/Library/Developer/CommandLineTools', + )); + + expect( + () => xcode.xcodeAppPath, + throwsToolExit(message: userMessages.xcodeMissing), + ); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); + }); + + group('pathToXcodeAutomationScript', () { + const String flutterRoot = '/path/to/flutter'; + + late MemoryFileSystem fileSystem; + + setUp(() { + fileSystem = MemoryFileSystem.test(); + }); + + testWithoutContext('returns path when file is found', () { + final Xcode xcode = Xcode.test( + processManager: fakeProcessManager, + xcodeProjectInterpreter: xcodeProjectInterpreter, + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); + + fileSystem.file('$flutterRoot/packages/flutter_tools/bin/xcode_debug.js').createSync(recursive: true); + + expect( + xcode.xcodeAutomationScriptPath, + '$flutterRoot/packages/flutter_tools/bin/xcode_debug.js', + ); + }); + + testWithoutContext('throws error when not found', () { + final Xcode xcode = Xcode.test( + processManager: fakeProcessManager, + xcodeProjectInterpreter: xcodeProjectInterpreter, + fileSystem: fileSystem, + flutterRoot: flutterRoot, + ); + + expect(() => + xcode.xcodeAutomationScriptPath, + throwsToolExit() + ); + }); + }); + group('macOS', () { late Xcode xcode; + late BufferLogger logger; setUp(() { xcodeProjectInterpreter = FakeXcodeProjectInterpreter(); + logger = BufferLogger.test(); xcode = Xcode.test( processManager: fakeProcessManager, xcodeProjectInterpreter: xcodeProjectInterpreter, + logger: logger, ); }); @@ -277,12 +434,66 @@ void main() { expect(fakeProcessManager, hasNoRemainingExpectations); }); }); + + group('SDK Platform Version', () { + testWithoutContext('--show-sdk-platform-version iphonesimulator', () async { + fakeProcessManager.addCommand(const FakeCommand( + command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'], + stdout: '16.4', + )); + + expect(await xcode.sdkPlatformVersion(EnvironmentType.simulator), Version(16, 4, null)); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); + + testWithoutContext('--show-sdk-platform-version iphonesimulator with leading and trailing new line', () async { + fakeProcessManager.addCommand(const FakeCommand( + command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'], + stdout: '\n16.4\n', + )); + + expect(await xcode.sdkPlatformVersion(EnvironmentType.simulator), Version(16, 4, null)); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); + + testWithoutContext('--show-sdk-platform-version returns version followed by text', () async { + fakeProcessManager.addCommand(const FakeCommand( + command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'], + stdout: '13.2 (a) 12344', + )); + + expect(await xcode.sdkPlatformVersion(EnvironmentType.simulator), Version(13, 2, null, text: '13.2 (a) 12344')); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); + + testWithoutContext('--show-sdk-platform-version returns something unexpected', () async { + fakeProcessManager.addCommand(const FakeCommand( + command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'], + stdout: 'bogus', + )); + + expect(await xcode.sdkPlatformVersion(EnvironmentType.simulator), null); + expect(fakeProcessManager, hasNoRemainingExpectations); + }); + + testWithoutContext('--show-sdk-platform-version fails', () async { + fakeProcessManager.addCommand(const FakeCommand( + command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'], + exitCode: 1, + stderr: 'xcrun: error:', + )); + expect(await xcode.sdkPlatformVersion(EnvironmentType.simulator), null); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.errorText, contains('Could not find SDK Platform Version')); + }); + }); }); }); group('xcdevice not installed', () { late XCDevice xcdevice; late Xcode xcode; + late MemoryFileSystem fileSystem; setUp(() { xcode = Xcode.test( @@ -292,6 +503,7 @@ void main() { version: null, // Not installed. ), ); + fileSystem = MemoryFileSystem.test(); xcdevice = XCDevice( processManager: fakeProcessManager, logger: logger, @@ -300,6 +512,9 @@ void main() { artifacts: Artifacts.test(), cache: Cache.test(processManager: FakeProcessManager.any()), iproxy: IProxy.test(logger: logger, processManager: fakeProcessManager), + fileSystem: fileSystem, + coreDeviceControl: FakeIOSCoreDeviceControl(), + xcodeDebug: FakeXcodeDebug(), ); }); @@ -317,9 +532,13 @@ void main() { group('xcdevice', () { late XCDevice xcdevice; late Xcode xcode; + late MemoryFileSystem fileSystem; + late FakeIOSCoreDeviceControl coreDeviceControl; setUp(() { xcode = Xcode.test(processManager: FakeProcessManager.any()); + fileSystem = MemoryFileSystem.test(); + coreDeviceControl = FakeIOSCoreDeviceControl(); xcdevice = XCDevice( processManager: fakeProcessManager, logger: logger, @@ -328,6 +547,9 @@ void main() { artifacts: Artifacts.test(), cache: Cache.test(processManager: FakeProcessManager.any()), iproxy: IProxy.test(logger: logger, processManager: fakeProcessManager), + fileSystem: fileSystem, + coreDeviceControl: coreDeviceControl, + xcodeDebug: FakeXcodeDebug(), ); }); @@ -791,7 +1013,7 @@ void main() { "available" : true, "platform" : "com.apple.platform.iphoneos", "modelCode" : "iPhone8,1", - "identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418", + "identifier" : "43ad2fda7991b34fe1acbda82f9e2fd3d6ddc9f7", "architecture" : "BOGUS", "modelName" : "Future iPad", "name" : "iPad" @@ -865,6 +1087,190 @@ void main() { Platform: () => macPlatform, }); + testUsingContext('use connected entry when filtering out duplicates', () async { + const String devicesOutput = ''' +[ + { + "simulator" : false, + "operatingSystemVersion" : "13.3 (17C54)", + "interface" : "usb", + "available" : false, + "platform" : "com.apple.platform.iphoneos", + "modelCode" : "iPhone8,1", + "identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2", + "architecture" : "arm64", + "modelName" : "iPhone 6s", + "name" : "iPhone" + }, + { + "simulator" : false, + "operatingSystemVersion" : "13.3 (17C54)", + "interface" : "usb", + "available" : false, + "platform" : "com.apple.platform.iphoneos", + "modelCode" : "iPhone8,1", + "identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2", + "architecture" : "arm64", + "modelName" : "iPhone 6s", + "name" : "iPhone", + "error" : { + "code" : -13, + "failureReason" : "", + "description" : "iPhone iPad is not connected", + "recoverySuggestion" : "Xcode will continue when iPhone is connected and unlocked.", + "domain" : "com.apple.platform.iphoneos" + } + } +] +'''; + + fakeProcessManager.addCommand(const FakeCommand( + command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'], + stdout: devicesOutput, + )); + final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices(); + expect(devices, hasLength(1)); + + expect(devices[0].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2'); + expect(devices[0].name, 'iPhone'); + expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54'); + expect(devices[0].cpuArchitecture, DarwinArch.arm64); + expect(devices[0].connectionInterface, DeviceConnectionInterface.attached); + expect(devices[0].isConnected, true); + + expect(fakeProcessManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + Platform: () => macPlatform, + Artifacts: () => Artifacts.test(), + }); + + testUsingContext('use entry with sdk when filtering out duplicates', () async { + const String devicesOutput = ''' +[ + { + "simulator" : false, + "interface" : "usb", + "available" : false, + "platform" : "com.apple.platform.iphoneos", + "modelCode" : "iPhone8,1", + "identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2", + "architecture" : "arm64", + "modelName" : "iPhone 6s", + "name" : "iPhone_1", + "error" : { + "code" : -13, + "failureReason" : "", + "description" : "iPhone iPad is not connected", + "recoverySuggestion" : "Xcode will continue when iPhone is connected and unlocked.", + "domain" : "com.apple.platform.iphoneos" + } + }, + { + "simulator" : false, + "operatingSystemVersion" : "13.3 (17C54)", + "interface" : "usb", + "available" : false, + "platform" : "com.apple.platform.iphoneos", + "modelCode" : "iPhone8,1", + "identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2", + "architecture" : "arm64", + "modelName" : "iPhone 6s", + "name" : "iPhone_2", + "error" : { + "code" : -13, + "failureReason" : "", + "description" : "iPhone iPad is not connected", + "recoverySuggestion" : "Xcode will continue when iPhone is connected and unlocked.", + "domain" : "com.apple.platform.iphoneos" + } + } +] +'''; + + fakeProcessManager.addCommand(const FakeCommand( + command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'], + stdout: devicesOutput, + )); + final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices(); + expect(devices, hasLength(1)); + + expect(devices[0].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2'); + expect(devices[0].name, 'iPhone_2'); + expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54'); + expect(devices[0].cpuArchitecture, DarwinArch.arm64); + expect(devices[0].connectionInterface, DeviceConnectionInterface.attached); + expect(devices[0].isConnected, false); + + expect(fakeProcessManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + Platform: () => macPlatform, + Artifacts: () => Artifacts.test(), + }); + + testUsingContext('use entry with higher sdk when filtering out duplicates', () async { + const String devicesOutput = ''' +[ + { + "simulator" : false, + "operatingSystemVersion" : "14.3 (17C54)", + "interface" : "usb", + "available" : false, + "platform" : "com.apple.platform.iphoneos", + "modelCode" : "iPhone8,1", + "identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2", + "architecture" : "arm64", + "modelName" : "iPhone 6s", + "name" : "iPhone_1", + "error" : { + "code" : -13, + "failureReason" : "", + "description" : "iPhone iPad is not connected", + "recoverySuggestion" : "Xcode will continue when iPhone is connected and unlocked.", + "domain" : "com.apple.platform.iphoneos" + } + }, + { + "simulator" : false, + "operatingSystemVersion" : "13.3 (17C54)", + "interface" : "usb", + "available" : false, + "platform" : "com.apple.platform.iphoneos", + "modelCode" : "iPhone8,1", + "identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2", + "architecture" : "arm64", + "modelName" : "iPhone 6s", + "name" : "iPhone_2", + "error" : { + "code" : -13, + "failureReason" : "", + "description" : "iPhone iPad is not connected", + "recoverySuggestion" : "Xcode will continue when iPhone is connected and unlocked.", + "domain" : "com.apple.platform.iphoneos" + } + } +] +'''; + + fakeProcessManager.addCommand(const FakeCommand( + command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'], + stdout: devicesOutput, + )); + final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices(); + expect(devices, hasLength(1)); + + expect(devices[0].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2'); + expect(devices[0].name, 'iPhone_1'); + expect(await devices[0].sdkNameAndVersion, 'iOS 14.3 17C54'); + expect(devices[0].cpuArchitecture, DarwinArch.arm64); + expect(devices[0].connectionInterface, DeviceConnectionInterface.attached); + expect(devices[0].isConnected, false); + + expect(fakeProcessManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + Platform: () => macPlatform, + Artifacts: () => Artifacts.test(), + }); + testUsingContext('handles bad output',() async { fakeProcessManager.addCommand(const FakeCommand( command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'], @@ -877,6 +1283,176 @@ void main() { }, overrides: <Type, Generator>{ Platform: () => macPlatform, }); + + group('with CoreDevices', () { + testUsingContext('returns devices with corresponding CoreDevices', () async { + const String devicesOutput = ''' +[ + { + "simulator" : true, + "operatingSystemVersion" : "13.3 (17K446)", + "available" : true, + "platform" : "com.apple.platform.appletvsimulator", + "modelCode" : "AppleTV5,3", + "identifier" : "CBB5E1ED-2172-446E-B4E7-F2B5823DBBA6", + "architecture" : "x86_64", + "modelName" : "Apple TV", + "name" : "Apple TV" + }, + { + "simulator" : false, + "operatingSystemVersion" : "13.3 (17C54)", + "interface" : "usb", + "available" : true, + "platform" : "com.apple.platform.iphoneos", + "modelCode" : "iPhone8,1", + "identifier" : "00008027-00192736010F802E", + "architecture" : "arm64", + "modelName" : "iPhone 6s", + "name" : "An iPhone (Space Gray)" + }, + { + "simulator" : false, + "operatingSystemVersion" : "10.1 (14C54)", + "interface" : "usb", + "available" : true, + "platform" : "com.apple.platform.iphoneos", + "modelCode" : "iPad11,4", + "identifier" : "98206e7a4afd4aedaff06e687594e089dede3c44", + "architecture" : "armv7", + "modelName" : "iPad Air 3rd Gen", + "name" : "iPad 1" + }, + { + "simulator" : false, + "operatingSystemVersion" : "10.1 (14C54)", + "interface" : "network", + "available" : true, + "platform" : "com.apple.platform.iphoneos", + "modelCode" : "iPad11,4", + "identifier" : "234234234234234234345445687594e089dede3c44", + "architecture" : "arm64", + "modelName" : "iPad Air 3rd Gen", + "name" : "A networked iPad" + }, + { + "simulator" : false, + "operatingSystemVersion" : "10.1 (14C54)", + "interface" : "usb", + "available" : true, + "platform" : "com.apple.platform.iphoneos", + "modelCode" : "iPad11,4", + "identifier" : "f577a7903cc54959be2e34bc4f7f80b7009efcf4", + "architecture" : "BOGUS", + "modelName" : "iPad Air 3rd Gen", + "name" : "iPad 2" + }, + { + "simulator" : true, + "operatingSystemVersion" : "6.1.1 (17S445)", + "available" : true, + "platform" : "com.apple.platform.watchsimulator", + "modelCode" : "Watch5,4", + "identifier" : "2D74FB11-88A0-44D0-B81E-C0C142B1C94A", + "architecture" : "i386", + "modelName" : "Apple Watch Series 5 - 44mm", + "name" : "Apple Watch Series 5 - 44mm" + }, + { + "simulator" : false, + "operatingSystemVersion" : "13.3 (17C54)", + "interface" : "usb", + "available" : false, + "platform" : "com.apple.platform.iphoneos", + "modelCode" : "iPhone8,1", + "identifier" : "c4ca6f7a53027d1b7e4972e28478e7a28e2faee2", + "architecture" : "arm64", + "modelName" : "iPhone 6s", + "name" : "iPhone", + "error" : { + "code" : -9, + "failureReason" : "", + "description" : "iPhone is not paired with your computer.", + "domain" : "com.apple.platform.iphoneos" + } + } +] +'''; + coreDeviceControl.devices.addAll(<FakeIOSCoreDevice>[ + FakeIOSCoreDevice( + udid: '00008027-00192736010F802E', + connectionInterface: DeviceConnectionInterface.wireless, + developerModeStatus: 'enabled', + ), + FakeIOSCoreDevice( + connectionInterface: DeviceConnectionInterface.wireless, + developerModeStatus: 'enabled', + ), + FakeIOSCoreDevice( + udid: '234234234234234234345445687594e089dede3c44', + connectionInterface: DeviceConnectionInterface.attached, + ), + FakeIOSCoreDevice( + udid: 'f577a7903cc54959be2e34bc4f7f80b7009efcf4', + connectionInterface: DeviceConnectionInterface.attached, + developerModeStatus: 'disabled', + ), + ]); + + fakeProcessManager.addCommand(const FakeCommand( + command: <String>['xcrun', 'xcdevice', 'list', '--timeout', '2'], + stdout: devicesOutput, + )); + + final List<IOSDevice> devices = await xcdevice.getAvailableIOSDevices(); + expect(devices, hasLength(5)); + expect(devices[0].id, '00008027-00192736010F802E'); + expect(devices[0].name, 'An iPhone (Space Gray)'); + expect(await devices[0].sdkNameAndVersion, 'iOS 13.3 17C54'); + expect(devices[0].cpuArchitecture, DarwinArch.arm64); + expect(devices[0].connectionInterface, DeviceConnectionInterface.wireless); + expect(devices[0].isConnected, true); + expect(devices[0].devModeEnabled, true); + + expect(devices[1].id, '98206e7a4afd4aedaff06e687594e089dede3c44'); + expect(devices[1].name, 'iPad 1'); + expect(await devices[1].sdkNameAndVersion, 'iOS 10.1 14C54'); + expect(devices[1].cpuArchitecture, DarwinArch.armv7); + expect(devices[1].connectionInterface, DeviceConnectionInterface.attached); + expect(devices[1].isConnected, true); + expect(devices[1].devModeEnabled, true); + + expect(devices[2].id, '234234234234234234345445687594e089dede3c44'); + expect(devices[2].name, 'A networked iPad'); + expect(await devices[2].sdkNameAndVersion, 'iOS 10.1 14C54'); + expect(devices[2].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture. + expect(devices[2].connectionInterface, DeviceConnectionInterface.attached); + expect(devices[2].isConnected, true); + expect(devices[2].devModeEnabled, false); + + expect(devices[3].id, 'f577a7903cc54959be2e34bc4f7f80b7009efcf4'); + expect(devices[3].name, 'iPad 2'); + expect(await devices[3].sdkNameAndVersion, 'iOS 10.1 14C54'); + expect(devices[3].cpuArchitecture, DarwinArch.arm64); // Defaults to arm64 for unknown architecture. + expect(devices[3].connectionInterface, DeviceConnectionInterface.attached); + expect(devices[3].isConnected, true); + expect(devices[3].devModeEnabled, false); + + expect(devices[4].id, 'c4ca6f7a53027d1b7e4972e28478e7a28e2faee2'); + expect(devices[4].name, 'iPhone'); + expect(await devices[4].sdkNameAndVersion, 'iOS 13.3 17C54'); + expect(devices[4].cpuArchitecture, DarwinArch.arm64); + expect(devices[4].connectionInterface, DeviceConnectionInterface.attached); + expect(devices[4].isConnected, false); + expect(devices[4].devModeEnabled, true); + + expect(fakeProcessManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + Platform: () => macPlatform, + Artifacts: () => Artifacts.test(), + }); + + }); }); group('diagnostics', () { @@ -1072,3 +1648,41 @@ class FakeXcodeProjectInterpreter extends Fake implements XcodeProjectInterprete @override List<String> xcrunCommand() => <String>['xcrun']; } + +class FakeXcodeDebug extends Fake implements XcodeDebug {} + +class FakeIOSCoreDeviceControl extends Fake implements IOSCoreDeviceControl { + + List<FakeIOSCoreDevice> devices = <FakeIOSCoreDevice>[]; + + @override + Future<List<IOSCoreDevice>> getCoreDevices({Duration timeout = Duration.zero}) async { + return devices; + } +} + +class FakeIOSCoreDevice extends Fake implements IOSCoreDevice { + FakeIOSCoreDevice({ + this.udid, + this.connectionInterface, + this.developerModeStatus, + }); + + final String? developerModeStatus; + + @override + final String? udid; + + @override + final DeviceConnectionInterface? connectionInterface; + + @override + IOSCoreDeviceProperties? get deviceProperties => FakeIOSCoreDeviceProperties(developerModeStatus: developerModeStatus); +} + +class FakeIOSCoreDeviceProperties extends Fake implements IOSCoreDeviceProperties { + FakeIOSCoreDeviceProperties({required this.developerModeStatus}); + + @override + final String? developerModeStatus; +} diff --git a/packages/flutter_tools/test/general.shard/macos/xcode_validator_test.dart b/packages/flutter_tools/test/general.shard/macos/xcode_validator_test.dart index bb6668e7ace64..70a454a57d3ab 100644 --- a/packages/flutter_tools/test/general.shard/macos/xcode_validator_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/xcode_validator_test.dart @@ -5,9 +5,11 @@ import 'package:flutter_tools/src/base/user_messages.dart'; import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/doctor_validator.dart'; +import 'package:flutter_tools/src/ios/simulators.dart'; import 'package:flutter_tools/src/ios/xcodeproj.dart'; import 'package:flutter_tools/src/macos/xcode.dart'; import 'package:flutter_tools/src/macos/xcode_validator.dart'; +import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/fake_process_manager.dart'; @@ -20,7 +22,11 @@ void main() { processManager: processManager, xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager, version: null), ); - final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages()); + final XcodeValidator validator = XcodeValidator( + xcode: xcode, + userMessages: UserMessages(), + iosSimulatorUtils: FakeIOSSimulatorUtils(), + ); final ValidationResult result = await validator.validate(); expect(result.type, ValidationType.missing); expect(result.statusInfo, isNull); @@ -39,7 +45,11 @@ void main() { processManager: processManager, xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager, version: null), ); - final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages()); + final XcodeValidator validator = XcodeValidator( + xcode: xcode, + userMessages: UserMessages(), + iosSimulatorUtils: FakeIOSSimulatorUtils(), + ); final ValidationResult result = await validator.validate(); expect(result.type, ValidationType.missing); expect(result.messages.last.type, ValidationMessageType.error); @@ -52,7 +62,11 @@ void main() { processManager: processManager, xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager, version: Version(7, 0, 1)), ); - final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages()); + final XcodeValidator validator = XcodeValidator( + xcode: xcode, + userMessages: UserMessages(), + iosSimulatorUtils: FakeIOSSimulatorUtils(), + ); final ValidationResult result = await validator.validate(); expect(result.type, ValidationType.partial); expect(result.messages.last.type, ValidationMessageType.error); @@ -65,7 +79,11 @@ void main() { processManager: processManager, xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager, version: Version(12, 4, null)), ); - final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages()); + final XcodeValidator validator = XcodeValidator( + xcode: xcode, + userMessages: UserMessages(), + iosSimulatorUtils: FakeIOSSimulatorUtils(), + ); final ValidationResult result = await validator.validate(); expect(result.type, ValidationType.partial); expect(result.messages.last.type, ValidationMessageType.hint); @@ -105,11 +123,16 @@ void main() { processManager: processManager, xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager), ); - final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages()); + final XcodeValidator validator = XcodeValidator( + xcode: xcode, + userMessages: UserMessages(), + iosSimulatorUtils: FakeIOSSimulatorUtils(), + ); final ValidationResult result = await validator.validate(); expect(result.type, ValidationType.partial); expect(result.messages.last.type, ValidationMessageType.error); expect(result.messages.last.message, contains('code end user license agreement not signed')); + expect(processManager, hasNoRemainingExpectations); }); testWithoutContext('Emits partial status when simctl is not installed', () async { @@ -143,11 +166,156 @@ void main() { processManager: processManager, xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager), ); - final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages()); + final XcodeValidator validator = XcodeValidator( + xcode: xcode, + userMessages: UserMessages(), + iosSimulatorUtils: FakeIOSSimulatorUtils(), + ); final ValidationResult result = await validator.validate(); expect(result.type, ValidationType.partial); expect(result.messages.last.type, ValidationMessageType.error); expect(result.messages.last.message, contains('Xcode requires additional components')); + expect(processManager, hasNoRemainingExpectations); + }); + + testWithoutContext('Emits partial status when unable to find simulator SDK', () async { + final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ + const FakeCommand( + command: <String>['/usr/bin/xcode-select', '--print-path'], + stdout: '/Library/Developer/CommandLineTools', + ), + const FakeCommand( + command: <String>[ + 'which', + 'sysctl', + ], + ), + const FakeCommand( + command: <String>[ + 'sysctl', + 'hw.optional.arm64', + ], + exitCode: 1, + ), + const FakeCommand( + command: <String>['xcrun', 'clang'], + ), + const FakeCommand( + command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'], + ), + const FakeCommand( + command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'], + ), + ]); + final Xcode xcode = Xcode.test( + processManager: processManager, + xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager), + ); + final XcodeValidator validator = XcodeValidator( + xcode: xcode, + userMessages: UserMessages(), + iosSimulatorUtils: FakeIOSSimulatorUtils(), + ); + final ValidationResult result = await validator.validate(); + expect(result.type, ValidationType.partial); + expect(result.messages.last.type, ValidationMessageType.error); + expect(result.messages.last.message, contains('Unable to find the iPhone Simulator SDK')); + expect(processManager, hasNoRemainingExpectations); + }); + + testWithoutContext('Emits partial status when unable to get simulator runtimes', () async { + final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ + const FakeCommand( + command: <String>['/usr/bin/xcode-select', '--print-path'], + stdout: '/Library/Developer/CommandLineTools', + ), + const FakeCommand( + command: <String>[ + 'which', + 'sysctl', + ], + ), + const FakeCommand( + command: <String>[ + 'sysctl', + 'hw.optional.arm64', + ], + exitCode: 1, + ), + const FakeCommand( + command: <String>['xcrun', 'clang'], + ), + const FakeCommand( + command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'], + ), + const FakeCommand( + command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'], + stdout: '17.0' + ), + ]); + final Xcode xcode = Xcode.test( + processManager: processManager, + xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager), + ); + final XcodeValidator validator = XcodeValidator( + xcode: xcode, + userMessages: UserMessages(), + iosSimulatorUtils: FakeIOSSimulatorUtils(), + ); + final ValidationResult result = await validator.validate(); + expect(result.type, ValidationType.partial); + expect(result.messages.last.type, ValidationMessageType.error); + expect(result.messages.last.message, contains('Unable to get list of installed Simulator runtimes')); + expect(processManager, hasNoRemainingExpectations); + }); + + testWithoutContext('Emits partial status with hint when simulator runtimes do not match SDK', () async { + final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[ + const FakeCommand( + command: <String>['/usr/bin/xcode-select', '--print-path'], + stdout: '/Library/Developer/CommandLineTools', + ), + const FakeCommand( + command: <String>[ + 'which', + 'sysctl', + ], + ), + const FakeCommand( + command: <String>[ + 'sysctl', + 'hw.optional.arm64', + ], + exitCode: 1, + ), + const FakeCommand( + command: <String>['xcrun', 'clang'], + ), + const FakeCommand( + command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'], + ), + const FakeCommand( + command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'], + stdout: '17.0' + ), + ]); + final Xcode xcode = Xcode.test( + processManager: processManager, + xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager), + ); + final FakeIOSSimulatorUtils simulatorUtils = FakeIOSSimulatorUtils(runtimes: <IOSSimulatorRuntime>[ + IOSSimulatorRuntime.fromJson(<String, String>{'version': '16.0'}), + ]); + final XcodeValidator validator = XcodeValidator( + xcode: xcode, + userMessages: UserMessages(), + iosSimulatorUtils: simulatorUtils, + ); + final ValidationResult result = await validator.validate(); + expect(result.type, ValidationType.partial); + expect(result.messages.last.type, ValidationMessageType.hint); + expect(result.messages.last.message, contains('iOS 17.0 Simulator not installed')); + expect(processManager, hasNoRemainingExpectations); }); testWithoutContext('Succeeds when all checks pass', () async { @@ -175,12 +343,23 @@ void main() { const FakeCommand( command: <String>['xcrun', 'simctl', 'list', 'devices', 'booted'], ), + const FakeCommand( + command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-platform-version'], + stdout: '17.0' + ), ]); final Xcode xcode = Xcode.test( processManager: processManager, xcodeProjectInterpreter: XcodeProjectInterpreter.test(processManager: processManager), ); - final XcodeValidator validator = XcodeValidator(xcode: xcode, userMessages: UserMessages()); + final FakeIOSSimulatorUtils simulatorUtils = FakeIOSSimulatorUtils(runtimes: <IOSSimulatorRuntime>[ + IOSSimulatorRuntime.fromJson(<String, String>{'version': '17.0'}), + ]); + final XcodeValidator validator = XcodeValidator( + xcode: xcode, + userMessages: UserMessages(), + iosSimulatorUtils: simulatorUtils, + ); final ValidationResult result = await validator.validate(); expect(result.type, ValidationType.success); expect(result.messages.length, 2); @@ -189,6 +368,24 @@ void main() { expect(firstMessage.message, 'Xcode at /Library/Developer/CommandLineTools'); expect(result.statusInfo, '1000.0.0'); expect(result.messages[1].message, 'Build 13C100'); + expect(processManager, hasNoRemainingExpectations); }); }); } + +class FakeIOSSimulatorUtils extends Fake implements IOSSimulatorUtils { + FakeIOSSimulatorUtils({ + this.runtimes, + }); + + List<IOSSimulatorRuntime>? runtimes; + + List<IOSSimulatorRuntime> get _runtimesList { + return runtimes ?? <IOSSimulatorRuntime>[]; + } + + @override + Future<List<IOSSimulatorRuntime>> getAvailableIOSRuntimes() async { + return _runtimesList; + } +} diff --git a/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart b/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart index b0f608538753e..8c6868512138d 100644 --- a/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart +++ b/packages/flutter_tools/test/general.shard/mdns_discovery_test.dart @@ -478,6 +478,18 @@ void main() { }); group('for launch', () { + testWithoutContext('Ensure either port or device name are provided', () async { + final MDnsClient client = FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}); + + final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery( + mdnsClient: client, + logger: BufferLogger.test(), + flutterUsage: TestUsage(), + ); + + expect(() async => portDiscovery.queryForLaunch(applicationId: 'app-id'), throwsAssertionError); + }); + testWithoutContext('No ports available', () async { final MDnsClient client = FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}); @@ -666,6 +678,93 @@ void main() { message:'Did not find a Dart VM Service advertised for srv-bar on port 321.'), ); }); + + testWithoutContext('Matches on application id and device name', () async { + final MDnsClient client = FakeMDnsClient( + <PtrResourceRecord>[ + PtrResourceRecord('foo', future, domainName: 'srv-foo'), + PtrResourceRecord('bar', future, domainName: 'srv-bar'), + PtrResourceRecord('baz', future, domainName: 'srv-boo'), + ], + <String, List<SrvResourceRecord>>{ + 'srv-bar': <SrvResourceRecord>[ + SrvResourceRecord('srv-foo', future, port: 123, weight: 1, priority: 1, target: 'My-Phone.local'), + ], + }, + ); + final FakeIOSDevice device = FakeIOSDevice( + name: 'My Phone', + ); + final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery( + mdnsClient: client, + logger: BufferLogger.test(), + flutterUsage: TestUsage(), + ); + + final Uri? uri = await portDiscovery.getVMServiceUriForLaunch( + 'srv-bar', + device, + ); + expect(uri.toString(), 'http://127.0.0.1:123/'); + }); + + testWithoutContext('Throw error if unable to find VM Service with app id and device name', () async { + final MDnsClient client = FakeMDnsClient( + <PtrResourceRecord>[ + PtrResourceRecord('foo', future, domainName: 'srv-foo'), + PtrResourceRecord('bar', future, domainName: 'srv-bar'), + PtrResourceRecord('baz', future, domainName: 'srv-boo'), + ], + <String, List<SrvResourceRecord>>{ + 'srv-foo': <SrvResourceRecord>[ + SrvResourceRecord('srv-foo', future, port: 123, weight: 1, priority: 1, target: 'target-foo'), + ], + }, + ); + final FakeIOSDevice device = FakeIOSDevice( + name: 'My Phone', + ); + final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery( + mdnsClient: client, + logger: BufferLogger.test(), + flutterUsage: TestUsage(), + ); + expect( + portDiscovery.getVMServiceUriForLaunch( + 'srv-bar', + device, + ), + throwsToolExit( + message:'Did not find a Dart VM Service advertised for srv-bar'), + ); + }); + }); + + group('deviceNameMatchesTargetName', () { + testWithoutContext('compares case insensitive and without spaces, hypthens, .local', () { + final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery( + mdnsClient: FakeMDnsClient( + <PtrResourceRecord>[], + <String, List<SrvResourceRecord>>{}, + ), + logger: BufferLogger.test(), + flutterUsage: TestUsage(), + ); + + expect(portDiscovery.deviceNameMatchesTargetName('My phone', 'My-Phone.local'), isTrue); + }); + + testWithoutContext('includes numbers in comparison', () { + final MDnsVmServiceDiscovery portDiscovery = MDnsVmServiceDiscovery( + mdnsClient: FakeMDnsClient( + <PtrResourceRecord>[], + <String, List<SrvResourceRecord>>{}, + ), + logger: BufferLogger.test(), + flutterUsage: TestUsage(), + ); + expect(portDiscovery.deviceNameMatchesTargetName('My phone', 'My-Phone-2.local'), isFalse); + }); }); testWithoutContext('Find firstMatchingVmService with many available and no application id', () async { @@ -895,6 +994,11 @@ class FakeMDnsClient extends Fake implements MDnsClient { // Until we fix that, we have to also ignore related lints here. // ignore: avoid_implementing_value_types class FakeIOSDevice extends Fake implements IOSDevice { + FakeIOSDevice({this.name = 'iPhone'}); + + @override + final String name; + @override Future<TargetPlatform> get targetPlatform async => TargetPlatform.ios; diff --git a/packages/flutter_tools/test/general.shard/message_parser_test.dart b/packages/flutter_tools/test/general.shard/message_parser_test.dart index 4d19d186bf021..efc9680adf249 100644 --- a/packages/flutter_tools/test/general.shard/message_parser_test.dart +++ b/packages/flutter_tools/test/general.shard/message_parser_test.dart @@ -306,6 +306,25 @@ void main() { ]) )); + expect(Parser('argumentTest', 'app_en.arb', 'Today is {date, date, ::yMMd}').parse(), equals( + Node(ST.message, 0, children: <Node>[ + Node(ST.string, 0, value: 'Today is '), + Node(ST.argumentExpr, 9, children: <Node>[ + Node(ST.openBrace, 9, value: '{'), + Node(ST.identifier, 10, value: 'date'), + Node(ST.comma, 14, value: ','), + Node(ST.argType, 16, children: <Node>[ + Node(ST.date, 16, value: 'date'), + ]), + Node(ST.comma, 20, value: ','), + Node(ST.colon, 22, value: ':'), + Node(ST.colon, 23, value: ':'), + Node(ST.identifier, 24, value: 'yMMd'), + Node(ST.closeBrace, 28, value: '}'), + ]), + ]) + )); + expect(Parser( 'plural', 'app_en.arb', diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart index 2cad7162ba095..85918adcc4d1b 100644 --- a/packages/flutter_tools/test/general.shard/project_test.dart +++ b/packages/flutter_tools/test/general.shard/project_test.dart @@ -11,6 +11,7 @@ import 'package:flutter_tools/src/android/java.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/os.dart'; +import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/convert.dart'; @@ -431,7 +432,7 @@ dependencies { final AndroidStudio androidStudio; final FakeAndroidSdkWithDir androidSdk; final FileSystem fileSystem = getFileSystemForPlatform(); - java = FakeJava(version: JavaVersion(longText: '17.0.2', number: '17.0.2')); + java = FakeJava(version: Version(17, 0, 2)); processManager = FakeProcessManager.empty(); androidStudio = FakeAndroidStudio(); androidSdk = @@ -462,7 +463,7 @@ dependencies { final AndroidStudio androidStudio; final FakeAndroidSdkWithDir androidSdk; final FileSystem fileSystem = getFileSystemForPlatform(); - java = FakeJava(version: JavaVersion(longText: '1.8.0_242', number: '1.8.0_242')); + java = FakeJava(version: const Version.withText(1, 8, 0, '1.8.0_242')); processManager = FakeProcessManager.empty(); androidStudio = FakeAndroidStudio(); androidSdk = @@ -495,7 +496,7 @@ dependencies { final FakeAndroidSdkWithDir androidSdk; final FileSystem fileSystem = getFileSystemForPlatform(); processManager = FakeProcessManager.empty(); - java = FakeJava(version: JavaVersion(longText: '11.0.14', number: '11.0.14')); + java = FakeJava(version: Version(11, 0, 14)); androidStudio = FakeAndroidStudio(); androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory); @@ -530,7 +531,7 @@ dependencies { final FakeAndroidSdkWithDir androidSdk; final FileSystem fileSystem = getFileSystemForPlatform(); processManager = FakeProcessManager.empty(); - java = FakeJava(version: JavaVersion(longText: javaV, number: javaV)); + java = FakeJava(version: Version.parse(javaV)); androidStudio = FakeAndroidStudio(); androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory); @@ -581,7 +582,7 @@ dependencies { final FakeAndroidSdkWithDir androidSdk; final FileSystem fileSystem = getFileSystemForPlatform(); processManager = FakeProcessManager.empty(); - java = FakeJava(version: JavaVersion(longText: '17.0.2', number: '17.0.2')); + java = FakeJava(version: Version(17, 0, 2)); androidStudio = FakeAndroidStudio(); androidSdk = FakeAndroidSdkWithDir(fileSystem.currentDirectory); @@ -621,7 +622,7 @@ dependencies { final AndroidStudio androidStudio; final FakeAndroidSdkWithDir androidSdk; final FileSystem fileSystem = getFileSystemForPlatform(); - java = FakeJava(version: JavaVersion(longText: '11.0.2', number: '11.0.2')); + java = FakeJava(version: Version(11, 0, 2)); processManager = FakeProcessManager.empty(); androidStudio = FakeAndroidStudio(); androidSdk = diff --git a/packages/flutter_tools/test/general.shard/resident_devtools_handler_test.dart b/packages/flutter_tools/test/general.shard/resident_devtools_handler_test.dart index 8c36d4e828b50..56d933291e84d 100644 --- a/packages/flutter_tools/test/general.shard/resident_devtools_handler_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_devtools_handler_test.dart @@ -156,6 +156,7 @@ void main() { }, ), listViews, + listViews, const FakeVmServiceRequest( method: 'ext.flutter.activeDevToolsServerAddress', args: <String, Object>{ @@ -163,7 +164,6 @@ void main() { 'value': 'http://localhost:8080', }, ), - listViews, const FakeVmServiceRequest( method: 'ext.flutter.connectedVmServiceUri', args: <String, Object>{ @@ -314,6 +314,7 @@ void main() { }, ), listViews, + listViews, const FakeVmServiceRequest( method: 'ext.flutter.activeDevToolsServerAddress', args: <String, Object>{ @@ -321,7 +322,6 @@ void main() { 'value': 'http://localhost:8080', }, ), - listViews, const FakeVmServiceRequest( method: 'ext.flutter.connectedVmServiceUri', args: <String, Object>{ diff --git a/packages/flutter_tools/test/general.shard/resident_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_runner_test.dart index f97a0d0a0e94c..35970ba5c196c 100644 --- a/packages/flutter_tools/test/general.shard/resident_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_runner_test.dart @@ -20,7 +20,6 @@ import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_system/targets/scene_importer.dart'; import 'package:flutter_tools/src/build_system/targets/shader_compiler.dart'; -import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/compile.dart'; import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/devfs.dart'; @@ -28,8 +27,6 @@ import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/device_port_forwarder.dart'; import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:flutter_tools/src/ios/devices.dart'; -import 'package:flutter_tools/src/ios/mac.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/reporting/reporting.dart'; import 'package:flutter_tools/src/resident_devtools_handler.dart'; @@ -44,17 +41,23 @@ import 'package:vm_service/vm_service.dart' as vm_service; import '../src/common.dart'; import '../src/context.dart'; -import '../src/fake_devices.dart'; import '../src/fake_vm_services.dart'; import '../src/fakes.dart'; import '../src/testbed.dart'; +final vm_service.Event fakeUnpausedEvent = vm_service.Event( + kind: vm_service.EventKind.kResume, + timestamp: 0 +); + +final vm_service.Event fakePausedEvent = vm_service.Event( + kind: vm_service.EventKind.kPauseException, + timestamp: 0 +); + final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate( id: '1', - pauseEvent: vm_service.Event( - kind: vm_service.EventKind.kResume, - timestamp: 0 - ), + pauseEvent: fakeUnpausedEvent, breakpoints: <vm_service.Breakpoint>[], extensionRPCs: <String>[], libraries: <vm_service.LibraryRef>[ @@ -76,10 +79,7 @@ final vm_service.Isolate fakeUnpausedIsolate = vm_service.Isolate( final vm_service.Isolate fakePausedIsolate = vm_service.Isolate( id: '1', - pauseEvent: vm_service.Event( - kind: vm_service.EventKind.kPauseException, - timestamp: 0 - ), + pauseEvent: fakePausedEvent, breakpoints: <vm_service.Breakpoint>[ vm_service.Breakpoint( breakpointNumber: 123, @@ -541,11 +541,11 @@ void main() { listViews, listViews, FakeVmServiceRequest( - method: 'getIsolate', + method: 'getIsolatePauseEvent', args: <String, Object>{ 'isolateId': '1', }, - jsonResponse: fakeUnpausedIsolate.toJson(), + jsonResponse: fakeUnpausedEvent.toJson(), ), FakeVmServiceRequest( method: 'ext.flutter.reassemble', @@ -608,11 +608,11 @@ void main() { }, ), FakeVmServiceRequest( - method: 'getIsolate', + method: 'getIsolatePauseEvent', args: <String, Object>{ 'isolateId': '1', }, - jsonResponse: fakeUnpausedIsolate.toJson(), + jsonResponse: fakeUnpausedEvent.toJson(), ), FakeVmServiceRequest( method: 'ext.flutter.reassemble', @@ -732,11 +732,11 @@ void main() { }, ), FakeVmServiceRequest( - method: 'getIsolate', + method: 'getIsolatePauseEvent', args: <String, Object>{ 'isolateId': '1', }, - jsonResponse: fakeUnpausedIsolate.toJson(), + jsonResponse: fakeUnpausedEvent.toJson(), ), FakeVmServiceRequest( method: 'ext.flutter.reassemble', @@ -795,11 +795,11 @@ void main() { }, ), FakeVmServiceRequest( - method: 'getIsolate', + method: 'getIsolatePauseEvent', args: <String, Object>{ 'isolateId': '1', }, - jsonResponse: fakeUnpausedIsolate.toJson(), + jsonResponse: fakeUnpausedEvent.toJson(), ), FakeVmServiceRequest( method: 'ext.flutter.fastReassemble', @@ -886,11 +886,11 @@ void main() { }, ), FakeVmServiceRequest( - method: 'getIsolate', + method: 'getIsolatePauseEvent', args: <String, Object>{ 'isolateId': '1', }, - jsonResponse: fakeUnpausedIsolate.toJson(), + jsonResponse: fakeUnpausedEvent.toJson(), ), FakeVmServiceRequest( method: 'ext.flutter.fastReassemble', @@ -2442,70 +2442,6 @@ flutter: expect(flutterDevice.devFS!.hasSetAssetDirectory, true); expect(fakeVmServiceHost!.hasRemainingExpectations, false); })); - - group('startEchoingDeviceLog', () { - late FakeProcessManager processManager; - late Artifacts artifacts; - late Cache fakeCache; - late BufferLogger logger; - - setUp(() { - processManager = FakeProcessManager.empty(); - fakeCache = Cache.test(processManager: FakeProcessManager.any()); - artifacts = Artifacts.test(); - logger = BufferLogger.test(); - }); - - testUsingContext('IOSDeviceLogReader does not print logs', () async { - final IOSDeviceLogReader logReader = IOSDeviceLogReader.test( - iMobileDevice: IMobileDevice( - artifacts: artifacts, - processManager: processManager, - cache: fakeCache, - logger: logger, - ), - useSyslog: false, - ); - device = FakeDevice(deviceLogReader: logReader); - final TestFlutterDevice flutterDevice = TestFlutterDevice( - device, - ); - - await flutterDevice.startEchoingDeviceLog(); - final Future<List<String>> linesFromStream = logReader.logLines.toList(); - logReader.linesController.add('event'); - await logReader.linesController.close(); - final List<String> lines = await linesFromStream; - - expect(lines, contains('event')); - expect(logger.statusText, isEmpty); - - await flutterDevice.stopEchoingDeviceLog(); - }, overrides: <Type, Generator>{ - Logger: () => logger, - }); - - testUsingContext('Non-IOSDeviceLogReader does print logs', () async { - final FakeDeviceLogReader logReader = FakeDeviceLogReader(); - device = FakeDevice(deviceLogReader: logReader); - final TestFlutterDevice flutterDevice = TestFlutterDevice( - device, - ); - - await flutterDevice.startEchoingDeviceLog(); - final Future<List<String>> linesFromStream = logReader.logLines.toList(); - logReader.addLine('event'); - await logReader.dispose(); - final List<String> lines = await linesFromStream; - - expect(lines, contains('event')); - expect(logger.statusText, contains('event')); - - await flutterDevice.stopEchoingDeviceLog(); - }, overrides: <Type, Generator>{ - Logger: () => logger, - }); - }); } // This implements [dds.DartDevelopmentService], not the [DartDevelopmentService] @@ -2744,16 +2680,13 @@ class FakeDevice extends Fake implements Device { this.supportsHotRestart = true, this.supportsScreenshot = true, this.supportsFlutterExit = true, - DeviceLogReader? deviceLogReader, }) : _isLocalEmulator = isLocalEmulator, _targetPlatform = targetPlatform, - _sdkNameAndVersion = sdkNameAndVersion, - _deviceLogReader = deviceLogReader; + _sdkNameAndVersion = sdkNameAndVersion; final bool _isLocalEmulator; final TargetPlatform _targetPlatform; final String _sdkNameAndVersion; - final DeviceLogReader? _deviceLogReader; bool disposed = false; bool appStopped = false; @@ -2811,12 +2744,7 @@ class FakeDevice extends Fake implements Device { FutureOr<DeviceLogReader> getLogReader({ ApplicationPackage? app, bool includePastLogs = false, - }) { - if (_deviceLogReader != null) { - return _deviceLogReader!; - } - return NoOpDeviceLogReader(name); - } + }) => NoOpDeviceLogReader(name); @override DevicePortForwarder portForwarder = const NoOpDevicePortForwarder(); diff --git a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart index aa50d13c406c7..ff803f7bbc7f3 100644 --- a/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart +++ b/packages/flutter_tools/test/general.shard/resident_web_runner_test.dart @@ -86,6 +86,10 @@ const List<VmServiceExpectation> kAttachIsolateExpectations = 'service': kFlutterGetIOSUniversalLinkSettingsServiceName, 'alias': kFlutterToolAlias, }), + FakeVmServiceRequest(method: 'registerService', args: <String, Object>{ + 'service': kFlutterGetAndroidAppLinkSettingsName, + 'alias': kFlutterToolAlias, + }), FakeVmServiceRequest( method: 'streamListen', args: <String, Object>{ @@ -726,8 +730,8 @@ void main() { final String entrypointContents = fileSystem.file(webDevFS.mainUri).readAsStringSync(); expect(entrypointContents, contains('// Flutter web bootstrap script')); - expect(entrypointContents, contains("import 'dart:ui' as ui;")); - expect(entrypointContents, contains('await ui.webOnlyWarmupEngine(')); + expect(entrypointContents, contains("import 'dart:ui_web' as ui_web;")); + expect(entrypointContents, contains('await ui_web.bootstrapEngine(')); expect(logger.statusText, contains('Restarted application in')); expect(result.code, 0); diff --git a/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart b/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart index d19b0c7ca0fdb..6c89db8fa8e3c 100644 --- a/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart +++ b/packages/flutter_tools/test/general.shard/runner/target_devices_test.dart @@ -1459,7 +1459,7 @@ No devices found yet. Checking for wireless devices... logger: logger, ); targetDevices.waitForWirelessBeforeInput = true; - targetDevices.deviceSelection.input = '1'; + targetDevices.deviceSelection.input = <String>['1']; logger.originalStatusText = ''' Connected devices: target-device-9 (mobile) • xxx • ios • iOS 16 @@ -1486,6 +1486,49 @@ Please choose one (or "q" to quit): ''')); }, overrides: <Type, Generator>{ AnsiTerminal: () => terminal, }); + + + testUsingContext('handle invalid options for device', () async { + deviceManager.iosDiscoverer.deviceList = <Device>[nonEphemeralDevice]; + + final TestTargetDevicesWithExtendedWirelessDeviceDiscovery targetDevices = TestTargetDevicesWithExtendedWirelessDeviceDiscovery( + deviceManager: deviceManager, + logger: logger, + ); + targetDevices.waitForWirelessBeforeInput = true; + + // Having the '0' first is an invalid choice for a device, the second + // item in the list is a '2' which is out of range since we only have + // one item in the deviceList. The final item in the list, is '1' + // which is a valid option though which will return a valid device + // + // Important: if none of the values in the list are valid, the test will + // hang indefinitely since the [userSelectDevice()] method uses a while + // loop to listen for valid devices + targetDevices.deviceSelection.input = <String>['0', '2', '1']; + logger.originalStatusText = ''' +Connected devices: +target-device-9 (mobile) • xxx • ios • iOS 16 + +Checking for wireless devices... + +[1]: target-device-9 (xxx) +'''; + + final List<Device>? devices = await targetDevices.findAllTargetDevices(); + + expect(logger.statusText, equals(''' +Connected devices: +target-device-9 (mobile) • xxx • ios • iOS 16 + +No wireless devices were found. + +[1]: target-device-9 (xxx) +Please choose one (or "q" to quit): ''')); + expect(devices, <Device>[nonEphemeralDevice]); + }, overrides: <Type, Generator>{ + AnsiTerminal: () => terminal, + }); }); group('without stdinHasTerminal', () { @@ -1753,7 +1796,7 @@ Checking for wireless devices... ]; targetDevices.waitForWirelessBeforeInput = true; - targetDevices.deviceSelection.input = '3'; + targetDevices.deviceSelection.input = <String>['3']; logger.originalStatusText = ''' Connected devices: target-device-1 (mobile) • xxx • ios • iOS 16 @@ -1791,7 +1834,7 @@ Please choose one (or "q" to quit): ''')); deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2]; targetDevices.waitForWirelessBeforeInput = true; - targetDevices.deviceSelection.input = '2'; + targetDevices.deviceSelection.input = <String>['2']; logger.originalStatusText = ''' Connected devices: target-device-1 (mobile) • xxx • ios • iOS 16 @@ -1828,7 +1871,7 @@ Please choose one (or "q" to quit): ''')); deviceManager.iosDiscoverer.refreshDeviceList = <Device>[connectedWirelessIOSDevice1, connectedWirelessIOSDevice2]; targetDevices.waitForWirelessBeforeInput = true; - targetDevices.deviceSelection.input = '2'; + targetDevices.deviceSelection.input = <String>['2']; terminal.setPrompt(<String>['1', '2', 'q', 'Q'], '1'); final List<Device>? devices = await targetDevices.findAllTargetDevices(); @@ -1906,7 +1949,7 @@ target-device-5 (mobile) • xxx • ios • iOS 16 deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2]; targetDevices.waitForWirelessBeforeInput = true; - targetDevices.deviceSelection.input = '2'; + targetDevices.deviceSelection.input = <String>['2']; logger.originalStatusText = ''' Connected devices: target-device-1 (mobile) • xxx • ios • iOS 16 @@ -1951,7 +1994,7 @@ Please choose one (or "q" to quit): ''')); deviceManager.iosDiscoverer.refreshDeviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2, connectedWirelessIOSDevice1]; targetDevices.waitForWirelessBeforeInput = true; - targetDevices.deviceSelection.input = '2'; + targetDevices.deviceSelection.input = <String>['2']; logger.originalStatusText = ''' Connected devices: target-device-1 (mobile) • xxx • ios • iOS 16 @@ -2133,7 +2176,7 @@ target-device-6 (mobile) • xxx • ios • iOS 16 ]; targetDevices.waitForWirelessBeforeInput = true; - targetDevices.deviceSelection.input = '3'; + targetDevices.deviceSelection.input = <String>['3']; logger.originalStatusText = ''' Found multiple devices with name or id matching target-device: target-device-1 (mobile) • xxx • ios • iOS 16 @@ -2173,7 +2216,7 @@ Please choose one (or "q" to quit): ''')); deviceManager.iosDiscoverer.deviceList = <Device>[attachedIOSDevice1, attachedIOSDevice2]; targetDevices.waitForWirelessBeforeInput = true; - targetDevices.deviceSelection.input = '2'; + targetDevices.deviceSelection.input = <String>['2']; logger.originalStatusText = ''' Found multiple devices with name or id matching target-device: target-device-1 (mobile) • xxx • ios • iOS 16 @@ -2443,11 +2486,21 @@ class TestTargetDevicesWithExtendedWirelessDeviceDiscovery extends TargetDevices class TestTargetDeviceSelection extends TargetDeviceSelection { TestTargetDeviceSelection(super.logger); - String input = ''; + List<String> input = <String>[]; @override Future<String> readUserInput() async { - return input; + // If only one value is provided for the input, continue + // to return that one input value without popping + // + // If more than one input values are provided, we are simulating + // the user selecting more than one option for a device, so we will pop + // them out from the front + if (input.length > 1) { + return input.removeAt(0); + } + + return input[0]; } } diff --git a/packages/flutter_tools/test/general.shard/terminal_handler_test.dart b/packages/flutter_tools/test/general.shard/terminal_handler_test.dart index 5992956b6937b..e234825ede40e 100644 --- a/packages/flutter_tools/test/general.shard/terminal_handler_test.dart +++ b/packages/flutter_tools/test/general.shard/terminal_handler_test.dart @@ -1496,7 +1496,6 @@ class FakeResidentCompiler extends Fake implements ResidentCompiler { } class TestRunner extends Fake implements ResidentRunner { bool hasHelpBeenPrinted = false; - String? receivedCommand; @override Future<void> cleanupAfterSignal() async { } diff --git a/packages/flutter_tools/test/general.shard/update_packages_test.dart b/packages/flutter_tools/test/general.shard/update_packages_test.dart index 93deb41a5f108..b92fe6b15a40e 100644 --- a/packages/flutter_tools/test/general.shard/update_packages_test.dart +++ b/packages/flutter_tools/test/general.shard/update_packages_test.dart @@ -108,8 +108,6 @@ void main() { 'material_color_utilities', 'url_launcher_android', 'archive', - 'path_provider_android', - 'flutter_plugin_android_lifecycle', ]), ); }); diff --git a/packages/flutter_tools/test/general.shard/version_test.dart b/packages/flutter_tools/test/general.shard/version_test.dart index 6fe00447e6c44..ab4c730d444d5 100644 --- a/packages/flutter_tools/test/general.shard/version_test.dart +++ b/packages/flutter_tools/test/general.shard/version_test.dart @@ -4,12 +4,13 @@ import 'dart:convert'; +import 'package:file/file.dart'; +import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/base/time.dart'; import 'package:flutter_tools/src/cache.dart'; -import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/version.dart'; import 'package:test/fake.dart'; @@ -18,7 +19,7 @@ import '../src/context.dart'; import '../src/fake_process_manager.dart'; import '../src/fakes.dart' show FakeFlutterVersion; -final SystemClock _testClock = SystemClock.fixed(DateTime(2015)); +final SystemClock _testClock = SystemClock.fixed(DateTime.utc(2015)); final DateTime _stampUpToDate = _testClock.ago(VersionFreshnessValidator.checkAgeConsideredUpToDate ~/ 2); final DateTime _stampOutOfDate = _testClock.ago(VersionFreshnessValidator.checkAgeConsideredUpToDate * 2); @@ -49,7 +50,11 @@ void main() { } group('$FlutterVersion for $channel', () { + late FileSystem fs; + const String flutterRoot = '/path/to/flutter'; + setUpAll(() { + fs = MemoryFileSystem.test(); Cache.disableLocking(); VersionFreshnessValidator.timeToPauseToLetUserReadTheMessage = Duration.zero; }); @@ -101,7 +106,7 @@ void main() { ), ]); - final FlutterVersion flutterVersion = globals.flutterVersion; + final FlutterVersion flutterVersion = FlutterVersion(clock: _testClock, fs: fs, flutterRoot: flutterRoot); await flutterVersion.checkFlutterVersionFreshness(); expect(flutterVersion.channel, channel); expect(flutterVersion.repositoryUrl, flutterUpstreamUrl); @@ -124,7 +129,6 @@ void main() { expect(testLogger.statusText, isEmpty); expect(processManager, hasNoRemainingExpectations); }, overrides: <Type, Generator>{ - FlutterVersion: () => FlutterVersion(clock: _testClock), ProcessManager: () => processManager, Cache: () => cache, }); @@ -419,15 +423,197 @@ void main() { ), ]); - final FlutterVersion flutterVersion = globals.flutterVersion; + final MemoryFileSystem fs = MemoryFileSystem.test(); + final FlutterVersion flutterVersion = FlutterVersion( + clock: _testClock, + fs: fs, + flutterRoot: '/path/to/flutter', + ); expect(flutterVersion.channel, '[user-branch]'); expect(flutterVersion.getVersionString(), 'feature-branch/1234abcd'); expect(flutterVersion.getBranchName(), 'feature-branch'); expect(flutterVersion.getVersionString(redactUnknownBranches: true), '[user-branch]/1234abcd'); expect(flutterVersion.getBranchName(redactUnknownBranches: true), '[user-branch]'); + + expect(processManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + ProcessManager: () => processManager, + Cache: () => cache, + }); + + testUsingContext('ensureVersionFile() writes version information to disk', () async { + processManager.addCommands(<FakeCommand>[ + const FakeCommand( + command: <String>['git', '-c', 'log.showSignature=false', 'log', '-n', '1', '--pretty=format:%H'], + stdout: '1234abcd', + ), + const FakeCommand( + command: <String>['git', 'tag', '--points-at', '1234abcd'], + ), + const FakeCommand( + command: <String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', '1234abcd'], + stdout: '0.1.2-3-1234abcd', + ), + const FakeCommand( + command: <String>['git', 'symbolic-ref', '--short', 'HEAD'], + stdout: 'feature-branch', + ), + const FakeCommand( + command: <String>['git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{upstream}'], + ), + FakeCommand( + command: const <String>[ + 'git', + '-c', + 'log.showSignature=false', + 'log', + 'HEAD', + '-n', + '1', + '--pretty=format:%ad', + '--date=iso', + ], + stdout: _testClock.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2).toString(), + ), + ]); + + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Directory flutterRoot = fs.directory('/path/to/flutter'); + flutterRoot.childDirectory('bin').childDirectory('cache').createSync(recursive: true); + final FlutterVersion flutterVersion = FlutterVersion( + clock: _testClock, + fs: fs, + flutterRoot: flutterRoot.path, + ); + + final File versionFile = fs.file('/path/to/flutter/bin/cache/flutter.version.json'); + expect(versionFile.existsSync(), isFalse); + + flutterVersion.ensureVersionFile(); + expect(versionFile.existsSync(), isTrue); + expect(versionFile.readAsStringSync(), ''' +{ + "frameworkVersion": "0.0.0-unknown", + "channel": "[user-branch]", + "repositoryUrl": "unknown source", + "frameworkRevision": "1234abcd", + "frameworkCommitDate": "2014-10-02 00:00:00.000Z", + "engineRevision": "abcdefg", + "dartSdkVersion": "2.12.0", + "devToolsVersion": "2.8.0", + "flutterVersion": "0.0.0-unknown" +}'''); + expect(processManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + ProcessManager: () => processManager, + Cache: () => cache, + }); + + testUsingContext('version does not call git if a .version.json file exists', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Directory flutterRoot = fs.directory('/path/to/flutter'); + final Directory cacheDir = flutterRoot + .childDirectory('bin') + .childDirectory('cache') + ..createSync(recursive: true); + const String devToolsVersion = '0000000'; + const Map<String, Object> versionJson = <String, Object>{ + 'channel': 'stable', + 'frameworkVersion': '1.2.3', + 'repositoryUrl': 'https://github.com/flutter/flutter.git', + 'frameworkRevision': '1234abcd', + 'frameworkCommitDate': '2023-04-28 12:34:56 -0400', + 'engineRevision': 'deadbeef', + 'dartSdkVersion': 'deadbeef2', + 'devToolsVersion': devToolsVersion, + 'flutterVersion': 'foo', + }; + cacheDir.childFile('flutter.version.json').writeAsStringSync( + jsonEncode(versionJson), + ); + final FlutterVersion flutterVersion = FlutterVersion( + clock: _testClock, + fs: fs, + flutterRoot: flutterRoot.path, + ); + expect(flutterVersion.channel, 'stable'); + expect(flutterVersion.getVersionString(), 'stable/1.2.3'); + expect(flutterVersion.getBranchName(), 'stable'); + expect(flutterVersion.dartSdkVersion, 'deadbeef2'); + expect(flutterVersion.devToolsVersion, devToolsVersion); + expect(flutterVersion.engineRevision, 'deadbeef'); + + expect(processManager, hasNoRemainingExpectations); + }, overrides: <Type, Generator>{ + ProcessManager: () => processManager, + Cache: () => cache, + }); + + testUsingContext('FlutterVersion() falls back to git if .version.json is malformed', () async { + final MemoryFileSystem fs = MemoryFileSystem.test(); + final Directory flutterRoot = fs.directory(fs.path.join('path', 'to', 'flutter')); + final Directory cacheDir = flutterRoot + .childDirectory('bin') + .childDirectory('cache') + ..createSync(recursive: true); + final File legacyVersionFile = flutterRoot.childFile('version'); + final File versionFile = cacheDir.childFile('flutter.version.json')..writeAsStringSync( + '{', + ); + + processManager.addCommands(<FakeCommand>[ + const FakeCommand( + command: <String>['git', '-c', 'log.showSignature=false', 'log', '-n', '1', '--pretty=format:%H'], + stdout: '1234abcd', + ), + const FakeCommand( + command: <String>['git', 'tag', '--points-at', '1234abcd'], + ), + const FakeCommand( + command: <String>['git', 'describe', '--match', '*.*.*', '--long', '--tags', '1234abcd'], + stdout: '0.1.2-3-1234abcd', + ), + const FakeCommand( + command: <String>['git', 'symbolic-ref', '--short', 'HEAD'], + stdout: 'feature-branch', + ), + const FakeCommand( + command: <String>['git', 'rev-parse', '--abbrev-ref', '--symbolic', '@{upstream}'], + stdout: 'feature-branch', + ), + FakeCommand( + command: const <String>[ + 'git', + '-c', + 'log.showSignature=false', + 'log', + 'HEAD', + '-n', + '1', + '--pretty=format:%ad', + '--date=iso', + ], + stdout: _testClock.ago(VersionFreshnessValidator.versionAgeConsideredUpToDate('stable') ~/ 2).toString(), + ), + ]); + + // version file exists in a malformed state + expect(versionFile.existsSync(), isTrue); + final FlutterVersion flutterVersion = FlutterVersion( + clock: _testClock, + fs: fs, + flutterRoot: flutterRoot.path, + ); + + // version file was deleted because it couldn't be parsed + expect(versionFile.existsSync(), isFalse); + expect(legacyVersionFile.existsSync(), isFalse); + // version file was written to disk + flutterVersion.ensureVersionFile(); expect(processManager, hasNoRemainingExpectations); + expect(versionFile.existsSync(), isTrue); + expect(legacyVersionFile.existsSync(), isTrue); }, overrides: <Type, Generator>{ - FlutterVersion: () => FlutterVersion(clock: _testClock), ProcessManager: () => processManager, Cache: () => cache, }); diff --git a/packages/flutter_tools/test/general.shard/vscode/vscode_validator_test.dart b/packages/flutter_tools/test/general.shard/vscode/vscode_validator_test.dart index e80987105b036..1be8be8ab5932 100644 --- a/packages/flutter_tools/test/general.shard/vscode/vscode_validator_test.dart +++ b/packages/flutter_tools/test/general.shard/vscode/vscode_validator_test.dart @@ -5,10 +5,15 @@ import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/base/user_messages.dart'; +import 'package:flutter_tools/src/base/version.dart'; +import 'package:flutter_tools/src/doctor_validator.dart'; import 'package:flutter_tools/src/vscode/vscode.dart'; +import 'package:flutter_tools/src/vscode/vscode_validator.dart'; +import 'package:test/fake.dart'; import '../../src/common.dart'; -import '../../src/fake_process_manager.dart'; +import '../../src/context.dart'; void main() { testWithoutContext('VsCode search locations on windows supports an empty environment', () { @@ -20,4 +25,26 @@ void main() { expect(VsCode.allInstalled(fileSystem, platform, FakeProcessManager.any()), isEmpty); }); + + group(VsCodeValidator, () { + testUsingContext('Warns if VS Code version could not be found', () async { + final VsCodeValidator validator = VsCodeValidator(_FakeVsCode()); + final ValidationResult result = await validator.validate(); + expect(result.messages, contains(const ValidationMessage.error('Unable to determine VS Code version.'))); + expect(result.statusInfo, 'version unknown'); + }, overrides: <Type, Generator>{ + UserMessages: () => UserMessages(), + }); + }); +} + +class _FakeVsCode extends Fake implements VsCode { + @override + Iterable<ValidationMessage> get validationMessages => <ValidationMessage>[]; + + @override + String get productName => 'VS Code'; + + @override + Version? get version => null; } diff --git a/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart b/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart index f0c28e1107422..b2f3ad7056922 100644 --- a/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart +++ b/packages/flutter_tools/test/general.shard/web/bootstrap_test.dart @@ -62,6 +62,18 @@ void main() { expect(result, matches(regex), reason: 'require.config must have a waitSeconds: 0 config entry'); }); + test('generateMainModule hides requireJS injected by DDC', () { + final String result = generateMainModule( + entrypoint: 'foo/bar/main.js', + nullAssertions: false, + nativeNullAssertions: false, + ); + expect(result, contains('''define._amd = define.amd;'''), + reason: 'define.amd must be preserved as _amd so users may restore it if needed.'); + expect(result, contains('''delete define.amd;'''), + reason: "define.amd must be removed so packages don't attempt to use Dart's instance."); + }); + test('generateMainModule embeds urls correctly', () { final String result = generateMainModule( entrypoint: 'foo/bar/main.js', diff --git a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart index f65db99ee4057..e457408c05f08 100644 --- a/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart +++ b/packages/flutter_tools/test/general.shard/web/devfs_web_test.dart @@ -1211,11 +1211,9 @@ class FakeShaderCompiler implements DevelopmentShaderCompiler { } class FakeDwds extends Fake implements Dwds { - FakeDwds(this.connectedAppsIterable) : + FakeDwds(Iterable<AppConnection> connectedAppsIterable) : connectedApps = Stream<AppConnection>.fromIterable(connectedAppsIterable); - final Iterable<AppConnection> connectedAppsIterable; - @override final Stream<AppConnection> connectedApps; diff --git a/packages/flutter_tools/test/general.shard/web/devices_test.dart b/packages/flutter_tools/test/general.shard/web/devices_test.dart index c5c9b9d4c88ea..06f4d0cfc76b6 100644 --- a/packages/flutter_tools/test/general.shard/web/devices_test.dart +++ b/packages/flutter_tools/test/general.shard/web/devices_test.dart @@ -394,12 +394,6 @@ void main() { class TestChromiumLauncher implements ChromiumLauncher { TestChromiumLauncher(); - bool _hasInstance = false; - void setInstance(Chromium chromium) { - _hasInstance = true; - currentCompleter.complete(chromium); - } - @override Completer<Chromium> currentCompleter = Completer<Chromium>(); @@ -417,7 +411,7 @@ class TestChromiumLauncher implements ChromiumLauncher { } @override - bool get hasChromeInstance => _hasInstance; + bool get hasChromeInstance => false; @override Future<Chromium> launch( diff --git a/packages/flutter_tools/test/general.shard/windows/migrations/show_window_migration_test.dart b/packages/flutter_tools/test/general.shard/windows/migrations/show_window_migration_test.dart index 2f86e265fc65e..85db407f2e16d 100644 --- a/packages/flutter_tools/test/general.shard/windows/migrations/show_window_migration_test.dart +++ b/packages/flutter_tools/test/general.shard/windows/migrations/show_window_migration_test.dart @@ -75,8 +75,8 @@ void main () { ' });\n' '\n' ' // Flutter can complete the first frame before the "show window" callback is\n' - ' // registered. Ensure a frame is pending to ensure the window is shown.\n' - " // This no-ops if the first frame hasn't completed yet.\n" + ' // registered. The following call ensures a frame is pending to ensure the\n' + " // window is shown. It is a no-op if the first frame hasn't completed yet.\n" ' flutter_controller_->ForceRedraw();\n' '\n' ' return true;\n'; @@ -103,8 +103,8 @@ void main () { ' });\r\n' '\r\n' ' // Flutter can complete the first frame before the "show window" callback is\r\n' - ' // registered. Ensure a frame is pending to ensure the window is shown.\r\n' - " // This no-ops if the first frame hasn't completed yet.\r\n" + ' // registered. The following call ensures a frame is pending to ensure the\r\n' + " // window is shown. It is a no-op if the first frame hasn't completed yet.\r\n" ' flutter_controller_->ForceRedraw();\r\n' '\r\n' ' return true;\r\n'; @@ -145,8 +145,8 @@ void main () { ' });\n' '\n' ' // Flutter can complete the first frame before the "show window" callback is\n' - ' // registered. Ensure a frame is pending to ensure the window is shown.\n' - " // This no-ops if the first frame hasn't completed yet.\n" + ' // registered. The following call ensures a frame is pending to ensure the\n' + " // window is shown. It is a no-op if the first frame hasn't completed yet.\n" ' flutter_controller_->ForceRedraw();\n' '\n' ' return true;\n' @@ -176,8 +176,8 @@ void main () { ' });\r\n' '\r\n' ' // Flutter can complete the first frame before the "show window" callback is\r\n' - ' // registered. Ensure a frame is pending to ensure the window is shown.\r\n' - " // This no-ops if the first frame hasn't completed yet.\r\n" + ' // registered. The following call ensures a frame is pending to ensure the\r\n' + " // window is shown. It is a no-op if the first frame hasn't completed yet.\r\n" ' flutter_controller_->ForceRedraw();\r\n' '\r\n' ' return true;\r\n' diff --git a/packages/flutter_tools/test/general.shard/xcode_backend_test.dart b/packages/flutter_tools/test/general.shard/xcode_backend_test.dart index fe7bb292343ff..482e4ede33d37 100644 --- a/packages/flutter_tools/test/general.shard/xcode_backend_test.dart +++ b/packages/flutter_tools/test/general.shard/xcode_backend_test.dart @@ -234,11 +234,6 @@ class TestContext extends Context { String stdout = ''; String stderr = ''; - @override - bool existsDir(String path) { - return fileSystem.directory(path).existsSync(); - } - @override bool existsFile(String path) { return fileSystem.file(path).existsSync(); diff --git a/packages/flutter_tools/test/host_cross_arch.shard/cache_test.dart b/packages/flutter_tools/test/host_cross_arch.shard/cache_test.dart index 4cacee849cc49..1da650ab8675a 100644 --- a/packages/flutter_tools/test/host_cross_arch.shard/cache_test.dart +++ b/packages/flutter_tools/test/host_cross_arch.shard/cache_test.dart @@ -31,6 +31,12 @@ HostPlatform _identifyMacBinaryArch(String path) { final ProcessResult result = processManager.runSync( <String>['file', _dartBinary.path], ); + expect( + result, + ProcessResultMatcher( + stdoutPattern: '${_dartBinary.path}: Mach-O 64-bit executable', + ), + ); final RegExpMatch? match = pattern.firstMatch(result.stdout as String); if (match == null) { fail('Unrecognized STDOUT from `file`: "${result.stdout}"'); diff --git a/packages/flutter_tools/test/host_cross_arch.shard/ios_content_validation_test.dart b/packages/flutter_tools/test/host_cross_arch.shard/ios_content_validation_test.dart index 7894341635c06..3b3de5f7921ee 100644 --- a/packages/flutter_tools/test/host_cross_arch.shard/ios_content_validation_test.dart +++ b/packages/flutter_tools/test/host_cross_arch.shard/ios_content_validation_test.dart @@ -44,18 +44,22 @@ void main() { ); // Pre-cache iOS engine Flutter.xcframework artifacts. - processManager.runSync(<String>[ - flutterBin, - ...getLocalEngineArguments(), - 'precache', - '--ios', - ], workingDirectory: tempDir.path); + ProcessResult result = processManager.runSync( + <String>[ + flutterBin, + ...getLocalEngineArguments(), + 'precache', + '--ios', + ], + workingDirectory: tempDir.path, + ); + expect(result, const ProcessResultMatcher()); // Pretend the SDK was on an external drive with stray "._" files in the xcframework hiddenFile = xcframeworkArtifact.childFile('._Info.plist')..createSync(); // Test a plugin example app to allow plugins validation. - processManager.runSync(<String>[ + result = processManager.runSync(<String>[ flutterBin, ...getLocalEngineArguments(), 'create', @@ -65,6 +69,7 @@ void main() { 'plugin', 'hello', ], workingDirectory: tempDir.path); + expect(result, const ProcessResultMatcher()); pluginRoot = tempDir.childDirectory('hello'); projectRoot = pluginRoot.childDirectory('example').path; diff --git a/packages/flutter_tools/test/integration.shard/analyze_all_templates_test.dart b/packages/flutter_tools/test/integration.shard/analyze_all_templates_test.dart index d29c835123c30..fb1b04e3aaf3e 100644 --- a/packages/flutter_tools/test/integration.shard/analyze_all_templates_test.dart +++ b/packages/flutter_tools/test/integration.shard/analyze_all_templates_test.dart @@ -9,6 +9,7 @@ import 'package:flutter_tools/src/globals.dart' as globals; import '../src/common.dart'; import '../src/context.dart'; import '../src/test_flutter_command_runner.dart'; +import 'test_utils.dart'; void main() { group('pass analyze template:', () { @@ -38,7 +39,7 @@ void main() { final ProcessResult result = await globals.processManager .run(<String>['flutter', 'analyze'], workingDirectory: projectPath); - expect(result.exitCode, 0); + expect(result, const ProcessResultMatcher()); }); } }); diff --git a/packages/flutter_tools/test/integration.shard/analyze_once_test.dart b/packages/flutter_tools/test/integration.shard/analyze_once_test.dart index 7424bd4a48e4d..6c993dd0dae46 100644 --- a/packages/flutter_tools/test/integration.shard/analyze_once_test.dart +++ b/packages/flutter_tools/test/integration.shard/analyze_once_test.dart @@ -27,10 +27,7 @@ void main() { '--no-color', ...arguments, ], workingDirectory: projectPath); - printOnFailure('Output of flutter ${arguments.join(" ")}'); - printOnFailure(result.stdout.toString()); - printOnFailure(result.stderr.toString()); - expect(result.exitCode, exitCode, reason: 'Expected to exit with non-zero exit code.'); + expect(result, ProcessResultMatcher(exitCode: exitCode)); assertContains(result.stdout.toString(), statusTextContains); assertContains(result.stdout.toString(), errorTextContains); expect(result.stderr, contains(exitMessageContains)); diff --git a/packages/flutter_tools/test/integration.shard/analyze_size_test.dart b/packages/flutter_tools/test/integration.shard/analyze_size_test.dart index 62bf2d63d6559..13e85cb5e742b 100644 --- a/packages/flutter_tools/test/integration.shard/analyze_size_test.dart +++ b/packages/flutter_tools/test/integration.shard/analyze_size_test.dart @@ -30,10 +30,10 @@ void main() { '--target-platform=android-arm64', ], workingDirectory: workingDirectory); - printOnFailure('Output of flutter build apk:'); - printOnFailure(result.stdout.toString()); - printOnFailure(result.stderr.toString()); - expect(result.stdout.toString(), contains('app-release.apk (total compressed)')); + expect( + result, + const ProcessResultMatcher(stdoutPattern: 'app-release.apk (total compressed)'), + ); final String line = result.stdout.toString() .split('\n') @@ -49,8 +49,6 @@ void main() { final String commandArguments = devToolsCommand.split(runDevToolsMessage).last.trim(); final String relativeAppSizePath = outputFilePath.split('.flutter-devtools/').last.trim(); expect(commandArguments.contains('--appSizeBase=$relativeAppSizePath'), isTrue); - - expect(result.exitCode, 0); }); testWithoutContext('--analyze-size flag produces expected output on hello_world for iOS', () async { @@ -68,10 +66,10 @@ void main() { '--no-codesign', ], workingDirectory: workingDirectory); - printOnFailure('Output of flutter build ios:'); - printOnFailure(result.stdout.toString()); - printOnFailure(result.stderr.toString()); - expect(result.stdout.toString(), contains('Dart AOT symbols accounted decompressed size')); + expect( + result, + const ProcessResultMatcher(stdoutPattern: 'Dart AOT symbols accounted decompressed size'), + ); final String line = result.stdout.toString() .split('\n') @@ -88,7 +86,6 @@ void main() { expect(commandArguments.contains('--appSizeBase=$relativeAppSizePath'), isTrue); expect(codeSizeDir.existsSync(), true); - expect(result.exitCode, 0); tempDir.deleteSync(recursive: true); }, skip: !platform.isMacOS); // [intended] iOS can only be built on macos. @@ -105,6 +102,11 @@ void main() { '--enable-macos-desktop', ], workingDirectory: workingDirectory); + expect( + configResult, + const ProcessResultMatcher(), + ); + printOnFailure('Output of flutter config:'); printOnFailure(configResult.stdout.toString()); printOnFailure(configResult.stderr.toString()); @@ -117,10 +119,10 @@ void main() { '--code-size-directory=${codeSizeDir.path}', ], workingDirectory: workingDirectory); - printOnFailure('Output of flutter build macos:'); - printOnFailure(result.stdout.toString()); - printOnFailure(result.stderr.toString()); - expect(result.stdout.toString(), contains('Dart AOT symbols accounted decompressed size')); + expect( + result, + const ProcessResultMatcher(stdoutPattern: 'Dart AOT symbols accounted decompressed size'), + ); final String line = result.stdout.toString() .split('\n') @@ -137,7 +139,6 @@ void main() { expect(commandArguments.contains('--appSizeBase=$relativeAppSizePath'), isTrue); expect(codeSizeDir.existsSync(), true); - expect(result.exitCode, 0); tempDir.deleteSync(recursive: true); }, skip: !platform.isMacOS); // [intended] this is a macos only test. @@ -152,13 +153,13 @@ void main() { '--target-platform=android-arm64', '--debug', ], workingDirectory: fileSystem.path.join(getFlutterRoot(), 'examples', 'hello_world')); - - printOnFailure('Output of flutter build apk:'); - printOnFailure(result.stdout.toString()); - printOnFailure(result.stderr.toString()); - expect(result.stderr.toString(), contains('"--analyze-size" can only be used on release builds')); - - expect(result.exitCode, 1); + expect( + result, + const ProcessResultMatcher( + exitCode: 1, + stderrPattern: '"--analyze-size" can only be used on release builds', + ), + ); }); testWithoutContext('--analyze-size is not supported in combination with --split-debug-info', () async { @@ -177,14 +178,13 @@ void main() { final ProcessResult result = await processManager.run(command, workingDirectory: workingDirectory); - printOnFailure('workingDirectory: $workingDirectory'); - printOnFailure('command:\n${command.join(" ")}'); - printOnFailure('stdout:\n${result.stdout}'); - printOnFailure('stderr:\n${result.stderr}'); - - expect(result.stderr.toString(), contains('"--analyze-size" cannot be combined with "--split-debug-info"')); - - expect(result.exitCode, 1); + expect( + result, + const ProcessResultMatcher( + exitCode: 1, + stderrPattern: '"--analyze-size" cannot be combined with "--split-debug-info"', + ), + ); }); testWithoutContext('--analyze-size allows overriding the directory for code size files', () async { @@ -211,15 +211,14 @@ void main() { workingDirectory: workingDirectory, ); - printOnFailure('workingDirectory: $workingDirectory'); - printOnFailure('command:\n${command.join(" ")}'); - printOnFailure('stdout:\n${result.stdout}'); - printOnFailure('stderr:\n${result.stderr}'); + expect( + result, + const ProcessResultMatcher(), + ); - expect(result.exitCode, 0); - expect(tempDir.existsSync(), true); - expect(tempDir.childFile('snapshot.arm64-v8a.json').existsSync(), true); - expect(tempDir.childFile('trace.arm64-v8a.json').existsSync(), true); + expect(tempDir, exists); + expect(tempDir.childFile('snapshot.arm64-v8a.json'), exists); + expect(tempDir.childFile('trace.arm64-v8a.json'), exists); tempDir.deleteSync(recursive: true); }); diff --git a/packages/flutter_tools/test/integration.shard/analyze_suggestions_integration_test.dart b/packages/flutter_tools/test/integration.shard/analyze_suggestions_integration_test.dart index c4c37ab48a55b..bac0d2f80c71b 100644 --- a/packages/flutter_tools/test/integration.shard/analyze_suggestions_integration_test.dart +++ b/packages/flutter_tools/test/integration.shard/analyze_suggestions_integration_test.dart @@ -158,7 +158,6 @@ void main() { expect(decoded['FlutterProject.isModule'], false); expect(decoded['FlutterProject.isPlugin'], false); expect(decoded['FlutterProject.manifest.appname'], 'test_project'); - expect(decoded['FlutterVersion.frameworkRevision'], ''); expect(decoded['Platform.isAndroid'], false); expect(decoded['Platform.isIOS'], false); diff --git a/packages/flutter_tools/test/integration.shard/android_e2e_api_test.dart b/packages/flutter_tools/test/integration.shard/android_e2e_api_test.dart index a409a96fb3488..73d11c688b147 100644 --- a/packages/flutter_tools/test/integration.shard/android_e2e_api_test.dart +++ b/packages/flutter_tools/test/integration.shard/android_e2e_api_test.dart @@ -27,7 +27,7 @@ void main() { tempDir.path, '--project-name=testapp', ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); + expect(result, const ProcessResultMatcher()); final File api33File = tempDir .childDirectory('android') @@ -68,7 +68,6 @@ public final class Android33Api extends Activity { 'build', 'apk', ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); - expect(result.stdout.toString(), contains('app-release.apk')); + expect(result, const ProcessResultMatcher(stdoutPattern: 'app-release.apk')); }); } diff --git a/packages/flutter_tools/test/integration.shard/android_gradle_java_version_test.dart b/packages/flutter_tools/test/integration.shard/android_gradle_java_version_test.dart index cfc9cef09ac14..6dea44867adb5 100644 --- a/packages/flutter_tools/test/integration.shard/android_gradle_java_version_test.dart +++ b/packages/flutter_tools/test/integration.shard/android_gradle_java_version_test.dart @@ -32,7 +32,7 @@ void main() { tempDir.path, '--project-name=testapp', ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); + expect(result, const ProcessResultMatcher()); // Ensure that gradle files exists from templates. result = await processManager.run(<String>[ flutterBin, @@ -40,7 +40,7 @@ void main() { 'apk', '--config-only', ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); + expect(result, const ProcessResultMatcher()); final Directory androidApp = tempDir.childDirectory('android'); result = await processManager.run(<String>[ @@ -50,7 +50,7 @@ void main() { 'javaVersion', ], workingDirectory: androidApp.path); // Verify that gradlew has a javaVersion task. - expect(result.exitCode, 0); + expect(result, const ProcessResultMatcher()); // Verify the format is a number on its own line. expect(result.stdout.toString(), matches(RegExp(r'\d+$', multiLine: true))); }); diff --git a/packages/flutter_tools/test/integration.shard/android_gradle_print_app_link_domains_test.dart b/packages/flutter_tools/test/integration.shard/android_gradle_print_app_link_domains_test.dart new file mode 100644 index 0000000000000..eed136cc72c0c --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/android_gradle_print_app_link_domains_test.dart @@ -0,0 +1,189 @@ +// 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:convert'; +import 'dart:io' as io; + +import 'package:collection/collection.dart'; +import 'package:file/file.dart'; +import 'package:flutter_tools/src/android/gradle_utils.dart' + show getGradlewFileName; +import 'package:flutter_tools/src/base/io.dart'; +import 'package:xml/xml.dart'; + +import '../src/common.dart'; +import 'test_utils.dart'; + +final XmlElement pureHttpIntentFilter = XmlElement( + XmlName('intent-filter'), + <XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')], + <XmlElement>[ + XmlElement( + XmlName('action'), + <XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW')], + ), + XmlElement( + XmlName('category'), + <XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT')], + ), + XmlElement( + XmlName('category'), + <XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE')], + ), + XmlElement( + XmlName('data'), + <XmlAttribute>[ + XmlAttribute(XmlName('scheme', 'android'), 'http'), + XmlAttribute(XmlName('host', 'android'), 'pure-http.com'), + ], + ), + ], +); + +final XmlElement nonHttpIntentFilter = XmlElement( + XmlName('intent-filter'), + <XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')], + <XmlElement>[ + XmlElement( + XmlName('action'), + <XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW')], + ), + XmlElement( + XmlName('category'), + <XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT')], + ), + XmlElement( + XmlName('category'), + <XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE')], + ), + XmlElement( + XmlName('data'), + <XmlAttribute>[ + XmlAttribute(XmlName('scheme', 'android'), 'custom'), + XmlAttribute(XmlName('host', 'android'), 'custom.com'), + ], + ), + ], +); + +final XmlElement hybridIntentFilter = XmlElement( + XmlName('intent-filter'), + <XmlAttribute>[XmlAttribute(XmlName('autoVerify', 'android'), 'true')], + <XmlElement>[ + XmlElement( + XmlName('action'), + <XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW')], + ), + XmlElement( + XmlName('category'), + <XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT')], + ), + XmlElement( + XmlName('category'), + <XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE')], + ), + XmlElement( + XmlName('data'), + <XmlAttribute>[ + XmlAttribute(XmlName('scheme', 'android'), 'custom'), + XmlAttribute(XmlName('host', 'android'), 'hybrid.com'), + ], + ), + XmlElement( + XmlName('data'), + <XmlAttribute>[ + XmlAttribute(XmlName('scheme', 'android'), 'http'), + ], + ), + ], +); + +final XmlElement nonAutoVerifyIntentFilter = XmlElement( + XmlName('intent-filter'), + <XmlAttribute>[], + <XmlElement>[ + XmlElement( + XmlName('action'), + <XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.action.VIEW')], + ), + XmlElement( + XmlName('category'), + <XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.DEFAULT')], + ), + XmlElement( + XmlName('category'), + <XmlAttribute>[XmlAttribute(XmlName('name', 'android'), 'android.intent.category.BROWSABLE')], + ), + XmlElement( + XmlName('data'), + <XmlAttribute>[ + XmlAttribute(XmlName('scheme', 'android'), 'http'), + XmlAttribute(XmlName('host', 'android'), 'non-auto-verify.com'), + ], + ), + ], +); + +void main() { + late Directory tempDir; + + setUp(() async { + tempDir = createResolvedTempDirectorySync('run_test.'); + }); + + tearDown(() async { + tryToDelete(tempDir); + }); + + testWithoutContext( + 'gradle task exists named print<mode>AppLinkDomains that prints app link domains', () async { + // Create a new flutter project. + final String flutterBin = + fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter'); + ProcessResult result = await processManager.run(<String>[ + flutterBin, + 'create', + tempDir.path, + '--project-name=testapp', + ], workingDirectory: tempDir.path); + expect(result.exitCode, 0); + // Adds intent filters for app links + final String androidManifestPath = fileSystem.path.join(tempDir.path, 'android', 'app', 'src', 'main', 'AndroidManifest.xml'); + final io.File androidManifestFile = io.File(androidManifestPath); + final XmlDocument androidManifest = XmlDocument.parse(androidManifestFile.readAsStringSync()); + final XmlElement activity = androidManifest.findAllElements('activity').first; + activity.children.add(pureHttpIntentFilter); + activity.children.add(nonHttpIntentFilter); + activity.children.add(hybridIntentFilter); + activity.children.add(nonAutoVerifyIntentFilter); + androidManifestFile.writeAsStringSync(androidManifest.toString(), flush: true); + + // Ensure that gradle files exists from templates. + result = await processManager.run(<String>[ + flutterBin, + 'build', + 'apk', + '--config-only', + ], workingDirectory: tempDir.path); + expect(result.exitCode, 0); + + final Directory androidApp = tempDir.childDirectory('android'); + result = await processManager.run(<String>[ + '.${platform.pathSeparator}${getGradlewFileName(platform)}', + ...getLocalEngineArguments(), + '-q', // quiet output. + 'printDebugAppLinkDomains', + ], workingDirectory: androidApp.path); + + expect(result.exitCode, 0); + + const List<String> expectedLines = <String>[ + // Should only pick up the pure and hybrid intent filters + 'Domain: pure-http.com', + 'Domain: hybrid.com', + ]; + final List<String> actualLines = LineSplitter.split(result.stdout.toString()).toList(); + expect(const ListEquality<String>().equals(actualLines, expectedLines), isTrue); + }); +} diff --git a/packages/flutter_tools/test/integration.shard/android_gradle_print_application_id_test.dart b/packages/flutter_tools/test/integration.shard/android_gradle_print_application_id_test.dart new file mode 100644 index 0000000000000..563344660e067 --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/android_gradle_print_application_id_test.dart @@ -0,0 +1,64 @@ +// 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:convert'; + +import 'package:collection/collection.dart'; +import 'package:file/file.dart'; +import 'package:flutter_tools/src/android/gradle_utils.dart' + show getGradlewFileName; +import 'package:flutter_tools/src/base/io.dart'; + +import '../src/common.dart'; +import 'test_utils.dart'; + +void main() { + late Directory tempDir; + + setUp(() async { + tempDir = createResolvedTempDirectorySync('run_test.'); + }); + + tearDown(() async { + tryToDelete(tempDir); + }); + + testWithoutContext( + 'gradle task exists named print<mode>ApplicationId that prints application id', () async { + // Create a new flutter project. + final String flutterBin = + fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter'); + ProcessResult result = await processManager.run(<String>[ + flutterBin, + 'create', + tempDir.path, + '--project-name=testapp', + ], workingDirectory: tempDir.path); + expect(result.exitCode, 0); + // Ensure that gradle files exists from templates. + result = await processManager.run(<String>[ + flutterBin, + 'build', + 'apk', + '--config-only', + ], workingDirectory: tempDir.path); + expect(result.exitCode, 0); + + final Directory androidApp = tempDir.childDirectory('android'); + result = await processManager.run(<String>[ + '.${platform.pathSeparator}${getGradlewFileName(platform)}', + ...getLocalEngineArguments(), + '-q', // quiet output. + 'printDebugApplicationId', + ], workingDirectory: androidApp.path); + // Verify that gradlew has a javaVersion task. + expect(result.exitCode, 0); + // Verify the format is a number on its own line. + const List<String> expectedLines = <String>[ + 'ApplicationId: com.example.testapp', + ]; + final List<String> actualLines = LineSplitter.split(result.stdout.toString()).toList(); + expect(const ListEquality<String>().equals(actualLines, expectedLines), isTrue); + }); +} diff --git a/packages/flutter_tools/test/integration.shard/build_ios_config_only_test.dart b/packages/flutter_tools/test/integration.shard/build_ios_config_only_test.dart index 21d7141da860a..86adcfba17fa1 100644 --- a/packages/flutter_tools/test/integration.shard/build_ios_config_only_test.dart +++ b/packages/flutter_tools/test/integration.shard/build_ios_config_only_test.dart @@ -37,13 +37,7 @@ void main() { ]; final ProcessResult firstRunResult = await processManager.run(buildCommand, workingDirectory: workingDirectory); - printOnFailure('Output of flutter build ios:'); - final String firstRunStdout = firstRunResult.stdout.toString(); - printOnFailure('First run stdout: $firstRunStdout'); - printOnFailure('First run stderr: ${firstRunResult.stderr}'); - - expect(firstRunResult.exitCode, 0); - expect(firstRunStdout, contains('Running pod install')); + expect(firstRunResult, const ProcessResultMatcher(stdoutPattern: 'Running pod install')); final File generatedConfig = fileSystem.file(fileSystem.path.join( workingDirectory, @@ -71,10 +65,8 @@ void main() { // Run again with no changes. final ProcessResult secondRunResult = await processManager.run(buildCommand, workingDirectory: workingDirectory); final String secondRunStdout = secondRunResult.stdout.toString(); - printOnFailure('Second run stdout: $secondRunStdout'); - printOnFailure('Second run stderr: ${secondRunResult.stderr}'); - expect(secondRunResult.exitCode, 0); + expect(secondRunResult, const ProcessResultMatcher()); // Do not run "pod install" when nothing changes. expect(secondRunStdout, isNot(contains('pod install'))); }, skip: !platform.isMacOS); // [intended] iOS builds only work on macos. diff --git a/packages/flutter_tools/test/integration.shard/debug_adapter/flutter_adapter_test.dart b/packages/flutter_tools/test/integration.shard/debug_adapter/flutter_adapter_test.dart index c27b7c295a937..a717a6dfdb72d 100644 --- a/packages/flutter_tools/test/integration.shard/debug_adapter/flutter_adapter_test.dart +++ b/packages/flutter_tools/test/integration.shard/debug_adapter/flutter_adapter_test.dart @@ -5,7 +5,6 @@ import 'dart:async'; import 'package:dds/dap.dart'; -import 'package:dds/src/dap/protocol_generated.dart'; import 'package:file/file.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/convert.dart'; @@ -257,6 +256,24 @@ void main() { allowExtras: true, ); + // Repeat the test for hot reload with custom syntax. + final Future<List<String>> customOutputEventsFuture = dap.client.stdoutOutput + // But skip any topLevelFunctions that come before the reload. + .skipWhile((String output) => output.startsWith('topLevelFunction')) + .take(2) + .toList(); + + await dap.client.customSyntaxHotReload(); + + expectLines( + (await customOutputEventsFuture).join(), + <Object>[ + startsWith('Reloaded'), + 'topLevelFunction', + ], + allowExtras: true, + ); + await dap.client.terminate(); }); diff --git a/packages/flutter_tools/test/integration.shard/debug_adapter/test_adapter_test.dart b/packages/flutter_tools/test/integration.shard/debug_adapter/test_adapter_test.dart index cc16010c7023a..00d9c92f0edf6 100644 --- a/packages/flutter_tools/test/integration.shard/debug_adapter/test_adapter_test.dart +++ b/packages/flutter_tools/test/integration.shard/debug_adapter/test_adapter_test.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:dds/src/dap/protocol_generated.dart'; +import 'package:dds/dap.dart'; import 'package:file/file.dart'; import 'package:flutter_tools/src/cache.dart'; diff --git a/packages/flutter_tools/test/integration.shard/debug_adapter/test_client.dart b/packages/flutter_tools/test/integration.shard/debug_adapter/test_client.dart index f691fb5eff1e5..8857d4dd2dfb2 100644 --- a/packages/flutter_tools/test/integration.shard/debug_adapter/test_client.dart +++ b/packages/flutter_tools/test/integration.shard/debug_adapter/test_client.dart @@ -4,9 +4,7 @@ import 'dart:async'; -import 'package:dds/src/dap/logging.dart'; -import 'package:dds/src/dap/protocol_generated.dart'; -import 'package:dds/src/dap/protocol_stream.dart'; +import 'package:dds/dap.dart'; import 'package:flutter_tools/src/debug_adapters/flutter_adapter_args.dart'; import 'test_server.dart'; @@ -113,6 +111,11 @@ class DapTestClient { return custom('hotReload'); } + /// Sends a custom request with custom syntax convention to the debug adapter to trigger a Hot Reload. + Future<Response> customSyntaxHotReload() { + return custom(r'$/hotReload'); + } + /// Sends a custom request to the debug adapter to trigger a Hot Restart. Future<Response> hotRestart() { return custom('hotRestart'); diff --git a/packages/flutter_tools/test/integration.shard/devtools_uri_test.dart b/packages/flutter_tools/test/integration.shard/devtools_uri_test.dart new file mode 100644 index 0000000000000..af15861554ed7 --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/devtools_uri_test.dart @@ -0,0 +1,53 @@ +// 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_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/convert.dart'; + +import '../src/common.dart'; +import 'test_data/basic_project.dart'; +import 'test_utils.dart'; + +void main() { + late Directory tempDir; + final BasicProject project = BasicProject(); + + setUp(() async { + tempDir = createResolvedTempDirectorySync('run_test.'); + await project.setUpIn(tempDir); + }); + + tearDown(() async { + tryToDelete(tempDir); + }); + + // Regression test for https://github.com/flutter/flutter/issues/126691 + testWithoutContext('flutter run --start-paused prints DevTools URI', () async { + final Completer<void> completer = Completer<void>(); + const String matcher = 'The Flutter DevTools debugger and profiler on'; + + final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter'); + final Process process = await processManager.start(<String>[ + flutterBin, + 'run', + '--start-paused', + '-d', + 'flutter-tester', + ], workingDirectory: tempDir.path); + + final StreamSubscription<String> sub; + sub = process.stdout.transform(utf8.decoder).listen((String message) { + if (message.contains(matcher)) { + completer.complete(); + } + }); + await completer.future; + await sub.cancel(); + process.kill(); + await process.exitCode; + }); +} diff --git a/packages/flutter_tools/test/integration.shard/downgrade_upgrade_integration_test.dart b/packages/flutter_tools/test/integration.shard/downgrade_upgrade_integration_test.dart index 26ac8752cc1cf..20b7675b78a51 100644 --- a/packages/flutter_tools/test/integration.shard/downgrade_upgrade_integration_test.dart +++ b/packages/flutter_tools/test/integration.shard/downgrade_upgrade_integration_test.dart @@ -15,14 +15,14 @@ const String _kInitialVersion = '3.0.0'; const String _kBranch = 'beta'; final Stdio stdio = Stdio(); -final ProcessUtils processUtils = ProcessUtils(processManager: processManager, logger: StdoutLogger( +final BufferLogger logger = BufferLogger.test( terminal: AnsiTerminal( platform: platform, stdio: stdio, ), - stdio: stdio, outputPreferences: OutputPreferences.test(wrapText: true), -)); +); +final ProcessUtils processUtils = ProcessUtils(processManager: processManager, logger: logger); final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', platform.isWindows ? 'flutter.bat' : 'flutter'); /// A test for flutter upgrade & downgrade that checks out a parallel flutter repo. @@ -43,20 +43,34 @@ void main() { final Directory testDirectory = parentDirectory.childDirectory('flutter'); testDirectory.createSync(recursive: true); - int exitCode = 0; - // Enable longpaths for windows integration test. await processManager.run(<String>[ 'git', 'config', '--system', 'core.longpaths', 'true', ]); + void checkExitCode(int code) { + expect( + exitCode, + 0, + reason: ''' +trace: +${logger.traceText} + +status: +${logger.statusText} + +error: +${logger.errorText}''', + ); + } + printOnFailure('Step 1 - clone the $_kBranch of flutter into the test directory'); exitCode = await processUtils.stream(<String>[ 'git', 'clone', 'https://github.com/flutter/flutter.git', ], workingDirectory: parentDirectory.path, trace: true); - expect(exitCode, 0); + checkExitCode(exitCode); printOnFailure('Step 2 - switch to the $_kBranch'); exitCode = await processUtils.stream(<String>[ @@ -67,7 +81,7 @@ void main() { _kBranch, 'origin/$_kBranch', ], workingDirectory: testDirectory.path, trace: true); - expect(exitCode, 0); + checkExitCode(exitCode); printOnFailure('Step 3 - revert back to $_kInitialVersion'); exitCode = await processUtils.stream(<String>[ @@ -76,7 +90,7 @@ void main() { '--hard', _kInitialVersion, ], workingDirectory: testDirectory.path, trace: true); - expect(exitCode, 0); + checkExitCode(exitCode); printOnFailure('Step 4 - upgrade to the newest $_kBranch'); // This should update the persistent tool state with the sha for HEAD @@ -86,8 +100,10 @@ void main() { 'upgrade', '--verbose', '--working-directory=${testDirectory.path}', - ], workingDirectory: testDirectory.path, trace: true); - expect(exitCode, 0); + // we intentionally run this in a directory outside the test repo to + // verify the tool overrides the working directory when invoking git + ], workingDirectory: parentDirectory.path, trace: true); + checkExitCode(exitCode); printOnFailure('Step 5 - verify that the version is different'); final RunResult versionResult = await processUtils.run(<String>[ @@ -107,8 +123,8 @@ void main() { 'downgrade', '--no-prompt', '--working-directory=${testDirectory.path}', - ], workingDirectory: testDirectory.path, trace: true); - expect(exitCode, 0); + ], workingDirectory: parentDirectory.path, trace: true); + checkExitCode(exitCode); printOnFailure('Step 7 - verify downgraded version matches original version'); final RunResult oldVersionResult = await processUtils.run(<String>[ diff --git a/packages/flutter_tools/test/integration.shard/exit_code_test.dart b/packages/flutter_tools/test/integration.shard/exit_code_test.dart index 88d07b3930ced..e919a6efc0d75 100644 --- a/packages/flutter_tools/test/integration.shard/exit_code_test.dart +++ b/packages/flutter_tools/test/integration.shard/exit_code_test.dart @@ -34,10 +34,7 @@ void main() { fileSystem.path.join(tempDir.path, 'main.dart'), ]); - printOnFailure('Output of dart main.dart:'); - printOnFailure(result.stdout.toString()); - printOnFailure(result.stderr.toString()); - expect(result.exitCode, 0); + expect(result, const ProcessResultMatcher()); }); testWithoutContext('dart.sh/bat can return a non-zero exit code', () async { @@ -54,9 +51,6 @@ void main() { fileSystem.path.join(tempDir.path, 'main.dart'), ]); - printOnFailure('Output of dart main.dart:'); - printOnFailure(result.stdout.toString()); - printOnFailure(result.stderr.toString()); - expect(result.exitCode, 1); + expect(result, const ProcessResultMatcher(exitCode: 1)); }); } diff --git a/packages/flutter_tools/test/integration.shard/flutter_build_config_only_test.dart b/packages/flutter_tools/test/integration.shard/flutter_build_config_only_test.dart index 55d793056b0e6..92f38142534f2 100644 --- a/packages/flutter_tools/test/integration.shard/flutter_build_config_only_test.dart +++ b/packages/flutter_tools/test/integration.shard/flutter_build_config_only_test.dart @@ -24,14 +24,18 @@ void main() { ); exampleAppDir = tempDir.childDirectory('bbb').childDirectory('example'); - processManager.runSync(<String>[ - flutterBin, - ...getLocalEngineArguments(), - 'create', - '--template=plugin', - '--platforms=android', - 'bbb', - ], workingDirectory: tempDir.path); + processManager.runSync( + <String>[ + flutterBin, + ...getLocalEngineArguments(), + 'create', + '--template=plugin', + '--platforms=android', + 'bbb', + '-v', + ], + workingDirectory: tempDir.path, + ); }); tearDown(() async { @@ -48,14 +52,17 @@ void main() { // Ensure file is gone prior to configOnly running. await gradleFile.delete(); - final ProcessResult result = processManager.runSync(<String>[ - flutterBin, - ...getLocalEngineArguments(), - 'build', - 'apk', - '--target-platform=android-arm', - '--config-only', - ], workingDirectory: exampleAppDir.path); + final ProcessResult result = processManager.runSync( + <String>[ + flutterBin, + ...getLocalEngineArguments(), + 'build', + 'apk', + '--target-platform=android-arm', + '--config-only', + ], + workingDirectory: exampleAppDir.path, + ); expect(gradleFile, exists); expect(result.stdout, contains(RegExp(r'Config complete'))); diff --git a/packages/flutter_tools/test/integration.shard/flutter_build_wasm_test.dart b/packages/flutter_tools/test/integration.shard/flutter_build_wasm_test.dart index 593aeb3c1498b..4290db33756a5 100644 --- a/packages/flutter_tools/test/integration.shard/flutter_build_wasm_test.dart +++ b/packages/flutter_tools/test/integration.shard/flutter_build_wasm_test.dart @@ -46,7 +46,7 @@ void main() { flutterWebWasm.environmentOverride!: 'true' }, ); - expect(result.exitCode, 0); + expect(result, const ProcessResultMatcher()); final Directory appBuildDir = fileSystem.directory(fileSystem.path.join( exampleAppDir.path, diff --git a/packages/flutter_tools/test/integration.shard/flutter_build_windows_test.dart b/packages/flutter_tools/test/integration.shard/flutter_build_windows_test.dart index 8311419c46999..c90a940e92d5d 100644 --- a/packages/flutter_tools/test/integration.shard/flutter_build_windows_test.dart +++ b/packages/flutter_tools/test/integration.shard/flutter_build_windows_test.dart @@ -26,16 +26,18 @@ void main() { 'bin', 'flutter', ); - processManager.runSync(<String>[flutterBin, 'config', + ProcessResult result = processManager.runSync(<String>[flutterBin, 'config', '--enable-windows-desktop', ]); + expect(result, const ProcessResultMatcher()); - processManager.runSync(<String>[ + result = processManager.runSync(<String>[ flutterBin, ...getLocalEngineArguments(), 'create', 'hello', ], workingDirectory: tempDir.path); + expect(result, const ProcessResultMatcher()); projectRoot = tempDir.childDirectory('hello'); @@ -65,8 +67,8 @@ void main() { 'windows', '--no-pub', ], workingDirectory: projectRoot.path); + expect(result, const ProcessResultMatcher()); - expect(result.exitCode, 0); expect(releaseDir, exists); expect(exeFile, exists); @@ -79,7 +81,7 @@ void main() { }); testWithoutContext('flutter build windows sets build name', () { - processManager.runSync(<String>[ + final ProcessResult result = processManager.runSync(<String>[ flutterBin, ...getLocalEngineArguments(), 'build', @@ -88,6 +90,7 @@ void main() { '--build-name', '1.2.3', ], workingDirectory: projectRoot.path); + expect(result, const ProcessResultMatcher()); final String fileVersion = _getFileVersion(exeFile); final String productVersion = _getProductVersion(exeFile); @@ -97,7 +100,7 @@ void main() { }); testWithoutContext('flutter build windows sets build name and build number', () { - processManager.runSync(<String>[ + final ProcessResult result = processManager.runSync(<String>[ flutterBin, ...getLocalEngineArguments(), 'build', @@ -108,6 +111,7 @@ void main() { '--build-number', '4', ], workingDirectory: projectRoot.path); + expect(result, const ProcessResultMatcher()); final String fileVersion = _getFileVersion(exeFile); final String productVersion = _getProductVersion(exeFile); @@ -129,9 +133,7 @@ String _getFileVersion(File file) { <String>[] ); - if (result.exitCode != 0) { - throw Exception('GetVersionInfo failed.'); - } + expect(result, const ProcessResultMatcher()); // Trim trailing new line. final String output = result.stdout as String; @@ -144,9 +146,7 @@ String _getProductVersion(File file) { <String>[] ); - if (result.exitCode != 0) { - throw Exception('GetVersionInfo failed.'); - } + expect(result, const ProcessResultMatcher()); // Trim trailing new line. final String output = result.stdout as String; diff --git a/packages/flutter_tools/test/integration.shard/flutter_build_with_compilation_error_test.dart b/packages/flutter_tools/test/integration.shard/flutter_build_with_compilation_error_test.dart index fa997263b5fb9..36d15e90859ec 100644 --- a/packages/flutter_tools/test/integration.shard/flutter_build_with_compilation_error_test.dart +++ b/packages/flutter_tools/test/integration.shard/flutter_build_with_compilation_error_test.dart @@ -64,11 +64,13 @@ int x = 'String'; ], workingDirectory: projectRoot.path); expect( - result.stderr, - contains("A value of type 'String' can't be assigned to a variable of type 'int'."), + result, + const ProcessResultMatcher( + exitCode: 1, + stderrPattern: "A value of type 'String' can't be assigned to a variable of type 'int'.", + ), ); expect(result.stderr, isNot(contains("Warning: The 'dart2js' entrypoint script is deprecated"))); - expect(result.exitCode, 1); }); } } diff --git a/packages/flutter_tools/test/integration.shard/gen_l10n_test.dart b/packages/flutter_tools/test/integration.shard/gen_l10n_test.dart index 6827b3814a17b..13be650bc83ec 100644 --- a/packages/flutter_tools/test/integration.shard/gen_l10n_test.dart +++ b/packages/flutter_tools/test/integration.shard/gen_l10n_test.dart @@ -126,46 +126,49 @@ void main() { '#l10n 73 (he)\n' '#l10n 74 (they)\n' '#l10n 75 (she)\n' - '#l10n 76 (--- es ---)\n' - '#l10n 77 (ES - Hello world)\n' - '#l10n 78 (ES - Hello _NEWLINE_ World)\n' - '#l10n 79 (ES - Hola \$ Mundo)\n' - '#l10n 80 (ES - Hello Mundo)\n' - '#l10n 81 (ES - Hola Mundo)\n' - '#l10n 82 (ES - Hello World on viernes, 1 de enero de 1960)\n' - '#l10n 83 (ES - Hello world argument on 1/1/1960 at 0:00)\n' - '#l10n 84 (ES - Hello World from 1960 to 2020)\n' - '#l10n 85 (ES - Hello for 123)\n' - '#l10n 86 (ES - Hello)\n' - '#l10n 87 (ES - Hello World)\n' - '#l10n 88 (ES - Hello two worlds)\n' - '#l10n 89 (ES - Hello)\n' - '#l10n 90 (ES - Hello nuevo World)\n' - '#l10n 91 (ES - Hello two nuevo worlds)\n' - '#l10n 92 (ES - Hello on viernes, 1 de enero de 1960)\n' - '#l10n 93 (ES - Hello World, on viernes, 1 de enero de 1960)\n' - '#l10n 94 (ES - Hello two worlds, on viernes, 1 de enero de 1960)\n' - '#l10n 95 (ES - Hello other 0 worlds, with a total of 100 citizens)\n' - '#l10n 96 (ES - Hello World of 101 citizens)\n' - '#l10n 97 (ES - Hello two worlds with 102 total citizens)\n' - '#l10n 98 (ES - [Hola] -Mundo- #123#)\n' - '#l10n 99 (ES - \$!)\n' - '#l10n 100 (ES - One \$)\n' - "#l10n 101 (ES - Flutter's amazing!)\n" - "#l10n 102 (ES - Flutter's amazing, times 2!)\n" - '#l10n 103 (ES - Flutter is "amazing"!)\n' - '#l10n 104 (ES - Flutter is "amazing", times 2!)\n' - '#l10n 105 (ES - 16 wheel truck)\n' - "#l10n 106 (ES - Sedan's elegance)\n" - '#l10n 107 (ES - Cabriolet has "acceleration")\n' - '#l10n 108 (ES - Oh, she found ES - 1 itemES - !)\n' - '#l10n 109 (ES - Indeed, ES - they like ES - Flutter!)\n' - '#l10n 110 (--- es_419 ---)\n' - '#l10n 111 (ES 419 - Hello World)\n' - '#l10n 112 (ES 419 - Hello)\n' + '#l10n 76 (6/26/2023)\n' + '#l10n 77 (5:23:00 AM)\n' + '#l10n 78 (--- es ---)\n' + '#l10n 79 (ES - Hello world)\n' + '#l10n 80 (ES - Hello _NEWLINE_ World)\n' + '#l10n 81 (ES - Hola \$ Mundo)\n' + '#l10n 82 (ES - Hello Mundo)\n' + '#l10n 83 (ES - Hola Mundo)\n' + '#l10n 84 (ES - Hello World on viernes, 1 de enero de 1960)\n' + '#l10n 85 (ES - Hello world argument on 1/1/1960 at 0:00)\n' + '#l10n 86 (ES - Hello World from 1960 to 2020)\n' + '#l10n 87 (ES - Hello for 123)\n' + '#l10n 88 (ES - Hello)\n' + '#l10n 89 (ES - Hello World)\n' + '#l10n 90 (ES - Hello two worlds)\n' + '#l10n 91 (ES - Hello)\n' + '#l10n 92 (ES - Hello nuevo World)\n' + '#l10n 93 (ES - Hello two nuevo worlds)\n' + '#l10n 94 (ES - Hello on viernes, 1 de enero de 1960)\n' + '#l10n 95 (ES - Hello World, on viernes, 1 de enero de 1960)\n' + '#l10n 96 (ES - Hello two worlds, on viernes, 1 de enero de 1960)\n' + '#l10n 97 (ES - Hello other 0 worlds, with a total of 100 citizens)\n' + '#l10n 98 (ES - Hello World of 101 citizens)\n' + '#l10n 99 (ES - Hello two worlds with 102 total citizens)\n' + '#l10n 100 (ES - [Hola] -Mundo- #123#)\n' + '#l10n 101 (ES - \$!)\n' + '#l10n 102 (ES - One \$)\n' + "#l10n 103 (ES - Flutter's amazing!)\n" + "#l10n 104 (ES - Flutter's amazing, times 2!)\n" + '#l10n 105 (ES - Flutter is "amazing"!)\n' + '#l10n 106 (ES - Flutter is "amazing", times 2!)\n' + '#l10n 107 (ES - 16 wheel truck)\n' + "#l10n 108 (ES - Sedan's elegance)\n" + '#l10n 109 (ES - Cabriolet has "acceleration")\n' + '#l10n 110 (ES - Oh, she found ES - 1 itemES - !)\n' + '#l10n 111 (ES - Indeed, ES - they like ES - Flutter!)\n' + '#l10n 112 (--- es_419 ---)\n' '#l10n 113 (ES 419 - Hello World)\n' - '#l10n 114 (ES 419 - Hello two worlds)\n' + '#l10n 114 (ES 419 - Hello)\n' + '#l10n 115 (ES 419 - Hello World)\n' + '#l10n 116 (ES 419 - Hello two worlds)\n' '#l10n END\n' + ); } diff --git a/packages/flutter_tools/test/integration.shard/gradle_non_android_plugin_test.dart b/packages/flutter_tools/test/integration.shard/gradle_non_android_plugin_test.dart index 3703ed600fbd4..59899ce826a05 100644 --- a/packages/flutter_tools/test/integration.shard/gradle_non_android_plugin_test.dart +++ b/packages/flutter_tools/test/integration.shard/gradle_non_android_plugin_test.dart @@ -4,6 +4,7 @@ import 'package:file/file.dart'; import 'package:file_testing/file_testing.dart'; +import 'package:flutter_tools/src/base/io.dart'; import '../src/common.dart'; import 'test_utils.dart'; @@ -68,7 +69,7 @@ void main() { // Build example APK final Directory exampleDir = projectRoot.childDirectory('example'); - processManager.runSync(<String>[ + final ProcessResult result = processManager.runSync(<String>[ flutterBin, ...getLocalEngineArguments(), 'build', @@ -78,6 +79,8 @@ void main() { '--verbose', ], workingDirectory: exampleDir.path); + expect(result, const ProcessResultMatcher()); + final String exampleAppApk = fileSystem.path.join( exampleDir.path, 'build', diff --git a/packages/flutter_tools/test/integration.shard/multidex_build_test.dart b/packages/flutter_tools/test/integration.shard/multidex_build_test.dart index 7bb5bf9875613..b3e838aa27ddf 100644 --- a/packages/flutter_tools/test/integration.shard/multidex_build_test.dart +++ b/packages/flutter_tools/test/integration.shard/multidex_build_test.dart @@ -36,8 +36,7 @@ void main() { '--debug', ], workingDirectory: tempDir.path); - expect(result.exitCode, 0); - expect(result.stdout.toString(), contains('app-debug.apk')); + expect(result, const ProcessResultMatcher(stdoutPattern: 'app-debug.apk')); }); testWithoutContext('simple build apk without FlutterMultiDexApplication fails', () async { @@ -52,8 +51,8 @@ void main() { '--debug', ], workingDirectory: tempDir.path); + expect(result, const ProcessResultMatcher(exitCode: 1)); expect(result.stderr.toString(), contains('Cannot fit requested classes in a single dex file')); expect(result.stderr.toString(), contains('The number of method references in a .dex file cannot exceed 64K.')); - expect(result.exitCode, 1); }); } diff --git a/packages/flutter_tools/test/integration.shard/test_data/gen_l10n_project.dart b/packages/flutter_tools/test/integration.shard/test_data/gen_l10n_project.dart index 46d2a51202df3..68cc153f92fe9 100644 --- a/packages/flutter_tools/test/integration.shard/test_data/gen_l10n_project.dart +++ b/packages/flutter_tools/test/integration.shard/test_data/gen_l10n_project.dart @@ -232,6 +232,8 @@ class Home extends StatelessWidget { "${localizations.selectInPlural('male', 1)}", "${localizations.selectInPlural('male', 2)}", "${localizations.selectInPlural('female', 1)}", + '${localizations.datetime1(DateTime(2023, 6, 26))}', + '${localizations.datetime2(DateTime(2023, 6, 26, 5, 23))}', ]); }, ), @@ -682,7 +684,9 @@ void main() { "type": "num" } } - } + }, + "datetime1": "{today, date, ::yMd}", + "datetime2": "{current, time, ::jms}" } '''; diff --git a/packages/flutter_tools/test/integration.shard/test_driver.dart b/packages/flutter_tools/test/integration.shard/test_driver.dart index 22eac01355e44..24a8e59575a09 100644 --- a/packages/flutter_tools/test/integration.shard/test_driver.dart +++ b/packages/flutter_tools/test/integration.shard/test_driver.dart @@ -757,7 +757,7 @@ class FlutterRunTestDriver extends FlutterTestDriver { // to throw if it sees an app.stop event before the response to this request. final Future<Map<String, Object?>> responseFuture = _waitFor( id: requestId, - ignoreAppStopEvent: method == 'app.stop', + ignoreAppStopEvent: method == 'app.stop' || method == 'app.detach', ); _process?.stdin.writeln(jsonEncoded); final Map<String, Object?> response = await responseFuture; diff --git a/packages/flutter_tools/test/integration.shard/test_test.dart b/packages/flutter_tools/test/integration.shard/test_test.dart index daf3073be0558..86e0d11bdc2b3 100644 --- a/packages/flutter_tools/test/integration.shard/test_test.dart +++ b/packages/flutter_tools/test/integration.shard/test_test.dart @@ -32,21 +32,27 @@ final List<String> integrationTestExtraArgs = <String>['-d', 'flutter-tester']; void main() { setUpAll(() async { - await processManager.run( - <String>[ - flutterBin, - 'pub', - 'get', - ], - workingDirectory: flutterTestDirectory + expect( + await processManager.run( + <String>[ + flutterBin, + 'pub', + 'get', + ], + workingDirectory: flutterTestDirectory + ), + const ProcessResultMatcher(), ); - await processManager.run( - <String>[ - flutterBin, - 'pub', - 'get', - ], - workingDirectory: missingDependencyDirectory + expect( + await processManager.run( + <String>[ + flutterBin, + 'pub', + 'get', + ], + workingDirectory: missingDependencyDirectory + ), + const ProcessResultMatcher(), ); }); @@ -112,71 +118,109 @@ void main() { }); testWithoutContext('flutter test should run a test when its name matches a regexp', () async { - final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory, - extraArguments: const <String>['--name', 'inc.*de']); - expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); - expect(result.exitCode, 0); + final ProcessResult result = await _runFlutterTest( + 'filtering', + automatedTestsDirectory, + flutterTestDirectory, + extraArguments: const <String>['--name', 'inc.*de'], + ); + expect( + result, + ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')), + ); }); testWithoutContext('flutter test should run a test when its name contains a string', () async { - final ProcessResult result = await _runFlutterTest('filtering', automatedTestsDirectory, flutterTestDirectory, - extraArguments: const <String>['--plain-name', 'include']); - expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); - expect(result.exitCode, 0); + final ProcessResult result = await _runFlutterTest( + 'filtering', + automatedTestsDirectory, + flutterTestDirectory, + extraArguments: const <String>['--plain-name', 'include'], + ); + expect( + result, + ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')), + ); }); testWithoutContext('flutter test should run a test with a given tag', () async { - final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory, - extraArguments: const <String>['--tags', 'include-tag']); - expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); - expect(result.exitCode, 0); + final ProcessResult result = await _runFlutterTest( + 'filtering_tag', + automatedTestsDirectory, + flutterTestDirectory, + extraArguments: const <String>['--tags', 'include-tag'], + ); + expect( + result, + ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')), + ); }); testWithoutContext('flutter test should not run a test with excluded tag', () async { final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory, extraArguments: const <String>['--exclude-tags', 'exclude-tag']); - expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); - expect(result.exitCode, 0); + expect( + result, + ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')), + ); }); testWithoutContext('flutter test should run all tests when tags are unspecified', () async { final ProcessResult result = await _runFlutterTest('filtering_tag', automatedTestsDirectory, flutterTestDirectory); - expect(result.stdout, contains(RegExp(r'\+\d+ -1: Some tests failed\.'))); - expect(result.exitCode, 1); + expect( + result, + ProcessResultMatcher( + exitCode: 1, + stdoutPattern: RegExp(r'\+\d+ -1: Some tests failed\.'), + ), + ); }); testWithoutContext('flutter test should run a widgetTest with a given tag', () async { final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory, extraArguments: const <String>['--tags', 'include-tag']); - expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); - expect(result.exitCode, 0); + expect( + result, + ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')), + ); }); testWithoutContext('flutter test should not run a widgetTest with excluded tag', () async { final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory, extraArguments: const <String>['--exclude-tags', 'exclude-tag']); - expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); - expect(result.exitCode, 0); + expect( + result, + ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')), + ); }); testWithoutContext('flutter test should run all widgetTest when tags are unspecified', () async { final ProcessResult result = await _runFlutterTest('filtering_tag_widget', automatedTestsDirectory, flutterTestDirectory); - expect(result.stdout, contains(RegExp(r'\+\d+ -1: Some tests failed\.'))); - expect(result.exitCode, 1); + expect( + result, + ProcessResultMatcher( + exitCode: 1, + stdoutPattern: RegExp(r'\+\d+ -1: Some tests failed\.'), + ), + ); }); testWithoutContext('flutter test should run a test with an exact name in URI format', () async { final ProcessResult result = await _runFlutterTest('uri_format', automatedTestsDirectory, flutterTestDirectory, query: 'full-name=exactTestName'); - expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); - expect(result.exitCode, 0); + expect( + result, + ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')), + ); }); testWithoutContext('flutter test should run a test by line number in URI format', () async { final ProcessResult result = await _runFlutterTest('uri_format', automatedTestsDirectory, flutterTestDirectory, query: 'line=11'); - expect(result.stdout, contains(RegExp(r'\+\d+: All tests passed!'))); - expect(result.exitCode, 0); + expect( + result, + ProcessResultMatcher(stdoutPattern: RegExp(r'\+\d+: All tests passed!')), + ); }); testWithoutContext('flutter test should test runs to completion', () async { @@ -191,7 +235,7 @@ void main() { if ((result.stderr as String).isNotEmpty) { fail('unexpected error output from test:\n\n${result.stderr}\n-- end stderr --\n\n'); } - expect(result.exitCode, 0); + expect(result, const ProcessResultMatcher()); }); testWithoutContext('flutter test should run all tests inside of a directory with no trailing slash', () async { @@ -206,7 +250,7 @@ void main() { if ((result.stderr as String).isNotEmpty) { fail('unexpected error output from test:\n\n${result.stderr}\n-- end stderr --\n\n'); } - expect(result.exitCode, 0); + expect(result, const ProcessResultMatcher()); }); testWithoutContext('flutter gold skips tests where the expectations are missing', () async { @@ -214,8 +258,8 @@ void main() { }); testWithoutContext('flutter test should respect --serve-observatory', () async { - late final Process process; - late final StreamSubscription<String> sub; + Process? process; + StreamSubscription<String>? sub; try { process = await _runFlutterTestConcurrent('trivial', automatedTestsDirectory, flutterTestDirectory, extraArguments: const <String>['--start-paused', '--serve-observatory']); @@ -231,16 +275,16 @@ void main() { final HttpClientRequest request = await client.getUrl(vmServiceUri); final HttpClientResponse response = await request.close(); final String content = await response.transform(utf8.decoder).join(); - expect(content.contains('Dart VM Observatory'), true); + expect(content, contains('Dart VM Observatory')); } finally { - await sub.cancel(); - process.kill(); + await sub?.cancel(); + process?.kill(); } }); testWithoutContext('flutter test should serve DevTools', () async { - late final Process process; - late final StreamSubscription<String> sub; + Process? process; + StreamSubscription<String>? sub; try { process = await _runFlutterTestConcurrent('trivial', automatedTestsDirectory, flutterTestDirectory, extraArguments: const <String>['--start-paused']); @@ -256,10 +300,10 @@ void main() { final HttpClientRequest request = await client.getUrl(devToolsUri); final HttpClientResponse response = await request.close(); final String content = await response.transform(utf8.decoder).join(); - expect(content.contains('DevTools'), true); + expect(content, contains('DevTools')); } finally { - await sub.cancel(); - process.kill(); + await sub?.cancel(); + process?.kill(); } }); } @@ -285,7 +329,12 @@ Future<void> _testFile( extraArguments: extraArguments, ); - expect(exec.exitCode, exitCode); + expect( + exec.exitCode, + exitCode, + reason: '"$testName" returned code ${exec.exitCode}\n\nstdout:\n' + '${exec.stdout}\nstderr:\n${exec.stderr}', + ); final List<String> output = (exec.stdout as String).split('\n'); if (output.first.startsWith('Waiting for another flutter command to release the startup lock...')) { output.removeAt(0); diff --git a/packages/flutter_tools/test/integration.shard/test_utils.dart b/packages/flutter_tools/test/integration.shard/test_utils.dart index ceb90a5084c14..101828206776f 100644 --- a/packages/flutter_tools/test/integration.shard/test_utils.dart +++ b/packages/flutter_tools/test/integration.shard/test_utils.dart @@ -124,3 +124,88 @@ abstract final class AppleTestUtils { return nmOutput.isEmpty ? const <String>[] : nmOutput.split('\n'); } } + +/// Matcher to be used for [ProcessResult] returned +/// from a process run +/// +/// The default for [exitCode] will be 0 while +/// [stdoutPattern] and [stderrPattern] are both optional +class ProcessResultMatcher extends Matcher { + const ProcessResultMatcher({ + this.exitCode = 0, + this.stdoutPattern, + this.stderrPattern, + }); + + /// The expected exit code to get returned from a process run + final int exitCode; + + /// Substring to find in the process's stdout + final Pattern? stdoutPattern; + + /// Substring to find in the process's stderr + final Pattern? stderrPattern; + + @override + Description describe(Description description) { + description.add('a process with exit code $exitCode'); + if (stdoutPattern != null) { + description.add(' and stdout: "$stdoutPattern"'); + } + if (stderrPattern != null) { + description.add(' and stderr: "$stderrPattern"'); + } + + return description; + } + + @override + bool matches(dynamic item, Map<dynamic, dynamic> matchState) { + final ProcessResult result = item as ProcessResult; + bool foundStdout = true; + bool foundStderr = true; + + final String stdout = result.stdout as String; + final String stderr = result.stderr as String; + if (stdoutPattern != null) { + foundStdout = stdout.contains(stdoutPattern!); + matchState['stdout'] = stdout; + } else if (stdout.isNotEmpty) { + // even if we were not asserting on stdout, show stdout for debug purposes + matchState['stdout'] = stdout; + } + + if (stderrPattern != null) { + foundStderr = stderr.contains(stderrPattern!); + matchState['stderr'] = stderr; + } else if (stderr.isNotEmpty) { + matchState['stderr'] = stderr; + } + + return result.exitCode == exitCode && foundStdout && foundStderr; + } + + @override + Description describeMismatch( + Object? item, + Description mismatchDescription, + Map<dynamic, dynamic> matchState, + bool verbose, + ) { + final ProcessResult result = item! as ProcessResult; + + if (result.exitCode != exitCode) { + mismatchDescription.add('Actual exitCode was ${result.exitCode}\n'); + } + + if (matchState.containsKey('stdout')) { + mismatchDescription.add('Actual stdout:\n${matchState["stdout"]}\n'); + } + + if (matchState.containsKey('stderr')) { + mismatchDescription.add('Actual stderr:\n${matchState["stderr"]}\n'); + } + + return mismatchDescription; + } +} diff --git a/packages/flutter_tools/test/integration.shard/tool_backend_test.dart b/packages/flutter_tools/test/integration.shard/tool_backend_test.dart index 56581bfd24d43..1beb8916aad40 100644 --- a/packages/flutter_tools/test/integration.shard/tool_backend_test.dart +++ b/packages/flutter_tools/test/integration.shard/tool_backend_test.dart @@ -20,8 +20,13 @@ void main() { 'debug', ]); - expect(result.exitCode, 1); - expect(result.stderr, contains('PROJECT_DIR environment variable must be set to the location of Flutter project to be built.')); + expect( + result, + const ProcessResultMatcher( + exitCode: 1, + stderrPattern: 'PROJECT_DIR environment variable must be set to the location of Flutter project to be built.', + ), + ); }); testWithoutContext('tool_backend.dart exits if FLUTTER_ROOT is not set', () async { @@ -37,8 +42,13 @@ void main() { 'PROJECT_DIR': examplePath, }, includeParentEnvironment: false); // Prevent FLUTTER_ROOT set by test environment from leaking - expect(result.exitCode, 1); - expect(result.stderr, contains('FLUTTER_ROOT environment variable must be set to the location of the Flutter SDK.')); + expect( + result, + const ProcessResultMatcher( + exitCode: 1, + stderrPattern: 'FLUTTER_ROOT environment variable must be set to the location of the Flutter SDK.', + ), + ); }); testWithoutContext('tool_backend.dart exits if local engine does not match build mode', () async { @@ -52,7 +62,12 @@ void main() { 'LOCAL_ENGINE': 'release_foo_bar', // Does not contain "debug", }); - expect(result.exitCode, 1); - expect(result.stderr, contains("ERROR: Requested build with Flutter local engine at 'release_foo_bar'")); + expect( + result, + const ProcessResultMatcher( + exitCode: 1, + stderrPattern: "ERROR: Requested build with Flutter local engine at 'release_foo_bar'", + ), + ); }); } diff --git a/packages/flutter_tools/test/integration.shard/xcode_backend_test.dart b/packages/flutter_tools/test/integration.shard/xcode_backend_test.dart index 36cfa614c5ad3..d34d242625117 100644 --- a/packages/flutter_tools/test/integration.shard/xcode_backend_test.dart +++ b/packages/flutter_tools/test/integration.shard/xcode_backend_test.dart @@ -9,6 +9,7 @@ import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import '../src/common.dart'; +import 'test_utils.dart'; const String xcodeBackendPath = 'bin/xcode_backend.sh'; const String xcodeBackendErrorHeader = '========================================================================'; @@ -85,8 +86,7 @@ void main() { 'INFOPLIST_PATH': 'Info.plist', }, ); - expect(result.stdout, contains('Info.plist does not exist.')); - expect(result.exitCode, 0); + expect(result, const ProcessResultMatcher(stdoutPattern: 'Info.plist does not exist.')); }); const String emptyPlist = ''' @@ -115,7 +115,7 @@ void main() { expect(actualInfoPlist, isNot(contains('dartVmService'))); expect(actualInfoPlist, isNot(contains('NSLocalNetworkUsageDescription'))); - expect(result.exitCode, 0); + expect(result, const ProcessResultMatcher()); }); for (final String buildConfiguration in <String>['Debug', 'Profile']) { @@ -137,7 +137,7 @@ void main() { expect(actualInfoPlist, contains('dartVmService')); expect(actualInfoPlist, contains('NSLocalNetworkUsageDescription')); - expect(result.exitCode, 0); + expect(result, const ProcessResultMatcher()); }); } @@ -181,7 +181,7 @@ void main() { </dict> </plist> '''); - expect(result.exitCode, 0); + expect(result, const ProcessResultMatcher()); }); }, skip: !io.Platform.isMacOS); // [intended] requires macos toolchain. } diff --git a/packages/flutter_tools/test/src/android_common.dart b/packages/flutter_tools/test/src/android_common.dart index b0c90c01020a3..109eb0ef6d338 100644 --- a/packages/flutter_tools/test/src/android_common.dart +++ b/packages/flutter_tools/test/src/android_common.dart @@ -39,6 +39,12 @@ class FakeAndroidBuilder implements AndroidBuilder { @override Future<List<String>> getBuildVariants({required FlutterProject project}) async => const <String>[]; + + @override + Future<List<String>> getAppLinkDomainsForVariant(String buildVariant, {required FlutterProject project}) async => const <String>[]; + + @override + Future<String> getApplicationIdForVariant(String buildVariant, {required FlutterProject project}) async => ''; } /// Creates a [FlutterProject] in a directory named [flutter_project] diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart index 0a70b2fd46799..7bea7532c3f5b 100644 --- a/packages/flutter_tools/test/src/context.dart +++ b/packages/flutter_tools/test/src/context.dart @@ -319,6 +319,9 @@ class NoopIOSSimulatorUtils implements IOSSimulatorUtils { @override Future<List<IOSSimulator>> getAttachedDevices() async => <IOSSimulator>[]; + + @override + Future<List<IOSSimulatorRuntime>> getAvailableIOSRuntimes() async => <IOSSimulatorRuntime>[]; } class FakeXcodeProjectInterpreter implements XcodeProjectInterpreter { diff --git a/packages/flutter_tools/test/src/fake_process_manager.dart b/packages/flutter_tools/test/src/fake_process_manager.dart index 171efdd6e2bf5..5486b959e5698 100644 --- a/packages/flutter_tools/test/src/fake_process_manager.dart +++ b/packages/flutter_tools/test/src/fake_process_manager.dart @@ -33,6 +33,7 @@ class FakeCommand { this.stdin, this.exception, this.outputFollowsExit = false, + this.processStartMode, }); /// The exact commands that must be matched for this [FakeCommand] to be @@ -102,14 +103,20 @@ class FakeCommand { /// [Future] on [io.Process] completes. final bool outputFollowsExit; + final io.ProcessStartMode? processStartMode; + void _matches( List<String> command, String? workingDirectory, Map<String, String>? environment, Encoding? encoding, + io.ProcessStartMode? mode, ) { final List<dynamic> matchers = this.command.map((Pattern x) => x is String ? x : matches(x)).toList(); expect(command, matchers); + if (processStartMode != null) { + expect(mode, processStartMode); + } if (this.workingDirectory != null) { expect(workingDirectory, this.workingDirectory); } @@ -267,18 +274,26 @@ abstract class FakeProcessManager implements ProcessManager { String? workingDirectory, Map<String, String>? environment, Encoding? encoding, + io.ProcessStartMode? mode, ); int _pid = 9999; FakeProcess _runCommand( - List<String> command, + List<String> command, { String? workingDirectory, Map<String, String>? environment, Encoding? encoding, - ) { + io.ProcessStartMode? mode, + }) { _pid += 1; - final FakeCommand fakeCommand = findCommand(command, workingDirectory, environment, encoding); + final FakeCommand fakeCommand = findCommand( + command, + workingDirectory, + environment, + encoding, + mode, + ); if (fakeCommand.exception != null) { assert(fakeCommand.exception is Exception || fakeCommand.exception is Error); throw fakeCommand.exception!; // ignore: only_throw_errors @@ -305,9 +320,15 @@ abstract class FakeProcessManager implements ProcessManager { Map<String, String>? environment, bool includeParentEnvironment = true, // ignored bool runInShell = false, // ignored - io.ProcessStartMode mode = io.ProcessStartMode.normal, // ignored + io.ProcessStartMode mode = io.ProcessStartMode.normal, }) { - final FakeProcess process = _runCommand(command.cast<String>(), workingDirectory, environment, io.systemEncoding); + final FakeProcess process = _runCommand( + command.cast<String>(), + workingDirectory: workingDirectory, + environment: environment, + encoding: io.systemEncoding, + mode: mode, + ); if (process._completer != null) { _fakeRunningProcesses[process.pid] = process; process.exitCode.whenComplete(() { @@ -327,7 +348,12 @@ abstract class FakeProcessManager implements ProcessManager { Encoding? stdoutEncoding = io.systemEncoding, Encoding? stderrEncoding = io.systemEncoding, }) async { - final FakeProcess process = _runCommand(command.cast<String>(), workingDirectory, environment, stdoutEncoding); + final FakeProcess process = _runCommand( + command.cast<String>(), + workingDirectory: workingDirectory, + environment: environment, + encoding: stdoutEncoding, + ); await process.exitCode; return io.ProcessResult( process.pid, @@ -347,7 +373,12 @@ abstract class FakeProcessManager implements ProcessManager { Encoding? stdoutEncoding = io.systemEncoding, Encoding? stderrEncoding = io.systemEncoding, }) { - final FakeProcess process = _runCommand(command.cast<String>(), workingDirectory, environment, stdoutEncoding); + final FakeProcess process = _runCommand( + command.cast<String>(), + workingDirectory: workingDirectory, + environment: environment, + encoding: stdoutEncoding, + ); return io.ProcessResult( process.pid, process._exitCode, @@ -385,12 +416,14 @@ class _FakeAnyProcessManager extends FakeProcessManager { String? workingDirectory, Map<String, String>? environment, Encoding? encoding, + io.ProcessStartMode? mode, ) { return FakeCommand( command: command, workingDirectory: workingDirectory, environment: environment, encoding: encoding, + processStartMode: mode, ); } @@ -415,12 +448,13 @@ class _SequenceProcessManager extends FakeProcessManager { String? workingDirectory, Map<String, String>? environment, Encoding? encoding, + io.ProcessStartMode? mode, ) { expect(_commands, isNotEmpty, reason: 'ProcessManager was told to execute $command (in $workingDirectory) ' 'but the FakeProcessManager.list expected no more processes.' ); - _commands.first._matches(command, workingDirectory, environment, encoding); + _commands.first._matches(command, workingDirectory, environment, encoding, mode); return _commands.removeAt(0); } diff --git a/packages/flutter_tools/test/src/fakes.dart b/packages/flutter_tools/test/src/fakes.dart index 30d379d7af505..65fb5578cf185 100644 --- a/packages/flutter_tools/test/src/fakes.dart +++ b/packages/flutter_tools/test/src/fakes.dart @@ -13,6 +13,8 @@ import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/os.dart'; +import 'package:flutter_tools/src/base/time.dart'; +import 'package:flutter_tools/src/base/version.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/features.dart'; @@ -336,6 +338,8 @@ class FakeFlutterVersion implements FlutterVersion { this.frameworkAge = '0 hours ago', this.frameworkCommitDate = '12/01/01', this.gitTagVersion = const GitTagVersion.unknown(), + this.flutterRoot = '/path/to/flutter', + this.nextFlutterVersion, }); final String branch; @@ -343,6 +347,17 @@ class FakeFlutterVersion implements FlutterVersion { bool get didFetchTagsAndUpdate => _didFetchTagsAndUpdate; bool _didFetchTagsAndUpdate = false; + /// Will be returned by [fetchTagsAndGetVersion] if not null. + final FlutterVersion? nextFlutterVersion; + + @override + FlutterVersion fetchTagsAndGetVersion({ + SystemClock clock = const SystemClock(), + }) { + _didFetchTagsAndUpdate = true; + return nextFlutterVersion ?? this; + } + bool get didCheckFlutterVersionFreshness => _didCheckFlutterVersionFreshness; bool _didCheckFlutterVersionFreshness = false; @@ -354,6 +369,9 @@ class FakeFlutterVersion implements FlutterVersion { return kUserBranch; } + @override + final String flutterRoot; + @override final String devToolsVersion; @@ -384,16 +402,11 @@ class FakeFlutterVersion implements FlutterVersion { @override final String frameworkCommitDate; - @override - String get frameworkDate => frameworkCommitDate; - @override final GitTagVersion gitTagVersion; @override - void fetchTagsAndUpdate() { - _didFetchTagsAndUpdate = true; - } + FileSystem get fs => throw UnimplementedError('FakeFlutterVersion.fs is not implemented'); @override Future<void> checkFlutterVersionFreshness() async { @@ -619,15 +632,12 @@ class FakeJava extends Fake implements Java { FakeJava({ this.javaHome = '/android-studio/jbr', String binary = '/android-studio/jbr/bin/java', - JavaVersion? version, + Version? version, bool canRun = true, }): binaryPath = binary, - version = version ?? JavaVersion( - longText: 'openjdk 19.0.2 2023-01-17', - number: '19.0.2', - ), + version = version ?? const Version.withText(19, 0, 2, 'openjdk 19.0.2 2023-01-17'), _environment = <String, String>{ - if (javaHome != null) 'JAVA_HOME': javaHome, + if (javaHome != null) Java.javaHomeEnvironmentVariable: javaHome, 'PATH': '/android-studio/jbr/bin', }, _canRun = canRun; @@ -645,7 +655,7 @@ class FakeJava extends Fake implements Java { Map<String, String> get environment => _environment; @override - JavaVersion? version; + Version? version; @override bool canRun() { diff --git a/packages/flutter_web_plugins/lib/flutter_web_plugins.dart b/packages/flutter_web_plugins/lib/flutter_web_plugins.dart index d9c5f208ce8f4..3b27fff155881 100644 --- a/packages/flutter_web_plugins/lib/flutter_web_plugins.dart +++ b/packages/flutter_web_plugins/lib/flutter_web_plugins.dart @@ -17,6 +17,5 @@ library flutter_web_plugins; export 'src/navigation/url_strategy.dart'; export 'src/navigation/utils.dart'; -export 'src/navigation_common/platform_location.dart'; export 'src/plugin_event_channel.dart'; export 'src/plugin_registry.dart'; diff --git a/packages/flutter_web_plugins/lib/src/navigation/url_strategy.dart b/packages/flutter_web_plugins/lib/src/navigation/url_strategy.dart index 59294723b52e6..427942a4a8d32 100644 --- a/packages/flutter_web_plugins/lib/src/navigation/url_strategy.dart +++ b/packages/flutter_web_plugins/lib/src/navigation/url_strategy.dart @@ -2,39 +2,23 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; -import 'dart:html' as html; -import 'dart:ui' as ui; import 'dart:ui_web' as ui_web; -import '../navigation_common/platform_location.dart'; import 'utils.dart'; -export 'dart:ui_web' show UrlStrategy; - -/// Saves the current [UrlStrategy] to be accessed by [urlStrategy] or -/// [setUrlStrategy]. -/// -/// This is particularly required for web plugins relying on valid URL -/// encoding. -// -// Keep this in sync with the default url strategy in the web engine. -// Find it at: -// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/window.dart#L360 -// -ui_web.UrlStrategy? _urlStrategy = const HashUrlStrategy(); - -/// Returns the present [UrlStrategy] for handling the browser URL. -/// -/// In case null is returned, the browser integration has been manually -/// disabled by [setUrlStrategy]. -ui_web.UrlStrategy? get urlStrategy => _urlStrategy; +export 'dart:ui_web' + show + BrowserPlatformLocation, + EventListener, + HashUrlStrategy, + PlatformLocation, + UrlStrategy, + urlStrategy; /// Change the strategy to use for handling browser URL. /// /// Setting this to null disables all integration with the browser history. void setUrlStrategy(ui_web.UrlStrategy? strategy) { - _urlStrategy = strategy; ui_web.urlStrategy = strategy; } @@ -43,99 +27,6 @@ void usePathUrlStrategy() { setUrlStrategy(PathUrlStrategy()); } -/// Uses the browser URL's [hash fragments](https://en.wikipedia.org/wiki/Uniform_Resource_Locator#Syntax) -/// to represent its state. -/// -/// By default, this class is used as the URL strategy for the app. However, -/// this class is still useful for apps that want to extend it. -/// -/// In order to use [HashUrlStrategy] for an app, it needs to be set like this: -/// -/// ```dart -/// import 'package:flutter_web_plugins/flutter_web_plugins.dart'; -/// -/// // Somewhere before calling `runApp()` do: -/// setUrlStrategy(const HashUrlStrategy()); -/// ``` -class HashUrlStrategy extends ui_web.UrlStrategy { - /// Creates an instance of [HashUrlStrategy]. - /// - /// The [PlatformLocation] parameter is useful for testing to mock out browser - /// interactions. - const HashUrlStrategy( - [this._platformLocation = const BrowserPlatformLocation()]); - - final PlatformLocation _platformLocation; - - @override - ui.VoidCallback addPopStateListener(ui_web.PopStateListener fn) { - void wrappedFn(Object event) { - // `fn` expects `event.state`, not a `html.Event`. - fn((event as html.PopStateEvent).state); - } - _platformLocation.addPopStateListener(wrappedFn); - return () => _platformLocation.removePopStateListener(wrappedFn); - } - - @override - String getPath() { - // the hash value is always prefixed with a `#` - // and if it is empty then it will stay empty - final String path = _platformLocation.hash; - assert(path.isEmpty || path.startsWith('#')); - - // We don't want to return an empty string as a path. Instead we default to "/". - if (path.isEmpty || path == '#') { - return '/'; - } - // At this point, we know [path] starts with "#" and isn't empty. - return path.substring(1); - } - - @override - Object? getState() => _platformLocation.state; - - @override - String prepareExternalUrl(String internalUrl) { - // It's convention that if the hash path is empty, we omit the `#`; however, - // if the empty URL is pushed it won't replace any existing fragment. So - // when the hash path is empty, we still return the location's path and - // query. - return '${_platformLocation.pathname}${_platformLocation.search}' - '${internalUrl.isEmpty ? '' : '#$internalUrl'}'; - } - - @override - void pushState(Object? state, String title, String url) { - _platformLocation.pushState(state, title, prepareExternalUrl(url)); - } - - @override - void replaceState(Object? state, String title, String url) { - _platformLocation.replaceState(state, title, prepareExternalUrl(url)); - } - - @override - Future<void> go(int count) { - _platformLocation.go(count); - return _waitForPopState(); - } - - /// Waits until the next popstate event is fired. - /// - /// This is useful, for example, to wait until the browser has handled the - /// `history.back` transition. - Future<void> _waitForPopState() { - final Completer<void> completer = Completer<void>(); - late ui.VoidCallback unsubscribe; - unsubscribe = addPopStateListener((_) { - unsubscribe(); - completer.complete(); - }); - return completer.future; - } -} - /// Uses the browser URL's pathname to represent Flutter's route name. /// /// In order to use [PathUrlStrategy] for an app, it needs to be set like this: @@ -146,17 +37,19 @@ class HashUrlStrategy extends ui_web.UrlStrategy { /// // Somewhere before calling `runApp()` do: /// setUrlStrategy(PathUrlStrategy()); /// ``` -class PathUrlStrategy extends HashUrlStrategy { +class PathUrlStrategy extends ui_web.HashUrlStrategy { /// Creates an instance of [PathUrlStrategy]. /// - /// The [PlatformLocation] parameter is useful for testing to mock out browser + /// The [ui_web.PlatformLocation] parameter is useful for testing to mock out browser /// interactions. PathUrlStrategy([ super.platformLocation, - ]) : _basePath = stripTrailingSlash(extractPathname(checkBaseHref( + ]) : _platformLocation = platformLocation, + _basePath = stripTrailingSlash(extractPathname(checkBaseHref( platformLocation.getBaseHref(), ))); + final ui_web.PlatformLocation _platformLocation; final String _basePath; @override @@ -170,67 +63,15 @@ class PathUrlStrategy extends HashUrlStrategy { @override String prepareExternalUrl(String internalUrl) { - if (internalUrl.isNotEmpty && !internalUrl.startsWith('/')) { - internalUrl = '/$internalUrl'; + if (internalUrl.isEmpty) { + internalUrl = '/'; } + assert( + internalUrl.startsWith('/'), + "When using PathUrlStrategy, all route names must start with '/' because " + "the browser's pathname always starts with '/'. " + "Found route name: '$internalUrl'", + ); return '$_basePath$internalUrl'; } } - -/// Delegates to real browser APIs to provide platform location functionality. -class BrowserPlatformLocation extends PlatformLocation { - /// Default constructor for [BrowserPlatformLocation]. - const BrowserPlatformLocation(); - - // Default value for [pathname] when it's not set in window.location. - // According to MDN this should be ''. Chrome seems to return '/'. - static const String _defaultPathname = ''; - - // Default value for [search] when it's not set in window.location. - // According to both chrome, and the MDN, this is ''. - static const String _defaultSearch = ''; - - html.Location get _location => html.window.location; - - html.History get _history => html.window.history; - - @override - void addPopStateListener(html.EventListener fn) { - html.window.addEventListener('popstate', fn); - } - - @override - void removePopStateListener(html.EventListener fn) { - html.window.removeEventListener('popstate', fn); - } - - @override - String get pathname => _location.pathname ?? _defaultPathname; - - @override - String get search => _location.search ?? _defaultSearch; - - @override - String get hash => _location.hash; - - @override - Object? get state => _history.state; - - @override - void pushState(Object? state, String title, String url) { - _history.pushState(state, title, url); - } - - @override - void replaceState(Object? state, String title, String url) { - _history.replaceState(state, title, url); - } - - @override - void go(int count) { - _history.go(count); - } - - @override - String? getBaseHref() => getBaseElementHrefFromDom(); -} diff --git a/packages/flutter_web_plugins/lib/src/navigation/utils.dart b/packages/flutter_web_plugins/lib/src/navigation/utils.dart index 5cb18924db2cb..5624c4bde8625 100644 --- a/packages/flutter_web_plugins/lib/src/navigation/utils.dart +++ b/packages/flutter_web_plugins/lib/src/navigation/utils.dart @@ -2,28 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:html'; - -final AnchorElement _urlParsingNode = AnchorElement(); - /// Extracts the pathname part of a full [url]. /// /// Example: for the url `http://example.com/foo`, the extracted pathname will /// be `/foo`. String extractPathname(String url) { - _urlParsingNode.href = url; // ignore: unsafe_html, node is never exposed to the user - final String pathname = _urlParsingNode.pathname ?? ''; - return (pathname.isEmpty || pathname[0] == '/') ? pathname : '/$pathname'; + return ensureLeadingSlash(Uri.parse(url).path); } -// The <base> element in the document. -final Element? _baseElement = document.querySelector('base'); - -/// Returns the `href` attribute of the <base> element in the document. -/// -/// Returns null if the element isn't found. -String? getBaseElementHrefFromDom() => _baseElement?.getAttribute('href'); - /// Checks that [baseHref] is set. /// /// Throws an exception otherwise. diff --git a/packages/flutter_web_plugins/lib/src/navigation_common/platform_location.dart b/packages/flutter_web_plugins/lib/src/navigation_non_web/platform_location.dart similarity index 76% rename from packages/flutter_web_plugins/lib/src/navigation_common/platform_location.dart rename to packages/flutter_web_plugins/lib/src/navigation_non_web/platform_location.dart index 3317a706c82c3..fb3e1338aaa8d 100644 --- a/packages/flutter_web_plugins/lib/src/navigation_common/platform_location.dart +++ b/packages/flutter_web_plugins/lib/src/navigation_non_web/platform_location.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'url_strategy.dart'; + /// Function type that handles pop state events. typedef EventListener = dynamic Function(Object event); @@ -10,11 +12,7 @@ typedef EventListener = dynamic Function(Object event); /// /// For convenience, the [PlatformLocation] class can be used by implementations /// of [UrlStrategy] to interact with DOM apis like pushState, popState, etc. -abstract class PlatformLocation { - /// Abstract const constructor. This constructor enables subclasses to provide - /// const constructors so that they can be used in const expressions. - const PlatformLocation(); - +abstract interface class PlatformLocation { /// Registers an event listener for the `popstate` event. /// /// See: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate @@ -74,3 +72,46 @@ abstract class PlatformLocation { /// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base String? getBaseHref(); } + +/// Delegates to real browser APIs to provide platform location functionality. +class BrowserPlatformLocation implements PlatformLocation { + @override + void addPopStateListener(EventListener fn) { + // No-op. + } + + @override + void removePopStateListener(EventListener fn) { + // No-op. + } + + @override + String get pathname => ''; + + @override + String get search => ''; + + @override + String get hash => ''; + + @override + Object? get state => null; + + @override + void pushState(Object? state, String title, String url) { + // No-op. + } + + @override + void replaceState(Object? state, String title, String url) { + // No-op. + } + + @override + void go(int count) { + // No-op. + } + + @override + String? getBaseHref() => null; +} diff --git a/packages/flutter_web_plugins/lib/src/navigation_non_web/url_strategy.dart b/packages/flutter_web_plugins/lib/src/navigation_non_web/url_strategy.dart index 149299712c8af..f9622608bdac5 100644 --- a/packages/flutter_web_plugins/lib/src/navigation_non_web/url_strategy.dart +++ b/packages/flutter_web_plugins/lib/src/navigation_non_web/url_strategy.dart @@ -5,7 +5,9 @@ import 'dart:async'; import 'dart:ui' as ui; -import '../navigation_common/platform_location.dart'; +import 'platform_location.dart'; + +export 'platform_location.dart'; /// Callback that receives the new state of the browser history entry. typedef PopStateListener = void Function(Object? state); @@ -123,5 +125,5 @@ class PathUrlStrategy extends HashUrlStrategy { /// /// The [PlatformLocation] parameter is useful for testing to mock out browser /// integrations. - PathUrlStrategy([PlatformLocation? _]); + const PathUrlStrategy([PlatformLocation? _]); } diff --git a/packages/flutter_web_plugins/lib/src/plugin_registry.dart b/packages/flutter_web_plugins/lib/src/plugin_registry.dart index b4a11f61f3884..78108b9699c39 100644 --- a/packages/flutter_web_plugins/lib/src/plugin_registry.dart +++ b/packages/flutter_web_plugins/lib/src/plugin_registry.dart @@ -4,6 +4,7 @@ import 'dart:async'; import 'dart:ui' as ui; +import 'dart:ui_web' as ui_web; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; @@ -57,13 +58,11 @@ class Registrar extends BinaryMessenger { /// previously-registered handler and replaces it with the handler /// from this object. /// - /// This method uses a function called `webOnlySetPluginHandler` in - /// the [dart:ui] library. That function is only available when + /// This method uses a function called `setPluginHandler` in + /// the [dart:ui_web] library. That function is only available when /// compiling for the web. void registerMessageHandler() { - // The `ui.webOnlySetPluginHandler` function below is only defined in the Web dart:ui. - // ignore: undefined_function, avoid_dynamic_calls - ui.webOnlySetPluginHandler(handleFrameworkMessage); + ui_web.setPluginHandler(handleFrameworkMessage); } /// Receives a platform message from the framework. @@ -101,7 +100,7 @@ class Registrar extends BinaryMessenger { /// the following: /// /// ```dart - /// ui.webOnlySetPluginHandler(webPluginRegistrar.handleFrameworkMessage); + /// ui_web.setPluginHandler(handleFrameworkMessage); /// ``` Future<void> handleFrameworkMessage( String channel, diff --git a/packages/flutter_web_plugins/lib/url_strategy.dart b/packages/flutter_web_plugins/lib/url_strategy.dart index 0ebf7b4a77d0f..79cc9265c27f0 100644 --- a/packages/flutter_web_plugins/lib/url_strategy.dart +++ b/packages/flutter_web_plugins/lib/url_strategy.dart @@ -2,7 +2,5 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -export 'src/navigation_common/platform_location.dart'; - export 'src/navigation_non_web/url_strategy.dart' if (dart.library.ui_web) 'src/navigation/url_strategy.dart'; diff --git a/packages/flutter_web_plugins/pubspec.yaml b/packages/flutter_web_plugins/pubspec.yaml index ee95401f33ba1..15883816ca0f3 100644 --- a/packages/flutter_web_plugins/pubspec.yaml +++ b/packages/flutter_web_plugins/pubspec.yaml @@ -9,13 +9,12 @@ dependencies: flutter: sdk: flutter - js: 0.6.7 - characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -25,13 +24,13 @@ dev_dependencies: boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" path: 1.8.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stack_trace: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" -# PUBSPEC CHECKSUM: 8a1e +# PUBSPEC CHECKSUM: b642 diff --git a/packages/flutter_web_plugins/test/navigation/common.dart b/packages/flutter_web_plugins/test/navigation/common.dart index 0f65f42f2df2b..e01616bf64c88 100644 --- a/packages/flutter_web_plugins/test/navigation/common.dart +++ b/packages/flutter_web_plugins/test/navigation/common.dart @@ -5,7 +5,7 @@ import 'package:flutter_web_plugins/url_strategy.dart'; /// A mock implementation of [PlatformLocation] that doesn't access the browser. -class TestPlatformLocation extends PlatformLocation { +class TestPlatformLocation implements PlatformLocation { @override String pathname = ''; diff --git a/packages/flutter_web_plugins/test/navigation/url_strategy_test.dart b/packages/flutter_web_plugins/test/navigation/url_strategy_test.dart index aa08f027ca75d..f5bfa160554bf 100644 --- a/packages/flutter_web_plugins/test/navigation/url_strategy_test.dart +++ b/packages/flutter_web_plugins/test/navigation/url_strategy_test.dart @@ -11,76 +11,21 @@ import 'package:flutter_web_plugins/url_strategy.dart'; import 'common.dart'; void main() { - group('$HashUrlStrategy', () { - late TestPlatformLocation location; - - setUp(() { - location = TestPlatformLocation(); - }); - - test('allows null state', () { - final HashUrlStrategy strategy = HashUrlStrategy(location); - expect(() => strategy.pushState(null, '', '/'), returnsNormally); - expect(() => strategy.replaceState(null, '', '/'), returnsNormally); - }); - - test('leading slash is optional', () { - final HashUrlStrategy strategy = HashUrlStrategy(location); - - location.hash = '#/'; - expect(strategy.getPath(), '/'); - - location.hash = '#/foo'; - expect(strategy.getPath(), '/foo'); - - location.hash = '#foo'; - expect(strategy.getPath(), 'foo'); - }); - - test('path should not be empty', () { - final HashUrlStrategy strategy = HashUrlStrategy(location); - - location.hash = ''; - expect(strategy.getPath(), '/'); - - location.hash = '#'; - expect(strategy.getPath(), '/'); - }); - - test('allows location path/search before fragment', () { - const String internalUrl = '/menu?foo=bar'; - final HashUrlStrategy strategy = HashUrlStrategy(location); - - location.pathname = '/'; - expect(strategy.prepareExternalUrl(internalUrl), '/#/menu?foo=bar'); - - location.pathname = '/main'; - expect(strategy.prepareExternalUrl(internalUrl), '/main#/menu?foo=bar'); - - location.search = '?foo=bar'; - expect( - strategy.prepareExternalUrl(internalUrl), - '/main?foo=bar#/menu?foo=bar', - ); - }); - }); - group('$PathUrlStrategy', () { late TestPlatformLocation location; setUp(() { location = TestPlatformLocation(); + location.baseHref = '/'; }); test('allows null state', () { - location.baseHref = '/'; final PathUrlStrategy strategy = PathUrlStrategy(location); expect(() => strategy.pushState(null, '', '/'), returnsNormally); expect(() => strategy.replaceState(null, '', '/'), returnsNormally); }); test('validates base href', () { - location.baseHref = '/'; expect( () => PathUrlStrategy(location), returnsNormally, @@ -112,7 +57,6 @@ void main() { }); test('leading slash is always prepended', () { - location.baseHref = '/'; final PathUrlStrategy strategy = PathUrlStrategy(location); location.pathname = ''; @@ -149,13 +93,35 @@ void main() { expect(strategy.getPath(), '/bar?q=1&t=r'); }); + test('empty route name is ok', () { + final PathUrlStrategy strategy = PathUrlStrategy(location); + expect(strategy.prepareExternalUrl(''), '/'); + expect(() => strategy.pushState(null, '', ''), returnsNormally); + expect(() => strategy.replaceState(null, '', ''), returnsNormally); + }); + + test('route names must start with /', () { + final PathUrlStrategy strategy = PathUrlStrategy(location); + + expect(() => strategy.prepareExternalUrl('foo'), throwsAssertionError); + expect(() => strategy.prepareExternalUrl('foo/'), throwsAssertionError); + expect(() => strategy.prepareExternalUrl('foo/bar'), throwsAssertionError); + + expect(() => strategy.pushState(null, '', 'foo'), throwsAssertionError); + expect(() => strategy.pushState(null, '', 'foo/'), throwsAssertionError); + expect(() => strategy.pushState(null, '', 'foo/bar'), throwsAssertionError); + + expect(() => strategy.replaceState(null, '', 'foo'), throwsAssertionError); + expect(() => strategy.replaceState(null, '', 'foo/'), throwsAssertionError); + expect(() => strategy.replaceState(null, '', 'foo/bar'), throwsAssertionError); + }); + test('generates external path correctly in the presence of basePath', () { location.baseHref = 'https://example.com/foo/'; final PathUrlStrategy strategy = PathUrlStrategy(location); - expect(strategy.prepareExternalUrl(''), '/foo'); + expect(strategy.prepareExternalUrl(''), '/foo/'); expect(strategy.prepareExternalUrl('/'), '/foo/'); - expect(strategy.prepareExternalUrl('bar'), '/foo/bar'); expect(strategy.prepareExternalUrl('/bar'), '/foo/bar'); expect(strategy.prepareExternalUrl('/bar/'), '/foo/bar/'); }); diff --git a/packages/flutter_web_plugins/test/navigation/utils_test.dart b/packages/flutter_web_plugins/test/navigation/utils_test.dart index b3690586135b7..0bfc64594ecfc 100644 --- a/packages/flutter_web_plugins/test/navigation/utils_test.dart +++ b/packages/flutter_web_plugins/test/navigation/utils_test.dart @@ -33,5 +33,9 @@ void main() { expect(extractPathname('https://example.com/foo'), '/foo'); expect(extractPathname('https://example.com/foo#bar'), '/foo'); expect(extractPathname('https://example.com/foo/#bar'), '/foo/'); + + // URL encoding. + expect(extractPathname('/foo bar'), '/foo%20bar'); + expect(extractPathname('https://example.com/foo bar'), '/foo%20bar'); }); } diff --git a/packages/flutter_web_plugins/test/plugin_event_channel_test.dart b/packages/flutter_web_plugins/test/plugin_event_channel_test.dart index 280580f5a51ae..b43a841bf116c 100644 --- a/packages/flutter_web_plugins/test/plugin_event_channel_test.dart +++ b/packages/flutter_web_plugins/test/plugin_event_channel_test.dart @@ -6,7 +6,7 @@ library; import 'dart:async'; -import 'dart:ui' as ui; +import 'dart:ui_web' as ui_web; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -14,7 +14,7 @@ import 'package:flutter_web_plugins/flutter_web_plugins.dart'; void main() { // Disabling tester emulation because this test relies on real message channel communication. - ui.debugEmulateFlutterTesterEnvironment = false; // ignore: undefined_prefixed_name + ui_web.debugEmulateFlutterTesterEnvironment = false; group('Plugin Event Channel', () { setUp(() { diff --git a/packages/flutter_web_plugins/test/plugin_registry_test.dart b/packages/flutter_web_plugins/test/plugin_registry_test.dart index d33d88866a020..43d724527e94a 100644 --- a/packages/flutter_web_plugins/test/plugin_registry_test.dart +++ b/packages/flutter_web_plugins/test/plugin_registry_test.dart @@ -5,7 +5,7 @@ @TestOn('chrome') // Uses web-only Flutter SDK library; -import 'dart:ui' as ui; +import 'dart:ui_web' as ui_web; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -31,7 +31,7 @@ class TestPlugin { void main() { // Disabling tester emulation because this test relies on real message channel communication. - ui.debugEmulateFlutterTesterEnvironment = false; // ignore: undefined_prefixed_name + ui_web.debugEmulateFlutterTesterEnvironment = false; group('Plugin Registry', () { setUp(() { diff --git a/packages/fuchsia_remote_debug_protocol/pubspec.yaml b/packages/fuchsia_remote_debug_protocol/pubspec.yaml index ea934299fd501..35ca0afb66185 100644 --- a/packages/fuchsia_remote_debug_protocol/pubspec.yaml +++ b/packages/fuchsia_remote_debug_protocol/pubspec.yaml @@ -8,7 +8,7 @@ environment: dependencies: process: 4.2.4 - vm_service: 11.6.0 + vm_service: 11.7.1 file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -16,11 +16,11 @@ dependencies: platform: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: - test: 1.24.2 + test: 1.24.3 - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -28,13 +28,13 @@ dev_dependencies: coverage: 1.6.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" crypto: 3.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -51,8 +51,8 @@ dev_dependencies: stream_channel: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -63,4 +63,4 @@ dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 7c78 +# PUBSPEC CHECKSUM: f780 diff --git a/packages/integration_test/analysis_options.yaml b/packages/integration_test/analysis_options.yaml new file mode 100644 index 0000000000000..e61f982d45eb5 --- /dev/null +++ b/packages/integration_test/analysis_options.yaml @@ -0,0 +1,5 @@ +include: ../analysis_options.yaml + +analyzer: + exclude: + - "test_fixes/**" diff --git a/packages/integration_test/android/build.gradle b/packages/integration_test/android/build.gradle index 8b476928b43be..ba3e75cde3acb 100644 --- a/packages/integration_test/android/build.gradle +++ b/packages/integration_test/android/build.gradle @@ -41,6 +41,7 @@ android { defaultConfig { minSdkVersion 19 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles 'lib-proguard-rules.txt' } dependencies { diff --git a/packages/integration_test/android/lib-proguard-rules.txt b/packages/integration_test/android/lib-proguard-rules.txt new file mode 100644 index 0000000000000..fb96b5604097b --- /dev/null +++ b/packages/integration_test/android/lib-proguard-rules.txt @@ -0,0 +1,5 @@ +# For some reason org.kxml2.io.KXmlParser and org.kxml2.io.KXmlSerializer +# are missing and not marked correctly by dependencies. +# Possibly related to https://github.com/flutter/flutter/issues/56591 +# See https://github.com/flutter/flutter/issues/127388 for more context. +-dontwarn org.kxml2.io.KXmlParser**,org.kxml2.io.KXmlSerializer** \ No newline at end of file diff --git a/packages/integration_test/example/android/app/build.gradle b/packages/integration_test/example/android/app/build.gradle index 2f30a19c0b11f..738d906f78388 100644 --- a/packages/integration_test/example/android/app/build.gradle +++ b/packages/integration_test/example/android/app/build.gradle @@ -55,8 +55,7 @@ android { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug - minifyEnabled false - shrinkResources false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } diff --git a/packages/integration_test/example/android/app/proguard-rules.pro b/packages/integration_test/example/android/app/proguard-rules.pro new file mode 100644 index 0000000000000..800c5c2adc6af --- /dev/null +++ b/packages/integration_test/example/android/app/proguard-rules.pro @@ -0,0 +1 @@ +# Intentionally empty. Validation is that apps can set their own proguard files. diff --git a/packages/integration_test/example/ios/Runner/Info.plist b/packages/integration_test/example/ios/Runner/Info.plist index 46c572f1030ba..c624d4e0c666f 100644 --- a/packages/integration_test/example/ios/Runner/Info.plist +++ b/packages/integration_test/example/ios/Runner/Info.plist @@ -39,8 +39,6 @@ <string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeRight</string> </array> - <key>UIViewControllerBasedStatusBarAppearance</key> - <false/> <key>CADisableMinimumFrameDurationOnPhone</key> <true/> <key>UIApplicationSupportsIndirectInputEvents</key> diff --git a/packages/integration_test/example/pubspec.yaml b/packages/integration_test/example/pubspec.yaml index 6267164d2a1a9..8512ef6481637 100644 --- a/packages/integration_test/example/pubspec.yaml +++ b/packages/integration_test/example/pubspec.yaml @@ -14,10 +14,10 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: flutter_test: @@ -28,15 +28,15 @@ dev_dependencies: sdk: flutter integration_test_macos: path: ../integration_test_macos - test: 1.24.2 + test: 1.24.3 pedantic: 1.11.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec - _fe_analyzer_shared: 60.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - analyzer: 5.12.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - args: 2.4.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + _fe_analyzer_shared: 61.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + analyzer: 5.13.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + args: 2.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" clock: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -46,12 +46,13 @@ dev_dependencies: fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" frontend_server_client: 3.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - glob: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + glob: 2.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_multi_server: 3.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" http_parser: 4.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" io: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - logging: 1.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + logging: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" mime: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" node_preamble: 2.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" package_config: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -70,10 +71,10 @@ dev_dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_core: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_core: 0.5.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" typed_data: 1.3.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - vm_service: 11.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vm_service: 11.7.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" watcher: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" web_socket_channel: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -83,4 +84,4 @@ dev_dependencies: flutter: uses-material-design: true -# PUBSPEC CHECKSUM: 889e +# PUBSPEC CHECKSUM: e4fc diff --git a/packages/integration_test/integration_test_macos/pubspec.yaml b/packages/integration_test/integration_test_macos/pubspec.yaml index 800067ea0a9d6..5628fc13372fa 100644 --- a/packages/integration_test/integration_test_macos/pubspec.yaml +++ b/packages/integration_test/integration_test_macos/pubspec.yaml @@ -18,12 +18,12 @@ dependencies: characters: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" dev_dependencies: pedantic: 1.11.1 -# PUBSPEC CHECKSUM: d263 +# PUBSPEC CHECKSUM: 4387 diff --git a/packages/integration_test/lib/fix_data/README.md b/packages/integration_test/lib/fix_data/README.md new file mode 100644 index 0000000000000..748818fc61284 --- /dev/null +++ b/packages/integration_test/lib/fix_data/README.md @@ -0,0 +1,45 @@ +## Directory contents + +The `.yaml` files in these directories are used to +define the [`dart fix` framework](https://dart.dev/tools/dart-fix) refactorings +used by `integration_test`. + +The number of fix rules defined in a file should not exceed 50 for better +maintainability. Searching for `title:` in a given `.yaml` file will account +for the number of fixes. Splitting out fix rules should be done by class. + +When adding a new `.yaml` file, make a copy of `template.yaml`. Each file should +be for a single class and named `fix_<class>.yaml`. To make sure each file is +grouped with related classes, a `fix_<filename>` folder will contain all of the +fix files for the individual classes. + +See the flutter/packages/integration_test/test_fixes directory for the tests +that validate these fix rules. + +To run these tests locally, execute this command in the +flutter/packages/integration_test/test_fixes directory. +```sh +dart fix --compare-to-golden +``` + +For more documentation about Data Driven Fixes, see +https://dart.dev/go/data-driven-fixes#test-folder. + +To learn more about how fixes are authored in package:integration_test, see +https://github.com/flutter/flutter/wiki/Data-driven-Fixes + +## When making structural changes to this directory + +The tests in this directory are also invoked from external +repositories. Specifically, the CI system for the dart-lang/sdk repo +runs these tests in order to ensure that changes to the dart fix file +format do not break Flutter. + +See [tools/bots/flutter/analyze_flutter_flutter.sh](https://github.com/dart-lang/sdk/blob/main/tools/bots/flutter/analyze_flutter_flutter.sh) +for where the flutter fix tests are invoked for the dart repo. + +See [dev/bots/test.dart](https://github.com/flutter/flutter/blob/master/dev/bots/test.dart) +for where the flutter fix tests are invoked for the flutter/flutter repo. + +When possible, please coordinate changes to this directory that might affect the +`analyze_flutter_flutter.sh` script. diff --git a/packages/integration_test/lib/fix_data/fix_integration_test/fix_binding/fix_integration_test_widgets_flutter_binding.yaml b/packages/integration_test/lib/fix_data/fix_integration_test/fix_binding/fix_integration_test_widgets_flutter_binding.yaml new file mode 100644 index 0000000000000..26092699f2091 --- /dev/null +++ b/packages/integration_test/lib/fix_data/fix_integration_test/fix_binding/fix_integration_test_widgets_flutter_binding.yaml @@ -0,0 +1,33 @@ +# 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. + +# For details regarding the *Flutter Fix* feature, see +# https://flutter.dev/docs/development/tools/flutter-fix + +# Please add new fixes to the top of the file, separated by one blank line +# from other fixes. In a comment, include a link to the PR where the change +# requiring the fix was made. + +# Every fix must be tested. See the +# flutter/packages/integration_test/test_fixes/README.md file for instructions +# on testing these data driven fixes. + +# For documentation about this file format, see +# https://dart.dev/go/data-driven-fixes. + +# * Fixes in this file are for IntegrationTestWidgetsFlutterBinding from the +# integration_test/integration_test.dart file. * + +version: 1 +transforms: + # Changes made in https://github.com/flutter/flutter/pull/89952 + - title: "Remove timeout" + date: 2023-06-26 + element: + uris: [ 'integration_test.dart' ] + method: 'runTest' + inClass: 'IntegrationTestWidgetsFlutterBinding' + changes: + - kind: 'removeParameter' + name: 'timeout' diff --git a/packages/integration_test/lib/fix_data/template.yaml b/packages/integration_test/lib/fix_data/template.yaml new file mode 100644 index 0000000000000..27c5d14a98c3a --- /dev/null +++ b/packages/integration_test/lib/fix_data/template.yaml @@ -0,0 +1,25 @@ +# 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. + +# For details regarding the *Flutter Fix* feature, see +# https://flutter.dev/docs/development/tools/flutter-fix + +# Please add new fixes to the top of the file, separated by one blank line +# from other fixes. In a comment, include a link to the PR where the change +# requiring the fix was made. + +# Every fix must be tested. See the +# flutter/packages/integration_test/lib/fix_data/README.md file for instructions +# on testing these data driven fixes. + +# For documentation about this file format, see +# https://dart.dev/go/data-driven-fixes. + +# * Fixes in this file are [for CLASS] from the <XXX> file. * + +# Uncomment version & transforms, and follow with fixes. +# version: 1 +# transforms: + +# Before adding a new fix: read instructions at the top of this file. diff --git a/packages/integration_test/pubspec.yaml b/packages/integration_test/pubspec.yaml index 6d02676c771dc..b50eb44ce5b62 100644 --- a/packages/integration_test/pubspec.yaml +++ b/packages/integration_test/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: flutter_test: sdk: flutter path: 1.8.3 - vm_service: 11.6.0 + vm_service: 11.7.1 async: 2.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" boolean_selector: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -22,8 +22,7 @@ dependencies: collection: 1.17.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" fake_async: 1.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" file: 6.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - js: 0.6.7 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - matcher: 0.12.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + matcher: 0.12.16 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" material_color_utilities: 0.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" meta: 1.9.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" source_span: 1.10.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" @@ -32,8 +31,9 @@ dependencies: string_scanner: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" sync_http: 0.3.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" term_glyph: 1.2.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" - test_api: 0.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + test_api: 0.6.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" vector_math: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + web: 0.1.4-beta # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" webdriver: 3.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" flutter: @@ -45,4 +45,4 @@ flutter: ios: pluginClass: IntegrationTestPlugin -# PUBSPEC CHECKSUM: 4c0e +# PUBSPEC CHECKSUM: 3234 diff --git a/packages/integration_test/test/binding_test.dart b/packages/integration_test/test/binding_test.dart index 9e4fc86292250..bc02524475fb8 100644 --- a/packages/integration_test/test/binding_test.dart +++ b/packages/integration_test/test/binding_test.dart @@ -161,10 +161,8 @@ class FakeVM extends Fake implements vm.VmService { return vm.Timestamp(timestamp: lastTimeStamp); } - List<String> recordedStreams = <String>[]; @override Future<vm.Success> setVMTimelineFlags(List<String> recordedStreams) async { - recordedStreams = recordedStreams; return vm.Success(); } diff --git a/packages/integration_test/test_fixes/.dartignore b/packages/integration_test/test_fixes/.dartignore new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/integration_test/test_fixes/analysis_options.yaml b/packages/integration_test/test_fixes/analysis_options.yaml new file mode 100644 index 0000000000000..7cca7b1d5ce21 --- /dev/null +++ b/packages/integration_test/test_fixes/analysis_options.yaml @@ -0,0 +1 @@ +# This ensures that parent analysis options do not accidentally break the fix tests. diff --git a/packages/integration_test/test_fixes/integration_test/binding/integration_test_widgets_flutter_binding.dart b/packages/integration_test/test_fixes/integration_test/binding/integration_test_widgets_flutter_binding.dart new file mode 100644 index 0000000000000..2360ceccd7e75 --- /dev/null +++ b/packages/integration_test/test_fixes/integration_test/binding/integration_test_widgets_flutter_binding.dart @@ -0,0 +1,15 @@ +// 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:integration_test/integration_test.dart'; + +void main() { + final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + binding.runTest( + () async { }, + () { }, + // Changes made in https://github.com/flutter/flutter/pull/89952 + timeout: Duration(minutes: 30), + ); +} diff --git a/packages/integration_test/test_fixes/integration_test/binding/integration_test_widgets_flutter_binding.dart.expect b/packages/integration_test/test_fixes/integration_test/binding/integration_test_widgets_flutter_binding.dart.expect new file mode 100644 index 0000000000000..8abee159a5b13 --- /dev/null +++ b/packages/integration_test/test_fixes/integration_test/binding/integration_test_widgets_flutter_binding.dart.expect @@ -0,0 +1,13 @@ +// 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:integration_test/integration_test.dart'; + +void main() { + final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + binding.runTest( + () async { }, + () { }, + ); +}