diff --git a/.ci.yaml b/.ci.yaml index dcf183eff6baa..8322c8c8b45d2 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -26,7 +26,7 @@ platform_properties: [ {"dependency": "curl"} ] - os: Linux + os: Debian device_type: none linux_android: properties: @@ -125,12 +125,35 @@ platform_properties: [ {"dependency": "xcode"}, {"dependency": "gems"}, - {"dependency": "ios_signing"} + {"dependency": "apple_signing"} ] os: Mac-12 cpu: x86 device_os: iOS-15.1 xcode: 13a233 + mac_arm64_ios: + properties: + caches: >- + [ + {"name":"builder_mac_devicelab","path":"builder"}, + {"name":"chrome_and_driver_96","path":"chrome"}, + {"name":"flutter_sdk","path":"flutter sdk"}, + {"name":"gradle","path":"gradle"}, + {"name":"openjdk","path":"java11"}, + {"name":"pub_cache","path":".pub-cache"}, + {"name":"xcode_binary","path":"xcode_binary"}, + {"name":"osx_sdk_13a233","path":"osx_sdk"} + ] + dependencies: >- + [ + {"dependency": "xcode"}, + {"dependency": "gems"}, + {"dependency": "apple_signing"} + ] + os: Mac-12 + cpu: arm64 + device_os: iOS-15.1 + xcode: 13a233 windows: properties: caches: >- @@ -182,32 +205,6 @@ targets: validation_name: Analyze scheduler: luci - - name: Linux build_aar_module_test - recipe: devicelab/devicelab_drone - timeout: 60 - properties: - add_recipes_cq: "true" - caches: >- - [ - {"name":"gradle","path":"gradle"}, - {"name": "openjdk_11", "path": "java"} - ] - dependencies: >- - [ - {"dependency": "android_sdk", "version": "version:31v8"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, - {"dependency": "open_jdk", "version": "11"} - ] - tags: > - ["devicelab","hostonly"] - task_name: build_aar_module_test - scheduler: luci - runIf: - - dev/** - - packages/flutter_tools/** - - bin/** - - .ci.yaml - - name: Linux build_tests_1_2 recipe: flutter/flutter_drone timeout: 60 @@ -1425,7 +1422,6 @@ targets: tags: > ["devicelab","android","linux"] task_name: android_semantics_integration_test - scheduler: luci - name: Linux_android android_stack_size_test recipe: devicelab/devicelab_drone @@ -1457,6 +1453,17 @@ targets: task_name: animated_image_gc_perf scheduler: luci + - name: Linux_android animated_complex_opacity_perf__e2e_summary + recipe: devicelab/devicelab_drone + presubmit: false + bringup: true + timeout: 60 + properties: + tags: > + ["devicelab","android","linux"] + task_name: animated_complex_opacity_perf__e2e_summary + scheduler: luci + - name: Linux_android animated_placeholder_perf__e2e_summary recipe: devicelab/devicelab_drone presubmit: false @@ -2302,7 +2309,6 @@ targets: ] tags: > ["framework","hostonly"] - scheduler: luci timeout: 60 - name: Linux_android opacity_peephole_one_rect_perf__e2e_summary @@ -2376,7 +2382,6 @@ targets: scheduler: luci - name: Linux_android gradient_dynamic_perf__e2e_summary - bringup: true recipe: devicelab/devicelab_drone presubmit: false timeout: 60 @@ -2384,7 +2389,6 @@ targets: tags: > ["devicelab","android","linux"] task_name: gradient_dynamic_perf__e2e_summary - scheduler: luci - name: Linux_android gradient_consistent_perf__e2e_summary bringup: true @@ -2395,7 +2399,6 @@ targets: tags: > ["devicelab","android","linux"] task_name: gradient_consistent_perf__e2e_summary - scheduler: luci - name: Linux_android gradient_static_perf__e2e_summary bringup: true @@ -2406,7 +2409,6 @@ targets: tags: > ["devicelab","android","linux"] task_name: gradient_static_perf__e2e_summary - scheduler: luci - name: Linux_android android_choreographer_do_frame_test recipe: devicelab/devicelab_drone @@ -2427,57 +2429,46 @@ targets: tags: > ["devicelab","android","linux"] task_name: android_lifecycles_test - scheduler: luci - - name: Mac build_aar_module_test + - name: Mac build_ios_framework_module_test recipe: devicelab/devicelab_drone timeout: 60 properties: - add_recipes_cq: "true" caches: >- [ - {"name":"gradle", "path":"gradle"}, - {"name": "openjdk_11", "path": "java"} + {"name":"gradle","path":"gradle"} ] dependencies: >- [ {"dependency": "android_sdk", "version": "version:31v8"}, - {"dependency": "open_jdk", "version": "11"} + {"dependency": "open_jdk", "version": "11"}, + {"dependency": "xcode"}, + {"dependency": "gems"} ] tags: > ["devicelab","hostonly"] - task_name: build_aar_module_test + task_name: build_ios_framework_module_test + scheduler: luci runIf: - dev/** - packages/flutter_tools/** - bin/** - .ci.yaml - scheduler: luci - - name: Mac build_ios_framework_module_test + - name: Mac_arm64_ios build_ios_framework_module_test recipe: devicelab/devicelab_drone + presubmit: false timeout: 60 properties: - caches: >- - [ - {"name":"gradle","path":"gradle"} - ] - dependencies: >- - [ - {"dependency": "android_sdk", "version": "version:31v8"}, - {"dependency": "open_jdk", "version": "11"}, - {"dependency": "xcode"}, - {"dependency": "gems"} - ] tags: > - ["devicelab","hostonly"] + ["devicelab","ios","mac","arm64"] task_name: build_ios_framework_module_test - scheduler: luci runIf: - dev/** - packages/flutter_tools/** - bin/** - .ci.yaml + scheduler: luci - name: Mac build_tests_1_4 recipe: flutter/flutter_drone @@ -2909,7 +2900,6 @@ targets: tags: > ["framework","hostonly","shard"] test_timeout_secs: "2700" - scheduler: luci runIf: - dev/** - packages/flutter_tools/** @@ -3104,7 +3094,6 @@ targets: tags: > ["devicelab","android","mac"] task_name: entrypoint_dart_registrant - scheduler: luci runIf: - dev/** - packages/flutter_tools/** @@ -3277,6 +3266,7 @@ targets: scheduler: luci - name: Mac_ios cubic_bezier_perf_ios_sksl_warmup__timeline_summary + bringup: true # Flaky https://github.com/flutter/flutter/issues/102230 recipe: devicelab/devicelab_drone presubmit: false timeout: 60 @@ -3326,6 +3316,16 @@ targets: task_name: flutter_gallery_ios__compile scheduler: luci + - name: Mac_arm64_ios flutter_gallery_ios__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","ios","mac","arm64"] + task_name: flutter_gallery_ios__compile + scheduler: luci + - name: Mac_ios flutter_gallery_ios__start_up recipe: devicelab/devicelab_drone presubmit: false @@ -3344,7 +3344,6 @@ targets: tags: > ["devicelab","ios","mac"] task_name: flutter_view_ios__start_up - scheduler: luci - name: Mac_ios hello_world_ios__compile recipe: devicelab/devicelab_drone @@ -3356,6 +3355,16 @@ targets: task_name: hello_world_ios__compile scheduler: luci + - name: Mac_arm64_ios hello_world_ios__compile + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","ios","mac","arm64"] + task_name: hello_world_ios__compile + scheduler: luci + - name: Mac_ios hot_mode_dev_cycle_macos_target__benchmark recipe: devicelab/devicelab_drone timeout: 60 @@ -3363,6 +3372,19 @@ targets: tags: > ["devicelab","ios","mac"] task_name: hot_mode_dev_cycle_macos_target__benchmark + runIf: + - dev/** + - .ci.yaml + scheduler: luci + + - name: Mac_arm64_ios hot_mode_dev_cycle_macos_target__benchmark + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","ios","mac","arm64"] + task_name: hot_mode_dev_cycle_macos_target__benchmark runIf: - dev/** scheduler: luci @@ -3437,6 +3459,16 @@ targets: task_name: ios_app_with_extensions_test scheduler: luci + - name: Mac_arm64_ios ios_app_with_extensions_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","ios","mac","arm64"] + task_name: ios_app_with_extensions_test + scheduler: luci + - name: Mac_ios ios_content_validation_test recipe: devicelab/devicelab_drone presubmit: false @@ -3447,6 +3479,16 @@ targets: task_name: ios_content_validation_test scheduler: luci + - name: Mac_arm64_ios ios_content_validation_test + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","ios","mac","arm64"] + task_name: ios_content_validation_test + scheduler: luci + - name: Mac_ios ios_defines_test recipe: devicelab/devicelab_drone presubmit: false @@ -3476,7 +3518,6 @@ targets: tags: > ["devicelab","ios","mac"] task_name: large_image_changer_perf_ios - scheduler: luci - name: Mac_ios macos_chrome_dev_mode recipe: devicelab/devicelab_drone @@ -3488,6 +3529,16 @@ targets: task_name: macos_chrome_dev_mode scheduler: luci + - name: Mac_arm64_ios macos_chrome_dev_mode + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","ios","mac","arm64"] + task_name: macos_chrome_dev_mode + scheduler: luci + - name: Mac_ios microbenchmarks_ios recipe: devicelab/devicelab_drone presubmit: false @@ -3507,7 +3558,6 @@ targets: tags: > ["devicelab","ios","mac"] task_name: new_gallery_ios__transition_perf - scheduler: luci - name: Mac_ios new_gallery_impeller_ios__transition_perf bringup: true # Flaky https://github.com/flutter/flutter/issues/96401 @@ -3518,7 +3568,6 @@ targets: tags: > ["devicelab","ios","mac"] task_name: new_gallery_impeller_ios__transition_perf - scheduler: luci - name: Mac_ios ios_picture_cache_complexity_scoring_perf__timeline_summary recipe: devicelab/devicelab_drone @@ -3619,7 +3668,6 @@ targets: tags: > ["devicelab","ios","mac"] task_name: hot_mode_dev_cycle_ios__benchmark - scheduler: luci - name: Mac_ios tiles_scroll_perf_ios__timeline_summary recipe: devicelab/devicelab_drone @@ -3639,6 +3687,15 @@ targets: tags: > ["devicelab","ios","mac"] task_name: native_ui_tests_ios + + - name: Mac_arm64_ios native_ui_tests_ios + recipe: devicelab/devicelab_drone + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab","ios","mac","arm64"] + task_name: native_ui_tests_ios scheduler: luci - name: Mac native_ui_tests_macos @@ -3679,32 +3736,20 @@ targets: - .ci.yaml scheduler: luci - - name: Windows build_aar_module_test - bringup: true # Flaky https://github.com/flutter/flutter/issues/102226 + - name: Mac_arm64_ios run_release_test_macos recipe: devicelab/devicelab_drone + presubmit: false timeout: 60 properties: - add_recipes_cq: "true" - caches: >- - [ - {"name":"gradle","path":"gradle"}, - {"name": "openjdk_11", "path": "java"} - ] - dependencies: >- - [ - {"dependency": "android_sdk", "version": "version:31v8"}, - {"dependency": "chrome_and_driver", "version": "version:96.2"}, - {"dependency": "open_jdk", "version": "11"} - ] tags: > - ["devicelab","hostonly"] - task_name: build_aar_module_test - scheduler: luci + ["devicelab","ios","mac","arm64"] + task_name: run_release_test_macos runIf: - dev/** - packages/flutter_tools/** - bin/** - .ci.yaml + scheduler: luci - name: Windows build_tests_1_3 recipe: flutter/flutter_drone diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml index a00102e3287e6..e4a0f5407ebb7 100644 --- a/.github/workflows/scorecards-analysis.yml +++ b/.github/workflows/scorecards-analysis.yml @@ -21,7 +21,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@dcd71f646680f2efd8db4afa5ad64fdcba30e748 + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b with: persist-credentials: false diff --git a/TESTOWNERS b/TESTOWNERS index d48ef1a5494c3..9706dde1c5154 100644 --- a/TESTOWNERS +++ b/TESTOWNERS @@ -78,6 +78,7 @@ /dev/devicelab/bin/tasks/gradient_consistent_perf__e2e_summary.dart @flar @flutter/engine /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/animated_complex_opacity_perf__e2e_summary.dart @jonahwilliams @flutter/engine ## Windows Android DeviceLab tests /dev/devicelab/bin/tasks/basic_material_app_win__compile.dart @zanderso @flutter/tool diff --git a/bin/internal/engine.version b/bin/internal/engine.version index f8a6975cadca4..d13baebf32373 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -586c90c1336f9bba303a4ce6147c212ffffdd5d8 +cd78221a88ae704ae94323344cc793d6ecc20e9c diff --git a/bin/internal/flutter_plugins.version b/bin/internal/flutter_plugins.version index 03bde7b570745..8fcea5085fea7 100644 --- a/bin/internal/flutter_plugins.version +++ b/bin/internal/flutter_plugins.version @@ -1 +1 @@ -6e18c7195678f5f2ff12e79a5fe738280a66fe26 +4ff0157340ea1874ca753bf921807e62c1943099 diff --git a/bin/internal/fuchsia-linux.version b/bin/internal/fuchsia-linux.version index 75b80067c63bd..8fe2d423b387a 100644 --- a/bin/internal/fuchsia-linux.version +++ b/bin/internal/fuchsia-linux.version @@ -1 +1 @@ -9g8p_giFbkP4781dwqfOd3BTojVaMQTirYYfd07wLLEC +YyokSAVV4CDbCsZkAKj1Qiony-m4sjI-Pvwb1wX18ZkC diff --git a/bin/internal/fuchsia-mac.version b/bin/internal/fuchsia-mac.version index 00c4bf56d3a74..1bcedb32f599e 100644 --- a/bin/internal/fuchsia-mac.version +++ b/bin/internal/fuchsia-mac.version @@ -1 +1 @@ -p0aZoDAqDDEeIK9bKWRhrH1nXxArDPLqvfGHm4vMVFkC +Jki3kG8_PI7MxcaiJMq3S_hlIs_wVuqBMOWOnuEHaw0C diff --git a/dev/benchmarks/macrobenchmarks/android/app/src/main/AndroidManifest.xml b/dev/benchmarks/macrobenchmarks/android/app/src/main/AndroidManifest.xml index c0f2c6be78295..6453a039057a8 100644 --- a/dev/benchmarks/macrobenchmarks/android/app/src/main/AndroidManifest.xml +++ b/dev/benchmarks/macrobenchmarks/android/app/src/main/AndroidManifest.xml @@ -21,6 +21,7 @@ found in the LICENSE file. --> android:label="macrobenchmarks" android:icon="@mipmap/ic_launcher"> const GradientPerfHomePage(), ...gradientPerfRoutes, + kAnimatedComplexOpacityPerfRouteName: (BuildContext context) => const AnimatedComplexOpacity(), }, ); } @@ -257,6 +259,13 @@ class HomePage extends StatelessWidget { Navigator.pushNamed(context, kGradientPerfRouteName); }, ), + ElevatedButton( + key: const Key(kAnimatedComplexOpacityPerfRouteName), + child: const Text('Animated complex opacity perf'), + onPressed: () { + Navigator.pushNamed(context, kAnimatedComplexOpacityPerfRouteName); + }, + ), ], ), ); diff --git a/dev/benchmarks/macrobenchmarks/lib/src/animated_complex_opacity.dart b/dev/benchmarks/macrobenchmarks/lib/src/animated_complex_opacity.dart new file mode 100644 index 0000000000000..90b2276e343bc --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/lib/src/animated_complex_opacity.dart @@ -0,0 +1,59 @@ +// 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'; + +// Various tests to verify that Aniamted opacity layers (i.e. FadeTransition) do not +// dirty children even without explicit repaint boundaries. These intentionally use +// text to ensure we don't measure the opacity peephole case. +class AnimatedComplexOpacity extends StatefulWidget { + const AnimatedComplexOpacity({ super.key }); + + @override + State createState() => _AnimatedComplexOpacityState(); +} + +class _AnimatedComplexOpacityState extends State with SingleTickerProviderStateMixin { + late final AnimationController controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 5000)); + late final Animation animation = controller.drive(Tween(begin: 0.0, end: 1.0)); + + @override + void initState() { + super.initState(); + controller.forward(from: 0.0); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: ListView( + children: [ + for (int i = 0; i < 20; i++) + FadeTransition(opacity: animation, child: Center( + child: Transform.scale(scale: 1.01, child: const ModeratelyComplexWidget()), + )) + ], + ), + ), + ); + } +} + +class ModeratelyComplexWidget extends StatelessWidget { + const ModeratelyComplexWidget({ super.key }); + + @override + Widget build(BuildContext context) { + return const Material( + elevation: 10, + clipBehavior: Clip.hardEdge, + child: ListTile( + leading: Icon(Icons.abc, size: 24), + title: DecoratedBox(decoration: BoxDecoration(color: Colors.red), child: Text('Hello World')), + trailing: FlutterLogo(), + ), + ); + } +} diff --git a/dev/benchmarks/macrobenchmarks/test/animated_complex_opacity_perf_e2e.dart b/dev/benchmarks/macrobenchmarks/test/animated_complex_opacity_perf_e2e.dart new file mode 100644 index 0000000000000..cd6e4f92f5c77 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test/animated_complex_opacity_perf_e2e.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:macrobenchmarks/common.dart'; + +import 'util.dart'; + +void main() { + macroPerfTestE2E( + 'animated_complex_opacity_perf', + kAnimatedComplexOpacityPerfRouteName, + pageDelay: const Duration(seconds: 1), + duration: const Duration(seconds: 5), + ); +} diff --git a/dev/ci/docker_linux/Dockerfile b/dev/ci/docker_linux/Dockerfile index 9263abdbacc7c..d6a930ab5e00b 100644 --- a/dev/ci/docker_linux/Dockerfile +++ b/dev/ci/docker_linux/Dockerfile @@ -12,7 +12,7 @@ # Last manual update 2021-09-24 (changing this comment will re-build image) -FROM debian@sha256:78fd65998de7a59a001d792fe2d3a6d2ea25b6f3f068e5c84881250373577414 +FROM debian@sha256:f75d8a3ac10acdaa9be6052ea5f28bcfa56015ff02298831994bd3e6d66f7e57 MAINTAINER Flutter Developers RUN apt-get update -y && \ diff --git a/dev/conductor/core/lib/src/codesign.dart b/dev/conductor/core/lib/src/codesign.dart index 271a5bcd1823c..32b3b7f0efe36 100644 --- a/dev/conductor/core/lib/src/codesign.dart +++ b/dev/conductor/core/lib/src/codesign.dart @@ -189,6 +189,8 @@ class CodesignCommand extends Command { 'artifacts/engine/darwin-x64-release/FlutterMacOS.framework/Versions/A/FlutterMacOS', 'artifacts/engine/darwin-x64/FlutterMacOS.framework/Versions/A/FlutterMacOS', 'artifacts/engine/darwin-x64/font-subset', + 'artifacts/engine/darwin-x64/impellerc', + 'artifacts/engine/darwin-x64/libtessellator.dylib', 'artifacts/engine/ios-profile/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter', 'artifacts/engine/ios-profile/Flutter.xcframework/ios-arm64_x86_64-simulator/Flutter.framework/Flutter', 'artifacts/engine/ios-release/Flutter.xcframework/ios-arm64/Flutter.framework/Flutter', diff --git a/dev/devicelab/bin/tasks/animated_complex_opacity_perf__e2e_summary.dart b/dev/devicelab/bin/tasks/animated_complex_opacity_perf__e2e_summary.dart new file mode 100644 index 0000000000000..64f073714bde0 --- /dev/null +++ b/dev/devicelab/bin/tasks/animated_complex_opacity_perf__e2e_summary.dart @@ -0,0 +1,14 @@ +// 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_devicelab/framework/devices.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/tasks/perf_tests.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(createAnimatedComplexOpacityPerfE2ETest()); +} diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index a004d8580d8d5..5618cc84613a7 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -577,6 +577,13 @@ TaskFunction createGradientStaticPerfE2ETest() { ).run; } +TaskFunction createAnimatedComplexOpacityPerfE2ETest() { + return PerfTest.e2e( + '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks', + 'test/animated_complex_opacity_perf_e2e.dart', + ).run; +} + Map _average(List> results, int iterations) { final Map tally = {}; for (final Map item in results) { diff --git a/dev/integration_tests/ui/lib/keyboard_textfield.dart b/dev/integration_tests/ui/lib/keyboard_textfield.dart index 4917657c6036a..0f9268a6fe289 100644 --- a/dev/integration_tests/ui/lib/keyboard_textfield.dart +++ b/dev/integration_tests/ui/lib/keyboard_textfield.dart @@ -62,11 +62,10 @@ class _MyHomePageState extends State { Text('$offset', key: const ValueKey(keys.kOffsetText), ), - Text( - isSoftKeyboardVisible ? 'keyboard visible' : 'keyboard hidden', - key: const ValueKey(keys.kKeyboardVisibleView), + if (isSoftKeyboardVisible) const Text( + 'keyboard visible', + key: ValueKey(keys.kKeyboardVisibleView), ), - const ElevatedButton(onPressed: debugDumpApp, child: Text('dump app')), Expanded( child: ListView( key: const ValueKey(keys.kListView), diff --git a/dev/integration_tests/ui/test_driver/keyboard_textfield_test.dart b/dev/integration_tests/ui/test_driver/keyboard_textfield_test.dart index d8353873cc18e..286fc396b1d50 100644 --- a/dev/integration_tests/ui/test_driver/keyboard_textfield_test.dart +++ b/dev/integration_tests/ui/test_driver/keyboard_textfield_test.dart @@ -40,23 +40,10 @@ void main() { // Bring up keyboard await driver.tap(textFieldFinder); - const int keyboardTimeout = 3; - bool keyboardVisible = false; - for (int i = 0; i < keyboardTimeout; i++) { - await Future.delayed(const Duration(seconds: 1)); - final String keyboardVisibilityText = await driver.getText(keyboardVisibilityIndicatorFinder); - keyboardVisible = keyboardVisibilityText == 'keyboard visible'; - if (keyboardVisible) { - break; - } - } - - if (!keyboardVisible) { - await driver.tap(find.text('dump app')); - } - - // TODO(jmagman): Remove timeout once flake has been diagnosed. https://github.com/flutter/flutter/issues/96787 - expect(keyboardVisible, isTrue); + // The blinking cursor may have animation. Do not wait for it to finish. + await driver.runUnsynchronized(() async { + await driver.waitFor(keyboardVisibilityIndicatorFinder); + }); // Ensure that TextField is visible again await driver.waitFor(textFieldFinder); diff --git a/examples/image_list/macos/.gitignore b/examples/image_list/macos/.gitignore new file mode 100644 index 0000000000000..d2fd3772308cc --- /dev/null +++ b/examples/image_list/macos/.gitignore @@ -0,0 +1,6 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/xcuserdata/ diff --git a/examples/image_list/macos/Flutter/Flutter-Debug.xcconfig b/examples/image_list/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000000000..c2efd0b608ba8 --- /dev/null +++ b/examples/image_list/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/examples/image_list/macos/Flutter/Flutter-Release.xcconfig b/examples/image_list/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000000000..c2efd0b608ba8 --- /dev/null +++ b/examples/image_list/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/examples/image_list/macos/Runner.xcodeproj/project.pbxproj b/examples/image_list/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000000000..65e1baa895ba6 --- /dev/null +++ b/examples/image_list/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,572 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* image_list.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "image_list.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* image_list.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* image_list.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 0930; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/examples/image_list/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/image_list/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000..18d981003d68d --- /dev/null +++ b/examples/image_list/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/image_list/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/examples/image_list/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000000..8d8f1dd636d6d --- /dev/null +++ b/examples/image_list/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/image_list/macos/Runner.xcworkspace/contents.xcworkspacedata b/examples/image_list/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000..1d526a16ed0f1 --- /dev/null +++ b/examples/image_list/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/examples/image_list/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/examples/image_list/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000..18d981003d68d --- /dev/null +++ b/examples/image_list/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/examples/image_list/macos/Runner/AppDelegate.swift b/examples/image_list/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000000000..d080d41951d35 --- /dev/null +++ b/examples/image_list/macos/Runner/AppDelegate.swift @@ -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 Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000000..a2ec33f19f110 --- /dev/null +++ b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000..3c4935a7ca84f Binary files /dev/null and b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000..ed4cc16421680 Binary files /dev/null and b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000000..483be61389733 Binary files /dev/null and b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000000000..bcbf36df2f2aa Binary files /dev/null and b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000000000..9c0a652864769 Binary files /dev/null and b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000000000..e71a726136a47 Binary files /dev/null and b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000000..8a31fe2dd3f91 Binary files /dev/null and b/examples/image_list/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/examples/image_list/macos/Runner/Base.lproj/MainMenu.xib b/examples/image_list/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000000000..537341abf994b --- /dev/null +++ b/examples/image_list/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,339 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/image_list/macos/Runner/Configs/AppInfo.xcconfig b/examples/image_list/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000000000..55939c7dca21e --- /dev/null +++ b/examples/image_list/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = image_list + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.imageList + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. diff --git a/examples/image_list/macos/Runner/Configs/Debug.xcconfig b/examples/image_list/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000000000..36b0fd9464f45 --- /dev/null +++ b/examples/image_list/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/examples/image_list/macos/Runner/Configs/Release.xcconfig b/examples/image_list/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000000000..dff4f49561c81 --- /dev/null +++ b/examples/image_list/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/examples/image_list/macos/Runner/Configs/Warnings.xcconfig b/examples/image_list/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000000000..42bcbf4780b18 --- /dev/null +++ b/examples/image_list/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/examples/image_list/macos/Runner/DebugProfile.entitlements b/examples/image_list/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000000000..dddb8a30c851e --- /dev/null +++ b/examples/image_list/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/examples/image_list/macos/Runner/Info.plist b/examples/image_list/macos/Runner/Info.plist new file mode 100644 index 0000000000000..4789daa6a443e --- /dev/null +++ b/examples/image_list/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/examples/image_list/macos/Runner/MainFlutterWindow.swift b/examples/image_list/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000000000..a97a96274ee93 --- /dev/null +++ b/examples/image_list/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,19 @@ +// 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 Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/examples/image_list/macos/Runner/Release.entitlements b/examples/image_list/macos/Runner/Release.entitlements new file mode 100644 index 0000000000000..852fa1a4728ae --- /dev/null +++ b/examples/image_list/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/packages/flutter/lib/src/cupertino/icons.dart b/packages/flutter/lib/src/cupertino/icons.dart index d214511b3a382..fd02f2350fcf9 100644 --- a/packages/flutter/lib/src/cupertino/icons.dart +++ b/packages/flutter/lib/src/cupertino/icons.dart @@ -912,7 +912,7 @@ class CupertinoIcons { static const IconData mail_solid = IconData(0xf423, fontFamily: iconFont, fontPackage: iconFontPackage); /// location — Cupertino icon for a location pin. - static const IconData location = IconData(0xf455, fontFamily: iconFont, fontPackage: iconFontPackage); + static const IconData location = IconData(0xf6ee, fontFamily: iconFont, fontPackage: iconFontPackage); /// placemark_fill — Cupertino icon for a location pin. This icon is filled in. /// This is the same icon as [placemark_fill] in cupertino_icons 1.0.0+. diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index 39c7bd39c339c..045d9d8325ac4 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -767,17 +767,33 @@ class _AppBarState extends State { } void _handleScrollNotification(ScrollNotification notification) { - if (notification is ScrollUpdateNotification) { - final bool oldScrolledUnder = _scrolledUnder; - _scrolledUnder = notification.depth == 0 - && notification.metrics.extentBefore > 0 - && notification.metrics.axis == Axis.vertical; - if (_scrolledUnder != oldScrolledUnder) { - setState(() { - // React to a change in MaterialState.scrolledUnder - }); + final bool oldScrolledUnder = _scrolledUnder; + final ScrollMetrics metrics = notification.metrics; + + if (notification.depth != 0) { + _scrolledUnder = false; + } else { + switch (metrics.axisDirection) { + case AxisDirection.up: + // Scroll view is reversed + _scrolledUnder = metrics.extentAfter > 0; + break; + case AxisDirection.down: + _scrolledUnder = metrics.extentBefore > 0; + break; + case AxisDirection.right: + case AxisDirection.left: + // Scrolled under is only supported in the vertical axis. + _scrolledUnder = false; + break; } } + + if (_scrolledUnder != oldScrolledUnder) { + setState(() { + // React to a change in MaterialState.scrolledUnder + }); + } } Color _resolveColor(Set states, Color? widgetColor, Color? themeColor, Color defaultColor) { diff --git a/packages/flutter/lib/src/material/range_slider.dart b/packages/flutter/lib/src/material/range_slider.dart index eff3119b3e87b..bb86fde4859cf 100644 --- a/packages/flutter/lib/src/material/range_slider.dart +++ b/packages/flutter/lib/src/material/range_slider.dart @@ -285,7 +285,9 @@ class RangeSlider extends StatefulWidget { /// If null, the slider is continuous. final int? divisions; - /// Labels to show as text in the [SliderThemeData.rangeValueIndicatorShape]. + /// Labels to show as text in the [SliderThemeData.rangeValueIndicatorShape] + /// when the slider is active and [SliderThemeData.showValueIndicator] + /// is satisfied. /// /// There are two labels: one for the start thumb and one for the end thumb. /// @@ -1491,7 +1493,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix double value, double increasedValue, double decreasedValue, - String? label, VoidCallback increaseAction, VoidCallback decreaseAction, ) { @@ -1503,7 +1504,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix config.onIncrease = increaseAction; config.onDecrease = decreaseAction; } - config.label = label ?? ''; + if (semanticFormatterCallback != null) { config.value = semanticFormatterCallback!(_state._lerp(value)); config.increasedValue = semanticFormatterCallback!(_state._lerp(increasedValue)); @@ -1529,7 +1530,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix values.start, _increasedStartValue, _decreasedStartValue, - labels?.start, _increaseStartAction, _decreaseStartAction, ); @@ -1537,7 +1537,6 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix values.end, _increasedEndValue, _decreasedEndValue, - labels?.end, _increaseEndAction, _decreaseEndAction, ); diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index 3110d05c3f5b6..5cb0ee675dc42 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -317,7 +317,8 @@ class Slider extends StatefulWidget { /// If null, the slider is continuous. final int? divisions; - /// A label to show above the slider when the slider is active. + /// A label to show above the slider when the slider is active and + /// [SliderThemeData.showValueIndicator] is satisfied. /// /// It is used to display the value of a discrete slider, and it is displayed /// as part of the value indicator shape. @@ -1506,7 +1507,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { config.onIncrease = increaseAction; config.onDecrease = decreaseAction; } - config.label = _label ?? ''; + if (semanticFormatterCallback != null) { config.value = semanticFormatterCallback!(_state._lerp(value)); config.increasedValue = semanticFormatterCallback!(_state._lerp((value + _semanticActionUnit).clamp(0.0, 1.0))); diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart index 7b49f81eb15e9..fd70a6b5eec2d 100644 --- a/packages/flutter/lib/src/material/tooltip.dart +++ b/packages/flutter/lib/src/material/tooltip.dart @@ -698,7 +698,7 @@ class TooltipState extends State with SingleTickerProviderStateMixin { _enableFeedback = widget.enableFeedback ?? tooltipTheme.enableFeedback ?? _defaultEnableFeedback; Widget result = Semantics( - label: _excludeFromSemantics + tooltip: _excludeFromSemantics ? null : _tooltipMessage, child: widget.child, diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index 2e59a4055faf2..ed1a0e28f5dd0 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -235,10 +235,13 @@ mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, Gesture /// /// See [dart:ui.PlatformDispatcher.onMetricsChanged]. @protected + @visibleForTesting void handleMetricsChanged() { assert(renderView != null); renderView.configuration = createViewConfiguration(); - scheduleForcedFrame(); + if (renderView.child != null) { + scheduleForcedFrame(); + } } /// Called when the platform text scale factor changes. diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 7d204b807671b..2b62dafa78d2c 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -899,6 +899,10 @@ class RenderOpacity extends RenderProxyBox { } assert(needsCompositing); layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer as OpacityLayer?); + assert(() { + layer!.debugCreator = debugCreator; + return true; + }()); } } @@ -1006,6 +1010,10 @@ mixin RenderAnimatedOpacityMixin on RenderObjectWithChil } assert(needsCompositing); layer = context.pushOpacity(offset, _alpha!, super.paint, oldLayer: layer as OpacityLayer?); + assert(() { + layer!.debugCreator = debugCreator; + return true; + }()); } } @@ -1115,6 +1123,10 @@ class RenderShaderMask extends RenderProxyBox { ..maskRect = offset & size ..blendMode = _blendMode; context.pushLayer(layer!, super.paint, offset); + assert(() { + layer!.debugCreator = debugCreator; + return true; + }()); } else { layer = null; } @@ -1181,6 +1193,10 @@ class RenderBackdropFilter extends RenderProxyBox { layer!.filter = _filter; layer!.blendMode = _blendMode; context.pushLayer(layer!, super.paint, offset); + assert(() { + layer!.debugCreator = debugCreator; + return true; + }()); } else { layer = null; } @@ -2426,6 +2442,10 @@ class RenderTransform extends RenderProxyBox { layer = ImageFilterLayer(imageFilter: filter); } context.pushLayer(layer!, super.paint, offset); + assert(() { + layer!.debugCreator = debugCreator; + return true; + }()); } } } @@ -3859,6 +3879,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox { AttributedString? attributedIncreasedValue, AttributedString? attributedDecreasedValue, AttributedString? attributedHint, + String? tooltip, SemanticsHintOverrides? hintOverrides, TextDirection? textDirection, SemanticsSortKey? sortKey, @@ -3917,6 +3938,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox { _attributedIncreasedValue = attributedIncreasedValue, _attributedDecreasedValue = attributedDecreasedValue, _attributedHint = attributedHint, + _tooltip = tooltip, _hintOverrides = hintOverrides, _textDirection = textDirection, _sortKey = sortKey, @@ -4311,6 +4333,18 @@ class RenderSemanticsAnnotations extends RenderProxyBox { markNeedsSemanticsUpdate(); } + /// If non-null, sets the [SemanticsNode.tooltip] semantic to the given value. + /// + /// The reading direction is given by [textDirection]. + String? get tooltip => _tooltip; + String? _tooltip; + set tooltip(String? value) { + if (_tooltip == value) + return; + _tooltip = value; + markNeedsSemanticsUpdate(); + } + /// If non-null, sets the [SemanticsConfiguration.hintOverrides] to the given value. SemanticsHintOverrides? get hintOverrides => _hintOverrides; SemanticsHintOverrides? _hintOverrides; @@ -4843,6 +4877,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox { config.attributedDecreasedValue = attributedDecreasedValue!; if (attributedHint != null) config.attributedHint = attributedHint!; + if (tooltip != null) + config.tooltip = tooltip!; if (hintOverrides != null && hintOverrides!.isNotEmpty) config.hintOverrides = hintOverrides; if (scopesRoute != null) @@ -5196,7 +5232,10 @@ class RenderLeaderLayer extends RenderProxyBox { ..offset = offset; } context.pushLayer(layer!, super.paint, Offset.zero); - assert(layer != null); + assert(() { + layer!.debugCreator = debugCreator; + return true; + }()); } @override @@ -5408,6 +5447,10 @@ class RenderFollowerLayer extends RenderProxyBox { double.infinity, ), ); + assert(() { + layer!.debugCreator = debugCreator; + return true; + }()); } @override diff --git a/packages/flutter/lib/src/rendering/proxy_sliver.dart b/packages/flutter/lib/src/rendering/proxy_sliver.dart index 520e0d1bba2bc..3328aac677862 100644 --- a/packages/flutter/lib/src/rendering/proxy_sliver.dart +++ b/packages/flutter/lib/src/rendering/proxy_sliver.dart @@ -173,6 +173,10 @@ class RenderSliverOpacity extends RenderProxySliver { super.paint, oldLayer: layer as OpacityLayer?, ); + assert(() { + layer!.debugCreator = debugCreator; + return true; + }()); } } diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index 851cd5e8819f9..a7373e589d195 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -316,6 +316,7 @@ class SemanticsData with Diagnosticable { required this.attributedIncreasedValue, required this.attributedDecreasedValue, required this.attributedHint, + required this.tooltip, required this.textDirection, required this.rect, required this.elevation, @@ -339,6 +340,7 @@ class SemanticsData with Diagnosticable { assert(attributedDecreasedValue != null), assert(attributedIncreasedValue != null), assert(attributedHint != null), + assert(tooltip == '' || textDirection != null, 'A SemanticsData object with tooltip "$tooltip" had a null textDirection.'), assert(attributedLabel.string == '' || textDirection != null, 'A SemanticsData object with label "${attributedLabel.string}" had a null textDirection.'), assert(attributedValue.string == '' || textDirection != null, 'A SemanticsData object with value "${attributedValue.string}" had a null textDirection.'), assert(attributedDecreasedValue.string == '' || textDirection != null, 'A SemanticsData object with decreasedValue "${attributedDecreasedValue.string}" had a null textDirection.'), @@ -429,6 +431,11 @@ class SemanticsData with Diagnosticable { /// See also [hint], which exposes just the raw text. final AttributedString attributedHint; + /// A textual description of the widget's tooltip. + /// + /// The reading direction is given by [textDirection]. + final String tooltip; + /// The reading direction for the text in [label], [value], /// [increasedValue], [decreasedValue], and [hint]. final TextDirection? textDirection; @@ -587,6 +594,7 @@ class SemanticsData with Diagnosticable { properties.add(AttributedStringProperty('increasedValue', attributedIncreasedValue)); properties.add(AttributedStringProperty('decreasedValue', attributedDecreasedValue)); properties.add(AttributedStringProperty('hint', attributedHint)); + properties.add(StringProperty('tooltip', tooltip, defaultValue: '')); properties.add(EnumProperty('textDirection', textDirection, defaultValue: null)); if (textSelection?.isValid ?? false) properties.add(MessageProperty('textSelection', '[${textSelection!.start}, ${textSelection!.end}]')); @@ -610,6 +618,7 @@ class SemanticsData with Diagnosticable { && other.attributedIncreasedValue == attributedIncreasedValue && other.attributedDecreasedValue == attributedDecreasedValue && other.attributedHint == attributedHint + && other.tooltip == tooltip && other.textDirection == textDirection && other.rect == rect && setEquals(other.tags, tags) @@ -637,6 +646,7 @@ class SemanticsData with Diagnosticable { attributedIncreasedValue, attributedDecreasedValue, attributedHint, + tooltip, textDirection, rect, tags, @@ -648,8 +658,8 @@ class SemanticsData with Diagnosticable { scrollExtentMin, platformViewId, maxValueLength, - currentValueLength, Object.hash( + currentValueLength, transform, elevation, thickness, @@ -785,6 +795,7 @@ class SemanticsProperties extends DiagnosticableTree { this.decreasedValue, this.attributedDecreasedValue, this.hint, + this.tooltip, this.attributedHint, this.hintOverrides, this.textDirection, @@ -1178,6 +1189,16 @@ class SemanticsProperties extends DiagnosticableTree { /// * [hint] for a plain string version of this property. final AttributedString? attributedHint; + /// Provides a textual description of the widget's tooltip. + /// + /// In Android, this property sets the `AccessibilityNodeInfo.setTooltipText`. + /// In iOS, this property is appended to the end of the + /// `UIAccessibilityElement.accessibilityLabel`. + /// + /// If a [tooltip] is provided, there must either by an ambient + /// [Directionality] or an explicit [textDirection] should be provided. + final String? tooltip; + /// Provides hint values which override the default hints on supported /// platforms. /// @@ -1469,6 +1490,7 @@ class SemanticsProperties extends DiagnosticableTree { properties.add(AttributedStringProperty('attributedDecreasedValue', attributedDecreasedValue, defaultValue: null)); properties.add(StringProperty('hint', hint, defaultValue: null)); properties.add(AttributedStringProperty('attributedHint', attributedHint, defaultValue: null)); + properties.add(StringProperty('tooltip', tooltip)); properties.add(EnumProperty('textDirection', textDirection, defaultValue: null)); properties.add(DiagnosticsProperty('sortKey', sortKey, defaultValue: null)); properties.add(DiagnosticsProperty('hintOverrides', hintOverrides, defaultValue: null)); @@ -1898,6 +1920,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { || _attributedValue != config.attributedValue || _attributedIncreasedValue != config.attributedIncreasedValue || _attributedDecreasedValue != config.attributedDecreasedValue + || _tooltip != config.tooltip || _flags != config._flags || _textDirection != config.textDirection || _sortKey != config._sortKey @@ -2027,6 +2050,12 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { AttributedString get attributedHint => _attributedHint; AttributedString _attributedHint = _kEmptyConfig.attributedHint; + /// A textual description of the widget's tooltip. + /// + /// The reading direction is given by [textDirection]. + String get tooltip => _tooltip; + String _tooltip = _kEmptyConfig.tooltip; + /// The elevation along the z-axis at which the [rect] of this [SemanticsNode] /// is located above its parent. /// @@ -2235,6 +2264,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { _attributedIncreasedValue = config.attributedIncreasedValue; _attributedDecreasedValue = config.attributedDecreasedValue; _attributedHint = config.attributedHint; + _tooltip = config.tooltip; _hintOverrides = config.hintOverrides; _elevation = config.elevation; _thickness = config.thickness; @@ -2282,6 +2312,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { AttributedString attributedIncreasedValue = _attributedIncreasedValue; AttributedString attributedDecreasedValue = _attributedDecreasedValue; AttributedString attributedHint = _attributedHint; + String tooltip = _tooltip; TextDirection? textDirection = _textDirection; Set? mergedTags = tags == null ? null : Set.of(tags!); TextSelection? textSelection = _textSelection; @@ -2336,6 +2367,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { attributedIncreasedValue = node._attributedIncreasedValue; if (attributedDecreasedValue == null || attributedDecreasedValue.string == '') attributedDecreasedValue = node._attributedDecreasedValue; + if (tooltip == '') + tooltip = node._tooltip; if (node.tags != null) { mergedTags ??= {}; mergedTags!.addAll(node.tags!); @@ -2385,6 +2418,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { attributedIncreasedValue: attributedIncreasedValue, attributedDecreasedValue: attributedDecreasedValue, attributedHint: attributedHint, + tooltip: tooltip, textDirection: textDirection, rect: rect, transform: transform, @@ -2457,6 +2491,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { decreasedValueAttributes: data.attributedDecreasedValue.attributes, hint: data.attributedHint.string, hintAttributes: data.attributedHint.attributes, + tooltip: data.tooltip, textDirection: data.textDirection, textSelectionBase: data.textSelection != null ? data.textSelection!.baseOffset : -1, textSelectionExtent: data.textSelection != null ? data.textSelection!.extentOffset : -1, @@ -2595,6 +2630,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { properties.add(AttributedStringProperty('increasedValue', _attributedIncreasedValue)); properties.add(AttributedStringProperty('decreasedValue', _attributedDecreasedValue)); properties.add(AttributedStringProperty('hint', _attributedHint)); + properties.add(StringProperty('tooltip', _tooltip, defaultValue: '')); properties.add(EnumProperty('textDirection', _textDirection, defaultValue: null)); properties.add(DiagnosticsProperty('sortKey', sortKey, defaultValue: null)); if (_textSelection?.isValid ?? false) @@ -3955,6 +3991,16 @@ class SemanticsConfiguration { _hasBeenAnnotated = true; } + /// A textual description of the widget's tooltip. + /// + /// The reading direction is given by [textDirection]. + String get tooltip => _tooltip; + String _tooltip = ''; + set tooltip(String tooltip) { + _tooltip = tooltip; + _hasBeenAnnotated = true; + } + /// Provides hint values which override the default hints on supported /// platforms. SemanticsHintOverrides? get hintOverrides => _hintOverrides; @@ -4420,6 +4466,8 @@ class SemanticsConfiguration { otherAttributedString: child._attributedHint, otherTextDirection: child.textDirection, ); + if (_tooltip == '') + _tooltip = child._tooltip; _thickness = math.max(_thickness, child._thickness + child._elevation); @@ -4442,6 +4490,7 @@ class SemanticsConfiguration { .._attributedDecreasedValue = _attributedDecreasedValue .._attributedHint = _attributedHint .._hintOverrides = _hintOverrides + .._tooltip = _tooltip .._elevation = _elevation .._thickness = _thickness .._flags = _flags diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 417f09f2a18ea..0d6b956a6f962 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -80,12 +80,69 @@ export 'package:flutter/services.dart' show // BIDIRECTIONAL TEXT SUPPORT +/// An [InheritedElement] that has hundreds of dependencies but will +/// infrequently change. This provides a performance tradeoff where building +/// the [Widget]s is faster but performing updates is slower. +/// +/// | | _UbiquitiousInheritedElement | InheritedElement | +/// |---------------------|------------------------------|------------------| +/// | insert (best case) | O(1) | O(1) | +/// | insert (worst case) | O(1) | O(n) | +/// | search (best case) | O(n) | O(1) | +/// | search (worst case) | O(n) | O(n) | +/// +/// Insert happens when building the [Widget] tree, search happens when updating +/// [Widget]s. +class _UbiquitousInheritedElement extends InheritedElement { + /// Creates an element that uses the given widget as its configuration. + _UbiquitousInheritedElement(super.widget); + + @override + void setDependencies(Element dependent, Object? value) { + // This is where the cost of [InheritedElement] is incurred during build + // time of the widget tree. Omitting this bookkeeping is where the + // performance savings come from. + assert(value == null); + } + + @override + Object? getDependencies(Element dependent) { + return null; + } + + @override + void notifyClients(InheritedWidget oldWidget) { + _recurseChildren(this, (Element element) { + if (element.doesDependOnInheritedElement(this)) { + notifyDependent(oldWidget, element); + } + }); + } + + static void _recurseChildren(Element element, ElementVisitor visitor) { + element.visitChildren((Element child) { + _recurseChildren(child, visitor); + }); + visitor(element); + } +} + +/// See also: +/// +/// * [_UbiquitousInheritedElement], the [Element] for [_UbiquitousInheritedWidget]. +abstract class _UbiquitousInheritedWidget extends InheritedWidget { + const _UbiquitousInheritedWidget({super.key, required super.child}); + + @override + InheritedElement createElement() => _UbiquitousInheritedElement(this); +} + /// A widget that determines the ambient directionality of text and /// text-direction-sensitive render objects. /// /// For example, [Padding] depends on the [Directionality] to resolve /// [EdgeInsetsDirectional] objects into absolute [EdgeInsets] objects. -class Directionality extends InheritedWidget { +class Directionality extends _UbiquitousInheritedWidget { /// Creates a widget that determines the directionality of text and /// text-direction-sensitive render objects. /// @@ -6695,6 +6752,7 @@ class Semantics extends SingleChildRenderObjectWidget { AttributedString? attributedDecreasedValue, String? hint, AttributedString? attributedHint, + String? tooltip, String? onTapHint, String? onLongPressHint, TextDirection? textDirection, @@ -6759,6 +6817,7 @@ class Semantics extends SingleChildRenderObjectWidget { attributedDecreasedValue: attributedDecreasedValue, hint: hint, attributedHint: attributedHint, + tooltip: tooltip, textDirection: textDirection, sortKey: sortKey, tagForChildren: tagForChildren, @@ -6901,6 +6960,7 @@ class Semantics extends SingleChildRenderObjectWidget { attributedIncreasedValue: _effectiveAttributedIncreasedValue, attributedDecreasedValue: _effectiveAttributedDecreasedValue, attributedHint: _effectiveAttributedHint, + tooltip: properties.tooltip, hintOverrides: properties.hintOverrides, textDirection: _getTextDirection(context), sortKey: properties.sortKey, @@ -6936,7 +6996,8 @@ class Semantics extends SingleChildRenderObjectWidget { final bool containsText = properties.attributedLabel != null || properties.label != null || properties.value != null || - properties.hint != null; + properties.hint != null || + properties.tooltip != null; if (!containsText) return null; @@ -6977,6 +7038,7 @@ class Semantics extends SingleChildRenderObjectWidget { ..attributedIncreasedValue = _effectiveAttributedIncreasedValue ..attributedDecreasedValue = _effectiveAttributedDecreasedValue ..attributedHint = _effectiveAttributedHint + ..tooltip = properties.tooltip ..hintOverrides = properties.hintOverrides ..namesRoute = properties.namesRoute ..textDirection = _getTextDirection(context) diff --git a/packages/flutter/lib/src/widgets/color_filter.dart b/packages/flutter/lib/src/widgets/color_filter.dart index b8d5c27367e1b..b604a908bf3ea 100644 --- a/packages/flutter/lib/src/widgets/color_filter.dart +++ b/packages/flutter/lib/src/widgets/color_filter.dart @@ -74,5 +74,9 @@ class _ColorFilterRenderObject extends RenderProxyBox { @override void paint(PaintingContext context, Offset offset) { layer = context.pushColorFilter(offset, colorFilter, super.paint, oldLayer: layer as ColorFilterLayer?); + assert(() { + layer!.debugCreator = debugCreator; + return true; + }()); } } diff --git a/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart b/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart index 0ccae36798f3d..a53437444a3ca 100644 --- a/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart +++ b/packages/flutter/lib/src/widgets/draggable_scrollable_sheet.dart @@ -52,6 +52,7 @@ typedef ScrollableWidgetBuilder = Widget Function( /// constraints provided to an attached sheet change. class DraggableScrollableController extends ChangeNotifier { _DraggableScrollableSheetScrollController? _attachedController; + final Set _animationControllers = {}; /// Get the current size (as a fraction of the parent height) of the attached sheet. double get size { @@ -115,6 +116,7 @@ class DraggableScrollableController extends ChangeNotifier { vsync: _attachedController!.position.context.vsync, value: _attachedController!.extent.currentSize, ); + _animationControllers.add(animationController); _attachedController!.position.goIdle(); // This disables any snapping until the next user interaction with the sheet. _attachedController!.extent.hasDragged = false; @@ -175,6 +177,7 @@ class DraggableScrollableController extends ChangeNotifier { assert(_attachedController == null, 'Draggable scrollable controller is already attached to a sheet.'); _attachedController = scrollController; _attachedController!.extent._currentSize.addListener(notifyListeners); + _attachedController!.onPositionDetached = _disposeAnimationControllers; } void _onExtentReplaced(_DraggableSheetExtent previousExtent) { @@ -193,6 +196,13 @@ class DraggableScrollableController extends ChangeNotifier { _attachedController?.extent._currentSize.removeListener(notifyListeners); _attachedController = null; } + + void _disposeAnimationControllers() { + for (final AnimationController animationController in _animationControllers) { + animationController.dispose(); + } + _animationControllers.clear(); + } } /// A container for a [Scrollable] that responds to drag gestures by resizing @@ -724,6 +734,7 @@ class _DraggableScrollableSheetScrollController extends ScrollController { }) : assert(extent != null); _DraggableSheetExtent extent; + VoidCallback? onPositionDetached; @override _DraggableScrollableSheetScrollPosition createScrollPosition( @@ -764,6 +775,12 @@ class _DraggableScrollableSheetScrollController extends ScrollController { } extent.updateSize(extent.initialSize, position.context.notificationContext!); } + + @override + void detach(ScrollPosition position) { + onPositionDetached?.call(); + super.detach(position); + } } /// A scroll position that manages scroll activities for diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 8e477b4032cb7..be622d5ba3bda 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -2514,6 +2514,11 @@ class EditableTextState extends State with AutomaticKeepAliveClien _showCaretOnScreenScheduled = true; SchedulerBinding.instance.addPostFrameCallback((Duration _) { _showCaretOnScreenScheduled = false; + + // if cursor is inactive, e.g. while selecting text, do not jump away + if (!_cursorActive) { + return; + } if (_currentCaretRect == null || !_scrollController.hasClients) { return; } diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 654c83f6a1181..cc42fc59f63df 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -4197,6 +4197,11 @@ abstract class Element extends DiagnosticableTree implements BuildContext { return true; } + /// Returns `true` if [dependOnInheritedElement] was previously called with [ancestor]. + @protected + bool doesDependOnInheritedElement(InheritedElement ancestor) => + _dependencies != null && _dependencies!.contains(ancestor); + @override InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) { assert(ancestor != null); diff --git a/packages/flutter/lib/src/widgets/image_filter.dart b/packages/flutter/lib/src/widgets/image_filter.dart index 8546ea969f015..67f9ae459fecc 100644 --- a/packages/flutter/lib/src/widgets/image_filter.dart +++ b/packages/flutter/lib/src/widgets/image_filter.dart @@ -11,6 +11,19 @@ import 'framework.dart'; /// Applies an [ImageFilter] to its child. /// +/// An image filter will always apply its filter operation to the child widget, +/// even if said filter is conceptually a "no-op", such as an ImageFilter.blur +/// with a radius of 0 or an ImageFilter.matrix with an identity matrix. Setting +/// [ImageFiltered.enabled] to `false` is a more efficient manner of disabling +/// an image filter. +/// +/// The framework does not attempt to optimize out "no-op" filters because it +/// cannot tell the difference between an intentional no-op and a filter that is +/// only incidentally a no-op. Consider an ImageFilter.matrix that is animated +/// and happens to pass through the identity matrix. If the framework identified it +/// as a no-op it would drop and then recreate the layer during the animation which +/// would be more expensive than keeping it around. +/// /// {@youtube 560 315 https://www.youtube.com/watch?v=7Lftorq4i2o} /// /// See also: @@ -27,17 +40,27 @@ class ImageFiltered extends SingleChildRenderObjectWidget { super.key, required this.imageFilter, super.child, + this.enabled = true, }) : assert(imageFilter != null); /// The image filter to apply to the child of this widget. final ImageFilter imageFilter; + /// Whether or not to apply the image filter opation to the child of this + /// widget. + /// + /// Prefer setting enabled to `false` instead of creating a "no-op" filter + /// type for performance reasons. + final bool enabled; + @override - RenderObject createRenderObject(BuildContext context) => _ImageFilterRenderObject(imageFilter); + RenderObject createRenderObject(BuildContext context) => _ImageFilterRenderObject(imageFilter, enabled); @override void updateRenderObject(BuildContext context, RenderObject renderObject) { - (renderObject as _ImageFilterRenderObject).imageFilter = imageFilter; + (renderObject as _ImageFilterRenderObject) + ..enabled = enabled + ..imageFilter = imageFilter; } @override @@ -48,7 +71,17 @@ class ImageFiltered extends SingleChildRenderObjectWidget { } class _ImageFilterRenderObject extends RenderProxyBox { - _ImageFilterRenderObject(this._imageFilter); + _ImageFilterRenderObject(this._imageFilter, this._enabled); + + bool get enabled => _enabled; + bool _enabled; + set enabled(bool value) { + if (enabled == value) { + return; + } + _enabled = value; + markNeedsPaint(); + } ImageFilter get imageFilter => _imageFilter; ImageFilter _imageFilter; @@ -61,11 +94,16 @@ class _ImageFilterRenderObject extends RenderProxyBox { } @override - bool get alwaysNeedsCompositing => child != null; + bool get alwaysNeedsCompositing => child != null && enabled; @override void paint(PaintingContext context, Offset offset) { assert(imageFilter != null); + if (!enabled) { + layer = null; + return super.paint(context, offset); + } + if (layer == null) { layer = ImageFilterLayer(imageFilter: imageFilter); } else { @@ -73,6 +111,9 @@ class _ImageFilterRenderObject extends RenderProxyBox { filterLayer.imageFilter = imageFilter; } context.pushLayer(layer!, super.paint, offset); - assert(layer != null); + assert(() { + layer!.debugCreator = debugCreator; + return true; + }()); } } diff --git a/packages/flutter/lib/src/widgets/scroll_notification_observer.dart b/packages/flutter/lib/src/widgets/scroll_notification_observer.dart index bc151190f1d5d..6fa7ffaa15b7c 100644 --- a/packages/flutter/lib/src/widgets/scroll_notification_observer.dart +++ b/packages/flutter/lib/src/widgets/scroll_notification_observer.dart @@ -9,6 +9,7 @@ import 'package:flutter/foundation.dart'; import 'framework.dart'; import 'notification_listener.dart'; import 'scroll_notification.dart'; +import 'scroll_position.dart'; /// A [ScrollNotification] listener for [ScrollNotificationObserver]. /// @@ -151,14 +152,26 @@ class ScrollNotificationObserverState extends State @override Widget build(BuildContext context) { - return NotificationListener( - onNotification: (ScrollNotification notification) { - _notifyListeners(notification); + // A ScrollMetricsNotification allows listeners to be notified for an + // initial state, as well as if the content dimensions change without + // scrolling. + return NotificationListener( + onNotification: (ScrollMetricsNotification notification) { + _notifyListeners(_ConvertedScrollMetricsNotification( + metrics: notification.metrics, + context: notification.context, + )); return false; }, - child: _ScrollNotificationObserverScope( - scrollNotificationObserverState: this, - child: widget.child, + child: NotificationListener( + onNotification: (ScrollNotification notification) { + _notifyListeners(notification); + return false; + }, + child: _ScrollNotificationObserverScope( + scrollNotificationObserverState: this, + child: widget.child, + ), ), ); } @@ -170,3 +183,10 @@ class ScrollNotificationObserverState extends State super.dispose(); } } + +class _ConvertedScrollMetricsNotification extends ScrollNotification { + _ConvertedScrollMetricsNotification({ + required super.metrics, + required super.context, + }); +} diff --git a/packages/flutter/lib/src/widgets/semantics_debugger.dart b/packages/flutter/lib/src/widgets/semantics_debugger.dart index b7796248921dd..062659186cd3e 100644 --- a/packages/flutter/lib/src/widgets/semantics_debugger.dart +++ b/packages/flutter/lib/src/widgets/semantics_debugger.dart @@ -281,27 +281,33 @@ class _SemanticsDebuggerPainter extends CustomPainter { assert(data.attributedLabel != null); final String message; - if (data.attributedLabel.string.isEmpty) { + final String tooltipAndLabel = [ + if (data.tooltip.isNotEmpty) + data.tooltip, + if (data.attributedLabel.string.isNotEmpty) + data.attributedLabel.string, + ].join('\n'); + if (tooltipAndLabel.isEmpty) { message = annotations.join('; '); } else { - final String label; + final String effectivelabel; if (data.textDirection == null) { - label = '${Unicode.FSI}${data.attributedLabel.string}${Unicode.PDI}'; + effectivelabel = '${Unicode.FSI}$tooltipAndLabel${Unicode.PDI}'; annotations.insert(0, 'MISSING TEXT DIRECTION'); } else { switch (data.textDirection!) { case TextDirection.rtl: - label = '${Unicode.RLI}${data.attributedLabel.string}${Unicode.PDF}'; + effectivelabel = '${Unicode.RLI}$tooltipAndLabel${Unicode.PDF}'; break; case TextDirection.ltr: - label = data.attributedLabel.string; + effectivelabel = tooltipAndLabel; break; } } if (annotations.isEmpty) { - message = label; + message = effectivelabel; } else { - message = '$label (${annotations.join('; ')})'; + message = '$effectivelabel (${annotations.join('; ')})'; } } diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart index 88b6963f68aab..1b08520ea620f 100644 --- a/packages/flutter/test/material/app_bar_test.dart +++ b/packages/flutter/test/material/app_bar_test.dart @@ -2567,311 +2567,457 @@ void main() { expect(actionIconTheme.color, foregroundColor); }); - testWidgets('SliverAppBar.backgroundColor MaterialStateColor scrolledUnder', (WidgetTester tester) async { + group('MaterialStateColor scrolledUnder', () { const double collapsedHeight = kToolbarHeight; const double expandedHeight = 200.0; const Color scrolledColor = Color(0xff00ff00); const Color defaultColor = Color(0xff0000ff); - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - elevation: 0, - backgroundColor: MaterialStateColor.resolveWith((Set states) { - return states.contains(MaterialState.scrolledUnder) ? scrolledColor : defaultColor; - }), - expandedHeight: expandedHeight, - pinned: true, - ), - SliverList( - delegate: SliverChildListDelegate( - [ - Container(height: 1200.0, color: Colors.teal), - ], - ), - ), - ], - ), - ), - ), - ); - Finder findAppBarMaterial() { - return find.descendant(of: find.byType(AppBar), matching: find.byType(Material)); + return find.descendant(of: find.byType(AppBar), matching: find.byType(Material)).first; } - Color? getAppBarBackgroundColor() { + Color? getAppBarBackgroundColor(WidgetTester tester) { return tester.widget(findAppBarMaterial()).color; } - expect(getAppBarBackgroundColor(), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, -expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, collapsedHeight); - - gesture = await tester.startGesture(const Offset(50.0, 300.0)); - await gesture.moveBy(const Offset(0.0, expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - }); - - testWidgets('SliverAppBar.backgroundColor with FlexibleSpace MaterialStateColor scrolledUnder', (WidgetTester tester) async { - const double collapsedHeight = kToolbarHeight; - const double expandedHeight = 200.0; - const Color scrolledColor = Color(0xff00ff00); - const Color defaultColor = Color(0xff0000ff); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - body: CustomScrollView( - slivers: [ - SliverAppBar( - elevation: 0, - backgroundColor: MaterialStateColor.resolveWith((Set states) { - return states.contains(MaterialState.scrolledUnder) ? scrolledColor : defaultColor; - }), - expandedHeight: expandedHeight, - pinned: true, - flexibleSpace: const FlexibleSpaceBar( - title: Text('SliverAppBar'), + group('SliverAppBar', () { + Widget _buildSliverApp({ + required double contentHeight, + bool reverse = false, + bool includeFlexibleSpace = false, + }) { + return MaterialApp( + home: Scaffold( + body: CustomScrollView( + reverse: reverse, + slivers: [ + SliverAppBar( + elevation: 0, + backgroundColor: MaterialStateColor.resolveWith((Set states) { + return states.contains(MaterialState.scrolledUnder) + ? scrolledColor + : defaultColor; + }), + expandedHeight: expandedHeight, + pinned: true, + flexibleSpace: includeFlexibleSpace + ? const FlexibleSpaceBar(title: Text('SliverAppBar')) + : null, ), - ), - SliverList( - delegate: SliverChildListDelegate( + SliverList( + delegate: SliverChildListDelegate( [ - Container(height: 1200.0, color: Colors.teal), + Container(height: contentHeight, color: Colors.teal), ], + ), ), - ), - ], - ), - ), - ), - ); - - Finder findAppBarMaterial() { - // There are 2 Material widgets below AppBar. The second is only added if - // flexibleSpace is non-null. - return find.descendant(of: find.byType(AppBar), matching: find.byType(Material)).first; - } - - Color? getAppBarBackgroundColor() { - return tester.widget(findAppBarMaterial()).color; - } - - expect(getAppBarBackgroundColor(), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, -expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, collapsedHeight); - - gesture = await tester.startGesture(const Offset(50.0, 300.0)); - await gesture.moveBy(const Offset(0.0, expandedHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); - }); - - testWidgets('AppBar.backgroundColor MaterialStateColor scrolledUnder', (WidgetTester tester) async { - const Color scrolledColor = Color(0xff00ff00); - const Color defaultColor = Color(0xff0000ff); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - appBar: AppBar( - elevation: 0, - backgroundColor: MaterialStateColor.resolveWith((Set states) { - return states.contains(MaterialState.scrolledUnder) ? scrolledColor : defaultColor; - }), - title: const Text('AppBar'), - ), - body: ListView( - children: [ - Container(height: 1200.0, color: Colors.teal), - ], - ), - ), - ), - ); - - Finder findAppBarMaterial() { - return find.descendant(of: find.byType(AppBar), matching: find.byType(Material)); - } - - Color? getAppBarBackgroundColor() { - return tester.widget(findAppBarMaterial()).color; - } - - expect(getAppBarBackgroundColor(), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, -kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - - gesture = await tester.startGesture(const Offset(50.0, 300.0)); - await gesture.moveBy(const Offset(0.0, kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - }); - - testWidgets('AppBar.backgroundColor with FlexibleSpace MaterialStateColor scrolledUnder', (WidgetTester tester) async { - const Color scrolledColor = Color(0xff00ff00); - const Color defaultColor = Color(0xff0000ff); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - appBar: AppBar( - elevation: 0, - backgroundColor: MaterialStateColor.resolveWith((Set states) { - return states.contains(MaterialState.scrolledUnder) ? scrolledColor : defaultColor; - }), - title: const Text('AppBar'), - flexibleSpace: const FlexibleSpaceBar( - title: Text('FlexibleSpace'), + ], ), ), - body: ListView( - children: [ - Container(height: 1200.0, color: Colors.teal), - ], - ), - ), - ), - ); - - Finder findAppBarMaterial() { - // There are 2 Material widgets below AppBar. The second is only added if - // flexibleSpace is non-null. - return find.descendant(of: find.byType(AppBar), matching: find.byType(Material)).first; - } - - Color? getAppBarBackgroundColor() { - return tester.widget(findAppBarMaterial()).color; - } - - expect(getAppBarBackgroundColor(), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(0.0, -kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(), scrolledColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - - gesture = await tester.startGesture(const Offset(50.0, 300.0)); - await gesture.moveBy(const Offset(0.0, kToolbarHeight)); - await gesture.up(); - await tester.pumpAndSettle(); - - expect(getAppBarBackgroundColor(), defaultColor); - expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); - }); + ); + } + + testWidgets('backgroundColor', (WidgetTester tester) async { + await tester.pumpWidget( + _buildSliverApp(contentHeight: 1200.0) + ); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, -expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, collapsedHeight); + + gesture = await tester.startGesture(const Offset(50.0, 300.0)); + await gesture.moveBy(const Offset(0.0, expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + }); + + testWidgets('backgroundColor with FlexibleSpace', (WidgetTester tester) async { + await tester.pumpWidget( + _buildSliverApp(contentHeight: 1200.0, includeFlexibleSpace: true) + ); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, -expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, collapsedHeight); + + gesture = await tester.startGesture(const Offset(50.0, 300.0)); + await gesture.moveBy(const Offset(0.0, expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + }); + + testWidgets('backgroundColor - reverse', (WidgetTester tester) async { + await tester.pumpWidget( + _buildSliverApp(contentHeight: 1200.0, reverse: true) + ); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, collapsedHeight); + + gesture = await tester.startGesture(const Offset(50.0, 300.0)); + await gesture.moveBy(const Offset(0.0, -expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + }); + + testWidgets('backgroundColor with FlexibleSpace - reverse', (WidgetTester tester) async { + await tester.pumpWidget( + _buildSliverApp( + contentHeight: 1200.0, + reverse: true, + includeFlexibleSpace: true, + ) + ); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, collapsedHeight); + + gesture = await tester.startGesture(const Offset(50.0, 300.0)); + await gesture.moveBy(const Offset(0.0, -expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + }); + + testWidgets('backgroundColor - not triggered in reverse for short content', (WidgetTester tester) async { + await tester.pumpWidget( + _buildSliverApp(contentHeight: 200, reverse: true) + ); + + // In reverse, the content here is not long enough to scroll under the app + // bar. + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + + final TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + }); + + testWidgets('backgroundColor with FlexibleSpace - not triggered in reverse for short content', (WidgetTester tester) async { + await tester.pumpWidget( + _buildSliverApp( + contentHeight: 200, + reverse: true, + includeFlexibleSpace: true, + ) + ); + + // In reverse, the content here is not long enough to scroll under the app + // bar. + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + + final TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, expandedHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, expandedHeight); + }); + }); - testWidgets('AppBar._handleScrollNotification safely calls setState()', (WidgetTester tester) async { - // Regression test for failures found in Google internal issue b/185192049. - final ScrollController controller = ScrollController(initialScrollOffset: 400); - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - appBar: AppBar( - title: const Text('AppBar'), - ), - body: Scrollbar( - isAlwaysShown: true, - controller: controller, - child: ListView( - controller: controller, + group('AppBar', () { + Widget _buildAppBar({ + required double contentHeight, + bool reverse = false, + bool includeFlexibleSpace = false + }) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + elevation: 0, + backgroundColor: MaterialStateColor.resolveWith((Set states) { + return states.contains(MaterialState.scrolledUnder) + ? scrolledColor + : defaultColor; + }), + title: const Text('AppBar'), + flexibleSpace: includeFlexibleSpace + ? const FlexibleSpaceBar(title: Text('FlexibleSpace')) + : null, + ), + body: ListView( + reverse: reverse, children: [ - Container(height: 1200.0, color: Colors.teal), + Container(height: contentHeight, color: Colors.teal), ], ), ), - ), - ), - ); - - expect(tester.takeException(), isNull); - }); - - testWidgets('AppBar scrolledUnder does not trigger on horizontal scroll', (WidgetTester tester) async { - const Color scrolledColor = Color(0xff00ff00); - const Color defaultColor = Color(0xff0000ff); - - await tester.pumpWidget( - MaterialApp( - home: Scaffold( - appBar: AppBar( - elevation: 0, - backgroundColor: MaterialStateColor.resolveWith((Set states) { - return states.contains(MaterialState.scrolledUnder) ? scrolledColor : defaultColor; - }), - title: const Text('AppBar'), - ), - body: ListView( - scrollDirection: Axis.horizontal, - children: [ - Container(height: 600.0, width: 1200.0, color: Colors.teal), - ], + ); + } + + testWidgets('backgroundColor', (WidgetTester tester) async { + await tester.pumpWidget( + _buildAppBar(contentHeight: 1200.0) + ); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, -kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + + gesture = await tester.startGesture(const Offset(50.0, 300.0)); + await gesture.moveBy(const Offset(0.0, kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + }); + + testWidgets('backgroundColor with FlexibleSpace', (WidgetTester tester) async { + await tester.pumpWidget( + _buildAppBar(contentHeight: 1200.0, includeFlexibleSpace: true) + ); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, -kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + + gesture = await tester.startGesture(const Offset(50.0, 300.0)); + await gesture.moveBy(const Offset(0.0, kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + }); + + testWidgets('backgroundColor - reverse', (WidgetTester tester) async { + await tester.pumpWidget( + _buildAppBar(contentHeight: 1200.0, reverse: true) + ); + await tester.pump(); + + // In this test case, the content always extends under the AppBar, so it + // should always be the scrolledColor. + expect(getAppBarBackgroundColor(tester), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + + gesture = await tester.startGesture(const Offset(50.0, 300.0)); + await gesture.moveBy(const Offset(0.0, -kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + }); + + testWidgets('backgroundColor with FlexibleSpace - reverse', (WidgetTester tester) async { + await tester.pumpWidget( + _buildAppBar( + contentHeight: 1200.0, + reverse: true, + includeFlexibleSpace: true, + ) + ); + await tester.pump(); + + // In this test case, the content always extends under the AppBar, so it + // should always be the scrolledColor. + expect(getAppBarBackgroundColor(tester), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + + gesture = await tester.startGesture(const Offset(50.0, 300.0)); + await gesture.moveBy(const Offset(0.0, -kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), scrolledColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + }); + + testWidgets('_handleScrollNotification safely calls setState()', (WidgetTester tester) async { + // Regression test for failures found in Google internal issue b/185192049. + final ScrollController controller = ScrollController(initialScrollOffset: 400); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('AppBar'), + ), + body: Scrollbar( + isAlwaysShown: true, + controller: controller, + child: ListView( + controller: controller, + children: [ + Container(height: 1200.0, color: Colors.teal), + ], + ), + ), + ), ), - ), - ), - ); - - Finder findAppBarMaterial() { - return find.descendant(of: find.byType(AppBar), matching: find.byType(Material)); - } - - Color? getAppBarBackgroundColor() { - return tester.widget(findAppBarMaterial()).color; - } - - expect(getAppBarBackgroundColor(), defaultColor); - - TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(-100.0, 0.0)); - await gesture.up(); - await tester.pumpAndSettle(); + ); - expect(getAppBarBackgroundColor(), defaultColor); - - gesture = await tester.startGesture(const Offset(50.0, 400.0)); - await gesture.moveBy(const Offset(100.0, 0.0)); - await gesture.up(); - await tester.pumpAndSettle(); + expect(tester.takeException(), isNull); + }); - expect(getAppBarBackgroundColor(), defaultColor); + testWidgets('does not trigger on horizontal scroll', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + appBar: AppBar( + elevation: 0, + backgroundColor: MaterialStateColor.resolveWith((Set states) { + return states.contains(MaterialState.scrolledUnder) + ? scrolledColor + : defaultColor; + }), + title: const Text('AppBar'), + ), + body: ListView( + scrollDirection: Axis.horizontal, + children: [ + Container(height: 600.0, width: 1200.0, color: Colors.teal), + ], + ), + ), + ), + ); + + expect(getAppBarBackgroundColor(tester), defaultColor); + + TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(-100.0, 0.0)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), defaultColor); + + gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(100.0, 0.0)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), defaultColor); + }); + + testWidgets('backgroundColor - not triggered in reverse for short content', (WidgetTester tester) async { + await tester.pumpWidget( + _buildAppBar( + contentHeight: 200.0, + reverse: true, + ) + ); + await tester.pump(); + + // In reverse, the content here is not long enough to scroll under the app + // bar. + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + + final TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + }); + + testWidgets('backgroundColor with FlexibleSpace - not triggered in reverse for short content', (WidgetTester tester) async { + await tester.pumpWidget( + _buildAppBar( + contentHeight: 200.0, + reverse: true, + includeFlexibleSpace: true, + ) + ); + await tester.pump(); + + // In reverse, the content here is not long enough to scroll under the app + // bar. + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + + final TestGesture gesture = await tester.startGesture(const Offset(50.0, 400.0)); + await gesture.moveBy(const Offset(0.0, kToolbarHeight)); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(getAppBarBackgroundColor(tester), defaultColor); + expect(tester.getSize(findAppBarMaterial()).height, kToolbarHeight); + }); + }); }); testWidgets('AppBar.preferredHeightFor', (WidgetTester tester) async { diff --git a/packages/flutter/test/material/back_button_test.dart b/packages/flutter/test/material/back_button_test.dart index eb4db41a6bab1..d4ff9a92ffab1 100644 --- a/packages/flutter/test/material/back_button_test.dart +++ b/packages/flutter/test/material/back_button_test.dart @@ -154,7 +154,7 @@ void main() { await tester.pumpAndSettle(); expect(tester.getSemantics(find.byType(BackButton)), matchesSemantics( - label: 'Back', + tooltip: 'Back', isButton: true, hasEnabledState: true, isEnabled: true, diff --git a/packages/flutter/test/material/calendar_date_picker_test.dart b/packages/flutter/test/material/calendar_date_picker_test.dart index bf1add0ef3b3b..f21300a043847 100644 --- a/packages/flutter/test/material/calendar_date_picker_test.dart +++ b/packages/flutter/test/material/calendar_date_picker_test.dart @@ -649,7 +649,7 @@ void main() { // Prev/Next month buttons. expect(tester.getSemantics(previousMonthIcon), matchesSemantics( - label: 'Previous month', + tooltip: 'Previous month', isButton: true, hasTapAction: true, isEnabled: true, @@ -657,7 +657,7 @@ void main() { isFocusable: true, )); expect(tester.getSemantics(nextMonthIcon), matchesSemantics( - label: 'Next month', + tooltip: 'Next month', isButton: true, hasTapAction: true, isEnabled: true, diff --git a/packages/flutter/test/material/chip_test.dart b/packages/flutter/test/material/chip_test.dart index 80cb5ccec09fe..ea4c7947a20a4 100644 --- a/packages/flutter/test/material/chip_test.dart +++ b/packages/flutter/test/material/chip_test.dart @@ -1964,7 +1964,7 @@ void main() { ], children: [ TestSemantics( - label: 'Delete', + tooltip: 'Delete', actions: [SemanticsAction.tap], textDirection: TextDirection.ltr, flags: [ diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart index 45ab8b05ccb97..525148fb82cf7 100644 --- a/packages/flutter/test/material/date_picker_test.dart +++ b/packages/flutter/test/material/date_picker_test.dart @@ -817,7 +817,7 @@ void main() { // Input mode toggle button expect(tester.getSemantics(switchToInputIcon), matchesSemantics( - label: 'Switch to input', + tooltip: 'Switch to input', isButton: true, hasTapAction: true, isEnabled: true, @@ -860,7 +860,7 @@ void main() { // Input mode toggle button expect(tester.getSemantics(switchToCalendarIcon), matchesSemantics( - label: 'Switch to calendar', + tooltip: 'Switch to calendar', isButton: true, hasTapAction: true, isEnabled: true, diff --git a/packages/flutter/test/material/debug_test.dart b/packages/flutter/test/material/debug_test.dart index 37628bb8c17a7..21492e972bc01 100644 --- a/packages/flutter/test/material/debug_test.dart +++ b/packages/flutter/test/material/debug_test.dart @@ -8,7 +8,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('debugCheckHasMaterial control test', (WidgetTester tester) async { - await tester.pumpWidget(const ListTile()); + await tester.pumpWidget(const Chip(label: Text('label'))); final dynamic exception = tester.takeException(); expect(exception, isFlutterError); final FlutterError error = exception as FlutterError; @@ -28,7 +28,7 @@ void main() { error.toStringDeep(), 'FlutterError\n' ' No Material widget found.\n' - ' ListTile widgets require a Material widget ancestor.\n' + ' Chip widgets require a Material widget ancestor.\n' ' In material design, most widgets are conceptually "printed" on a\n' " sheet of material. In Flutter's material library, that material\n" ' is represented by the Material widget. It is the Material widget\n' @@ -39,7 +39,7 @@ void main() { ' one, or use a widget that contains Material itself, such as a\n' ' Card, Dialog, Drawer, or Scaffold.\n' ' The specific widget that could not find a Material ancestor was:\n' - ' ListTile\n' + ' Chip\n' ' The ancestors of this widget were:\n' ' [root]\n', ); @@ -349,6 +349,7 @@ void main() { ' Material\n' ' _ScrollNotificationObserverScope\n' ' NotificationListener\n' + ' NotificationListener\n' ' ScrollNotificationObserver\n' ' _ScaffoldScope\n' ' Scaffold-[LabeledGlobalKey#00000]\n' diff --git a/packages/flutter/test/material/floating_action_button_test.dart b/packages/flutter/test/material/floating_action_button_test.dart index 3b41bc336d01e..987e8a9905005 100644 --- a/packages/flutter/test/material/floating_action_button_test.dart +++ b/packages/flutter/test/material/floating_action_button_test.dart @@ -670,7 +670,7 @@ void main() { semantics.dispose(); }); - testWidgets('Tooltip is used as semantics label', (WidgetTester tester) async { + testWidgets('Tooltip is used as semantics tooltip', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( @@ -697,7 +697,7 @@ void main() { ], children: [ TestSemantics( - label: 'Add Photo', + tooltip: 'Add Photo', actions: [ SemanticsAction.tap, ], diff --git a/packages/flutter/test/material/range_slider_test.dart b/packages/flutter/test/material/range_slider_test.dart index 621c3da740c7c..6e8595bf0d87e 100644 --- a/packages/flutter/test/material/range_slider_test.dart +++ b/packages/flutter/test/material/range_slider_test.dart @@ -1681,6 +1681,65 @@ void main() { ); }); + // Regression test for https://github.com/flutter/flutter/issues/101868 + testWidgets('RangeSlider.label info should not write to semantic node', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Theme( + data: ThemeData.light(), + child: Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: RangeSlider( + values: const RangeValues(10.0, 12.0), + max: 100.0, + onChanged: (RangeValues v) { }, + labels: const RangeLabels('Begin', 'End'), + ), + ), + ), + ), + ), + ); + + await tester.pumpAndSettle(); + + expect( + tester.getSemantics(find.byType(RangeSlider)), + matchesSemantics( + scopesRoute: true, + children:[ + matchesSemantics( + children: [ + matchesSemantics( + isEnabled: true, + isSlider: true, + hasEnabledState: true, + hasIncreaseAction: true, + hasDecreaseAction: true, + value: '10%', + increasedValue: '10%', + decreasedValue: '5%', + label: '' + ), + matchesSemantics( + isEnabled: true, + isSlider: true, + hasEnabledState: true, + hasIncreaseAction: true, + hasDecreaseAction: true, + value: '12%', + increasedValue: '17%', + decreasedValue: '12%', + label: '' + ), + ], + ), + ], + ), + ); + }); + testWidgets('Range Slider Semantics', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( diff --git a/packages/flutter/test/material/scaffold_test.dart b/packages/flutter/test/material/scaffold_test.dart index 3a10ba8c08fb3..7aeee33dc42ec 100644 --- a/packages/flutter/test/material/scaffold_test.dart +++ b/packages/flutter/test/material/scaffold_test.dart @@ -2339,6 +2339,7 @@ void main() { ' Material\n' ' _ScrollNotificationObserverScope\n' ' NotificationListener\n' + ' NotificationListener\n' ' ScrollNotificationObserver\n' ' _ScaffoldScope\n' ' Scaffold\n' diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart index 9d7ccb30daca4..9a6c24e1529d8 100644 --- a/packages/flutter/test/material/search_test.dart +++ b/packages/flutter/test/material/search_test.dart @@ -616,7 +616,7 @@ void main() { SemanticsFlag.isFocusable, ], actions: [SemanticsAction.tap], - label: 'Back', + tooltip: 'Back', textDirection: TextDirection.ltr, ), TestSemantics( diff --git a/packages/flutter/test/material/slider_test.dart b/packages/flutter/test/material/slider_test.dart index 354ddde960265..543831055c32f 100644 --- a/packages/flutter/test/material/slider_test.dart +++ b/packages/flutter/test/material/slider_test.dart @@ -1816,6 +1816,66 @@ void main() { semantics.dispose(); }); + // Regression test for https://github.com/flutter/flutter/issues/101868 + testWidgets('Slider.label info should not write to semantic node', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + + await tester.pumpWidget(MaterialApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: Slider( + value: 40.0, + max: 200.0, + divisions: 10, + semanticFormatterCallback: (double value) => value.round().toString(), + onChanged: (double v) { }, + label: 'Bingo', + ), + ), + ), + )); + + expect( + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics( + id: 1, + textDirection: TextDirection.ltr, + children: [ + TestSemantics( + id: 2, + children: [ + TestSemantics( + id: 3, + flags: [SemanticsFlag.scopesRoute], + children: [ + TestSemantics( + id: 4, + flags: [SemanticsFlag.hasEnabledState, SemanticsFlag.isEnabled, SemanticsFlag.isFocusable, SemanticsFlag.isSlider], + actions: [SemanticsAction.increase, SemanticsAction.decrease], + value: '40', + increasedValue: '60', + decreasedValue: '20', + textDirection: TextDirection.ltr, + ), + ], + ), + ], + ), + ], + ), + ], + ), + ignoreRect: true, + ignoreTransform: true, + ), + ); + semantics.dispose(); + }); + testWidgets('Slider is focusable and has correct focus color', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(debugLabel: 'Slider'); tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart index 842cf8b0c112c..2d25385bc6dc9 100644 --- a/packages/flutter/test/material/tooltip_test.dart +++ b/packages/flutter/test/material/tooltip_test.dart @@ -1263,7 +1263,7 @@ void main() { children: [ TestSemantics.rootChild( id: 1, - label: 'TIP', + tooltip: 'TIP', textDirection: TextDirection.ltr, ), ], @@ -1462,7 +1462,8 @@ void main() { flags: [SemanticsFlag.scopesRoute], children: [ TestSemantics( - label: 'Foo\nBar', + tooltip: 'Foo', + label: 'Bar', textDirection: TextDirection.ltr, ), ], diff --git a/packages/flutter/test/material/tooltip_theme_test.dart b/packages/flutter/test/material/tooltip_theme_test.dart index 999c335115b6a..2fedbf3a5e6f1 100644 --- a/packages/flutter/test/material/tooltip_theme_test.dart +++ b/packages/flutter/test/material/tooltip_theme_test.dart @@ -1066,7 +1066,8 @@ void main() { flags: [SemanticsFlag.scopesRoute], children: [ TestSemantics( - label: 'Foo\nBar', + tooltip: 'Foo', + label: 'Bar', textDirection: TextDirection.ltr, ), ], @@ -1108,7 +1109,8 @@ void main() { flags: [SemanticsFlag.scopesRoute], children: [ TestSemantics( - label: 'Foo\nBar', + tooltip: 'Foo', + label: 'Bar', textDirection: TextDirection.ltr, ), ], diff --git a/packages/flutter/test/rendering/binding_test.dart b/packages/flutter/test/rendering/binding_test.dart new file mode 100644 index 0000000000000..c10df12b05f41 --- /dev/null +++ b/packages/flutter/test/rendering/binding_test.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:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + + test('handleMetricsChanged does not scheduleForcedFrame unless there is a child to the renderView', () async { + expect(SchedulerBinding.instance.hasScheduledFrame, false); + RendererBinding.instance.handleMetricsChanged(); + expect(SchedulerBinding.instance.hasScheduledFrame, false); + + RendererBinding.instance.renderView.child = RenderLimitedBox(); + RendererBinding.instance.handleMetricsChanged(); + expect(SchedulerBinding.instance.hasScheduledFrame, true); + }); +} diff --git a/packages/flutter/test/semantics/semantics_test.dart b/packages/flutter/test/semantics/semantics_test.dart index ebc240b3436d4..4f2be0e967e11 100644 --- a/packages/flutter/test/semantics/semantics_test.dart +++ b/packages/flutter/test/semantics/semantics_test.dart @@ -561,6 +561,7 @@ void main() { ' increasedValue: ""\n' ' decreasedValue: ""\n' ' hint: ""\n' + ' tooltip: ""\n' ' textDirection: null\n' ' sortKey: null\n' ' platformViewId: null\n' @@ -659,6 +660,7 @@ void main() { ' increasedValue: ""\n' ' decreasedValue: ""\n' ' hint: ""\n' + ' tooltip: ""\n' ' textDirection: null\n' ' sortKey: null\n' ' platformViewId: null\n' diff --git a/packages/flutter/test/semantics/semantics_update_test.dart b/packages/flutter/test/semantics/semantics_update_test.dart index 78b0926dee053..0026e50674306 100644 --- a/packages/flutter/test/semantics/semantics_update_test.dart +++ b/packages/flutter/test/semantics/semantics_update_test.dart @@ -158,7 +158,8 @@ void main() { 'properties: SemanticsProperties, ' 'attributedLabel: "label" [SpellOutStringAttribute(TextRange(start: 0, end: 5))], ' 'attributedValue: "value" [LocaleStringAttribute(TextRange(start: 0, end: 5), en-MX)], ' - 'attributedHint: "hint" [SpellOutStringAttribute(TextRange(start: 1, end: 2))]' // ignore: missing_whitespace_between_adjacent_strings + 'attributedHint: "hint" [SpellOutStringAttribute(TextRange(start: 1, end: 2))], ' + 'tooltip: null'// ignore: missing_whitespace_between_adjacent_strings ')', ); diff --git a/packages/flutter/test/widgets/debug_test.dart b/packages/flutter/test/widgets/debug_test.dart index 47e3cffabc95e..ce1eabb770fb9 100644 --- a/packages/flutter/test/widgets/debug_test.dart +++ b/packages/flutter/test/widgets/debug_test.dart @@ -2,8 +2,10 @@ // 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 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -269,4 +271,74 @@ void main() { } debugHighlightDeprecatedWidgets = false; }); + + testWidgets('debugCreator of layers should not be null', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Directionality( + textDirection: TextDirection.ltr, + child: Material( + child: Stack( + children: [ + const ColorFiltered( + colorFilter: ColorFilter.mode(Color(0xFFFF0000), BlendMode.color), + child: Placeholder(), + ), + const Opacity( + opacity: 1.0, + child: Placeholder(), + ), + ImageFiltered( + imageFilter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: const Placeholder(), + ), + BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: const Placeholder(), + ), + ShaderMask( + shaderCallback: (Rect bounds) => const RadialGradient( + radius: 0.05, + colors: [Color(0xFFFF0000), Color(0xFF00FF00)], + tileMode: TileMode.mirror, + ).createShader(bounds), + child: const Placeholder(), + ), + RangeSlider( + values: const RangeValues(0.3, 0.7), + onChanged: (RangeValues newValues) {}, + ), + CompositedTransformFollower( + link: LayerLink(), + ), + ], + ), + ), + ), + ), + ); + + RenderObject renderObject; + + renderObject = tester.firstRenderObject(find.byType(Opacity)); + expect(renderObject.debugLayer?.debugCreator, isNotNull); + + renderObject = tester.firstRenderObject(find.byType(ColorFiltered)); + expect(renderObject.debugLayer?.debugCreator, isNotNull); + + renderObject = tester.firstRenderObject(find.byType(ImageFiltered)); + expect(renderObject.debugLayer?.debugCreator, isNotNull); + + renderObject = tester.firstRenderObject(find.byType(BackdropFilter)); + expect(renderObject.debugLayer?.debugCreator, isNotNull); + + renderObject = tester.firstRenderObject(find.byType(ShaderMask)); + expect(renderObject.debugLayer?.debugCreator, isNotNull); + + renderObject = tester.firstRenderObject(find.byType(RangeSlider)); + expect(renderObject.debugLayer?.debugCreator, isNotNull); + + renderObject = tester.firstRenderObject(find.byType(CompositedTransformFollower)); + expect(renderObject.debugLayer?.debugCreator, isNotNull); + }); } diff --git a/packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart b/packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart index bfd65190b9dc9..28375d25328c2 100644 --- a/packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart +++ b/packages/flutter/test/widgets/draggable_scrollable_sheet_test.dart @@ -1209,4 +1209,19 @@ void main() { expect(controller.isAttached, true); expect(controller.size, isNotNull); }); + + testWidgets('DraggableScrollableController.animateTo should not leak Ticker', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/102483 + final DraggableScrollableController controller = DraggableScrollableController(); + await tester.pumpWidget(_boilerplate(() {}, controller: controller)); + + controller.animateTo(0.0, curve: Curves.linear, duration: const Duration(milliseconds: 200)); + await tester.pump(); + + // Dispose the DraggableScrollableSheet + await tester.pumpWidget(const SizedBox.shrink()); + // Controller should be detached and no exception should be thrown + expect(controller.isAttached, false); + expect(tester.takeException(), isNull); + }); } diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index c13aecb79da75..4b0226ebb82fe 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -5183,6 +5183,53 @@ void main() { // toolbar. Until we change that, this test should remain skipped. }, skip: kIsWeb); // [intended] + + testWidgets('text selection handle visibility for long text', (WidgetTester tester) async { + // long text which is scrollable based on given box size + const String testText = + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.'; + final TextEditingController controller = + TextEditingController(text: testText); + final ScrollController scrollController = ScrollController(); + + await tester.pumpWidget(MaterialApp( + home: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 100, + height: 100, + child: SingleChildScrollView( + controller: scrollController, + child: EditableText( + controller: controller, + showSelectionHandles: true, + focusNode: FocusNode(), + style: Typography.material2018().black.subtitle1!, + cursorColor: Colors.blue, + backgroundCursorColor: Colors.grey, + selectionControls: materialTextSelectionControls, + keyboardType: TextInputType.multiline, + maxLines: null, + ), + ), + ), + ), + )); + + // scroll to a text that is outside of the inital visible rect + scrollController.jumpTo(151); + await tester.pump(); + + // long press on a word to trigger a select + await tester.longPressAt(const Offset(20, 15)); + // wait for adjustments of scroll area + await tester.pump(); + await tester.pumpAndSettle(); + + // assert not jumped to top + expect(scrollController.offset, equals(151)); + }); + const String testText = 'Now is the time for\n' // 20 'all good people\n' // 20 + 16 => 36 'to come to the aid\n' // 36 + 19 => 55 diff --git a/packages/flutter/test/widgets/framework_test.dart b/packages/flutter/test/widgets/framework_test.dart index ca2a57fba2c25..b754a442863f6 100644 --- a/packages/flutter/test/widgets/framework_test.dart +++ b/packages/flutter/test/widgets/framework_test.dart @@ -1694,6 +1694,30 @@ The findRenderObject() method was called for the following element: expect(inheritedElement.hashCode, identityHashCode(inheritedElement)); }); + + testWidgets('doesDependOnInheritedElement', (WidgetTester tester) async { + final _TestInheritedElement ancestor = + _TestInheritedElement(const Directionality( + textDirection: TextDirection.ltr, + child: Placeholder(), + )); + final _TestInheritedElement child = + _TestInheritedElement(const Directionality( + textDirection: TextDirection.ltr, + child: Placeholder(), + )); + expect(child.doesDependOnInheritedElement(ancestor), isFalse); + child.dependOnInheritedElement(ancestor); + expect(child.doesDependOnInheritedElement(ancestor), isTrue); + }); +} + +class _TestInheritedElement extends InheritedElement { + _TestInheritedElement(super.widget); + @override + bool doesDependOnInheritedElement(InheritedElement element) { + return super.doesDependOnInheritedElement(element); + } } class _WidgetWithNoVisitChildren extends StatelessWidget { diff --git a/packages/flutter/test/widgets/image_filter_test.dart b/packages/flutter/test/widgets/image_filter_test.dart index 38d949860baa1..6395cb044edca 100644 --- a/packages/flutter/test/widgets/image_filter_test.dart +++ b/packages/flutter/test/widgets/image_filter_test.dart @@ -86,4 +86,25 @@ void main() { await pumpWithSigma(10.0); expect(renderObject.debugLayer, same(originalLayer)); }); + + testWidgets('Image filter - enabled and disabled', (WidgetTester tester) async { + Future pumpWithEnabledStaet(bool enabled) async { + await tester.pumpWidget( + RepaintBoundary( + child: ImageFiltered( + enabled: enabled, + imageFilter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: const Placeholder(), + ), + ), + ); + } + + await pumpWithEnabledStaet(false); + expect(tester.layers, isNot(contains(isA()))); + + + await pumpWithEnabledStaet(true); + expect(tester.layers, contains(isA())); + }); } diff --git a/packages/flutter/test/widgets/nested_scroll_view_test.dart b/packages/flutter/test/widgets/nested_scroll_view_test.dart index 212e86428e3af..62a257e38d59a 100644 --- a/packages/flutter/test/widgets/nested_scroll_view_test.dart +++ b/packages/flutter/test/widgets/nested_scroll_view_test.dart @@ -599,9 +599,15 @@ void main() { return child; } if (child is ContainerLayer) { - final PhysicalModelLayer? candidate = _dfsFindPhysicalLayer(child); - if (candidate != null) { - return candidate; + Layer? innerChild = child.firstChild; + while (innerChild != null) { + if (innerChild is ContainerLayer) { + final PhysicalModelLayer? candidate = _dfsFindPhysicalLayer(innerChild); + if (candidate != null) { + return candidate; + } + } + innerChild = innerChild.nextSibling; } } child = child.nextSibling; diff --git a/packages/flutter/test/widgets/semantics_test.dart b/packages/flutter/test/widgets/semantics_test.dart index d8e6fc636d9da..5586aa83860a6 100644 --- a/packages/flutter/test/widgets/semantics_test.dart +++ b/packages/flutter/test/widgets/semantics_test.dart @@ -61,6 +61,34 @@ void main() { semantics.dispose(); }, semanticsEnabled: false); + testWidgets('Semantics tooltip', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + + final TestSemantics expectedSemantics = TestSemantics.root( + children: [ + TestSemantics.rootChild( + tooltip: 'test1', + textDirection: TextDirection.ltr, + ), + ], + ); + + await tester.pumpWidget( + Semantics( + tooltip: 'test1', + textDirection: TextDirection.ltr, + ), + ); + + expect(semantics, hasSemantics( + expectedSemantics, + ignoreTransform: true, + ignoreRect: true, + ignoreId: true, + )); + semantics.dispose(); + }); + testWidgets('Detach and reattach assert', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); final GlobalKey key = GlobalKey(); diff --git a/packages/flutter/test/widgets/semantics_tester.dart b/packages/flutter/test/widgets/semantics_tester.dart index 3c4cc90ac8112..e15332dfc7d31 100644 --- a/packages/flutter/test/widgets/semantics_tester.dart +++ b/packages/flutter/test/widgets/semantics_tester.dart @@ -39,6 +39,7 @@ class TestSemantics { this.actions = 0, this.label = '', this.value = '', + this.tooltip = '', this.increasedValue = '', this.decreasedValue = '', this.hint = '', @@ -72,6 +73,7 @@ class TestSemantics { this.increasedValue = '', this.decreasedValue = '', this.hint = '', + this.tooltip = '', this.textDirection, this.transform, this.textSelection, @@ -110,6 +112,7 @@ class TestSemantics { this.label = '', this.hint = '', this.value = '', + this.tooltip = '', this.increasedValue = '', this.decreasedValue = '', this.textDirection, @@ -176,6 +179,9 @@ class TestSemantics { /// performed on this node. final String hint; + /// A textual tooltip of this node. + final String tooltip; + /// The reading direction of the [label]. /// /// Even if this is not set, the [hasSemantics] matcher will verify that if a @@ -292,6 +298,8 @@ class TestSemantics { return fail('expected node id $id to have decreasedValue "$decreasedValue" but found value "${nodeData.decreasedValue}".'); if (hint != nodeData.hint) return fail('expected node id $id to have hint "$hint" but found hint "${nodeData.hint}".'); + if (tooltip != nodeData.tooltip) + return fail('expected node id $id to have tooltip "$tooltip" but found hint "${nodeData.tooltip}".'); if (textDirection != null && textDirection != nodeData.textDirection) return fail('expected node id $id to have textDirection "$textDirection" but found "${nodeData.textDirection}".'); if ((nodeData.label != '' || nodeData.value != '' || nodeData.hint != '' || node.increasedValue != '' || node.decreasedValue != '') && nodeData.textDirection == null) @@ -365,6 +373,8 @@ class TestSemantics { buf.writeln("$indent decreasedValue: '$decreasedValue',"); if (hint != null && hint != '') buf.writeln("$indent hint: '$hint',"); + if (tooltip != null && tooltip != '') + buf.writeln("$indent tooltip: '$tooltip',"); if (textDirection != null) buf.writeln('$indent textDirection: $textDirection,'); if (textSelection?.isValid ?? false) diff --git a/packages/flutter_test/lib/src/accessibility.dart b/packages/flutter_test/lib/src/accessibility.dart index 5bb9262a91745..f23c589b81de5 100644 --- a/packages/flutter_test/lib/src/accessibility.dart +++ b/packages/flutter_test/lib/src/accessibility.dart @@ -228,7 +228,7 @@ class LabeledTapTargetGuideline extends AccessibilityGuideline { !data.hasAction(ui.SemanticsAction.tap)) { return result; } - if (data.label == null || data.label.isEmpty) { + if ((data.label == null || data.label.isEmpty) && (data.tooltip == null || data.tooltip.isEmpty)) { result += Evaluation.fail( '$node: expected tappable node to have semantic label, ' 'but none was found.\n', diff --git a/packages/flutter_test/lib/src/matchers.dart b/packages/flutter_test/lib/src/matchers.dart index bad708fe4f7b8..5434b0be538fe 100644 --- a/packages/flutter_test/lib/src/matchers.dart +++ b/packages/flutter_test/lib/src/matchers.dart @@ -37,6 +37,7 @@ import 'widget_tester.dart' show WidgetTester; /// * [findsWidgets], when you want the finder to find one or more widgets. /// * [findsOneWidget], when you want the finder to find exactly one widget. /// * [findsNWidgets], when you want the finder to find a specific number of widgets. +/// * [findsAtLeastNWidgets], when you want the finder to find at least a specific number of widgets. const Matcher findsNothing = _FindsWidgetMatcher(null, 0); /// Asserts that the [Finder] locates at least one widget in the widget tree. @@ -52,6 +53,7 @@ const Matcher findsNothing = _FindsWidgetMatcher(null, 0); /// * [findsNothing], when you want the finder to not find anything. /// * [findsOneWidget], when you want the finder to find exactly one widget. /// * [findsNWidgets], when you want the finder to find a specific number of widgets. +/// * [findsAtLeastNWidgets], when you want the finder to find at least a specific number of widgets. const Matcher findsWidgets = _FindsWidgetMatcher(1, null); /// Asserts that the [Finder] locates at exactly one widget in the widget tree. @@ -67,6 +69,7 @@ const Matcher findsWidgets = _FindsWidgetMatcher(1, null); /// * [findsNothing], when you want the finder to not find anything. /// * [findsWidgets], when you want the finder to find one or more widgets. /// * [findsNWidgets], when you want the finder to find a specific number of widgets. +/// * [findsAtLeastNWidgets], when you want the finder to find at least a specific number of widgets. const Matcher findsOneWidget = _FindsWidgetMatcher(1, 1); /// Asserts that the [Finder] locates the specified number of widgets in the widget tree. @@ -82,8 +85,25 @@ const Matcher findsOneWidget = _FindsWidgetMatcher(1, 1); /// * [findsNothing], when you want the finder to not find anything. /// * [findsWidgets], when you want the finder to find one or more widgets. /// * [findsOneWidget], when you want the finder to find exactly one widget. +/// * [findsAtLeastNWidgets], when you want the finder to find at least a specific number of widgets. Matcher findsNWidgets(int n) => _FindsWidgetMatcher(n, n); +/// Asserts that the [Finder] locates at least a number of widgets in the widget tree. +/// +/// ## Sample code +/// +/// ```dart +/// expect(find.text('Save'), findsAtLeastNWidgets(2)); +/// ``` +/// +/// See also: +/// +/// * [findsNothing], when you want the finder to not find anything. +/// * [findsWidgets], when you want the finder to find one or more widgets. +/// * [findsOneWidget], when you want the finder to find exactly one widget. +/// * [findsNWidgets], when you want the finder to find a specific number of widgets. +Matcher findsAtLeastNWidgets(int n) => _FindsWidgetMatcher(n, null); + /// Asserts that the [Finder] locates a single widget that has at /// least one [Offstage] widget ancestor. /// @@ -494,6 +514,7 @@ Matcher matchesSemantics({ String? increasedValue, AttributedString? attributedIncreasedValue, String? decreasedValue, + String? tooltip, AttributedString? attributedDecreasedValue, TextDirection? textDirection, Rect? rect, @@ -625,6 +646,7 @@ Matcher matchesSemantics({ value: value, attributedValue: attributedValue, increasedValue: increasedValue, + tooltip: tooltip, attributedIncreasedValue: attributedIncreasedValue, decreasedValue: decreasedValue, attributedDecreasedValue: attributedDecreasedValue, @@ -1783,6 +1805,7 @@ class _MatchesSemanticsData extends Matcher { this.attributedIncreasedValue, this.decreasedValue, this.attributedDecreasedValue, + this.tooltip, this.flags, this.actions, this.textDirection, @@ -1808,6 +1831,7 @@ class _MatchesSemanticsData extends Matcher { final AttributedString? attributedIncreasedValue; final String? decreasedValue; final AttributedString? attributedDecreasedValue; + final String? tooltip; final SemanticsHintOverrides? hintOverrides; final List? actions; final List? customActions; @@ -1845,6 +1869,8 @@ class _MatchesSemanticsData extends Matcher { description.add(' with decreasedValue: $decreasedValue '); if (attributedDecreasedValue != null) description.add(' with attributedDecreasedValue: $attributedDecreasedValue'); + if (tooltip != null) + description.add(' with tooltip: $tooltip'); if (actions != null) description.add(' with actions: ').addDescriptionOf(actions); if (flags != null) @@ -1942,6 +1968,8 @@ class _MatchesSemanticsData extends Matcher { return failWithDescription( matchState, 'attributedDecreasedValue was: ${data.attributedDecreasedValue}'); } + if (tooltip != null && tooltip != data.tooltip) + return failWithDescription(matchState, 'tooltip was: ${data.tooltip}'); if (textDirection != null && textDirection != data.textDirection) return failWithDescription(matchState, 'textDirection was: $textDirection'); if (rect != null && rect != data.rect) diff --git a/packages/flutter_test/lib/src/test_default_binary_messenger.dart b/packages/flutter_test/lib/src/test_default_binary_messenger.dart index ba227d6e40f71..0907209e7e616 100644 --- a/packages/flutter_test/lib/src/test_default_binary_messenger.dart +++ b/packages/flutter_test/lib/src/test_default_binary_messenger.dart @@ -8,6 +8,11 @@ import 'dart:ui' as ui; import 'package:fake_async/fake_async.dart'; import 'package:flutter/services.dart'; +/// A function which takes the name of the method channel, it's handler, +/// platform message and asynchronously returns an encoded response. +typedef AllMessagesHandler = Future? Function( + String channel, MessageHandler? handler, ByteData? message); + /// A [BinaryMessenger] subclass that is used as the default binary messenger /// under testing environment. /// @@ -116,11 +121,17 @@ class TestDefaultBinaryMessenger extends BinaryMessenger { // can implement the [checkMockMessageHandler] method. final Map _outboundHandlerIdentities = {}; + /// Handler that intercepts and responds to outgoing messages, pretending + /// to be the platform, for all channels. + AllMessagesHandler? allMessagesHandler; + @override Future? send(String channel, ByteData? message) { final Future? resultFuture; final MessageHandler? handler = _outboundHandlers[channel]; - if (handler != null) { + if (allMessagesHandler != null) { + resultFuture = allMessagesHandler!(channel, handler, message); + } else if (handler != null) { resultFuture = handler(message); } else { resultFuture = delegate.send(channel, message); diff --git a/packages/flutter_test/test/matchers_test.dart b/packages/flutter_test/test/matchers_test.dart index 48a9d781035b6..2477313a06484 100644 --- a/packages/flutter_test/test/matchers_test.dart +++ b/packages/flutter_test/test/matchers_test.dart @@ -566,6 +566,7 @@ void main() { attributedValue: AttributedString('c'), attributedDecreasedValue: AttributedString('d'), attributedHint: AttributedString('e'), + tooltip: 'f', textDirection: TextDirection.ltr, rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), elevation: 3.0, @@ -673,6 +674,42 @@ void main() { handle.dispose(); }); }); + + group('findsAtLeastNWidgets', () { + Widget boilerplate(Widget child) { + return Directionality( + textDirection: TextDirection.ltr, + child: child, + ); + } + + testWidgets('succeeds when finds more then the specified count', + (WidgetTester tester) async { + await tester.pumpWidget(boilerplate(Column( + children: const [Text('1'), Text('2'), Text('3')], + ))); + + expect(find.byType(Text), findsAtLeastNWidgets(2)); + }); + + testWidgets('succeeds when finds the exact specified count', + (WidgetTester tester) async { + await tester.pumpWidget(boilerplate(Column( + children: const [Text('1'), Text('2')], + ))); + + expect(find.byType(Text), findsAtLeastNWidgets(2)); + }); + + testWidgets('fails when finds less then specified count', + (WidgetTester tester) async { + await tester.pumpWidget(boilerplate(Column( + children: const [Text('1'), Text('2')], + ))); + + expect(find.byType(Text), isNot(findsAtLeastNWidgets(3))); + }); + }); } enum _ComparatorBehavior { diff --git a/packages/flutter_test/test/test_default_binary_messenger_test.dart b/packages/flutter_test/test/test_default_binary_messenger_test.dart index 5052882682f52..30ed9e686bd46 100644 --- a/packages/flutter_test/test/test_default_binary_messenger_test.dart +++ b/packages/flutter_test/test/test_default_binary_messenger_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 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/services.dart'; @@ -26,6 +27,19 @@ class TestDelegate extends BinaryMessenger { void setMessageHandler(String channel, MessageHandler? handler) => throw UnimplementedError(); } +class WorkingTestDelegate extends BinaryMessenger { + @override + Future? send(String channel, ByteData? message) async { + return ByteData.sublistView(Uint8List.fromList([1, 2, 3])); + } + + // Rest of the API isn't needed for this test. + @override + Future handlePlatformMessage(String channel, ByteData? data, ui.PlatformMessageResponseCallback? callback) => throw UnimplementedError(); + @override + void setMessageHandler(String channel, MessageHandler? handler) => throw UnimplementedError(); +} + void main() { testWidgets('Caught exceptions are caught by the test framework', (WidgetTester tester) async { final BinaryMessenger delegate = TestDelegate(); @@ -39,4 +53,29 @@ void main() { expect(error, const RecognizableTestException()); } }); + + testWidgets('Mock MessageHandler is set correctly', + (WidgetTester tester) async { + final TestDefaultBinaryMessenger binaryMessenger = + TestDefaultBinaryMessenger(WorkingTestDelegate()); + binaryMessenger.setMockMessageHandler( + '', + (ByteData? message) async => + ByteData.sublistView(Uint8List.fromList([2, 3, 4]))); + + final ByteData? result = await binaryMessenger.send('', null); + expect(result?.buffer.asUint8List(), Uint8List.fromList([2, 3, 4])); + }); + + testWidgets('Mock AllMessagesHandler is set correctly', + (WidgetTester tester) async { + final TestDefaultBinaryMessenger binaryMessenger = + TestDefaultBinaryMessenger(WorkingTestDelegate()); + binaryMessenger.allMessagesHandler = + (String channel, MessageHandler? handler, ByteData? message) async => + ByteData.sublistView(Uint8List.fromList([2, 3, 4])); + + final ByteData? result = await binaryMessenger.send('', null); + expect(result?.buffer.asUint8List(), Uint8List.fromList([2, 3, 4])); + }); } diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 19948c57b6384..d5caca41af480 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -269,7 +269,7 @@ class AndroidGradleBuilder implements AndroidBuilder { ]; if (_logger.isVerbose) { command.add('--full-stacktrace'); - command.add('--debug'); + command.add('--info'); command.add('-Pverbose=true'); } else { command.add('-q'); @@ -593,7 +593,7 @@ class AndroidGradleBuilder implements AndroidBuilder { ]; if (_logger.isVerbose) { command.add('--full-stacktrace'); - command.add('--debug'); + command.add('--info'); command.add('-Pverbose=true'); } else { command.add('-q'); diff --git a/packages/flutter_tools/lib/src/isolated/devfs_web.dart b/packages/flutter_tools/lib/src/isolated/devfs_web.dart index 8b6f22b78703d..7539977abd3ed 100644 --- a/packages/flutter_tools/lib/src/isolated/devfs_web.dart +++ b/packages/flutter_tools/lib/src/isolated/devfs_web.dart @@ -275,7 +275,7 @@ class WebAssetServer implements AssetReader { } logging.Logger.root.level = logging.Level.ALL; - logging.Logger.root.onRecord.listen(_log); + logging.Logger.root.onRecord.listen(log); // In debug builds, spin up DWDS and the full asset server. final Dwds dwds = await dwdsLauncher( @@ -1004,11 +1004,22 @@ class ReleaseAssetServer { } } -void _log(logging.LogRecord event) { +@visibleForTesting +void log(logging.LogRecord event) { final String error = event.error == null? '': 'Error: ${event.error}'; if (event.level >= logging.Level.SEVERE) { globals.printError('${event.loggerName}: ${event.message}$error', stackTrace: event.stackTrace); } else if (event.level == logging.Level.WARNING) { + // TODO(elliette): Remove the following message suppressions after DWDS is + // >13.1.0, https://github.com/flutter/flutter/issues/101639 + const String dartUri = 'DartUri'; + if (event.loggerName == dartUri) { + const String webSqlWarning = 'Unresolved uri: dart:web_sql'; + const String uiWarning = 'Unresolved uri: dart:ui'; + if (event.message == webSqlWarning || event.message == uiWarning) { + return; + } + } globals.printWarning('${event.loggerName}: ${event.message}$error'); } else { globals.printTrace('${event.loggerName}: ${event.message}$error'); diff --git a/packages/flutter_tools/lib/src/localizations/localizations_utils.dart b/packages/flutter_tools/lib/src/localizations/localizations_utils.dart index 3f482cf69c955..a8e413526670e 100644 --- a/packages/flutter_tools/lib/src/localizations/localizations_utils.dart +++ b/packages/flutter_tools/lib/src/localizations/localizations_utils.dart @@ -7,6 +7,7 @@ import 'package:yaml/yaml.dart'; import '../base/file_system.dart'; import '../base/logger.dart'; +import 'gen_l10n_types.dart'; import 'language_subtag_registry.dart'; typedef HeaderGenerator = String Function(String regenerateInstructions); @@ -215,8 +216,15 @@ void precacheLanguageAndRegionTags() { String describeLocale(String tag) { final List subtags = tag.split('_'); assert(subtags.isNotEmpty); - assert(_languages.containsKey(subtags[0])); - final String language = _languages[subtags[0]]!; + final String languageCode = subtags[0]; + if (!_languages.containsKey(languageCode)) { + throw L10nException( + '"$languageCode" is not a supported language code.\n' + 'See https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry ' + 'for the supported list.', + ); + } + final String language = _languages[languageCode]!; String output = language; String? region; String? script; diff --git a/packages/flutter_tools/lib/src/macos/cocoapods.dart b/packages/flutter_tools/lib/src/macos/cocoapods.dart index a471e36b1fbf3..26365b6a46425 100644 --- a/packages/flutter_tools/lib/src/macos/cocoapods.dart +++ b/packages/flutter_tools/lib/src/macos/cocoapods.dart @@ -349,10 +349,11 @@ class CocoaPods { } void _diagnosePodInstallFailure(ProcessResult result) { - if (result.stdout is! String) { + final Object? stdout = result.stdout; + final Object? stderr = result.stderr; + if (stdout is! String || stderr is! String) { return; } - final String stdout = result.stdout as String; if (stdout.contains('out-of-date source repos')) { _logger.printError( "Error: CocoaPods's specs repository is too out-of-date to satisfy dependencies.\n" @@ -360,7 +361,7 @@ class CocoaPods { ' pod repo update\n', emphasis: true, ); - } else if ((stdout.contains('ffi_c.bundle') || stdout.contains('/ffi/')) && + } else if ((stderr.contains('ffi_c.bundle') || stderr.contains('/ffi/')) && _operatingSystemUtils.hostPlatform == HostPlatform.darwin_arm) { // https://github.com/flutter/flutter/issues/70796 UsageEvent( diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 419c1953a0c79..d303e639a60db 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -682,7 +682,7 @@ abstract class ResidentHandlers { /// use case is to look at the various layers in proportion to see what /// contributes the most towards raster performance. Future debugFrameJankMetrics() async { - if (!supportsServiceProtocol || !isRunningDebug) { + if (!supportsServiceProtocol) { return false; } for (final FlutterDevice device in flutterDevices) { 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 b0d0636bed699..bd90fa1730346 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 @@ -142,7 +142,7 @@ void main() { command: [ 'gradlew', '--full-stacktrace', - '--debug', + '--info', '-Pverbose=true', '-Ptarget-platform=android-arm,android-arm64,android-x64', '-Ptarget=lib/main.dart', @@ -782,7 +782,7 @@ void main() { '-Pis-plugin=false', '-PbuildNumber=1.0', '--full-stacktrace', - '--debug', + '--info', '-Pverbose=true', '-Pdart-obfuscation=false', '-Ptrack-widget-creation=false', 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 e850ca3fb4630..286c051d0f206 100644 --- a/packages/flutter_tools/test/general.shard/generate_localizations_test.dart +++ b/packages/flutter_tools/test/general.shard/generate_localizations_test.dart @@ -2595,6 +2595,39 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e ); }); }); + + testWithoutContext('throws when the language code is not supported', () { + const String arbFileWithInvalidCode = ''' +{ + "@@locale": "invalid", + "title": "invalid" +}'''; + + final Directory l10nDirectory = fs.currentDirectory.childDirectory('lib').childDirectory('l10n') + ..createSync(recursive: true); + l10nDirectory.childFile('app_invalid.arb') + .writeAsStringSync(arbFileWithInvalidCode); + + expect( + () { + LocalizationsGenerator( + fileSystem: fs, + inputPathString: defaultL10nPathString, + outputPathString: defaultL10nPathString, + templateArbFileName: 'app_invalid.arb', + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + ) + ..loadResources() + ..writeOutputFiles(BufferLogger.test()); + }, + throwsA(isA().having( + (L10nException e) => e.message, + 'message', + contains('"invalid" is not a supported language code.'), + )), + ); + }); }); testWithoutContext('should generate a valid pubspec.yaml file when using synthetic package if it does not already exist', () { diff --git a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart index e5554ba2466eb..81139a64730c0 100644 --- a/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/cocoapods_test.dart @@ -514,7 +514,7 @@ Note: as of CocoaPods 1.0, `pod repo update` does not happen on `pod install` by 'LANG': 'en_US.UTF-8', }, exitCode: 1, - stdout: cocoaPodsError, + stderr: cocoaPodsError, ), const FakeCommand( command: ['which', 'sysctl'], 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 909258801c6ae..cc0f158caecc7 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 @@ -8,6 +8,7 @@ import 'dart:io' hide Directory, File; 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/build_info.dart'; import 'package:flutter_tools/src/build_system/targets/web.dart'; @@ -16,6 +17,7 @@ import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/isolated/devfs_web.dart'; import 'package:flutter_tools/src/web/compile.dart'; +import 'package:logging/logging.dart' as logging; import 'package:package_config/package_config.dart'; import 'package:shelf/shelf.dart'; import 'package:test/fake.dart'; @@ -39,6 +41,7 @@ void main() { PackageConfig packages; Platform windows; FakeHttpServer httpServer; + BufferLogger logger; setUpAll(() async { packages = PackageConfig([ @@ -50,6 +53,7 @@ void main() { httpServer = FakeHttpServer(); linux = FakePlatform(environment: {}); windows = FakePlatform(operatingSystem: 'windows', environment: {}); + logger = BufferLogger.test(); testbed = Testbed(setup: () { webAssetServer = WebAssetServer( httpServer, @@ -67,9 +71,35 @@ void main() { webBuildDirectory: null, basePath: null, ); + }, overrides: { + Logger: () => logger, }); }); + test('.log() filters events', () => testbed.run(() { + // harmless warning that should be filtered out + const String harmlessMessage = 'Unresolved uri: dart:ui'; + // serious warning + const String seriousMessage = 'Something bad happened'; + + final List events = [ + logging.LogRecord( + logging.Level.WARNING, + harmlessMessage, + 'DartUri', + ), + logging.LogRecord( + logging.Level.WARNING, + seriousMessage, + 'DartUri', + ), + ]; + + events.forEach(log); + expect(logger.warningText, contains(seriousMessage)); + expect(logger.warningText, isNot(contains(harmlessMessage))); + })); + test('Handles against malformed manifest', () => testbed.run(() async { final File source = globals.fs.file('source') ..writeAsStringSync('main() {}'); diff --git a/packages/flutter_tools/test/web.shard/output_web_test.dart b/packages/flutter_tools/test/web.shard/output_web_test.dart index a689088ab0dcf..bcb4b7473a703 100644 --- a/packages/flutter_tools/test/web.shard/output_web_test.dart +++ b/packages/flutter_tools/test/web.shard/output_web_test.dart @@ -78,4 +78,18 @@ void main() { await sendEvent({'type': 'DevtoolsEvent'}); await warning; }, skip: true); // Skipping for 'https://github.com/dart-lang/webdev/issues/1562' + + testWithoutContext( + 'flutter run output skips DartUri warning messages from dwds', () async { + bool containsDartUriWarning = false; + flutter.stderr.listen((String msg) { + if (msg.contains('DartUri')) { + containsDartUriWarning = true; + } + }); + await start(); + await flutter.stop(); + expect(containsDartUriWarning, isFalse); + // TODO(elliette): Enable for DWDS >13.1.0, https://github.com/flutter/flutter/issues/101639 + }, skip: true); // [intended] enable for DWDS >13.1.0 }