From 0eb891db107b3168804a36d5f02e6c12892f262e Mon Sep 17 00:00:00 2001 From: Reid Baker <1063596+reidbaker@users.noreply.github.com> Date: Tue, 30 Sep 2025 17:41:08 +0000 Subject: [PATCH 001/204] Set minimum supported java version to 17 (#176226) Related to #176027 Minimum agp version already requires Java 17 this aligns our tooling. This pr needs to land after the following prs 1. https://github.com/flutter/flutter/pull/176203 2. https://github.com/flutter/flutter/pull/176204 3. https://github.com/flutter/flutter/pull/176097 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- .../main/kotlin/DependencyVersionChecker.kt | 2 +- .../kotlin/DependencyVersionCheckerTest.kt | 30 ++++++------- .../lib/src/android/gradle_utils.dart | 2 +- .../android/android_workflow_test.dart | 44 +------------------ 4 files changed, 18 insertions(+), 60 deletions(-) diff --git a/packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt b/packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt index 2227f7c1fc012..2482f9862b99a 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt @@ -97,7 +97,7 @@ object DependencyVersionChecker { // Java error and warn should align with packages/flutter_tools/lib/src/android/gradle_utils.dart. @VisibleForTesting internal val warnJavaVersion: JavaVersion = JavaVersion.VERSION_17 - @VisibleForTesting internal val errorJavaVersion: JavaVersion = JavaVersion.VERSION_11 + @VisibleForTesting internal val errorJavaVersion: JavaVersion = JavaVersion.VERSION_17 @VisibleForTesting internal val warnAGPVersion: AndroidPluginVersion = AndroidPluginVersion(8, 6, 0) diff --git a/packages/flutter_tools/gradle/src/test/kotlin/DependencyVersionCheckerTest.kt b/packages/flutter_tools/gradle/src/test/kotlin/DependencyVersionCheckerTest.kt index ed7fade009594..b4807229bfde2 100644 --- a/packages/flutter_tools/gradle/src/test/kotlin/DependencyVersionCheckerTest.kt +++ b/packages/flutter_tools/gradle/src/test/kotlin/DependencyVersionCheckerTest.kt @@ -16,6 +16,7 @@ import com.flutter.gradle.DependencyVersionChecker.OUT_OF_SUPPORT_RANGE_PROPERTY import com.flutter.gradle.DependencyVersionChecker.POTENTIAL_JAVA_FIX import com.flutter.gradle.DependencyVersionChecker.errorAGPVersion import com.flutter.gradle.DependencyVersionChecker.errorGradleVersion +import com.flutter.gradle.DependencyVersionChecker.errorJavaVersion import com.flutter.gradle.DependencyVersionChecker.errorKGPVersion import com.flutter.gradle.DependencyVersionChecker.errorMinSdkVersion import com.flutter.gradle.DependencyVersionChecker.getErrorMessage @@ -27,7 +28,6 @@ import com.flutter.gradle.DependencyVersionChecker.getPotentialSDKFix import com.flutter.gradle.DependencyVersionChecker.getWarnMessage import com.flutter.gradle.DependencyVersionChecker.warnAGPVersion import com.flutter.gradle.DependencyVersionChecker.warnGradleVersion -import com.flutter.gradle.DependencyVersionChecker.warnJavaVersion import com.flutter.gradle.DependencyVersionChecker.warnKGPVersion import com.flutter.gradle.DependencyVersionChecker.warnMinSdkVersion import io.mockk.every @@ -154,31 +154,31 @@ class DependencyVersionCheckerTest { verify(exactly = 0) { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true) } } - // No test for Java version in error range, as the lowest supported Java version is also the + // No test for Java version in warn range, as the lowest supported Java version is also the // lowest possible. - @Test - fun `Java version in warn range results in warning logs`() { - val exampleWarnJavaVersion = JavaVersion.VERSION_16 - val mockProject = MockProjectFactory.createMockProjectWithSpecifiedDependencyVersions(javaVersion = exampleWarnJavaVersion) + fun `Java version in error range results in error logs`() { + val exampleErrorJavaVersion = JavaVersion.VERSION_16 + val mockProject = MockProjectFactory.createMockProjectWithSpecifiedDependencyVersions(javaVersion = exampleErrorJavaVersion) val mockExtraPropertiesExtension = mockProject.extra every { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, false) } returns Unit + every { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true) } returns Unit val mockLogger = mockProject.logger every { mockLogger.error(any()) } returns Unit - DependencyVersionChecker.checkDependencyVersions(mockProject) - verify { - mockLogger.error( - getWarnMessage( + val dependencyValidationException = + assertFailsWith { DependencyVersionChecker.checkDependencyVersions(mockProject) } + assert( + dependencyValidationException.message == + getErrorMessage( JAVA_NAME, - exampleWarnJavaVersion.toString(), - warnJavaVersion.toString(), + exampleErrorJavaVersion.toString(), + errorJavaVersion.toString(), POTENTIAL_JAVA_FIX ) - ) - } - verify(exactly = 0) { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true) } + ) + verify(exactly = 1) { mockExtraPropertiesExtension.set(OUT_OF_SUPPORT_RANGE_PROPERTY, true) } } @Test diff --git a/packages/flutter_tools/lib/src/android/gradle_utils.dart b/packages/flutter_tools/lib/src/android/gradle_utils.dart index 4e158b4d534a0..14b3245aa5b52 100644 --- a/packages/flutter_tools/lib/src/android/gradle_utils.dart +++ b/packages/flutter_tools/lib/src/android/gradle_utils.dart @@ -59,7 +59,7 @@ const targetSdkVersion = '36'; const ndkVersion = '27.0.12077973'; final minBuildToolsVersion = Version(28, 0, 3); // Align with packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt. -final errorJavaMinVersionAndroid = Version(11, 0, 0); +final errorJavaMinVersionAndroid = Version(17, 0, 0); final warnJavaMinVersionAndroid = Version(17, 0, 0); // Update these when new major versions of Java are supported by new Gradle diff --git a/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart b/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart index b0cd70e13e93f..2c68f084fb424 100644 --- a/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart +++ b/packages/flutter_tools/test/general.shard/android/android_workflow_test.dart @@ -593,6 +593,7 @@ Review licenses that have not been accepted (y/N)? ); }); + // Warning test not available when minimum and error are aligned. testUsingContext('detects minimum required java version', () async { // Test with older version of JDK final Platform platform = FakePlatform() @@ -636,49 +637,6 @@ Review licenses that have not been accepted (y/N)? ); }); - testUsingContext('detects minimum recommended java version', () async { - // Test with older version of JDK - final Platform platform = FakePlatform() - ..environment = { - 'HOME': '/home/me', - Java.javaHomeEnvironmentVariable: 'home/java', - 'PATH': '', - }; - final sdkVersion = FakeAndroidSdkVersion() - ..sdkLevel = gradle_utils.compileSdkVersionInt - ..buildToolsVersion = gradle_utils.minBuildToolsVersion; - - // Mock a pass through scenario to reach _checkJavaVersion() - sdk - ..licensesAvailable = true - ..platformToolsAvailable = true - ..cmdlineToolsAvailable = true - ..directory = fileSystem.directory('/foo/bar') - ..sdkManagerPath = '/foo/bar/sdkmanager' - ..emulatorPath = 'path/to/emulator'; - sdk.latestVersion = sdkVersion; - - const javaVersionText = 'openjdk version "16.0.1"'; - final String errorMessage = UserMessages().androidJavaMinimumVersion(javaVersionText); - - final ValidationResult validationResult = await AndroidValidator( - java: FakeJava(version: const Version.withText(16, 0, 1, javaVersionText)), - androidSdk: sdk, - logger: logger, - platform: platform, - userMessages: UserMessages(), - processManager: processManager, - ).validate(); - expect(validationResult.type, ValidationType.success); - expect(validationResult.messages.last.message, errorMessage); - expect( - validationResult.messages.any( - (ValidationMessage message) => message.message.contains('Unable to locate Android SDK'), - ), - false, - ); - }); - testWithoutContext('Mentions `flutter config --android-sdk if user has no AndroidSdk`', () async { final ValidationResult validationResult = await AndroidValidator( java: FakeJava(), From bfde3e8f3139563b4a298a265646bee1743fbdcb Mon Sep 17 00:00:00 2001 From: Hannah Jin Date: Tue, 30 Sep 2025 10:47:09 -0700 Subject: [PATCH 002/204] Update flutter test to use SemanticsFlags (#175987) Update flutter test to use SemanticsFlags so it will support incoming flags ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../test/widgets/semantics_tester.dart | 73 ++++++--- .../test/widgets/semantics_tester_test.dart | 103 ++++++++++++ .../flutter_test/lib/src/accessibility.dart | 18 +-- packages/flutter_test/lib/src/controller.dart | 26 +-- packages/flutter_test/lib/src/matchers.dart | 153 +++++++++++------- 5 files changed, 268 insertions(+), 105 deletions(-) diff --git a/packages/flutter/test/widgets/semantics_tester.dart b/packages/flutter/test/widgets/semantics_tester.dart index c10cb41912353..580e4fdfe8775 100644 --- a/packages/flutter/test/widgets/semantics_tester.dart +++ b/packages/flutter/test/widgets/semantics_tester.dart @@ -9,7 +9,7 @@ import 'package:flutter/physics.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; -export 'dart:ui' show SemanticsAction, SemanticsFlag; +export 'dart:ui' show SemanticsAction, SemanticsFlag, SemanticsFlags; export 'package:flutter/rendering.dart' show SemanticsData; const String _matcherHelp = @@ -62,7 +62,7 @@ class TestSemantics { this.currentValueLength, this.identifier = '', this.hintOverrides, - }) : assert(flags is int || flags is List), + }) : assert(flags is int || flags is List || flags is SemanticsFlags), assert(actions is int || actions is List), tags = tags?.toSet() ?? {}; @@ -95,7 +95,7 @@ class TestSemantics { this.identifier = '', this.hintOverrides, }) : id = 0, - assert(flags is int || flags is List), + assert(flags is int || flags is List || flags is SemanticsFlags), assert(actions is int || actions is List), rect = TestSemantics.rootRect, tags = tags?.toSet() ?? {}; @@ -138,7 +138,7 @@ class TestSemantics { this.currentValueLength, this.identifier = '', this.hintOverrides, - }) : assert(flags is int || flags is List), + }) : assert(flags is int || flags is List || flags is SemanticsFlags), assert(actions is int || actions is List), transform = _applyRootChildScale(transform), tags = tags?.toSet() ?? {}; @@ -149,12 +149,15 @@ class TestSemantics { /// they are created. final int? id; - /// The [SemanticsFlag]s set on this node. + /// The SemanticsFlags on this node. /// - /// There are two ways to specify this property: as an `int` that encodes the - /// flags as a bit field, or as a `List` that are _on_. + /// There are three ways to specify this property: as an `int` that encodes the + /// flags as a bit field, or as a `List` that are _on_, or as a `SemanticsFlags`. + /// + /// Using `SemanticsFlags` is recommended. /// - /// Using `List` is recommended due to better readability. + /// The `int` and `List` types are considered deprecated as they + /// have limited bits and only support the first 31 flags. final dynamic flags; /// The [SemanticsAction]s set on this node. @@ -323,14 +326,25 @@ class TestSemantics { final SemanticsData nodeData = node.getSemanticsData(); - final int flagsBitmask = flags is int - ? flags as int - : (flags as List).fold( - 0, - (int bitmask, SemanticsFlag flag) => bitmask | flag.index, - ); - if (flagsBitmask != nodeData.flags) { - return fail('expected node id $id to have flags $flags but found flags ${nodeData.flags}.'); + if (flags is SemanticsFlags) { + if (flags != nodeData.flagsCollection) { + return fail( + 'expected node id $id to have flags $flags but found flags ${nodeData.flagsCollection}.', + ); + } + } + // the bitmask flags only support first 31 flags. + else { + final int flagsBitmask = flags is int + ? flags as int + : (flags as List).fold( + 0, + (int bitmask, SemanticsFlag flag) => bitmask | flag.index, + ); + + if (flagsBitmask != nodeData.flags) { + return fail('expected node id $id to have flags $flags but found flags ${nodeData.flags}.'); + } } final int actionsBitmask = actions is int @@ -505,8 +519,9 @@ class TestSemantics { if (id != null) { buf.writeln('$indent id: $id,'); } - if (flags is int && flags != 0 || - flags is List && (flags as List).isNotEmpty) { + if ((flags is int && flags != 0) || + (flags is List && (flags as List).isNotEmpty) || + (flags is SemanticsFlags && (flags as SemanticsFlags) != SemanticsFlags.none)) { buf.writeln('$indent flags: ${SemanticsTester._flagsToSemanticsFlagExpression(flags)},'); } if (actions is int && actions != 0 || @@ -659,6 +674,7 @@ class SemanticsTester { TextDirection? textDirection, List? actions, List? flags, + SemanticsFlags? flagsCollection, Set? tags, double? scrollPosition, double? scrollExtentMax, @@ -719,7 +735,16 @@ class SemanticsTester { return false; } } - if (flags != null) { + + if (flagsCollection != null) { + final SemanticsFlags expectedFlags = flagsCollection; + final SemanticsFlags actualFlags = node.getSemanticsData().flagsCollection; + if (expectedFlags != actualFlags) { + return false; + } + } + // `flags` are deprecated and only support the first 31 flags. + else if (flags != null) { final int expectedFlags = flags.fold( 0, (int value, SemanticsFlag flag) => value | flag.index, @@ -827,7 +852,9 @@ class SemanticsTester { static String _flagsToSemanticsFlagExpression(dynamic flags) { Iterable list; - if (flags is int) { + if (flags is SemanticsFlags) { + return '[${flags.toStrings().join(', ')}]'; + } else if (flags is int) { list = SemanticsFlag.values.where((SemanticsFlag flag) => (flag.index & flags) != 0); } else { list = flags as List; @@ -1048,6 +1075,7 @@ class _IncludesNodeWith extends Matcher { this.textDirection, this.actions, this.flags, + this.flagsCollection, this.tags, this.scrollPosition, this.scrollExtentMax, @@ -1060,6 +1088,7 @@ class _IncludesNodeWith extends Matcher { value != null || actions != null || flags != null || + flagsCollection != null || tags != null || increasedValue != null || decreasedValue != null || @@ -1081,6 +1110,7 @@ class _IncludesNodeWith extends Matcher { final TextDirection? textDirection; final List? actions; final List? flags; + final SemanticsFlags? flagsCollection; final Set? tags; final double? scrollPosition; final double? scrollExtentMax; @@ -1104,6 +1134,7 @@ class _IncludesNodeWith extends Matcher { textDirection: textDirection, actions: actions, flags: flags, + flagsCollection: flagsCollection, tags: tags, scrollPosition: scrollPosition, scrollExtentMax: scrollExtentMax, @@ -1168,6 +1199,7 @@ Matcher includesNodeWith({ TextDirection? textDirection, List? actions, List? flags, + SemanticsFlags? flagsCollection, Set? tags, double? scrollPosition, double? scrollExtentMax, @@ -1188,6 +1220,7 @@ Matcher includesNodeWith({ decreasedValue: decreasedValue, actions: actions, flags: flags, + flagsCollection: flagsCollection, tags: tags, scrollPosition: scrollPosition, scrollExtentMax: scrollExtentMax, diff --git a/packages/flutter/test/widgets/semantics_tester_test.dart b/packages/flutter/test/widgets/semantics_tester_test.dart index 908daaa0a6115..f25ca1c6bcd0d 100644 --- a/packages/flutter/test/widgets/semantics_tester_test.dart +++ b/packages/flutter/test/widgets/semantics_tester_test.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:ui'; + import 'package:flutter/gestures.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -46,4 +48,105 @@ void main() { ); semantics.dispose(); }); + + testWidgets('Semantics tester support flags as an int', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + + await tester.pumpWidget( + Semantics( + container: true, + child: Semantics( + label: 'test1', + textDirection: TextDirection.ltr, + selected: true, + child: Container(), + ), + ), + ); + + expect( + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics.rootChild( + id: 1, + label: 'test1', + rect: TestSemantics.fullScreen, + flags: SemanticsFlag.hasSelectedState.index | SemanticsFlag.isSelected.index, + ), + ], + ), + ), + ); + semantics.dispose(); + }); + + testWidgets('Semantics tester support flags as a list of SemanticsFlag', ( + WidgetTester tester, + ) async { + final SemanticsTester semantics = SemanticsTester(tester); + + await tester.pumpWidget( + Semantics( + container: true, + child: Semantics( + label: 'test1', + textDirection: TextDirection.ltr, + selected: true, + child: Container(), + ), + ), + ); + + expect( + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics.rootChild( + id: 1, + label: 'test1', + rect: TestSemantics.fullScreen, + flags: [SemanticsFlag.hasSelectedState, SemanticsFlag.isSelected], + ), + ], + ), + ), + ); + semantics.dispose(); + }); + + testWidgets('Semantics tester support flags as a SemanticsFlags', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + + await tester.pumpWidget( + Semantics( + container: true, + child: Semantics( + label: 'test1', + textDirection: TextDirection.ltr, + selected: true, + child: Container(), + ), + ), + ); + + expect( + semantics, + hasSemantics( + TestSemantics.root( + children: [ + TestSemantics.rootChild( + id: 1, + label: 'test1', + rect: TestSemantics.fullScreen, + flags: SemanticsFlags(isSelected: Tristate.isTrue), + ), + ], + ), + ), + ); + semantics.dispose(); + }); } diff --git a/packages/flutter_test/lib/src/accessibility.dart b/packages/flutter_test/lib/src/accessibility.dart index 285279d3804c5..1a5552752e948 100644 --- a/packages/flutter_test/lib/src/accessibility.dart +++ b/packages/flutter_test/lib/src/accessibility.dart @@ -163,7 +163,7 @@ class MinimumTapTargetGuideline extends AccessibilityGuideline { } // skip node if it is touching the edge scrollable, since it might // be partially scrolled offscreen. - if (current.hasFlag(SemanticsFlag.hasImplicitScrolling) && + if (current.flagsCollection.hasImplicitScrolling && _isAtBoundary(paintBounds, current.rect)) { return result; } @@ -207,11 +207,11 @@ class MinimumTapTargetGuideline extends AccessibilityGuideline { // Skip node if it has no actions, or is marked as hidden. if ((!data.hasAction(ui.SemanticsAction.longPress) && !data.hasAction(ui.SemanticsAction.tap)) || - data.hasFlag(ui.SemanticsFlag.isHidden)) { + data.flagsCollection.isHidden) { return true; } // Skip links https://www.w3.org/WAI/WCAG21/Understanding/target-size.html - if (data.hasFlag(ui.SemanticsFlag.isLink)) { + if (data.flagsCollection.isLink) { return true; } return false; @@ -253,8 +253,8 @@ class LabeledTapTargetGuideline extends AccessibilityGuideline { }); if (node.isMergedIntoParent || node.isInvisible || - node.hasFlag(ui.SemanticsFlag.isHidden) || - node.hasFlag(ui.SemanticsFlag.isTextField)) { + node.flagsCollection.isHidden || + node.flagsCollection.isTextField) { return result; } final SemanticsData data = node.getSemanticsData(); @@ -346,12 +346,11 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline { Evaluation result = const Evaluation.pass(); // Skip disabled nodes, as they not required to pass contrast check. - final bool isDisabled = - node.hasFlag(ui.SemanticsFlag.hasEnabledState) && !node.hasFlag(ui.SemanticsFlag.isEnabled); + final bool isDisabled = node.flagsCollection.isEnabled == ui.Tristate.isFalse; if (node.isInvisible || node.isMergedIntoParent || - node.hasFlag(ui.SemanticsFlag.isHidden) || + node.flagsCollection.isHidden || isDisabled) { return result; } @@ -481,8 +480,7 @@ class MinimumTextContrastGuideline extends AccessibilityGuideline { /// /// Skip routes which might have labels, and nodes without any text. bool shouldSkipNode(SemanticsData data) => - data.hasFlag(ui.SemanticsFlag.scopesRoute) || - (data.label.trim().isEmpty && data.value.trim().isEmpty); + data.flagsCollection.scopesRoute || (data.label.trim().isEmpty && data.value.trim().isEmpty); /// Returns if a rectangle of node is off the screen. /// diff --git a/packages/flutter_test/lib/src/controller.dart b/packages/flutter_test/lib/src/controller.dart index 9d54f05a71aea..413db05458d17 100644 --- a/packages/flutter_test/lib/src/controller.dart +++ b/packages/flutter_test/lib/src/controller.dart @@ -13,6 +13,7 @@ /// @docImport 'widget_tester.dart'; library; +import 'dart:ui' as ui; import 'package:clock/clock.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -74,17 +75,6 @@ class SemanticsController { SemanticsAction.scrollRight.index | SemanticsAction.scrollToOffset.index; - /// Based on Android's FOCUSABLE_FLAGS. See [flutter/engine/AccessibilityBridge.java](https://github.com/flutter/flutter/blob/main/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java). - static final int _importantFlagsForAccessibility = - SemanticsFlag.hasCheckedState.index | - SemanticsFlag.hasToggledState.index | - SemanticsFlag.hasEnabledState.index | - SemanticsFlag.isButton.index | - SemanticsFlag.isTextField.index | - SemanticsFlag.isFocusable.index | - SemanticsFlag.isSlider.index | - SemanticsFlag.isInMutuallyExclusiveGroup.index; - final WidgetController _controller; /// Attempts to find the [SemanticsNode] of first result from `finder`. @@ -353,7 +343,7 @@ class SemanticsController { final SemanticsData data = node.getSemanticsData(); // If the node scopes a route, it doesn't matter what other flags/actions it // has, it is _not_ important for accessibility, so we short circuit. - if (data.hasFlag(SemanticsFlag.scopesRoute)) { + if (data.flagsCollection.scopesRoute) { return false; } @@ -362,7 +352,17 @@ class SemanticsController { return true; } - final bool hasImportantFlag = data.flags & _importantFlagsForAccessibility != 0; + /// Based on Android's FOCUSABLE_FLAGS. See [flutter/engine/AccessibilityBridge.java](https://github.com/flutter/flutter/blob/main/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java). + final bool hasImportantFlag = + data.flagsCollection.isChecked != ui.CheckedState.none || + data.flagsCollection.isToggled != ui.Tristate.none || + data.flagsCollection.isEnabled != ui.Tristate.none || + data.flagsCollection.isButton || + data.flagsCollection.isTextField || + data.flagsCollection.isFocused != ui.Tristate.none || + data.flagsCollection.isSlider || + data.flagsCollection.isInMutuallyExclusiveGroup; + if (hasImportantFlag) { return true; } diff --git a/packages/flutter_test/lib/src/matchers.dart b/packages/flutter_test/lib/src/matchers.dart index 2fb1f571b24d5..8d5444c2209de 100644 --- a/packages/flutter_test/lib/src/matchers.dart +++ b/packages/flutter_test/lib/src/matchers.dart @@ -2454,38 +2454,38 @@ class _MatchesSemanticsData extends Matcher { required String? onLongPressHint, required this.customActions, required this.children, - }) : flags = { - SemanticsFlag.hasCheckedState: ?hasCheckedState, - SemanticsFlag.isChecked: ?isChecked, - SemanticsFlag.isCheckStateMixed: ?isCheckStateMixed, - SemanticsFlag.isSelected: ?isSelected, - SemanticsFlag.hasSelectedState: ?hasSelectedState, - SemanticsFlag.isButton: ?isButton, - SemanticsFlag.isSlider: ?isSlider, - SemanticsFlag.isKeyboardKey: ?isKeyboardKey, - SemanticsFlag.isLink: ?isLink, - SemanticsFlag.isTextField: ?isTextField, - SemanticsFlag.isReadOnly: ?isReadOnly, - SemanticsFlag.isFocused: ?isFocused, - SemanticsFlag.isFocusable: ?isFocusable, - SemanticsFlag.hasEnabledState: ?hasEnabledState, - SemanticsFlag.isEnabled: ?isEnabled, - SemanticsFlag.isInMutuallyExclusiveGroup: ?isInMutuallyExclusiveGroup, - SemanticsFlag.isHeader: ?isHeader, - SemanticsFlag.isObscured: ?isObscured, - SemanticsFlag.isMultiline: ?isMultiline, - SemanticsFlag.namesRoute: ?namesRoute, - SemanticsFlag.scopesRoute: ?scopesRoute, - SemanticsFlag.isHidden: ?isHidden, - SemanticsFlag.isImage: ?isImage, - SemanticsFlag.isLiveRegion: ?isLiveRegion, - SemanticsFlag.hasToggledState: ?hasToggledState, - SemanticsFlag.isToggled: ?isToggled, - SemanticsFlag.hasImplicitScrolling: ?hasImplicitScrolling, - SemanticsFlag.hasExpandedState: ?hasExpandedState, - SemanticsFlag.isExpanded: ?isExpanded, - SemanticsFlag.hasRequiredState: ?hasRequiredState, - SemanticsFlag.isRequired: ?isRequired, + }) : flags = { + 'hasCheckedState': ?hasCheckedState, + 'isChecked': ?isChecked, + 'isCheckStateMixed': ?isCheckStateMixed, + 'isSelected': ?isSelected, + 'hasSelectedState': ?hasSelectedState, + 'isButton': ?isButton, + 'isSlider': ?isSlider, + 'isKeyboardKey': ?isKeyboardKey, + 'isLink': ?isLink, + 'isTextField': ?isTextField, + 'isReadOnly': ?isReadOnly, + 'isFocused': ?isFocused, + 'isFocusable': ?isFocusable, + 'hasEnabledState': ?hasEnabledState, + 'isEnabled': ?isEnabled, + 'isInMutuallyExclusiveGroup': ?isInMutuallyExclusiveGroup, + 'isHeader': ?isHeader, + 'isObscured': ?isObscured, + 'isMultiline': ?isMultiline, + 'namesRoute': ?namesRoute, + 'scopesRoute': ?scopesRoute, + 'isHidden': ?isHidden, + 'isImage': ?isImage, + 'isLiveRegion': ?isLiveRegion, + 'hasToggledState': ?hasToggledState, + 'isToggled': ?isToggled, + 'hasImplicitScrolling': ?hasImplicitScrolling, + 'hasExpandedState': ?hasExpandedState, + 'isExpanded': ?isExpanded, + 'hasRequiredState': ?hasRequiredState, + 'isRequired': ?isRequired, }, actions = { SemanticsAction.tap: ?hasTapAction, @@ -2546,7 +2546,7 @@ class _MatchesSemanticsData extends Matcher { /// 2. If the flag/action maps to `false`, then it must not be present in the SemanticData /// 3. If the flag/action is not in the map, then it will not be validated against final Map actions; - final Map flags; + final Map flags; @override Description describe(Description description, [String? index]) { @@ -2598,27 +2598,27 @@ class _MatchesSemanticsData extends Matcher { .toList(); if (expectedActions.isNotEmpty) { - description.add(' with actions: ${_createEnumsSummary(expectedActions)} '); + description.add(' with actions: ${_createSemanticsActionSummary(expectedActions)} '); } if (notExpectedActions.isNotEmpty) { - description.add(' without actions: ${_createEnumsSummary(notExpectedActions)} '); + description.add(' without actions: ${_createSemanticsActionSummary(notExpectedActions)} '); } } if (flags.isNotEmpty) { - final List expectedFlags = flags.entries - .where((MapEntry e) => e.value) - .map((MapEntry e) => e.key) + final List expectedFlags = flags.entries + .where((MapEntry e) => e.value) + .map((MapEntry e) => e.key) .toList(); - final List notExpectedFlags = flags.entries - .where((MapEntry e) => !e.value) - .map((MapEntry e) => e.key) + final List notExpectedFlags = flags.entries + .where((MapEntry e) => !e.value) + .map((MapEntry e) => e.key) .toList(); if (expectedFlags.isNotEmpty) { - description.add(' with flags: ${_createEnumsSummary(expectedFlags)} '); + description.add(' with flags: ${expectedFlags.join(',')} '); } if (notExpectedFlags.isNotEmpty) { - description.add(' without flags: ${_createEnumsSummary(notExpectedFlags)} '); + description.add(' without flags: ${notExpectedFlags.join(', ')} '); } } if (textDirection != null) { @@ -2803,7 +2803,7 @@ class _MatchesSemanticsData extends Matcher { if (unexpectedActions.isNotEmpty || missingActions.isNotEmpty) { return failWithDescription( matchState, - 'missing actions: ${_createEnumsSummary(missingActions)} unexpected actions: ${_createEnumsSummary(unexpectedActions)}', + 'missing actions: ${_createSemanticsActionSummary(missingActions)} unexpected actions: ${_createSemanticsActionSummary(unexpectedActions)}', ); } } @@ -2848,17 +2848,18 @@ class _MatchesSemanticsData extends Matcher { } } if (flags.isNotEmpty) { - final List unexpectedFlags = []; - final List missingFlags = []; - for (final MapEntry flagEntry in flags.entries) { - final ui.SemanticsFlag flag = flagEntry.key; + final Map foundFlags = _stringsMapFromSemanticsFlags(data.flagsCollection); + final List unexpectedFlags = []; + final List missingFlags = []; + for (final MapEntry flagEntry in flags.entries) { + final String flagName = flagEntry.key; final bool flagExpected = flagEntry.value; - final bool flagPresent = flag.index & data.flags == flag.index; + final bool flagPresent = foundFlags[flagName]!; if (flagPresent != flagExpected) { if (flagExpected) { - missingFlags.add(flag); + missingFlags.add(flagName); } else { - unexpectedFlags.add(flag); + unexpectedFlags.add(flagName); } } } @@ -2866,7 +2867,7 @@ class _MatchesSemanticsData extends Matcher { if (unexpectedFlags.isNotEmpty || missingFlags.isNotEmpty) { return failWithDescription( matchState, - 'missing flags: ${_createEnumsSummary(missingFlags)} unexpected flags: ${_createEnumsSummary(unexpectedFlags)}', + 'missing flags: ${missingFlags.join(',')} unexpected flags: ${unexpectedFlags.join(',')}', ); } } @@ -2897,16 +2898,8 @@ class _MatchesSemanticsData extends Matcher { return mismatchDescription.add(matchState['failure'] as String); } - static String _createEnumsSummary(List enums) { - assert( - T == SemanticsAction || T == SemanticsFlag, - 'This method is only intended for lists of SemanticsActions or SemanticsFlags.', - ); - if (T == SemanticsAction) { - return '[${(enums as List).map((SemanticsAction d) => d.name).join(', ')}]'; - } else { - return '[${(enums as List).map((SemanticsFlag d) => d.name).join(', ')}]'; - } + static String _createSemanticsActionSummary(List enums) { + return '[${enums.map((ui.SemanticsAction d) => d.name).join(', ')}]'; } } @@ -2949,3 +2942,39 @@ class _DoesNotMatchAccessibilityGuideline extends AsyncMatcher { return null; } } + +Map _stringsMapFromSemanticsFlags(SemanticsFlags flagsCollection) { + return { + 'hasCheckedState': flagsCollection.isChecked != ui.CheckedState.none, + 'isChecked': flagsCollection.isChecked == ui.CheckedState.isTrue, + 'isCheckStateMixed': flagsCollection.isChecked == ui.CheckedState.mixed, + 'isSelected': flagsCollection.isSelected == ui.Tristate.isTrue, + 'hasSelectedState': flagsCollection.isSelected != ui.Tristate.none, + 'isButton': flagsCollection.isButton, + 'isSlider': flagsCollection.isSlider, + 'isKeyboardKey': flagsCollection.isKeyboardKey, + 'isLink': flagsCollection.isLink, + 'isFocused': flagsCollection.isFocused == ui.Tristate.isTrue, + 'isFocusable': flagsCollection.isFocused != ui.Tristate.none, + 'isTextField': flagsCollection.isTextField, + 'isReadOnly': flagsCollection.isReadOnly, + 'hasEnabledState': flagsCollection.isEnabled != ui.Tristate.none, + 'isEnabled': flagsCollection.isEnabled == ui.Tristate.isTrue, + 'isInMutuallyExclusiveGroup': flagsCollection.isInMutuallyExclusiveGroup, + 'isHeader': flagsCollection.isHeader, + 'isObscured': flagsCollection.isObscured, + 'isMultiline': flagsCollection.isMultiline, + 'namesRoute': flagsCollection.namesRoute, + 'scopesRoute': flagsCollection.scopesRoute, + 'isHidden': flagsCollection.isHidden, + 'isImage': flagsCollection.isImage, + 'isLiveRegion': flagsCollection.isLiveRegion, + 'hasToggledState': flagsCollection.isToggled != ui.Tristate.none, + 'isToggled': flagsCollection.isToggled == ui.Tristate.isTrue, + 'hasImplicitScrolling': flagsCollection.hasImplicitScrolling, + 'hasExpandedState': flagsCollection.isExpanded != ui.Tristate.none, + 'isExpanded': flagsCollection.isExpanded == ui.Tristate.isTrue, + 'hasRequiredState': flagsCollection.isRequired != ui.Tristate.none, + 'isRequired': flagsCollection.isRequired == ui.Tristate.isTrue, + }; +} From c9608e28d01a81bfbb86aec4bca4a149bf581467 Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Tue, 30 Sep 2025 15:16:13 -0400 Subject: [PATCH 003/204] Implement framework interface for the dialog window archetype (#176202) ## What's new? - :green_apple: Added the concept of dialogs to the windowing API. New symbols: - `DialogWindowControllerDelegate` - `DialogWindowController` - `WindowingOwner.createDialogWindowController` - `DialogWindow` - `WindowControllerCommon` - :shallow_pan_of_food: Introduced the `WindowControllerCommon` mixin to share common windowing functionality between controller types - I have NOT yet implemented dialogs. This pull request ONLY adds the idea to the API. Win32 implementation to follow :) ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- .../multiple_windows/lib/app/main_window.dart | 2 + .../lib/app/window_content.dart | 1 + packages/flutter/lib/src/widgets/_window.dart | 406 +++++++++++++++- .../lib/src/widgets/_window_win32.dart | 12 + .../flutter/test/widgets/windowing_test.dart | 436 ++++++++++++++++-- 5 files changed, 822 insertions(+), 35 deletions(-) diff --git a/examples/multiple_windows/lib/app/main_window.dart b/examples/multiple_windows/lib/app/main_window.dart index c33e1d12e48de..9d42ab20c13e3 100644 --- a/examples/multiple_windows/lib/app/main_window.dart +++ b/examples/multiple_windows/lib/app/main_window.dart @@ -96,12 +96,14 @@ class _WindowsTable extends StatelessWidget { context: context, controller: regular, ), + DialogWindowController() => throw UnimplementedError(), }; } static String _getWindowTypeName(BaseWindowController controller) { return switch (controller) { RegularWindowController() => 'Regular', + DialogWindowController() => 'Dialog', }; } diff --git a/examples/multiple_windows/lib/app/window_content.dart b/examples/multiple_windows/lib/app/window_content.dart index 72ea7fd599075..880b552972881 100644 --- a/examples/multiple_windows/lib/app/window_content.dart +++ b/examples/multiple_windows/lib/app/window_content.dart @@ -33,6 +33,7 @@ class WindowContent extends StatelessWidget { controller: regular, child: MaterialApp(home: RegularWindowContent(window: regular)), ), + DialogWindowController() => throw UnimplementedError(), }; } } diff --git a/packages/flutter/lib/src/widgets/_window.dart b/packages/flutter/lib/src/widgets/_window.dart index 5540b8ef6b827..05ff9d36052e8 100644 --- a/packages/flutter/lib/src/widgets/_window.dart +++ b/packages/flutter/lib/src/widgets/_window.dart @@ -159,7 +159,9 @@ mixin class RegularWindowControllerDelegate { /// {@tool snippet} /// An example usage might look like: /// +/// /// ```dart +/// // TODO(mattkae): remove invalid_use_of_internal_member ignore comment when this API is stable. /// // ignore_for_file: invalid_use_of_internal_member /// import 'package:flutter/widgets.dart'; /// import 'package:flutter/material.dart'; @@ -181,7 +183,7 @@ mixin class RegularWindowControllerDelegate { /// {@end-tool} /// /// Children of a [RegularWindow] widget can access the [RegularWindowController] -/// via the [WindowControllerScope] inherited widget. +/// via the [WindowScope] inherited widget. /// /// {@macro flutter.widgets.windowing.experimental} @internal @@ -190,6 +192,7 @@ abstract class RegularWindowController extends BaseWindowController { /// /// Upon construction, the window is created by the platform. /// + /// {@template flutter.widgets.windowing.constraints} /// The [preferredSize] is the preferred content size of the window. /// This might not be honored by the platform. This is the size that /// the platform will try to apply to the window when it is created. In contrast, @@ -209,6 +212,7 @@ abstract class RegularWindowController extends BaseWindowController { /// /// If both [preferredSize] and [preferredConstraints] are null, /// then the platform will use its own default size for the window. + /// {@endtemplate} /// /// The [title] argument configures the window's initial title. /// If omitted, some platforms might fall back to the app's name. @@ -372,6 +376,250 @@ abstract class RegularWindowController extends BaseWindowController { void setFullscreen(bool fullscreen, {Display? display}); } +/// Delegate class for dialog window controller. +/// +/// {@macro flutter.widgets.windowing.experimental} +/// +/// See also: +/// +/// * [DialogWindowController], the controller that creates and manages dialog windows. +/// * [DialogWindow], the widget for a dialog window. +/// * [RegularWindowControllerDelegate], the delegate for regular window controllers. +@internal +mixin class DialogWindowControllerDelegate { + /// Invoked when the user attempts to close the window. + /// + /// The default implementation destroys the window. Subclasses + /// can override the behavior to delay or prevent the window from closing. + /// + /// {@macro flutter.widgets.windowing.experimental} + /// + /// See also: + /// + /// * [onWindowDestroyed], which is invoked after the window is closed. + @internal + void onWindowCloseRequested(DialogWindowController controller) { + if (!isWindowingEnabled) { + throw UnsupportedError(_kWindowingDisabledErrorMessage); + } + + controller.destroy(); + } + + /// Invoked after the window is closed. + /// + /// {@macro flutter.widgets.windowing.experimental} + /// + /// See also: + /// + /// * [onWindowCloseRequested], which is invoked when the user attempts to close the window. + @internal + void onWindowDestroyed() { + if (!isWindowingEnabled) { + throw UnsupportedError(_kWindowingDisabledErrorMessage); + } + } +} + +/// A controller for a dialog window. +/// +/// Two types of dialogs are supported: +/// * Modal dialogs: created with a non-null parent. These dialogs are modal +/// to the parent, do not have a system menu, and are not selectable from the +/// window switcher. +/// * Modeless dialogs: created with a null parent. These dialogs can be +/// minimized (but not maximized), and have a disabled close button. +/// +/// This class does not interact with the widget tree. Instead, it is typically +/// provided to the [DialogWindow] widget, which renders the content inside the +/// dialog window. +/// +/// The user of this class is responsible for managing the lifecycle of the window. +/// When the window is no longer needed, the user should call [destroy] on this +/// controller to release the resources associated with the window. +/// +/// {@tool snippet} +/// An example usage might look like: +/// +/// ```dart +/// // TODO(mattkae): remove invalid_use_of_internal_member ignore comment when this API is stable. +/// // ignore_for_file: invalid_use_of_internal_member +/// import 'package:flutter/widgets.dart'; +/// import 'package:flutter/material.dart'; +/// import 'package:flutter/src/widgets/_window.dart'; +/// +/// void main() { +/// runWidget( +/// RegularWindow( +/// controller: RegularWindowController( +/// preferredSize: const Size(800, 600), +/// preferredConstraints: const BoxConstraints(minWidth: 640, minHeight: 480), +/// title: 'Example Window', +/// ), +/// child: const MyApp() +/// ) +/// ); +/// } +/// +/// class MyApp extends StatelessWidget { +/// const MyApp({super.key}); +/// +/// @override +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// home: DialogWindow( +/// controller: DialogWindowController( +/// preferredSize: const Size(400, 300), +/// parent: WindowScope.of(context), +/// title: 'Example Dialog' +/// ), +/// child: const Text('Hello, World!') +/// ) +/// ); +/// } +/// } +/// ``` +/// {@end-tool} +/// +/// Children of a [DialogWindow] widget can access the [DialogWindowController] +/// via the [WindowScope] inherited widget. +/// +/// {@macro flutter.widgets.windowing.experimental} +abstract class DialogWindowController extends BaseWindowController { + /// Creates a [DialogWindowController] with the provided properties. + /// + /// Upon construction, the window is created by the platform. + /// + /// {@macro flutter.widgets.windowing.constraints} + /// + /// The [parent] argument specifies the parent window of this dialog. + /// + /// If the [parent] is null, then the dialog is modeless. Such dialogs can + /// be minimized but not maximized. They also have a disabled close button. + /// + /// If the [parent] is non-null, then the dialog is modal to the parent. + /// Such dialogs do not have a system menu. They are also not selectable + /// from the window switcher and they are closed when the parent is closed. + /// + /// The [title] argument configures the window's initial title. + /// If omitted, some platforms might fall back to the app's name. + /// + /// The [delegate] argument can be used to listen to the window's + /// lifecycle. For example, it can be used to save state before + /// a window is closed. + /// + /// {@macro flutter.widgets.windowing.experimental} + factory DialogWindowController({ + Size? preferredSize, + BoxConstraints? preferredConstraints, + BaseWindowController? parent, + String? title, + DialogWindowControllerDelegate? delegate, + }) { + WidgetsFlutterBinding.ensureInitialized(); + final WindowingOwner owner = WidgetsBinding.instance.windowingOwner; + return owner.createDialogWindowController( + delegate: delegate ?? DialogWindowControllerDelegate(), + preferredSize: preferredSize, + preferredConstraints: preferredConstraints, + title: title, + parent: parent, + ); + } + + /// Creates an empty [DialogWindowController]. + /// + /// This method is only intended to be used by subclasses of the + /// [DialogWindowController]. + /// + /// Users who want to instantiate a new [DialogWindowController] should + /// always use the factory method to create a controller that is valid + /// for their particular platform. + /// + /// {@macro flutter.widgets.windowing.experimental} + @internal + @protected + DialogWindowController.empty(); + + /// The parent controller of this dialog, if any. + /// + /// If null, this dialog is modeless. + /// If non-null, this dialog is modal to the parent. + BaseWindowController? get parent; + + /// The current title of the window. + /// + /// This might differ from the requested title. + /// + /// {@macro flutter.widgets.windowing.experimental} + @internal + String get title; + + /// Whether the window is currently activated. + /// + /// If `true` this means that the window is currently focused and + /// can receive user input. + /// + /// {@macro flutter.widgets.windowing.experimental} + @internal + bool get isActivated; + + /// Whether or not window is currently minimized. + /// + /// {@macro flutter.widgets.windowing.experimental} + @internal + bool get isMinimized; + + /// Request change to the content size of the window. + /// + /// The [size] describes the new requested window size. If the size disagrees + /// with the current constraints placed upon the window, the platform might + /// clamp the size within the constraints. + /// + /// The platform is free to ignore this request. + /// + /// {@macro flutter.widgets.windowing.experimental} + @internal + void setSize(Size size); + + /// Request change to the constraints of the window. + /// + /// The [constraints] describes the new constraints that the window should + /// satisfy. If the constraints disagree with the current size of the window, + /// the platform might resize the window to satisfy the new constraints. + /// + /// The platform is free to ignore this request. + /// + /// {@macro flutter.widgets.windowing.experimental} + @internal + void setConstraints(BoxConstraints constraints); + + /// Request change for the window title. + /// + /// {@macro flutter.widgets.windowing.experimental} + @internal + void setTitle(String title); + + /// Requests that the window be displayed in its current size and position. + /// + /// The platform may also give the window input focus and bring it to the + /// top of the window stack. However, this behavior is platform-dependent. + /// + /// If the window is minimized, the window returns to the size and position + /// that it had before that state was applied. The window will also be + /// brought to the top of the window stack. + /// + /// {@macro flutter.widgets.windowing.experimental} + @internal + void activate(); + + /// Requests window to be minimized. + /// + /// {@macro flutter.widgets.windowing.experimental} + @internal + void setMinimized(bool minimized); +} + /// [WindowingOwner] is responsible for creating and managing window controllers. /// /// A custom implementation can be provided by setting [WidgetsBinding.windowingOwner]. @@ -394,6 +642,22 @@ abstract class WindowingOwner { String? title, }); + /// Creates a [DialogWindowController] with the provided properties. + /// + /// Most app developers should use [DialogWindowController]'s constructor + /// instead of calling this method directly. This method allows platforms + /// to inject platform-specific logic. + /// + /// {@macro flutter.widgets.windowing.experimental} + @internal + DialogWindowController createDialogWindowController({ + required DialogWindowControllerDelegate delegate, + Size? preferredSize, + BoxConstraints? preferredConstraints, + BaseWindowController? parent, + String? title, + }); + /// Returns whether the application has any top level windows created by this /// windowing owner. /// @@ -435,6 +699,17 @@ class _WindowingOwnerUnsupported extends WindowingOwner { throw UnsupportedError(errorMessage); } + @override + DialogWindowController createDialogWindowController({ + required DialogWindowControllerDelegate delegate, + Size? preferredSize, + BoxConstraints? preferredConstraints, + BaseWindowController? parent, + String? title, + }) { + throw UnsupportedError(errorMessage); + } + @override bool hasTopLevelWindows() { throw UnsupportedError(errorMessage); @@ -458,6 +733,7 @@ class _WindowingOwnerUnsupported extends WindowingOwner { /// An example usage might look like: /// /// ```dart +/// // TODO(mattkae): remove invalid_use_of_internal_member ignore comment when this API is stable. /// // ignore_for_file: invalid_use_of_internal_member /// import 'package:flutter/widgets.dart'; /// import 'package:flutter/material.dart'; @@ -523,6 +799,107 @@ class RegularWindow extends StatelessWidget { } } +/// The [DialogWindow] widget provides a way to render a dialog window in the +/// widget tree. +/// +/// The provided [controller] creates the native window that backs +/// the widget. The [child] widget is rendered into this newly created window. +/// +/// When a [DialogWindow] widget is removed from the tree, the window that was created +/// by the [controller] remains valid until the caller destroys it by calling +/// [DialogWindowController.destroy]. +/// +/// Widgets in the same tree as the [child] widget will have access to the +/// [DialogWindowController] via the [WindowScope] widget. +/// +/// {@tool snippet} +/// An example usage might look like: +/// +/// ```dart +/// // TODO(mattkae): remove invalid_use_of_internal_member ignore comment when this API is stable. +/// // ignore_for_file: invalid_use_of_internal_member +/// import 'package:flutter/widgets.dart'; +/// import 'package:flutter/material.dart'; +/// import 'package:flutter/src/widgets/_window.dart'; +/// +/// void main() { +/// runWidget( +/// RegularWindow( +/// controller: RegularWindowController( +/// preferredSize: const Size(800, 600), +/// preferredConstraints: const BoxConstraints(minWidth: 640, minHeight: 480), +/// title: 'Example Window', +/// ), +/// child: const MyApp() +/// ) +/// ); +/// } +/// +/// class MyApp extends StatelessWidget { +/// const MyApp({super.key}); +/// +/// @override +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// home: DialogWindow( +/// controller: DialogWindowController( +/// preferredSize: const Size(400, 300), +/// parent: WindowScope.of(context), +/// title: 'Example Dialog' +/// ), +/// child: const Text('Hello, World!') +/// ) +/// ); +/// } +/// } +/// ``` +/// {@end-tool} +/// +/// {@macro flutter.widgets.windowing.experimental} +@internal +class DialogWindow extends StatelessWidget { + /// Creates a dialog window widget. + /// + /// The [controller] creates the native backing window into which the + /// [child] widget is rendered. + /// + /// It is up to the caller to destroy the window by calling + /// [DialogWindowController.destroy] when the window is no longer needed. + /// + /// {@macro flutter.widgets.windowing.experimental} + @internal + DialogWindow({super.key, required this.controller, required this.child}) { + if (!isWindowingEnabled) { + throw UnsupportedError(_kWindowingDisabledErrorMessage); + } + } + + /// Controller for this widget. + /// + /// {@macro flutter.widgets.windowing.experimental} + @internal + final DialogWindowController controller; + + /// The content rendered into this window. + /// + /// {@macro flutter.widgets.windowing.experimental} + @internal + final Widget child; + + /// {@macro flutter.widgets.windowing.experimental} + @internal + @override + Widget build(BuildContext context) { + return ListenableBuilder( + listenable: controller, + builder: (BuildContext context, Widget? widget) => WindowScope( + controller: controller, + child: View(view: controller.rootView, child: child), + ), + ); + } +} + enum _WindowControllerAspect { contentSize, title, activated, maximized, minimized, fullscreen } /// Provides descendants with access to the [BaseWindowController] associated with @@ -537,6 +914,7 @@ enum _WindowControllerAspect { contentSize, title, activated, maximized, minimiz /// See also: /// /// * [RegularWindow], the widget to create a regular window. +/// * [DialogWindow], the widget to create a dialog window. @internal class WindowScope extends InheritedModel<_WindowControllerAspect> { /// Creates a new [WindowScope]. @@ -565,8 +943,8 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// Returns the [BaseWindowController] for the window that hosts the given context. /// - /// {@template flutter.widgets.windowing.windowControllerScope.of} - /// If there is no [WindowControllerScope] in scope, this method + /// {@template flutter.widgets.windowing.WindowScope.of} + /// If there is no [WindowScope] in scope, this method /// will throw a [TypeError] exception in release builds, and throws /// a descriptive [FlutterError] in debug builds. /// @@ -580,7 +958,9 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// See also: /// /// * [RegularWindowController], the controller for regular top-level windows. + /// * [DialogWindowController], the controller for dialog windows. /// * [RegularWindow], the widget for a regular window. + /// * [DialogWindow], the widget for a dialog window. /// * [maybeOf], which doesn't throw or assert if it doesn't find a /// [WindowScope] ancestor. It returns null instead. @internal @@ -595,7 +975,9 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { /// See also: /// /// * [RegularWindowController], the controller for regular top-level windows. + /// * [DialogWindowController], the controller for dialog windows. /// * [RegularWindow], the widget for a regular window. + /// * [DialogWindow], the widget for a dialog window. /// * [of], which will throw if it doesn't find a [WindowScope] ancestor, /// instead of returning null. @internal @@ -648,6 +1030,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { final BaseWindowController controller = _of(context, _WindowControllerAspect.title); return switch (controller) { RegularWindowController() => controller.title, + DialogWindowController() => controller.title, }; } @@ -668,6 +1051,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { return switch (controller) { RegularWindowController() => controller.title, + DialogWindowController() => controller.title, }; } @@ -689,6 +1073,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { final BaseWindowController controller = _of(context, _WindowControllerAspect.activated); return switch (controller) { RegularWindowController() => controller.isActivated, + DialogWindowController() => controller.isActivated, }; } @@ -710,6 +1095,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { return switch (controller) { RegularWindowController() => controller.isActivated, + DialogWindowController() => controller.isActivated, }; } @@ -731,6 +1117,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { final BaseWindowController controller = _of(context, _WindowControllerAspect.minimized); return switch (controller) { RegularWindowController() => controller.isMinimized, + DialogWindowController() => controller.isMinimized, }; } @@ -752,6 +1139,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { return switch (controller) { RegularWindowController() => controller.isMinimized, + DialogWindowController() => controller.isMinimized, }; } @@ -773,6 +1161,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { final BaseWindowController controller = _of(context, _WindowControllerAspect.maximized); return switch (controller) { RegularWindowController() => controller.isMaximized, + DialogWindowController() => false, }; } @@ -794,6 +1183,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { return switch (controller) { RegularWindowController() => controller.isMaximized, + DialogWindowController() => false, }; } @@ -816,6 +1206,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { return switch (controller) { RegularWindowController() => controller.isFullscreen, + DialogWindowController() => false, }; } @@ -837,6 +1228,7 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { return switch (controller) { RegularWindowController() => controller.isFullscreen, + DialogWindowController() => false, }; } @@ -898,26 +1290,34 @@ class WindowScope extends InheritedModel<_WindowControllerAspect> { _WindowControllerAspect.title => switch (controller) { final RegularWindowController regular => regular.title != (oldWidget.controller as RegularWindowController).title, + final DialogWindowController dialog => + dialog.title != (oldWidget.controller as DialogWindowController).title, }, _WindowControllerAspect.activated => switch (controller) { final RegularWindowController regular => regular.isActivated != (oldWidget.controller as RegularWindowController).isActivated, + final DialogWindowController dialog => + dialog.isActivated != (oldWidget.controller as DialogWindowController).isActivated, }, _WindowControllerAspect.maximized => switch (controller) { final RegularWindowController regular => regular.isMaximized != (oldWidget.controller as RegularWindowController).isMaximized, + DialogWindowController() => false, }, _WindowControllerAspect.minimized => switch (controller) { final RegularWindowController regular => regular.isMinimized != (oldWidget.controller as RegularWindowController).isMinimized, + final DialogWindowController dialog => + dialog.isMinimized != (oldWidget.controller as DialogWindowController).isMinimized, }, _WindowControllerAspect.fullscreen => switch (controller) { final RegularWindowController regular => regular.isFullscreen != (oldWidget.controller as RegularWindowController).isFullscreen, + DialogWindowController() => false, }, }, ); diff --git a/packages/flutter/lib/src/widgets/_window_win32.dart b/packages/flutter/lib/src/widgets/_window_win32.dart index 1d6a81c3ccfe7..46647a630a6db 100644 --- a/packages/flutter/lib/src/widgets/_window_win32.dart +++ b/packages/flutter/lib/src/widgets/_window_win32.dart @@ -148,6 +148,18 @@ class WindowingOwnerWin32 extends WindowingOwner { ); } + @internal + @override + DialogWindowController createDialogWindowController({ + required DialogWindowControllerDelegate delegate, + Size? preferredSize, + BoxConstraints? preferredConstraints, + BaseWindowController? parent, + String? title, + }) { + throw UnimplementedError('Dialog windows are not yet implemented on Windows.'); + } + /// Register a new [WindowsMessageHandler]. /// /// The handler will be triggered for unhandled messages for all top level diff --git a/packages/flutter/test/widgets/windowing_test.dart b/packages/flutter/test/widgets/windowing_test.dart index ca8995bed2da1..c14aa3b833a84 100644 --- a/packages/flutter/test/widgets/windowing_test.dart +++ b/packages/flutter/test/widgets/windowing_test.dart @@ -7,6 +7,9 @@ import 'package:flutter/src/foundation/_features.dart' show isWindowingEnabled; import 'package:flutter/src/widgets/_window.dart' show BaseWindowController, + DialogWindow, + DialogWindowController, + DialogWindowControllerDelegate, RegularWindow, RegularWindowController, RegularWindowControllerDelegate, @@ -18,8 +21,8 @@ import 'package:flutter_test/flutter_test.dart'; import 'multi_view_testing.dart'; -class _StubWindowController extends RegularWindowController { - _StubWindowController(WidgetTester tester) : super.empty() { +class _StubRegularWindowController extends RegularWindowController { + _StubRegularWindowController(WidgetTester tester) : super.empty() { rootView = FakeView(tester.view); } @@ -66,6 +69,45 @@ class _StubWindowController extends RegularWindowController { void destroy() {} } +class _StubDialogWindowController extends DialogWindowController { + _StubDialogWindowController(WidgetTester tester) : super.empty() { + rootView = FakeView(tester.view); + } + + @override + BaseWindowController? get parent => null; + + @override + Size get contentSize => Size.zero; + + @override + String get title => 'Stub Window'; + + @override + bool get isActivated => true; + + @override + bool get isMinimized => false; + + @override + void setSize(Size size) {} + + @override + void setConstraints(BoxConstraints constraints) {} + + @override + void setTitle(String title) {} + + @override + void activate() {} + + @override + void setMinimized(bool minimized) {} + + @override + void destroy() {} +} + void main() { group('Windowing', () { group('isWindowingEnabled is false', () { @@ -86,6 +128,14 @@ void main() { ); }); + test('default WindowingOwner throws when accessing createDialogWindowController', () { + final WindowingOwner owner = createDefaultWindowingOwner(); + expect( + () => owner.createDialogWindowController(delegate: DialogWindowControllerDelegate()), + throwsUnsupportedError, + ); + }); + test('default WindowingOwner throws when accessing hasTopLevelWindows', () { final WindowingOwner owner = createDefaultWindowingOwner(); expect(() => owner.hasTopLevelWindows(), throwsUnsupportedError); @@ -93,7 +143,20 @@ void main() { testWidgets('RegularWindow throws UnsupportedError', (WidgetTester tester) async { expect( - () => RegularWindow(controller: _StubWindowController(tester), child: const Text('Test')), + () => RegularWindow( + controller: _StubRegularWindowController(tester), + child: const Text('Test'), + ), + throwsUnsupportedError, + ); + }); + + testWidgets('DialogWindow throws UnsupportedError', (WidgetTester tester) async { + expect( + () => DialogWindow( + controller: _StubDialogWindowController(tester), + child: const Text('Test'), + ), throwsUnsupportedError, ); }); @@ -112,7 +175,7 @@ void main() { }); testWidgets('RegularWindow does not throw', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -120,8 +183,17 @@ void main() { ); }); - testWidgets('Can access WindowScope.of', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Dialog does not throw', (WidgetTester tester) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow(controller: controller, child: Container()), + ); + }); + + testWidgets('Can access WindowScope.of for regular windows', (WidgetTester tester) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -138,8 +210,28 @@ void main() { ); }); - testWidgets('Can access WindowScope.maybeOf', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Can access WindowScope.of for dialog windows', (WidgetTester tester) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final BaseWindowController scope = WindowScope.of(context); + expect(scope, isA()); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); + + testWidgets('Can access WindowScope.maybeOf for regular windows', ( + WidgetTester tester, + ) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -156,8 +248,28 @@ void main() { ); }); - testWidgets('Can access WindowScope.contentSizeOf', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Can access WindowScope.maybeOf for dialog windows', (WidgetTester tester) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final BaseWindowController? scope = WindowScope.maybeOf(context); + expect(scope, isA()); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); + + testWidgets('Can access WindowScope.contentSizeOf for regular windows', ( + WidgetTester tester, + ) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -174,8 +286,30 @@ void main() { ); }); - testWidgets('Can access WindowScope.maybeContentSizeOf', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Can access WindowScope.contentSizeOf for dialog windows', ( + WidgetTester tester, + ) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final Size size = WindowScope.contentSizeOf(context); + expect(size, equals(Size.zero)); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); + + testWidgets('Can access WindowScope.maybeContentSizeOf for regular windows', ( + WidgetTester tester, + ) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -192,8 +326,30 @@ void main() { ); }); - testWidgets('Can access WindowScope.titleOf', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Can access WindowScope.maybeContentSizeOf for dialog windows', ( + WidgetTester tester, + ) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final Size? size = WindowScope.maybeContentSizeOf(context); + expect(size, equals(Size.zero)); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); + + testWidgets('Can access WindowScope.titleOf for regular windows', ( + WidgetTester tester, + ) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -210,8 +366,28 @@ void main() { ); }); - testWidgets('Can access WindowScope.maybeTitleOf', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Can access WindowScope.titleOf for dialog windows', (WidgetTester tester) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final String title = WindowScope.titleOf(context); + expect(title, equals('Stub Window')); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); + + testWidgets('Can access WindowScope.maybeTitleOf for regular windows', ( + WidgetTester tester, + ) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -228,8 +404,30 @@ void main() { ); }); - testWidgets('Can access WindowScope.isActivatedOf', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Can access WindowScope.maybeTitleOf for dialog windows', ( + WidgetTester tester, + ) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final String? title = WindowScope.maybeTitleOf(context); + expect(title, equals('Stub Window')); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); + + testWidgets('Can access WindowScope.isActivatedOf for regular windows', ( + WidgetTester tester, + ) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -246,8 +444,30 @@ void main() { ); }); - testWidgets('Can access WindowScope.maybeIsActivatedOf', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Can access WindowScope.isActivatedOf for dialog windows', ( + WidgetTester tester, + ) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final bool isActivated = WindowScope.isActivatedOf(context); + expect(isActivated, equals(true)); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); + + testWidgets('Can access WindowScope.maybeIsActivatedOf for regular windows', ( + WidgetTester tester, + ) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -264,8 +484,30 @@ void main() { ); }); - testWidgets('Can access WindowScope.isMinimizedOf', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Can access WindowScope.maybeIsActivatedOf for dialog windows', ( + WidgetTester tester, + ) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final bool? isActivated = WindowScope.maybeIsActivatedOf(context); + expect(isActivated, equals(true)); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); + + testWidgets('Can access WindowScope.isMinimizedOf for regular windows', ( + WidgetTester tester, + ) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -282,8 +524,30 @@ void main() { ); }); - testWidgets('Can access WindowScope.maybeIsMinimizedOf', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Can access WindowScope.isMinimizedOf for dialog windows', ( + WidgetTester tester, + ) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final bool isMinimized = WindowScope.isMinimizedOf(context); + expect(isMinimized, equals(false)); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); + + testWidgets('Can access WindowScope.maybeIsMinimizedOf for regular windows', ( + WidgetTester tester, + ) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -300,8 +564,30 @@ void main() { ); }); - testWidgets('Can access WindowScope.isMaximizedOf', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Can access WindowScope.maybeIsMinimizedOf for dialog windows', ( + WidgetTester tester, + ) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final bool? isMinimized = WindowScope.maybeIsMinimizedOf(context); + expect(isMinimized, equals(false)); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); + + testWidgets('Can access WindowScope.isMaximizedOf for regular windows', ( + WidgetTester tester, + ) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -318,8 +604,30 @@ void main() { ); }); - testWidgets('Can access WindowScope.maybeIsMaximizedOf', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Can access WindowScope.isMaximizedOf for dialog windows', ( + WidgetTester tester, + ) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final bool isMaximized = WindowScope.isMaximizedOf(context); + expect(isMaximized, equals(false)); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); + + testWidgets('Can access WindowScope.maybeIsMaximizedOf for regular windows', ( + WidgetTester tester, + ) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -336,8 +644,30 @@ void main() { ); }); - testWidgets('Can access WindowScope.isFullscreenOf', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Can access WindowScope.maybeIsMaximizedOf for dialog windows', ( + WidgetTester tester, + ) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final bool? isMaximized = WindowScope.maybeIsMaximizedOf(context); + expect(isMaximized, equals(false)); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); + + testWidgets('Can access WindowScope.isFullscreenOf for regular windows', ( + WidgetTester tester, + ) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -354,8 +684,30 @@ void main() { ); }); - testWidgets('Can access WindowScope.maybeIsFullscreenOf', (WidgetTester tester) async { - final _StubWindowController controller = _StubWindowController(tester); + testWidgets('Can access WindowScope.isFullscreenOf for dialog windows', ( + WidgetTester tester, + ) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final bool isFullscreen = WindowScope.isFullscreenOf(context); + expect(isFullscreen, equals(false)); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); + + testWidgets('Can access WindowScope.maybeIsFullscreenOf for regular windows', ( + WidgetTester tester, + ) async { + final _StubRegularWindowController controller = _StubRegularWindowController(tester); addTearDown(controller.dispose); await tester.pumpWidget( wrapWithView: false, @@ -371,6 +723,26 @@ void main() { ), ); }); + + testWidgets('Can access WindowScope.maybeIsFullscreenOf for dialog windows', ( + WidgetTester tester, + ) async { + final _StubDialogWindowController controller = _StubDialogWindowController(tester); + addTearDown(controller.dispose); + await tester.pumpWidget( + wrapWithView: false, + DialogWindow( + controller: controller, + child: Builder( + builder: (BuildContext context) { + final bool? isFullscreen = WindowScope.maybeIsFullscreenOf(context); + expect(isFullscreen, equals(false)); + return const SizedBox.shrink(); + }, + ), + ), + ); + }); }); }); } From ef611a7e4cdbd93a7b61a8cc5214f80e95e4f07f Mon Sep 17 00:00:00 2001 From: zhongliugo Date: Tue, 30 Sep 2025 13:52:43 -0700 Subject: [PATCH 004/204] Web semantics: Fix email field selection/cursor by using type="text" + inputmode="email" (#175876) **What/Why** On Flutter Web with semantics enabled, can reject selection APIs in some browsers, causing: cursor to not advance while typing, selection to fail (and selected text not deletable), and, in some cases, InvalidStateError exceptions. This PR updates the semantics editing element for email fields to keep selection/cursor APIs working while preserving email UX. **How** In engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart: Use type="text" for SemanticsInputType.email Add inputmode="email" (keeps email keyboard layout/hints) Add autocomplete="email" (preserves autofill) Add autocapitalize="none" (prevents unwanted capitalization) Remove the attributes when not email Rationale: type="text" avoids browser selection restrictions; inputmode="email" preserves the expected email keyboard; autofill and capitalization behavior is retained/optimized. **Before/After** Before the change https://email-0923-before.web.app/ After the change https://email-0923.web.app/ **Impact** Cursor now advances correctly while typing in email fields under semantics. Text can be selected and deleted normally. Avoids InvalidStateError crashes with accessibility semantics enabled. **Tests/Verification** Manually verified on Chrome and safari: Typing in email fields advances cursor correctly Drag-select, double-click select, and Cmd/Ctrl+A work Deleting selected text works No exceptions thrown with semantics enabled Autofill prompts continue to appear when saved emails are available. Mobile keyboards (iOS/Android) show email-optimized layout via inputmode="email". **Fixed issues** Fixes flutter/flutter#173239 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- .../lib/src/engine/semantics/text_field.dart | 29 ++++++++++++++----- .../engine/semantics/text_field_test.dart | 16 +++++++++- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart index 59567e10feec4..e86283c091143 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart @@ -353,13 +353,28 @@ class SemanticTextField extends SemanticRole { if (semanticsObject.flags.isObscured) { input.type = 'password'; } else { - input.type = switch (semanticsObject.inputType) { - ui.SemanticsInputType.search => 'search', - ui.SemanticsInputType.email => 'email', - ui.SemanticsInputType.url => 'url', - ui.SemanticsInputType.phone => 'tel', - _ => 'text', - }; + // For email inputs, prefer type="text" with inputmode="email" so that + // browsers keep selection APIs enabled while still providing email + // keyboards and hints. This avoids InvalidStateError and enables + // proper selection/cursor operations. + input.removeAttribute('inputmode'); + input.removeAttribute('autocapitalize'); + input.autocomplete = 'off'; + input.type = 'text'; + + switch (semanticsObject.inputType) { + case ui.SemanticsInputType.search: + input.type = 'search'; + case ui.SemanticsInputType.url: + input.type = 'url'; + case ui.SemanticsInputType.phone: + input.type = 'tel'; + case ui.SemanticsInputType.email: + input.setAttribute('inputmode', 'email'); + input.setAttribute('autocapitalize', 'none'); + input.autocomplete = 'email'; + default: + } } } diff --git a/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart b/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart index e0dc95eafff14..01df1f9ef3640 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart @@ -124,7 +124,8 @@ void testMain() { ui.SemanticsInputType.url: 'url', ui.SemanticsInputType.phone: 'tel', ui.SemanticsInputType.search: 'search', - ui.SemanticsInputType.email: 'email', + // Email uses type="text" to preserve selection APIs under semantics. + ui.SemanticsInputType.email: 'text', }; for (final ui.SemanticsInputType type in ui.SemanticsInputType.values) { createTextFieldSemantics(value: 'text', inputType: type); @@ -133,6 +134,19 @@ void testMain() { } }); + test('email input uses type=text with inputmode=email and autocomplete=email', () { + createTextFieldSemantics(value: 'text', inputType: ui.SemanticsInputType.email); + + final node = owner().debugSemanticsTree![0]!; + final textFieldRole = node.semanticRole! as SemanticTextField; + final inputElement = textFieldRole.editableElement as DomHTMLInputElement; + + expect(inputElement.type, 'text'); + expect(inputElement.getAttribute('inputmode'), 'email'); + expect(inputElement.getAttribute('autocapitalize'), 'none'); + expect(inputElement.autocomplete, 'email'); + }); + test('renders a disabled text field', () { createTextFieldSemantics(isEnabled: false, value: 'hello'); expectSemanticsTree(owner(), ''''''); From 7c5e70191f6634e9a99d527f7cc8c8c16adcd8cd Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Tue, 30 Sep 2025 18:36:54 -0500 Subject: [PATCH 005/204] Add verbose logs to module_uiscene_test_ios (#176306) Debugging why https://ci.chromium.org/ui/p/flutter/builders/staging/Mac%20module_uiscene_test_ios is failing. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- dev/devicelab/bin/tasks/module_uiscene_test_ios.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart index e212b1c0a00ba..995d65d804a81 100644 --- a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart +++ b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart @@ -213,11 +213,14 @@ Future _installPlugins({ await eval( 'pod', - ['install'], - environment: {'LANG': 'en_US.UTF-8'}, + ['install', '--verbose'], + environment: { + // See https://github.com/flutter/flutter/issues/10873. + // CocoaPods analytics adds a lot of latency. + 'COCOAPODS_DISABLE_STATS': 'true', + 'LANG': 'en_US.UTF-8', + }, workingDirectory: xcodeProjectDir.path, - printStdout: false, - printStderr: false, ); } From 37487b3e4c4a7b6adf06202921eb8f415ae1bc95 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 30 Sep 2025 19:36:56 -0400 Subject: [PATCH 006/204] [ Widget Preview ] Forward Widget Inspector navigation events via DTD (#176218) With the assumption that IDEs will not create a debug session for the widget previewer, the widget inspector's source navigation functionality won't function as there's no IDE to listen to `navigate` events sent via `postEvent`. This change overrides some widget inspector behavior to allow for navigating to source locations via the DTD Editor service instead of relying on the VM service's `postEvent`. This change also makes some minor changes to the diagnostic properties and descriptions displayed within the inspector to display group and custom preview annotation details. Towards https://github.com/flutter/flutter/issues/166423 --- .../lib/src/commands/widget_preview.dart | 1 + .../lib/src/resident_runner.dart | 10 +-- .../src/widget_preview/dependency_graph.dart | 16 +++- .../preview_code_generator.dart | 4 + .../src/widget_preview/preview_details.dart | 8 ++ .../lib/src/utils.dart.tmpl | 12 +++ .../lib/src/widget_preview.dart.tmpl | 19 +++++ .../src/widget_preview_rendering.dart.tmpl | 14 +++- ...dget_preview_scaffold_controller.dart.tmpl | 83 ++++++++++++++++++- .../preview_code_generator_test.dart | 16 ++++ .../widget_preview/widget_preview_test.dart | 2 + .../lib/src/utils.dart | 12 +++ .../lib/src/widget_preview.dart | 19 +++++ .../lib/src/widget_preview_rendering.dart | 14 +++- .../widget_preview_scaffold_controller.dart | 83 ++++++++++++++++++- .../test/custom_previews_test.dart | 2 + .../test/error_widget_test.dart | 4 +- .../test/filter_by_selected_file_test.dart | 14 +--- .../test/localizations_test.dart | 4 +- .../test/no_previews_detected_test.dart | 2 + .../test/soft_restart_test.dart | 4 +- .../test/theming_test.dart | 4 +- .../test/widget_inspector_test.dart | 57 ++++++++++++- 23 files changed, 369 insertions(+), 35 deletions(-) diff --git a/packages/flutter_tools/lib/src/commands/widget_preview.dart b/packages/flutter_tools/lib/src/commands/widget_preview.dart index e6c509658a535..8452657df2b3a 100644 --- a/packages/flutter_tools/lib/src/commands/widget_preview.dart +++ b/packages/flutter_tools/lib/src/commands/widget_preview.dart @@ -448,6 +448,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C widgetPreviewScaffoldProject.packageConfig.readAsBytesSync(), widgetPreviewScaffoldProject.packageConfig.uri, ), + trackWidgetCreation: true, // Don't try and download canvaskit from the CDN. useLocalCanvasKit: true, webEnableHotReload: true, diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index ed44be71db965..534cfc22a89c8 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -1468,11 +1468,11 @@ abstract class ResidentRunner extends ResidentHandlers { /// of the default URI encoding. String urlToDisplayString(Uri uri) { final base = StringBuffer(uri.withoutQueryParameters().toString()); - base.write( - uri.queryParameters.keys - .map((String key) => '$key=${uri.queryParameters[key]}') - .join('&'), - ); + if (uri.hasQuery) { + base.write( + '?${uri.queryParameters.keys.map((String key) => '$key=${uri.queryParameters[key]}').join('&')}', + ); + } return base.toString(); } diff --git a/packages/flutter_tools/lib/src/widget_preview/dependency_graph.dart b/packages/flutter_tools/lib/src/widget_preview/dependency_graph.dart index 278e52c308b87..1dba4fa28f9b1 100644 --- a/packages/flutter_tools/lib/src/widget_preview/dependency_graph.dart +++ b/packages/flutter_tools/lib/src/widget_preview/dependency_graph.dart @@ -14,6 +14,7 @@ import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/diagnostic/diagnostic.dart'; import 'package:analyzer/error/error.dart'; +import 'package:analyzer/source/line_info.dart'; import '../base/logger.dart'; import 'preview_details.dart'; @@ -45,9 +46,12 @@ class _PreviewVisitor extends RecursiveAstVisitor { MethodDeclaration? _currentMethod; late Uri _currentScriptUri; + late CompilationUnit _currentUnit; void findPreviewsInResolvedUnitResult(ResolvedUnitResult unit) { - _scopedVisitChildren(unit.unit, (_) => _currentScriptUri = unit.file.toUri()); + _currentScriptUri = unit.file.toUri(); + _currentUnit = unit.unit; + _currentUnit.visitChildren(this); } /// Handles previews defined on top-level functions. @@ -96,6 +100,10 @@ class _PreviewVisitor extends RecursiveAstVisitor { if (preview == null) { return; } + final LineInfo lineInfo = _currentUnit.lineInfo; + final CharacterLocation location = lineInfo.getLocation(node.offset); + final int line = location.lineNumber; + final int column = location.columnNumber; if (_currentFunction != null && !hasRequiredParams(_currentFunction!.functionExpression.parameters)) { final TypeAnnotation? returnTypeAnnotation = _currentFunction!.returnType; @@ -105,6 +113,8 @@ class _PreviewVisitor extends RecursiveAstVisitor { previewEntries.add( PreviewDetails( scriptUri: _currentScriptUri, + line: line, + column: column, packageName: packageName, functionName: _currentFunction!.name.toString(), isBuilder: returnType.isWidgetBuilder, @@ -120,6 +130,8 @@ class _PreviewVisitor extends RecursiveAstVisitor { previewEntries.add( PreviewDetails( scriptUri: _currentScriptUri, + line: line, + column: column, packageName: packageName, functionName: '$returnType${name == null ? '' : '.$name'}', isBuilder: false, @@ -136,6 +148,8 @@ class _PreviewVisitor extends RecursiveAstVisitor { previewEntries.add( PreviewDetails( scriptUri: _currentScriptUri, + line: line, + column: column, packageName: packageName, functionName: '${parentClass.name}.${_currentMethod!.name}', isBuilder: returnType.isWidgetBuilder, diff --git a/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart b/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart index 29244214dd7c3..baeacbe763303 100644 --- a/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart +++ b/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart @@ -32,7 +32,9 @@ class PreviewCodeGenerator { static const _kBuildMultiWidgetPreview = 'buildMultiWidgetPreview'; static const _kBuildWidgetPreview = 'buildWidgetPreview'; static const _kBuildWidgetPreviewError = 'buildWidgetPreviewError'; + static const _kColumn = 'column'; static const _kDependencyHasErrors = 'dependencyHasErrors'; + static const _kLine = 'line'; static const _kListType = 'List'; static const _kPackageName = 'packageName'; static const _kPackageUri = 'packageUri'; @@ -164,6 +166,8 @@ class PreviewCodeGenerator { final args = { _kPackageName: cb.literalString(preview.packageName!), _kScriptUri: cb.literalString(preview.scriptUri.toString()), + _kLine: cb.literalNum(preview.line), + _kColumn: cb.literalNum(preview.column), }; // TODO(bkonyi): improve the error related code. if (libraryDetails.hasErrors || libraryDetails.dependencyHasErrors) { diff --git a/packages/flutter_tools/lib/src/widget_preview/preview_details.dart b/packages/flutter_tools/lib/src/widget_preview/preview_details.dart index 91b0bf0ad6fbe..733438af9ee90 100644 --- a/packages/flutter_tools/lib/src/widget_preview/preview_details.dart +++ b/packages/flutter_tools/lib/src/widget_preview/preview_details.dart @@ -10,6 +10,8 @@ typedef PreviewProperty = ({String key, DartObject object, bool isCallback}); final class PreviewDetails { PreviewDetails({ required this.scriptUri, + required this.line, + required this.column, required this.packageName, required this.functionName, required this.isBuilder, @@ -20,6 +22,12 @@ final class PreviewDetails { /// The file:// URI pointing to the script in which the preview is defined. final Uri scriptUri; + /// The 1-based line at which the Preview annotation was applied. + final int line; + + /// The 1-based column at which the Preview annotation was applied. + final int column; + /// The name of the package in which the preview was defined. /// /// For example, if this preview is defined in 'package:foo/src/bar.dart', this diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils.dart.tmpl index f66c203e5b29e..05d0bef7315e0 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils.dart.tmpl @@ -9,6 +9,8 @@ import 'package:widget_preview_scaffold/src/widget_preview.dart'; Iterable buildMultiWidgetPreview({ required String packageName, required String scriptUri, + required int line, + required int column, required MultiPreview preview, required Object? Function() previewFunction, }) { @@ -16,6 +18,8 @@ Iterable buildMultiWidgetPreview({ (p) => buildWidgetPreview( packageName: packageName, scriptUri: scriptUri, + line: line, + column: column, transformedPreview: p, previewFunction: previewFunction, ), @@ -25,6 +29,8 @@ Iterable buildMultiWidgetPreview({ WidgetPreview buildWidgetPreview({ required String packageName, required String scriptUri, + required int line, + required int column, required Preview transformedPreview, required Object? Function() previewFunction, }) { @@ -39,6 +45,8 @@ WidgetPreview buildWidgetPreview({ return WidgetPreview( builder: previewBuilder, scriptUri: scriptUri, + line: line, + column: column, previewData: transformedPreview, packageName: packageName, ); @@ -47,6 +55,8 @@ WidgetPreview buildWidgetPreview({ WidgetPreview buildWidgetPreviewError({ required String packageName, required String scriptUri, + required int line, + required int column, required String packageUri, required String functionName, required bool dependencyHasErrors, @@ -58,6 +68,8 @@ WidgetPreview buildWidgetPreviewError({ return WidgetPreview( builder: () => Text('$functionName: $errorMessage'), scriptUri: scriptUri, + line: line, + column: column, previewData: const Preview(group: 'Invalid Previews'), packageName: packageName, ); diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview.dart.tmpl index 2c4af1eddf541..29be79b4769e9 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview.dart.tmpl @@ -31,15 +31,33 @@ class WidgetPreview { const WidgetPreview({ required this.builder, required this.scriptUri, + required this.line, + required this.column, required this.previewData, required this.packageName, }); + @visibleForTesting + const WidgetPreview.test({ + required this.builder, + required this.previewData, + this.scriptUri = '', + this.line = -1, + this.column = -1, + this.packageName = '', + }); + /// The absolute file:// URI pointing to the script containing this preview. /// /// This matches the URI format sent by IDEs for active location change events. final String scriptUri; + /// The line at which the Preview annotation was applied. + final int line; + + /// The column at which the Preview annotation was applied. + final int column; + /// The name of the package in which a preview was defined. /// /// For example, if a preview is defined in 'package:foo/src/bar.dart', this @@ -104,6 +122,7 @@ class WidgetPreview { void debugFillProperties(DiagnosticPropertiesBuilder properties) { properties ..add(DiagnosticsProperty('name', name, ifNull: 'not set')) + ..add(DiagnosticsProperty('group', previewData.group)) ..add(DiagnosticsProperty('size', size)) ..add(DiagnosticsProperty('textScaleFactor', textScaleFactor)) ..add(DiagnosticsProperty('theme', theme)) diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl index 2549899543a76..47faa6c5c1051 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl @@ -178,12 +178,16 @@ class NoPreviewsDetectedWidget extends StatelessWidget { } } +/// A wrapper that serves as the root entry for a single preview in the widget inspector. class PreviewWidget extends StatelessWidget { const PreviewWidget({super.key, required this.preview, required this.child}); final WidgetPreview preview; final Widget child; + @override + StatelessElement createElement() => PreviewWidgetElement(this); + @override Widget build(BuildContext context) { return child; @@ -191,7 +195,9 @@ class PreviewWidget extends StatelessWidget { @override String toStringShort() { - final StringBuffer buffer = StringBuffer('@Preview'); + final StringBuffer buffer = StringBuffer( + '@${preview.previewData.runtimeType}', + ); if (preview.name != null) { buffer.write('(name: "${preview.name}")'); } @@ -205,6 +211,12 @@ class PreviewWidget extends StatelessWidget { } } +/// A custom [StatelessElement] with the sole purpose of simplifying identifying +/// selections of @Preview annotations in the widget inspector. +class PreviewWidgetElement extends StatelessElement { + PreviewWidgetElement(super.widget); +} + class WidgetPreviewGroupWidget extends StatelessWidget { const WidgetPreviewGroupWidget({ super.key, diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart.tmpl index 1a53a1c3d7cc6..a5b1106c93c0c 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart.tmpl @@ -3,7 +3,10 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; import 'package:path/path.dart' as path; +import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; +import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart'; import 'dtd/dtd_services.dart'; import 'widget_preview.dart'; @@ -21,7 +24,11 @@ class WidgetPreviewScaffoldController { required PreviewsCallback previews, @visibleForTesting WidgetPreviewScaffoldDtdServices? dtdServicesOverride, }) : _previews = previews, - dtdServices = dtdServicesOverride ?? WidgetPreviewScaffoldDtdServices(); + dtdServices = dtdServicesOverride ?? WidgetPreviewScaffoldDtdServices() { + // Overrides the default WidgetInspectorService instance to handle selection + // events. + _WidgetPreviewScaffoldInspectorService(dtdServices: dtdServices); + } /// Initializes the controller by establishing a connection to DTD and /// listening for events. @@ -154,3 +161,77 @@ class WidgetPreviewScaffoldController { } } } + +/// A custom [WidgetInspectorService] responsible for routing navigation events +/// to the IDE. +class _WidgetPreviewScaffoldInspectorService with WidgetInspectorService { + _WidgetPreviewScaffoldInspectorService({required this.dtdServices}) { + WidgetInspectorService.instance = this; + } + + final WidgetPreviewScaffoldDtdServices dtdServices; + + // Keys used to specify the creation location of a widget when serializing a + // DiagnosticsNode to JSON. This location is used by the widget inspector + // to jump to the creation location of a selected widget. + static const kFile = 'fileUri'; + static const kLine = 'line'; + static const kColumn = 'column'; + + CodeLocation? _nextNavigationLocation; + + @protected + @override + bool setSelection(Object? object, [String? groupName]) { + // The next navigation event sent to `postEvent` will be for this selection. + // Save the location of preview annotation applications so we can override + // the navigation target in `postEvent`. + if (object is PreviewWidgetElement) { + final previewData = (object.widget as PreviewWidget).preview; + _nextNavigationLocation = CodeLocation( + uri: previewData.scriptUri, + line: previewData.line, + column: previewData.column, + ); + } + final result = super.setSelection(object, groupName); + _nextNavigationLocation = null; + return result; + } + + @override + void postEvent( + String eventKind, + Map eventData, { + String stream = 'Extension', + }) { + // It's unlikely that the widget previewer will be connected to directly by + // an IDE via the VM service, so we forward navigation events via the + // Editor DTD service. + if (eventKind == 'navigate') { + CodeLocation? location = _nextNavigationLocation; + if (eventData case { + kFile: final String file, + kLine: final int line, + kColumn: final int column, + } when location == null) { + location = CodeLocation(uri: file, line: line, column: column); + } else if (location != null) { + // If a [PreviewWidgetElement] was selected, we're not navigating to the + // creation location of the widget. Override the location details in the + // event data, just in case an IDE is attached and listening for + // navigation events through the VM service. + // TODO(bkonyi): determine if this is necessary + eventData.addAll({ + kFile: location.uri, + kLine: location.line!, + kColumn: location.column!, + }); + } + if (location != null) { + dtdServices.navigateToCode(location); + } + } + super.postEvent(eventKind, eventData, stream: stream); + } +} diff --git a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart index 9d3d2e71e758c..65ec94e110eaf 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_code_generator_test.dart @@ -297,18 +297,24 @@ List<_i1.WidgetPreview> previews() => [ _i2.buildWidgetPreview( packageName: 'foo_project', scriptUri: 'STRIPPED', + line: 4, + column: 1, previewFunction: () => _i3.preview(), transformedPreview: const _i4.Preview().transform(), ), _i2.buildWidgetPreview( packageName: 'foo_project', scriptUri: 'STRIPPED', + line: 10, + column: 1, previewFunction: () => _i5.barPreview1(), transformedPreview: const _i4.Preview(group: 'group').transform(), ), _i2.buildWidgetPreview( packageName: 'foo_project', scriptUri: 'STRIPPED', + line: 13, + column: 1, previewFunction: () => _i5.barPreview2(), transformedPreview: const _i4.Preview(brightness: _i6.brightnessConstant).transform(), @@ -316,6 +322,8 @@ List<_i1.WidgetPreview> previews() => [ _i2.buildWidgetPreview( packageName: 'foo_project', scriptUri: 'STRIPPED', + line: 16, + column: 1, previewFunction: () => _i5.barPreview3(), transformedPreview: const _i4.Preview( group: 'group', @@ -334,12 +342,16 @@ List<_i1.WidgetPreview> previews() => [ ..._i2.buildMultiWidgetPreview( packageName: 'foo_project', scriptUri: 'STRIPPED', + line: 51, + column: 1, previewFunction: () => _i11.preview(), preview: const _i11.BrightnessPreview(name: 'Foo'), ), _i2.buildWidgetPreview( packageName: 'foo_project', scriptUri: 'STRIPPED', + line: 52, + column: 1, previewFunction: () => _i11.preview(), transformedPreview: const _i11.FixedSizePreview(name: 'Bar').transform(), @@ -347,6 +359,8 @@ List<_i1.WidgetPreview> previews() => [ _i2.buildWidgetPreviewError( packageName: 'foo_project', scriptUri: 'STRIPPED', + line: 6, + column: 1, packageUri: 'package:foo_project/src/error.dart', functionName: 'preview', dependencyHasErrors: false, @@ -354,6 +368,8 @@ List<_i1.WidgetPreview> previews() => [ _i2.buildWidgetPreviewError( packageName: 'foo_project', scriptUri: 'STRIPPED', + line: 6, + column: 1, packageUri: 'package:foo_project/src/transitive_error.dart', functionName: 'preview', dependencyHasErrors: true, diff --git a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart b/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart index c8a6f4276cb1d..30112f186eacb 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/widget_preview/widget_preview_test.dart @@ -335,6 +335,8 @@ List<_i1.WidgetPreview> previews() => [ _i2.buildWidgetPreview( packageName: 'flutter_project', scriptUri: 'STRIPPED', + line: 4, + column: 1, previewFunction: () => _i3.preview(), transformedPreview: const _i4.Preview(name: 'preview').transform(), ) diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils.dart index f66c203e5b29e..05d0bef7315e0 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils.dart @@ -9,6 +9,8 @@ import 'package:widget_preview_scaffold/src/widget_preview.dart'; Iterable buildMultiWidgetPreview({ required String packageName, required String scriptUri, + required int line, + required int column, required MultiPreview preview, required Object? Function() previewFunction, }) { @@ -16,6 +18,8 @@ Iterable buildMultiWidgetPreview({ (p) => buildWidgetPreview( packageName: packageName, scriptUri: scriptUri, + line: line, + column: column, transformedPreview: p, previewFunction: previewFunction, ), @@ -25,6 +29,8 @@ Iterable buildMultiWidgetPreview({ WidgetPreview buildWidgetPreview({ required String packageName, required String scriptUri, + required int line, + required int column, required Preview transformedPreview, required Object? Function() previewFunction, }) { @@ -39,6 +45,8 @@ WidgetPreview buildWidgetPreview({ return WidgetPreview( builder: previewBuilder, scriptUri: scriptUri, + line: line, + column: column, previewData: transformedPreview, packageName: packageName, ); @@ -47,6 +55,8 @@ WidgetPreview buildWidgetPreview({ WidgetPreview buildWidgetPreviewError({ required String packageName, required String scriptUri, + required int line, + required int column, required String packageUri, required String functionName, required bool dependencyHasErrors, @@ -58,6 +68,8 @@ WidgetPreview buildWidgetPreviewError({ return WidgetPreview( builder: () => Text('$functionName: $errorMessage'), scriptUri: scriptUri, + line: line, + column: column, previewData: const Preview(group: 'Invalid Previews'), packageName: packageName, ); diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview.dart index 2c4af1eddf541..29be79b4769e9 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview.dart @@ -31,15 +31,33 @@ class WidgetPreview { const WidgetPreview({ required this.builder, required this.scriptUri, + required this.line, + required this.column, required this.previewData, required this.packageName, }); + @visibleForTesting + const WidgetPreview.test({ + required this.builder, + required this.previewData, + this.scriptUri = '', + this.line = -1, + this.column = -1, + this.packageName = '', + }); + /// The absolute file:// URI pointing to the script containing this preview. /// /// This matches the URI format sent by IDEs for active location change events. final String scriptUri; + /// The line at which the Preview annotation was applied. + final int line; + + /// The column at which the Preview annotation was applied. + final int column; + /// The name of the package in which a preview was defined. /// /// For example, if a preview is defined in 'package:foo/src/bar.dart', this @@ -104,6 +122,7 @@ class WidgetPreview { void debugFillProperties(DiagnosticPropertiesBuilder properties) { properties ..add(DiagnosticsProperty('name', name, ifNull: 'not set')) + ..add(DiagnosticsProperty('group', previewData.group)) ..add(DiagnosticsProperty('size', size)) ..add(DiagnosticsProperty('textScaleFactor', textScaleFactor)) ..add(DiagnosticsProperty('theme', theme)) diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart index 2549899543a76..47faa6c5c1051 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart @@ -178,12 +178,16 @@ class NoPreviewsDetectedWidget extends StatelessWidget { } } +/// A wrapper that serves as the root entry for a single preview in the widget inspector. class PreviewWidget extends StatelessWidget { const PreviewWidget({super.key, required this.preview, required this.child}); final WidgetPreview preview; final Widget child; + @override + StatelessElement createElement() => PreviewWidgetElement(this); + @override Widget build(BuildContext context) { return child; @@ -191,7 +195,9 @@ class PreviewWidget extends StatelessWidget { @override String toStringShort() { - final StringBuffer buffer = StringBuffer('@Preview'); + final StringBuffer buffer = StringBuffer( + '@${preview.previewData.runtimeType}', + ); if (preview.name != null) { buffer.write('(name: "${preview.name}")'); } @@ -205,6 +211,12 @@ class PreviewWidget extends StatelessWidget { } } +/// A custom [StatelessElement] with the sole purpose of simplifying identifying +/// selections of @Preview annotations in the widget inspector. +class PreviewWidgetElement extends StatelessElement { + PreviewWidgetElement(super.widget); +} + class WidgetPreviewGroupWidget extends StatelessWidget { const WidgetPreviewGroupWidget({ super.key, diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart index 1a53a1c3d7cc6..a5b1106c93c0c 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart @@ -3,7 +3,10 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; import 'package:path/path.dart' as path; +import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; +import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart'; import 'dtd/dtd_services.dart'; import 'widget_preview.dart'; @@ -21,7 +24,11 @@ class WidgetPreviewScaffoldController { required PreviewsCallback previews, @visibleForTesting WidgetPreviewScaffoldDtdServices? dtdServicesOverride, }) : _previews = previews, - dtdServices = dtdServicesOverride ?? WidgetPreviewScaffoldDtdServices(); + dtdServices = dtdServicesOverride ?? WidgetPreviewScaffoldDtdServices() { + // Overrides the default WidgetInspectorService instance to handle selection + // events. + _WidgetPreviewScaffoldInspectorService(dtdServices: dtdServices); + } /// Initializes the controller by establishing a connection to DTD and /// listening for events. @@ -154,3 +161,77 @@ class WidgetPreviewScaffoldController { } } } + +/// A custom [WidgetInspectorService] responsible for routing navigation events +/// to the IDE. +class _WidgetPreviewScaffoldInspectorService with WidgetInspectorService { + _WidgetPreviewScaffoldInspectorService({required this.dtdServices}) { + WidgetInspectorService.instance = this; + } + + final WidgetPreviewScaffoldDtdServices dtdServices; + + // Keys used to specify the creation location of a widget when serializing a + // DiagnosticsNode to JSON. This location is used by the widget inspector + // to jump to the creation location of a selected widget. + static const kFile = 'fileUri'; + static const kLine = 'line'; + static const kColumn = 'column'; + + CodeLocation? _nextNavigationLocation; + + @protected + @override + bool setSelection(Object? object, [String? groupName]) { + // The next navigation event sent to `postEvent` will be for this selection. + // Save the location of preview annotation applications so we can override + // the navigation target in `postEvent`. + if (object is PreviewWidgetElement) { + final previewData = (object.widget as PreviewWidget).preview; + _nextNavigationLocation = CodeLocation( + uri: previewData.scriptUri, + line: previewData.line, + column: previewData.column, + ); + } + final result = super.setSelection(object, groupName); + _nextNavigationLocation = null; + return result; + } + + @override + void postEvent( + String eventKind, + Map eventData, { + String stream = 'Extension', + }) { + // It's unlikely that the widget previewer will be connected to directly by + // an IDE via the VM service, so we forward navigation events via the + // Editor DTD service. + if (eventKind == 'navigate') { + CodeLocation? location = _nextNavigationLocation; + if (eventData case { + kFile: final String file, + kLine: final int line, + kColumn: final int column, + } when location == null) { + location = CodeLocation(uri: file, line: line, column: column); + } else if (location != null) { + // If a [PreviewWidgetElement] was selected, we're not navigating to the + // creation location of the widget. Override the location details in the + // event data, just in case an IDE is attached and listening for + // navigation events through the VM service. + // TODO(bkonyi): determine if this is necessary + eventData.addAll({ + kFile: location.uri, + kLine: location.line!, + kColumn: location.column!, + }); + } + if (location != null) { + dtdServices.navigateToCode(location); + } + } + super.postEvent(eventKind, eventData, stream: stream); + } +} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/custom_previews_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/custom_previews_test.dart index 5402ba7690486..76b0f83befb32 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/custom_previews_test.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/custom_previews_test.dart @@ -71,6 +71,8 @@ WidgetPreviewerWidgetScaffolding previewsForCustomMultiPreview() { scriptUri: '', preview: BrightnessPreview(name: 'MyPreview'), previewFunction: () => Text('Foo'), + line: -1, + column: -1, ); final controller = FakeWidgetPreviewScaffoldController(); return WidgetPreviewerWidgetScaffolding( diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/error_widget_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/error_widget_test.dart index f160845a5d261..73f85d39e6d4a 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/error_widget_test.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/error_widget_test.dart @@ -42,11 +42,9 @@ void main() { final controller = WidgetPreviewScaffoldController( dtdServicesOverride: fakeDtdServices, previews: () => [ - WidgetPreview( + WidgetPreview.test( builder: () => throw Exception('Error!'), - scriptUri: '', previewData: Preview(), - packageName: '', ), ], ); diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/filter_by_selected_file_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/filter_by_selected_file_test.dart index 75ac17ec8f588..369dbbddd98a0 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/filter_by_selected_file_test.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/filter_by_selected_file_test.dart @@ -29,17 +29,15 @@ Future testImpl({ isWindows: isWindows, ); final previews = [ - WidgetPreview( + WidgetPreview.test( builder: () => Text('widget1'), scriptUri: script1Uri.toString(), previewData: Preview(group: 'group'), - packageName: '', ), - WidgetPreview( + WidgetPreview.test( builder: () => Text('widget2'), scriptUri: script2Uri.toString(), previewData: Preview(group: 'group'), - packageName: '', ), ]; final controller = FakeWidgetPreviewScaffoldController( @@ -189,17 +187,13 @@ void main() { ) async { final dtdServices = FakeWidgetPreviewScaffoldDtdServices(); final previews = [ - WidgetPreview( + WidgetPreview.test( builder: () => Text('widget1'), - scriptUri: '', previewData: Preview(group: 'group'), - packageName: '', ), - WidgetPreview( + WidgetPreview.test( builder: () => Text('widget2'), - scriptUri: '', previewData: Preview(group: 'group'), - packageName: '', ), ]; final controller = FakeWidgetPreviewScaffoldController( diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/localizations_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/localizations_test.dart index 0d6f1c5c03609..f0e93d4b27157 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/localizations_test.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/localizations_test.dart @@ -37,11 +37,9 @@ WidgetPreviewerWidgetScaffolding previewForLocalizations({ return WidgetPreviewerWidgetScaffolding( child: WidgetPreviewWidget( controller: controller, - preview: WidgetPreview( - scriptUri: '', + preview: WidgetPreview.test( builder: () => Text('Foo', key: key), previewData: Preview(localizations: previewLocalizationsData), - packageName: '', ), ), ); diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/no_previews_detected_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/no_previews_detected_test.dart index 91ddbe2b9b097..4ebd631880021 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/no_previews_detected_test.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/no_previews_detected_test.dart @@ -50,6 +50,8 @@ void main() { currentPreviews.add( WidgetPreview( scriptUri: '', + line: -1, + column: -1, builder: () => const Text('Foo'), previewData: Preview(), packageName: '', diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/soft_restart_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/soft_restart_test.dart index 8326a7b479a1d..5c6249e1fed84 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/soft_restart_test.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/soft_restart_test.dart @@ -23,11 +23,9 @@ void main() { final widgetPreview = WidgetPreviewerWidgetScaffolding( child: WidgetPreviewWidget( controller: controller, - preview: WidgetPreview( - scriptUri: '', + preview: WidgetPreview.test( builder: () => const Text(kTestText), previewData: Preview(), - packageName: '', ), ), ); diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/theming_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/theming_test.dart index 0c2bb23ead8e0..2735b1395cb0d 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/theming_test.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/theming_test.dart @@ -55,11 +55,9 @@ WidgetPreviewerWidgetScaffolding previewForBrightness({ platformBrightness: platformBrightness ?? Brightness.light, child: WidgetPreviewWidget( controller: controller, - preview: WidgetPreview( - scriptUri: '', + preview: WidgetPreview.test( builder: () => Text('Foo', key: key), previewData: Preview(theme: previewTheme, brightness: brightness), - packageName: '', ), ), ); diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/widget_inspector_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/widget_inspector_test.dart index 59f975cf102fc..fdd17cb97fc3a 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/widget_inspector_test.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/widget_inspector_test.dart @@ -24,11 +24,9 @@ void main() { for (int i = 0; i < kNumPreviewedWidgets; ++i) WidgetPreviewWidget( controller: controller, - preview: WidgetPreview( - scriptUri: '', + preview: WidgetPreview.test( builder: () => Text('$kTestText$i'), previewData: Preview(), - packageName: '', ), ), ], @@ -87,4 +85,57 @@ void main() { await tester.pump(); expect(widgetInspectorFinder, findsNothing); }); + + testWidgets('WidgetInspector navigates to Preview application location', ( + tester, + ) async { + final controller = FakeWidgetPreviewScaffoldController(); + await controller.initialize(); + final dtd = controller.dtdServices as FakeWidgetPreviewScaffoldDtdServices; + + final service = WidgetInspectorService.instance; + service.isSelectMode = true; + + const kLine = 123; + const kColumn = 456; + const kScriptUri = 'file:///script/containing/preview.dart'; + + final widgetPreview = WidgetPreviewerWidgetScaffolding( + child: Column( + children: [ + WidgetPreviewWidget( + controller: controller, + preview: WidgetPreview.test( + builder: () => Text(''), + previewData: Preview(), + line: kLine, + column: kColumn, + scriptUri: kScriptUri, + ), + ), + ], + ), + ); + + await tester.pumpWidget(widgetPreview); + final element = find.byType(PreviewWidget).evaluate().first; + + // Select the WidgetPreviewWidget, which acts as the root entry of the + // preview within the inspector. + expect(service.selection.current, isNull); + // ignore: invalid_use_of_protected_member + service.setSelection(element); + + // Ensure that a navigation event has been sent to the IDE via DTD as a + // result of the inspector selection. + expect(dtd.navigationEvents, hasLength(1)); + + // The navigation event should be to the location of the preview provided + // to the WidgetPreviewWidget, not the actual creation location of the + // WidgetPreviewWidget.annotation location + final codeLocation = dtd.navigationEvents.single; + expect(codeLocation.uri, kScriptUri); + expect(codeLocation.line, kLine); + expect(codeLocation.column, kColumn); + }); } From a2c1b383ce3ad9c7d2dd00a92b91be7dfe80cf36 Mon Sep 17 00:00:00 2001 From: Mohellebi abdessalem Date: Wed, 1 Oct 2025 00:40:06 +0100 Subject: [PATCH 007/204] replace `onPop` usage with `onPopWithResult` in `navigation_bar.2.dart ` (#174841) replace the usage of `onPop ` with `onPopWithResult ` to solve the analyzerr warning ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- examples/api/lib/material/navigation_bar/navigation_bar.2.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/api/lib/material/navigation_bar/navigation_bar.2.dart b/examples/api/lib/material/navigation_bar/navigation_bar.2.dart index f723b309e9beb..29285c71ef2b3 100644 --- a/examples/api/lib/material/navigation_bar/navigation_bar.2.dart +++ b/examples/api/lib/material/navigation_bar/navigation_bar.2.dart @@ -78,7 +78,7 @@ class _HomeState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { return NavigatorPopHandler( - onPop: () { + onPopWithResult: (void result) { final NavigatorState navigator = navigatorKeys[selectedIndex].currentState!; navigator.pop(); }, From f86835076b98cf1581d6364ebbaccb09648d4bbc Mon Sep 17 00:00:00 2001 From: chunhtai <47866232+chunhtai@users.noreply.github.com> Date: Tue, 30 Sep 2025 16:42:02 -0700 Subject: [PATCH 008/204] Adds dart ui API for setting application level locale (#175100) part of https://github.com/flutter/flutter/issues/99600 ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- engine/src/flutter/lib/ui/dart_ui.cc | 1 + .../flutter/lib/ui/platform_dispatcher.dart | 9 ++ .../flutter/lib/ui/semantics/semantics_node.h | 1 + .../lib/ui/window/platform_configuration.cc | 8 ++ .../lib/ui/window/platform_configuration.h | 9 ++ .../lib/web_ui/lib/platform_dispatcher.dart | 2 + .../flutter/runtime/dart_isolate_unittests.cc | 1 + .../src/flutter/runtime/runtime_controller.cc | 5 ++ .../src/flutter/runtime/runtime_controller.h | 3 + .../runtime/runtime_controller_unittests.cc | 23 +++++ engine/src/flutter/runtime/runtime_delegate.h | 2 + engine/src/flutter/shell/common/engine.cc | 4 + engine/src/flutter/shell/common/engine.h | 10 +++ .../shell/common/engine_animator_unittests.cc | 4 + .../flutter/shell/common/engine_unittests.cc | 5 ++ .../src/flutter/shell/common/platform_view.cc | 4 + .../src/flutter/shell/common/platform_view.h | 8 ++ engine/src/flutter/shell/common/shell.cc | 14 +++ engine/src/flutter/shell/common/shell.h | 3 + .../android/android_shell_holder_unittests.cc | 4 + .../flutter/embedding/engine/FlutterJNI.java | 17 ++++ .../io/flutter/view/AccessibilityBridge.java | 36 +++++++- .../shell/platform/android/jni/jni_mock.h | 4 + .../android/jni/platform_view_android_jni.h | 7 ++ .../platform/android/platform_view_android.cc | 5 ++ .../platform/android/platform_view_android.h | 3 + .../android/platform_view_android_jni_impl.cc | 20 +++++ .../android/platform_view_android_jni_impl.h | 2 + .../flutter/view/AccessibilityBridgeTest.java | 85 +++++++++++++++++++ .../Source/FlutterViewController_Internal.h | 1 + .../ios/framework/Source/SemanticsObject.mm | 6 +- .../framework/Source/SemanticsObjectTest.mm | 31 +++++++ .../Source/SemanticsObjectTestMocks.h | 4 + .../framework/Source/accessibility_bridge.h | 1 + .../Source/accessibility_bridge_ios.h | 1 + .../platform/darwin/ios/platform_view_ios.h | 4 + .../platform/darwin/ios/platform_view_ios.mm | 6 ++ .../Source/FlutterViewController_Internal.h | 2 + .../cipd/android_embedding_bundle/README.md | 4 +- .../android_embedding_bundle/build.gradle | 1 + .../lib/src/widgets/localizations.dart | 14 ++- .../test/widgets/localizations_test.dart | 18 ++++ packages/flutter_test/lib/src/window.dart | 8 ++ 43 files changed, 389 insertions(+), 11 deletions(-) diff --git a/engine/src/flutter/lib/ui/dart_ui.cc b/engine/src/flutter/lib/ui/dart_ui.cc index 44c21df990104..9b5921d58cb43 100644 --- a/engine/src/flutter/lib/ui/dart_ui.cc +++ b/engine/src/flutter/lib/ui/dart_ui.cc @@ -101,6 +101,7 @@ typedef CanvasPath Path; V(PlatformConfigurationNativeApi::EndWarmUpFrame) \ V(PlatformConfigurationNativeApi::Render) \ V(PlatformConfigurationNativeApi::UpdateSemantics) \ + V(PlatformConfigurationNativeApi::SetApplicationLocale) \ V(PlatformConfigurationNativeApi::SetNeedsReportTimings) \ V(PlatformConfigurationNativeApi::SetIsolateDebugName) \ V(PlatformConfigurationNativeApi::SetSemanticsTreeEnabled) \ diff --git a/engine/src/flutter/lib/ui/platform_dispatcher.dart b/engine/src/flutter/lib/ui/platform_dispatcher.dart index ff5a56e0b4e92..a799001c0108b 100644 --- a/engine/src/flutter/lib/ui/platform_dispatcher.dart +++ b/engine/src/flutter/lib/ui/platform_dispatcher.dart @@ -976,6 +976,15 @@ class PlatformDispatcher { /// list has not been set or is empty. Locale get locale => locales.isEmpty ? const Locale.fromSubtags() : locales.first; + /// Sets the locale for the application in engine. + /// + /// This is typically called by framework to set the locale based on which + /// locale the Flutter app actually uses. + void setApplicationLocale(Locale locale) => _setApplicationLocale(locale.toLanguageTag()); + + @Native(symbol: 'PlatformConfigurationNativeApi::SetApplicationLocale') + external static void _setApplicationLocale(String locale); + /// The full system-reported supported locales of the device. /// /// This establishes the language and formatting conventions that application diff --git a/engine/src/flutter/lib/ui/semantics/semantics_node.h b/engine/src/flutter/lib/ui/semantics/semantics_node.h index d419b3e037f5e..7ca763f307813 100644 --- a/engine/src/flutter/lib/ui/semantics/semantics_node.h +++ b/engine/src/flutter/lib/ui/semantics/semantics_node.h @@ -168,6 +168,7 @@ struct SemanticsNode { std::string linkUrl; SemanticsRole role; SemanticsValidationResult validationResult = SemanticsValidationResult::kNone; + // A locale string in BCP 47 format std::string locale; }; diff --git a/engine/src/flutter/lib/ui/window/platform_configuration.cc b/engine/src/flutter/lib/ui/window/platform_configuration.cc index 2edeb2cf59850..9c63ebc1501e5 100644 --- a/engine/src/flutter/lib/ui/window/platform_configuration.cc +++ b/engine/src/flutter/lib/ui/window/platform_configuration.cc @@ -677,6 +677,14 @@ void PlatformConfigurationNativeApi::UpdateSemantics(int64_t view_id, view_id, update); } +void PlatformConfigurationNativeApi::SetApplicationLocale(std::string locale) { + UIDartState::ThrowIfUIOperationsProhibited(); + UIDartState::Current() + ->platform_configuration() + ->client() + ->SetApplicationLocale(std::move(locale)); +} + void PlatformConfigurationNativeApi::SetSemanticsTreeEnabled(bool enabled) { UIDartState::ThrowIfUIOperationsProhibited(); UIDartState::Current() diff --git a/engine/src/flutter/lib/ui/window/platform_configuration.h b/engine/src/flutter/lib/ui/window/platform_configuration.h index 0f30732923464..c1a489d059f34 100644 --- a/engine/src/flutter/lib/ui/window/platform_configuration.h +++ b/engine/src/flutter/lib/ui/window/platform_configuration.h @@ -98,6 +98,13 @@ class PlatformConfigurationClient { /// virtual void UpdateSemantics(int64_t viewId, SemanticsUpdate* update) = 0; + //-------------------------------------------------------------------------- + /// @brief Framework sets the application locale + /// + /// @param[in] locale The application locale in BCP 47 format. + /// + virtual void SetApplicationLocale(std::string locale) = 0; + //-------------------------------------------------------------------------- /// @brief Notifies whether Framework starts generating semantics tree. /// @@ -633,6 +640,8 @@ class PlatformConfigurationNativeApi { static void UpdateSemantics(int64_t viewId, SemanticsUpdate* update); + static void SetApplicationLocale(std::string locale); + static void SetSemanticsTreeEnabled(bool enabled); static void SetNeedsReportTimings(bool value); diff --git a/engine/src/flutter/lib/web_ui/lib/platform_dispatcher.dart b/engine/src/flutter/lib/web_ui/lib/platform_dispatcher.dart index 36b74594828ef..4ef10f8afc64c 100644 --- a/engine/src/flutter/lib/web_ui/lib/platform_dispatcher.dart +++ b/engine/src/flutter/lib/web_ui/lib/platform_dispatcher.dart @@ -87,6 +87,8 @@ abstract class PlatformDispatcher { void setSemanticsTreeEnabled(bool enabled) {} + void setApplicationLocale(Locale locale) {} + AccessibilityFeatures get accessibilityFeatures; VoidCallback? get onAccessibilityFeaturesChanged; diff --git a/engine/src/flutter/runtime/dart_isolate_unittests.cc b/engine/src/flutter/runtime/dart_isolate_unittests.cc index 3be980588e173..e39dd4a2699f4 100644 --- a/engine/src/flutter/runtime/dart_isolate_unittests.cc +++ b/engine/src/flutter/runtime/dart_isolate_unittests.cc @@ -712,6 +712,7 @@ class FakePlatformConfigurationClient : public PlatformConfigurationClient { double width, double height) override {} void UpdateSemantics(int64_t view_id, SemanticsUpdate* update) override {} + void SetApplicationLocale(std::string locale) override {} void SetSemanticsTreeEnabled(bool enabled) override {} void HandlePlatformMessage( std::unique_ptr message) override {} diff --git a/engine/src/flutter/runtime/runtime_controller.cc b/engine/src/flutter/runtime/runtime_controller.cc index 95708ef3585b8..53f1684eea6ae 100644 --- a/engine/src/flutter/runtime/runtime_controller.cc +++ b/engine/src/flutter/runtime/runtime_controller.cc @@ -444,6 +444,11 @@ void RuntimeController::UpdateSemantics(int64_t view_id, client_.UpdateSemantics(view_id, update->takeNodes(), update->takeActions()); } +// |PlatformConfigurationClient| +void RuntimeController::SetApplicationLocale(std::string locale) { + client_.SetApplicationLocale(std::move(locale)); +} + // |PlatformConfigurationClient| void RuntimeController::SetSemanticsTreeEnabled(bool enabled) { client_.SetSemanticsTreeEnabled(enabled); diff --git a/engine/src/flutter/runtime/runtime_controller.h b/engine/src/flutter/runtime/runtime_controller.h index 1c7df47280f9d..38a279bf3f511 100644 --- a/engine/src/flutter/runtime/runtime_controller.h +++ b/engine/src/flutter/runtime/runtime_controller.h @@ -636,6 +636,9 @@ class RuntimeController : public PlatformConfigurationClient, // |PlatformConfigurationClient| void UpdateSemantics(int64_t view_id, SemanticsUpdate* update) override; + // |PlatformConfigurationClient| + void SetApplicationLocale(std::string locale) override; + // |PlatformConfigurationClient| void SetSemanticsTreeEnabled(bool enabled) override; diff --git a/engine/src/flutter/runtime/runtime_controller_unittests.cc b/engine/src/flutter/runtime/runtime_controller_unittests.cc index a8e27bae1e39f..08d0b3ecc7e9c 100644 --- a/engine/src/flutter/runtime/runtime_controller_unittests.cc +++ b/engine/src/flutter/runtime/runtime_controller_unittests.cc @@ -19,6 +19,7 @@ class MockRuntimeDelegate : public RuntimeDelegate { std::vector updates; std::vector actions; std::string DefaultRouteName() override { return ""; } + std::string locale; void ScheduleFrame(bool regenerate_layer_trees = true) override {} @@ -35,6 +36,10 @@ class MockRuntimeDelegate : public RuntimeDelegate { this->actions.push_back(actions); } + void SetApplicationLocale(std::string locale) override { + this->locale = std::move(locale); + } + void SetSemanticsTreeEnabled(bool enabled) override {} void HandlePlatformMessage( @@ -96,6 +101,12 @@ class RuntimeControllerTester { ASSERT_FALSE(delegate_.actions.empty()); } + void CanUpdateSetApplicationLocale() { + ASSERT_TRUE(delegate_.locale.empty()); + runtime_controller_.SetApplicationLocale("es-MX"); + ASSERT_TRUE(delegate_.locale == "es-MX"); + } + private: MockRuntimeDelegate delegate_; UIDartState::Context& context_; @@ -146,4 +157,16 @@ TEST_F(RuntimeControllerTest, CanUpdateSemanticsWhenSetSemanticsTreeEnabled) { DestroyShell(std::move(shell), task_runners); } +TEST_F(RuntimeControllerTest, CanSetApplicationLocale) { + TaskRunners task_runners("test", // label + GetCurrentTaskRunner(), // platform + CreateNewThread(), // raster + CreateNewThread(), // ui + CreateNewThread() // io + ); + UIDartState::Context context(task_runners); + auto tester = std::make_shared(context); + tester->CanUpdateSetApplicationLocale(); +} + } // namespace flutter::testing diff --git a/engine/src/flutter/runtime/runtime_delegate.h b/engine/src/flutter/runtime/runtime_delegate.h index 84ca390db09f1..3563cd5f887a8 100644 --- a/engine/src/flutter/runtime/runtime_delegate.h +++ b/engine/src/flutter/runtime/runtime_delegate.h @@ -36,6 +36,8 @@ class RuntimeDelegate { SemanticsNodeUpdates update, CustomAccessibilityActionUpdates actions) = 0; + virtual void SetApplicationLocale(std::string locale) = 0; + virtual void SetSemanticsTreeEnabled(bool enabled) = 0; virtual void HandlePlatformMessage( diff --git a/engine/src/flutter/shell/common/engine.cc b/engine/src/flutter/shell/common/engine.cc index 1848b9db53ff1..bce7fa5007d56 100644 --- a/engine/src/flutter/shell/common/engine.cc +++ b/engine/src/flutter/shell/common/engine.cc @@ -529,6 +529,10 @@ void Engine::UpdateSemantics(int64_t view_id, std::move(actions)); } +void Engine::SetApplicationLocale(std::string locale) { + delegate_.OnEngineSetApplicationLocale(std::move(locale)); +} + void Engine::SetSemanticsTreeEnabled(bool enabled) { delegate_.OnEngineSetSemanticsTreeEnabled(enabled); } diff --git a/engine/src/flutter/shell/common/engine.h b/engine/src/flutter/shell/common/engine.h index bee80938fdf3e..e07f7d4f5697c 100644 --- a/engine/src/flutter/shell/common/engine.h +++ b/engine/src/flutter/shell/common/engine.h @@ -161,6 +161,13 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { SemanticsNodeUpdates updates, CustomAccessibilityActionUpdates actions) = 0; + //-------------------------------------------------------------------------- + /// @brief Framework sets the application locale. + /// + /// @param[in] locale The application locale in BCP 47 format. + /// + virtual void OnEngineSetApplicationLocale(std::string locale) = 0; + //-------------------------------------------------------------------------- /// @brief When the Framework starts or stops generating semantics /// tree, @@ -1029,6 +1036,9 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate { SemanticsNodeUpdates update, CustomAccessibilityActionUpdates actions) override; + // |RuntimeDelegate| + void SetApplicationLocale(std::string locale) override; + // |RuntimeDelegate| void SetSemanticsTreeEnabled(bool enabled) override; diff --git a/engine/src/flutter/shell/common/engine_animator_unittests.cc b/engine/src/flutter/shell/common/engine_animator_unittests.cc index f1010131615e6..f7653b895416f 100644 --- a/engine/src/flutter/shell/common/engine_animator_unittests.cc +++ b/engine/src/flutter/shell/common/engine_animator_unittests.cc @@ -57,6 +57,10 @@ class MockDelegate : public Engine::Delegate { OnEngineUpdateSemantics, (int64_t, SemanticsNodeUpdates, CustomAccessibilityActionUpdates), (override)); + MOCK_METHOD(void, + OnEngineSetApplicationLocale, + (const std::string), + (override)); MOCK_METHOD(void, OnEngineSetSemanticsTreeEnabled, (bool), (override)); MOCK_METHOD(void, OnEngineHandlePlatformMessage, diff --git a/engine/src/flutter/shell/common/engine_unittests.cc b/engine/src/flutter/shell/common/engine_unittests.cc index 324104d918531..3c4673f801e16 100644 --- a/engine/src/flutter/shell/common/engine_unittests.cc +++ b/engine/src/flutter/shell/common/engine_unittests.cc @@ -64,6 +64,10 @@ class MockDelegate : public Engine::Delegate { OnEngineUpdateSemantics, (int64_t, SemanticsNodeUpdates, CustomAccessibilityActionUpdates), (override)); + MOCK_METHOD(void, + OnEngineSetApplicationLocale, + (const std::string), + (override)); MOCK_METHOD(void, OnEngineSetSemanticsTreeEnabled, (bool), (override)); MOCK_METHOD(void, OnEngineHandlePlatformMessage, @@ -116,6 +120,7 @@ class MockRuntimeDelegate : public RuntimeDelegate { UpdateSemantics, (int64_t, SemanticsNodeUpdates, CustomAccessibilityActionUpdates), (override)); + MOCK_METHOD(void, SetApplicationLocale, (const std::string), (override)); MOCK_METHOD(void, SetSemanticsTreeEnabled, (bool), (override)); MOCK_METHOD(void, HandlePlatformMessage, diff --git a/engine/src/flutter/shell/common/platform_view.cc b/engine/src/flutter/shell/common/platform_view.cc index 496c75f85d288..462acd516a8b5 100644 --- a/engine/src/flutter/shell/common/platform_view.cc +++ b/engine/src/flutter/shell/common/platform_view.cc @@ -130,6 +130,10 @@ void PlatformView::UpdateSemantics( // NOLINTNEXTLINE(performance-unnecessary-value-param) CustomAccessibilityActionUpdates actions) {} +void PlatformView::SetApplicationLocale( + std::string locale // NOLINT(performance-unnecessary-value-param) +) {} + void PlatformView::SetSemanticsTreeEnabled( bool enabled // NOLINT(performance-unnecessary-value-param) ) {} diff --git a/engine/src/flutter/shell/common/platform_view.h b/engine/src/flutter/shell/common/platform_view.h index 9332c76d3ca20..60227f386b496 100644 --- a/engine/src/flutter/shell/common/platform_view.h +++ b/engine/src/flutter/shell/common/platform_view.h @@ -514,6 +514,14 @@ class PlatformView { SemanticsNodeUpdates updates, CustomAccessibilityActionUpdates actions); + //---------------------------------------------------------------------------- + /// @brief Used by the framework to set application locale in the + /// embedding + /// + /// @param[in] locale The application locale in BCP 47 format. + /// + virtual void SetApplicationLocale(std::string locale); + //---------------------------------------------------------------------------- /// @brief Used by the framework to tell the embedder to prepare or clear /// resoruce for accepting semantics tree. diff --git a/engine/src/flutter/shell/common/shell.cc b/engine/src/flutter/shell/common/shell.cc index c8e9154a7a55b..73d05330599da 100644 --- a/engine/src/flutter/shell/common/shell.cc +++ b/engine/src/flutter/shell/common/shell.cc @@ -1448,6 +1448,20 @@ void Shell::OnEngineUpdateSemantics(int64_t view_id, }); } +// |Engine::Delegate| +void Shell::OnEngineSetApplicationLocale(std::string locale) { + FML_DCHECK(is_set_up_); + FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()); + + task_runners_.GetPlatformTaskRunner()->RunNowOrPostTask( + task_runners_.GetPlatformTaskRunner(), + [view = platform_view_->GetWeakPtr(), locale_holder = std::move(locale)] { + if (view) { + view->SetApplicationLocale(locale_holder); + } + }); +} + // |Engine::Delegate| void Shell::OnEngineSetSemanticsTreeEnabled(bool enabled) { FML_DCHECK(is_set_up_); diff --git a/engine/src/flutter/shell/common/shell.h b/engine/src/flutter/shell/common/shell.h index a98c3a2aaedf2..7b86a6523a2d4 100644 --- a/engine/src/flutter/shell/common/shell.h +++ b/engine/src/flutter/shell/common/shell.h @@ -675,6 +675,9 @@ class Shell final : public PlatformView::Delegate, SemanticsNodeUpdates update, CustomAccessibilityActionUpdates actions) override; + // |Engine::Delegate| + void OnEngineSetApplicationLocale(std::string locale) override; + // |Engine::Delegate| void OnEngineSetSemanticsTreeEnabled(bool enabled) override; diff --git a/engine/src/flutter/shell/platform/android/android_shell_holder_unittests.cc b/engine/src/flutter/shell/platform/android/android_shell_holder_unittests.cc index 2b4a5e67e0e7a..9664cc06cc581 100644 --- a/engine/src/flutter/shell/platform/android/android_shell_holder_unittests.cc +++ b/engine/src/flutter/shell/platform/android/android_shell_holder_unittests.cc @@ -29,6 +29,10 @@ class MockPlatformViewAndroidJNI : public PlatformViewAndroidJNI { std::vector strings, std::vector> string_attribute_args), (override)); + MOCK_METHOD(void, + FlutterViewSetApplicationLocale, + (const std::string locale), + (override)); MOCK_METHOD(void, FlutterViewUpdateCustomAccessibilityActions, (std::vector actions_buffer, diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 77d7269035fad..2f97368b6e327 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -803,6 +803,16 @@ private void updateSemantics( } } + /** Invoked by native to set application locale in Android. */ + @SuppressWarnings("unused") + @UiThread + private void setApplicationLocale(@NonNull String locale) { + ensureRunningOnMainThread(); + if (accessibilityDelegate != null) { + accessibilityDelegate.setLocale(locale); + } + } + /** * Invoked by native to send new custom accessibility events from Flutter to Android. * @@ -1631,6 +1641,13 @@ void updateSemantics( @NonNull ByteBuffer buffer, @NonNull String[] strings, @NonNull ByteBuffer[] stringAttributeArgs); + + /** + * Sets the locale for the assistive technologies. + * + *

Must be called on the main thread + */ + void setLocale(@NonNull String locale); } public interface AsyncWaitForVsyncDelegate { diff --git a/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java b/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java index 8c95a68d00b4e..def3e430b038f 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/view/AccessibilityBridge.java @@ -227,6 +227,16 @@ public class AccessibilityBridge extends AccessibilityNodeProvider { // a bitmask whose values comes from {@link AccessibilityFeature}. private int accessibilityFeatureFlags = 0; + // The default locale for assistive technologies in BCP 47 format. + // + // For example "en-US", "de-DE", "fr-FR". + @Nullable private String defaultLocale; + + @VisibleForTesting + public void setLocale(@NonNull String locale) { + defaultLocale = locale; + } + // The {@code SemanticsNode} within Flutter that currently has the focus of Android's input // system. // @@ -372,6 +382,11 @@ public void updateSemantics( } AccessibilityBridge.this.updateSemantics(buffer, strings, stringAttributeArgs); } + + @Override + public void setLocale(String locale) { + AccessibilityBridge.this.setLocale(locale); + } }; // Listener that is notified when accessibility is turned on/off. @@ -2795,6 +2810,21 @@ private String getRouteName() { return null; } + /** + * Returns the effective locale for this semantics node after taking app default locale into + * account. + * + *

Can be null if there is no preference. + * + * @return the effective locale. + */ + private @Nullable String getEffectiveLocale() { + if (locale != null && !locale.isEmpty()) { + return locale; + } + return accessibilityBridge.defaultLocale; + } + private void updateRecursively( float[] ancestorTransform, Set visitedObjects, boolean forceUpdate) { visitedObjects.add(this); @@ -2890,7 +2920,7 @@ private CharSequence getValue() { return new AccessibilityStringBuilder() .addString(value) .addAttributes(valueAttributes) - .addLocale(locale) + .addLocale(getEffectiveLocale()) .build(); } @@ -2899,7 +2929,7 @@ private CharSequence getLabel() { .addString(label) .addAttributes(labelAttributes) .addUrl(linkUrl) - .addLocale(locale) + .addLocale(getEffectiveLocale()) .build(); } @@ -2907,7 +2937,7 @@ private CharSequence getHint() { return new AccessibilityStringBuilder() .addString(hint) .addAttributes(hintAttributes) - .addLocale(locale) + .addLocale(getEffectiveLocale()) .build(); } diff --git a/engine/src/flutter/shell/platform/android/jni/jni_mock.h b/engine/src/flutter/shell/platform/android/jni/jni_mock.h index 8f9158c97de6a..17939a68c8975 100644 --- a/engine/src/flutter/shell/platform/android/jni/jni_mock.h +++ b/engine/src/flutter/shell/platform/android/jni/jni_mock.h @@ -33,6 +33,10 @@ class JNIMock final : public PlatformViewAndroidJNI { std::vector strings, std::vector> string_attribute_args), (override)); + MOCK_METHOD(void, + FlutterViewSetApplicationLocale, + (std::string locale), + (override)); MOCK_METHOD(void, FlutterViewUpdateCustomAccessibilityActions, diff --git a/engine/src/flutter/shell/platform/android/jni/platform_view_android_jni.h b/engine/src/flutter/shell/platform/android/jni/platform_view_android_jni.h index e952c6127a92d..0598507d6ccb3 100644 --- a/engine/src/flutter/shell/platform/android/jni/platform_view_android_jni.h +++ b/engine/src/flutter/shell/platform/android/jni/platform_view_android_jni.h @@ -63,6 +63,13 @@ class PlatformViewAndroidJNI { std::vector strings, std::vector> string_attribute_args) = 0; + //---------------------------------------------------------------------------- + /// @brief Set application locale to a given language. + /// + /// @note Must be called from the platform thread. + /// + virtual void FlutterViewSetApplicationLocale(std::string locale) = 0; + //---------------------------------------------------------------------------- /// @brief Sends new custom accessibility events. /// diff --git a/engine/src/flutter/shell/platform/android/platform_view_android.cc b/engine/src/flutter/shell/platform/android/platform_view_android.cc index 8ac83e0227cdb..fc19fbbfc879c 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android.cc @@ -325,6 +325,11 @@ void PlatformViewAndroid::UpdateSemantics( platform_view_android_delegate_.UpdateSemantics(update, actions); } +// |PlatformView| +void PlatformViewAndroid::SetApplicationLocale(std::string locale) { + jni_facade_->FlutterViewSetApplicationLocale(std::move(locale)); +} + void PlatformViewAndroid::RegisterExternalTexture( int64_t texture_id, const fml::jni::ScopedJavaGlobalRef& surface_texture) { diff --git a/engine/src/flutter/shell/platform/android/platform_view_android.h b/engine/src/flutter/shell/platform/android/platform_view_android.h index 0c21ad651b39e..644ee25964c94 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android.h +++ b/engine/src/flutter/shell/platform/android/platform_view_android.h @@ -144,6 +144,9 @@ class PlatformViewAndroid final : public PlatformView { flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) override; + // |PlatformView| + void SetApplicationLocale(std::string locale) override; + // |PlatformView| void HandlePlatformMessage( std::unique_ptr message) override; diff --git a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc index 80dcf7458f36e..cb88853bbe2ee 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc +++ b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.cc @@ -69,6 +69,8 @@ static jfieldID g_jni_shell_holder_field = nullptr; "(ILjava/nio/ByteBuffer;)V") \ V(g_update_semantics_method, updateSemantics, \ "(Ljava/nio/ByteBuffer;[Ljava/lang/String;[Ljava/nio/ByteBuffer;)V") \ + V(g_set_application_locale_method, setApplicationLocale, \ + "(Ljava/lang/String;)V") \ V(g_on_display_platform_view_method, onDisplayPlatformView, \ "(IIIIIIILio/flutter/embedding/engine/mutatorsstack/" \ "FlutterMutatorsStack;)V") \ @@ -1358,6 +1360,24 @@ void PlatformViewAndroidJNIImpl::FlutterViewHandlePlatformMessage( FML_CHECK(fml::jni::CheckException(env)); } +void PlatformViewAndroidJNIImpl::FlutterViewSetApplicationLocale( + std::string locale) { + JNIEnv* env = fml::jni::AttachCurrentThread(); + + auto java_object = java_object_.get(env); + if (java_object.is_null()) { + return; + } + + fml::jni::ScopedJavaLocalRef jlocale = + fml::jni::StringToJavaString(env, locale); + + env->CallVoidMethod(java_object.obj(), g_set_application_locale_method, + jlocale.obj()); + + FML_CHECK(fml::jni::CheckException(env)); +} + void PlatformViewAndroidJNIImpl::FlutterViewHandlePlatformMessageResponse( int responseId, std::unique_ptr data) { diff --git a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.h b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.h index 06be3d01c1f82..bae55e608fba1 100644 --- a/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.h +++ b/engine/src/flutter/shell/platform/android/platform_view_android_jni_impl.h @@ -34,6 +34,8 @@ class PlatformViewAndroidJNIImpl final : public PlatformViewAndroidJNI { std::vector strings, std::vector> string_attribute_args) override; + void FlutterViewSetApplicationLocale(std::string locale) override; + void FlutterViewUpdateCustomAccessibilityActions( std::vector actions_buffer, std::vector strings) override; diff --git a/engine/src/flutter/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java b/engine/src/flutter/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java index 93b5a7997f349..5adc1d11d60f4 100644 --- a/engine/src/flutter/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java +++ b/engine/src/flutter/shell/platform/android/test/io/flutter/view/AccessibilityBridgeTest.java @@ -885,6 +885,91 @@ public void itBuildsAttributedStringWithLocale() { assertEquals(actual.getSpanEnd(localeSpan), actual.length()); } + @Test + @Config(minSdk = API_LEVELS.FLUTTER_MIN) + public void itSetsDefaultLocale() { + AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); + AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class); + AccessibilityManager mockManager = mock(AccessibilityManager.class); + View mockRootView = mock(View.class); + Context context = mock(Context.class); + when(mockRootView.getContext()).thenReturn(context); + when(context.getPackageName()).thenReturn("test"); + AccessibilityBridge accessibilityBridge = + setUpBridge( + /* rootAccessibilityView= */ mockRootView, + /* accessibilityChannel= */ mockChannel, + /* accessibilityManager= */ mockManager, + /* contentResolver= */ null, + /* accessibilityViewEmbedder= */ mockViewEmbedder, + /* platformViewsAccessibilityDelegate= */ null); + + ViewParent mockParent = mock(ViewParent.class); + when(mockRootView.getParent()).thenReturn(mockParent); + when(mockManager.isEnabled()).thenReturn(true); + + TestSemanticsNode root = new TestSemanticsNode(); + root.id = 0; + root.label = "label"; + + accessibilityBridge.setLocale("es-MX"); + TestSemanticsUpdate testSemanticsUpdate = root.toUpdate(); + testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge); + AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(0); + SpannableString actual = (SpannableString) nodeInfo.getContentDescription(); + assertEquals(actual.toString(), "label"); + Object[] objectSpans = actual.getSpans(0, actual.length(), Object.class); + assertEquals(objectSpans.length, 1); + LocaleSpan localeSpan = (LocaleSpan) objectSpans[0]; + assertEquals(localeSpan.getLocale().toLanguageTag(), "es-MX"); + assertEquals(actual.getSpanStart(localeSpan), 0); + assertEquals(actual.getSpanEnd(localeSpan), actual.length()); + } + + @Test + @Config(minSdk = API_LEVELS.FLUTTER_MIN) + public void itPrioritizesSectionLocale() { + AccessibilityChannel mockChannel = mock(AccessibilityChannel.class); + AccessibilityViewEmbedder mockViewEmbedder = mock(AccessibilityViewEmbedder.class); + AccessibilityManager mockManager = mock(AccessibilityManager.class); + View mockRootView = mock(View.class); + Context context = mock(Context.class); + when(mockRootView.getContext()).thenReturn(context); + when(context.getPackageName()).thenReturn("test"); + AccessibilityBridge accessibilityBridge = + setUpBridge( + /* rootAccessibilityView= */ mockRootView, + /* accessibilityChannel= */ mockChannel, + /* accessibilityManager= */ mockManager, + /* contentResolver= */ null, + /* accessibilityViewEmbedder= */ mockViewEmbedder, + /* platformViewsAccessibilityDelegate= */ null); + + ViewParent mockParent = mock(ViewParent.class); + when(mockRootView.getParent()).thenReturn(mockParent); + when(mockManager.isEnabled()).thenReturn(true); + + TestSemanticsNode root = new TestSemanticsNode(); + root.id = 0; + root.label = "label"; + // Sets both section locale and main locale. + root.locale = "fr-FR"; + accessibilityBridge.setLocale("es-MX"); + + TestSemanticsUpdate testSemanticsUpdate = root.toUpdate(); + testSemanticsUpdate.sendUpdateToBridge(accessibilityBridge); + AccessibilityNodeInfo nodeInfo = accessibilityBridge.createAccessibilityNodeInfo(0); + SpannableString actual = (SpannableString) nodeInfo.getContentDescription(); + assertEquals(actual.toString(), "label"); + Object[] objectSpans = actual.getSpans(0, actual.length(), Object.class); + assertEquals(objectSpans.length, 1); + LocaleSpan localeSpan = (LocaleSpan) objectSpans[0]; + // Prioritizes section locale over main locale. + assertEquals(localeSpan.getLocale().toLanguageTag(), "fr-FR"); + assertEquals(actual.getSpanStart(localeSpan), 0); + assertEquals(actual.getSpanEnd(localeSpan), actual.length()); + } + @Test @Config(minSdk = API_LEVELS.FLUTTER_MIN) public void itSetsTextCorrectly() { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h index f22aacb251529..8c61b980e0789 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h @@ -48,6 +48,7 @@ typedef void (^FlutterKeyboardAnimationCallback)(fml::TimePoint); @property(nonatomic, readonly) BOOL isPresentingViewController; @property(nonatomic, readonly) BOOL isVoiceOverRunning; @property(nonatomic, strong) FlutterKeyboardManager* keyboardManager; +@property(nonatomic, readwrite) NSString* applicationLocale; /** * @brief Whether the status bar is preferred hidden. diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm index b4b344b87fa04..73b55e0ed227e 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObject.mm @@ -459,10 +459,10 @@ - (NSString*)accessibilityLanguage { return nil; } - if (self.node.locale.empty()) { - return nil; + if (!self.node.locale.empty()) { + return @(self.node.locale.data()); } - return @(self.node.locale.data()); + return self.bridge->GetDefaultLocale(); } - (bool)isFocusable { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm index 225bc799cd657..d6eed6f265022 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTest.mm @@ -585,6 +585,37 @@ - (void)testFlutterSemanticsObjectHasLocale { XCTAssertTrue([object.accessibilityLanguage isEqualToString:@"es-MX"]); } +- (void)testFlutterSemanticsObjectUseDefaultLocale { + flutter::testing::MockAccessibilityBridge* mock = new flutter::testing::MockAccessibilityBridge(); + mock->isVoiceOverRunningValue = true; + fml::WeakPtrFactory factory(mock); + fml::WeakPtr bridge = factory.GetWeakPtr(); + + flutter::SemanticsNode node; + mock->mockedLocale = @"es-MX"; + + FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0]; + [object setSemanticsNode:&node]; + XCTAssertTrue([object.accessibilityLanguage isEqualToString:@"es-MX"]); +} + +- (void)testFlutterSemanticsObjectPrioritizedSectionLocale { + flutter::testing::MockAccessibilityBridge* mock = new flutter::testing::MockAccessibilityBridge(); + mock->isVoiceOverRunningValue = true; + fml::WeakPtrFactory factory(mock); + fml::WeakPtr bridge = factory.GetWeakPtr(); + + flutter::SemanticsNode node; + // Set both locales. + mock->mockedLocale = @"es-MX"; + node.locale = "zh-TW"; + + FlutterSemanticsObject* object = [[FlutterSemanticsObject alloc] initWithBridge:bridge uid:0]; + [object setSemanticsNode:&node]; + // node.locale takes priority. + XCTAssertTrue([object.accessibilityLanguage isEqualToString:@"zh-TW"]); +} + - (void)testFlutterSemanticsObjectLocaleNil { flutter::testing::MockAccessibilityBridge* mock = new flutter::testing::MockAccessibilityBridge(); mock->isVoiceOverRunningValue = true; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h index a740349ab1376..c64c4cd968c57 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/SemanticsObjectTestMocks.h @@ -39,6 +39,8 @@ class MockAccessibilityBridge : public AccessibilityBridgeIos { bool isVoiceOverRunning() const override { return isVoiceOverRunningValue; } UIView* view() const override { return view_; } UIView* textInputView() override { return nil; } + + NSString* GetDefaultLocale() override { return mockedLocale; } void DispatchSemanticsAction(int32_t id, SemanticsAction action) override { SemanticsActionObservation observation(id, action); observations.push_back(observation); @@ -54,6 +56,7 @@ class MockAccessibilityBridge : public AccessibilityBridgeIos { FlutterPlatformViewsController* GetPlatformViewsController() const override { return nil; } std::vector observations; bool isVoiceOverRunningValue; + NSString* mockedLocale; private: UIView* view_; @@ -80,6 +83,7 @@ class MockAccessibilityBridgeNoWindow : public AccessibilityBridgeIos { } void AccessibilityObjectDidBecomeFocused(int32_t id) override {} void AccessibilityObjectDidLoseFocus(int32_t id) override {} + NSString* GetDefaultLocale() override { return nil; } FlutterPlatformViewsController* GetPlatformViewsController() const override { return nil; } std::vector observations; bool isVoiceOverRunningValue; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h index debad9b257e7a..1b65d66a998e3 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h @@ -62,6 +62,7 @@ class AccessibilityBridge final : public AccessibilityBridgeIos { fml::MallocMapping args) override; void AccessibilityObjectDidBecomeFocused(int32_t id) override; void AccessibilityObjectDidLoseFocus(int32_t id) override; + NSString* GetDefaultLocale() override { return view_controller_.applicationLocale; } UIView* textInputView() override; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h index 2168ca050d072..752e35a409281 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge_ios.h @@ -39,6 +39,7 @@ class AccessibilityBridgeIos { * The input id is the uid of the newly focused SemanticObject. */ virtual void AccessibilityObjectDidLoseFocus(int32_t id) = 0; + virtual NSString* GetDefaultLocale() = 0; virtual FlutterPlatformViewsController* GetPlatformViewsController() const = 0; }; diff --git a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.h b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.h index 7f03b7b8abb01..6594bc94f2930 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.h +++ b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.h @@ -13,6 +13,7 @@ #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterTexture.h" #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/accessibility_bridge.h" #import "flutter/shell/platform/darwin/ios/ios_context.h" #import "flutter/shell/platform/darwin/ios/ios_external_view_embedder.h" @@ -110,6 +111,9 @@ class PlatformViewIOS final : public PlatformView { flutter::SemanticsNodeUpdates update, flutter::CustomAccessibilityActionUpdates actions) override; + // |PlatformView| + void SetApplicationLocale(std::string locale) override; + // |PlatformView| std::unique_ptr CreateVSyncWaiter() override; diff --git a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.mm b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.mm index b19103eaa28a1..94b6901cdc9cb 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/platform_view_ios.mm @@ -159,6 +159,12 @@ new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {} } } +// |PlatformView| +void PlatformViewIOS::SetApplicationLocale(std::string locale) { + FML_DCHECK(owner_controller_); + owner_controller_.applicationLocale = locale.empty() ? nil : @(locale.data()); +} + // |PlatformView| void PlatformViewIOS::SetSemanticsTreeEnabled(bool enabled) { FML_DCHECK(owner_controller_); diff --git a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h index d2d8434ddf355..47d0d8bd6aa3c 100644 --- a/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h @@ -20,6 +20,8 @@ @property(nonatomic, readonly) std::weak_ptr accessibilityBridge; +@property(nonatomic, nullable) NSString* applicationLocale; + /** * Returns YES if provided event is being currently redispatched by keyboard manager. */ diff --git a/engine/src/flutter/tools/cipd/android_embedding_bundle/README.md b/engine/src/flutter/tools/cipd/android_embedding_bundle/README.md index c249d590d4a90..f1d99fe2aed0a 100644 --- a/engine/src/flutter/tools/cipd/android_embedding_bundle/README.md +++ b/engine/src/flutter/tools/cipd/android_embedding_bundle/README.md @@ -5,7 +5,7 @@ contains the build-time dependencies of the Android embedding of the Engine, and the dependencies of the in-tree testing framework. The Android embedder is shipped to Flutter end-users, but these build-time dependencies are not. Therefore, the license script can skip over the destination of the CIPD package -in an Engine checkout at `src/third_party/android_embedding_dependencies`. +in an Engine checkout at `src/flutter/third_party/android_embedding_dependencies`. Even so, the CIPD package should contain a LICENSE file, and the instructions below explain how to fetch the license information for the dependencies. @@ -26,7 +26,7 @@ below explain how to fetch the license information for the dependencies. 1. Examine the file `./build/reports/license/license-dependency.xml`. If it contains licenses other than "The Apache License, Version 2.0" or something very similar, STOP. Ask Hixie for adivce on how to proceed. -1. Copy or move the `lib/` directory to `src/third_party/android_embedding_dependencies/`, +1. Copy or move the `lib/` directory to `src/flutter/third_party/android_embedding_dependencies/`, overwriting its contents, and ensure the Android build still works. 1. Run `cipd create --pkg-def cipd.yaml -tag last_updated:"$version_tag"` where `$version_tag` is the output of `date +%Y-%m-%dT%T%z`. diff --git a/engine/src/flutter/tools/cipd/android_embedding_bundle/build.gradle b/engine/src/flutter/tools/cipd/android_embedding_bundle/build.gradle index 78edce934eff2..e14dd4095d5cf 100644 --- a/engine/src/flutter/tools/cipd/android_embedding_bundle/build.gradle +++ b/engine/src/flutter/tools/cipd/android_embedding_bundle/build.gradle @@ -33,6 +33,7 @@ allprojects { apply plugin: "com.android.application" android { + namespace "io.flutter.licensecheck" compileSdk 36 } diff --git a/packages/flutter/lib/src/widgets/localizations.dart b/packages/flutter/lib/src/widgets/localizations.dart index 2ca7600107b28..9cbe30a2ff3de 100644 --- a/packages/flutter/lib/src/widgets/localizations.dart +++ b/packages/flutter/lib/src/widgets/localizations.dart @@ -634,6 +634,14 @@ class _LocalizationsState extends State { Locale? get locale => _locale; Locale? _locale; + set locale(Locale? locale) { + assert(locale != null); + if (_locale == locale) { + return; + } + WidgetsBinding.instance.platformDispatcher.setApplicationLocale(locale!); + _locale = locale; + } @override void initState() { @@ -668,7 +676,7 @@ class _LocalizationsState extends State { void load(Locale locale) { final Iterable> delegates = widget.delegates; if (delegates.isEmpty) { - _locale = locale; + this.locale = locale; return; } @@ -681,7 +689,7 @@ class _LocalizationsState extends State { if (typeToResources != null) { // All of the delegates' resources loaded synchronously. _typeToResources = typeToResources!; - _locale = locale; + this.locale = locale; } else { // - Don't rebuild the dependent widgets until the resources for the new locale // have finished loading. Until then the old locale will continue to be used. @@ -692,7 +700,7 @@ class _LocalizationsState extends State { if (mounted) { setState(() { _typeToResources = value; - _locale = locale; + this.locale = locale; }); } RendererBinding.instance.allowFirstFrame(); diff --git a/packages/flutter/test/widgets/localizations_test.dart b/packages/flutter/test/widgets/localizations_test.dart index cdc639c9b5dd3..8896d6f5c594b 100644 --- a/packages/flutter/test/widgets/localizations_test.dart +++ b/packages/flutter/test/widgets/localizations_test.dart @@ -61,6 +61,24 @@ void main() { expect(find.text('loaded'), findsOneWidget); }); + testWidgets('Locale is sent to engine if this is a top level Localizations', ( + WidgetTester tester, + ) async { + final FakeLocalizationsDelegate delegate = FakeLocalizationsDelegate(); + await tester.pumpWidget( + Localizations( + locale: const Locale('fo'), + isApplicationLevel: true, + delegates: >[WidgetsLocalizationsDelegate(), delegate], + child: const Text('loaded'), + ), + ); + delegate.completer.complete('foo'); + await tester.idle(); + await tester.pump(); + expect(tester.binding.platformDispatcher.applicationLocale, const Locale('fo')); + }); + testWidgets('Localizations.localeOf throws when no localizations exist', ( WidgetTester tester, ) async { diff --git a/packages/flutter_test/lib/src/window.dart b/packages/flutter_test/lib/src/window.dart index 78e7af53eb5dc..17ddfbc051df8 100644 --- a/packages/flutter_test/lib/src/window.dart +++ b/packages/flutter_test/lib/src/window.dart @@ -484,6 +484,14 @@ class TestPlatformDispatcher implements PlatformDispatcher { bool get semanticsEnabled => _semanticsEnabledTestValue ?? _platformDispatcher.semanticsEnabled; bool? _semanticsEnabledTestValue; + /// The application locale set during the test. + Locale? applicationLocale; + + @override + void setApplicationLocale(Locale locale) { + applicationLocale = locale; + } + /// Hides the real semantics enabled and reports the given /// [semanticsEnabledTestValue] instead. // ignore: avoid_setters_without_getters From be81070e631f297008bbe905fc3f8b38ef326fcc Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 30 Sep 2025 19:44:17 -0400 Subject: [PATCH 009/204] Roll Fuchsia Linux SDK from rcOl0yxJb4znJ903Y... to 1Ai6VL4vb_GdGnWhg... (#176315) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-linux-sdk-flutter Please CC jimgraham@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 198d80926b6e8..918c3c3e2c1f2 100644 --- a/DEPS +++ b/DEPS @@ -807,7 +807,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'rcOl0yxJb4znJ903YZ0BtnELs13r0WQ7TACeefPZ3bgC' + 'version': '1Ai6VL4vb_GdGnWhg0uhlZW3yT8DJinUL0G7BZ_yGcIC' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', From d4e5754790907c6abe9ded468f2ed78f1fb91494 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 30 Sep 2025 19:55:54 -0400 Subject: [PATCH 010/204] Roll Skia from bb3b6bd4be0d to b242cc09488d (22 revisions) (#176320) https://skia.googlesource.com/skia.git/+log/bb3b6bd4be0d..b242cc09488d 2025-09-30 briansalomon@gmail.com Fix unused var in gm/imagemakewithfilter.cpp 2025-09-30 nbilling@google.com [graphite] Use correct reuse policy on mtl/vk graphite buffers. 2025-09-30 sharaks@google.com Merge 6 release notes into RELEASE_NOTES.md 2025-09-30 sharaks@google.com Update Skia milestone to 143 2025-09-30 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2025-09-30 briansalomon@gmail.com Viewer builds with Graphite but not Ganesh 2025-09-30 mike@reedtribe.org Add new Encode() variant to _none impls 2025-09-30 robertphillips@google.com [graphite] Make use of ThreadSafeResourceProvider 2025-09-30 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from c18184683c0b to 02470a3c2773 (1 revision) 2025-09-30 mike@reedtribe.org Reorganize SkDraw::drawPath() to keep SkPath immutable 2025-09-30 bungeman@google.com [pdf] Remove no longer needed #undefs 2025-09-30 skia-autoroll@skia-public.iam.gserviceaccount.com Roll ANGLE from 7a7681cc8f88 to 81cda6698311 (9 revisions) 2025-09-30 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from d930194b7928 to c2dcfa0865c5 (20 revisions) 2025-09-30 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Skia Infra from ed3dfff05a5e to e521bec27829 (5 revisions) 2025-09-29 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from a9e2ca3b57ab to c18184683c0b (7 revisions) 2025-09-29 mike@reedtribe.org compare utility for paths 2025-09-29 mike@reedtribe.org Add new Encode api to NDK variants 2025-09-29 mike@reedtribe.org Reapply "Use pathbuilder or factories to keep path immutable" 2025-09-29 jsimmons@google.com Apply font variation arguments to system default fonts and character lookup fallback fonts in SkParagraph's FontCollection 2025-09-29 mike@reedtribe.org Harden our conic math for bad W values 2025-09-29 robertphillips@google.com [graphite] Add a ThreadSafeResourceProvider 2025-09-29 mike@reedtribe.org Unify most common form of encoders: returning SkData If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 918c3c3e2c1f2..168beae57b120 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'bb3b6bd4be0dd757165a5723e1794f7ad2211d49', + 'skia_revision': 'b242cc09488db38b2be3e308b8eabdbda7219534', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From b719a6c79938ad85096d58e8031c2c861e8c1c8d Mon Sep 17 00:00:00 2001 From: Victor Sanni Date: Tue, 30 Sep 2025 18:03:40 -0700 Subject: [PATCH 011/204] Fix docs referencing deprecated radio properties (#176244) Docs change to CupertinoRadio and RadioListTile. No tests required. --- packages/flutter/lib/src/cupertino/radio.dart | 12 +++++------- packages/flutter/lib/src/material/radio.dart | 2 +- .../flutter/lib/src/material/radio_list_tile.dart | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/radio.dart b/packages/flutter/lib/src/cupertino/radio.dart index 392ad897950f1..00edeecb2394a 100644 --- a/packages/flutter/lib/src/cupertino/radio.dart +++ b/packages/flutter/lib/src/cupertino/radio.dart @@ -66,23 +66,21 @@ const List _kDisabledDarkGradientOpacities = [0.08, 0.14]; /// deselected. The values are of type `T`, the type parameter of the /// [CupertinoRadio] class. Enums are commonly used for this purpose. /// -/// The radio button itself does not maintain any state. Instead, selecting the -/// radio invokes the [onChanged] callback, passing [value] as a parameter. If -/// [groupValue] and [value] match, this radio will be selected. Most widgets -/// will respond to [onChanged] by calling [State.setState] to update the -/// radio button's [groupValue]. +/// This widget typically has a [RadioGroup] ancestor, which takes in a +/// [RadioGroup.groupValue], and the [CupertinoRadio] under it with matching +/// [value] will be selected. /// /// {@tool dartpad} /// Here is an example of CupertinoRadio widgets wrapped in CupertinoListTiles. /// -/// The currently selected character is passed into `groupValue`, which is +/// The currently selected character is passed into `RadioGroup.groupValue`, which is /// maintained by the example's `State`. In this case, the first [CupertinoRadio] /// will start off selected because `_character` is initialized to /// `SingingCharacter.lafayette`. /// /// If the second radio button is pressed, the example's state is updated /// with `setState`, updating `_character` to `SingingCharacter.jefferson`. -/// This causes the buttons to rebuild with the updated `groupValue`, and +/// This causes the buttons to rebuild with the updated `RadioGroup.groupValue`, and /// therefore the selection of the second button. /// /// ** See code in examples/api/lib/cupertino/radio/cupertino_radio.0.dart ** diff --git a/packages/flutter/lib/src/material/radio.dart b/packages/flutter/lib/src/material/radio.dart index fb84347356f7e..9207d34e67d4b 100644 --- a/packages/flutter/lib/src/material/radio.dart +++ b/packages/flutter/lib/src/material/radio.dart @@ -55,7 +55,7 @@ const double _kInnerRadius = 4.5; /// /// If the second radio button is pressed, the example's state is updated /// with `setState`, updating `_character` to `SingingCharacter.jefferson`. -/// This causes the buttons to rebuild with the updated `groupValue`, and +/// This causes the buttons to rebuild with the updated `RadioGroup.groupValue`, and /// therefore the selection of the second button. /// /// Requires one of its ancestors to be a [Material] widget. diff --git a/packages/flutter/lib/src/material/radio_list_tile.dart b/packages/flutter/lib/src/material/radio_list_tile.dart index f217d6867a746..6a3c756282ea8 100644 --- a/packages/flutter/lib/src/material/radio_list_tile.dart +++ b/packages/flutter/lib/src/material/radio_list_tile.dart @@ -50,7 +50,7 @@ enum _RadioType { material, adaptive } /// This widget does not coordinate the [selected] state and the /// [checked] state; to have the list tile appear selected when the /// radio button is the selected radio button, set [selected] to true -/// when [value] matches [groupValue]. +/// when [value] matches [RadioGroup.groupValue]. /// /// The radio button is shown on the left by default in left-to-right languages /// (i.e. the leading edge). This can be changed using [controlAffinity]. The From 495342fa96b9ca717a83ad2b5c3cb465bd4e7eca Mon Sep 17 00:00:00 2001 From: Konstantin Scheglov Date: Tue, 30 Sep 2025 18:25:35 -0700 Subject: [PATCH 012/204] Stop using deprecated analyzer 7.x.y APIs. (#176242) Preparation for landing https://dart-review.googlesource.com/c/sdk/+/443460 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../lib/src/widget_preview/dependency_graph.dart | 11 +++++------ .../src/widget_preview/preview_code_generator.dart | 12 ++++++------ .../flutter_tools/lib/src/widget_preview/utils.dart | 2 +- .../utils/preview_details_matcher.dart | 8 ++++---- 4 files changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/flutter_tools/lib/src/widget_preview/dependency_graph.dart b/packages/flutter_tools/lib/src/widget_preview/dependency_graph.dart index 1dba4fa28f9b1..69d8950e66451 100644 --- a/packages/flutter_tools/lib/src/widget_preview/dependency_graph.dart +++ b/packages/flutter_tools/lib/src/widget_preview/dependency_graph.dart @@ -11,9 +11,8 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart'; import 'package:analyzer/dart/ast/visitor.dart'; import 'package:analyzer/dart/constant/value.dart'; -import 'package:analyzer/dart/element/element2.dart'; +import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/diagnostic/diagnostic.dart'; -import 'package:analyzer/error/error.dart'; import 'package:analyzer/source/line_info.dart'; import '../base/logger.dart'; @@ -34,7 +33,7 @@ typedef PreviewDependencyGraph = Map; /// Visitor which detects previews and extracts [PreviewDetails] for later code /// generation. class _PreviewVisitor extends RecursiveAstVisitor { - _PreviewVisitor({required LibraryElement2 lib}) + _PreviewVisitor({required LibraryElement lib}) : packageName = lib.uri.scheme == 'package' ? lib.uri.pathSegments.first : null; late final String? packageName; @@ -208,7 +207,7 @@ final class LibraryPreviewNode { bool get hasErrors => errors.isNotEmpty; /// The set of errors found in this library. - final errors = []; + final errors = []; /// Determines the set of errors found in this library. /// @@ -217,8 +216,8 @@ final class LibraryPreviewNode { errors.clear(); for (final String file in files) { errors.addAll( - ((await context.currentSession.getErrors(file)) as ErrorsResult).errors - .where((AnalysisError error) => error.severity == Severity.error) + ((await context.currentSession.getErrors(file)) as ErrorsResult).diagnostics + .where((error) => error.severity == Severity.error) .toList(), ); } diff --git a/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart b/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart index baeacbe763303..104306a7744d7 100644 --- a/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart +++ b/packages/flutter_tools/lib/src/widget_preview/preview_code_generator.dart @@ -3,8 +3,8 @@ // found in the LICENSE file. import 'package:analyzer/dart/constant/value.dart'; -import 'package:analyzer/dart/element/element2.dart' as analyzer; -import 'package:analyzer/dart/element/element2.dart'; +import 'package:analyzer/dart/element/element.dart' as analyzer; +import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:built_collection/built_collection.dart'; import 'package:code_builder/code_builder.dart' as cb; @@ -210,7 +210,7 @@ extension on DartObject { DartType(isDartCoreInt: true) => cb.literalNum(toIntValue()!), DartType(isDartCoreString: true) => cb.literalString(toStringValue()!), DartType(isDartCoreNull: true) => cb.literalNull, - InterfaceType(element3: EnumElement()) => _createEnumInstance(this), + InterfaceType(element: EnumElement()) => _createEnumInstance(this), InterfaceType() => _createInstance(type, this), FunctionType() => _createTearoff(toFunctionValue()!), _ => throw UnsupportedError('Unexpected DartObject type: $runtimeType'), @@ -242,10 +242,10 @@ extension on DartObject { final ConstructorInvocation constructorInvocation = object.constructorInvocation!; final ConstructorElement constructor = constructorInvocation.constructor; final cb.Expression type = cb.refer( - dartType.element3.name3!, - _elementToLibraryIdentifier(dartType.element3), + dartType.element.name!, + _elementToLibraryIdentifier(dartType.element), ); - final String? name = constructor.name3 == 'new' ? null : constructor.name3; + final String? name = constructor.name == 'new' ? null : constructor.name; final List positionalArguments = constructorInvocation.positionalArguments .map((e) => e.toExpression()) diff --git a/packages/flutter_tools/lib/src/widget_preview/utils.dart b/packages/flutter_tools/lib/src/widget_preview/utils.dart index e0f6747b4544c..6b37a8904ca0a 100644 --- a/packages/flutter_tools/lib/src/widget_preview/utils.dart +++ b/packages/flutter_tools/lib/src/widget_preview/utils.dart @@ -8,7 +8,7 @@ import 'dart:collection'; import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/token.dart'; -import 'package:analyzer/dart/element/element2.dart'; +import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:analyzer/source/source.dart'; import 'package:collection/collection.dart'; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/utils/preview_details_matcher.dart b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/utils/preview_details_matcher.dart index 700f597aae979..c82329b7c40a9 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/utils/preview_details_matcher.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/utils/preview_details_matcher.dart @@ -104,24 +104,24 @@ class PreviewMatcher extends Matcher { ); checkPropertyMatch( name: kWrapper, - actual: item.getFirstField(kWrapper)?.toFunctionValue2()?.displayName, + actual: item.getFirstField(kWrapper)?.toFunctionValue()?.displayName, expected: wrapper, ); checkPropertyMatch( name: kTheme, - actual: item.getFirstField(kTheme)?.toFunctionValue2()?.displayName, + actual: item.getFirstField(kTheme)?.toFunctionValue()?.displayName, expected: theme, ); final DartObject? actualBrightness = item.getFirstField(kBrightness); checkPropertyMatch( name: kBrightness, - actual: (actualBrightness?.isNull ?? true) ? null : actualBrightness!.variable2!.toString(), + actual: (actualBrightness?.isNull ?? true) ? null : actualBrightness!.variable!.toString(), expected: brightness, ); checkPropertyMatch( name: kLocalizations, - actual: item.getFirstField(kLocalizations)?.toFunctionValue2()?.displayName, + actual: item.getFirstField(kLocalizations)?.toFunctionValue()?.displayName, expected: localizations, ); return matches; From 231b6dd578daa6da235603008aba87be216cc411 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 30 Sep 2025 22:21:11 -0400 Subject: [PATCH 013/204] Roll Dart SDK from af31d2637b6b to 527333cfe4cf (17 revisions) (#176325) https://dart.googlesource.com/sdk.git/+log/af31d2637b6b..527333cfe4cf 2025-10-01 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-264.0.dev 2025-09-30 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-263.0.dev 2025-09-30 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-262.0.dev 2025-09-30 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-261.0.dev 2025-09-30 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-260.0.dev 2025-09-30 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-259.0.dev 2025-09-30 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-258.0.dev 2025-09-29 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-257.0.dev 2025-09-29 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-256.0.dev 2025-09-29 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-255.0.dev 2025-09-28 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-254.0.dev 2025-09-27 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-253.0.dev 2025-09-27 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-252.0.dev 2025-09-26 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-251.0.dev 2025-09-26 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-250.0.dev 2025-09-26 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-249.0.dev 2025-09-26 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-248.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jimgraham@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/DEPS b/DEPS index 168beae57b120..c121fca00b3e8 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': 'af31d2637b6bced76edd4bcead0d941fe9ad871a', + 'dart_revision': '527333cfe4cf3a0d10d5a4fbe3dc2a51f10ea4ea', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py @@ -71,10 +71,10 @@ vars = { 'dart_libprotobuf_rev': '24487dd1045c7f3d64a21f38a3f0c06cc4cf2edb', 'dart_perfetto_rev': '13ce0c9e13b0940d2476cd0cff2301708a9a2e2b', 'dart_protobuf_gn_rev': 'ca669f79945418f6229e4fef89b666b2a88cbb10', - 'dart_protobuf_rev': '07eed6e8cc0535189112ae8373aa8f16f4a5c3ca', - 'dart_pub_rev': '469eb6193c0a49495ea2ce7432cf749f077ad596', + 'dart_protobuf_rev': '14bbd0bd7ff9b7e322ff4e85bd243f6905170b92', + 'dart_pub_rev': 'f7f1891e2de3d795532f45ec214f88ac912ffcd6', 'dart_sync_http_rev': '6666fff944221891182e1f80bf56569338164d72', - 'dart_tools_rev': 'ecd7dd523c8a5761f337eedce8878b092996a75c', + 'dart_tools_rev': '2ef298e48450de9d2443d466ec4d61172a71b113', 'dart_vector_math_rev': '3939545edc38ed657381381d33acde02c49ff827', 'dart_web_rev': '0baaea4de4dfd60db85dd112d8cc7480d0dd8bd8', 'dart_webdev_rev': '23aefebea46c1f94c27703743b2c8db1f651bf29', @@ -368,7 +368,7 @@ deps = { Var('dart_git') + '/external/github.com/google/webkit_inspection_protocol.dart.git' + '@' + Var('dart_webkit_inspection_protocol_rev'), 'engine/src/flutter/third_party/dart/tools/sdks/dart-sdk': - {'dep_type': 'cipd', 'packages': [{'package': 'dart/dart-sdk/${{platform}}', 'version': 'git_revision:0bf5d0bb49d7872a1a01e55dbea63ad7455d754e'}]}, + {'dep_type': 'cipd', 'packages': [{'package': 'dart/dart-sdk/${{platform}}', 'version': 'git_revision:782552bdddc112c62db28ec7e9b2763f4457a3ea'}]}, # WARNING: end of dart dependencies list that is cleaned up automatically - see create_updated_flutter_deps.py. From 674aa2b8af57938af1cf35fbe2ac35d333728cf2 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 1 Oct 2025 00:50:27 -0400 Subject: [PATCH 014/204] Roll Skia from b242cc09488d to 6998b06397c5 (2 revisions) (#176331) https://skia.googlesource.com/skia.git/+log/b242cc09488d..6998b06397c5 2025-10-01 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 02470a3c2773 to 9023de589c86 (6 revisions) 2025-10-01 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index c121fca00b3e8..2cb303be37fb9 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'b242cc09488db38b2be3e308b8eabdbda7219534', + 'skia_revision': '6998b06397c5a317b5e71dcc714cbec96ceb9e59', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From bb07841a701384caa505a6a04e3dc2e73a77fdae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Awi=C4=99tnicki?= Date: Wed, 1 Oct 2025 07:36:19 +0200 Subject: [PATCH 015/204] Update description in _LastFinderMixin to properly describe finding last (#174232) *Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.* *List which issues are fixed by this PR. You must list at least one issue. An issue is not required if the PR fixes something trivial like a typo.* *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --------- Co-authored-by: Tong Mu --- packages/flutter_test/lib/src/finders.dart | 2 +- packages/flutter_test/test/finders_test.dart | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/flutter_test/lib/src/finders.dart b/packages/flutter_test/lib/src/finders.dart index 8ec1c8b9c2d2c..be9c9df3e98bd 100644 --- a/packages/flutter_test/lib/src/finders.dart +++ b/packages/flutter_test/lib/src/finders.dart @@ -1351,7 +1351,7 @@ class _FirstWidgetFinder extends ChainedFinder with _FirstFinderMixin { mixin _LastFinderMixin on ChainedFinderMixin { @override String describeMatch(Plurality plurality) { - return '${parent.describeMatch(plurality)} (ignoring all but first)'; + return '${parent.describeMatch(plurality)} (ignoring all but last)'; } @override diff --git a/packages/flutter_test/test/finders_test.dart b/packages/flutter_test/test/finders_test.dart index ff7c61f96265e..280a95d9cfd07 100644 --- a/packages/flutter_test/test/finders_test.dart +++ b/packages/flutter_test/test/finders_test.dart @@ -1655,6 +1655,18 @@ void main() { expect(finder.found, orderedEquals(expected)); }); }); + + group('first and last', () { + test('describes first correctly', () { + final _FakeFinder finder = _FakeFinder(); + expect(finder.first.toString(describeSelf: true), contains('(ignoring all but first)')); + }); + + test('describes last correctly', () { + final _FakeFinder finder = _FakeFinder(); + expect(finder.last.toString(describeSelf: true), contains('(ignoring all but last)')); + }); + }); }); } From d5d3ffa38444e1384992666dcd0557d215da8e8e Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 1 Oct 2025 03:24:28 -0400 Subject: [PATCH 016/204] Roll Skia from 6998b06397c5 to ecaff95f51aa (1 revision) (#176333) https://skia.googlesource.com/skia.git/+log/6998b06397c5..ecaff95f51aa 2025-10-01 skia-autoroll@skia-public.iam.gserviceaccount.com Roll SwiftShader from 5f1c459a11bb to 18d4f3db9407 (1 revision) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 2cb303be37fb9..7ff02d23b4ec8 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': '6998b06397c5a317b5e71dcc714cbec96ceb9e59', + 'skia_revision': 'ecaff95f51aa05fdc768c809e76f03fc14ad2fb6', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 2822801b96568c3bbb5dcfe23d7435e4c41a38b6 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 1 Oct 2025 03:38:38 -0400 Subject: [PATCH 017/204] Roll Dart SDK from 527333cfe4cf to 8ffec1435cf3 (1 revision) (#176334) https://dart.googlesource.com/sdk.git/+log/527333cfe4cf..8ffec1435cf3 2025-10-01 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-265.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jimgraham@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 7ff02d23b4ec8..6a9d08f2b8000 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '527333cfe4cf3a0d10d5a4fbe3dc2a51f10ea4ea', + 'dart_revision': '8ffec1435cf31802e237765d6e9aae48fe5bd19e', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From dc3ad9d2461df34e58e68d640193f54720ed48cb Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Wed, 1 Oct 2025 11:18:11 +0200 Subject: [PATCH 018/204] [native assets] Roll dependencies (#176287) Rolling the dependencies to the latest versions. Tests: All existing tests of build hooks and code assets. --- packages/flutter_tools/lib/src/update_packages_pins.dart | 4 ++-- packages/flutter_tools/pubspec.yaml | 6 +++--- .../flutter_tools/templates/package_ffi/pubspec.yaml.tmpl | 2 +- pubspec.lock | 4 ++-- pubspec.yaml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/flutter_tools/lib/src/update_packages_pins.dart b/packages/flutter_tools/lib/src/update_packages_pins.dart index 9615e1522c56c..d0d5123cb3d67 100644 --- a/packages/flutter_tools/lib/src/update_packages_pins.dart +++ b/packages/flutter_tools/lib/src/update_packages_pins.dart @@ -22,7 +22,7 @@ const kManuallyPinnedDependencies = { // Add pinned packages here. Please leave a comment explaining why. 'archive': '3.6.1', // https://github.com/flutter/flutter/issues/115660 'code_assets': - '0.19.6', // Under active development with breaking changes until 1.0.0. Manually rolled by @dcharkes. + '0.19.7', // Under active development with breaking changes until 1.0.0. Manually rolled by @dcharkes. 'dds': '5.0.3', // 5.0.4 contains bugs described in https://github.com/Dart-Code/Dart-Code/issues/4678. 'flutter_gallery_assets': '1.0.2', // Tests depend on the exact version. @@ -31,7 +31,7 @@ const kManuallyPinnedDependencies = { 'hooks': '0.20.1', // Under active development with breaking changes until 1.0.0. Manually rolled by @dcharkes. 'hooks_runner': - '0.22.1', // Under active development with breaking changes until 1.0.0. Manually rolled by @dcharkes. + '0.23.0', // Under active development with breaking changes until 1.0.0. Manually rolled by @dcharkes. 'material_color_utilities': '0.11.1', // Keep pinned to latest until 1.0.0. }; diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index 479b74552f6ca..fb669482f8a79 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -58,9 +58,9 @@ dependencies: pubspec_parse: 1.5.0 graphs: 2.3.2 - hooks_runner: 0.22.1 + hooks_runner: 0.23.0 hooks: 0.20.1 - code_assets: 0.19.6 + code_assets: 0.19.7 data_assets: 0.19.3 # We depend on very specific internal implementation details of the @@ -127,4 +127,4 @@ dev_dependencies: dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: sro36f +# PUBSPEC CHECKSUM: 37t3vr diff --git a/packages/flutter_tools/templates/package_ffi/pubspec.yaml.tmpl b/packages/flutter_tools/templates/package_ffi/pubspec.yaml.tmpl index 935e47e865254..e616c27d1e9be 100644 --- a/packages/flutter_tools/templates/package_ffi/pubspec.yaml.tmpl +++ b/packages/flutter_tools/templates/package_ffi/pubspec.yaml.tmpl @@ -6,7 +6,7 @@ environment: sdk: {{dartSdkVersionBounds}} dependencies: - code_assets: ^0.19.6 + code_assets: ^0.19.7 hooks: ^0.20.1 logging: ^1.3.0 native_toolchain_c: ^0.17.1 diff --git a/pubspec.lock b/pubspec.lock index 6f9c0922ed289..a31d0bd525ea0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -126,10 +126,10 @@ packages: dependency: "direct main" description: name: code_assets - sha256: "3b182b03a2f263ad6ab47cdadf9b64daa1d2d72ac941a277f685fb77ba96cbd5" + sha256: "5865a34983367f6884e8311f67d9ae459945b99bad20c0e66484a1aae7feaae0" url: "https://pub.dev" source: hosted - version: "0.19.6" + version: "0.19.7" collection: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 2c63dafa0ca9f..c4b523044cf0d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -90,7 +90,7 @@ dependencies: checked_yaml: 2.0.4 cli_config: 0.2.0 clock: 1.1.2 - code_assets: 0.19.6 + code_assets: 0.19.7 collection: 1.19.1 convert: 3.1.2 coverage: 1.15.0 @@ -212,4 +212,4 @@ dependencies: pedantic: 1.11.1 quiver: 3.2.2 yaml_edit: 2.2.2 -# PUBSPEC CHECKSUM: 5pih92 +# PUBSPEC CHECKSUM: 9hqic From c33437606c4d944b204a498344e15e37cd653fc6 Mon Sep 17 00:00:00 2001 From: Daco Harkes Date: Wed, 1 Oct 2025 11:20:05 +0200 Subject: [PATCH 019/204] [native assets] Enable build hooks and code assets on stable (#176285) Dart CL enabling for next stable: * https://dart-review.googlesource.com/c/sdk/+/449803 Tests: * All existing tests for the feature on the other channels. --- packages/flutter_tools/lib/src/features.dart | 1 + packages/flutter_tools/test/general.shard/features_test.dart | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/flutter_tools/lib/src/features.dart b/packages/flutter_tools/lib/src/features.dart index 74fb5da826baf..c375078c4dfae 100644 --- a/packages/flutter_tools/lib/src/features.dart +++ b/packages/flutter_tools/lib/src/features.dart @@ -188,6 +188,7 @@ const nativeAssets = Feature( environmentOverride: 'FLUTTER_NATIVE_ASSETS', master: FeatureChannelSetting(available: true, enabledByDefault: true), beta: FeatureChannelSetting(available: true, enabledByDefault: true), + stable: FeatureChannelSetting(available: true, enabledByDefault: true), ); /// Enable Dart data assets building and bundling. diff --git a/packages/flutter_tools/test/general.shard/features_test.dart b/packages/flutter_tools/test/general.shard/features_test.dart index ed8eeae18b416..86dcfa7d541ca 100644 --- a/packages/flutter_tools/test/general.shard/features_test.dart +++ b/packages/flutter_tools/test/general.shard/features_test.dart @@ -334,7 +334,7 @@ void main() { nativeAssets, allOf([ _onChannelIs('master', available: true, enabledByDefault: true), - _onChannelIs('stable', available: false, enabledByDefault: false), + _onChannelIs('stable', available: true, enabledByDefault: true), _onChannelIs('beta', available: true, enabledByDefault: true), ]), ); From 72eec03e860accebd7adca221c8568e9802cd7d6 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 1 Oct 2025 05:41:27 -0400 Subject: [PATCH 020/204] Roll Skia from ecaff95f51aa to c44a36470d07 (4 revisions) (#176336) https://skia.googlesource.com/skia.git/+log/ecaff95f51aa..c44a36470d07 2025-10-01 skia-autoroll@skia-public.iam.gserviceaccount.com Roll ANGLE from 81cda6698311 to ae02c3292a95 (9 revisions) 2025-10-01 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from c2dcfa0865c5 to cd3a5de0811f (31 revisions) 2025-10-01 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2025-10-01 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Skia Infra from e521bec27829 to a3b8ecf9f94a (9 revisions) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 6a9d08f2b8000..309c181fc29ea 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'ecaff95f51aa05fdc768c809e76f03fc14ad2fb6', + 'skia_revision': 'c44a36470d077020b978f87fc5720f85dfc68d5b', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 80496d7662acea2bdeef601035b3553adf540fd2 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Wed, 1 Oct 2025 09:24:37 -0500 Subject: [PATCH 021/204] Add SwiftUI support for UIScene migration (#176230) When embedding Flutter in a SwiftUI app, it appears that SwiftUI wraps the scene delegate given by the developer in an internal class called `SwiftUI.AppSceneDelegate`. This causes the scene delegate to fail to check if it conforms to the `FlutterSceneLifeCycleProvider` protocol - even though it does. However, after force casting it, selectors respond and can be used. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../bin/tasks/module_uiscene_test_ios.dart | 151 +++-- .../native/Info-migrated-no-config.plist | 13 + .../native/SwiftUIApp-ContentView.swift | 29 + .../SwiftUIApp-FlutterSceneDelegate.swift | 46 ++ ...tUIApp-FlutterSceneLifeCycleProvider.swift | 113 ++++ ...ests-SceneEvents-NoApplicationEvents.swift | 55 ++ .../ios_add2app_uiscene/xcode_swiftui/Podfile | 22 + .../xcode_swiftui/xcode-swiftui-Info.plist | 5 + .../xcode_swiftui.xcodeproj/project.pbxproj | 553 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../contents.xcworkspacedata | 10 + .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 35 ++ .../Assets.xcassets/Contents.json | 6 + .../xcode_swiftui/ContentView.swift | 21 + .../xcode_swiftui/xcode_swiftuiApp.swift | 14 + .../ios/framework/Source/FlutterEngine.mm | 8 +- .../framework/Source/FlutterSceneLifeCycle.mm | 31 +- .../Source/FlutterSceneLifeCycleTest.mm | 30 + .../Source/FlutterSceneLifeCycle_Internal.h | 2 + .../ios/framework/Source/FlutterView.mm | 26 +- 21 files changed, 1119 insertions(+), 69 deletions(-) create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/Info-migrated-no-config.plist create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/SwiftUIApp-ContentView.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/SwiftUIApp-FlutterSceneDelegate.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/SwiftUIApp-FlutterSceneLifeCycleProvider.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-NoApplicationEvents.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/Podfile create mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode-swiftui-Info.plist create mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.pbxproj create mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcworkspace/contents.xcworkspacedata create mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/Contents.json create mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/ContentView.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/xcode_swiftuiApp.swift diff --git a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart index 995d65d804a81..51c03c98904b3 100644 --- a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart +++ b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart @@ -27,9 +27,11 @@ import 'package:path/path.dart' as path; Future main(List args) async { const String kDestination = 'destination'; const String kTestName = 'name'; + const String kXcodeProjecType = 'type'; final ArgParser argParser = ArgParser() ..addOption(kDestination) - ..addOption(kTestName); + ..addOption(kTestName) + ..addOption(kXcodeProjecType); await task(() async { final ArgResults argResults = argParser.parse(args); @@ -49,6 +51,18 @@ Future main(List args) async { final String? testName = argResults.option(kTestName); + XcodeProjectType? projectType; + final String? projectTypeName = argResults.option(kXcodeProjecType); + if (projectTypeName?.toLowerCase() == 'swiftui') { + projectType = XcodeProjectType.SwiftUI; + } else if (projectTypeName?.toLowerCase() == 'uikit-swift') { + projectType = XcodeProjectType.UIKitSwift; + } + List projectTypesToTest = XcodeProjectType.values; + if (projectType != null) { + projectTypesToTest = [projectType]; + } + String? simulatorDeviceId; final Directory templatesDir = Directory( path.join(flutterDirectory.path, 'dev', 'integration_tests', 'ios_add2app_uiscene'), @@ -63,50 +77,56 @@ Future main(List args) async { destinationDir: destinationDir, templatesDir: templatesDir, ); - final Directory xcodeProjectDir = await _createNativeApp( - destinationDir: destinationDir, - templatesDir: templatesDir, - xcodeProjectType: XcodeProjectType.UIKitSwift, - ); bool testFailed = false; - await testWithNewIOSSimulator('TestAdd2AppSim', (String deviceId) async { - simulatorDeviceId = deviceId; - final Scenarios scenarios = Scenarios(); - for (final String scenarioName in scenarios.scenarios.keys) { - if (testName != null && scenarioName != testName) { - continue; - } - final List replacements = FileReplacements.fromScenario( - scenarios.scenarios[scenarioName]!, + for (final XcodeProjectType xcodeProjectType in projectTypesToTest) { + final (String xcodeProjectName, Directory xcodeProjectDir) = await _createNativeApp( + destinationDir: destinationDir, templatesDir: templatesDir, - xcodeProjectDir: xcodeProjectDir, - pluginDir: pluginDir, - appDir: appDir, + xcodeProjectType: xcodeProjectType, ); - for (final FileReplacements replacement in replacements) { - replacement.replace(); - } - - section('Test Scenario $scenarioName'); - - await _installPlugins(appDir: appDir, xcodeProjectDir: xcodeProjectDir); - final int result = await _testNativeApp( - deviceId: simulatorDeviceId!, - scenarioName: scenarioName, - templatesDir: templatesDir, - xcodeProjectDir: xcodeProjectDir, + simulatorDeviceId = deviceId; + final Scenarios scenarios = Scenarios(); + final Map> scenariosMap = scenarios.scenarios( + xcodeProjectType, ); - if (result != 0) { - testFailed = true; - } + for (final String scenarioName in scenariosMap.keys) { + if (testName != null && scenarioName != testName) { + continue; + } + final List replacements = FileReplacements.fromScenario( + scenariosMap[scenarioName]!, + templatesDir: templatesDir, + xcodeProjectDir: xcodeProjectDir, + pluginDir: pluginDir, + appDir: appDir, + ); - // Reset files to original between scenarios unless we're targetting a specific test. - if (testName == null) { for (final FileReplacements replacement in replacements) { - replacement.reset(); + replacement.replace(); + } + + section('Test Scenario $scenarioName'); + + await _installPlugins(appDir: appDir, xcodeProjectDir: xcodeProjectDir); + final int result = await _testNativeApp( + deviceId: simulatorDeviceId!, + scenarioName: scenarioName, + templatesDir: templatesDir, + xcodeProjectDir: xcodeProjectDir, + xcodeProjectName: xcodeProjectName, + ); + if (result != 0) { + testFailed = true; + } + + // Reset files to original between scenarios unless we're targetting a specific test. + if (testName == null) { + for (final FileReplacements replacement in replacements) { + replacement.reset(); + } } } } @@ -167,7 +187,7 @@ Future _createFlutterPlugin({ return Directory(path.join(destinationDir.path, pluginName)); } -Future _createNativeApp({ +Future<(String, Directory)> _createNativeApp({ required Directory templatesDir, required Directory destinationDir, required XcodeProjectType xcodeProjectType, @@ -179,12 +199,8 @@ Future _createNativeApp({ switch (xcodeProjectType) { case XcodeProjectType.UIKitSwift: xcodeProjectName = 'xcode_uikit_swift'; - case XcodeProjectType.UIKitObjC: - // TODO(vashworth): add Objective C integration test - throw UnimplementedError(); case XcodeProjectType.SwiftUI: - // TODO(vashworth): add SwiftUI integration test - throw UnimplementedError(); + xcodeProjectName = 'xcode_swiftui'; } // Copy Xcode project final Directory xcodeProjectDir = Directory(path.join(destinationDir.path, xcodeProjectName)); @@ -192,7 +208,7 @@ Future _createNativeApp({ final Directory xcodeProjectTemplate = Directory(path.join(templatesDir.path, xcodeProjectName)); recursiveCopy(xcodeProjectTemplate, xcodeProjectDir); - return xcodeProjectDir; + return (xcodeProjectName, xcodeProjectDir); } Future _installPlugins({ @@ -279,6 +295,7 @@ Future _testNativeApp({ required String scenarioName, required Directory xcodeProjectDir, required String deviceId, + required String xcodeProjectName, }) async { final String resultBundleTemp = Directory.systemTemp .createTempSync('flutter_module_test_ios_xcresult.') @@ -288,9 +305,9 @@ Future _testNativeApp({ 'xcodebuild', [ '-workspace', - 'xcode_uikit_swift.xcworkspace', + '$xcodeProjectName.xcworkspace', '-scheme', - 'xcode_uikit_swift', + xcodeProjectName, '-configuration', 'Debug', '-destination', @@ -332,17 +349,26 @@ Future _uploadTestResults({ } } -enum XcodeProjectType { UIKitSwift, UIKitObjC, SwiftUI } +enum XcodeProjectType { UIKitSwift, SwiftUI } class Scenarios { Scenarios(); + Map> scenarios(XcodeProjectType projectType) { + switch (projectType) { + case XcodeProjectType.UIKitSwift: + return uiKitSwiftScenarios; + case XcodeProjectType.SwiftUI: + return swiftUIScenarios; + } + } + /// A map of scenario names to a map of file replacements. /// /// Each scenario is a different configuration for testing the Flutter module /// in a native iOS app. The file replacements are used to set up the /// specific configuration for each scenario. - late Map> scenarios = >{ + late Map> uiKitSwiftScenarios = >{ // When both the app and the plugin have migrated to scenes, we expect scene events. 'AppMigrated-FlutterSceneDelegate-PluginMigrated': { ...sharedLifecycleFiles, @@ -412,6 +438,37 @@ class Scenarios { }, }; + late Map> swiftUIScenarios = >{ + 'SwiftUI-FlutterSceneDelegate': { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/Info-migrated-no-config.plist': + r'$XCODE_PROJ_DIR/xcode_swiftui/xcode_swiftui-Info.plist', + r'$TEMPLATE_DIR/native/SwiftUIApp-FlutterSceneDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_swiftui/xcode_swiftuiApp.swift', + r'$TEMPLATE_DIR/native/SwiftUIApp-ContentView.swift': + r'$XCODE_PROJ_DIR/xcode_swiftui/ContentView.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift': + r'$XCODE_PROJ_DIR/xcode_swiftuiUITests/xcode_swiftuiUITests.swift', + }, + 'SwiftUI-FlutterSceneLifeCycleProvider': { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/Info-migrated-no-config.plist': + r'$XCODE_PROJ_DIR/xcode_swiftui/xcode_swiftui-Info.plist', + r'$TEMPLATE_DIR/native/SwiftUIApp-FlutterSceneLifeCycleProvider.swift': + r'$XCODE_PROJ_DIR/xcode_swiftui/xcode_swiftuiApp.swift', + r'$TEMPLATE_DIR/native/SwiftUIApp-ContentView.swift': + r'$XCODE_PROJ_DIR/xcode_swiftui/ContentView.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift': + r'$XCODE_PROJ_DIR/xcode_swiftuiUITests/xcode_swiftuiUITests.swift', + }, + }; + late Map sharedLifecycleFiles = { ...sharedAppLifecycleFiles, ...sharedPluginLifecycleFiles, diff --git a/dev/integration_tests/ios_add2app_uiscene/native/Info-migrated-no-config.plist b/dev/integration_tests/ios_add2app_uiscene/native/Info-migrated-no-config.plist new file mode 100644 index 0000000000000..816a1493708e3 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/Info-migrated-no-config.plist @@ -0,0 +1,13 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + + + diff --git a/dev/integration_tests/ios_add2app_uiscene/native/SwiftUIApp-ContentView.swift b/dev/integration_tests/ios_add2app_uiscene/native/SwiftUIApp-ContentView.swift new file mode 100644 index 0000000000000..e0d83a39f2112 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/SwiftUIApp-ContentView.swift @@ -0,0 +1,29 @@ +// 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 Flutter +import SwiftUI + +struct FlutterViewControllerRepresentable: UIViewControllerRepresentable { + @Environment(AppDelegate.self) var appDelegate + + func makeUIViewController(context: Context) -> some UIViewController { + return FlutterViewController( + engine: appDelegate.flutterEngine, + nibName: nil, + bundle: nil + ) + } + + func updateUIViewController( + _ uiViewController: UIViewControllerType, + context: Context + ) {} +} + +struct ContentView: View { + var body: some View { + FlutterViewControllerRepresentable() + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/SwiftUIApp-FlutterSceneDelegate.swift b/dev/integration_tests/ios_add2app_uiscene/native/SwiftUIApp-FlutterSceneDelegate.swift new file mode 100644 index 0000000000000..2e224e185938e --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/SwiftUIApp-FlutterSceneDelegate.swift @@ -0,0 +1,46 @@ +// 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 Flutter +import FlutterPluginRegistrant +import SwiftUI + +@Observable +class AppDelegate: FlutterAppDelegate { + let flutterEngine = FlutterEngine(name: "my flutter engine") + + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication + .LaunchOptionsKey: Any]? + ) -> Bool { + flutterEngine.run() + GeneratedPluginRegistrant.register(with: self.flutterEngine) + return true + } + + override func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + let configuration = UISceneConfiguration( + name: nil, + sessionRole: connectingSceneSession.role + ) + configuration.delegateClass = FlutterSceneDelegate.self + return configuration + } +} + +@main +struct xcode_swiftuiApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/SwiftUIApp-FlutterSceneLifeCycleProvider.swift b/dev/integration_tests/ios_add2app_uiscene/native/SwiftUIApp-FlutterSceneLifeCycleProvider.swift new file mode 100644 index 0000000000000..6dabf06ed88b2 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/SwiftUIApp-FlutterSceneLifeCycleProvider.swift @@ -0,0 +1,113 @@ +// 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 Flutter +import FlutterPluginRegistrant +import SwiftUI + +@Observable +class AppDelegate: FlutterAppDelegate { + let flutterEngine = FlutterEngine(name: "my flutter engine") + + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication + .LaunchOptionsKey: Any]? + ) -> Bool { + flutterEngine.run() + GeneratedPluginRegistrant.register(with: self.flutterEngine) + return true + } + + override func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + let configuration = UISceneConfiguration( + name: nil, + sessionRole: connectingSceneSession.role + ) + configuration.delegateClass = SceneDelegate.self + return configuration + } +} + +class SceneDelegate: UIResponder, UIWindowSceneDelegate, + FlutterSceneLifeCycleProvider +{ + + var sceneLifeCycleDelegate: FlutterPluginSceneLifeCycleDelegate = + FlutterPluginSceneLifeCycleDelegate() + + var window: UIWindow? + + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard (scene as? UIWindowScene) != nil else { return } + + sceneLifeCycleDelegate.scene( + scene, + willConnectTo: session, + options: connectionOptions + ) + } + + func sceneDidDisconnect(_ scene: UIScene) { + sceneLifeCycleDelegate.sceneDidDisconnect(scene) + } + + func sceneWillEnterForeground(_ scene: UIScene) { + sceneLifeCycleDelegate.sceneWillEnterForeground(scene) + } + + func sceneDidBecomeActive(_ scene: UIScene) { + sceneLifeCycleDelegate.sceneDidBecomeActive(scene) + } + + func sceneWillResignActive(_ scene: UIScene) { + sceneLifeCycleDelegate.sceneWillResignActive(scene) + } + + func sceneDidEnterBackground(_ scene: UIScene) { + sceneLifeCycleDelegate.sceneDidEnterBackground(scene) + } + + func scene( + _ scene: UIScene, + openURLContexts URLContexts: Set + ) { + sceneLifeCycleDelegate.scene(scene, openURLContexts: URLContexts) + } + + func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { + sceneLifeCycleDelegate.scene(scene, continue: userActivity) + } + + func windowScene( + _ windowScene: UIWindowScene, + performActionFor shortcutItem: UIApplicationShortcutItem, + completionHandler: @escaping (Bool) -> Void + ) { + sceneLifeCycleDelegate.windowScene( + windowScene, + performActionFor: shortcutItem, + completionHandler: completionHandler + ) + } +} + +@main +struct xcode_swiftuiApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-NoApplicationEvents.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-NoApplicationEvents.swift new file mode 100644 index 0000000000000..1bd19653eaf15 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-NoApplicationEvents.swift @@ -0,0 +1,55 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import XCTest + +final class xcode_uikit_swiftUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + @MainActor + func testLifecycleEvents() throws { + let app = XCUIApplication() + app.launch() + let button = app.buttons["Get Lifecycle Events"].firstMatch + XCTAssertTrue(button.waitForExistence(timeout: 5)) + button.tap() + + let expectedStartEvents = [ + "sceneWillConnect", + "sceneWillEnterForeground", "sceneDidBecomeActive", + ] + let startEventsPredicate = NSPredicate( + format: "label == %@", + expectedStartEvents.joined(separator: "\n") + ) + let startEventsElement = app.staticTexts.element( + matching: startEventsPredicate + ) + XCTAssertTrue(startEventsElement.waitForExistence(timeout: 5)) + + // Background the app, then reactivate it and check the events again + XCUIDevice.shared.press(.home) + app.activate() + XCTAssertTrue(button.waitForExistence(timeout: 5)) + button.tap() + + let expectedEventsAfterBackgroundAndReactivate = [ + "sceneWillConnect", + "sceneWillEnterForeground", "sceneDidBecomeActive", + "sceneWillResignActive", "sceneDidEnterBackground", + "sceneWillEnterForeground", "sceneDidBecomeActive", + ] + let backgroundEventsPredicate = NSPredicate( + format: "label == %@", + expectedEventsAfterBackgroundAndReactivate.joined(separator: "\n") + ) + let backgroundEventsElement = app.staticTexts.element( + matching: backgroundEventsPredicate + ) + XCTAssertTrue(backgroundEventsElement.waitForExistence(timeout: 5)) + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/Podfile b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/Podfile new file mode 100644 index 0000000000000..d04cf9b1f849e --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/Podfile @@ -0,0 +1,22 @@ +# Uncomment the next line to define a global platform for your project +# platform :ios, '9.0' + +flutter_application_path = '../my_module' +load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') + +target 'xcode_swiftui' do + # Comment the next line if you don't want to use dynamic frameworks + use_frameworks! + + # Pods for xcode_swiftui + install_all_flutter_pods(flutter_application_path) + + target 'xcode_swiftuiUITests' do + # Pods for testing + end + +end + +post_install do |installer| + flutter_post_install(installer) if defined?(flutter_post_install) +end diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode-swiftui-Info.plist b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode-swiftui-Info.plist new file mode 100644 index 0000000000000..0c67376ebacb4 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode-swiftui-Info.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.pbxproj b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.pbxproj new file mode 100644 index 0000000000000..657179a24e02d --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.pbxproj @@ -0,0 +1,553 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 09AE28A9A100161970ABA956 /* Pods_xcode_swiftui.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 149DE053CEEA02EA47E518CD /* Pods_xcode_swiftui.framework */; }; + 1991B26C9F33F99FB17C6D71 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A71D00ADA4BD1423A4888D5 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 787D51772E8A1A61002EB011 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 787D51572E8A1A60002EB011 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 787D515E2E8A1A60002EB011; + remoteInfo = xcode_swiftui; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 0625163ECFD280D6722E02EB /* Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig"; path = "Target Support Files/Pods-xcode_swiftui-xcode_swiftuiUITests/Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig"; sourceTree = ""; }; + 149DE053CEEA02EA47E518CD /* Pods_xcode_swiftui.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xcode_swiftui.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 36F75FFFDAB0368B5D9AEF50 /* Pods-xcode_swiftuiTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftuiTests.release.xcconfig"; path = "Target Support Files/Pods-xcode_swiftuiTests/Pods-xcode_swiftuiTests.release.xcconfig"; sourceTree = ""; }; + 5B754AE90ACC1361E738A440 /* Pods-xcode_swiftui.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftui.debug.xcconfig"; path = "Target Support Files/Pods-xcode_swiftui/Pods-xcode_swiftui.debug.xcconfig"; sourceTree = ""; }; + 714588AEC6F0B96B28798AB0 /* Pods-xcode_swiftui.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftui.release.xcconfig"; path = "Target Support Files/Pods-xcode_swiftui/Pods-xcode_swiftui.release.xcconfig"; sourceTree = ""; }; + 787D515F2E8A1A60002EB011 /* xcode_swiftui.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = xcode_swiftui.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 787D51762E8A1A61002EB011 /* xcode_swiftuiUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = xcode_swiftuiUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 8A71D00ADA4BD1423A4888D5 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xcode_swiftui_xcode_swiftuiUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 8B2B617A60DAC180929CD642 /* Pods-xcode_swiftuiTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftuiTests.debug.xcconfig"; path = "Target Support Files/Pods-xcode_swiftuiTests/Pods-xcode_swiftuiTests.debug.xcconfig"; sourceTree = ""; }; + BB561EFA00EC91BA8A168BE3 /* Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig"; path = "Target Support Files/Pods-xcode_swiftui-xcode_swiftuiUITests/Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig"; sourceTree = ""; }; + EE34DDFB6BB2E47ABD6F8EE5 /* Pods_xcode_swiftuiTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xcode_swiftuiTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 787D51612E8A1A60002EB011 /* xcode_swiftui */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = xcode_swiftui; + sourceTree = ""; + }; + 787D51792E8A1A61002EB011 /* xcode_swiftuiUITests */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = xcode_swiftuiUITests; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 787D515C2E8A1A60002EB011 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 09AE28A9A100161970ABA956 /* Pods_xcode_swiftui.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 787D51732E8A1A61002EB011 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1991B26C9F33F99FB17C6D71 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 3FAB8759C7E0FBF58FE109A5 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 149DE053CEEA02EA47E518CD /* Pods_xcode_swiftui.framework */, + 8A71D00ADA4BD1423A4888D5 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework */, + EE34DDFB6BB2E47ABD6F8EE5 /* Pods_xcode_swiftuiTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 787D51562E8A1A60002EB011 = { + isa = PBXGroup; + children = ( + 787D51612E8A1A60002EB011 /* xcode_swiftui */, + 787D51792E8A1A61002EB011 /* xcode_swiftuiUITests */, + 787D51602E8A1A60002EB011 /* Products */, + E283866923164D07FF92228F /* Pods */, + 3FAB8759C7E0FBF58FE109A5 /* Frameworks */, + ); + sourceTree = ""; + }; + 787D51602E8A1A60002EB011 /* Products */ = { + isa = PBXGroup; + children = ( + 787D515F2E8A1A60002EB011 /* xcode_swiftui.app */, + 787D51762E8A1A61002EB011 /* xcode_swiftuiUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + E283866923164D07FF92228F /* Pods */ = { + isa = PBXGroup; + children = ( + 5B754AE90ACC1361E738A440 /* Pods-xcode_swiftui.debug.xcconfig */, + 714588AEC6F0B96B28798AB0 /* Pods-xcode_swiftui.release.xcconfig */, + 0625163ECFD280D6722E02EB /* Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig */, + BB561EFA00EC91BA8A168BE3 /* Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig */, + 8B2B617A60DAC180929CD642 /* Pods-xcode_swiftuiTests.debug.xcconfig */, + 36F75FFFDAB0368B5D9AEF50 /* Pods-xcode_swiftuiTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 787D515E2E8A1A60002EB011 /* xcode_swiftui */ = { + isa = PBXNativeTarget; + buildConfigurationList = 787D51802E8A1A61002EB011 /* Build configuration list for PBXNativeTarget "xcode_swiftui" */; + buildPhases = ( + 6E75E8B810F47E67163AA6AA /* [CP] Check Pods Manifest.lock */, + 787D515B2E8A1A60002EB011 /* Sources */, + 787D515C2E8A1A60002EB011 /* Frameworks */, + 787D515D2E8A1A60002EB011 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 787D51612E8A1A60002EB011 /* xcode_swiftui */, + ); + name = xcode_swiftui; + productName = xcode_swiftui; + productReference = 787D515F2E8A1A60002EB011 /* xcode_swiftui.app */; + productType = "com.apple.product-type.application"; + }; + 787D51752E8A1A61002EB011 /* xcode_swiftuiUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 787D51862E8A1A61002EB011 /* Build configuration list for PBXNativeTarget "xcode_swiftuiUITests" */; + buildPhases = ( + B6AB117EAEF0BDF8D52A82C6 /* [CP] Check Pods Manifest.lock */, + 787D51722E8A1A61002EB011 /* Sources */, + 787D51732E8A1A61002EB011 /* Frameworks */, + 787D51742E8A1A61002EB011 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 787D51782E8A1A61002EB011 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 787D51792E8A1A61002EB011 /* xcode_swiftuiUITests */, + ); + name = xcode_swiftuiUITests; + productName = xcode_swiftuiUITests; + productReference = 787D51762E8A1A61002EB011 /* xcode_swiftuiUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 787D51572E8A1A60002EB011 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 2600; + LastUpgradeCheck = 2600; + TargetAttributes = { + 787D515E2E8A1A60002EB011 = { + CreatedOnToolsVersion = 26.0; + }; + 787D51752E8A1A61002EB011 = { + CreatedOnToolsVersion = 26.0; + TestTargetID = 787D515E2E8A1A60002EB011; + }; + }; + }; + buildConfigurationList = 787D515A2E8A1A60002EB011 /* Build configuration list for PBXProject "xcode_swiftui" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 787D51562E8A1A60002EB011; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 787D51602E8A1A60002EB011 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 787D515E2E8A1A60002EB011 /* xcode_swiftui */, + 787D51752E8A1A61002EB011 /* xcode_swiftuiUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 787D515D2E8A1A60002EB011 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 787D51742E8A1A61002EB011 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 6E75E8B810F47E67163AA6AA /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-xcode_swiftui-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + B6AB117EAEF0BDF8D52A82C6 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-xcode_swiftui-xcode_swiftuiUITests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 787D515B2E8A1A60002EB011 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 787D51722E8A1A61002EB011 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 787D51782E8A1A61002EB011 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 787D515E2E8A1A60002EB011 /* xcode_swiftui */; + targetProxy = 787D51772E8A1A61002EB011 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 787D517E2E8A1A61002EB011 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_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_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = S8QB4VV633; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 787D517F2E8A1A61002EB011 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_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_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = S8QB4VV633; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 26.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 787D51812E8A1A61002EB011 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 5B754AE90ACC1361E738A440 /* Pods-xcode_swiftui.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S8QB4VV633; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "xcode-swiftui-Info.plist"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-swiftui"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 787D51822E8A1A61002EB011 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 714588AEC6F0B96B28798AB0 /* Pods-xcode_swiftui.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S8QB4VV633; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "xcode-swiftui-Info.plist"; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-swiftui"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = YES; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 787D51872E8A1A61002EB011 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 0625163ECFD280D6722E02EB /* Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S8QB4VV633; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-swiftuiUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = xcode_swiftui; + }; + name = Debug; + }; + 787D51882E8A1A61002EB011 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BB561EFA00EC91BA8A168BE3 /* Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig */; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = S8QB4VV633; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-swiftuiUITests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + STRING_CATALOG_GENERATE_SYMBOLS = NO; + SWIFT_APPROACHABLE_CONCURRENCY = YES; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = xcode_swiftui; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 787D515A2E8A1A60002EB011 /* Build configuration list for PBXProject "xcode_swiftui" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 787D517E2E8A1A61002EB011 /* Debug */, + 787D517F2E8A1A61002EB011 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 787D51802E8A1A61002EB011 /* Build configuration list for PBXNativeTarget "xcode_swiftui" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 787D51812E8A1A61002EB011 /* Debug */, + 787D51822E8A1A61002EB011 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 787D51862E8A1A61002EB011 /* Build configuration list for PBXNativeTarget "xcode_swiftuiUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 787D51872E8A1A61002EB011 /* Debug */, + 787D51882E8A1A61002EB011 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 787D51572E8A1A60002EB011 /* Project object */; +} diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000..919434a6254f0 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcworkspace/contents.xcworkspacedata b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000..457d4d1cd781a --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/AccentColor.colorset/Contents.json b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000000000..eb87897008164 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/AppIcon.appiconset/Contents.json b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000000..2305880107db5 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/Contents.json b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/Contents.json new file mode 100644 index 0000000000000..73c00596a7fca --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/ContentView.swift b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/ContentView.swift new file mode 100644 index 0000000000000..1d87238b72718 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/ContentView.swift @@ -0,0 +1,21 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import SwiftUI + +struct ContentView: View { + var body: some View { + VStack { + Image(systemName: "globe") + .imageScale(.large) + .foregroundStyle(.tint) + Text("Hello, world!") + } + .padding() + } +} + +#Preview { + ContentView() +} diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/xcode_swiftuiApp.swift b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/xcode_swiftuiApp.swift new file mode 100644 index 0000000000000..ea03864988d41 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/xcode_swiftuiApp.swift @@ -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 SwiftUI + +@main +struct xcode_swiftuiApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 895694c1c9f4d..a23d3b0f32d05 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -284,10 +284,10 @@ - (void)sceneWillConnect:(NSNotification*)notification API_AVAILABLE(ios(13.0)) // plugins to receive the `scene:willConnectToSession:options` event. // If we want to support multi-window on iPad later, we may need to add a way for deveopers to // register their FlutterEngine to the scene manually during this event. - if ([scene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) { - NSObject* sceneProvider = - (NSObject*)scene.delegate; - [sceneProvider.sceneLifeCycleDelegate engine:self receivedConnectNotificationFor:scene]; + FlutterPluginSceneLifeCycleDelegate* sceneLifeCycleDelegate = + [FlutterPluginSceneLifeCycleDelegate fromScene:scene]; + if (sceneLifeCycleDelegate != nil) { + return [sceneLifeCycleDelegate engine:self receivedConnectNotificationFor:scene]; } } } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm index 47235584764e0..f9bc82e39a8c4 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm @@ -252,6 +252,28 @@ - (BOOL)windowScene:(UIWindowScene*)windowScene } return consumedByPlugin; } + ++ (FlutterPluginSceneLifeCycleDelegate*)fromScene:(UIScene*)scene { + if ([scene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) { + NSObject* sceneProvider = + (NSObject*)scene.delegate; + return sceneProvider.sceneLifeCycleDelegate; + } + + // When embedded in a SwiftUI app, the scene delegate does not conform to + // FlutterSceneLifeCycleProvider even if it does. However, after force casting it, + // selectors respond and can be used. + NSObject* sceneProvider = + (NSObject*)scene.delegate; + if ([sceneProvider respondsToSelector:@selector(sceneLifeCycleDelegate)]) { + id sceneLifeCycleDelegate = sceneProvider.sceneLifeCycleDelegate; + // Double check that the selector is the expected class. + if ([sceneLifeCycleDelegate isKindOfClass:[FlutterPluginSceneLifeCycleDelegate class]]) { + return (FlutterPluginSceneLifeCycleDelegate*)sceneLifeCycleDelegate; + } + } + return nil; +} @end @implementation FlutterEnginePluginSceneLifeCycleDelegate { @@ -286,11 +308,10 @@ - (BOOL)scene:(UIScene*)scene for (NSObject* delegate in _delegates.allObjects) { if ([delegate respondsToSelector:_cmd]) { // If this event has already been consumed by a plugin, send the event with nil options. - if (consumedByPlugin) { - [delegate scene:scene willConnectToSession:session options:nil]; - continue; - } else if ([delegate scene:scene willConnectToSession:session options:connectionOptions]) { - // Only allow one plugin to process this event. + // Only allow one plugin to process the connection options. + if ([delegate scene:scene + willConnectToSession:session + options:(consumedByPlugin ? nil : connectionOptions)]) { consumedByPlugin = YES; } } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm index 3c27236a2b880..4c4ffb7762295 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm @@ -5,6 +5,7 @@ #import #import #import +#include #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h" @@ -13,6 +14,24 @@ FLUTTER_ASSERT_ARC +// This class is meant to mimic the SceneDelegate class generated by SwiftUI that does not conform +// FlutterSceneLifeCycleProvider but actually does. +@interface FlutterSwiftUIAppSceneDelegate : NSObject +@property(nonatomic, strong) FlutterPluginSceneLifeCycleDelegate* sceneLifeCycleDelegate; +@end + +@implementation FlutterSwiftUIAppSceneDelegate + +@synthesize sceneLifeCycleDelegate = _sceneLifeCycleDelegate; + +- (instancetype)init { + if (self = [super init]) { + _sceneLifeCycleDelegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + } + return self; +} +@end + @interface FlutterSceneLifecycleTest : XCTestCase @end @@ -799,4 +818,15 @@ - (void)testEngineWindowScenePerformActionForShortcutItem { completionHandler:handler]); } +- (void)testFlutterPluginSceneLifeCycleDelegateFromScene { + id mockScene = OCMClassMock([UIWindowScene class]); + id mockSceneDelegate = OCMClassMock([FlutterSwiftUIAppSceneDelegate class]); + id mockSceneLifeCycleDelegate = OCMClassMock([FlutterPluginSceneLifeCycleDelegate class]); + + OCMStub([mockScene delegate]).andReturn(mockSceneDelegate); + OCMStub([mockSceneDelegate sceneLifeCycleDelegate]).andReturn(mockSceneLifeCycleDelegate); + + XCTAssertEqual([FlutterPluginSceneLifeCycleDelegate fromScene:mockScene], + mockSceneLifeCycleDelegate); +} @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h index e404d6eee61a5..c88a1be3607e8 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h @@ -20,6 +20,8 @@ - (void)engine:(FlutterEngine*)engine receivedConnectNotificationFor:(UIScene*)scene; ++ (FlutterPluginSceneLifeCycleDelegate*)fromScene:(UIScene*)scene; + @end /** diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm index 3a46e9144397c..8262b6733b2c8 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm @@ -250,21 +250,21 @@ - (void)willMoveToWindow:(UIWindow*)newWindow { if (newScene == previousScene) { return; } - if ([newScene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) { - id lifeCycleProvider = - (id)newScene.delegate; - [lifeCycleProvider.sceneLifeCycleDelegate addFlutterEngine:(FlutterEngine*)self.delegate]; + FlutterPluginSceneLifeCycleDelegate* newSceneLifeCycleDelegate = + [FlutterPluginSceneLifeCycleDelegate fromScene:newScene]; + if (newSceneLifeCycleDelegate != nil) { + return [newSceneLifeCycleDelegate addFlutterEngine:(FlutterEngine*)self.delegate]; } - if ([previousScene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) { - // The window, and therefore windowScene, property may be nil if the receiver does not currently - // reside in any window. This occurs when the receiver has just been removed from its superview - // or when the receiver has just been added to a superview that is not attached to a window. - // Remove the engine from the previous scene if set since it is no longer in that window and - // scene. - id lifeCycleProvider = - (id)previousScene.delegate; - [lifeCycleProvider.sceneLifeCycleDelegate removeFlutterEngine:(FlutterEngine*)self.delegate]; + // The window, and therefore windowScene, property may be nil if the receiver does not currently + // reside in any window. This occurs when the receiver has just been removed from its superview + // or when the receiver has just been added to a superview that is not attached to a window. + // Remove the engine from the previous scene if set since it is no longer in that window and + // scene. + FlutterPluginSceneLifeCycleDelegate* previousSceneLifeCycleDelegate = + [FlutterPluginSceneLifeCycleDelegate fromScene:previousScene]; + if (previousSceneLifeCycleDelegate != nil) { + return [previousSceneLifeCycleDelegate removeFlutterEngine:(FlutterEngine*)self.delegate]; } } From 7519cfd2515c56af11540c51d3377fb29a4f141e Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 1 Oct 2025 11:44:18 -0400 Subject: [PATCH 022/204] Roll Packages from 287739d0acce to 321a5846838d (3 revisions) (#176357) https://github.com/flutter/packages/compare/287739d0acce...321a5846838d 2025-09-30 stuartmorgan@google.com [local_auth] Adopt structured errors - platform interface (flutter/packages#10023) 2025-09-30 jessiewong401@gmail.com Revert hardcoded compilesdk back to `flutter.compileSdkVersion` in `camera_android` (flutter/packages#9668) 2025-09-30 mohellebiabdessalem@gmail.com [file_selector] updates build files to use JVM 17 (flutter/packages#10097) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC flutter-ecosystem@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- bin/internal/flutter_packages.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/flutter_packages.version b/bin/internal/flutter_packages.version index fae1bdd6a5489..5818c12807e6d 100644 --- a/bin/internal/flutter_packages.version +++ b/bin/internal/flutter_packages.version @@ -1 +1 @@ -287739d0accebbcd6459dfa86f559916db793318 +321a5846838d2bfa5f3dd463e3833b951da0db32 From 77a581a19e06aec23052ff61f51eb78489087896 Mon Sep 17 00:00:00 2001 From: Matej Knopp Date: Wed, 1 Oct 2025 19:19:14 +0200 Subject: [PATCH 023/204] [win32] Runloop should use high resolution timer and avoid deadlock (#176023) Replaces `SetTimer`/`WM_TIMER` with a threadpool timer, which doesn't suffer from 16ms granularity and changes the waking up mechanism to give the run loop chance to process input messages before processing flutter tasks. - Fixes https://github.com/flutter/flutter/issues/173843 - Fixes https://github.com/flutter/flutter/issues/175135 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../flutter_windows_engine_unittests.cc | 70 +++++++++++++++++++ .../platform/windows/task_runner_window.cc | 69 ++++++++++++++---- .../platform/windows/task_runner_window.h | 8 +++ 3 files changed, 134 insertions(+), 13 deletions(-) diff --git a/engine/src/flutter/shell/platform/windows/flutter_windows_engine_unittests.cc b/engine/src/flutter/shell/platform/windows/flutter_windows_engine_unittests.cc index a6976f993a993..7f6dd78a18dd6 100644 --- a/engine/src/flutter/shell/platform/windows/flutter_windows_engine_unittests.cc +++ b/engine/src/flutter/shell/platform/windows/flutter_windows_engine_unittests.cc @@ -64,6 +64,76 @@ TEST_F(FlutterWindowsEngineTest, RunHeadless) { ASSERT_EQ(engine->view(123), nullptr); } +TEST_F(FlutterWindowsEngineTest, TaskRunnerDelayedTask) { + bool finished = false; + auto runner = std::make_unique( + [] { + return static_cast( + fml::TimePoint::Now().ToEpochDelta().ToNanoseconds()); + }, + [&](const FlutterTask*) { finished = true; }); + runner->PostFlutterTask( + FlutterTask{}, + static_cast((fml::TimePoint::Now().ToEpochDelta() + + fml::TimeDelta::FromMilliseconds(50)) + .ToNanoseconds())); + auto start = fml::TimePoint::Now(); + while (!finished) { + PumpMessage(); + } + auto duration = fml::TimePoint::Now() - start; + EXPECT_GE(duration, fml::TimeDelta::FromMilliseconds(50)); +} + +// https://github.com/flutter/flutter/issues/173843) +TEST_F(FlutterWindowsEngineTest, TaskRunnerDoesNotDeadlock) { + auto runner = std::make_unique( + [] { + return static_cast( + fml::TimePoint::Now().ToEpochDelta().ToNanoseconds()); + }, + [&](const FlutterTask*) {}); + + struct RunnerHolder { + void PostTaskLoop() { + runner->PostTask([this] { PostTaskLoop(); }); + } + std::unique_ptr runner; + }; + + RunnerHolder container{.runner = std::move(runner)}; + // Spam flutter tasks. + container.PostTaskLoop(); + + const LPCWSTR class_name = L"FlutterTestWindowClass"; + WNDCLASS wc = {0}; + wc.lpfnWndProc = DefWindowProc; + wc.lpszClassName = class_name; + RegisterClass(&wc); + + HWND window; + container.runner->PostTask([&] { + window = CreateWindowEx(0, class_name, L"Empty Window", WS_OVERLAPPEDWINDOW, + CW_USEDEFAULT, CW_USEDEFAULT, 800, 600, nullptr, + nullptr, nullptr, nullptr); + ShowWindow(window, SW_SHOW); + }); + + while (true) { + ::MSG msg; + if (::GetMessage(&msg, nullptr, 0, 0)) { + if (msg.message == WM_PAINT) { + break; + } + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + } + + DestroyWindow(window); + UnregisterClassW(class_name, nullptr); +} + TEST_F(FlutterWindowsEngineTest, RunDoesExpectedInitialization) { FlutterWindowsEngineBuilder builder{GetContext()}; builder.AddDartEntrypointArgument("arg1"); diff --git a/engine/src/flutter/shell/platform/windows/task_runner_window.cc b/engine/src/flutter/shell/platform/windows/task_runner_window.cc index 88369ca434700..a0cb9e25d4d7c 100644 --- a/engine/src/flutter/shell/platform/windows/task_runner_window.cc +++ b/engine/src/flutter/shell/platform/windows/task_runner_window.cc @@ -4,14 +4,14 @@ #include "flutter/shell/platform/windows/task_runner_window.h" +#include #include +#include #include "flutter/fml/logging.h" namespace flutter { -static const uintptr_t kTimerId = 0; - // Timer used for PollOnce timeout. static const uintptr_t kPollTimeoutTimerId = 1; @@ -21,6 +21,13 @@ TaskRunnerWindow::TaskRunnerWindow() { CreateWindowEx(0, window_class.lpszClassName, L"", 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, window_class.hInstance, nullptr); + timer_ = CreateThreadpoolTimer(TimerProc, this, nullptr); + if (!timer_) { + FML_LOG(ERROR) << "Failed to create threadpool timer, error: " + << GetLastError(); + FML_CHECK(timer_); + } + if (window_handle_) { SetWindowLongPtr(window_handle_, GWLP_USERDATA, reinterpret_cast(this)); @@ -35,14 +42,40 @@ TaskRunnerWindow::TaskRunnerWindow() { OutputDebugString(message); LocalFree(message); } + + thread_id_ = GetCurrentThreadId(); + + // Increase timer precision for this process (the call only affects + // current process since Windows 10, version 2004). + timeBeginPeriod(1); } TaskRunnerWindow::~TaskRunnerWindow() { + SetThreadpoolTimer(timer_, nullptr, 0, 0); + // Ensures that no callbacks will run after CloseThreadpoolTimer. + // https://learn.microsoft.com/en-us/windows/win32/api/threadpoolapiset/nf-threadpoolapiset-closethreadpooltimer#remarks + WaitForThreadpoolTimerCallbacks(timer_, TRUE); + CloseThreadpoolTimer(timer_); + if (window_handle_) { DestroyWindow(window_handle_); window_handle_ = nullptr; } UnregisterClass(window_class_name_.c_str(), nullptr); + + timeEndPeriod(1); +} + +void TaskRunnerWindow::OnTimer() { + if (!PostMessage(window_handle_, WM_NULL, 0, 0)) { + FML_LOG(ERROR) << "Failed to post message to main thread."; + } +} + +void TaskRunnerWindow::TimerProc(PTP_CALLBACK_INSTANCE instance, + PVOID context, + PTP_TIMER timer) { + reinterpret_cast(context)->OnTimer(); } std::shared_ptr TaskRunnerWindow::GetSharedInstance() { @@ -57,6 +90,18 @@ std::shared_ptr TaskRunnerWindow::GetSharedInstance() { } void TaskRunnerWindow::WakeUp() { + // When waking up from main thread while there are messages in the message + // queue use timer to post the WM_NULL message from background thread. This + // gives message loop chance to process input events before WM_NULL is + // processed - which is necessary because messages scheduled through + // PostMessage take precedence over input event messages. Otherwise await + // Future.delayed(Duration.zero) deadlocks the main thread. (See + // https://github.com/flutter/flutter/issues/173843) + if (thread_id_ == GetCurrentThreadId() && GetQueueStatus(QS_ALLEVENTS) != 0) { + SetTimer(std::chrono::nanoseconds::zero()); + return; + } + if (!PostMessage(window_handle_, WM_NULL, 0, 0)) { FML_LOG(ERROR) << "Failed to post message to main thread."; } @@ -99,10 +144,16 @@ void TaskRunnerWindow::ProcessTasks() { void TaskRunnerWindow::SetTimer(std::chrono::nanoseconds when) { if (when == std::chrono::nanoseconds::max()) { - KillTimer(window_handle_, kTimerId); + SetThreadpoolTimer(timer_, nullptr, 0, 0); } else { - auto millis = std::chrono::duration_cast(when); - ::SetTimer(window_handle_, kTimerId, millis.count() + 1, nullptr); + auto microseconds = + std::chrono::duration_cast(when).count(); + ULARGE_INTEGER ticks; + ticks.QuadPart = -static_cast(microseconds * 10); + FILETIME ft; + ft.dwLowDateTime = ticks.LowPart; + ft.dwHighDateTime = ticks.HighPart; + SetThreadpoolTimer(timer_, &ft, 0, 0); } } @@ -129,14 +180,6 @@ TaskRunnerWindow::HandleMessage(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { switch (message) { - case WM_TIMER: - if (wparam == kPollTimeoutTimerId) { - // Ignore PollOnce timeout timer. - return 0; - } - FML_DCHECK(wparam == kTimerId); - ProcessTasks(); - return 0; case WM_NULL: ProcessTasks(); return 0; diff --git a/engine/src/flutter/shell/platform/windows/task_runner_window.h b/engine/src/flutter/shell/platform/windows/task_runner_window.h index 1544de5fbc9e0..8e4dc2ba437d3 100644 --- a/engine/src/flutter/shell/platform/windows/task_runner_window.h +++ b/engine/src/flutter/shell/platform/windows/task_runner_window.h @@ -59,9 +59,17 @@ class TaskRunnerWindow { WPARAM const wparam, LPARAM const lparam) noexcept; + void OnTimer(); + + static void TimerProc(PTP_CALLBACK_INSTANCE Instance, + PVOID Context, + PTP_TIMER Timer); + HWND window_handle_; std::wstring window_class_name_; std::vector delegates_; + PTP_TIMER timer_ = nullptr; + DWORD thread_id_ = 0; FML_DISALLOW_COPY_AND_ASSIGN(TaskRunnerWindow); }; From f973b2f13c7b84dd7437f6d8405a65e62be0645a Mon Sep 17 00:00:00 2001 From: Valentin Vignal <32538273+ValentinVignal@users.noreply.github.com> Date: Thu, 2 Oct 2025 02:54:58 +0800 Subject: [PATCH 024/204] Migrate to `WidgetStateOutlinedBorder` (#176270) Follow up https://github.com/flutter/flutter/pull/176164 Migrate the remaining files from `MaterialStateOutlinedBorder ` to `WidgetStateOutlinedBorder `. This PR only focus on `WidgetStateOutlinedBorder`. - This minimizes conflicts and reduces the size of the PR for easier reviews and follow up - I'll work on the other elements of `packages/flutter/lib/src/material/material_state.dart` into other PRs ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../widget_state_outlined_border.0.dart} | 21 +++++++++---------- .../widget_state_outlined_border.0_test.dart} | 6 +++--- .../lib/src/material/material_state.dart | 2 +- .../flutter/lib/src/widgets/widget_state.dart | 4 +--- packages/flutter/test/material/chip_test.dart | 12 +++++------ .../test/material/chip_theme_test.dart | 8 +++---- 6 files changed, 25 insertions(+), 28 deletions(-) rename examples/api/lib/{material/material_state/material_state_outlined_border.0.dart => widgets/widget_state/widget_state_outlined_border.0.dart} (54%) rename examples/api/test/{material/material_state/material_state_outlined_border.0_test.dart => widgets/widget_state/widget_state_outlined_border.0_test.dart} (82%) diff --git a/examples/api/lib/material/material_state/material_state_outlined_border.0.dart b/examples/api/lib/widgets/widget_state/widget_state_outlined_border.0.dart similarity index 54% rename from examples/api/lib/material/material_state/material_state_outlined_border.0.dart rename to examples/api/lib/widgets/widget_state/widget_state_outlined_border.0.dart index 5a95fb77cc4e9..e7a7f9a507a20 100644 --- a/examples/api/lib/material/material_state/material_state_outlined_border.0.dart +++ b/examples/api/lib/widgets/widget_state/widget_state_outlined_border.0.dart @@ -4,20 +4,20 @@ import 'package:flutter/material.dart'; -/// Flutter code sample for [MaterialStateOutlinedBorder]. +/// Flutter code sample for [WidgetStateOutlinedBorder]. -void main() => runApp(const MaterialStateOutlinedBorderExampleApp()); +void main() => runApp(const WidgetStateOutlinedBorderExampleApp()); -class MaterialStateOutlinedBorderExampleApp extends StatelessWidget { - const MaterialStateOutlinedBorderExampleApp({super.key}); +class WidgetStateOutlinedBorderExampleApp extends StatelessWidget { + const WidgetStateOutlinedBorderExampleApp({super.key}); @override Widget build(BuildContext context) { - return const MaterialApp(home: MaterialStateOutlinedBorderExample()); + return const MaterialApp(home: WidgetStateOutlinedBorderExample()); } } -class SelectedBorder extends RoundedRectangleBorder implements MaterialStateOutlinedBorder { +class SelectedBorder extends RoundedRectangleBorder implements WidgetStateOutlinedBorder { const SelectedBorder(); @override @@ -29,15 +29,14 @@ class SelectedBorder extends RoundedRectangleBorder implements MaterialStateOutl } } -class MaterialStateOutlinedBorderExample extends StatefulWidget { - const MaterialStateOutlinedBorderExample({super.key}); +class WidgetStateOutlinedBorderExample extends StatefulWidget { + const WidgetStateOutlinedBorderExample({super.key}); @override - State createState() => - _MaterialStateOutlinedBorderExampleState(); + State createState() => _WidgetStateOutlinedBorderExampleState(); } -class _MaterialStateOutlinedBorderExampleState extends State { +class _WidgetStateOutlinedBorderExampleState extends State { bool isSelected = true; @override diff --git a/examples/api/test/material/material_state/material_state_outlined_border.0_test.dart b/examples/api/test/widgets/widget_state/widget_state_outlined_border.0_test.dart similarity index 82% rename from examples/api/test/material/material_state/material_state_outlined_border.0_test.dart rename to examples/api/test/widgets/widget_state/widget_state_outlined_border.0_test.dart index 41d981d65a0d4..f3c37335df0b8 100644 --- a/examples/api/test/material/material_state/material_state_outlined_border.0_test.dart +++ b/examples/api/test/widgets/widget_state/widget_state_outlined_border.0_test.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:flutter_api_samples/material/material_state/material_state_outlined_border.0.dart' +import 'package:flutter_api_samples/widgets/widget_state/widget_state_outlined_border.0.dart' as example; import 'package:flutter_test/flutter_test.dart'; @@ -21,7 +21,7 @@ void main() { } testWidgets('FilterChip displays the correct border when selected', (WidgetTester tester) async { - await tester.pumpWidget(const example.MaterialStateOutlinedBorderExampleApp()); + await tester.pumpWidget(const example.WidgetStateOutlinedBorderExampleApp()); expect( findBorderShape(const RoundedRectangleBorder(side: BorderSide(color: Colors.transparent))), @@ -32,7 +32,7 @@ void main() { testWidgets('FilterChip displays the correct border when not selected', ( WidgetTester tester, ) async { - await tester.pumpWidget(const example.MaterialStateOutlinedBorderExampleApp()); + await tester.pumpWidget(const example.WidgetStateOutlinedBorderExampleApp()); await tester.tap(find.byType(FilterChip)); await tester.pumpAndSettle(); diff --git a/packages/flutter/lib/src/material/material_state.dart b/packages/flutter/lib/src/material/material_state.dart index 17ecc5c7a62ee..c9fa4f7285fdf 100644 --- a/packages/flutter/lib/src/material/material_state.dart +++ b/packages/flutter/lib/src/material/material_state.dart @@ -222,7 +222,7 @@ typedef MaterialStateBorderSide = WidgetStateBorderSide; /// implementation of [MaterialStateOutlinedBorder], that resolves to /// [RoundedRectangleBorder] when its widget is selected. /// -/// ** See code in examples/api/lib/material/material_state/material_state_outlined_border.0.dart ** +/// ** See code in examples/api/lib/widgets/widget_state/widget_state_outlined_border.0.dart ** /// {@end-tool} /// /// This class should only be used for parameters which are documented to take diff --git a/packages/flutter/lib/src/widgets/widget_state.dart b/packages/flutter/lib/src/widgets/widget_state.dart index 20c8550885149..27b389bfbe8ac 100644 --- a/packages/flutter/lib/src/widgets/widget_state.dart +++ b/packages/flutter/lib/src/widgets/widget_state.dart @@ -638,7 +638,7 @@ class _WidgetBorderSideMapper extends WidgetStateMapper /// implementation of [WidgetStateOutlinedBorder], that resolves to /// [RoundedRectangleBorder] when its widget is selected. /// -/// ** See code in examples/api/lib/material/material_state/material_state_outlined_border.0.dart ** +/// ** See code in examples/api/lib/widgets/widget_state/widget_state_outlined_border.0.dart ** /// {@end-tool} /// /// This class should only be used for parameters which are documented to take @@ -647,8 +647,6 @@ class _WidgetBorderSideMapper extends WidgetStateMapper /// See also: /// /// * [ShapeBorder] the base class for shape outlines. -/// * [MaterialStateOutlinedBorder], the Material specific version of -/// `WidgetStateOutlinedBorder`. abstract class WidgetStateOutlinedBorder extends OutlinedBorder implements WidgetStateProperty { /// Abstract const constructor. This constructor enables subclasses to provide diff --git a/packages/flutter/test/material/chip_test.dart b/packages/flutter/test/material/chip_test.dart index 5788bff4c749b..819e647ce677f 100644 --- a/packages/flutter/test/material/chip_test.dart +++ b/packages/flutter/test/material/chip_test.dart @@ -4290,7 +4290,7 @@ void main() { child: ChoiceChip( selected: selected, label: const Text('Chip'), - shape: _MaterialStateOutlinedBorder(getShape), + shape: _TestWidgetStateOutlinedBorder(getShape), onSelected: enabled ? (_) {} : null, ), ), @@ -4360,7 +4360,7 @@ void main() { child: ChoiceChip( selected: selected, label: const Text('Chip'), - shape: _MaterialStateOutlinedBorder(getShape), + shape: _TestWidgetStateOutlinedBorder(getShape), onSelected: enabled ? (_) {} : null, ), ), @@ -4433,7 +4433,7 @@ void main() { body: ChoiceChip( selected: selected, label: const Text('Chip'), - shape: _MaterialStateOutlinedBorder(getShape), + shape: _TestWidgetStateOutlinedBorder(getShape), side: _TestWidgetStateBorderSide(getBorderSide), onSelected: enabled ? (_) {} : null, ), @@ -4493,7 +4493,7 @@ void main() { body: ChoiceChip( selected: selected, label: const Text('Chip'), - shape: _MaterialStateOutlinedBorder(getShape), + shape: _TestWidgetStateOutlinedBorder(getShape), side: _TestWidgetStateBorderSide(getBorderSide), onSelected: enabled ? (_) {} : null, ), @@ -6445,8 +6445,8 @@ void main() { }); } -class _MaterialStateOutlinedBorder extends StadiumBorder implements MaterialStateOutlinedBorder { - const _MaterialStateOutlinedBorder(this.resolver); +class _TestWidgetStateOutlinedBorder extends StadiumBorder implements WidgetStateOutlinedBorder { + const _TestWidgetStateOutlinedBorder(this.resolver); final WidgetPropertyResolver resolver; diff --git a/packages/flutter/test/material/chip_theme_test.dart b/packages/flutter/test/material/chip_theme_test.dart index bd65b788e1b55..ca04ad7b4ba36 100644 --- a/packages/flutter/test/material/chip_theme_test.dart +++ b/packages/flutter/test/material/chip_theme_test.dart @@ -1045,7 +1045,7 @@ void main() { brightness: Brightness.light, secondaryColor: Colors.blue, labelStyle: const TextStyle(), - ).copyWith(shape: _MaterialStateOutlinedBorder(getShape)); + ).copyWith(shape: _TestWidgetStateOutlinedBorder(getShape)); Widget chipWidget({bool selected = false}) { return MaterialApp( @@ -1073,7 +1073,7 @@ void main() { return null; } - final ChipThemeData chipTheme = ChipThemeData(shape: _MaterialStateOutlinedBorder(getShape)); + final ChipThemeData chipTheme = ChipThemeData(shape: _TestWidgetStateOutlinedBorder(getShape)); Widget chipWidget({bool selected = false}) { return MaterialApp( @@ -1526,8 +1526,8 @@ void main() { }); } -class _MaterialStateOutlinedBorder extends StadiumBorder implements MaterialStateOutlinedBorder { - const _MaterialStateOutlinedBorder(this.resolver); +class _TestWidgetStateOutlinedBorder extends StadiumBorder implements WidgetStateOutlinedBorder { + const _TestWidgetStateOutlinedBorder(this.resolver); final WidgetPropertyResolver resolver; From 7811e8982355a88cb964671bd7d711b3a04540f0 Mon Sep 17 00:00:00 2001 From: Valentin Vignal <32538273+ValentinVignal@users.noreply.github.com> Date: Thu, 2 Oct 2025 02:55:00 +0800 Subject: [PATCH 025/204] Migrate to `WidgetStateTextStyle` (#176330) Follow up https://github.com/flutter/flutter/pull/176270 Migrate the remaining files from `MaterialStateTextStyle ` to `WidgetStateTextStyle `. This PR only focus on `WidgetStateTextStyle`. - This minimizes conflicts and reduces the size of the PR for easier reviews and follow up - I'll work on the other elements of `packages/flutter/lib/src/material/material_state.dart` into other PRs If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../lib/input_decorator_template.dart | 10 ++--- .../gen_defaults/lib/text_field_template.dart | 2 +- .../lib/time_picker_template.dart | 4 +- .../lib/src/material/input_decorator.dart | 43 +++++++++---------- .../flutter/lib/src/material/text_field.dart | 4 +- .../flutter/lib/src/material/time_picker.dart | 4 +- .../flutter/lib/src/widgets/widget_state.dart | 5 --- .../test/material/input_decorator_test.dart | 4 +- .../test/material/text_field_test.dart | 2 +- 9 files changed, 36 insertions(+), 42 deletions(-) diff --git a/dev/tools/gen_defaults/lib/input_decorator_template.dart b/dev/tools/gen_defaults/lib/input_decorator_template.dart index 8c1f9e0f9861b..3d879483c4f8c 100644 --- a/dev/tools/gen_defaults/lib/input_decorator_template.dart +++ b/dev/tools/gen_defaults/lib/input_decorator_template.dart @@ -36,7 +36,7 @@ class _${blockName}DefaultsM3 extends InputDecorationThemeData { // see https://github.com/flutter/flutter/pull/125905. @override - TextStyle? get hintStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get hintStyle => WidgetStateTextStyle.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return TextStyle(color: ${componentColor('md.comp.filled-text-field.disabled.supporting-text')}); } @@ -147,7 +147,7 @@ class _${blockName}DefaultsM3 extends InputDecorationThemeData { }); @override - TextStyle? get labelStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get labelStyle => WidgetStateTextStyle.resolveWith((Set states) { final TextStyle textStyle = ${textStyle("md.comp.filled-text-field.label-text")} ?? const TextStyle(); if (states.contains(WidgetState.disabled)) { return textStyle.copyWith(color: ${componentColor('md.comp.filled-text-field.disabled.label-text')}); @@ -171,7 +171,7 @@ class _${blockName}DefaultsM3 extends InputDecorationThemeData { }); @override - TextStyle? get floatingLabelStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get floatingLabelStyle => WidgetStateTextStyle.resolveWith((Set states) { final TextStyle textStyle = ${textStyle("md.comp.filled-text-field.label-text")} ?? const TextStyle(); if (states.contains(WidgetState.disabled)) { return textStyle.copyWith(color: ${componentColor('md.comp.filled-text-field.disabled.label-text')}); @@ -195,7 +195,7 @@ class _${blockName}DefaultsM3 extends InputDecorationThemeData { }); @override - TextStyle? get helperStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get helperStyle => WidgetStateTextStyle.resolveWith((Set states) { final TextStyle textStyle = ${textStyle("md.comp.filled-text-field.supporting-text")} ?? const TextStyle(); if (states.contains(WidgetState.disabled)) { return textStyle.copyWith(color: ${componentColor('md.comp.filled-text-field.disabled.supporting-text')}); @@ -210,7 +210,7 @@ class _${blockName}DefaultsM3 extends InputDecorationThemeData { }); @override - TextStyle? get errorStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get errorStyle => WidgetStateTextStyle.resolveWith((Set states) { final TextStyle textStyle = ${textStyle("md.comp.filled-text-field.supporting-text")} ?? const TextStyle();${componentColor('md.comp.filled-text-field.error.hover.supporting-text') == componentColor('md.comp.filled-text-field.error.supporting-text') ? '' : ''' if (states.contains(WidgetState.focused)) { return textStyle.copyWith(color: ${componentColor('md.comp.filled-text-field.error.focus.supporting-text')}); diff --git a/dev/tools/gen_defaults/lib/text_field_template.dart b/dev/tools/gen_defaults/lib/text_field_template.dart index b9924b76fc685..b250e272b8447 100644 --- a/dev/tools/gen_defaults/lib/text_field_template.dart +++ b/dev/tools/gen_defaults/lib/text_field_template.dart @@ -10,7 +10,7 @@ class TextFieldTemplate extends TokenTemplate { @override String generate() => ''' -TextStyle? _m3StateInputStyle(BuildContext context) => MaterialStateTextStyle.resolveWith((Set states) { +TextStyle? _m3StateInputStyle(BuildContext context) => WidgetStateTextStyle.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return TextStyle(color: ${textStyle("md.comp.filled-text-field.label-text")}!.color?.withOpacity(0.38)); } diff --git a/dev/tools/gen_defaults/lib/time_picker_template.dart b/dev/tools/gen_defaults/lib/time_picker_template.dart index b525958dc4475..2bad15921b8a1 100644 --- a/dev/tools/gen_defaults/lib/time_picker_template.dart +++ b/dev/tools/gen_defaults/lib/time_picker_template.dart @@ -176,7 +176,7 @@ class _${blockName}DefaultsM3 extends _TimePickerDefaults { @override TextStyle get helpTextStyle { - return MaterialStateTextStyle.resolveWith((Set states) { + return WidgetStateTextStyle.resolveWith((Set states) { final TextStyle textStyle = ${textStyle('$tokenGroup.headline')}!; return textStyle.copyWith(color: ${componentColor('$tokenGroup.headline')}); }); @@ -285,7 +285,7 @@ class _${blockName}DefaultsM3 extends _TimePickerDefaults { @override TextStyle get hourMinuteTextStyle { - return MaterialStateTextStyle.resolveWith((Set states) { + return WidgetStateTextStyle.resolveWith((Set states) { // TODO(tahatesser): Update this when https://github.com/flutter/flutter/issues/131247 is fixed. // This is using the correct text style from Material 3 spec. // https://m3.material.io/components/time-pickers/specs#fd0b6939-edab-4058-82e1-93d163945215 diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 0e07356a9605e..1e984d4c1b308 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -5779,7 +5779,7 @@ class _InputDecoratorDefaultsM2 extends InputDecorationThemeData { final BuildContext context; @override - TextStyle? get hintStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get hintStyle => WidgetStateTextStyle.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return TextStyle(color: Theme.of(context).disabledColor); } @@ -5787,7 +5787,7 @@ class _InputDecoratorDefaultsM2 extends InputDecorationThemeData { }); @override - TextStyle? get labelStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get labelStyle => WidgetStateTextStyle.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return TextStyle(color: Theme.of(context).disabledColor); } @@ -5795,22 +5795,21 @@ class _InputDecoratorDefaultsM2 extends InputDecorationThemeData { }); @override - TextStyle? get floatingLabelStyle => - MaterialStateTextStyle.resolveWith((Set states) { - if (states.contains(WidgetState.disabled)) { - return TextStyle(color: Theme.of(context).disabledColor); - } - if (states.contains(WidgetState.error)) { - return TextStyle(color: Theme.of(context).colorScheme.error); - } - if (states.contains(WidgetState.focused)) { - return TextStyle(color: Theme.of(context).colorScheme.primary); - } - return TextStyle(color: Theme.of(context).hintColor); - }); + TextStyle? get floatingLabelStyle => WidgetStateTextStyle.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return TextStyle(color: Theme.of(context).disabledColor); + } + if (states.contains(WidgetState.error)) { + return TextStyle(color: Theme.of(context).colorScheme.error); + } + if (states.contains(WidgetState.focused)) { + return TextStyle(color: Theme.of(context).colorScheme.primary); + } + return TextStyle(color: Theme.of(context).hintColor); + }); @override - TextStyle? get helperStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get helperStyle => WidgetStateTextStyle.resolveWith((Set states) { final ThemeData themeData = Theme.of(context); if (states.contains(WidgetState.disabled)) { return themeData.textTheme.bodySmall!.copyWith(color: Colors.transparent); @@ -5820,7 +5819,7 @@ class _InputDecoratorDefaultsM2 extends InputDecorationThemeData { }); @override - TextStyle? get errorStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get errorStyle => WidgetStateTextStyle.resolveWith((Set states) { final ThemeData themeData = Theme.of(context); if (states.contains(WidgetState.disabled)) { return themeData.textTheme.bodySmall!.copyWith(color: Colors.transparent); @@ -5912,7 +5911,7 @@ class _InputDecoratorDefaultsM3 extends InputDecorationThemeData { // see https://github.com/flutter/flutter/pull/125905. @override - TextStyle? get hintStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get hintStyle => WidgetStateTextStyle.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return TextStyle(color: _colors.onSurface.withOpacity(0.38)); } @@ -5999,7 +5998,7 @@ class _InputDecoratorDefaultsM3 extends InputDecorationThemeData { }); @override - TextStyle? get labelStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get labelStyle => WidgetStateTextStyle.resolveWith((Set states) { final TextStyle textStyle = _textTheme.bodyLarge ?? const TextStyle(); if (states.contains(WidgetState.disabled)) { return textStyle.copyWith(color: _colors.onSurface.withOpacity(0.38)); @@ -6023,7 +6022,7 @@ class _InputDecoratorDefaultsM3 extends InputDecorationThemeData { }); @override - TextStyle? get floatingLabelStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get floatingLabelStyle => WidgetStateTextStyle.resolveWith((Set states) { final TextStyle textStyle = _textTheme.bodyLarge ?? const TextStyle(); if (states.contains(WidgetState.disabled)) { return textStyle.copyWith(color: _colors.onSurface.withOpacity(0.38)); @@ -6047,7 +6046,7 @@ class _InputDecoratorDefaultsM3 extends InputDecorationThemeData { }); @override - TextStyle? get helperStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get helperStyle => WidgetStateTextStyle.resolveWith((Set states) { final TextStyle textStyle = _textTheme.bodySmall ?? const TextStyle(); if (states.contains(WidgetState.disabled)) { return textStyle.copyWith(color: _colors.onSurface.withOpacity(0.38)); @@ -6056,7 +6055,7 @@ class _InputDecoratorDefaultsM3 extends InputDecorationThemeData { }); @override - TextStyle? get errorStyle => MaterialStateTextStyle.resolveWith((Set states) { + TextStyle? get errorStyle => WidgetStateTextStyle.resolveWith((Set states) { final TextStyle textStyle = _textTheme.bodySmall ?? const TextStyle(); return textStyle.copyWith(color: _colors.error); }); diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 1f1e3bb879fcc..ee7f3c3ffbe4e 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -1853,7 +1853,7 @@ class _TextFieldState extends State } TextStyle? _m2StateInputStyle(BuildContext context) => - MaterialStateTextStyle.resolveWith((Set states) { + WidgetStateTextStyle.resolveWith((Set states) { final ThemeData theme = Theme.of(context); if (states.contains(WidgetState.disabled)) { return TextStyle(color: theme.disabledColor); @@ -1872,7 +1872,7 @@ TextStyle _m2CounterErrorStyle(BuildContext context) => // dev/tools/gen_defaults/bin/gen_defaults.dart. // dart format off -TextStyle? _m3StateInputStyle(BuildContext context) => MaterialStateTextStyle.resolveWith((Set states) { +TextStyle? _m3StateInputStyle(BuildContext context) => WidgetStateTextStyle.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return TextStyle(color: Theme.of(context).textTheme.bodyLarge!.color?.withOpacity(0.38)); } diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index 3ae85d8c17177..0d69d21953043 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -3786,7 +3786,7 @@ class _TimePickerDefaultsM3 extends _TimePickerDefaults { @override TextStyle get helpTextStyle { - return MaterialStateTextStyle.resolveWith((Set states) { + return WidgetStateTextStyle.resolveWith((Set states) { final TextStyle textStyle = _textTheme.labelMedium!; return textStyle.copyWith(color: _colors.onSurfaceVariant); }); @@ -3895,7 +3895,7 @@ class _TimePickerDefaultsM3 extends _TimePickerDefaults { @override TextStyle get hourMinuteTextStyle { - return MaterialStateTextStyle.resolveWith((Set states) { + return WidgetStateTextStyle.resolveWith((Set states) { // TODO(tahatesser): Update this when https://github.com/flutter/flutter/issues/131247 is fixed. // This is using the correct text style from Material 3 spec. // https://m3.material.io/components/time-pickers/specs#fd0b6939-edab-4058-82e1-93d163945215 diff --git a/packages/flutter/lib/src/widgets/widget_state.dart b/packages/flutter/lib/src/widgets/widget_state.dart index 27b389bfbe8ac..c199d9568344e 100644 --- a/packages/flutter/lib/src/widgets/widget_state.dart +++ b/packages/flutter/lib/src/widgets/widget_state.dart @@ -715,11 +715,6 @@ class _WidgetOutlinedBorderMapper extends WidgetStateMapper /// 2. Use [WidgetStateTextStyle.resolveWith] and pass in a callback that /// will be used to resolve the text style in the given states. /// 3. Use [WidgetStateTextStyle.fromMap] to assign a style using a [WidgetStateMap]. -/// -/// See also: -/// -/// * [MaterialStateTextStyle], the Material specific version of -/// `WidgetStateTextStyle`. abstract class WidgetStateTextStyle extends TextStyle implements WidgetStateProperty { /// Abstract const constructor. This constructor enables subclasses to provide /// const constructors so that they can be used in const expressions. diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index 95ff4b73c4f31..8ed040a535840 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -8404,13 +8404,13 @@ void main() { testWidgets('InputDecorationThemeData.inputDecoration with WidgetState', ( WidgetTester tester, ) async { - final MaterialStateTextStyle themeStyle = MaterialStateTextStyle.resolveWith(( + final WidgetStateTextStyle themeStyle = WidgetStateTextStyle.resolveWith(( Set states, ) { return const TextStyle(color: Colors.green); }); - final MaterialStateTextStyle decorationStyle = MaterialStateTextStyle.resolveWith(( + final WidgetStateTextStyle decorationStyle = WidgetStateTextStyle.resolveWith(( Set states, ) { return const TextStyle(color: Colors.blue); diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 687513c7297d9..566f5017bffaa 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -7375,7 +7375,7 @@ void main() { child: TextField( controller: controller, enabled: enabled, - style: MaterialStateTextStyle.resolveWith((Set states) { + style: WidgetStateTextStyle.resolveWith((Set states) { if (states.contains(WidgetState.disabled)) { return const TextStyle(color: Colors.red); } From 4f49888a06baf5c52562075c0e2e3163bb4cdfda Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Sameh Date: Wed, 1 Oct 2025 22:01:57 +0300 Subject: [PATCH 026/204] Make sure that a DrawerButton doesn't crash in 0x0 environment (#172948) This is my attempt to handle https://github.com/flutter/flutter/issues/6537 for the DrawerButton UI control. --- .../flutter/test/material/drawer_button_test.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/flutter/test/material/drawer_button_test.dart b/packages/flutter/test/material/drawer_button_test.dart index f94017dded708..3018a134017f1 100644 --- a/packages/flutter/test/material/drawer_button_test.dart +++ b/packages/flutter/test/material/drawer_button_test.dart @@ -286,4 +286,15 @@ void main() { // The custom callback is called, setting customCallbackWasCalled to true. expect(customCallbackWasCalled, true); }); + + testWidgets('DrawerButton does not crash at zero area', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Center( + child: SizedBox.shrink(child: Scaffold(body: DrawerButton())), + ), + ), + ); + expect(tester.getSize(find.byType(DrawerButton)), Size.zero); + }); } From 7ceee3b074cde1f6bc6df791ef682c856bf8fff8 Mon Sep 17 00:00:00 2001 From: Ahmed Mohamed Sameh Date: Wed, 1 Oct 2025 22:01:57 +0300 Subject: [PATCH 027/204] Make sure that a DateRangePickerDialog doesn't crash in 0x0 environments (#173754) This is my attempt to handle https://github.com/flutter/flutter/issues/6537 for the DateRangePickerDialog UI control. --- packages/flutter/lib/src/material/date_picker.dart | 6 ++++-- .../test/material/date_range_picker_test.dart | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index 4e7a7bae2e4c6..a6056c9c099e6 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -2395,8 +2395,10 @@ class _MonthItemGridDelegate extends SliverGridDelegate { @override SliverGridLayout getLayout(SliverConstraints constraints) { - final double tileWidth = - (constraints.crossAxisExtent - 2 * _horizontalPadding) / DateTime.daysPerWeek; + final double tileWidth = math.max( + (constraints.crossAxisExtent - 2 * _horizontalPadding) / DateTime.daysPerWeek, + 0.0, + ); return _MonthSliverGridLayout( crossAxisCount: DateTime.daysPerWeek + 2, dayChildWidth: tileWidth, diff --git a/packages/flutter/test/material/date_range_picker_test.dart b/packages/flutter/test/material/date_range_picker_test.dart index aba6da11aa8d7..b3138d3ae0512 100644 --- a/packages/flutter/test/material/date_range_picker_test.dart +++ b/packages/flutter/test/material/date_range_picker_test.dart @@ -1967,6 +1967,19 @@ void main() { expect(getDayCount(secondMonthItem), 21); }); }); + + testWidgets('DateRangePickerDialog does not crash at zero area', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Center( + child: SizedBox.shrink( + child: DateRangePickerDialog(firstDate: firstDate, lastDate: lastDate), + ), + ), + ), + ); + expect(tester.getSize(find.byType(DateRangePickerDialog)), Size.zero); + }); } class _RestorableDateRangePickerDialogTestWidget extends StatefulWidget { From 40f013a0e91f2d5b8452a47167316471519f27b1 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Wed, 1 Oct 2025 16:04:07 -0400 Subject: [PATCH 028/204] [ Tool / l10n ] Fix issue where localization generator assumed current directory was the target project (#175881) When running `flutter pub get` from the root of a Pub workspace, localizations are generated for each subproject. However, we were trying to write the untranslated messages file relative to the current directory, not the target project's directory. This change updates the logic for determining the output location of the untranslated messages file to include the target project root. Fixes https://github.com/flutter/flutter/issues/174205 --- .../lib/src/localizations/gen_l10n.dart | 32 ++++------ .../generate_localizations_test.dart | 64 +++++++++++++++++-- 2 files changed, 70 insertions(+), 26 deletions(-) diff --git a/packages/flutter_tools/lib/src/localizations/gen_l10n.dart b/packages/flutter_tools/lib/src/localizations/gen_l10n.dart index 429b0b41a373e..7b0fa8b083ad5 100644 --- a/packages/flutter_tools/lib/src/localizations/gen_l10n.dart +++ b/packages/flutter_tools/lib/src/localizations/gen_l10n.dart @@ -526,7 +526,7 @@ class LocalizationsGenerator { String? headerFile, bool useDeferredLoading = false, String? inputsAndOutputsListPath, - String? projectPathString, + required String projectPathString, bool areResourceAttributesRequired = false, String? untranslatedMessagesFile, bool usesNullableGetter = true, @@ -536,7 +536,7 @@ class LocalizationsGenerator { bool useRelaxedSyntax = false, bool useNamedParameters = false, }) { - final Directory? projectDirectory = projectDirFromPath(fileSystem, projectPathString); + final Directory projectDirectory = projectDirFromPath(fileSystem, projectPathString); final Directory inputDirectory = inputDirectoryFromPath( fileSystem, inputPathString, @@ -561,6 +561,7 @@ class LocalizationsGenerator { useDeferredLoading: useDeferredLoading, untranslatedMessagesFile: _untranslatedMessagesFileFromPath( fileSystem, + projectDirectory, untranslatedMessagesFile, ), inputsAndOutputsListFile: _inputsAndOutputsListFileFromPath( @@ -590,7 +591,7 @@ class LocalizationsGenerator { this.header = '', this.useDeferredLoading = false, required this.inputsAndOutputsListFile, - this.projectDirectory, + required this.projectDirectory, this.areResourceAttributesRequired = false, this.untranslatedMessagesFile, this.usesNullableGetter = true, @@ -627,7 +628,7 @@ class LocalizationsGenerator { final Directory inputDirectory; /// The Flutter project's root directory. - final Directory? projectDirectory; + final Directory projectDirectory; /// The directory to generate the project's localizations files in. /// @@ -762,11 +763,7 @@ class LocalizationsGenerator { } @visibleForTesting - static Directory? projectDirFromPath(FileSystem fileSystem, String? projectPathString) { - if (projectPathString == null) { - return null; - } - + static Directory projectDirFromPath(FileSystem fileSystem, String projectPathString) { final Directory directory = fileSystem.directory(projectPathString); if (!directory.existsSync()) { throw L10nException( @@ -783,12 +780,10 @@ class LocalizationsGenerator { static Directory inputDirectoryFromPath( FileSystem fileSystem, String inputPathString, - Directory? projectDirectory, + Directory projectDirectory, ) { final Directory inputDirectory = fileSystem.directory( - projectDirectory != null - ? _getAbsoluteProjectPath(inputPathString, projectDirectory) - : inputPathString, + _getAbsoluteProjectPath(inputPathString, projectDirectory), ); if (!inputDirectory.existsSync()) { @@ -812,13 +807,9 @@ class LocalizationsGenerator { static Directory _outputDirectoryFromPath( FileSystem fileSystem, String outputPathString, - Directory? projectDirectory, + Directory projectDirectory, ) { - return fileSystem.directory( - projectDirectory != null - ? _getAbsoluteProjectPath(outputPathString, projectDirectory) - : outputPathString, - ); + return fileSystem.directory(_getAbsoluteProjectPath(outputPathString, projectDirectory)); } /// Sets the reference [File] for [templateArbFile]. @@ -914,6 +905,7 @@ class LocalizationsGenerator { static File? _untranslatedMessagesFileFromPath( FileSystem fileSystem, + Directory projectDirectory, String? untranslatedMessagesFileString, ) { if (untranslatedMessagesFileString == null || untranslatedMessagesFileString.isEmpty) { @@ -923,7 +915,7 @@ class LocalizationsGenerator { r'\', fileSystem.path.separator, ); - return fileSystem.file(untranslatedMessagesFileString); + return projectDirectory.childFile(untranslatedMessagesFileString); } static File? _inputsAndOutputsListFileFromPath( 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 717d89e3e6b6d..ed6fee7c1b05b 100644 --- a/packages/flutter_tools/test/general.shard/generate_localizations_test.dart +++ b/packages/flutter_tools/test/general.shard/generate_localizations_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:file/memory.dart'; +import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; @@ -120,6 +121,7 @@ void main() { suppressWarnings: suppressWarnings, useRelaxedSyntax: relaxSyntax, useNamedParameters: useNamedParameters, + projectPathString: (fileSystem ?? fs).currentDirectory.path, ) ..loadResources() ..writeOutputFiles(isFromYaml: isFromYaml); @@ -404,6 +406,40 @@ void main() { expect(unimplementedOutputString, contains('subtitle')); }); + testWithoutContext('correctly creates an untranslated messages file when project directory is ' + 'not the current directory', () { + // Regression test for https://github.com/flutter/flutter/issues/174205 + final String untranslatedMessagesFilePath = fs.path.join( + 'lib', + 'l10n', + 'unimplemented_message_translations.json', + ); + + final Directory projectRoot = fs.directory('/my/project/root/')..createSync(recursive: true); + final Directory current = fs.currentDirectory; + try { + fs.currentDirectory = projectRoot; + _standardFlutterDirectoryL10nSetup(fs); + } finally { + fs.currentDirectory = current; + } + final generator = + LocalizationsGenerator( + fileSystem: fs, + inputPathString: defaultL10nPath, + outputPathString: defaultL10nPath, + templateArbFileName: defaultTemplateArbFileName, + outputFileString: defaultOutputFileString, + classNameString: defaultClassNameString, + logger: logger, + projectPathString: projectRoot.path, + untranslatedMessagesFile: untranslatedMessagesFilePath, + ) + ..loadResources() + ..writeOutputFiles(); + expect(generator.untranslatedMessagesFile, exists); + }); + testWithoutContext('untranslated messages suggestion is printed when translation is missing: ' 'command line message', () { setupLocalizations({ @@ -475,6 +511,7 @@ void main() { outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, logger: logger, + projectPathString: fs.currentDirectory.path, ) ..loadResources() ..writeOutputFiles(); @@ -505,6 +542,7 @@ void main() { outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, logger: logger, + projectPathString: fs.currentDirectory.path, ) ..loadResources() ..writeOutputFiles(); @@ -530,6 +568,7 @@ void main() { outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, logger: logger, + projectPathString: fs.currentDirectory.path, ) ..loadResources() ..writeOutputFiles(); @@ -776,9 +815,9 @@ flutter: expect(generator.header, 'HEADER'); expect(generator.useDeferredLoading, isTrue); expect(generator.inputsAndOutputsListFile?.path, '/gen_l10n_inputs_and_outputs.json'); - expect(generator.projectDirectory?.path, '/'); + expect(generator.projectDirectory.path, '/'); expect(generator.areResourceAttributesRequired, isTrue); - expect(generator.untranslatedMessagesFile?.path, 'untranslated'); + expect(generator.untranslatedMessagesFile?.path, '/untranslated'); expect(generator.usesNullableGetter, isFalse); // Just validate one file. @@ -954,6 +993,7 @@ class AppLocalizationsEn extends AppLocalizations { outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, logger: logger, + projectPathString: fs.currentDirectory.path, )..loadResources(); expect(generator.supportedLocales.contains(LocaleInfo.fromString('en')), true); @@ -980,6 +1020,7 @@ class AppLocalizationsEn extends AppLocalizations { outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, logger: logger, + projectPathString: fs.currentDirectory.path, )..loadResources(); expect(generator.supportedLocales.first, LocaleInfo.fromString('en')); @@ -1008,6 +1049,7 @@ class AppLocalizationsEn extends AppLocalizations { classNameString: defaultClassNameString, preferredSupportedLocales: preferredSupportedLocale, logger: logger, + projectPathString: fs.currentDirectory.path, )..loadResources(); expect(generator.supportedLocales.first, LocaleInfo.fromString('zh')); @@ -1038,6 +1080,7 @@ class AppLocalizationsEn extends AppLocalizations { classNameString: defaultClassNameString, preferredSupportedLocales: preferredSupportedLocale, logger: logger, + projectPathString: fs.currentDirectory.path, ).loadResources(); }, throwsA( @@ -1068,11 +1111,12 @@ class AppLocalizationsEn extends AppLocalizations { outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, logger: logger, + projectPathString: fs.currentDirectory.path, )..loadResources(); - expect(generator.arbPathStrings.first, fs.path.join('lib', 'l10n', 'app_en.arb')); - expect(generator.arbPathStrings.elementAt(1), fs.path.join('lib', 'l10n', 'app_es.arb')); - expect(generator.arbPathStrings.elementAt(2), fs.path.join('lib', 'l10n', 'app_zh.arb')); + expect(generator.arbPathStrings.first, fs.path.join('/', 'lib', 'l10n', 'app_en.arb')); + expect(generator.arbPathStrings.elementAt(1), fs.path.join('/', 'lib', 'l10n', 'app_es.arb')); + expect(generator.arbPathStrings.elementAt(2), fs.path.join('/', 'lib', 'l10n', 'app_zh.arb')); }); testWithoutContext('correctly parses @@locale property in arb file', () { @@ -1108,6 +1152,7 @@ class AppLocalizationsEn extends AppLocalizations { outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, logger: logger, + projectPathString: fs.currentDirectory.path, )..loadResources(); expect(generator.supportedLocales.contains(LocaleInfo.fromString('en')), true); @@ -1151,6 +1196,7 @@ class AppLocalizationsEn extends AppLocalizations { outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, logger: logger, + projectPathString: fs.currentDirectory.path, ).loadResources(); }, throwsA( @@ -1178,6 +1224,7 @@ class AppLocalizationsEn extends AppLocalizations { outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, logger: logger, + projectPathString: fs.currentDirectory.path, ).loadResources(); }, throwsA( @@ -1213,6 +1260,7 @@ class AppLocalizationsEn extends AppLocalizations { outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, logger: logger, + projectPathString: fs.currentDirectory.path, ).loadResources(), throwsA( isA().having( @@ -1249,6 +1297,7 @@ class AppLocalizationsEn extends AppLocalizations { outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, logger: logger, + projectPathString: fs.currentDirectory.path, ).loadResources(); }, throwsA( @@ -1277,6 +1326,7 @@ class AppLocalizationsEn extends AppLocalizations { outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, logger: logger, + projectPathString: fs.currentDirectory.path, ).loadResources(); }, throwsA( @@ -1309,13 +1359,14 @@ class AppLocalizationsEn extends AppLocalizations { outputFileString: outputFileString, classNameString: classNameString, logger: logger, + projectPathString: fs.currentDirectory.path, ); expect( () => generator.loadResources(), throwsToolExit( message: 'Localized message for key "helloWorld" in ' - '"lib/l10n/app_es.arb" is not a string.', + '"/lib/l10n/app_es.arb" is not a string.', ), ); }); @@ -3046,6 +3097,7 @@ import 'output-localization-file_en.dart' deferred as output-localization-file_e outputFileString: defaultOutputFileString, classNameString: defaultClassNameString, logger: logger, + projectPathString: fs.currentDirectory.path, ) ..loadResources() ..writeOutputFiles(); From d0a688425d314c925dbb9f5c568737d90fd83ddb Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 1 Oct 2025 17:11:39 -0400 Subject: [PATCH 029/204] Roll Dart SDK from 8ffec1435cf3 to 4f90a06328cb (3 revisions) (#176369) https://dart.googlesource.com/sdk.git/+log/8ffec1435cf3..4f90a06328cb 2025-10-01 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-268.0.dev 2025-10-01 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-267.0.dev 2025-10-01 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-266.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jimgraham@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 309c181fc29ea..a2a42a2314242 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '8ffec1435cf31802e237765d6e9aae48fe5bd19e', + 'dart_revision': '4f90a06328cb4bdfbbf97e3ef344e0b96dddf177', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From a3eba8eab2fd56422d0529688106344d1965b8d7 Mon Sep 17 00:00:00 2001 From: chunhtai <47866232+chunhtai@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:34:09 -0700 Subject: [PATCH 030/204] =?UTF-8?q?Reapply=20"Update=20the=20Accessibility?= =?UTF-8?q?Plugin::Announce=20method=20to=20account=20f=E2=80=A6=20(#17610?= =?UTF-8?q?7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …… (#174365)" This reverts commit 5e146d47a61098e5ed967352812bcbbe0fe24f20. straight reland, previous pr was reverted because g3fix is outdated ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../new_gallery/lib/pages/backdrop.dart | 3 +- .../platform/windows/accessibility_plugin.cc | 23 ++++++++--- .../platform/windows/accessibility_plugin.h | 4 +- .../shell/platform/windows/fixtures/main.dart | 12 ++++-- .../src/material/calendar_date_picker.dart | 11 ++++-- .../lib/src/material/expansion_tile.dart | 4 +- .../lib/src/semantics/semantics_event.dart | 10 ++++- .../lib/src/semantics/semantics_service.dart | 38 ++++++++++++++++++- packages/flutter/lib/src/widgets/form.dart | 18 +++++---- .../semantics/semantics_service_test.dart | 14 +++++-- .../flutter_test/test/widget_tester_test.dart | 13 ++++--- 11 files changed, 114 insertions(+), 36 deletions(-) diff --git a/dev/integration_tests/new_gallery/lib/pages/backdrop.dart b/dev/integration_tests/new_gallery/lib/pages/backdrop.dart index dd423af0c9435..f6df47c23aa59 100644 --- a/dev/integration_tests/new_gallery/lib/pages/backdrop.dart +++ b/dev/integration_tests/new_gallery/lib/pages/backdrop.dart @@ -264,7 +264,8 @@ class _SettingsIcon extends AnimatedWidget { child: InkWell( onTap: () { toggleSettings(); - SemanticsService.announce( + SemanticsService.sendAnnouncement( + View.of(context), _settingsSemanticLabel(isSettingsOpenNotifier.value, context), GalleryOptions.of(context).resolvedTextDirection()!, ); diff --git a/engine/src/flutter/shell/platform/windows/accessibility_plugin.cc b/engine/src/flutter/shell/platform/windows/accessibility_plugin.cc index a1fe9fc21d060..f0caa4b6d3741 100644 --- a/engine/src/flutter/shell/platform/windows/accessibility_plugin.cc +++ b/engine/src/flutter/shell/platform/windows/accessibility_plugin.cc @@ -20,6 +20,7 @@ static constexpr char kAccessibilityChannelName[] = "flutter/accessibility"; static constexpr char kTypeKey[] = "type"; static constexpr char kDataKey[] = "data"; static constexpr char kMessageKey[] = "message"; +static constexpr char kViewIdKey[] = "viewId"; static constexpr char kAnnounceValue[] = "announce"; // Handles messages like: @@ -61,7 +62,20 @@ void HandleMessage(AccessibilityPlugin* plugin, const EncodableValue& message) { return; } - plugin->Announce(*message); + const auto& view_itr = data->find(EncodableValue{kViewIdKey}); + if (view_itr == data->end()) { + FML_LOG(ERROR) << "Announce message 'viewId' property is missing."; + return; + } + + const auto* view_id_val = std::get_if(&view_itr->second); + if (!view_id_val) { + FML_LOG(ERROR) + << "Announce message 'viewId' property must be a FlutterViewId."; + return; + } + + plugin->Announce(*view_id_val, *message); } else { FML_LOG(WARNING) << "Accessibility message type '" << *type << "' is not supported."; @@ -89,14 +103,13 @@ void AccessibilityPlugin::SetUp(BinaryMessenger* binary_messenger, }); } -void AccessibilityPlugin::Announce(const std::string_view message) { +void AccessibilityPlugin::Announce(const FlutterViewId view_id, + const std::string_view message) { if (!engine_->semantics_enabled()) { return; } - // TODO(loicsharma): Remove implicit view assumption. - // https://github.com/flutter/flutter/issues/142845 - auto view = engine_->view(kImplicitViewId); + auto view = engine_->view(view_id); if (!view) { return; } diff --git a/engine/src/flutter/shell/platform/windows/accessibility_plugin.h b/engine/src/flutter/shell/platform/windows/accessibility_plugin.h index cd0c24ead8645..55a501df82a28 100644 --- a/engine/src/flutter/shell/platform/windows/accessibility_plugin.h +++ b/engine/src/flutter/shell/platform/windows/accessibility_plugin.h @@ -12,6 +12,7 @@ namespace flutter { +using FlutterViewId = int64_t; class FlutterWindowsEngine; // Handles messages on the flutter/accessibility channel. @@ -27,7 +28,8 @@ class AccessibilityPlugin { AccessibilityPlugin* plugin); // Announce a message through the assistive technology. - virtual void Announce(const std::string_view message); + virtual void Announce(const FlutterViewId view_id, + const std::string_view message); private: // The engine that owns this plugin. diff --git a/engine/src/flutter/shell/platform/windows/fixtures/main.dart b/engine/src/flutter/shell/platform/windows/fixtures/main.dart index 0fcba55bced3b..26f1efe182220 100644 --- a/engine/src/flutter/shell/platform/windows/fixtures/main.dart +++ b/engine/src/flutter/shell/platform/windows/fixtures/main.dart @@ -60,9 +60,9 @@ Future sendAccessibilityAnnouncement() async { // Standard message codec magic number identifiers. // See: https://github.com/flutter/flutter/blob/ee94fe262b63b0761e8e1f889ae52322fef068d2/packages/flutter/lib/src/services/message_codecs.dart#L262 - const int valueMap = 13, valueString = 7; + const int valueMap = 13, valueString = 7, valueInt64 = 4; - // Corresponds to: {"type": "announce", "data": {"message": "hello"}} + // Corresponds to: {"type": "announce", "data": {"viewId": 0, "message": "hello"}} // See: https://github.com/flutter/flutter/blob/b781da9b5822de1461a769c3b245075359f5464d/packages/flutter/lib/src/semantics/semantics_event.dart#L86 final Uint8List data = Uint8List.fromList([ // Map with 2 entries @@ -73,8 +73,12 @@ Future sendAccessibilityAnnouncement() async { valueString, 'announce'.length, ...'announce'.codeUnits, // Map key: "data" valueString, 'data'.length, ...'data'.codeUnits, - // Map value: map with 1 entry - valueMap, 1, + // Map value: map with 2 entries + valueMap, 2, + // Map key: "viewId" + valueString, 'viewId'.length, ...'viewId'.codeUnits, + // Map value: 0 + valueInt64, 0, 0, 0, 0, 0, 0, 0, 0, // Map key: "message" valueString, 'message'.length, ...'message'.codeUnits, // Map value: "hello" diff --git a/packages/flutter/lib/src/material/calendar_date_picker.dart b/packages/flutter/lib/src/material/calendar_date_picker.dart index 1b55a732fe72c..e8f5bf8437b54 100644 --- a/packages/flutter/lib/src/material/calendar_date_picker.dart +++ b/packages/flutter/lib/src/material/calendar_date_picker.dart @@ -236,7 +236,8 @@ class _CalendarDatePickerState extends State { _announcedInitialDate = true; final bool isToday = widget.calendarDelegate.isSameDay(widget.currentDate, _selectedDate); final String semanticLabelSuffix = isToday ? ', ${_localizations.currentDateLabel}' : ''; - SemanticsService.announce( + SemanticsService.sendAnnouncement( + View.of(context), '${_localizations.formatFullDate(_selectedDate!)}$semanticLabelSuffix', _textDirection, ); @@ -265,7 +266,7 @@ class _CalendarDatePickerState extends State { DatePickerMode.day => widget.calendarDelegate.formatMonthYear(selected, _localizations), DatePickerMode.year => widget.calendarDelegate.formatYear(selected.year, _localizations), }; - SemanticsService.announce(message, _textDirection); + SemanticsService.sendAnnouncement(View.of(context), message, _textDirection); } }); } @@ -315,7 +316,8 @@ class _CalendarDatePickerState extends State { case TargetPlatform.windows: final bool isToday = widget.calendarDelegate.isSameDay(widget.currentDate, _selectedDate); final String semanticLabelSuffix = isToday ? ', ${_localizations.currentDateLabel}' : ''; - SemanticsService.announce( + SemanticsService.sendAnnouncement( + View.of(context), '${_localizations.selectedDateLabel} ${widget.calendarDelegate.formatFullDate(_selectedDate!, _localizations)}$semanticLabelSuffix', _textDirection, ); @@ -665,7 +667,8 @@ class _MonthPickerState extends State<_MonthPicker> { // the same day of the month. _focusedDay = _focusableDayForMonth(_currentMonth, _focusedDay!.day); } - SemanticsService.announce( + SemanticsService.sendAnnouncement( + View.of(context), widget.calendarDelegate.formatMonthYear(_currentMonth, _localizations), _textDirection, ); diff --git a/packages/flutter/lib/src/material/expansion_tile.dart b/packages/flutter/lib/src/material/expansion_tile.dart index a107ccbfa8528..c0f2809cf4a19 100644 --- a/packages/flutter/lib/src/material/expansion_tile.dart +++ b/packages/flutter/lib/src/material/expansion_tile.dart @@ -538,12 +538,12 @@ class _ExpansionTileState extends State { // semantic announcements on iOS. https://github.com/flutter/flutter/issues/122101. _timer?.cancel(); _timer = Timer(const Duration(seconds: 1), () { - SemanticsService.announce(stateHint, textDirection); + SemanticsService.sendAnnouncement(View.of(context), stateHint, textDirection); _timer?.cancel(); _timer = null; }); } else { - SemanticsService.announce(stateHint, textDirection); + SemanticsService.sendAnnouncement(View.of(context), stateHint, textDirection); } widget.onExpansionChanged?.call(_tileController.isExpanded); } diff --git a/packages/flutter/lib/src/semantics/semantics_event.dart b/packages/flutter/lib/src/semantics/semantics_event.dart index 9b9e034d04491..801dd357ae759 100644 --- a/packages/flutter/lib/src/semantics/semantics_event.dart +++ b/packages/flutter/lib/src/semantics/semantics_event.dart @@ -93,13 +93,18 @@ abstract class SemanticsEvent { /// [1]: https://developer.android.com/reference/android/view/View#announceForAccessibility(java.lang.CharSequence) /// class AnnounceSemanticsEvent extends SemanticsEvent { - /// Constructs an event that triggers an announcement by the platform. + /// Constructs an event that triggers an announcement by the platform + /// for the provided view. const AnnounceSemanticsEvent( this.message, - this.textDirection, { + this.textDirection, + this.viewId, { this.assertiveness = Assertiveness.polite, }) : super('announce'); + /// The view that this announcement is on. + final int viewId; + /// The message to announce. final String message; @@ -117,6 +122,7 @@ class AnnounceSemanticsEvent extends SemanticsEvent { @override Map getDataMap() { return { + 'viewId': viewId, 'message': message, 'textDirection': textDirection.index, if (assertiveness != Assertiveness.polite) 'assertiveness': assertiveness.index, diff --git a/packages/flutter/lib/src/semantics/semantics_service.dart b/packages/flutter/lib/src/semantics/semantics_service.dart index 750e887e39758..8104ff0fbafed 100644 --- a/packages/flutter/lib/src/semantics/semantics_service.dart +++ b/packages/flutter/lib/src/semantics/semantics_service.dart @@ -5,7 +5,7 @@ /// @docImport 'package:flutter/widgets.dart'; library; -import 'dart:ui' show TextDirection; +import 'dart:ui' show FlutterView, PlatformDispatcher, TextDirection; import 'package:flutter/services.dart' show SystemChannels; @@ -23,6 +23,9 @@ export 'dart:ui' show TextDirection; abstract final class SemanticsService { /// Sends a semantic announcement. /// + /// This method is deprecated. Prefer using [sendAnnouncement] instead. + /// + /// {@template flutter.semantics.service.announce} /// This should be used for announcement that are not seamlessly announced by /// the system as a result of a UI state change. /// @@ -43,15 +46,48 @@ abstract final class SemanticsService { /// trigger announcements. /// /// [1]: https://developer.android.com/reference/android/view/View#announceForAccessibility(java.lang.CharSequence) + /// {@endtemplate} /// + @Deprecated( + 'Use sendAnnouncement instead. ' + 'This API is incompatible with multiple windows. ' + 'This feature was deprecated after v3.35.0-0.1.pre.', + ) static Future announce( String message, TextDirection textDirection, { Assertiveness assertiveness = Assertiveness.polite, + }) async { + final FlutterView? view = PlatformDispatcher.instance.implicitView; + assert( + view != null, + 'SemanticsService.announce is incompatible with multiple windows. ' + 'Use SemanticsService.sendAnnouncement instead.', + ); + final AnnounceSemanticsEvent event = AnnounceSemanticsEvent( + message, + textDirection, + view!.viewId, + assertiveness: assertiveness, + ); + await SystemChannels.accessibility.send(event.toMap()); + } + + /// Sends a semantic announcement for a particular view. + /// + /// One can use [View.of] to get the current [FlutterView]. + /// + /// {@macro flutter.semantics.service.announce} + static Future sendAnnouncement( + FlutterView view, + String message, + TextDirection textDirection, { + Assertiveness assertiveness = Assertiveness.polite, }) async { final AnnounceSemanticsEvent event = AnnounceSemanticsEvent( message, textDirection, + view.viewId, assertiveness: assertiveness, ); await SystemChannels.accessibility.send(event.toMap()); diff --git a/packages/flutter/lib/src/widgets/form.dart b/packages/flutter/lib/src/widgets/form.dart index 5e2c07bebd214..8e5721d3af668 100644 --- a/packages/flutter/lib/src/widgets/form.dart +++ b/packages/flutter/lib/src/widgets/form.dart @@ -7,6 +7,7 @@ library; import 'dart:async'; +import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; @@ -22,6 +23,7 @@ import 'pop_scope.dart'; import 'restoration.dart'; import 'restoration_properties.dart'; import 'routes.dart'; +import 'view.dart'; import 'will_pop_scope.dart'; // Duration for delay before announcement in IOS so that the announcement won't be interrupted. @@ -271,10 +273,10 @@ class FormState extends State

{ Widget build(BuildContext context) { switch (widget.autovalidateMode) { case AutovalidateMode.always: - _validate(); + _validate(View.of(context)); case AutovalidateMode.onUserInteraction: if (_hasInteractedByUser) { - _validate(); + _validate(View.of(context)); } case AutovalidateMode.onUnfocus: case AutovalidateMode.disabled: @@ -335,7 +337,7 @@ class FormState extends State { bool validate() { _hasInteractedByUser = true; _forceRebuild(); - return _validate(); + return _validate(View.of(context)); } /// Validates every [FormField] that is a descendant of this [Form], and @@ -352,11 +354,11 @@ class FormState extends State { final Set> invalidFields = >{}; _hasInteractedByUser = true; _forceRebuild(); - _validate(invalidFields); + _validate(View.of(context), invalidFields); return invalidFields; } - bool _validate([Set>? invalidFields]) { + bool _validate(FlutterView view, [Set>? invalidFields]) { bool hasError = false; String errorMessage = ''; final bool validateOnFocusChange = widget.autovalidateMode == AutovalidateMode.onUnfocus; @@ -383,7 +385,8 @@ class FormState extends State { unawaited( Future(() async { await Future.delayed(_kIOSAnnouncementDelayDuration); - SemanticsService.announce( + SemanticsService.sendAnnouncement( + view, errorMessage, directionality, assertiveness: Assertiveness.assertive, @@ -391,7 +394,8 @@ class FormState extends State { }), ); } else { - SemanticsService.announce( + SemanticsService.sendAnnouncement( + view, errorMessage, directionality, assertiveness: Assertiveness.assertive, diff --git a/packages/flutter/test/semantics/semantics_service_test.dart b/packages/flutter/test/semantics/semantics_service_test.dart index f741f316e5f1c..b8c1879d05fa8 100644 --- a/packages/flutter/test/semantics/semantics_service_test.dart +++ b/packages/flutter/test/semantics/semantics_service_test.dart @@ -9,7 +9,7 @@ import 'package:flutter_test/flutter_test.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - test('Semantic announcement', () async { + testWidgets('Semantic announcement', (WidgetTester tester) async { final List> log = >[]; Future handleMessage(dynamic mockMessage) async { @@ -20,8 +20,9 @@ void main() { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockDecodedMessageHandler(SystemChannels.accessibility, handleMessage); - await SemanticsService.announce('announcement 1', TextDirection.ltr); - await SemanticsService.announce( + await SemanticsService.sendAnnouncement(tester.view, 'announcement 1', TextDirection.ltr); + await SemanticsService.sendAnnouncement( + tester.view, 'announcement 2', TextDirection.rtl, assertiveness: Assertiveness.assertive, @@ -31,11 +32,16 @@ void main() { equals(>[ { 'type': 'announce', - 'data': {'message': 'announcement 1', 'textDirection': 1}, + 'data': { + 'viewId': tester.view.viewId, + 'message': 'announcement 1', + 'textDirection': 1, + }, }, { 'type': 'announce', 'data': { + 'viewId': tester.view.viewId, 'message': 'announcement 2', 'textDirection': 0, 'assertiveness': 1, diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart index 09e940561d86b..687aeb82e098c 100644 --- a/packages/flutter_test/test/widget_tester_test.dart +++ b/packages/flutter_test/test/widget_tester_test.dart @@ -701,13 +701,14 @@ void main() { isFalse, ); - await SemanticsService.announce('announcement 1', TextDirection.ltr); - await SemanticsService.announce( + await SemanticsService.sendAnnouncement(tester.view, 'announcement 1', TextDirection.ltr); + await SemanticsService.sendAnnouncement( + tester.view, 'announcement 2', TextDirection.rtl, assertiveness: Assertiveness.assertive, ); - await SemanticsService.announce('announcement 3', TextDirection.rtl); + await SemanticsService.sendAnnouncement(tester.view, 'announcement 3', TextDirection.rtl); final List list = tester.takeAnnouncements(); expect(list, hasLength(3)); @@ -729,7 +730,7 @@ void main() { expect(emptyList, []); }); - test('New test API is not breaking existing tests', () async { + testWidgets('New test API is not breaking existing tests', (WidgetTester tester) async { final List> log = >[]; Future handleMessage(dynamic mockMessage) async { @@ -740,7 +741,8 @@ void main() { TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger .setMockDecodedMessageHandler(SystemChannels.accessibility, handleMessage); - await SemanticsService.announce( + await SemanticsService.sendAnnouncement( + tester.view, 'announcement 1', TextDirection.rtl, assertiveness: Assertiveness.assertive, @@ -751,6 +753,7 @@ void main() { { 'type': 'announce', 'data': { + 'viewId': 0, 'message': 'announcement 1', 'textDirection': 0, 'assertiveness': 1, From c1764c14d887e20739bbfc4ec85856045ea66b0b Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Wed, 1 Oct 2025 18:37:23 -0400 Subject: [PATCH 031/204] Roll Skia from c44a36470d07 to b5d8ae8d3410 (5 revisions) (#176367) https://skia.googlesource.com/skia.git/+log/c44a36470d07..b5d8ae8d3410 2025-10-01 kjlubick@google.com Revert "Reapply "Use pathbuilder or factories to keep path immutable"" 2025-10-01 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 9023de589c86 to 29b917fb5921 (3 revisions) 2025-10-01 mike@reedtribe.org Remove unused mutable-path parameter 2025-10-01 robertphillips@google.com [graphite] Add SupportsPipelineCreationCacheControl bool to VulkanCaps 2025-10-01 nicolettep@google.com Only enable shader precision modifier usage for ganesh/GL If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index a2a42a2314242..12132ab82df6b 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'c44a36470d077020b978f87fc5720f85dfc68d5b', + 'skia_revision': 'b5d8ae8d3410f025c0a520a64b5b78098a982231', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 23a9ce38af01fe11591db58d8b0be60217ecd3d5 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Wed, 1 Oct 2025 18:39:09 -0400 Subject: [PATCH 032/204] [ Widget Preview ] Persist "Filter by Selected File" toggle (#176289) This change introduces `PersistentPreferences`, which allows for the widget previewer to save settings to disk. `PersistentPreferences` makes use of the existing `~/.flutter-devtools` directory used by DevTools for the same purpose, writing preferences in JSON format to `~/.flutter-devtools/.widget-preview`. --- .../lib/src/commands/widget_preview.dart | 1 + .../lib/src/widget_preview/dtd_services.dart | 38 +++++++++ .../persistent_preferences.dart | 82 +++++++++++++++++++ .../lib/src/dtd/dtd_services.dart.tmpl | 47 ++++++++++- ...dget_preview_scaffold_controller.dart.tmpl | 13 ++- .../widget_preview/dtd_services_test.dart | 72 ++++++++++++++++ .../forbidden_imports_test.dart | 4 +- .../lib/src/dtd/dtd_services.dart | 47 ++++++++++- .../widget_preview_scaffold_controller.dart | 13 ++- .../test/error_widget_test.dart | 2 +- .../test/filter_by_selected_file_test.dart | 35 +++++++- .../test/no_previews_detected_test.dart | 2 +- .../widget_preview_scaffold_test_utils.dart | 15 ++++ 13 files changed, 356 insertions(+), 15 deletions(-) create mode 100644 packages/flutter_tools/lib/src/widget_preview/persistent_preferences.dart diff --git a/packages/flutter_tools/lib/src/commands/widget_preview.dart b/packages/flutter_tools/lib/src/commands/widget_preview.dart index 8452657df2b3a..d8cf24a3f3311 100644 --- a/packages/flutter_tools/lib/src/commands/widget_preview.dart +++ b/packages/flutter_tools/lib/src/commands/widget_preview.dart @@ -250,6 +250,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C ); late var _dtdService = WidgetPreviewDtdServices( + fs: fs, logger: logger, shutdownHooks: shutdownHooks, onHotRestartPreviewerRequest: onHotRestartRequest, diff --git a/packages/flutter_tools/lib/src/widget_preview/dtd_services.dart b/packages/flutter_tools/lib/src/widget_preview/dtd_services.dart index e4f20112542bc..64222a5048fd9 100644 --- a/packages/flutter_tools/lib/src/widget_preview/dtd_services.dart +++ b/packages/flutter_tools/lib/src/widget_preview/dtd_services.dart @@ -6,11 +6,13 @@ import 'dart:async'; import 'package:dtd/dtd.dart'; import 'package:json_rpc_2/json_rpc_2.dart'; +import 'package:meta/meta.dart'; import 'package:package_config/package_config_types.dart'; import 'package:process/process.dart'; import '../artifacts.dart'; import '../base/common.dart'; +import '../base/file_system.dart'; import '../base/io.dart'; import '../base/logger.dart'; import '../base/platform.dart'; @@ -18,12 +20,14 @@ import '../base/process.dart'; import '../convert.dart'; import '../dart/package_map.dart'; import '../project.dart'; +import 'persistent_preferences.dart'; typedef DtdService = (String, DTDServiceCallback); /// Provides services, streams, and RPC invocations to interact with the Widget Preview Scaffold. class WidgetPreviewDtdServices { WidgetPreviewDtdServices({ + required this.fs, required this.logger, required this.shutdownHooks, required this.dtdLauncher, @@ -45,16 +49,28 @@ class WidgetPreviewDtdServices { static const kIsWindows = 'isWindows'; static const kHotRestartPreviewer = 'hotRestartPreviewer'; static const kResolveUri = 'resolveUri'; + static const kSetPreference = 'setPreference'; + static const kGetPreference = 'getPreference'; + + /// Error code for RpcException thrown when attempting to load a key from + /// persistent preferences that doesn't have an entry. + static const kNoValueForKey = 200; /// The list of DTD service methods registered by the tool. late final services = [ (kHotRestartPreviewer, _hotRestart), (kIsWindows, _isWindows), (kResolveUri, _resolveUri), + (kSetPreference, _setPreference), + (kGetPreference, _getPreference), ]; // END KEEP SYNCED + @visibleForTesting + late final preferences = PersistentPreferences(fs: fs); + + final FileSystem fs; final Logger logger; final ShutdownHooks shutdownHooks; final DtdLauncher dtdLauncher; @@ -115,6 +131,28 @@ class WidgetPreviewDtdServices { final Uri? result = _packageConfig!.resolve(Uri.parse(params.asMap['uri'] as String)); return StringResponse(result.toString()).toJson(); } + + Future> _setPreference(Parameters params) async { + final String key = params['key'].asString; + final Object? value = params['value'].value; + preferences[key] = value; + return const Success().toJson(); + } + + Future> _getPreference(Parameters params) async { + final String key = params['key'].asString; + final Object? value = preferences[key]; + if (value == null) { + throw RpcException(kNoValueForKey, 'No entry for $key in preferences.'); + } + if (value is String) { + return StringResponse(value).toJson(); + } + if (value is bool) { + return BoolResponse(value).toJson(); + } + throw UnimplementedError('Unexpected preference value: ${value.runtimeType}'); + } } /// Manages the lifecycle of a Dart Tooling Daemon (DTD) instance. diff --git a/packages/flutter_tools/lib/src/widget_preview/persistent_preferences.dart b/packages/flutter_tools/lib/src/widget_preview/persistent_preferences.dart new file mode 100644 index 0000000000000..f962f4b4cc5a3 --- /dev/null +++ b/packages/flutter_tools/lib/src/widget_preview/persistent_preferences.dart @@ -0,0 +1,82 @@ +// 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:devtools_shared/devtools_server.dart' as devtools; +import 'package:meta/meta.dart'; + +import '../base/file_system.dart'; +import '../convert.dart'; + +/// A persistent store used to save settings across sessions. +/// +/// The store is written to `~/.flutter-devtools/.widget-preview` in JSON format. +class PersistentPreferences { + PersistentPreferences({required this.fs}) { + if (!file.existsSync()) { + file.createSync(recursive: true); + } + syncSettings(); + } + + static const _kPreferencesFileName = '.widget-preview'; + + final FileSystem fs; + + @visibleForTesting + late final File file = fs.file( + fs.path.join(devtools.LocalFileSystem.devToolsDir(), _kPreferencesFileName), + ); + + late Map _map; + + Object? operator [](String key) => _map[key]; + + void operator []=(String key, Object? value) { + if (value == null && !_map.containsKey(key)) { + return; + } + if (_map[key] == value) { + return; + } + + if (value == null) { + _map.remove(key); + } else { + _map[key] = value; + } + _writeChanges(); + } + + /// Re-read settings from the backing store. + /// + /// May be a no-op on some platforms. + void syncSettings() { + try { + String contents = file.readAsStringSync(); + if (contents.isEmpty) { + contents = '{}'; + } + _map = (json.decode(contents) as Map).cast(); + } on Exception { + _map = {}; + } + } + + void remove(String propertyName) { + if (!_map.containsKey(propertyName)) { + return; + } + _map.remove(propertyName); + _writeChanges(); + } + + void _writeChanges() { + try { + const jsonEncoder = JsonEncoder.withIndent(' '); + file.writeAsStringSync('${jsonEncoder.convert(_map)}\n'); + } on Exception { + // Do nothing. + } + } +} diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl index 189d83c19a4f9..76469ef7b1e6f 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:dtd/dtd.dart'; +import 'package:json_rpc_2/json_rpc_2.dart'; import 'package:widget_preview_scaffold/src/dtd/utils.dart'; import 'editor_service.dart'; @@ -18,10 +19,16 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { // // START KEEP SYNCED - static const String kWidgetPreviewService = 'widget-preview'; + static const kWidgetPreviewService = 'widget-preview'; static const kIsWindows = 'isWindows'; - static const String kHotRestartPreviewer = 'hotRestartPreviewer'; - static const String kResolveUri = 'resolveUri'; + static const kHotRestartPreviewer = 'hotRestartPreviewer'; + static const kResolveUri = 'resolveUri'; + static const kSetPreference = 'setPreference'; + static const kGetPreference = 'getPreference'; + + /// Error code for RpcException thrown when attempting to load a key from + /// persistent preferences that doesn't have an entry. + static const kNoValueForKey = 200; // END KEEP SYNCED @@ -81,6 +88,40 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { return result == null ? null : Uri.parse(result); } + /// Retrieves an arbitrary value associated with [key] from the persistent + /// preferences map. + /// + /// Returns null if [key] is not in the map. + Future getPreference(String key) async { + try { + final response = StringResponse.fromDTDResponse( + (await _call(kGetPreference, params: {'key': key}))!, + ); + return response.value; + } on RpcException catch (e) { + if (e.code == kNoValueForKey) { + return null; + } + rethrow; + } + } + + /// Retrieves the state of flag [key] from the persistent preferences map. + /// + /// If [key] is not set, [defaultValue] is returned. + Future getFlag(String key, {bool defaultValue = false}) async { + final result = await getPreference(key); + if (result == null) { + return defaultValue; + } + return bool.tryParse(result) ?? defaultValue; + } + + /// Sets [key] to [value] in the persistent preferences map. + Future setPreference(String key, Object value) async { + await _call(kSetPreference, params: {'key': key, 'value': value}); + } + @override late final DartToolingDaemon dtd; } diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart.tmpl index a5b1106c93c0c..2444abfb7bd79 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart.tmpl @@ -30,6 +30,9 @@ class WidgetPreviewScaffoldController { _WidgetPreviewScaffoldInspectorService(dtdServices: dtdServices); } + @visibleForTesting + static const kFilterBySelectedFilePreference = 'filterBySelectedFile'; + /// Initializes the controller by establishing a connection to DTD and /// listening for events. Future initialize() async { @@ -38,6 +41,10 @@ class WidgetPreviewScaffoldController { style: dtdServices.isWindows ? path.Style.windows : path.Style.posix, ); _registerListeners(); + _filterBySelectedFile.value = await dtdServices.getFlag( + kFilterBySelectedFilePreference, + defaultValue: true, + ); } /// Cleanup internal controller state. @@ -77,8 +84,10 @@ class WidgetPreviewScaffoldController { final _filterBySelectedFile = ValueNotifier(true); /// Enable or disable filtering by selected source file. - void toggleFilterBySelectedFile() { - _filterBySelectedFile.value = !_filterBySelectedFile.value; + Future toggleFilterBySelectedFile() async { + final updated = !_filterBySelectedFile.value; + await dtdServices.setPreference(kFilterBySelectedFilePreference, updated); + _filterBySelectedFile.value = updated; } /// The current set of previews to be displayed. diff --git a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/dtd_services_test.dart b/packages/flutter_tools/test/commands.shard/permeable/widget_preview/dtd_services_test.dart index fe269e324f501..d7ce8f9b926f7 100644 --- a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/dtd_services_test.dart +++ b/packages/flutter_tools/test/commands.shard/permeable/widget_preview/dtd_services_test.dart @@ -5,11 +5,16 @@ import 'dart:async'; import 'package:dtd/dtd.dart'; +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/process.dart'; +import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/widget_preview/dtd_services.dart'; +import 'package:flutter_tools/src/widget_preview/persistent_preferences.dart'; +import 'package:json_rpc_2/json_rpc_2.dart'; import 'package:test/fake.dart'; import '../../../src/common.dart'; @@ -43,6 +48,7 @@ void main() { // restart requests. final hotRestartRequestCompleter = Completer(); dtdServer = WidgetPreviewDtdServices( + fs: MemoryFileSystem.test(), logger: logger, shutdownHooks: ShutdownHooks(), dtdLauncher: DtdLauncher( @@ -70,5 +76,71 @@ void main() { }, overrides: {ProcessManager: () => loggingProcessManager}, ); + + testUsingContext('can set and retreive values from $PersistentPreferences', () async { + dtdServer = WidgetPreviewDtdServices( + fs: MemoryFileSystem.test(), + logger: logger, + shutdownHooks: ShutdownHooks(), + dtdLauncher: DtdLauncher( + logger: logger, + artifacts: globals.artifacts!, + processManager: globals.processManager, + ), + onHotRestartPreviewerRequest: () {}, + project: FakeFlutterProject(), + ); + await dtdServer.launchAndConnect(); + + // The properties file should be created by the PersistentProperties constructor. + final File preferencesFile = dtdServer.preferences.file; + expect(preferencesFile.existsSync(), true); + + // Connect to the DTD instance. + final DartToolingDaemon dtd = await DartToolingDaemon.connect(dtdServer.dtdUri!); + + Future getPreference(String key) async { + try { + return StringResponse.fromDTDResponse( + await dtd.call( + WidgetPreviewDtdServices.kWidgetPreviewService, + WidgetPreviewDtdServices.kGetPreference, + params: {'key': key}, + ), + ).value; + } on RpcException catch (e) { + if (e.code == WidgetPreviewDtdServices.kNoValueForKey) { + return null; + } + rethrow; + } + } + + Future setPreference(String key, String? value) async { + await dtd.call( + WidgetPreviewDtdServices.kWidgetPreviewService, + WidgetPreviewDtdServices.kSetPreference, + params: {'key': key, 'value': value}, + ); + } + + const kTestKey = 'myKey'; + + // The preferences file should be empty. + expect(await getPreference(kTestKey), null); + expect(preferencesFile.readAsStringSync(), isEmpty); + + // Set a preference and ensure it's read back. + const kFirstValue = 'foo'; + await setPreference(kTestKey, kFirstValue); + expect(await getPreference(kTestKey), kFirstValue); + expect(json.decode(preferencesFile.readAsStringSync()), {kTestKey: kFirstValue}); + + // Overwrite kTestKey and ensure it's read back. + const kSecondValue = 'bar'; + await setPreference(kTestKey, kSecondValue); + expect(await getPreference(kTestKey), kSecondValue); + expect(json.decode(preferencesFile.readAsStringSync()), {kTestKey: kSecondValue}); + }); }); } diff --git a/packages/flutter_tools/test/integration.shard/forbidden_imports_test.dart b/packages/flutter_tools/test/integration.shard/forbidden_imports_test.dart index 7b17de5532ea6..8d434b804e13a 100644 --- a/packages/flutter_tools/test/integration.shard/forbidden_imports_test.dart +++ b/packages/flutter_tools/test/integration.shard/forbidden_imports_test.dart @@ -276,8 +276,8 @@ void main() { ) || line.startsWith(RegExp(r'import.*package:build_runner/build_runner.dart')) || line.startsWith(RegExp(r'import.*package:build_config/build_config.dart')) || - line.startsWith(RegExp(r'import.*dwds:*.dart')) || - line.startsWith(RegExp(r'import.*devtools_server:*.dart')) || + line.startsWith(RegExp(r'import.*package:dwds/.*.dart')) || + line.startsWith(RegExp(r'import.*package:devtools_server/.*.dart')) || line.startsWith(RegExp(r'import.*build_runner/.*.dart'))) { final String relativePath = fileSystem.path.relative(file.path, from: flutterTools); fail('$relativePath imports a build_runner/dwds/devtools package'); diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart index 189d83c19a4f9..76469ef7b1e6f 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:dtd/dtd.dart'; +import 'package:json_rpc_2/json_rpc_2.dart'; import 'package:widget_preview_scaffold/src/dtd/utils.dart'; import 'editor_service.dart'; @@ -18,10 +19,16 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { // // START KEEP SYNCED - static const String kWidgetPreviewService = 'widget-preview'; + static const kWidgetPreviewService = 'widget-preview'; static const kIsWindows = 'isWindows'; - static const String kHotRestartPreviewer = 'hotRestartPreviewer'; - static const String kResolveUri = 'resolveUri'; + static const kHotRestartPreviewer = 'hotRestartPreviewer'; + static const kResolveUri = 'resolveUri'; + static const kSetPreference = 'setPreference'; + static const kGetPreference = 'getPreference'; + + /// Error code for RpcException thrown when attempting to load a key from + /// persistent preferences that doesn't have an entry. + static const kNoValueForKey = 200; // END KEEP SYNCED @@ -81,6 +88,40 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { return result == null ? null : Uri.parse(result); } + /// Retrieves an arbitrary value associated with [key] from the persistent + /// preferences map. + /// + /// Returns null if [key] is not in the map. + Future getPreference(String key) async { + try { + final response = StringResponse.fromDTDResponse( + (await _call(kGetPreference, params: {'key': key}))!, + ); + return response.value; + } on RpcException catch (e) { + if (e.code == kNoValueForKey) { + return null; + } + rethrow; + } + } + + /// Retrieves the state of flag [key] from the persistent preferences map. + /// + /// If [key] is not set, [defaultValue] is returned. + Future getFlag(String key, {bool defaultValue = false}) async { + final result = await getPreference(key); + if (result == null) { + return defaultValue; + } + return bool.tryParse(result) ?? defaultValue; + } + + /// Sets [key] to [value] in the persistent preferences map. + Future setPreference(String key, Object value) async { + await _call(kSetPreference, params: {'key': key, 'value': value}); + } + @override late final DartToolingDaemon dtd; } diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart index a5b1106c93c0c..2444abfb7bd79 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart @@ -30,6 +30,9 @@ class WidgetPreviewScaffoldController { _WidgetPreviewScaffoldInspectorService(dtdServices: dtdServices); } + @visibleForTesting + static const kFilterBySelectedFilePreference = 'filterBySelectedFile'; + /// Initializes the controller by establishing a connection to DTD and /// listening for events. Future initialize() async { @@ -38,6 +41,10 @@ class WidgetPreviewScaffoldController { style: dtdServices.isWindows ? path.Style.windows : path.Style.posix, ); _registerListeners(); + _filterBySelectedFile.value = await dtdServices.getFlag( + kFilterBySelectedFilePreference, + defaultValue: true, + ); } /// Cleanup internal controller state. @@ -77,8 +84,10 @@ class WidgetPreviewScaffoldController { final _filterBySelectedFile = ValueNotifier(true); /// Enable or disable filtering by selected source file. - void toggleFilterBySelectedFile() { - _filterBySelectedFile.value = !_filterBySelectedFile.value; + Future toggleFilterBySelectedFile() async { + final updated = !_filterBySelectedFile.value; + await dtdServices.setPreference(kFilterBySelectedFilePreference, updated); + _filterBySelectedFile.value = updated; } /// The current set of previews to be displayed. diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/error_widget_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/error_widget_test.dart index 73f85d39e6d4a..99c990fface50 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/error_widget_test.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/error_widget_test.dart @@ -51,7 +51,7 @@ void main() { if (controller.filterBySelectedFileListenable.value) { // Disable filter by selected file. - controller.toggleFilterBySelectedFile(); + await controller.toggleFilterBySelectedFile(); } await controller.initialize(); diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/filter_by_selected_file_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/filter_by_selected_file_test.dart index 369dbbddd98a0..9a78a19460e30 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/filter_by_selected_file_test.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/filter_by_selected_file_test.dart @@ -10,6 +10,7 @@ import 'package:widget_preview_scaffold/src/controls.dart'; import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; import 'package:widget_preview_scaffold/src/widget_preview.dart'; import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart'; +import 'package:widget_preview_scaffold/src/widget_preview_scaffold_controller.dart'; import 'utils/widget_preview_scaffold_test_utils.dart'; @@ -182,7 +183,7 @@ void main() { ); }); - testWidgets('Filter previews is responsive to Editor service availablility', ( + testWidgets('Filter previews is responsive to Editor service availability', ( tester, ) async { final dtdServices = FakeWidgetPreviewScaffoldDtdServices(); @@ -233,4 +234,36 @@ void main() { expect(dtdServices.selectedSourceFile.value, isNull); expect(controller.filteredPreviewSetListenable.value, isEmpty); }); + + testWidgets('Filter by selected file preference is persisted', ( + tester, + ) async { + final dtdServices = FakeWidgetPreviewScaffoldDtdServices(); + + bool? getFilterBySelectedFileValue() => + dtdServices.preferences[WidgetPreviewScaffoldController + .kFilterBySelectedFilePreference] + as bool?; + + // Validate setting isn't set in preferences yet. + expect(getFilterBySelectedFileValue(), null); + + final controller = FakeWidgetPreviewScaffoldController( + dtdServicesOverride: dtdServices, + ); + await controller.initialize(); + expect(controller.filterBySelectedFileListenable.value, true); + // Still null as we've just used the default value and haven't actually + // written to the preferences. + expect(getFilterBySelectedFileValue(), null); + + // Toggle the setting, which will cause it to be written to the preferences. + await controller.toggleFilterBySelectedFile(); + expect(controller.filterBySelectedFileListenable.value, false); + expect(getFilterBySelectedFileValue(), false); + + await controller.toggleFilterBySelectedFile(); + expect(controller.filterBySelectedFileListenable.value, true); + expect(getFilterBySelectedFileValue(), true); + }); } diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/no_previews_detected_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/no_previews_detected_test.dart index 4ebd631880021..511045e073014 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/no_previews_detected_test.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/no_previews_detected_test.dart @@ -25,7 +25,7 @@ void main() { if (controller.filterBySelectedFileListenable.value) { // Don't filter by selected file. - controller.toggleFilterBySelectedFile(); + await controller.toggleFilterBySelectedFile(); } // Start with no previews populated and verify the help message is displayed with a link to // documentation. diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/utils/widget_preview_scaffold_test_utils.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/utils/widget_preview_scaffold_test_utils.dart index 0b11c048de2bd..9eaaecd5d0f6d 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/utils/widget_preview_scaffold_test_utils.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/utils/widget_preview_scaffold_test_utils.dart @@ -62,6 +62,7 @@ class FakeWidgetPreviewScaffoldDtdServices extends Fake FakeWidgetPreviewScaffoldDtdServices({this.isWindows = false}); final navigationEvents = []; + final preferences = {}; @override Future connect({Uri? dtdUri}) async {} @@ -105,6 +106,20 @@ class FakeWidgetPreviewScaffoldDtdServices extends Fake Future navigateToCode(CodeLocation location) async { navigationEvents.add(location); } + + /// Retrieves the state of flag [key] from the persistent preferences map. + /// + /// If [key] is not set, [defaultValue] is returned. + @override + Future getFlag(String flag, {bool defaultValue = true}) async { + return preferences[flag] as bool? ?? defaultValue; + } + + /// Sets [key] to [value] in the persistent preferences map. + @override + Future setPreference(String key, Object value) async { + preferences[key] = value; + } } class FakeWidgetPreviewScaffoldController From 2bf5da46a5fd81631c3926037a6b9274f03eb3ee Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 2 Oct 2025 00:24:31 -0400 Subject: [PATCH 033/204] Roll Fuchsia Linux SDK from 1Ai6VL4vb_GdGnWhg... to Vnoygds8HtDUvGLCK... (#176381) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-linux-sdk-flutter Please CC jimgraham@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 12132ab82df6b..2f2308cb79f2a 100644 --- a/DEPS +++ b/DEPS @@ -807,7 +807,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': '1Ai6VL4vb_GdGnWhg0uhlZW3yT8DJinUL0G7BZ_yGcIC' + 'version': 'Vnoygds8HtDUvGLCKWF3Vz-HKH4CCwNOXv86GX09s9oC' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', From b77acbf30b8bbf48e5ead37b1ad3d2e468166f64 Mon Sep 17 00:00:00 2001 From: Jim Graham Date: Thu, 2 Oct 2025 00:11:39 -0700 Subject: [PATCH 034/204] Delete Skia-specific performance overlay implementation (#176364) (Essentially a re-issue of https://github.com/flutter/flutter/pull/174682 which ended up with broken Google Testing links) We had 2 different implementations of the rendering code for the performance overlay layer. The skia version used some skia-specific code to render the overlay incrementally into an offscreen surface and so we created a different implementation for Impeller that only uses standard rendering calls (and no surface cache). It turns out that the Impeller version was faster anyway even on Skia so it is a simple change to delete the old code and always use the new visualizer. The new visualizer reduces the time to render the graph from just under 1ms to about .1ms on Skia. The new visualizer takes .1ms longer to compute on the UI thread, but overall we save time between the 2 threads. The new visualizer is much faster on Impeller. Some work could be done to save some of that time on the UI thread by only incrementally updating the graph data, but for now we can take the ~.8-.9ms savings with just some deleted code. --- engine/src/flutter/flow/BUILD.gn | 2 - .../flow/layers/performance_overlay_layer.cc | 9 +- .../performance_overlay_layer_unittests.cc | 20 +- engine/src/flutter/flow/stopwatch.cc | 7 +- engine/src/flutter/flow/stopwatch.h | 5 +- engine/src/flutter/flow/stopwatch_dl.cc | 69 +++---- engine/src/flutter/flow/stopwatch_sk.cc | 186 ------------------ engine/src/flutter/flow/stopwatch_sk.h | 40 ---- .../src/flutter/flow/stopwatch_unittests.cc | 24 ++- .../performance_overlay_gold_120fps.png | Bin 16260 -> 16080 bytes .../performance_overlay_gold_60fps.png | Bin 16534 -> 16118 bytes .../performance_overlay_gold_90fps.png | Bin 16266 -> 16204 bytes 12 files changed, 77 insertions(+), 285 deletions(-) delete mode 100644 engine/src/flutter/flow/stopwatch_sk.cc delete mode 100644 engine/src/flutter/flow/stopwatch_sk.h diff --git a/engine/src/flutter/flow/BUILD.gn b/engine/src/flutter/flow/BUILD.gn index 89abc7244af28..c4a31be405235 100644 --- a/engine/src/flutter/flow/BUILD.gn +++ b/engine/src/flutter/flow/BUILD.gn @@ -82,8 +82,6 @@ source_set("flow") { "stopwatch.h", "stopwatch_dl.cc", "stopwatch_dl.h", - "stopwatch_sk.cc", - "stopwatch_sk.h", "surface.cc", "surface.h", "surface_frame.cc", diff --git a/engine/src/flutter/flow/layers/performance_overlay_layer.cc b/engine/src/flutter/flow/layers/performance_overlay_layer.cc index 505f6081c61be..ce4855e3e2380 100644 --- a/engine/src/flutter/flow/layers/performance_overlay_layer.cc +++ b/engine/src/flutter/flow/layers/performance_overlay_layer.cc @@ -12,7 +12,6 @@ #include "display_list/dl_text_skia.h" #include "flow/stopwatch.h" #include "flow/stopwatch_dl.h" -#include "flow/stopwatch_sk.h" #include "third_party/skia/include/core/SkFont.h" #include "third_party/skia/include/core/SkFontMgr.h" #include "third_party/skia/include/core/SkTextBlob.h" @@ -44,12 +43,8 @@ void VisualizeStopWatch(DlCanvas* canvas, if (show_graph) { DlRect visualization_rect = DlRect::MakeXYWH(x, y, width, height); - if (impeller_enabled) { - DlStopwatchVisualizer(stopwatch, point_storage, color_storage) - .Visualize(canvas, visualization_rect); - } else { - SkStopwatchVisualizer(stopwatch).Visualize(canvas, visualization_rect); - } + DlStopwatchVisualizer(stopwatch, point_storage, color_storage) + .Visualize(canvas, visualization_rect); } if (show_labels) { diff --git a/engine/src/flutter/flow/layers/performance_overlay_layer_unittests.cc b/engine/src/flutter/flow/layers/performance_overlay_layer_unittests.cc index e787e9162ac66..078c4e34f9b4d 100644 --- a/engine/src/flutter/flow/layers/performance_overlay_layer_unittests.cc +++ b/engine/src/flutter/flow/layers/performance_overlay_layer_unittests.cc @@ -73,6 +73,7 @@ static void TestPerformanceOverlayLayerGold(int refresh_rate) { .ui_time = mock_stopwatch, .texture_registry = nullptr, .raster_cache = nullptr, + .impeller_enabled = false, // clang-format on }; @@ -141,7 +142,13 @@ class ImageSizeTextBlobInspector : public virtual DlOpReceiver, const DlPoint& point, DlImageSampling sampling, bool render_with_attributes) override { - sizes_.push_back(image->GetBounds().GetSize()); + // We no longer render performance overlays with temp images. + FML_UNREACHABLE(); + } + + void drawVertices(const std::shared_ptr& vertices, + DlBlendMode mode) override { + sizes_.push_back(vertices->GetBounds().GetSize()); } void drawText(const std::shared_ptr& text, @@ -151,17 +158,17 @@ class ImageSizeTextBlobInspector : public virtual DlOpReceiver, text_positions_.push_back(DlPoint(x, y)); } - const std::vector& sizes() { return sizes_; } + const std::vector& sizes() { return sizes_; } const std::vector> texts() { return texts_; } const std::vector text_positions() { return text_positions_; } private: - std::vector sizes_; + std::vector sizes_; std::vector> texts_; std::vector text_positions_; }; -TEST_F(PerformanceOverlayLayerTest, PaintingEmptyLayerDies) { +TEST_F(PerformanceOverlayLayerTest, PaintingEmptyLayerDoesNotDie) { const uint64_t overlay_opts = kVisualizeRasterizerStatistics; auto layer = std::make_shared(overlay_opts); @@ -169,8 +176,7 @@ TEST_F(PerformanceOverlayLayerTest, PaintingEmptyLayerDies) { EXPECT_EQ(layer->paint_bounds(), DlRect()); EXPECT_FALSE(layer->needs_painting(paint_context())); - // Crashes reading a nullptr. - EXPECT_DEATH_IF_SUPPORTED(layer->Paint(paint_context()), ""); + layer->Paint(paint_context()); } TEST_F(PerformanceOverlayLayerTest, InvalidOptions) { @@ -237,7 +243,7 @@ TEST_F(PerformanceOverlayLayerTest, MarkAsDirtyWhenResized) { layer->set_paint_bounds(DlRect::MakeLTRB(0.0f, 0.0f, 48.0f, 48.0f)); layer->Preroll(preroll_context()); layer->Paint(display_list_paint_context()); - DlISize first_draw_size; + DlSize first_draw_size; { ImageSizeTextBlobInspector inspector; display_list()->Dispatch(inspector); diff --git a/engine/src/flutter/flow/stopwatch.cc b/engine/src/flutter/flow/stopwatch.cc index ac0e25ca05cfe..03fe10878f555 100644 --- a/engine/src/flutter/flow/stopwatch.cc +++ b/engine/src/flutter/flow/stopwatch.cc @@ -6,8 +6,6 @@ namespace flutter { -static const size_t kMaxSamples = 120; - Stopwatch::Stopwatch(const RefreshRateUpdater& updater) : refresh_rate_updater_(updater), start_(fml::TimePoint::Now()) { const fml::TimeDelta delta = fml::TimeDelta::Zero(); @@ -26,11 +24,10 @@ FixedRefreshRateUpdater::FixedRefreshRateUpdater( void Stopwatch::Start() { start_ = fml::TimePoint::Now(); - current_sample_ = (current_sample_ + 1) % kMaxSamples; } void Stopwatch::Stop() { - laps_[current_sample_] = fml::TimePoint::Now() - start_; + SetLapTime(fml::TimePoint::Now() - start_); } void Stopwatch::SetLapTime(const fml::TimeDelta& delta) { @@ -39,7 +36,7 @@ void Stopwatch::SetLapTime(const fml::TimeDelta& delta) { } const fml::TimeDelta& Stopwatch::LastLap() const { - return laps_[(current_sample_ - 1) % kMaxSamples]; + return laps_[current_sample_]; } const fml::TimeDelta& Stopwatch::GetLap(size_t index) const { diff --git a/engine/src/flutter/flow/stopwatch.h b/engine/src/flutter/flow/stopwatch.h index 71869aae24e8b..ffdda914316aa 100644 --- a/engine/src/flutter/flow/stopwatch.h +++ b/engine/src/flutter/flow/stopwatch.h @@ -16,6 +16,9 @@ namespace flutter { class Stopwatch { public: + // The number of samples that will be accumulated for performance monitoring + static const size_t kMaxSamples = 120; + /// The refresh rate interface for `Stopwatch`. class RefreshRateUpdater { public: @@ -56,7 +59,7 @@ class Stopwatch { const RefreshRateUpdater& refresh_rate_updater_; fml::TimePoint start_; std::vector laps_; - size_t current_sample_ = 0; + size_t current_sample_ = kMaxSamples - 1u; FML_DISALLOW_COPY_AND_ASSIGN(Stopwatch); }; diff --git a/engine/src/flutter/flow/stopwatch_dl.cc b/engine/src/flutter/flow/stopwatch_dl.cc index 2235d08904295..914430dcbb182 100644 --- a/engine/src/flutter/flow/stopwatch_dl.cc +++ b/engine/src/flutter/flow/stopwatch_dl.cc @@ -20,21 +20,21 @@ static const size_t kMaxFrameMarkers = 8; void DlStopwatchVisualizer::Visualize(DlCanvas* canvas, const DlRect& rect) const { - auto painter = DlVertexPainter(vertices_storage_, color_storage_); + DlVertexPainter painter(vertices_storage_, color_storage_); DlPaint paint; // Establish the graph position. - auto const x = rect.GetX(); - auto const y = rect.GetY(); - auto const width = rect.GetWidth(); - auto const height = rect.GetHeight(); - auto const bottom = rect.GetBottom(); + const DlScalar x = rect.GetX(); + const DlScalar y = rect.GetY(); + const DlScalar width = rect.GetWidth(); + const DlScalar height = rect.GetHeight(); + const DlScalar bottom = rect.GetBottom(); // Scale the graph to show time frames up to those that are 3x the frame time. - auto const one_frame_ms = GetFrameBudget().count(); - auto const max_interval = one_frame_ms * 3.0; - auto const max_unit_interval = UnitFrameInterval(max_interval); - auto const sample_unit_width = (1.0 / kMaxSamples); + const DlScalar one_frame_ms = GetFrameBudget().count(); + const DlScalar max_interval = one_frame_ms * 3.0; + const DlScalar max_unit_interval = UnitFrameInterval(max_interval); + const DlScalar sample_unit_width = width / kMaxSamples; // resize backing storage to match expected lap count. size_t required_storage = @@ -50,20 +50,21 @@ void DlStopwatchVisualizer::Visualize(DlCanvas* canvas, // Prepare a path for the data; we start at the height of the last point so // it looks like we wrap around. { - for (auto i = 0u; i < stopwatch_.GetLapsCount(); i++) { - auto const sample_unit_height = - (1.0 - UnitHeight(stopwatch_.GetLap(i).ToMillisecondsF(), - max_unit_interval)); + DlScalar bar_left = x; + for (size_t i = 0u; i < stopwatch_.GetLapsCount(); i++) { + const double time_ms = stopwatch_.GetLap(i).ToMillisecondsF(); + const DlScalar sample_unit_height = static_cast( + height * UnitHeight(time_ms, max_unit_interval)); - auto const bar_width = width * sample_unit_width; - auto const bar_height = height * sample_unit_height; - auto const bar_left = x + width * sample_unit_width * i; + const DlScalar bar_top = bottom - sample_unit_height; + const DlScalar bar_right = x + (i + 1) * sample_unit_width; painter.DrawRect(DlRect::MakeLTRB(/*left=*/bar_left, - /*top=*/y + bar_height, - /*right=*/bar_left + bar_width, + /*top=*/bar_top, + /*right=*/bar_right, /*bottom=*/bottom), DlColor(0xAA0000FF)); + bar_left = bar_right; } } @@ -71,22 +72,22 @@ void DlStopwatchVisualizer::Visualize(DlCanvas* canvas, { if (max_interval > one_frame_ms) { // Paint the horizontal markers. - auto count = static_cast(max_interval / one_frame_ms); + size_t count = static_cast(max_interval / one_frame_ms); // Limit the number of markers to a reasonable amount. if (count > kMaxFrameMarkers) { count = 1; } - for (auto i = 0u; i < count; i++) { - auto const frame_height = + for (uint32_t i = 0u; i < count; i++) { + const DlScalar frame_height = height * (1.0 - (UnitFrameInterval(i + 1) * one_frame_ms) / max_unit_interval); // Draw a skinny rectangle (i.e. a line). painter.DrawRect(DlRect::MakeLTRB(/*left=*/x, /*top=*/y + frame_height, - /*right=*/width, + /*right=*/x + width, /*bottom=*/y + frame_height + 1), DlColor(0xCC000000)); } @@ -100,12 +101,12 @@ void DlStopwatchVisualizer::Visualize(DlCanvas* canvas, // budget exceeded. color = DlColor::kRed(); } - auto const l = - x + width * (static_cast(stopwatch_.GetCurrentSample()) / - kMaxSamples); - auto const t = y; - auto const r = l + width * sample_unit_width; - auto const b = rect.GetBottom(); + size_t sample = + (stopwatch_.GetCurrentSample() + 1) % stopwatch_.GetLapsCount(); + const DlScalar l = x + sample * sample_unit_width; + const DlScalar t = y; + const DlScalar r = l + sample_unit_width; + const DlScalar b = rect.GetBottom(); painter.DrawRect(DlRect::MakeLTRB(l, t, r, b), color); } @@ -124,10 +125,10 @@ DlVertexPainter::DlVertexPainter(std::vector& vertices_storage, : vertices_(vertices_storage), colors_(color_storage) {} void DlVertexPainter::DrawRect(const DlRect& rect, const DlColor& color) { - auto const left = rect.GetLeft(); - auto const top = rect.GetTop(); - auto const right = rect.GetRight(); - auto const bottom = rect.GetBottom(); + const DlScalar left = rect.GetLeft(); + const DlScalar top = rect.GetTop(); + const DlScalar right = rect.GetRight(); + const DlScalar bottom = rect.GetBottom(); FML_DCHECK(6 + colors_offset_ <= vertices_.size()); FML_DCHECK(6 + colors_offset_ <= colors_.size()); @@ -139,7 +140,7 @@ void DlVertexPainter::DrawRect(const DlRect& rect, const DlColor& color) { vertices_[vertices_offset_++] = DlPoint(right, bottom); // tl vertices_[vertices_offset_++] = DlPoint(left, bottom); // bl br vertices_[vertices_offset_++] = DlPoint(left, top); // - for (auto i = 0u; i < 6u; i++) { + for (size_t i = 0u; i < 6u; i++) { colors_[colors_offset_++] = color; } } diff --git a/engine/src/flutter/flow/stopwatch_sk.cc b/engine/src/flutter/flow/stopwatch_sk.cc deleted file mode 100644 index c40eedd5acbb8..0000000000000 --- a/engine/src/flutter/flow/stopwatch_sk.cc +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "flutter/flow/stopwatch_sk.h" -#include "include/core/SkCanvas.h" -#include "include/core/SkImageInfo.h" -#include "include/core/SkPaint.h" -#include "include/core/SkPath.h" -#include "include/core/SkSize.h" -#include "include/core/SkSurface.h" - -namespace flutter { - -static const size_t kMaxSamples = 120; -static const size_t kMaxFrameMarkers = 8; - -void SkStopwatchVisualizer::InitVisualizeSurface(SkISize size) const { - // Mark as dirty if the size has changed. - if (visualize_cache_surface_) { - if (size.width() != visualize_cache_surface_->width() || - size.height() != visualize_cache_surface_->height()) { - cache_dirty_ = true; - }; - } - - if (!cache_dirty_) { - return; - } - cache_dirty_ = false; - - // TODO(garyq): Use a GPU surface instead of a CPU surface. - visualize_cache_surface_ = - SkSurfaces::Raster(SkImageInfo::MakeN32Premul(size)); - - SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas(); - - // Establish the graph position. - const SkScalar x = 0; - const SkScalar y = 0; - const SkScalar width = size.width(); - const SkScalar height = size.height(); - - SkPaint paint; - paint.setColor(0x99FFFFFF); - cache_canvas->drawRect(SkRect::MakeXYWH(x, y, width, height), paint); - - // Scale the graph to show frame times up to those that are 3 times the frame - // time. - const double one_frame_ms = GetFrameBudget().count(); - const double max_interval = one_frame_ms * 3.0; - const double max_unit_interval = UnitFrameInterval(max_interval); - - // Draw the old data to initially populate the graph. - // Prepare a path for the data. We start at the height of the last point, so - // it looks like we wrap around - SkPath path; - path.setIsVolatile(true); - path.moveTo(x, height); - path.lineTo( - x, y + height * (1.0 - UnitHeight(stopwatch_.GetLap(0).ToMillisecondsF(), - max_unit_interval))); - double unit_x; - double unit_next_x = 0.0; - for (size_t i = 0; i < kMaxSamples; i += 1) { - unit_x = unit_next_x; - unit_next_x = (static_cast(i + 1) / kMaxSamples); - const double sample_y = - y + height * (1.0 - UnitHeight(stopwatch_.GetLap(i).ToMillisecondsF(), - max_unit_interval)); - path.lineTo(x + width * unit_x, sample_y); - path.lineTo(x + width * unit_next_x, sample_y); - } - path.lineTo( - width, - y + height * - (1.0 - - UnitHeight(stopwatch_.GetLap(kMaxSamples - 1).ToMillisecondsF(), - max_unit_interval))); - path.lineTo(width, height); - path.close(); - - // Draw the graph. - paint.setColor(0xAA0000FF); - cache_canvas->drawPath(path, paint); -} - -void SkStopwatchVisualizer::Visualize(DlCanvas* canvas, - const DlRect& rect) const { - // Initialize visualize cache if it has not yet been initialized. - InitVisualizeSurface(SkISize::Make(rect.GetWidth(), rect.GetHeight())); - - SkCanvas* cache_canvas = visualize_cache_surface_->getCanvas(); - SkPaint paint; - - // Establish the graph position. - const SkScalar x = 0; - const SkScalar y = 0; - const SkScalar width = visualize_cache_surface_->width(); - const SkScalar height = visualize_cache_surface_->height(); - - // Scale the graph to show frame times up to those that are 3 times the frame - // time. - const double one_frame_ms = GetFrameBudget().count(); - const double max_interval = one_frame_ms * 3.0; - const double max_unit_interval = UnitFrameInterval(max_interval); - - const double sample_unit_width = (1.0 / kMaxSamples); - - // Draw vertical replacement bar to erase old/stale pixels. - paint.setColor(0x99FFFFFF); - paint.setStyle(SkPaint::Style::kFill_Style); - paint.setBlendMode(SkBlendMode::kSrc); - double sample_x = - x + width * (static_cast(prev_drawn_sample_index_) / kMaxSamples); - const auto eraser_rect = SkRect::MakeLTRB( - sample_x, y, sample_x + width * sample_unit_width, height); - cache_canvas->drawRect(eraser_rect, paint); - - // Draws blue timing bar for new data. - paint.setColor(0xAA0000FF); - paint.setBlendMode(SkBlendMode::kSrcOver); - const auto bar_rect = SkRect::MakeLTRB( - sample_x, - y + height * - (1.0 - - UnitHeight(stopwatch_ - .GetLap(stopwatch_.GetCurrentSample() == 0 - ? kMaxSamples - 1 - : stopwatch_.GetCurrentSample() - 1) - .ToMillisecondsF(), - max_unit_interval)), - sample_x + width * sample_unit_width, height); - cache_canvas->drawRect(bar_rect, paint); - - // Draw horizontal frame markers. - paint.setStrokeWidth(0); // hairline - paint.setStyle(SkPaint::Style::kStroke_Style); - paint.setColor(0xCC000000); - - if (max_interval > one_frame_ms) { - // Paint the horizontal markers - size_t frame_marker_count = - static_cast(max_interval / one_frame_ms); - - // Limit the number of markers displayed. After a certain point, the graph - // becomes crowded - if (frame_marker_count > kMaxFrameMarkers) { - frame_marker_count = 1; - } - - for (size_t frame_index = 0; frame_index < frame_marker_count; - frame_index++) { - const double frame_height = - height * (1.0 - (UnitFrameInterval((frame_index + 1) * one_frame_ms) / - max_unit_interval)); - cache_canvas->drawLine(x, y + frame_height, width, y + frame_height, - paint); - } - } - - // Paint the vertical marker for the current frame. - // We paint it over the current frame, not after it, because when we - // paint this we don't yet have all the times for the current frame. - paint.setStyle(SkPaint::Style::kFill_Style); - paint.setBlendMode(SkBlendMode::kSrcOver); - if (UnitFrameInterval(stopwatch_.LastLap().ToMillisecondsF()) > 1.0) { - // budget exceeded - paint.setColor(SK_ColorRED); - } else { - // within budget - paint.setColor(SK_ColorGREEN); - } - sample_x = x + width * (static_cast(stopwatch_.GetCurrentSample()) / - kMaxSamples); - const auto marker_rect = SkRect::MakeLTRB( - sample_x, y, sample_x + width * sample_unit_width, height); - cache_canvas->drawRect(marker_rect, paint); - prev_drawn_sample_index_ = stopwatch_.GetCurrentSample(); - - // Draw the cached surface onto the output canvas. - auto image = DlImage::Make(visualize_cache_surface_->makeImageSnapshot()); - canvas->DrawImage(image, rect.GetOrigin(), DlImageSampling::kNearestNeighbor); -} - -} // namespace flutter diff --git a/engine/src/flutter/flow/stopwatch_sk.h b/engine/src/flutter/flow/stopwatch_sk.h deleted file mode 100644 index 9a73b0dbeab83..0000000000000 --- a/engine/src/flutter/flow/stopwatch_sk.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef FLUTTER_FLOW_STOPWATCH_SK_H_ -#define FLUTTER_FLOW_STOPWATCH_SK_H_ - -#include "flow/stopwatch.h" -#include "include/core/SkSurface.h" - -namespace flutter { - -//------------------------------------------------------------------------------ -/// A stopwatch visualizer that uses Skia (|SkCanvas|) to draw the stopwatch. -/// -/// @see DlStopwatchVisualizer for the newer non-backend specific version. -class SkStopwatchVisualizer : public StopwatchVisualizer { - public: - explicit SkStopwatchVisualizer(const Stopwatch& stopwatch) - : StopwatchVisualizer(stopwatch) {} - - void Visualize(DlCanvas* canvas, const DlRect& rect) const override; - - private: - /// Initializes the |SkSurface| used for drawing the stopwatch. - /// - /// Draws the base background and any timing data from before the initial - /// call to |Visualize|. - void InitVisualizeSurface(SkISize size) const; - - // Mutable data cache for performance optimization of the graphs. - // Prevents expensive redrawing of old data. - mutable bool cache_dirty_ = true; - mutable sk_sp visualize_cache_surface_; - mutable size_t prev_drawn_sample_index_ = 0; -}; - -} // namespace flutter - -#endif // FLUTTER_FLOW_STOPWATCH_SK_H_ diff --git a/engine/src/flutter/flow/stopwatch_unittests.cc b/engine/src/flutter/flow/stopwatch_unittests.cc index c8fae3e047907..cae2f55b8d4a3 100644 --- a/engine/src/flutter/flow/stopwatch_unittests.cc +++ b/engine/src/flutter/flow/stopwatch_unittests.cc @@ -51,15 +51,33 @@ TEST(Instrumentation, GetLapByIndexTest) { fml::Milliseconds frame_budget_90fps = fml::RefreshRateToFrameBudget(90); FixedRefreshRateStopwatch stopwatch(frame_budget_90fps); stopwatch.SetLapTime(fml::TimeDelta::FromMilliseconds(10)); - EXPECT_EQ(stopwatch.GetLap(1), fml::TimeDelta::FromMilliseconds(10)); + EXPECT_EQ(stopwatch.GetLap(0), fml::TimeDelta::FromMilliseconds(10)); } -TEST(Instrumentation, GetCurrentSampleTest) { +TEST(Instrumentation, GetCurrentSampleStartStopTest) { fml::Milliseconds frame_budget_90fps = fml::RefreshRateToFrameBudget(90); FixedRefreshRateStopwatch stopwatch(frame_budget_90fps); + // Stopwatch starts primed to place the first sample in slot 0 when + // the actual time is available. + EXPECT_EQ(stopwatch.GetCurrentSample(), size_t(Stopwatch::kMaxSamples - 1u)); stopwatch.Start(); + // CurrentSample still not updated because we are still measuring + // this frame. + EXPECT_EQ(stopwatch.GetCurrentSample(), size_t(Stopwatch::kMaxSamples - 1u)); stopwatch.Stop(); - EXPECT_EQ(stopwatch.GetCurrentSample(), size_t(1)); + // The most current sample is placed in slot #0. + EXPECT_EQ(stopwatch.GetCurrentSample(), size_t(0)); +} + +TEST(Instrumentation, GetCurrentSampleSetLapTimeTest) { + fml::Milliseconds frame_budget_90fps = fml::RefreshRateToFrameBudget(90); + FixedRefreshRateStopwatch stopwatch(frame_budget_90fps); + // Stopwatch starts primed to place the first sample in slot 0 when + // the actual time is available. + EXPECT_EQ(stopwatch.GetCurrentSample(), size_t(Stopwatch::kMaxSamples - 1u)); + stopwatch.SetLapTime(fml::TimeDelta::FromMilliseconds(10)); + // The most current sample is placed in slot #0. + EXPECT_EQ(stopwatch.GetCurrentSample(), size_t(0)); } TEST(Instrumentation, GetLapsCount) { diff --git a/engine/src/flutter/testing/resources/performance_overlay_gold_120fps.png b/engine/src/flutter/testing/resources/performance_overlay_gold_120fps.png index 20bb1f8704fe4d9983cbb16f399ec53d247f150e..572a5bc0dc3bfe35155f12ffea7d600238ad7478 100644 GIT binary patch literal 16080 zcmeI3d05le*6-=DR;nVjs3-`OTE!U=We%~Rf-+eFnFK^8Awa+Y5dy>kp$I|+1sPLB zK&C{dFa#0>0hwhCA&|%zhCmn-$i&@g&wcOdeb4FXIq&`BJojmQczDc@{mXB!z4qE` zeLpMFSIrD1w##l86BCoTY;@t8nAnzS(U15S;2qQKz6tQ_bA)`oXl|jv*bj=KuL&n1F!ey@e)PTvz z#x`zqP=9-0h9$Bqz}hV&w?<7PsAoymrFoyq&4;$_X6@gmm>PZa*|U{|XBJ31~l9^42w zHTB@#Ci|&%s$M(jlb1F870?1Qb_Dmd_%`{UY6Hu`0dvRym{qALjz9-!2NGv zv$C9rZ&IUnPE)+uEV$Rj?3?`si;G4?lBb0Ybm-i0EF!kgi0XNya!l56sI$TI;Zp^d zSMz>qT+a`(w@MTxY9MKXs9*1OYHy6-?wk1~dux%c{Z9Tx8~nO(@7^x(>#J{m-XbP; z{&R6)dNHZbe!X;lIY>rIcs}U}xOA`E^>cY}?c4L`w}JnOOMUzdtixYE4*>7|DE8Uk zUi$I<8CWP}XT=T2H?9g|VsFpw|Nfs1`JKD{|8Oaxk{}l8AzHYrJny011N2m{moUxw z00QizIWSQFzCUW9$Q$k*2|oanpXSE?X2D9bf{vU>$H;o4L|$eTbKCdSF@+YbNUree zOFxN&5TReEYEMwje&35av3>JIx^L6DU4e1j$jH`rc=N(yA9+s+@F0`2va(GQ_^=Pi zk1{H>Ud=&v+R`qr5gnm8%QJ|sKNq{Y-k{ZNM4fT3+B1LpS|u->u%E2ys?&T6D(j61 z3lrcB?SQZNRoi$ks@{pO`tL6F_9xcQ4H~AqY5l+@h{3;|#6NY&-|^MId#Q!$@K&R- zz-n~#9Ud;SdySXKU1bmct(X7Zk-vGAoARs9vR?8CFm~UU|Kixcx3B*BC;p40L>pCX zIJEHkqo{3E;>^Hv5T14>e)V_u=I`vLH@mpx4_9GMV9}%;F8$8-mZ&gr9_9Dlq{6(k zsfKnJ5Qra^j5<~OI8kTPVf$vZF4;BQ#%x~SKOMFg}XcM_3)j_m1cK_kPcquHUBCVuo z;I>MK+l&hELtl-5Z-(E&k)MT@nPP}Ju0!QMIW4a6Pyv^uOY60GRy}e~HG~$%8QI{m z=j{i{-1hA@=K2X>hE!r)SDf9q!{fIXNfeb(cRFDw%9}VQfywxp(qk2aLU^ zKRJ4&>P1=LTv^A-lSs#N)9=Sq1``g!E#u_KSDzWV-jbJh3*)gN!$U*b=K_!8U~fy^ zNClm)cX&wVdXVwu%ZLRgMe%yNrt0P`m+|2^8clyRROPiw_lR(agb;WqF;Tq8I8uL-&JMruyUMb=dgfhhPI(Y505| zwmi{dc)iG`L^(pr45R13oyo(T3Z8s!JuorBugbxxkkGH1F*+PU=~0WI=e27CXw49d z?QyS@9YZ`##g*98+40z}mX^!#;VOShA&$tk(G@0^2?WvJ>t`meTgji)4d-LoY93x) z89Ev>so#6Ndr*12=?UzJw%@0B195Sst3|eHt+TU^`QeNVlI~M8#*->1tl=4oG_FY3 zu_|y5nN}}zzwDccyFp!<&7JOLOU&Y;Nl8=P%;*lxCcDbUd)mDQO#5tLVBqB0bLUKL z2|@E|N)cC`oS;e(KZQ-Dc&ICX9c>@N`b_Mt;)68yt`J8@M{^$^9~k(VgK&lst{ti8 zf`Z7G4upYpN*&4+X3<-OH95>k%`eWK`uIXxdCEyQ$lJM@Qf6@t6VB~RaV^!* z3tkq!v=8xj8UWt-LSbKBSBGh%TFQm%nu*x!1n%l(g70Oj0Kd!8-@amJZbHRM=hMwubX_1e6dZzf^>PbGZ z!Wq#+8GCrxa$^Kk=^ZjyIcrDS3&j{W1ABYZu1rXLMNoj{Dnz($o;NiSpsX)GnxE(R z8gkcfjbaeBP$w1R*5H7wvrzRP^Kcnk3Mi_pD@C$shIZ7UCeAJGIv5kwx7X-1<@%sMNoCvK+|$&tZT2;pll)^C#4-C8qM!2=c!5d|qzxw*CuOLq^mKRrZ4wVod{gAc7X=beIVzK@nx z;WuxtD{e0Jbd4>k)Wg2*mXTD7xSYt|Q(5uEJVVppnsGJk>Qz6bl*C?XI82VNwrG3I zD*bZKcD2UFlhqn?+--9N<9MiN&wW)>(=r`>ap%caOuUK;)b{j8^E`9&x#5~MR32`? z<6#Bs$$)NOo!X0cv$l0yw3$iXjx%W*n?J0)6&79>tgH)#uI%x6^v4@^A;(yxO%6G; z?(Wj9;P1cxzByeB!DbDXjAvHB$V62*t=7UCO3$y&w?HO)(}gB0U^73HyiQ#kR)lP9 zjBW}FN!#0?>)-TsrAT*LSw^VN&yU{Mpzma8z%z+HRJ;;5D1oF z{lj#+B1*7EP6mfqSG#*zbV5QxnIvTOqoL@jC|N@w%KAS4A*E3;SmH7XCvHrIX=!Oe zL<=&$Wa8cU*eQ<1ijRti3g6|M>uoq9938E1Esbdud_gR8ZJDzIrbwVxqVs~Oc#pJ| z<(jVp168b}r0ThAP`&Pten@*KC&x{DxPX^!76UE@nuIW3ZwJNu#0i_lfvUyH?B<6K)TQwq+IIG==~Nv~-Q&Bu(rIc8LFw?Y zWhkRl**>x$yQHLKA_GaY)e2$`B0M_#g23|*mK0al)oo4TE$|ejF`>&)`o@QYn?B2Z z`8qK;wqSU8;jXySDtqq@^;;=sj5WqoE|$U}BLvPr66LO12Iy%8c^lKZvtxz~#GTuc zSQ;8PVCb(Gc!Bp zXg8EjAJoh{xVJ5mzzWZ)A(5a<9}OgnovQClPBlbHcF8hBOKj9n*p>UU%_T*HrN)?1jV_1`m|jW1Wb1Q9D0|Qa-h8WsNnjCq#rFkH zsQC(>j<$9a*0yYF{v2$e_W6~Z?M~qvlZGmgshQrqm@W(y>Psng!#t9z2OhdP0QOPP zp~My%n!^%~Z1NY7B%bgsu{mB5m!ay?Pwtvs-2_wE4S;i@I!J1=2BE_|)nr8%yjhNOon>aDb^ z!6zmrR*CE(80=d9v*z`1BZ=!Vo*V1tTyWtOLyMu*rJS5n$&4Z5%XB zFeaG0&!Ma=YfDQXK5(E6Mov|G@&us+R+7ig>(XTpnzLS<9=$pGR{U0&pcHQqbsg`} zRnbSrX`+`WYC9J%ezPMmkfeZK{4|T$!Z>>5Ht95+#`TZB;B+GGNmLfr2l$=c0%_AD zQRak=wU+Y`e#y@f=(oHrKt<8us3XpVfpgq z*oAi@_lF{Vug%8k1jS`QH#7X@<@bbnbeUB%9mau!?@)^HW_zWSZKi&|8iIu;wzTXT zKyG0W$K%YK@fB8_fWYUuAa?@qJ5usU&`lQ4Ta(kdHjr+@Zi+&%ZnWK8JD`Jq9X3!D z->Ph?eKmkNSRdDt$dPe76U}T%MCd4+>0Y(G`Y|(fxka&7Cvdh8F&d+68sdZ&tmRAE`LPH*RhU1xPlB)BHu|`uaqo`P8%IWDK^T zV0L|3yEpp6x6=CJAPgr%a!#X#{ET=#?(~9`{s-4HOL>^^5C;%^AJx~_6BjSugO;N> z3j`HG=gW&RLBRRK6o?1pohp4tqyx#Xt1Bxtf>)=z_e<(7z9nn{^@W!@f1WO!$1rJi zsMh7lHo3@NJScvLF1aOu{AU1SV`*BBsD978k}R;+ zo#~Rt6~fn9x#>`7KCds|HiO531}xYyjvt4$$26WPX^f%2cC1~0(uUu&7eBd^kJQdU zjj#x8rv!&>moOIjs!koJGeP8NzD4;7tk1Tjt#ZtvYx!gC?;g^%QDg^JEDTn-pcODp z+wm|1-Sg_Q4|-V{y&O~K(G^Fj37mUr?HD{6U0bV&QP6~o{Pm#W^&8?!5eUZN3QNoM zcG7s9L!S8AH!oCR}4;>=C^)!@~Q13dHr#rc%|& zGBTA@eFMHKBh@P{y$vZJRX}ie=n^dTbGT^qbnSx57c<6JuGA2@Csc46X&p@=C{&`z z5w`Bw%cK#*co3*y#(Xorpr9ZZoTleOc~m+rWJVR_%hw5hqxB93{Ri~LtE;P>1UAo& zzT=iryqS$MPN5Uvq(jWN8~*bh9Nvqrl!Ci9|2AaMHHvu<$-z>VW~z@>B%O z>)0GfI!ta*B|<8pCE&!~xp)b6Rl9GWPUG_Pt8FYS5dK5uo*v=cUW{<&*-=!}0dVv^ z3ec`ZfAih0Y37N`jLE=5SY0!7^U-8;xc~>YCJLMvG%|9<-A}QARZt+i{mz{l>E`$@ zRu#=fvN8<*{>iXTLw;U~zge>bvKZG6_Ga?)b9+O%Gef1E zhl#ueLOgSXz?qOluQybJXbHBgG+4(SNhj@tCPLyzLV*qsP|DDv*L&ZftB!3fUs|&6!J+@~c*b*X7&0x>$o%{^KJcp9ZON zvPtgO+a$4ibebb2xudD9Wr9Ut>u8%Z84lyLS#8}ald!hdb}D?i1(7Ft;O@_BF_%e0 z38Tjj=KA=Iqe>|@M~-S`kh_2A)>t&Rw)W(@pO{m>Vc=jdt`xf;q3*$5Jvk{MfvlWl zR4OytgGo%U<{=V#Gh%ZUBkaY(eR6doqpY(cKzm!1G z44Yi4AsF|Bg>6kKEp6i8@7lio)OQF3AwlMprG(c0A-|n@tXva{L+Dg{hQ`^nb-y7_ z$dd`$elv)&dFLDxBv>G+DKN=H@X}J<13}D0p)NX^nVIuJnw_(Sm)ko7`+^b1Q_$kk zP-y?L9<;Xhu@HNZ;)Jm3NSfUv4$C+$KV%_Z&VOmFk-sSbIq*Y9XU6f6PxrU6Hy5Lz z{P|M1Ha#yduhfvK_7qj6h&LcAQpTI(p-C7FdW2Dg!{KJ<=GvUhmVP zzP=oLcwHd9_4)D2si`Tp53M%XADp}}uT;t&eeozs+->`s)&A7r720u_ZzkMeZCLQ#i?4zJ$ej*PA%(dcif zJ$DSr5npa=7HJHHUs0Qysxv^$8RRs{!F6jvX72+k0fJjwk{h|8EHCc}V)DmuG+H;U z<-5`?Y7gh$T&xkKq_zDy0y+QULhkjF)^{AsEEm&nYk<~ z(SujJ4e8ZS$H{$t<&C#WgpzveKl=hN;+bb?TUDbsg^wtpf^Bj2o!-`1T3WgQoI{aP z38#;xr>C!$d-j_3lgZ|Tpt?$6V9>(J+1XjPoYpCzhx27o%*JQ3GSXdsx^x zQsAecFK*yKU7fMNa^*_u3TGkg)wu|%U5Z48zM-Kl{pCgYb%zb#yW!#CTpjvGXU*{N zaAqYvocEOMi1P7)!{Ne2Q1T4CmD~f$p_*MFssz?YY-v1xz4mO@tBC**#LP2@ep$I1 z5mHU@P#a4J2PaLx_Bxaz1jSul?W{)7aaD}WvH zYpVU^)Nyqll^rMa!(Lmg}6GASw=d9zEwJ)IyYPf#-a#v0o`Ac+s_&3zO8W?m!+!(Y9Dr{Z8b z*c%%wOCSKQEl@ch6x&036O~!=Wqza7@o*j$jbgSQn!R)^g&9rS45z`j2|)mvno$g2 zAAJbrcE=0zi;9YLhF=nCt3MP4eUvYypsSva3z;5JdwW}33U{^F9A4c$1hn0`W4}_S z0Wo*Wu+{Dr(@H|*oRwqv84{6*op+B~kt^!|p{c6Vb2|ao(CB?_`ZWL!VmmSan0EcD%7Z`p zz1LT}<-DSXhK7>tEn1(zet|5UUu1AXWSW26Q&~?PiOK6X#*L`Knm(s{Tw=@ReJ?>J ztPH{o3?^?<^ZEzyaphoMpYBLVNQ{3$)pI@H>YncM z>e7&zLG>Q8W>1iUnOyAZzJoqdY2D@q15z=9+aKpCOla({_bRbrHb3-Ds~U{ z)j!tm|0@3j9(8YVTj_=U;O}D(|4}9R{C7#V<8#ihCzl@_S^*=mE#m)-ApSom9sgN3 z{HbuFul!bJ^vfGsB+u#|Ra@XVt+JPZd22HW&U4Rm_q3@j-Y47u0s@f*zg<3ENi zUq1q{Whn{~_Y~jT;dgw<`IRq65Tu6ne~swVIBU5JA7%$sTq6KV#7>bvemw2_X5N7K zka%|~v*{h49*g{F$j#(kr7K?9jdgz2tk|gq+)bmQ_;%FOnh_4QqpC@Xs~HY*zc;0S zK1u|UKTX+J9MjbuWSdrEcZ-SLeDSXW)WDCVOObXw(PB=YdGL?PzxQkT?K>EGUr-$_ zdMSE6-=?O;JT|-&mqmm=8@?uHwMJ{)n3|x+W z_OB6c;>1FX49gRAHRPH)3!dx_%Fl9FRGd9|FNq?v=zA{eF0cm?B$0)90O+Cibi7H zDlVZsPZ^`ZQRYYZ!+h{JZ5ZDK6Lx%~ooi0NcE)E2-viJJF=*9HqV~|$}_Rs_saRr8FMrj9j6EtP9VA9B! zK;Ha^$S=rg{w@F-c-I!W=i^Q1b|3mW1wbBKIx$SL5|1}C9qHM@>-o2t-;m&|`ix5a zpo+r~{bYZ?b3>weWMi5}lokOQ^#F^dUXupF3Z>w$_VSQNF>@?-C(Pt*zsan&VD-OFDht`9Nxn0zoP0Pyx>bDR!jZ2Mx>r;&mr< zCWxTCkIg;KiY>MI3e)PD`b5IsrvE43e}LpRha`uh&Zf_#=A z1D=Yhi3++h+iQ-|P6YCnvN_Euz*uFWZ3q>zy?0ACr@A`-?%~7_fGuWOFf@k$4u``s zS(eVuA7 z$X2cm019abheKf~wl25dWh^Jhw0dRA=k<}3KAm0eE)+aIHFJbOE)NJw%E>dKsJV~U z2U>y5y?40pY~gW*uJ;O|i!9~5YmGCeX~Ols90nYYW1?0%HW|}77>U?p%3p^q0Ty}I z)5+hSy51KpjSSos2oY%$0I4kD@k@{`n4D{kDV7DNEp7}apeBVG85z|ggtL+dG)0Pt z{~m>rgP!kVa939TF(z~f;a^VN73=AhI`Wmi_;G7tXlU?8%L6I>yR-Q+Ugnt0f$G3H zju~{VZ$|~tsHn$}#mTsysTRno^V1SVG>vV0B1r-{vS!x!J{t2Ry1BOm$mT%tRP^+L z$w6<6DHM>baYBrA@63btsSb3X?wid5t4&=Z37R5UA6Gn^oRryWU$t1=IyQ!=hCnXP zWE;vjq(gWOk5AeY=>f&$-B*mwCrvYI2(CP~N2*7c@aNDsLs*8es+Nym2bt$9|x<*W#+BWx&$A zZHmL1C^mBn$kBjj)d3*U2C@tf_cF?b%a>ayQ3@f`**dJ+73S6X&&9-?Jf13kkIUCO zI|a(~F8B!78)4q{V)emRxOK;iC4b8OEt2UYM8V9IxBA|_C+@;-tS@(u?>Qc>1uUeX zCtTl_r?)YI>Ie>Yh!)Ac+m#|fl^alwr*)r0E}-^+_@nO8$#Vm~zTTN(j^Alz=SGHK>Eu0_@ae%xut`zZ+YVSw+ z$xSv8k<<4ZyhOc=XD1b$OQcniP^9TZI#ti5X$p5q8@Ukra#F}cs|UY(5cphLS>XU;eG$UY)@kEm z1BD3*Q^WAEIkYKoCy{)xG6nUL9ZUkc=$<{w+jq&=aQM}T#B0dm5(E>tQtaIH>uDWS zP^%kDRRs7bs81BBX-m?Rw8R z*Lz$nv0)b?=>E-lF4Um{R87s5UeNUb)PAsM)FY%oKaL9Tvn*n`Ej7#FIQ0vxGw=MW2Axz13>P=`oc)O1nd>Zj??_^ z;iLXC_l}y`_jh9Uo*dffFRp{2dPK+ZO+07(#aM9z&c~Jt$RttdVa9BuJoDd$uTq#&jP@J4s`A7 zKu~c7%Fyz3cea|s-B0(C+wed+^IoTt1XL*K@!&k0Y)jVIndkEvP$rHne^$4dz|=m? zO~3MF!z*$LEE^PHM^W#|n-hZodS7$6){~G1mT>g()$}WSJa#GY2PlMv5RV)7_JLi1 z`#J7o@?})6uhnsAPU%NL>r%R^3u5N+9#TOts}2g)@}qINZa#)_MO9Mz;vhrt%73ky zf^L>4JjU&u-ogrU^0`%V-!JrB^o6m)x4M15kRE_8@LCAHqPy#(q_jIi{;LIxJomr_ z`sT-K<9IFS`mC1+4U;M*f<}@R(4R;=9_cmW4O@at@o;l{5?o?)7=L$3t{-rGe~SP1 z9n?Et6#Bdl0p7st%1G;Tut4~=m$>ZE@$%XwQ-L>|Z*{#>+SpiJJrm%&d0rEAh?pD> z?g&3}%C{rGc*L;=!AQ*87QSgV$@t+I_u5XeHG&r>P zo9OOR%3XYR_DdZ2VGUPb&OZke&dH1NAJKl5=@F5zpEQ3iEBh`ZnY@M@dYPB^21MiK z<(gBdbcnL@9@d%)kyI(6szh!_BXgtemPwSAr=uz{lBG5}-8axi;jWavrz z7d{jF<`~H371i8Lc;JLSj^o!B7Z<-QEDZe+P4rD#UT&&&J@Z@>^fPUU2)&fP(P0&I zd~o}VGA$t89);hoBKWjEgSh+xU*6u|*5wdf1p2ui)YK%y)P4EmO~z!1WO8hIP*4&l zDH(G_d^8hxX*A~XsqgIV^Z13FM8ezPCPJU0Mk#O{(;T~#LBErt4*n2htkFLVxQ)>(T<}TI zQh0|gIuld`MWXZ9vOu#A$!hQrJ+^f?!;WM4!w-{zQIm!H=Y`n+8^%0RK?lfP?y$%+ z^LYY}r`+7!R=`Qq53JYXa4Js|6V;|A&w#oj5NYuj?pR|ChtIAgh4BGQ#3-ad(LxV^ zd*E~;MP6S15I|@?3S4mwLlI$8Py5iBDfoV)JKOkF(zEo=x|PoP9MEF2GxnK^BR|c5 zWW)s|4(RC{aJaL(s5!tqBemCmGIkWyBxY-?yIuF5qIRsu;PH(lq%F6I@&kWxLupw=LmY>KW%NIqJRDW}! z;Sf>dPCGDgZ{OK6m!b0F9DwaaRE1OJZ@2?gsdhE~hm8|3X#JDkOq9=1r02!Cw_)&khN|KI+{ILJoBO)5DrZAWc96XDug%0-g-W zK5Y&sR%d;AxUIFd26S+Rx`H}121xily}713R-kRtnT&NX&O{40k_!t9sqhJ)KP?9O zS-AT zaKd)kZtd|Nl_y;t^Jd;4lU#Xw+f&qI37W8OXPh2AzXUFtiXdyHOS47ZHX2L;D!lna zU|`tjTU9__R+%%sy*iA}Fw$5i%25`Rn0N*B->tr(c`C$#B%1hI3r7+?Ac`eujHs>c zd_Pc3*UA7$2xNv$ufT#gq}=CwMS9WYicw^&vOVaX3jBrtt<9n{0iwi~0MFLuH1_yF zN@i!dFZujVbRce5Wu~<9-g5L3Ya{s)X`YJF8lfw8ow8lNv{oC;x-Jy9L=~FsKD`q_ z6c7UQE%Jng%Id!FZ*!1VR#wfk{e^TWa^c1#=&DfI1O-SQXikMWi7Oq@3G)7UXzGg< zW?5Ibrd#W?gP_mVZh0(5MGy7q?fqa;{&;=#0O%L5Q8I-5eL!O09VaT>_>Xp2)q&u! zyseD3I?;k^K5=86T}2nN=?a89(KS!697c^L3pLOD@21QUMh519an57IdH*^4HKh9+ z;je&1#iMM~Pfhe?gK(8!dB{^tV@4VyJ3bRG+vNN7+J6Tkg8*5qfSgqxz+8lT=dfkC zkpF(6dnco^NAVUKuw4zxk6UWKx^t=bZ%GI^QI>UB!?p>4*kT|$sudnx=16$Npz``*ZZ47WmTwe_G&A3;bz;KP~VdVu5=n7rZ{9?rmEXeMMdD@h^iF^fVxfpAj3Qk?KtQAk z0YXbq5F%27^iUE3DWN1%L;Cw??z!*Y``$fs&Uuc!BV5esk%-7IB3I6z zyBYFwahw@o1$&^tUHz-`;hcMipMTQ+(d6CEr5=f=tHXU0%1vrmh8d%~5f9gZbEG>H zrb@@(A}6EYQqoH2%j}S-k#V!(oo(B99G%r1etxC$uVPY<6V_fcx54F}JpJn<-*JBU zTico-MnHGntnJP4>;RI!JXp*hJ;JR$U+=nVES(>%OX=wuMH|5bcU9q1k`ij3e6gRs z?DfddfbuBfKzkRVTjmQXPkGAf$@*R5TO8x;7hj^QNjBwF`pOR?_sZSf-1cjYEq}W# zs;;r*BTL_$>AEwuuD)nxgFUKua^&-`5$>9huCh1wKzIH=MBJDz)ub4$rUSC z&{|}@y}kJZZ$57m5h47#S0*A}FQUWdFJR@i&+bWpr=Na#wM9ha>|ez|s70iH_=^sB zI)Cq94xOCT^tk@rbU1ehc=2M(j(>mb58n3u>9EUFDx{_peDG$NOV1DBKK)I7V9Tr85;15Ae!&z($4++06HAXyk=8>Oe6ir0|&wyF0Hbp4Db4=DnU$T525+) zPd0QeqITkP<7K{>=LKOotc$f|UWf+z^X?0>)UxBEG-T5zVj2j@X1SVWx?LR33e_O=Ii#j<~f!E#D_o%C{1t-j`%18E4?5&{bIv3g8f!aMmPYU?( zFVXNjRm=`tz9cA1c=Wc%(YjDa_FDXx7s9l#G&b(2orq7^+{-K|;FadNy%*N9yHH5k zq}R-{%jOY?mCPYYNj(;-5{wdzU@2nj@vv-mQbS zH3Cqxzqwc!N%Y7d6JppIrO*y|2kXGmgt?v=@}WW z>+9=D0I6)FEex_`LdIvzP=^x;Wo(J++6DO`lKNZr|Hkq z^H_58Sec#q%hD20)t9==@$^Im>7eSGZpQwDC-!v5EKkHhG#l4?jT6jYo>@+m%EC{E zR@{PiX5pO|KgKr6SlsBfe23=rExD4+8kEm%&Kd|!%`Ht46JU&yYLE3N z`_&{=Z@qg}mzz6TGxiWPL`yBtt@u)}VDo_0&vEjR*X$$8Znr)0vMjd3gRagI>`qQf z+8JNNWvx=n+&#zOEcSw6VtA*U@A*=j^^Rq*hD8eRBh6DYEnAC`Id56auY+draLea^ zeI_m+nax{tm8`+xw7OGIO(FXV%?-32+n%Vw6n&N_wcOjDdK}jc-{i+^Y~{Ek{$ z)YXL=ipdN5EzG@S>r(&@!T7ZdzAEf%trM3%Dq&lD((miX*j9ZM6B<_V=8YB$gx{?8 zdJvbOq{=vUOc|f57dXcc0AIi`K$e{f`u=r`Y-_8XXjfNPwS45epoPISFE1|(M%`?d zDM@d&Gr6FkU{`BvYnoCLxyLuKvpq4cJ|2ST4WQdPx-;oS(&5C1%k7DhN#nh7;_~X6 zbcou?Yw7#b@v0|GTCwEw_GcjE;&MX$uNhrO6%`e`C6V@yQ~XmwLCVf3W|=P5hgu(B zQ=^S0dw%euQI|$>ByiSzhAz?@B_{v+Mo})f9Y1ydAo6u#yh>w^7U-uEzLB!hEM>U) zEz#Uv(`zJfHcDK2-l5_ap}wBIb|^t_#U71DBgN!*OCuGMPgon2)z?>_+1nA*-5q_O z$AQr2Xhl~LY%$(cf`JzA)3fT?cscxn!--@`acS>2X=j#OPA!KV9UQmQ4f*>1p=@U6 z)V7#zFRx6hokc+I;SA+YuaPw$vzMr;{9vA4H3PISwbBV^a{a!LJz#PR6g|d+?DoLr*j|9Hev&~?|Vo$idH-oEz^}%4d5YpWtiHEhAQrr zM2Q49kBre>hGIx6Z*_TKpzvyR8*PoYA3ur{Owh_@uoG-%*r;AFUhUfEA`wV^>P-_#6ht=h)qBU1ZtX01RP=+E^qn z%SYY{3Rw`*w9nD`WOb7}LG!c`JoXtYfsb=ZwMbl*0 z?D5U901|JM)>nub9vsv>o@P>jSxL<1`qxtT;PLn<3=@fAmd|u$;28Zy7Ekr{^^?Gc zKo?t;jwE+XPr}>-R8r#b+?9@EVGW&KWu13+o~~m=FdN_nrWwKBuOt2LU5)IPtRPIO#p>N32RhDxi^4n%-P&qOvkFu%Pck-4H?$=Lo?xzEz5pN>8`n zKh*R6I{E$<$w1F>c1w*(!7-fwv_XRT;a-H^R$GL?k6dIS-eAC<>lTNN@}r~k9EIDU zd1J{OC%NNE`w4sQ#qknr222(jPOSs^=t-J&yt6zxM2sR0g_PU#I+p8f^%@2I1Ss$8 zLjzA$#d%|O4g3vhLoo%j6Li{K>WCJG57$@@(bV*zj9NxT6>VneCuGSM$xctpipK5r zdb30;+JDf-F=C=QD!sY6xgW#v5iCjaS228&fbApL@WE6$cO;V$OjdIiu`d%b7@8~bX_gC-kg<*`HpG-3N1>=RMwTmqZ+FAjUmITsh}X|=H~hOzB`N}=?elT z^Ah+eZj#VVgo1fmrR%T`)VAta32VuxMUuY^F$#sjLe@D{$%f!1n+WDOvPtm0MY8Fq zxYiXP0&4~t-HoB;r_h&Zw4z%$?{S%h!BUid($yrw=470toy`o$&<<5l;k~CnCqbg6 znz1A(hus*ux^XnI{R2k85bzB2S3A{wBr{QyA7g1)moBz1Yh}8jYzBEjO#Yy5uqXTR z9;IOD%BMpM##gU~QBe#$D#l|~T($9gCg`;0O<-S8(DOxw_Q#JM+ZPTNGnjwlxa#M> ziu`igdl+FRBg2G&B2~VREdY&gEnb#Xm6Vbq>4YV3CYnR*2st?u4sLF4I>Hj2$JtnF zaGc}z_I5ugE33bcbuBC~6uZNq1i31hY`!}I0u7Qfg`}`D@jQ){n@jl^x&2bx;&=ny z#AAU=kun^vTWwd9G~jAkD43kNp`-qAc&xEM~IGheTpRJ@cD&Dl3DbP3`DRnxnd2F!Mz*9yix!vh;cb2|x zMNyF(@%5F&?!{4@4?S5KPA@7g#qVo=shYG4lQ7PupfhA5JVfZ`#%VJns^t-WTAB{{AAA;$G#ElVyd4N6~cKsiCq9O{=B2 z=9dq*83Ft_x%@p2!z*t|2WfHwLEzpJlMf6ImK&?_8XdW*m~=u#r8^Lbgbnuhzsxki zM8H9p-LnTDFxQjgR%ffSHvv;)JMmBwnhLYCL)Vv;l_^9@J&uc018`2pP%Nb>(cw$( z1(aOaTn>6-wR+s0Qtc+}*K&BijZxNJV|xiRrX3a2EqWt~x7!*XY2mNPOYMib4XXS=hr z`>8#?PMynB#$g8zKou9}QhQUn_I~T=(ESly;Mlb@lJb$BnxUN2<)O{@$-F-Q%s?w8ui=6cH?H;g;HWtyc2zA35M2x%A69c19*g5c{zNR=dvA#AzpYd0~pBW^f<)g=J> z!q?}e59sI|W%^LKi-r3718Mu!YG9q29Ph5_*(fQ)(`m7-4t2x6suw$Er&w88>4E0v zrfe?jN>R`PKWsi;R@X&;^Xo%SBG~bIa)RCz2|z4pSMzTik&jf3s=a@|mz1qJ$LXC~ z6csHB(Lqm9zCwG42L`HN?B2bmVD|D>e?X}-!b@PEuuz9AE6^`3T@3??b_;%M7*hV$ zU73vMRP;@(`r#j^yu;jCo#`?qdurC*X^D=}@*Mu&N&=nh41JlHEe?8)t3q_vWwC67 zBtRQyq6z2s^^U^Rj)Puq8`OHJmPtWLs@m1BC4x*eF#u`tQOH+5Y}qF9l09X>pVBfE zo9M|g+O>7tHe;(YTh?-QGFqcascfh|Xu-a?xR}$c!&+JKhvR<`k=n22%$u%oG#yuS z@hnc)MFv=k8SDV0BZwAQ9|G=0!%_3BvHdEcH0w>S-};5P2>2?*@%bS!V__cwnN1iJ7L|tFKRfzDLR4GlYRR za9^5#%jtD|FDfdUCS~~M&70aDfDECL?=W+>%+1X&7#SIPh~>=nlGd0omtNX)PfJUW zxB#pDnFoTg;o0Rmc+f1}7Qg}>M;jR=2J zRaG?$8a~m1$4f$lqA)%_>$ii}+n-C(xs%7AR#wKf$H%wHR7!@yV6bJS@<#um`GI04 z6-hdPE(9H2VIL@2bou=m6);}IW4eq+Y;I8z zt*06vfs7Z9DpH9%a+1O#hsWxA=zz~8A01bFXEH@ckdA+z6bAHaDSu?gc3I(y@s z6Wg5%3TLs~IUPh!UV;@;1;NjV78R$wb@$08YdL?WJO;?i$+%;sOzCETX&X>kdcJ(Q z-sCscRt=JcxlP!Yi1D%+H?Cd#?QLo4lI*^Hm#eC)HK1!fMlxl&wdMP0$kz22)|Qq7 z!UB;XDW^Al6P%lN%CW5wZ0}tg^znv8K(f$&cNMmah}4dIe}<2>%qHj;_8lR2WoW6E z7Z)#B13)}FOUf>{Yl`48p{#F-`vYb=wf8~c#WtBFV)Hf#_mwgL{-w8;9a`*=yzK24 zF5qLAMg?EjdOBG^k_fAB@Yt5DjH>pY{76lTl4_>l6SBMjgH{We*{JXs;(JcMIF3?^ zKWH7mw~AVoGMrnG$sjigaAQg;kso)JfD?GBrQ`H9p(ZGg3r)c z=&Wj+^zPR`>ms3{VRNFcc^bXLrs;vc09&Mjv)Y2XzQ*@A4UN(#=$Q{5+`olJ2Uz?0 zF<|^~TQ8JZmhBji+$4=Y&RrT~_0w!svJf?kNo;V(OL!@1OXZ>0-&@v(H;#_XtN~_+ zAVzMF>425_jvI?z1APTcMM44!FsaEBRe;a10`mCxt~*<0y>m7;ta(dI*fI|^(44<>mJ0BP zg)UQV*gvugz)64fIDhR=TN4-$f7+V=m26FA&0AMQL?EidS%5z+POyFdv^f9ffBk83 z{{I-e_^)Dd&ZTMxz^9+=*KIor{Gz8a0`AB6XRSAaaj`%#2Ll)nQS|Lalat>CP^Rs% zC0gCMDbUP)s%3uefrNi&$dEEU7O8m7GB$-DPOeNL^41YJE)}yZjakM5E2t2|E(|KV z`F4{e;F1uHKdnYz>nz;3L4lck+~~wx@D0JZ!>$j z*>DP+DmscY7?0Rktm2m|smhVs0F=xR9;;2R1j5w`L5Z}aS>OT-ub%J_GrQd^cG zS`(f+TD=g9O-fpd0stQ#u<|XpY;cgJ4;V%>>m)Gzq=8D#jZ||jV0u&eF@@!UlgA!v6 zzqJ(DLV&zzG+P3`od?!l+?|>6VnoRGXG!Mu4#W11n@`jIrffb-KnP8a3xklVof64T zFyKp~Piy;j5P??pXlO9&n0`d~73Ag#-{gPwBje+b7XhWlU!HmcsP#2>zsO8ERB#{7 z+IR5$)a;-X1(!~-3R{(MW)iIV^%T+QL`s8fmvFW6uMp=#Loc0kqqTm^F=A%x2DpR? z>H?k?2`%DdDs;s+Z`oBRBc%?=FcXT3J&unyAji{y*A-L+$Zm&=cvjLITJC=mvOEB71{xmVim=C^R;!$0wx197f1X^jQ3KqeRtcUYq(;m70_HNnSAmc zFeQ8%>fkg6MAaa8q)q#-q+aWvKzP8Iwn4059aAccW3N%tbXyMll2kyx+$VJj>L8uV+K%XALAt zya7bi5#8WOa~F~X3%rLR_@-dMnpVgTzkvlhp)N2?u1N!}b^v&skkD_>nDYw@4JZ^! zTtH{==?FkUeW*nig$FLVulDGtcT78|HgQ+p&dkh^0js`lZ5>K)pz`R+;G;5Sin0Of zXIg)A?U4~CZrECxxUg_QPo6xA4Ox~6!n)uQL7U&vv%&Q6O8xAFsMa_HL>h`&3jrK4 zo-{o(>#vKxwX*GCbqw;Aeh`*iQhH3U6b9gcZ6N~_a5U~Q`)4Jq##rSwMiM*G>(s$DY@$K{4}O zC1mRvW56IG1TRK^bYQ&;q_O<~tqL>C&N?-zv+^%Fv`5Kmv5`E52dcZ_b;UjW76jip zWKp_lb8<{lT4hHZ&PTE%MU|Qa#4X^$mP2BRQ*}VLV&@a(P>a7gRd;|<1*=C~#G{P=)S&{qj2SIu zc;yPSRa9Iv-WVwB;ZQlC3#4t(=_7$hEnWZ>5VVnTtcw@Vp&)>ib*YDlOX#inOw%^QTaLcdAM+ybp8gJg6(VhyY(@AfNw5|Qu=Io?_?*AfP zC&;`t_kv6tD3-55C*}6qOyvYEAew4)k*&Yz1oFhCqX%tOq~Sn(IizG&GF%bPT5$wJ zWMp!v?`Vyehk>|OYYy;2UV$e1b&*_D1k5>5%gNh!K~I34h3MB0nx{YR?q=U75GsIP z`pHFng%$oQ#83>(YtTE^%NO;+*67_i9{YgP71a5>{ln(JdVQTe%fMXLTm9aGQ44C0 zJd}T^3o1F3HAPGbCZ%EX!+Kx0I0HM3mZrzLX04g=DO*RQ2|4+(#repQ@pdq}kZ2*T zs<}hLjx#?%GOxX1Cv3%*Ap5B}#zb*C4WG=J>LtJ{9LT_3N}Ex7iv0B%;WuD=lp`B= zkx7&vZoOdptI@+bOYi10k%q&y6-P3<+Ooq}+ShmP+$n8rY}^M5CoofgcQdDYNhj>; z14+O{z33UBm~>S_LgHwGtghw)fkQ_H%!L>kwG=c7I8A~?G-pGj%%=M7v^#sRh)4wF z0`%3aN_&|RmQ>WAs!BPT1#Xu^Mfl~T+op_09{@x_T2D%H6;Eux`K#^T#}-8^i_Z>G zwQ;@^(SDhk>sx>%801&WZm6H{^}(-CB?8FOZjG&px5KleqHz>#!A{*x!vNotOr?e45FG^nLxmnN8DPa&Db( z{}=o0rkR_Y>jz&inIe5*8h|D7cJ92?cF=(Ex=>gF0PX-&Kj#RIjd>@50=`oy%YAZ5 zgdL1oS&4r@>qEwmV`sng86mdrPpYAihZsXD9}SrjCe;@?v)>?6q4h7=n#94FOb8I2soARxO3^`5HZ3{s(qYvep183ty)Q zt}$6vVA=x2W7g!Xu#*3d2L8nja(UA-i|rrs$Jmx*xA_)R#w5M$W%mD9ajw)A!&j2hlL}K zdTZF{&*R#gn+d?)4?0BHvE;sc&-zi&JzDF92WDoH>;S9a^~Fe1hnC6WqdJM8*bUc~ zb34=AhSoKgi)mDk0kU?)FQ9`9bx|WkT^mTq`%e_{+FmV)yJWR$TzK2=r8InDaEP z*{v@){%PyVox25%!l@)4Zx2nl*CTx2sq4CSC(oN3WDW(eWLij>87~j)JJzmZ$DXeh zPPYl2n+y$)g<+GUX<$%o9E|-y&{g#TtQD&IiCf>TEK!_J0)TZ^!ob6Al951?7xJx* z4XgZ^K`UH!*L#u%q$WRQiFmZ zDbA-`$wF5Mpy{W;2nuA<70jql`1q{Tpk8|Zu~)Qh>(v0To%HCyiPzE9RoG$6CKvB# z*s44;Kn1%~h3)92Ufo>W{!!3;@nDZ<*b}%~>%c(N&ISeq0H$`!rHfm(IFmbE*8=K7 zpc$s$VkoH*tdN92%#*<`IYxAJboZ?j$*9>(q!p0=W!Yey%R^FDr|K~HfJ*XW5V{Wb zk6n@q0)fB@`z$JQkGf6EX)7L&4yFpnZ1(I~Kb(PWx#De)kS0%;n-M@Pzo6C@RZ9fNE>vvYLt#DVBfZ<%I@7E9UfGyAE>8ft5-ONu> zL6_23swY4Z<_jxKa{GT`XmENw-Qc4y2`w>@Ofh-<%HT^wV5vn+KZ8~{uzd~har?C~B|&9R^% zLE5#O{Tai6fS7@KjgxM@A5Nv2+@ZTu&hmpU&Qlk>07Ct{CJwfAbgRSqdY5x+D;`k? zE3eU4Fjt}T7pZZC-F$$)=L1!wNviOTtGoxIK7+x5>Jb!G#A@!nV&8weWA*j*S=Amx z1T>$^8d+Z$!Z5%PQ`#xl?w1ci(+42$4lY%uyU*N!=k4e?MMN>_b{pJPk^y@2`-CWR z%#Rp6aS(_U4L`Z+GYGm=z4RY7eI#Tq>9}^Ke}8{>ep9pdCtiq`)B`S}>Yp%b%KBGi6*ml3hp3CaW$BVe{BnWA5t37|={> zXR|ObhbhovW`B{WK7j2%x{xTg=!xc!{L%g0KCgWyL$-*#$B|7X=Uj;Up9-1zSV^U% zEZdv7NX{rOG##}_126^X45=ec0+d!M6io6@q@J6F7vs@Z7!;>uaC9747x#Sg?0*J7 zdJ%aN|BrsR^hf^1f0ttd49fQx;l=*)i5eF5gW`Xe>;CgP|BS$&5%@C#e@5WX2>cm= dKZ-z&NciOP8`Gv-Q{h=xE|{G!Graxae*mDcR)+up diff --git a/engine/src/flutter/testing/resources/performance_overlay_gold_60fps.png b/engine/src/flutter/testing/resources/performance_overlay_gold_60fps.png index 5e5dd0218ded359c2cd0fce0e4d783585a81ceb4..0742106286ddde9a0379b47ad2bee364b1fd5c9b 100644 GIT binary patch literal 16118 zcmeI32UL^ky6+j?3R^6UjDiY8#)3l;6%ipoW^AaGs0g7)L5TEDXd%uBGJwJ;0@8wt zfV5y}p(Z0$y3$(+J=6pUB#?UF%s#iwJ+t@O`<%7zx@WD)S{fzf%lp0WQ~$pwQMXJC z_Ut;mOH52`&yDL>?udzPpA-F&*a?1OoY^}Kp8n!{>Be0N@CcQ7@UxiMQL!6Wez+T$ zwnPc?GNYv{@n~cDm>*PLNaaTip+B6tAXPNj)I^wSn6D|ykH^m&z0m40o94zdV3H zEGAZ=eg49QSb03A;StUx2OE4VNY2;xOI=c1dFA}`m*LC+wZ_9 z{n{u2c129;t8F^q>B^%=d%@G!M_+Fj6Z`%zf4=lz*o|NOYbB5Bm={Q}_YIJ|9W2nx zd6hxWdiVW+1iQYcMdF{_?{Q66#LtMAUx|r%X+Qnlu@IhLc%Pr|T74ml=k4mQrmBFi z&Caf74x2IiMkQ!o$7FLMbqD5Rnw~VZP7E(*_m?3%1z(9hg7pp#UJR&UeZ4(cCtWaq zpm1)d|FQNT$X|C(Tsw0I_O$6b=71ytvU>oxcOF|c3e4(}Z=*c{vUh;~H5wSl&thN6 zgQsu5|6UxtB(b8fp86@_)ktVl$NJr)4@IA(}?}s9wvOnw0b;>npCV_g81+9NuL>ZhX@@;lqZdIM~qr ztM=m?{VQw+xM9`%X&1ZC9zJqdCDOGh)oLKDQWIQ==Hlii2esk;d~7b};IfMXo`3V{ zYb5;?e+EA)AK_au%8#3sy=^hUv$Bb7^RHzPRlH-5$sapt50k~$zIijDm1GSdA?iD z2duq9?vHHbKX)o3GZeE45}ijR<=&L0$__Cx*%QAvF@sUhqsR37cY~1p;Ls1hdEM{t z#P@U5H_SQU+o=iv>}~&kwZ(Y!SEHk2q0Ek;YBa;{%*BhZn0y9SC=9<9Tx&R~tV1Rn z8XATap^ZVRIDk0*TDVLVEThH6#hI5R6c5H^l@u3qRFsutsTelJ`Xn3|x^`1;%E^1O zA?hcDU65mtwY9aoPI_!oVq$rrRoOzV)JaUGv1;Ppo*XhlCyd94USoDW@ElsZ;%Ysj zCbL^-HBh-caJ4PLq4~nWllyZm5Qw31I)zOn65;gfL|VdX43ak~xbp3DUMHHw3@|k{ z-C`||BpIjc_%qV9eGfN1J>+Za!g085IrNg~Hl$J395>((u3zXNGeW;ZkIOg4ZWsG? z&X!;>s$`P3IUf(#-%9nwELwL_yqo12Xn+4_L4qwB0*yw~Y@+x=7LjbbM#OT7)aw1* z=weg`CcqzoK(uV%r~AR9BSo|5UC;cWqQ$ zsY{nHUv`&J$k70*i*J+`*clqG!(vzrX)-Utga*nbTK<+nkPvyCkVv`mEfPr(NqG9kuE1>Cu?| zC=Ln5&_^pf6vKask7o)>y7ajn#l@NFN+w228Rwl5x6RBPCSv8|D@z7*8DX4xh^k#pAT!S_ zKVZ+EJ&kt3^I0R1;P=ixy11wQ0p@!{d8>J>*jVrFis37BneYKktuA`O`-?9#ZqDp3 zL<%8syAi&?P&%o63Y9-pU|ABNpzZVE4zQ0Kc6N4M%S0+EsQVr7?IDc|FKM%^lfl6^ zcY-C1w>38y8e03zwp$`^TMefqnKXCH%WLwXXuQWF{j{M;N@@PXk3W@}jg;$+4R@c* zR8oL9(rei{(p*k-{1Gj|{FjnASQu}!2fKm5ZgioriH97Gj_;gO3_o+fjy?%ijLG~f za%(H6q_~7TB`-h4(a6}?IFQ5P?7Gv~=#E`^a5*3V$$Jwi;J)nAx9+}ewqg0h4wEl|U9_mCR@ z*>-emWd1U6;~GKD%7BoHj6{22mD*ii9Z)tg5AS^2xzegv6mFbzfl}qa*q_Dc^S2@* zB2ZxO#`Yw?#)zU4Rlp*uj0_JqMM&9;?8Usuu9lFUJ=;FSg?WwtT59c4InyI`X>+J6 ztUSB6w)R5kDpdD=U3gOtF4%?A1BdY+S5;NPt*oq0K5%s0BiL+)3HrRS=DXQYo%83v zAzO4L!&D9*KAfG?Gk@jAjg}}by_!)gx@yDqfxn80;S=)LUipgRmP%2L4t76xIk}FZ~;DWpk}?>5RD3RTAfVF+a(D{xsR}(^%q)aJOr+C zqjvy?kiR4W<9vw|bcqb`0Sd(yHX?bAT?}y36kxcENFm47*x1+~f(^xTvyHLcFN|jQ zs@ZZFQ~RSnszbz!@Pg8nPtPmU#rKYSyOeG~bOS%Νom`hBpP3*cNr>YwaE!&6yd zLu0k9WNAdq;(7U$;Fthd!m0!+489 znwF3s3I$NOyC|7pvA2B$yn(#a0fPJCq_$J4x(S+2M%MI?k0qhtz06rp;)M%i{m+eN zch_vR{6{G1!w2vAv3$MK@ zcg?U8c5W7PPQ8XSxv|nk70|r0H;n?<+-<<`LMx7~KZ)4#%D*%?s z`^^f-TOFRH^dZ*gH<$f;vW>Y9T3(zOF<_#uR(xX2d1;~q1r>|<>M;y&wHevj*{Kk? z+9-oyZ){kek`{aPslpzs&0p_<({#b#zUPF^jP8$>GkVl-1g)=L3#Oi7!WudnitB4hvB;%2!5wdosu;=zCTy$ z(kWF{ISIvZ;OEowx`AH9dhAi=Y)Mxq?(iiE5c&dmAH}ifvdROHKREq`@aDdAvw+_Q zJw0P{@U+e5Z48EwnuvK$EOQ;uukoa|I&^fDR9}`*gprx+Y1)fTC-*-l_~|Hs{Y))P zPfvK<@_>FzLh(F^;At1!dyXDz8*s-6pOcz{}3g zUPB7jlBmFj3knK;^bDOkqAr}2xj#V@Qj^}lznDhG3e$5;aA}{l99s<>W;;S0+z^Py z@hIu|&SclLWFpQ4dCGUM?fIWCoL7bVPk&0ItFyjdSj1|0P)P|&>VAUUy8*!!q&B&b z)|dO5{rv8-+3PP)f;&iPHOc9T@{>^9y?1s`dI*oowgk1sGe3-a*(_K|p!+iEtvu8m z(p8F_8>@UZvEb}tHK^^-6fz@ya!Jk2#p<)B_av_FuJUtzp9cZ#&D9V|CKFns5Ht<- zbUFxcZ)-aeIG>fIP&-I)>I@~SD&=UAd})s}56{=W#?B{RF@hkT?p;c z5J>3C$AY!BH3u^@Gh1XDYK=#ocpAi<#jO9jec#e<#qcjY%dbzbL9LM3i4^gjJB!a@ z%^wWAy14WU`BWjF9Y7z|o@b4CddhW_?GSsEzZ|_|JxW?BtjcYuSd@zbmWM+mB_*Lb z@Qkp~#%M3BMAR+w#8jwjKXc~933qKvOH9$!COBEdhH-)75@c_;&L8*vs#r(OvuQ&9Bq$&tH;^ajaX7*qxq0 z(a2=TK!taep_(>S$KHOrI&Zya#9-rU%Mj+m`SqamdRwYtVP!#qw$?$z#)E3XmBAp) zbq#e5u{$G*I#xD*vp4V=#B3+zZPT8g@F^+VBbJxrL+<1*`FVSXn1C(tW2NpXE-t zfkC`r(z>XX?jz;Uu(kLM9unAxnub&RLmp>LjqNX73j~er>up*Se`aZOh~8 zEbYjV#V%2xz@51dzwhX1Dwu3kM-YjOCI0whPvTzms3`l+FDS!te9M#Pk&>C(#P$GC zK_?_UZM45kFbX970v3GN)YKP5k=gd%T+=w=v=VHq);wTgX{B0+9p>YxV5~HaYQLq$aC=H;p(`X6)(9{`Bk7uVV}0CSSg9q=e(z{Ow>X@t0T z7loR+ry~zXIr78so*o|P$?_3gerp#jz3=mf^>%THZjc*jp@w0|_55QhHuoAzGaYQO zI`WbyPBi;eS1Iq*5UztT+J$|u>Q^ENRnIEQ@QLrmh)1V=IEy(r_^C-SXS48gSgIxWi=BVnLIZ3VtV{u zSRQa6b*5#QV7?$N4Oh^UlYh--xBK~PJ?Qw8%c}ZeyhPkxW$p`PzGli|NbYIc*db#* zgV^?Yg~QR2pa(28I_dkw%F1aSw0I~~a@E zR#wUy$k4vt-n88J@8|S|-XTErz~NiB-B=CO)kAfrQAUWKNOSZ4(9)6;`!ne#?@*ng zIlS?Yji{S9V`oNv+RD2@QDjeeGx+QGKjf8`p6~=!STUAeT@61OuYK9fEaTwG_@G=P ziFtnQV2Ff!So z3U(TC`}S>YNJz*A=yuFcBT2l+JRa}I$Kv};Kq*JhFDO`|GZ^qvU=qROKZ=iSDszi+ zbLW2sL5fksG~vI!8pD#)3oiBP!s$tN&-W@#*R?=7k0>rF5%0&_azA^9F;j~`tv&-=`*f^G^g4LAX9uX_Uuk*TF%d&p zkXi>?6I7`BL2D0YhRa+boTZYk?9NxKTe|{4OIS~`_qYhxU2Uk2KWWVI`|WsxW|Ngz zbPAMKKH@R*ZQ6D*sT0Ryzdwz{dC1~1KsNTMV6|^^+s(LE8{3v7v>z3@L^ZmIlNm%^ zZ-|1(s9CHG4-O!OaAO+zE#1e_aif_U24!!w3q6NR=ljt>C>(?E#>ItqKo~iKzl|gl z&zfu5gy_5z&gU<&Q84aMN^5H?t63q$jfpr7IE=@wzk#a|@iU0N5Qbi#Ppxak-I*DK zwZn%Opm@KF`xL{g=^WasI?1Bi@8eH9GRld>xS=Y4SB#_M%on4JLI*YtE1;>z*=vZ2 zjRR0{v8yvZf263N#9pc_!tgeUO$-p%?10i#fXC}|J{^mzL6^k>y_L5r^?Bm3i+g%M z_c6riNPvz=diJeyiD>|Gu~??k->{4g>lhtdkRKyLXD?;{y-o{$^&dTce*Guf`!C?O z|Ch+}Pqg<>5##@+Xiq-x{82G6g*T#Efd7k&|B3nj#C(6l-oFQMza!#*^wR%7nD0un zs0&Kg@ppAL@BcoOQU#G;V5l3}GEypn&E2fOrgO&ezYHYF-~?FQUfUhE1J%bt@tC-|5EsxRXN*SSRloFV*uPAJzEYl#)gwsamwPE)thGz(Qw0s~=u7#~+d(`uGB03rU-)a@S^vuQQ!AYsi0NJhGUETc$?G%GqjABvErGi z{_=9`VD(Zm(2A{VHfLH14o4)VqwW^u=ev2a>D-Z#5hb>5k2JTgJBy4r$?|zp?kNnm zzH!5~vptz%E~l@{FaotS{>LA;O@zuOBp3&^CF+@m@B(ewiE31x0K)QVKWw;HTbUY5 z62(UQTl)t-;2*o+^Vax2tswo45}8)j3urhGXD}MM3W_B>ePKa42vpDgn3a!2tIOA$ zJ;;1&{?zJS)sha;5^(w$)1fG7X^uY6tAh})q-1m6tt&%l)i_9a77@Z88zDLO5UoMm zY`U@0g+MuOAemvGS{LFF<_gLWfmU}&e_z^`Vna`LfL`F+T8s};NB&5Vl2)!rE7xL_ z5eOE+4eshQf{K(*1|v5z`^lTZTY+1oq{}_w*=N2vQl;PsN)goR&)lo$wX~Af*Vms0 zff^f`0Y!T-qonWmi{NRI%@$edWrBQ%wzs{#@ZF8-=)AnclIW$t>A{J5-)e(llyQgP zZG9vy9Y~3v4_uOfgl*2P0?pmKyCEuM*JX=P<4zTa?sv9V`TQL%?Q<9t6Qpj!O@KZ( zKjkvd6QQ-Z>j=)_!EFz8FBgZJi+NX6^X|~kMEy4{c1a)cvmjG z28^N+5$(*W&pud;VXjzP*)q@@a`(39`ZTDzy}qJApsd? zW*GBZx4d*h&TBT?&!0ilj~tk4#@_iRPRRiX-`gSBn!MUfhtUsT1-=v5Rj$#d;#+vV zA`X-?6xJAxmDf7|G9gUJXOt!;Ds81HMHoQ&7z^CMawi_74WwV9OYj#M(mhv2*` zE6c!F2|i|7>^?G8sDUDx>f5*j2%Scuvd2Q)>K+zR5Ol_~3@m(23obO?3~Xwikckt1 z%j@XDN|&D=Je-ZapvSw+f#7(naadkHpv?vLe9i_sO~*e^8FW8wZEbOSyp{SRdHMO! zF0w%+R~}HT5Uut!?Gnv!sV7gK*sIsBcP|4iqo-%y@9n#H(<1P~9#bQZ`q;l)1fHu^ z{0TN`2Vl_qe}}LWMx0WP@m2vx68M-%vV2z z$^B38mZ%0VWwB*W(7LdNF2kklHWs~>Ch9NCvU>=>XbXX84Mc^!y1E|Ak#bBXK%Gif zzP=&*L4+~OsO_q&GqrSRDxFc#t@xl3E_K0E5a8-C<>?Q$;QUPG925!1M4&3t=RJ+3fU`_|@aGn?o+yso4M@X6=yETR7C=dyAv zZSH@F#IAgM07QI)SRk@@Mg3KpW%Ge?&Ys|BQQ@^b*x&DL+Z=0Ld+!l!Bx@9MNPM3{ zLG?>lwi%aO=;#?i8YKa?%Rok=26VYI<-V}wBjxSX{fVlU#TYNIGpjsi7fdd$IT9B* z86`7NV9C6RL?S;Ls@M9lw>0C{p`3qXXGK{4Vv!~glL1GxW#|hwr_=$k*jKYT86!$m z-*O`q!Pt}JbD^a7<@#a}o?~E=$fq@hw$-c&4u?}{n5e2Pdr3lGUcMThF~%zcp#o@6 z^pru&3d9|`im3<2#vREq z^5opyTwW5HH$$Y8py5COe%O-@b8aaG4e|+P<-If!4af!QW8FA;?aQkm=9uhApV`^K z$%c2nvxiZ$g-9@e@%ifGxw;5R7>G#1rOnkdnhNj;4u|>_G_Nx)U?Bka{*plr3hqqv zcaVeKA9*5!X%0sok^8OexBO-ra z03l31lj4fj=BM8bCGEV2q*t0wQb|+GE1k=XaR2+Z4$2A9FxH@hD}YHRGT(f&`-xN? zka$^)(Mo1H(-mJ-1Yx25n6n)!zzm8-RAR4!?!{$5Rf9Bo>`YBd1Pq~oK_>drI&ToE zVIflw@oHee@n?MS_b}g+xYT5HC@nqcz|hd;D8uAP-SgjYea3viU``xb*E(G-vdHXjVGISNs_S_VM3$+ zQs>m0NorjG4r@>T)^iazIA|F8_nFXKawSE^%0g8-R{i+%>!F8U0VagOTt(tP;Bt?t zxE{sbbj&$?WDyEN7;#d-72fa8HI)vIQ%L_(i|WECb&a9imj!ijSZ%GtbiQy)pdr-z zH*&OcHRD(aTI1wi=qQLr65 z_U!lfL%7pG4~N>?jN;+}Yj>;*l2Cki$5LiFa&nTjR8{4k1u*O2u~XEoCI<2CB(ipD^qQ7y)|Yrz0OSn~X9CLtc44AOxs~OWD&X4XA;%q$DkX-%gx3 zfjljlk(vtq1yG{x+qVM^_n8Ao5TPKs}#N;y$woQg_y=CFIpZ< z^18q%Tgjc=n8+>_wSUNk8n;Fwq%N78pF0?{r}2rktXg_(jws-Z{=fkRCI)X{GtP+y zpSFwBFQ$ub_LBWIH2TIlEh23n{wfyx5%A~fSymeJVhEVN3H9=t{#DEB*|QB)N-7Es zfAt~fLvitUSTN?1KQe-5g@JO@`m)5Eyu7n8XtkF2+~*p@u=$OLMGOSNe^FY^)^?_) z1a@RzZz(f3r!vTVV6<{k+1Dp{%F(f73ldf_Yq9wjsqUD?neWj`Y@BKtd~rg(7YN>U zNf~j6dJ?&!d8XQ}*?s#Quf4CA$?OGN4}#vB0cQMbn+^e;PVWqwh>$TK=&G&hh%hi3X~MZ*Yrd3l{dK3NqNn^gr~UKw@Grp_)@VTisey!1fs2N#o`Bu#uVlW8i8gsa4PKmGYib(NIcYqs=_CR) zAmg#8x43zEF6~7{SiDpH6PUX`x0W_h@VuW+U;A-wt$s^!)LW;=+#*mXzZTOCX@}F7 z)o$$FJDbzmn!-F6=T4(#bb`d%qJybgh_=Zb9i=yb$vH$QXMQ>7$l*ks^Fxc^*+gr7 zRUDN4i~#J|Qlwr3Mt2Yb3p0c(CtzBTX-{W4c6nf+iVx)RmoMeo+}+bzGyXBnrUYjF zG9s7u>A5zg1XWa&2701a;GXZZ9IwG86#W9scRQij%_8Kc@9VU5PeSqd{+1YAn+WiT zI9KmbMjx2np#V<6+nnOU*8u_P5+=t`AQ2G>Npgwm(=Kc@AnASMAQl4&mk3Y0NH`X{ znl&=hs~fa7&9YEU^l*001*t~xyqyNXPSn!U(tYwTU>NiW<-lLTaIM<#24}GdwyrK* zQ_lw^fqetS|FPN-C(-;O3FB&IFjk-NWytdED-)$|Q85>`GCrvxlD$1D9j%?gkY@7p zwW~2<&~hTWeH^F9PM4ECb-m-&s@>L?%#x~3fI9(?h*u8OT zdG7h-_M3p3{EMJv-#-K`0P`rTxwoXkgk?)qKAj#9>X}<+eK`>=5A5=Y#N)}=cQ-Y~ z>RGHzL?X%KQ+-Jx7;>vV7@0FwQ|qbDHlf@}x#^fSGMv30D<5)3*l$gI=`pyH(*b&# z%P}(K^8sLb2t)}NZkJ%_%R2k>t#2v)7qW7|{K*%5i9KhFk0Ed!=DQ==p$V@3q_DC%35d*9VrCmW+ldX&9w8g#8wrDB#kjaTm^?@1+kC z99BgGJ~m*mlZF^l2N_8M|4ErHDRWwv_xbX^!sX>Pb{45m=)gK zUHSP(cAet_OBXkhg9f;=mxeqMqu$LWQ(!=GKVTmh^0YfLpPMw-(KKm$MULT1OD7kA z2{#kBp|xiYV4sa%eW*T_+UC@I(Rgc5%AG2o8FFlfZcy_p z4A(r3$Ln+d->{_uVPM?_&JYR{vTJ>MOJKSKv3xKZp7<@ZmoF|g$tUE0*BXGxm-R{ zEnRjg`?u7xL$cqzcC{%kmBWcH8lLJOSU@{f2*Nxa3Op(@ zj3d`!wo#(5ZBRjP1Czt+trb~LwFWV2bF8irEZ@BZgPX1o-!m$@Cv?N5=v9ju&BSt( zMKB~}g1A4CyCg~!89JIIw#yTy>wFx6{FT_a)5+J;&Z4TQTN}zeguC0=i^`6ffXT9TwL_U60x6ng$UW#zy0%Hy278o{O1n*xdVUhz@Izt j=MMb21OFX%pj?cYd4CCJ_3ndcwKuMsT)|(u_tXCX7*8y3 literal 16534 zcmeI4dpOkl`uDZlm9NbrZuKKkolgL_}oAmCF}yiim8U6@H0s1wS#)AN&U1{v2R%#atA;!bP3_A|mp$ z$dwD{%tNvl$JqfEut-HNy{TcWPbFGJ;p`&EMT+pnzCwmhHGyp$gI;k-Mg}60#h(Yi ziND}#`nj(RIxWn-E>X-VBC0#q4v^ke$UN2ZJQ;7j{q!B9x0Qe1_4AkD!{gKJ4{1@S zF7K^4Kq$AAjETV2Q^K)qBs_kNhorAXgGc&DuceC>6SwYz7v@xfh}Km{Gqcs}-9@KQ zSCi}9MMS<5@`{VynAf{wcg&7@vgA>27vJ|v$Mcqd?XDU{G~TTd5qV#G;e-^Y>w5Q; z7HSW;(^vmL2ZFc1h-{JtZwJqw-6kUPKvd$(CSZ#{{qfPiVU5eg#XqfYSkC$+mzzaI&i+~SZ$9dLJN~m={1&ip z*O`Cw`G2}WcvTHrDt&uRBN~libXWugHUH4LWPGY0ec#i)M9!W4{PAN}Y1s}Dk-F-_ z!ot4u%JRY6H%2NFD6zM_7n!N-BTvr>jvQ#yYWMXz^|o;2*G)|o70dWTWBMW@ONT2) zzC?WU_WO<*aOWrihve`FHg$A?=XS6R``29NeZZIUB{mtE3m)wK>g#Ra@xG_$V&k`K zDSq%}{PdK`6S{7S+`sRuKl@dQea&*5{_IQlUGN;+qe*-LB1fdz{%`I3`_{a#y|;ZM z^=Se~7!R_ph<|6h!j(H-ifcV4R@H!rS#KDK`p#ZD#GjfC4|rh;Zu7POd$;*V9`FyG z<6rzJF3U~?kNuoFLCQ<>5iY#W{gqo?z@C`>)w@MR!gc4){rxsS_$ld}7E%g4`6U%i z;*S?EGLjFCp7Zj8Q7)6@4MfYz+7BNZ?ewL$>^cNVd?weHFw$9b#`I876OK{i+k~4+ z&5n9T%g0dKxOr)&ZujrYcd<5!JW%y;ci#^gT)7MXBCR|&pqKn7)J5@)%ka7K_MUNY zAsN4-b9=A@m+k0`aAjLMBjhmU&c{x+_uIQ~+7l8ITIBkEyGmU9W7;9Dt-rYr@TUo- zC#^YPr`~HN{j+}Z-f{Tgy*fjf*YD;4%dG4B?gpKj#eed3`~>#+&e4C-)BnYk`ru=) z$^#i!p?~(G!2KQ=eb=WtUNUO#9e{usH7@vG@cg%_`?ve})E1l7J~z&&EYo{7o8&XH zq}XY2w0ZQfpKKF2AjtZ&AFX!rN75WJHy4dLJaOL3TvKv+*`mTH~zC`JGrr6Z-wKIt3LLVHGV-kCv z!Pd#Rb_5;C+t>m&bRaA&?CS7aYsP%Gc7T5UAZv0t&~tgRW+>lKOOo>KwWFP#IOcGGvw6-$pZEE#NW5b&4!aq+q@K@5;X1;xU z9m&j>Zwh73_I;Ms4SxGV&G|E5J7DH?T3QPio4lUC zT8@d6Yj0SoKYInSFU3UQr>Cf>xZ6O~6tX%vKRi6Vam=ici_XiNSbu)Z%*4(c&Y-m1 z(g+JQTjuj%wXh(I*DgIH<`sc+49DSeLCRFIdF0F`Ih%uh(&Z@qTh&1eYc>X=c?}!) z?}{!jA|Y$JSeL0$8S8Gmu})563)oI!ri$KH*f1uwyyjpBU@6Qnw-oczIqi9M3!ECddA zj*gD2%(($mFH&QuyOB}%Yh8L{K-dcaK2u!O^?t~ zot5AUsZHpGVv9adotORFW9d}MJ-F{$r-0oQkFpv9(M?o5dTIldtzgoVcoM|Vm znInJXmz2ZB##xbcvDyMy@GbGimCuNc)}(z=vW1&BZ;o4<^TMi^l$6{q|0P>m3TgCh zhSjdC)4|%&oOUhkbxPN(z$;g5NS4}ss}sAkPEF-}=5k5dNMz+h2%N^@YO)_a!e(y~ z8%{vI^;G#wdu3jJK3bS92$+VFtt!MwyK(*vWo31Yf#?K_fY3%)u3VV7lZDjNE`To# zleO7PZKj&1wQ#hzB|5jxjJQlsD~ATmc)o2IWH*gvZY$3L8Q|%YPO}OucAhT|wH+t* zoeCboKpvO44wWF}a<57jYWeWThd-=Z0`K)alJco}jg)k|yBpf&@b2r-d~&jSXhcMN z@gYMgWUe-YTB2+dIpSYW_>z^GSt2g0b9$|rLxWSB)<`xw=3YTTa3dX^jw8SnYm$4S z1uP0eF0Ezt*8OY^rR?I8AM1t%=>AZ!@Hn!$O(w z&qliNxopF2Tetcn5q$Qx$>orlP{5;Ovpz;`FAqd{Yua0K3g# z!2P9^Z`&~D_!L-8t?7IQgp*Jbq9>cBvC#XZvC%1%ZcnANKuuH8_0A`oPqC#%10ot!e=Irfxco(g}__NBQujuf-7@nvR`iZMoAMp0irUh(~!-u%Vru-4vJ2 zh~+*!22MC50t2je07hJfc0kv^IvCqGZM2xaIO9k8m2>8#kG&N;iySS_AArUrGbnA{ z(Die5#8tV+?6*60LRXx+Myp&6Rc+9VXGInCmOt(;$7X7HSRQ;fD-ah>79vx5-&qT3L@dUG8#qBI_L+pio3gU;^)-ZG4e<`XM%(xxHxtU3p@!4ybuT%*`qmA~ z13%WsIPLL?fwNL+D!kQ^0wYUrnMRA2{Cr1xnzGHU!zo=CyuH0=`V8gm)?yK7BA36u zpv}yT<_r{ip$-s9*c|cd@lcBDk)GTZR2X+XDqnwNc36FUJauzA0by7n)vW=KmO%&ENp@&xh+^(6N>6qooXI{vua@;MKWtmoih1FNr2 zkb%8CBh?+*Ouv$%f0R1AcK|I#5Ro?w7@1;HL*2&XvR( zfkn2s_r_g7gfETlvu%#xOu|P-e!^3yl{}s_GCpRcO^4FM3J>2Fxp0+tgPe4 zPT2-3@j3^JXm^jqv(Lfp-i^@-wv-@IU!@1!H=X^v0C$_oSk4(Qh?Yy-u zR8-;hKwLMvZF#D*dkv(8B^rhv8FBLM&ch%+!0~h5ajG&g32N^L+5l{Ju}5i%&W)39 z{ix*bEzInm7QTKmnKNg{a&~rxcl5@eCz~k}a9zA+A2(l~8huq8?ZeeG&uv^^S z-EX_}yy#;BH}-7u%`!}q^Rjw(=L2fOeWyZnV`C#O$*Ye3L<}#(rGtOY&vW+%ZFEkr z%an|kw{4T(REO#!CO<#_G&tQa*rs2ZOgvCu;E2uMkq`PW#`(SS4Hyi=K4crsZ&C9U1!iVI9Gn#Ws-g#Y@kq4e{R0y8t5o%0%v+^OI7TFQ}2p1 zKOxoq@o{F2URHvuu$*fEU%*;MC~F2j1?@%9^je)Cg6|S;v7EpliSgz}K2|ZMt5u>^ z+Q8Pqq0bd2!wjDYW)X=_;dj1{^?ud2B@7LomYkVk%^c9xEMF+~=3K?mhw3stC|Bn} zCL7X#(H_q?Hj+XvUJRH3%~qgV_%O=yPSx1*5{WHnv>~IN^4wtGcCaeH!nHbUAYox*tAJR zVjtd_JN@iXyiU=z(VM3NW(uE3L~5PZ)EwTvefyrbw`<%xqkq_~)4C-E8H4k-%+;c6 zSqQ5sTi|hwgoJLaDUy4xi&(#4C@mTEOIX-vGwE$viHQ{vqn;<8r{?tdc;&Fz-)=95 z=mh(ntw$bL%3|z}bHaXzD#Zqa;yqfz;NWt*Ur+v}9BhtGkdd>nY!e=zpd+oiC*WgR zPRqcIjm;+d3vfft3@lwE7wW)>Av_2O2ryHKzJB|57`mXq*;Di`?yIms?FXICjD!I{ zhCWACHrla$Poc2u-|9n9IiZ*Sse9o{wyvf6q#-#*$rH1&F@Ctn#F%!aD4@k3W81~% zm0a?t+FpSun)J*eA#lf4F1!3z(aq3|w6uK?h3GG%)*8QN5u{Z?kpw|RGC>sqlCTQ= zgvapP(d27KV7BXH0XPWE;l_;{cmba*-W~?;zA4>Oguzs{RIY^F6|IeoOuyu}Tz>%$ z=V^rQ++Z^(V#6pqIN^z;5{npwx8oRya!8RJYDO050cg zvnRZ`d1CvTou9@Llk5>Wpd%Ct9x9Ki)W|PQ!UzE{VoQvbcr)TXH z7i>7Dq`0`a0gXl<0=a?|woq0OFQq)T2HH7uI`ETonpp`219Kq=#Epi=#)3Pb#PvsT z2)ek}VC|WiN%3aIkC=Bv=N6Q%B6Nb+YujCMi3At3>g3TP(ct9Z#O(2dPnAF^GwbF&jeQl<>$X^#1l&-1TuCL5)-bOo<0y1KuF~Jsj z^>8If*pU4OqP0Z-ZtUs;p4<{l9kVGk&5iV2qm8S>;c!D`8?>i`=-p-J96b#Nv)sNt zvCP{oV^~;aek0ar@=>UghZ*YR;>!g;_D6Lay4Rt*yY~GhK`!F;Y?Qd1b|?f(AM#Ys zIB`WhhziajXDQW4H)h`X+MvL|rwf>+Cd+S?S*SjpAx9AAE_K&t z$UCPlF~^;9wxLI|5@Lk6X>@v~5WTXpLINq}QP2q~BSnyvEb&FMEa}=cL;12o4D^!MLHfWxh<+9=_fv6MS zKQeO08;`P!&kdQ3qioIn1qgjwgrJOeNO&-T@&4@Huz^BjBakl|Zk63$odJ4G3BJr` zZM-d3+%)5qV+Uzz&T8nhr=}EWb7GZ2z2`F5PjUCbPz6uBL7h6 zahogV78vE0%IdP-zj5r8LW<==57BglcyZ_$8W7=PsFa}=!g2duPu)H+!_yCPV*|`FJE$ua+8$GDC^7YoLoMHE;ixA zVRf3gs4Xu}wtIB;WH7>3TUeeB_g+0v^7Vt)R`<+~wg^xxzbRJwTdU=sy2{EFHjo*^ zS6cn(hki=xtRjP&)&WF3)dAFZFa&SG0Sa=E7-*zE;6d9QOh9{?fh5VH^k(71H3;%F z%%uB5BzL)!j4Ce|0MQ9-d*sGk=Ng?1rKH(-j~>t7EFy8Zjdhk32}k?=qB~N3%yX}I zPO4H{6A;&HKj2sOKx$I$MCw`&w7}@|<-LqT#cS)ngO9e9C-8fH_`JS&`5Rg{<1RRG zTjdSs7uKHMes{;NcViU_$pq3%+Ya{yV|vH_$F>Styw1{58qgNvV@pa)3GxxlK4nWA z86f|gwco4A-Cjdfe^8GCU2a^Ol9lq!<4UG`PHU=A>xL&lyGyH^%I&B{n=I)2-7~TD zTOK9O4QN>)A-c`5li7%IUY|U#Z%oNbh?@SqNmiv2zLv>asG)-9(k)j%!fg6mmo8{d z8hhpjrc&Un@U;f2IAA6l5?>~^x_jje6UKC?B^|DSCXqeyUdd;Kxa9K>zvD1GPipNb z_Jk0w+{piXy&L@aAH9Blbg!-K3wQ-PUftyVyrZyi{&yVF;x{DZO6SeY24?fp#AUuz zjV?lM2cA60QkCLNDWtnr2}0W30kQCjvpT^_$xG|>u{{Rv^nUtpG2OpNV*Y#X_y6q$ zf2|wcvb0*^?_39aGo7UwvqKA&>7(TZ@*`RAp9U{U)|hj8Qeps}j=zXy8T{yWnYNzx*Z<9w8Gl%xhafEKH z_)nAP|MQK{PP$0H*Xk64#EIcikgMuWfhZCQZ~9wX|4yJD__Xh3D(#a2NkK&GyZmIluMrgFeZJT(b zOO;6zB*fuMVFwOD9?MiDdH5kO&4=YuJSM_&JC(Gz9U3=;;bPh6m2>;Qe9=#)3VHkR z;Na=7H`8sbm#Vj`i$Z_dVjC~Ve8%c`h|jJNzUG%JhL}A|s^2+j6?1#T3 zF~+UhuE)#B$RHrEe!I%9_#NYh-rIlY+LV`VmV07!!A|{a6ABkU`Xdt zH^}iz`Ll_RU%|8g@1qR=*R!DzbZh+vy0h%S{Y26Wzvoj=&rv+u&xC;(&AR$Mvj5*_ zc;S8eP6f@URUR`#rz-B^E$@)wf3tFM@Y`^~e_XAKnEt%PmxV<;ojbRVK!&UB2?`ot zOa?c!p=SP>IaDfW<}>FuNaFe-b<|e8w6MT+5CjbfctwbfB2&QApaKT8{K}D(Er=CG z#0s%yguZ&~zcN+9e0jeBAe=^F`%E9k<8x(gLEHQhw6-l@X_%xTNhu_5ykT{D7>({~ zN*}Y=LnbQQH=@IsvxSq{*@16MOYhyGli&mbLGg&s?869`(Ms3O*8`y$Kyh|cQu64{ z5|WOoPD+jsPzQ&|4!>lJ3xVQ?Cz7T~x&ubv2&|Lxm(aR~D&Xoi}W zTE*6i2Ig=$f|)6#CJb~Fwb}Z1>0E&QOpw}Uu+euA1mD9#2!`oo1dKD&3-z3%t6o#J z+u**q@QVJp_WXbF&p-eSu}f}WPi$8}j#p9m161nnlu^&cOXDN>R- zMxJE;NS9gB{xH`bQAGx5EZ{R4uFd3;5i3Z<2Gb0*k;t+BeziHFTD|0oKsVm4^BNX1 z4eoj)!!Su@7U-p5>Fa~@L(uZvlXo4KC)(n5eLg*mC~S_zy=iWKHF$>}VQ;U7G@&?E zjjk^hGjwi)VIsKt8se}EE;9e1qN3!&e964Ldy|lUy1QpSfl(2(oxf` zEE6fo7~;c+64E`JM1EE~DDwpaBp^PG&Zm)kfC9fZ4$FP3bX+H$fVX_1@B1}zpu*~i zrelY0Dx2M;4S_gs-nR2?Nse}ai-)Mf0sZhg_o{PZeU@+8pU@5t#Gi=xMi&?hC^R`D;F}IDhT}63JPV@wgn(= z9*maQdEm^+d?3IFu~(*z6{5lMd3T~7sNxQd`Hr6CuO3}aS9CH?TupyoJ#E(*>Fup1 z96eZl-4>f3I5>DfWuv=Ak23`XG30c2x=)vr+A;v1$_on%N+(oQ^bufDpfz>|Z(KaaA8>Fb6T3m^juvj=@(4!7N{^phDkh5~?W8 zI3H>f7(Ad6S1qPG<@ZV^72V6p8R7|>sHS|!#a}mp#_Zn)kwR3Nd&Uno%AzLKV6)k` zrmxow4N;ZnX7^?_Ci_NC)N@90J9#ew6YzY)+IiPy?-jUD36}}`RZ%dh+WJ%+x;=@v zl0MPZvL65L4gu{Ovcde7H;Ri~8p!1{Ez>vjAWQEz)1U5BFJ!dhX5)4%&c~L9F75CA zuW<42;B~Ann0s0>mSWowLA%`V`*%pM&=_7W)SFCv$o%^rnlfVLKvRRY7JbmBuqnBS zpXHKV)Rr#_%;djJSLQo**PQd7c&v2Yv^Y8Rke9aL4_N3LglKu^ry z_x4t?X^G@f3knKcfzr#kBC3!LJomV&sw$h2E8xP#_17&^EX(=y-A7D_*8at7l33VEIGQczax>`lAz%_w3foU zb=!L!7QM!szzXpYDu+NpOQ`qFYHxSlT~Oe~HcULR2uO;MACCO%GiifAlSK~vrD_WRm*;P`vg3*R|z7q|7Wn}{mAQ03R&#d(K zvl{vQR;DbBo3z|!Cohe}6d0v@w~(k!lL=Y|k!t(+Q@MiBkl74ZeTTL8#QMAZ;j+73 z$%l1S)U)X4{l&M-FkK z6ad*1eJ0qp+!`YW(`WQ#H!QjREM}C>4_g!}nL^+X#PQZvY{22eG9@WpP6vS=!vG{5 zRa#aCu{1Y7Eny(q3DoVuz6w|NS5`1B{3R8ui#P?qY|s!0#sjN;hP6nL!w%FD`9dDv z%*YW~uML{zDLlK(M9|@68{N7SsaL0KYibf4s_*xq1uSymnrUWrRUkmd`9e9{Bo`%U zwVDE8R!kwN|d#JbJx6;>=pglQ)8oSR|ii)JKp#azfE>H$ch zRADy%sOCTvR3-w-Mc=FU7%23S9SEifzPJUhFJDa8wI`&u66K9p2H!Lsxi(^%4GtmG z>=)yZNG^X8SoCmK4$cq^Q%*+X3iqcF)rBGUO%>Uuk~M_?KjD3yV%M zalnQaGt`5+@=!@)xbgmicmT)3GF9;;ZSBpj19Qa1AvDZnV?bMkk>YS-YFLZBy_+1S z6um8ub7grpLWJ$M(g0+J-=e$0>@GL>W^)Nr@Sed=J*9Y}0qfRe{~(Fj-UV2y=E z&e0dA-7LG>H+lMct`!`SlG)ywo?QPkMtaX4W~_vPwe={)%$q`?thrW=S^<=BF#6G# z!Rl(q;kUJ=w5B!JRae~bYUE1SQyS1#v;!tII}>!xfxy(Q`&fr|uGpMJc?e1QD*%h3 ztWC=O79BED@=~pP`V0+U*=~Dm}^}$!pKy)r}vXXiofHK0DcTK`M3e%nZy(gb% zXFs$82?g*89esXCd%c9g-uaEX!FrjJrrmLTFqoCqJWS1jox6kU>gp0&e!vvkw6Qo^ zH3UY8woDA*2BfiOCMH-4#j(z#8fb)ppepns*7>b=B*?V9qbDXN_A3?t1p4J-)RnV1 zC;!R{s|};3_sb#2rQ+MYN2VJ515%cO@L1;MllukZHn2N4Fvy~?@Y?8Y9pQ|ibU7GP ziMs&)vh%(3#!u6FJF39Y$7bOd*qg)nx9o*0fP!2C!dMwaC|2FMd|+_gF6}dp-c=u> z8xe6a1I#<(=L0SmXjrP5K(<95UIoc)K2sNVJ_t;gG`@OO zzXYru+%@*{@?wZbE=5Uq0@Tj$)gotCfl(XDLuGK`0MFpyV0Oz=O>%N_ zmRcK_CcV(Eu`h~5u*p2G#96uET2Md@1?Q*bU;s?n{FWv~aaUKia8`f{D!ET!nDD}6 z{L~A>1f3jR&;@-K&aC|Sqt0$XsIC2qy@Y^HUpOkFq6}FrnA*SSMx~(^%irn&23a{d z84yPzB@ed$EiQ6U`Y*I-(doYBL1>FE5J{1x$G`{o!MG)#w}$Zb2lc`jH0NE}p`Q#y z!GNWaNlqp)#e@<$JvvtYjT>nJpfsm))as~`ZU%Jy{AEkap|hr@q4SdjYIz|N9Ik<` zYRe}P{L-OfbNfP|hMS5i1P0&QAskr)X4R(`;JU8Zv8-4$yNC@T(B`&6g|k7zdgHfn zhr_#@2WK3nH|qA|F-@SO_Sm|0>yJRJuW@XR{y`QD4rYA#VORO^$GF`D#|kSz$_fAl zVsXFbfl3nK4E-Ht9)qKmLGRxfpLRH%_q8dFMlhxNdOb`_)|< zZP;CkRShc0Q(WfU4_(d8txPa$kR+_~)xn}&gmk{x{2-xUtw~X+EBhBb>}%-KUR zW<}o02ucpwgSMdb`Gh^Ue-<2d3$wF(@vsSa`RgY2jBHPEU?Tmdb|&gXhLb1|G?;F} z03*TgfH3JepjWqY4HT5%`2t3*M>!i$2Bg5OLWG zs}qc28y{smaL6~C>a$;mk((pLDgjQ-F>x45f=fzDHtjxgZ9RleYKr#<6x6Kn8vAuc zg&z@RhmnGUu_j!?KJ}T$`rg73;cuDDmq070kD{p@Wj5O`4(t)ix-o?Mg3Co_gW~Ut z8kiL={c)RReo>KLjVms~tDvB0C0!)~1%})<=O@bAgs6nG&Y*-7+Nr5EQlR~qXMxGU z*?b#ZP>{v;6SnJDd)pn`geNE{3f~=0J^mf)64g`yh9<*U^p?^HVjb@=K=tYFfajVZyu=&fT+K67)!Oy?1JsW z$uycBM$Xd%w1OHz^TR6^rO5qIAyotE1IcFgc|}wR8IRbH4K0CX_HGa`;!Ec;>7fpy z2!MBj)l%YIrB&^9U9j2sQUYNCs;ip*;^@~OZHXjMa)RG4AYi~Rvv>@ZSok2pc@;L^ z5JZNAuP;5{r|z-{@@>9h(oXhY>htvY>r*~>JRZ^Lfk(BQLV3(7&Kf;8pj)}tQxdyL zWF0Mo#xq!is)R$M9}11{w)rmDx+kA$p1qyj2}+#l5m4f6tkLXvHK8PC^bhihx8HrO z>1$#4wm`#{56xdX{%khhVSVZ{h}avTy)3nE;TIXEbS2AZ!dlx=L#{4wSR^L+y@gL& z041CWF97P7^0Aw*yJXk4szvJ{DQtd)yJZ9e*5IXLJ zd`D^rfAfFLYh6Hy+6)#dB65yDthD|5Z-4wBv${VP@`nZfu)rS{_`?E!Sl|x}{F@d? n=Xab0m(URYzX1Q@r93y?JsTIHtm^#RKfH3$5BtIQz6j0!4ZhHXLViqba^K9HeeUdiJmZ{u_x?=#Ka^oUO01EOl;Gv=udnLc*QJh-~)L28_MXSl{k2WiM#wLCib1!#WSa^ zg3=k3b(9S!Ly^lYzj6M?*>4G|ff|I(Bd6*nHp{iemHRIS1cc-f6jRbt;F3nMC*Z|Xe-j7iEhhcNm%qOC%kcGG=-nCv_XXE>=W|1q!CSYKAO9Qf=%3d72kZDQ zm>}^!-+3^v#I1zgoz@)Ln-47Ueb4nRzwXu_eDi&rT?^*rV2fyr=es_!wtrssnd`ea zPi?MI>vTPL*SmvtUS0Y8&j0vEX_34q^2!64-9vEX?^8eZJEuHfO1gV^z`X;$>Fz$; za<5`|z`rv71J8JmyRi9KpJ8m|!_1ttYtL!dP820RT%5Au`07OFJ$>5n)USj+cC(`J zKp|K|wZp&KqV2JP$?L|dDq86+?dQsb6T5Fh&NW{k_IGRau{8D$cU<83=JRy+84pTJ zltQ)-WlE0XcHfi~6Z>WQdg(~~77uicK3L`-o&G=X&_5Z*|Esrts-tYyFvz!wIBD!v z9*i2%&>K86e5BlWr{fO) zHp>?IT5-(p!^B>2?Psl@^|jm9oWaVt zS0ZQgla!R4+Tvlcz2aU=3`cAGPy;xaTL&T>-9>JBux(kFMq#<%g_wi-We@B!NTkMer`PpT&&Rr~=Dz41I9T?R_t$$Wy@)5PUXd|_Pj0`kVdPuEllB9aBqd z@kBO9v1}sdC%ARMtB-3-p<~4KTqt^x{M=(p#%77}yxrPGPiKF*69w1T>*yahpBh?b=w4)Z zY5@1)`TgsTqL<6cENc|PHC#K!k6Bwwo+Y4*q2);8*id%1y<>`L&q+(TS@+XpisoI7 zL5z{taplOI=Z1o{IZcrvE`QiKn>ALW?#`S$YAV$=PMgV|Ofmhta=Mh)A{pKJZM1(u zR@O6g$wCo_Tb)?xMbbK;u3qfHW{h?#g#YyYi{)>l&s@>jDt0@c;7WW#qtQ4K>TYJn z;!j__I*J~zd$8-pi|+MNPZiRTO~2@-l6rc|R%oOuMHw{*SW#WwF_=teWMyS)W_>8c zXJPi`Z0KY(-n6@Y&OBWDb-zX9rO=P>=gXtEQ*a94gQ1JTgzJ-UEZW{o&Dn*9x-u2< z24L5;8qilst*tc+yLPon98Vz&9>@t-+nd~b+Ap@?gC-ltS4WX1q`W*sWfhgWpZfbP ze_CJZ-`dl9eWTRATq}__9iUiS`#qPvh+%Y+-fX3lem)x7rs7cD;NBL8OiN333uu{2 z8EKXhZhWxH)2R<;;WKMj)^;eG*L{Qtr*Ql>6Ie_eOsY8g48kB>`mouUEqO@GFktcx zG{gA8>C;USF6QPIb%kDG#9xrPR=$T5mAZDyR(%*R5J+hSCNJXpo^NtaO?5+qC3hsk zDbulvP|oMizIhg+?QkyTHU-MQ1hD@7A21TM#fS9w$DteI~8fYqstjx@ah zi}pBqq82f81G|2;0yyhKY63(sFWkugC|Kq+r*n0mPCA^u0qNgA9yI%)WSXBe2Z9u0 z2yEabD=Yt{i73eU3F%kFOelOUjmmcmFfr*|lQt@-tK+YTYHaVigF zjFc{Gj|WZNNl#DD%+4OoSPD8^dA&uwjl*ecW@0%TmJrNJ#!OvZ(VNMZVq3{DWc#Dx zy!-dn4x$-rHZ1l6@gy*&L-X@adD|7jCFQXG&z??0ihKY| zs-&fchO|DHRSC*>Z;hoU%Ikc5H!826d9%mO`C8Lm?Y0PYZ|MF4pSF1Q*&AN<`5wTF zl_!@>J$uqQ9eyawB}YQ0`<}eG*4EZXRa9)~^hl{QF+ndUv>s&7HqX$x$C9+Mnun=Ts0D}aORFbN zb}t@LhuLeC;M3Es>R!FlZ7lUQKd+J`$rkVFNpMo5C4SQ#sv<)=IkE&Yncb@! z8`U#2d*pL0EaaATm+3WzTe^h)*tLZ`Y9x+74vUOwk!?ofSd=ZZZ4sX`6%P~G zsK|6SuzK{VyGI0rwg^l~R+cjZm@4%OA#for*DCMhi)5z|Mjs~A5L3IxUuR7vslwSe z#T7&*>eB-p34J_;v4IvYQa6Y`I39^9=^4sjTN>M==@wS&Ki8Lm!C<135!m(iAcJus z8AYtG^`_KnU(U6f@Bk+G{sj*fvU0=A(UB6Th!69_oIZV;mTi`{e_z22Vq@NxssqyF z!X_z$k4+#^@IuVZ&6)Yd4YgW{-`gMAA$I$1^-UovJQ4Z-J(W%l5mkwkE&2&x03qKTnwPU-?_O9=S zhSfdv{cpIER##p$U|ZG`6lnpG#7*`u^LeXl_qN*N<#fzXpXNmbv$SGO4sx%LdKzpS ztO9oD8T_#cZ|+ob6*K)NdTZN0!!T-1PtS6LhrAG2;WSLRPHfy^B;M{)#(`70Lv$F= z-fm?bZyA06{*KKe^B0h7F6I&z$4&+c_*HEI{#Gj)Q2psL9 zt~9X6JS-4&yK*G%Qc@a8o?1>@y4JnoL|i@gp28cK+o*h4V6Cg86V_J=Y`p&%!>yG#-dSY6!I)KrOxhzQIC zOAL1d#AuEaH{)!K%tBs%K2Ig=n0^CxgR4lNFLFZp`1mZ(_GW4B51D<6-ZJLl?ye;i2!~?c6e1c06VIMKV*_W%1Q8vk;>}hcJakC4rrky9 znc760GQ+DfCCkZV(>gC+YuBLlbolqjw zC*A{`sjmO5K;oV}$e$OzaBL)w%>pGx+$gA0p8e{Bb#;)8{W{*5jk3xE=WQH76^5c6 zuCTI7?`otMQCe)bl-xlQiYrS-PU-BwDb!3!@4D6M{W{$E?D;qq$dJ^b?)zM;6?Auca3hm?IL z(Py5)(z;un!j@Xj6G5&Fo+Bx!G3k)|Jwzf?zF;OYGSV01z6Ov>&ue>=gGNh9V`Ht` zbIyTwO;TI+iImE>vWEC?NQ40T&>(0b6BPy`K|~H~xsMopRcXFBUbnt(DK9@gceB6p zK?am}@BSU8_K5*UPtSCc?#({IGtdBR7IhJnvDzG7R(!*-!H92_53VRTE6V^ghs;`6 zTbNk!x=N5Xx^TF^)UE)gHS+ofxa-F3?3wFkX5B5IV$WD_x4^qGO3HiFQn)HNTMjA!=U0!NvNUOTbyX0(ZJ$UJ-g#HX2Y#6p} zbuqM>#yLlRTcMdAvR97g58`lS2bsJWWN+`>)CMWmmv2~W`Y?~ybf9#lq;*a%`Fqaw z*}5mGVWvBYZE;@hq1-uHcWN;0eB0K=3*rcJ;w|WK+k=pgO)Lqnjf!Ey*xOu*~# zuVksA%=%z`^rFMDzS`j;N8UMW>p@pyyR58i=%5_vhv>bpX|S@sbW6b=(a4xQ55aI&?rf5(z&6a< z!jC(+w!Q+zr-hcB#GykuEImCX?iPV49#Uh6YbA#-VL*U7=G-%53Odv}QN6%MzDjK>b4?#Pr)P783{u zDZ{ou9aZ+|f~*xhd2e)FyGl8D!f5cPn5U9$z`HtrhPEJk$Qa}NXek=ssraFTeseA; z+=4SUHkN?drzOt$d*dwo3yP$TZuk_>cDBz&<8^$W`CldZ>tVXz*ZEMhkW>su-NJ!u zasK>S9}l{tBeQ{*tXNAh5!~o1rg5vkkyUr<5>kac94u^+T=Ohyf5;7?6!zmisv5kK z(VU%~or~;A-vd%jD4UT2#q!sj*VosFF*DDom znPcwtpym1~_;SY`NQ1k(yP6HCft`X0P@oi>SGnAI9%PW2q3tu?kfDz*I-=}hFgWhi zo7{c4tQ^^hz^=?biUFICVm5@HzX__FQac>*b`8rbV?Q@uZG!7I3ODZ2`atx^%R6v( z&L8XcUHHn%PB%kb;2P&Fjc74Dx2See`(!ucv6dJ7=HY~{%2(t;s5iOy?p%LWqW9QJ z&Vx<)%~W^eRaMZE%Bl;xs}~nbF+2{;uL%5(rpHj!bdGuVT(w`zN0FnocXuyfS>4(Y zyBfdgN-~+;pbD>4F+ZGuF*DO77>Q4OoY>`MlH71v(`TIE+TGnx$Tp`ywLpC^`mSW< zy0}7D`ueq-NTfaWm-0dI6+K0>F+|vek+lyv6e?pht)p*_-*t+2LGolEXp20_)mae` z9KXB2YLY+x*3c4!@~gw<78cipj}8?-&Qomb%(cu>;xMU=8X&eFPwA4}cw%Q`GXxc>6g>648Xa(v!>;c|pvD;}2NnKB(h(7ly_~hhaC~79k)zR_A9B6hu_UN-sog679C#TwQ zdNXTFXJuc@78i85IZud!=g;P})#1T z-6!v`IUn6XO zJz?v1g8{v3ukTFmQpO&8`uv0ccu$4}zTwu>Z0&IA)_9m5SUWA z#S5_8sxo&d%Dln}nCrHPP z{qe}gjRl{^hY1P&1W|#5-Jm0Inu*`(90VwP%`pCilrXoTpg=#i&kk3u zYol)$s^ub_ILGGo;|2bXuCA_hO`<{hYGlkNvD^AqDl)v@s|({_bg8aRL=m+qi+hV~ zgV)>nTyAY;ntYG3_{5#90^iB}VspF7TNAGj6c!dbSNTw#Z0+rX-M>r64Q{j`_$~D2 zk!^rreEo%dQHe-Kf>$s%c#xYJI!l!3ty25b@fQwqK@n*I1v?5E&zk}YI70qQz1U}< zA)|m>jy(-Q0@br2^W467jd{d#B2M))xy+gjK6ra#$0sP-uUbF&${#&$i{F>5IPK-- z)j9j~^578J!OFlc)XWUYo3%zcFNPbEzlz)&b!$iKv)_@JQJ9$6$F{wD_qIw78M>Sz zx|QQ_mddrpg+R~U_Ny+g5i&@Atp8LBefMdr0y(_&$A}TBtscZk1}xS^2;d7OrsX

~2TqUF9v#Rmu#cC02 ziH-bMQnBXj_TO#je^ST>nf&VJ+fQv)q?+vpsc_LTz?=RJcl0R)`N?&(lN^DGh%nK; z3ZLO+-9@Kz^1IeD`gBwN%B|zk{Y(#Luqt)Il3xT93wn zJHP2ZAN~RsF-U#+K_6yz|+`!E=0t5 zGNXE{ZO+xk!zpbOyYnLLe%!cK!uEVCXujY9?ov%QrUiS#jsO>V)xSXLPocB_;MMcrZRpE9cd;l}<50^qd?{=n6RlP5D44yZn>sd*XnCxG7X1|@z2dLB1!?AEpm z^?NC=;#61V`x~kU+3K>$MGxquM+9Q!JAVxQ0kF9P1fCOf$rm$QzU8_OS% zxenV`P~XrX82%61t)jKIujUVIakt+?o%i|3w-Ga1@mG$5kf`x_TwijZC-3q$kpK+* zN0rZ;iH)z~<$^O(Vq#&ApTGA1)qKW39ow%X4|G?+R%Zf-MTT+q^M>KQuC%D_Jh%w2 zU#?0NjXYFS>f9cv|M!H@zQWl$=zZ2!Bdijv?jx>VTJLLpZ}dfw-6{etzvP0RIYEd5 zI;3JFkHW;>nxi%Hr_~#m)~32GsUdWsp^T9@hbm;Hrl%LZ;W355Q{6g?g*KON9V-RB zN206i10WsZs)0U;WRAD>IMvw=>X(>-E=3cGwbS#RWVY?x>CiP=xll8cltciENh82r z@Ujo99lle0?|&O@3K&s=54GVWC@3r~v&Kr*Rp9LWdDFvHI9iWS<RFgwP~F+H^#}W46cZ)@*RLgXe{Cy%)#atjN~b%8FZA{k-)yP0uVmN` zY9hUl>W2nio}Y>z&M-uK_nmBJ-`(tE9V<6{$^ZQ?&4uvFu;SdRs_!gqIer1hM>XfC zfZp^*Svg*IzkX#2G;F_)gwc;k&LW2I=|LZ`(vp)4QAAh*p;ZeffKOd7W&A_}QlGD~ zqwaWroQ1?jso*2`Kyj3FLR^HvPPlLcMA_=_+gQn#mO|Z#2-!!VAq!&zM7k)VaG-(c zPsQVoPDAn`$Ts&#TY%B z8xO;BO^=Vt;SGXuy)Oe7W5z;5zV6>t&Z()%4LY3g)~$T$sU>ZgOs-)j9r||84zy*= zt0YI}Z}%FJ`}$pSzbtp^t}YlVIqQNyP5eJ%WhN$Rezb0q-49pB((&D`WnNwaKT<8sU)=9c(-zL69&Gbw*+;s%&?WE^bQAu?<(3qyJPk_+@}tmc_9qa~ zP$6AO-dxCHk#brlROA+GXbbnFcfjO# z#OF8zNv0lvsT&?}(%R`wA1_VShpYkQps9pA-0$Y+e=BO0+2#m(#pQgav4EwZswKtbd9QxM@Y z01Ejd)xJ|08WCT7A4nNMbnLep?gKR?e_F7Azsi+cp*rlRT2WS<4lOaq6;*jrdw zoc`g5-j*l`HmD!iWEw!bfT{t}yI8fjxEP<(?Y864p+iVW@P~&|K!;f@H_lK5HlJ`7 zyQMyR=G|lCs6|+#7YWr3g*x|1$kbV%bZ%vKY~L3&%K-541hTwTr20&~maW=fY`4@2 zHfZT1P|1V?K*Y+?1)^Umkcq7o!f)}{?#2xb5o%|8FjapC9zN3*T&$w4y^9LO)ugBM zN7TH#Q((e{j<~q{fzPeEe?LCMkY=H0W3GOzcXpP%C(Tz_G=ahxPVG z16&&tDxsEEW(p_>3S~LGFDmPN{Lm0!i@VK*;1d5W-)r#4pFK-U0L<)zJhb*DG+esU zd&3E3++6Pk6f9wykARn@k2XZPT_yc9%>$k8aW^0of@Mv)yKk4pEVri)slo-%k7o4H z5tR%?tKzqqd*eu$71YeET)(5!LS`ESOf>9LSM}=g@l5akCdRcH$bAkO7hJA`*f)>@ zx-&g}Ai?IxQEmgPlu^9`E2;@T*9O|}qP_3c1AumUTeK=CVhy2p35#waC?qC-ja!8X zl=#;v>J4OerUmmWl8mQ>NwOMcba*&9Xni^vAKNyyL4Y(i~ z7;$(jxGF9tmZh*PGQopd-?edjbNJ zIF9tSLx;L+sB5jl)SX7+qC-kj#r2(dmt>m#?XF#lUvHPM74mCP376573J(T<-6x(7 z<`F>l=x`$Ub zIdHZuPL=T(WPu+*)Xy}~twK${*>raE=7n}C!?p4RS+#+c?JF2jo^|-Il#0&P2`VWG z87i>1b+q&DeRh@r=4gTwB2kOyj*q9jp?Qd9FRS4W3hH^|5=?a*%u`%$@9`8t1&N_Luu5}y+!77 zUA@l76;w+biPL(_+;d;*q~L}f2{FanW;YQ75nk6y@7xMI8GUB5^A`gv5hgMq4vw>{)2b9z1>kGe1z!1N=}IJ!hzBs$=E<;q7u|6Bjcl*eW123R!wv^&2Pj+JKaE zOkJk)4Hy8Z4_TTVRwr3py9V#MS|Mp|fk0q|!1`)E+_%R85_#>sXh2|SC>M5x#@Py4ytsMMZ`F_vnR?`ITSdzqof1pw{79!@z~Hle8;eYy`%ml{iw# zUO>VIx<{!jB5bLbMI+j>S68P22W8xkS?fz>u~?ztXr4d+(CIqC`ri<0^xZoRHK2F` zP_#DN`an)yKM*v(BNGF_ThV!7APr1QTiVQtovQp!UmK;%(h<}B+`r1b;0vB-g9vwf| zJ8y?HFpa=5!=%66>K~Z&oV8plm~{YRI2+uylhd$k6&Un*K8e8?Z*7=xzwweV0^A?f z_k81QVq)9ZNh-nhwG{yt9FRVWb;G6e^7el1=U2O>&mOTr za7QmOuR+YAAj9h|)*y{tK}M)6C@6g|sus5+V(F^|Rxs4eOj(eDJiK-#G4+KBVttua z>I`4b#Bw9X(Btd9OLz9ne{4}%ttg7OQnJ=yY?7L=i9;ii$VKJiy0S_jE-w+lU{3yy z!=}}1Kriejfna(6;ZQv|C~d%U)ttEYzP?Ld_7nTT-05WY-&T$}x4y!|a1F~HM1bg& zYHK^99A@cmN7a40Ob%N{iwL_90Q>;jxGXQz9=FBnrfFihL(7qop(GJG3lxodfCRfn zPUHb{c;sb%Xs%m-Hq5JTrJgqNC}jvR_wnWB9h()%oN1M?A9^EPJm^2eLJVtuPj9Nd z2*1NOX1eEtgEOMnSva^aFl~v|)lM}q2S5noP$05Y6p=LbwFK#rD8L=HWbef5qBxR; zhYizNq4fYVa$L$wFJ1Q2E3OG{O{S@W++DP-O8YWV6zyp}6uX*FW1o0=(j(N;WOV}| zh6nqG8J!K7_)KWft<`lC7#PsY)k{<|$ARQoPN5u{Q*J~R(z9N^T&`_i9wg5%6@lVp z9?VN3Ko!>0o}gG2p^o*JyV(7Ejk#ukC@JXrqsI;v5`e*8CW-dq~wvj*Edq~#gdvn@2 zh7;EZvhWtSjz6X__;muLDuT&km z-*?vqGm}ngTQF1g0yrap_qiB1_r-uh832VB85ypaO++aGhi21H+{7)3lxBJPU6^vD zkCI6aOLNo0fk6`d@ljQ;4n)cO!gMf)*@O~NHtWHM&;0dZ{S?PXpzgjt)he&bUz_rA zT4m2a%%sYVB9*MuL4`be%xkQ&&-8GL*Ug&zPyli^_viR1Q(nzk$bpuFm#h-(MFw%U zs-nVdwq}LUyoV_eoT&tZCZd6nI9=cCf@IA~;9~knqTq=S0Ihja`yB$*(jG*NmTNnr ztl62F&$<#dxuEj%1SxEL!DH-n5Q5INt7NoqX!{iv`RBJ0fFG&K#H=;~G7|s>+s1KV z0HXuUN2qs!;AVOlWh`Nul$aP`k!i60wgJPF0Sf=~9J91Q!1J)L7b%2f(FBjWHONQQ zw^%UCV1CQe#uxGGMsKuYX3%vE8jbE1vD~+jl5+daUR>S`Y93y9BJn!)`#mZh zDhciFpy=eJ_RZbx0;tOd6uB`=H8qrIQ54$$Sk=otbui%lFUEyC4(~hSR5~fU<9lF5 z3*_~cxjVGfwm(f&z>H;>6UqrEB1)oO|0|Kdf^pRVJ+RRi6W9+f=Ad{{>>JD%22T#;6593~RL_%X#oqL2YujPjX&PG4TcJk|F;BO9Cs=Q! zA!G!gnPdCoa0UdIcKRS|rrvTrN*_{GcIg)l2i19}ccQkP!i=UR#VO{i)bvn~AE!~1zjaaym=pN1<4?$q0VjCIa0RN=x@5|tWU ztKjiN)Rd-HUUPTpSW`n9_+J;*dqm2x+vef?h&&l{M5Li42 zqZ6QW0CRE>MJ;XOEI`Fh=YQy3PfyX*PR7Gvr97KRR_}`SJ|FNOi}ZH9i+%+S9jYA9 z!!W7B3v|)Q0l>hv7eSK4u^Dd$#_KJ}Wg?8S?c6DvM?NUs(I{4IE6EXz2%oCn^94n7 z2HcT`pSJ5RH2B9=mwytVey_TMcmI0)=^eN&Y=r7ai#{&>!KePU*seeQ&A&eQ*AD!( q1ApznUpw&E4*azP|G(|PFET#U|MK%x$103{;`NR23h zv>+gaj(`xUp-2xzY6yXZnh?_NC+`04-T57N-Fwdd&i&&~&hcb@WOeE zp!CIYir;RROt~$x!swImJ%>wv*x^iSRH(?RyGV zLeBXYFXaK1@t@9)@4U8K2kV<$PEve_%v$Yx8Owo;`NT)a_Qzl41wRgaeOw-b7}MqvU1kM}nMEDKm zRSY{WB=qiB%K7c!gTfL$-v9%A`xk%kvEPKgkp@40Id|^!TYF1lwMa6D0(d;l=l&uL%qt}E&A0#NTff)#ZoAMpT7H{*nR@1IMAuO6 ze(?MCk2}A5wfQNrjw{&udYPr)To1x6E8debA$i=S=8^bBN{9;O$ffDLGT@)7F2%yn#GsP=cl^W zqT)E<^RVtO+2#GEn^|xDuenvuii*~aTbI93?wzo=f+X#~w8gpmXhR(zvSX-uK;6t? zcf?v~Q8BLI)S!cO!9+bXCK3wqYkd?~;vp*}wD}?_Dd~r1McEQVVyP1w8U;n_q=q~$ zFB!g!k$qeuLmtY~82xWoktrxD+1IS-<>kfOcvc}UBoumHL6vi$3QGK^zV|QO@_+WN z`!kP~?iaZHW0W0Dy`*Vy?RBI2H$v}l3H!bfDStT9{ZsIJLr(-w-TKAn@P~ML_pGL} z8P|2$TM2x=^5?HR$Cm>2k6$mi0kc9osX=jEZQgN@dgK7Ov`^jFS3a*gQgyO>w9_&h5Mhfu(X9}H(ON>@u39kL1N){SAj4MR6|zt{Sy zN3Ax+$;S>f+t>jQGV~@PE;cJ-G#oDBZ9aR;H6x>r@W~`OgVA>am&0E_XKp^MokWO| zXcFWR8^)#xBtMLuoas8KC!=sgRrwmkdwTmYhUD8_UhWMH+# zue#FLkMQ?Y{iW0W@vV5ZYN5(ufw>-I1-bmv$*-mqiD)I7!xAw}M-xR=0{Q1%`L5{u z-MFDHHSgkd4L|axqwmT3;5Ce_?A~J?6Z5H+f%ah=0~fc89k7Wv%@skdzm(;6Y_3eR zz5PuS-@<&UP%dafs3c689uv7~&reGu;NZ){{onBHINHlVUG;&I!HNi*oSu&R*k&R{!t zMemHQz6m8WcZenZNNx_|bD2_^2wGK?oLMI4ny{IDV4aPEtTSOk6S+D=LdzegoER>d zUSX)XcvT-fzbVwX;Ai;- zs=rPQs@CLI1rJ0>=p9qmg`V`AW8@DMU|i>yxTqgH+S|X!S94iwuwpk4B9yh-!Dr1| z9#?;1Rm>ZB56-|?44$!2P3t72F)wW_4DfGax_CFVpIn~N6P_gN@>z9DOG}ScT)H{j zNBdMvJJU7R)@LhlK`ZiScOHHFR9e#?ZeV6+>D-;3lb66yVyeo1re00>|$ieq(Y$ge%ib0c$T@+zzBOa=}g@R(F9EO0ubQ3<1@3KH$ zh)a^ut$)K_r|ctlXQcV+KlFjYVCSJw=(N7L&iZxXgJ;y#%zivLBa2up@zftW4I;!F zdo!WZzgiv67uV;aEAfgi%qU z5w;Sf8ps^ypFEp*f^g<6x0mc?M5kd*8rJ(zETZ(c`a5$d{+jzBRb9cRCMG8Ag@FR- zrTxFg$BVQ*eE1;9h5Y_O7UtFU*%6EUGf;bE&a#^{-^T&I!pJq&ID;+n^fD7ZNJO%g zk6Kw{6!z`UHP3Zs(1ur{a_c=hBefc=Z?0V)>h_5&a2c;gWu~`1b;NnH&$D~B+=qCf zca)Ml8@LSdF+7@IdABV-*}bEKuv=`$Q>Qo?yV0|7TH4t$9I9c-7ng;5`}U)ZhOgsf z7;~K2KIYv}=UX|NW2|v?VnpLqz|{Mui6?Om&nwkAC=@IC_J)*q_+0Q>Q&no}NoZIWtwp`nts)Nl9jreO(Ab zDLEE~owHSvMDSQ-Yc(}BLN|FWy1l)sPJ<9f>3K^zy$h9<*wgejQht}n7`hh6#G0L;ZUuTgq4Sw?~9m%>dYpcM6RqjKw zZ|Ou<5aQZ3*qzyS7plJS?X@{s%w5;2S1K7w?RA`0*a7Zx^MKvK@HZYKB0Kl*ha5xD z>o%mvD!9)T6OOkR-P|bNxifm*1^3O+5XvTSb*70X4yoMW+@*ToRX8IV^``%;6Ro>JY&3^+RF%q?AfD?4A$FR-$y^|jy!uRIW`JM;!Wb|wQEsJ z7cYiIgFAZ-lHD|3k_jpVJx!Nio*JYrm8M4D$Zu-0;Ecm%wNR?e>!{S;*1b0P3Z|?yG7V(kOQS+zr z3dVMUiw*57Q9reMUA{5Ga=h4*^5UgIavcd$R^3VnmNHg z6)^f19yN|<$L@(JZ(!SP@^rc8$LI?`DkJHL+f3THycY-`9KC_hv%XVe!yB-Mal3oA z2s%t+hK`QTZs5g1PR)^`%DLC``dlz!jQUty@WRKu)ipA~%%s$X|KGe>QiNOk)f@HH_9epC3jkAy&SCY!SI&`=x&=T-&yQ-nV?$^ zhK7c8sCQie+2^e8TBiz0BXDu}kru6Tx(hFZSd4i1@L?l46XB(QBC+q-PNDlBD&0^T zo*qP4?{HA5BYtmP<7hN`@0s%ZozpBIxz(cPUzQ&dA4>ShYdZ z%+Mg>c3ENw7KhUetKwRNRt8Dd;v=pIA9Oq&QRO*?>`{HIkkGa(L&MK`wbZF2&@|=r z?0S6|d(LaTdLwRhbX4SECyN+3wc?>*dq#vGT-EV?Z?P|D#N#}&vP5c>xlTcx(A1)C z-`Z+*(GxzHruYG-nZvK(qom3}h)7X5$br;^w#H51oE`hHnGX1*Ktf7cc}V+HeJ6aJ zA|*w04 zSbVcGpW7hrZjB{Y-D!z=c`&?ZWF+_&o3TW_o@Y^7S*hMXK5loz+}zKZ$6vr$KW%P6B@SL@+rcSWu2ZIu6kjRATjn%ZE!3hWogh#axWtQ->ghF_%Oz z5O>`^dW?jgO|?IzM&26BITpHgq_y?2l&no~O6vCQ+Y=1XG8q9wcJMmQx3yVWw>!*G zTm8BtIB(eWUD~B@gzi%(Q_n<0tJhnqA3TUzA1O264vY&rxK&o@A|)47&jiV(oC3O| zaEUu5!!Cr@inoP}>1uS4+Nk{c6%Ok-R~Dt6%ly=cP-545T3WhaQ%HKO2xaJTOP!QM zy>7U=x-|BnTuw*8SiwWYiz5Md85)WEnKzqBxvy$-fD^5u9@} z#W-xR3wim3Wu{K(rk0DO67ft{SsC&Z$gWB|aC|JbdfmO4JAc8@(Asyf&~tjIFgPcx zYz=L6<;s=s*I1z*lT8uy2~a7)o!n+Dj^y$g-u%G68hX7Jz3)C`-Gg~Cm-WIax&xBT zv5eCNjuhv-c+^DmI%6`bz+mHITZ=IO_D2r;JvhM{E2MBSxHs zpYXw5d-fm$JzlcxPQ9JzK}kwVii=tM31-$%h%5=jUmdi@($bO#YTSkQmLl`| zCR3nqpYMfgN-7L%XXWRY9}92G!r^esxCZVL$V`!g^e{StV3K+!kGe4ggS|m8X=n8@ z-mW)B21Yd-;2_C6m6zkMBq^h^>R&p*_*(SPhOkO54OCsInb`(8HQSB=ca_|gJ>@>A z-QUxDs=OhO(uM|^P({Us_WGF?vn$nT2vk7F(>r5Zd`l+xznooL2?cRhott|!Ob~pL zq9~m((At;L>X;K;khM?BpiwlVsC(0`0k5v-#Y;4i^@#7zQF7r@>tVD&#>0me+8OmB z(nN#(HPzKg@ed#J-mgr@hSV=q7$A_v?-OmcUgqV3x$-1*B=!x>usT4I4j`mg)w$7#Ir$Pu ziPW?-Y9Gaar8yK8DIz3HT4H-(|@y9*k@zUS#27Sv5r8Cj(YdX@6> zE(-7+8ZFiTeB-&=7gNUPt%t(>g%2M7Ii*9UBT`hV0<;3-lKjOH7ffC8+qZAYFy3sI z4GJ|YSZoyc$|H^_KAD zr!?<+4LF$gNE%^V>y_2j)tO;zMpuUVy{VGP2_+wmLtJd-&;2*pevvrV-WLZ{ED=CbfURrbCGh?XlW~O%)g%f0adX z?k$1RkUX=-vQbTYEH0Mhj7NLR+t+R;J|yvZ-F_*?x3O4SbE$ETy4Ehe@4vrNhSApI zuuH)pE4t3Kb{-vdW_G4mIfQ~a9iyRAhiVFcc|?s=>%f6}bFaC3dkZ#R!n8-ME_3feh+mD!G@I`yoEhF`*#Kk1Oa-)>aqH|J5r+nf&8 zNe8VjXx!J=LCu$CpeUsAUCU|I3hth#)G_)1oaAr1A{OdmYYOyotZl9 zO+oc%Fqvp5XvP{PnpQ{3aB=jdqATJ$(D6al0zp2Rl~npD%2@xHP3{gWIFWF?TTD^5 z;7O~}lgs#{#_Bh#T?mJAu`R5+c+VU#rA=Zp9z5tHuFew7)O}|vy1Tm#zmr4-o|(?* zII`-}4La&Glx4vL9;WIrCdgR8?)~ysoC@CZWI1`2Wdkx$(Sow#>F|?C&L$7!l7|5x zX&+5!Gd3g0?PMg!F}KSWQfy16aH1l6YOMw)kbb1Mt_b; zq@|@dz`$b$+{*wmi`3Ogwn-;DSJs07VMtx+{(S@Cnk|woSDl zKYuING-GS>!FSt}c7w11$_VS8pWj+`9fGNZT5XfVNQWf+1t|7+hq7Z4o9h*1|LVhI zA3wf*fyH9Mj2Tdj(Sll+5LR>=!Pg{;~4O-;P_-5F@y(BL2g zC={Sh#TDNycXJ1YYZ3I)w8^-*xN49)nF3GJlGGF4DJP>;Q0%QFJvxV=kHZ-s^U@Gx z0;+Cq5Q@?$dH&bLVK8}_>x*``x@DK7ot}MH3Fmv~g)CYqNfMM{JU#BiZN-DUu}((vnIHE?7~yM* zJWWYpybCZ+^?x1Ol?pS>FVwbti(oeADj{iM3R5dVJRkuDj!;qYwEPWBL zs@WvzCJiB>Yp17n%q-!^Y53A#b8_AUX9o;oMXC5wwD+&Y)Zkh!17=+7ljXJi?g64f zm7bQVxlspXk=oOf@CG&-uM~E1+~!EacPp_C2V4buQ(OGwfHYO^j1B0yM2B1p(<)k> zchu-X_-O0WaSV6SMSNgzFi?AI{^eNlTUEeYa$-KCyehbmpNZ%AmgCqltJtRt3kfY< zUGiGRf$Bh1*P?Cawk<7{g_vd9_Ly4**56_*<}84u#1xNME3VPy_Une--c(R)0yCvUr)(_%d4a`5l6}r%-Kv7Gj$0tn$0bVo=HtNZ_EH`0z!?+ zBmjO|KY|TDho1851!A|v*`dN%0V_Be@=wtpc=unuB+i#SvHCz>skL+2TSVAb#=%oiREJD${^`CAf5Q!U5N9WjV_m zDpQy-sx_d#C$D@RqFh>>>c-(OXEZd3)dqm8Y*wHCsv7=vPI%bc|C?)X0zGYX`!qlE z**svf``(9Cz6waIE1Qp8T3FD%Zon@=f_k+X_@6k>zl&?mtzMFRr-uJ;NcMiQu1-Te+a-6b;38$M^d`)w{xYQw11CAUW#Hu!uT zz3#+9@Q$ZDw zCWk^iE8uQi;2*E}4Da?o2Ackea=wZHzmAPQldA;*x%%G`7oGE@``RwIlcqqWUvm8Z z9~S|>M3-)L+7Sb?1rA>F#b@$|oN#hZiMH&i(w8Ozo;f!BtB&$--oQ=)jBi%-cKb1{ z7$yL4w%%WlegAt6S1v6y30$`M)PErA{|l=tXaN;>Nv4Ag#QeIt!cYbFUdr0bdpe9$ zNnCVJq*U0Bm!}4)M}EYgw6b<6H0T>++V}c&xO6}3J(ctsOMdL@JAJ!yw(W^3P(*oy zCA)gS0swk;4Jf37oRSpwXr0wtDZ}2|!80&#ehi81-Amp0Il^IY0U-5_kgQHgd^!4;|;YBRlMhgx@cay4P0Uq{Iy!gxWouZ&? zuk)Sj%$OGFTytG0WazEu3W%5PQ==p0fu2F+I4!+=00A1zbar1sgn0QIf_p6rwET9f zj>&tw0+cGA4nhXvw?ar(Mj@ME{D2NVR}!@%sSD~gibqCG2yhXfL*R2p3Q9=sMcLW% zB>>iy3nt=~ASM5R0a3tzNFzXkY0L@$z~`9`g1Xt3fYQZgXBXVU$Kl7oWSYnCsE|av zICLa8T-Ca*XLRG2CyzJQEnbNOQr6phN-@Are9^qXO2-`2wP!Anj+`-eazacJtGox( z)k|I%lm{RQpKj&(Kpp|+*8+3`9SUG=by|U($-IstrX?2bzBIK0F^9=kXm8%Ksqln8 z18UYW3bp2^BcPj*T-mX8@-v_8>l4`q=6K(Q3^?zNPI#!t#saDcjV~+73a}~)$w4v} zJuO`briM!+Ydv{D+ObvjP`2k>=?ecr(Fu&R<0YPB%rP+Pl*(K4av&)ODrwWd8;iYn zcSk470Dh+x`m?%`-fv_k5THE_@NX?3vQoi^NY?>zs<_)Lzw=HB=Qk=ckve^|+P@Y} zNZ)s-%C?dmA=&iQ0k5Z!T=SEY;Iu#WD8H<&efj+Dy58!i#u`EQU- z6I=6Rem+EL$HDM_hWt8mDk38fO`C%5JwTvJmAOcngPRW3OgZq#EFGRiiheG0(tsSZnt+8;JO7@4xcW`%uH>Z9AxJY;miFgLAh6q^v`QbPdx;E z=7md+nDfg24=^2GlHO2Lqsr|w;?K^v=t`wpnjY>01moH1*>k~w%6*L9DS|N2%>1XI z&L?}CU(Da&pkrcV*;d8&bvgh&fN`(N?bfa5*@cCLR9X!k>HarLdexSgVN>cj9lo$I zI0aO7O)aVK8T5vRmjco0%dGE*gW*xKNVnEJa}@-9akJT6T$l6l(cmC!{Ono0QRzq% zF-eg>IRiArD|mtI7a^iHV4TU}RR(GAq|&g`q4YY0mZp}RnAClP{qz7TFWC2}dHi_A z()J~NYOx(y$g8qF&1a11ddTYXre7Q}vd1ba>sqUyyqcsD;_o^n2p11Pc`7=_BOyo* z{$hX5QUOswJ*)gkuVt_m#lB&-p)?+kjt(u(&CT_#ToJqNwYMWk%@WnOww%|nRdAAA z$82(F*t{6DP|Q1_FjA!wdIk~rfzKtO=yJNOwqn2|UZyHSD~KULrmnV8NGir+Asg?$ z6R)*|!ARp{W6(`FiV5d?P1FXnO*6Ge)US8x58VYsPlwN0D*)RqF61@V1t+K35wN}B zLN!qv_Ycfo1M@4%@d@Vq{~a6Ozc6XbFuAK4G` zBs#A@rw+<Cc2OQ65v&5R~TRL{8Yf+a*Ro zdZoeE0N}iJuV$0F!Enl4?Lgi02m2sq;zdvx|Mm!AG&Q=Lp9a?sWsv0el>xE8Vlm+J;wrkS(JiY$ zhe4WSz$yyJ5nDQatLC;1Iad)Fy5Xh54WSP z*sqPcXB|uYk0vS*ci)GH9Y>%8ZfKAo?$Qin6)j=z*qiqS|X!fUHp@a>FRjx9QFF?xlP%_F4 z#5Ax!&T~wBG&1SP*QgIc$6b4!xRtCsmQmv&A+5=a5YcxBzVAt_TzQZvZ%wlW+^|x6`wh?r12ExfQ$TsW(KBGF<}=s1ZXtYdalB@-Gux1!4BUpm&3;L4CVY3QhqOZVWyadWXm!On@x!pC2Ml>G~8b%rwm?2wrF)L)Ji zx@Kl-U)gg~-GA|Ox%FkI0wEp%XC6@Km}s{gW`V7&B&H@&J{h$OlHpoU43hhqvIfW; z?+RY)2{(KDt!oQwPQgU~qH^F{JK&sRQ=|c(#no3WTl5+HgC&AX00b^+l-qC`& z@poKxpk&VCMB;%12aIKOLi`8q;Nu$$J*xmTDC%-4W_(9dec0my>te5cT~*t_)cWGX z_Vs^{ARTEslBEt-K4c)Aj52}b-7|7S*GI6X5;=ylwzk_UA42-S0R4@I`(VY207fj2 zx}!pT?>2OG6=>>me7e9wlseq2JtZ|Yp`|~UuGnzqzNpR^*XeOfWYfxJJj~Wsd#|2w zVqzkuoTzl-#Qsk5T1jJLqoZ(3)+-R*wZYzlg)lzzF<{LU{^qns9l+pzv?)*1RR8S7 zW^rq{7@VkoI;Dy5EkcQDn9-a;GIE!3?aa@=8GEP114zG;3(1OzNx!k0>Q9s5yE>nhxN~ag zkRZ4Dj{B2`f2IsuS3&9*iK1!~Z8NvD0s69ET2ZP!_D1gKXuy%tI^6YsW~MHxUNys1 z5?p98K34t7<UXF5qdL85lR~e!W@|~J~DFSRO)^Zt7!}Qe_6LxdV+2# zg+1FA2eyA~6avTXhb*uD9#ec{%~;<6|B;uIBd1HF#G_UvNcfy!+ibSEX=?n3ydH zTnlmj{E19c^nuMw1;F~&?o`eM@cE>HK^gfQ^_+;UxVSJb*s|aC3^91fGS{a3iEhfhSB@6e^j&BWwNQOT(CHipFbE68fVSj5b2@fzC6%4 z=H!Di4i%`tKs;uYVXQ6EkB4kd<~rAIEuhijU00*bT=E*)H}1W{&IU0B8!!x9!*~yP zZPfvUR%MuQG@y>RMHLAYY~~dPcn`msd{tdtjg3WtZ2+XcChl{YW?&UX0`j$8mP1#wv`FVQ-4pG4%lJF)s0z9Pij*S?6+G8R5b%ONO1uZ4xGOz` zD_4{b10WU;B>2a}J3P+-k$WfD?C`KNY>RLNn*><+1ZyHsKZ>@nnSC4TayndMzZ5eO z0}1Qj9k5fj3nVE)gEO;Ma`i@V{@wZ5-@%jhhmkfrU*X1JhB|U)O7mT_Q@2;9lHGyQ zXA6YpdXO$@SVPHOJKoEm=nhpi0)ZeQvPzRSXH3QsSetGf%{QXo|9-ww_<3I!B?e1RS2}a`mTe$%0 z;R*{E^Esf+ySLcPNr2?N9UyZ-Q@ zY|zmlf%oR4W$Y#_!Hq5Fqy;`kx2)d2n-dD6Rt}sgTU@4=O#F1qHLyFqXaL~xVsc;H zB$zc!M0Y%OOjZiJ*Tkli?kNFg)sS=-n87*odOCZ=5YMLO-|tbk0!p|oV);|!5f)hW>vArh@2Jbq&*$Zt=OTkXy?fvU`i7Jg6&ECtNYr)( z!PJ2XU2Surg8ju}#~Q(??_=rQGdAYJTq3eAK(Adh^99d{`WaBaeyv;*#%7L!#aO?K zlb^1vZ#*Ul#@m#bnEs3M){wE2_omp>ak8+GgP~iRSinH+L8#bsCJ@ZPaET_two6uS z?uj~oicvr^L@Kju&kX5rh~*1FL%b`g0^mt4|9NbjVS-p9$ci0?iHW%}U;y6y*yGpr zZ-IHuAgw&&pOjauW$_ht6mRPieLT_8Ru))4*t4T&o$O65ZbjZB|I#8(d?It#vt!oEL1;_h|}*G=~v9u5mye4c6ph zqXj5XM%{-#G#A!yo?EX5xP|pm4h_y<8Gcp#TB^X&_piv6ET9pMY<}@kKz4;!;fz3rW5$n9|Zpi33gFo4|~O@;}EGOqrinaC^vSgJz}vJdZx# z>B?q!&R1HEL4uYu<3`HT^1Z7mUZBq=i6#>2M`2qIah{(QaTlVlDE3+y1ginG6cA5fM*Uw>NQ rPYe8Mfj=$qrv?7B!2j Date: Thu, 2 Oct 2025 03:35:21 -0400 Subject: [PATCH 035/204] Roll Skia from b5d8ae8d3410 to 257c1f94afaa (6 revisions) (#176389) https://skia.googlesource.com/skia.git/+log/b5d8ae8d3410..257c1f94afaa 2025-10-02 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from cd3a5de0811f to 603a5155599a (14 revisions) 2025-10-01 kjlubick@google.com Make SkPathPriv::Raw handle SkPaths with infinite bounds correctly 2025-10-01 kjlubick@google.com Reapply "Reapply "Use pathbuilder or factories to keep path immutable"" 2025-10-01 robertphillips@google.com [graphite] Make use of PipelineCreateCacheControl in Vulkan 2025-10-01 mike@reedtribe.org Update tests to use pathbuilder 2025-10-01 jmbetancourt@google.com [skcapture] add capture callbacks for SkSurface If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 2f2308cb79f2a..50a7574c95669 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'b5d8ae8d3410f025c0a520a64b5b78098a982231', + 'skia_revision': '257c1f94afaa47bd312a059d5b22d8ea7611557b', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From f35a2875aecafd728932621ae41a1ac4099e16a6 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Thu, 2 Oct 2025 07:13:41 -0400 Subject: [PATCH 036/204] [ Widget Preview ] Fix resolution for workspace "hosted" dependencies (#176358) Projects within the same workspace can depend on each other without explicitly specifying path dependencies or dependency overrides using the following syntax: ``` dependencies: my_workspace_project: # No constraint or path ``` This is treated as a "hosted" dependency, which conflicts with the path dependencies used by the widget_preview_scaffold. This change introduces dependency_overrides for each package in the workspace watched by the widget previewer to allow for dependencies to be resolved correctly. Fixes https://github.com/flutter/flutter/issues/176018 --- .../preview_pubspec_builder.dart | 23 ++- ...dart => preview_pubspec_builder_test.dart} | 2 +- .../widget_preview/regress_176018_test.dart | 142 ++++++++++++++++++ .../widget_preview/utils/preview_project.dart | 11 +- 4 files changed, 165 insertions(+), 13 deletions(-) rename packages/flutter_tools/test/commands.shard/hermetic/widget_preview/{widget_preview_test.dart => preview_pubspec_builder_test.dart} (99%) create mode 100644 packages/flutter_tools/test/commands.shard/hermetic/widget_preview/regress_176018_test.dart diff --git a/packages/flutter_tools/lib/src/widget_preview/preview_pubspec_builder.dart b/packages/flutter_tools/lib/src/widget_preview/preview_pubspec_builder.dart index 1dba75f813929..bd4070f12bce3 100644 --- a/packages/flutter_tools/lib/src/widget_preview/preview_pubspec_builder.dart +++ b/packages/flutter_tools/lib/src/widget_preview/preview_pubspec_builder.dart @@ -112,13 +112,15 @@ class PreviewPubspecBuilder { rootProject, ...rootProject.workspaceProjects, ]) - // Use `json.encode` to handle escapes correctly. - project.manifest.appName: json.encode({ - // `pub add` interprets relative paths relative to the current directory. - 'path': widgetPreviewScaffoldProject.directory.fileSystem.path.relative( - project.directory.path, - ), - }), + // Don't try and depend on unnamed projects. + if (project.manifest.appName.isNotEmpty) + // Use `json.encode` to handle escapes correctly. + project.manifest.appName: json.encode({ + // `pub add` interprets relative paths relative to the current directory. + 'path': widgetPreviewScaffoldProject.directory.fileSystem.path.relative( + project.directory.path, + ), + }), }; final PubOutputMode outputMode = verbose ? PubOutputMode.all : PubOutputMode.failuresOnly; @@ -130,8 +132,13 @@ class PreviewPubspecBuilder { widgetPreviewScaffoldProject.directory.path, // Ensure the path using POSIX separators, otherwise the "path_not_posix" check will fail. for (final MapEntry(:String key, :String value) - in workspacePackages.entries) + in workspacePackages.entries) ...[ '$key:$value', + // Add dependency overrides to handle "hosted" dependencies on other projects within the + // workspace. These dependencies take the form of "my_workspace_project: " in the + // pubspec's dependency list. See https://github.com/flutter/flutter/issues/176018. + 'override:$key:$value', + ], ], context: PubContext.pubAdd, command: pubAdd, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/widget_preview_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_pubspec_builder_test.dart similarity index 99% rename from packages/flutter_tools/test/commands.shard/hermetic/widget_preview/widget_preview_test.dart rename to packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_pubspec_builder_test.dart index bd4925a9a9985..12ab73b36ae94 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/widget_preview_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/preview_pubspec_builder_test.dart @@ -18,7 +18,7 @@ import '../../../src/context.dart'; import '../../../src/package_config.dart'; void main() { - group('WidgetPreviewStartCommand', () { + group('$PreviewPubspecBuilder', () { late MemoryFileSystem fileSystem; late ProcessManager processManager; late PreviewPubspecBuilder pubspecBuilder; diff --git a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/regress_176018_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/regress_176018_test.dart new file mode 100644 index 0000000000000..073e6eb51c3e6 --- /dev/null +++ b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/regress_176018_test.dart @@ -0,0 +1,142 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; + +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/base/signals.dart'; +import 'package:flutter_tools/src/dart/pub.dart'; +import 'package:flutter_tools/src/project.dart'; +import 'package:flutter_tools/src/widget_preview/preview_manifest.dart'; +import 'package:flutter_tools/src/widget_preview/preview_pubspec_builder.dart'; +import 'package:process/process.dart'; +import 'package:test/fake.dart'; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart'; + +import '../../../src/common.dart'; +import '../../../src/context.dart'; +import '../../../src/fakes.dart'; +import 'utils/preview_project.dart'; + +// Regression test for https://github.com/flutter/flutter/issues/176018. + +void main() { + group('$PreviewPubspecBuilder', () { + late final LocalFileSystem fs; + late final Directory root; + late final Logger logger; + late final ProcessManager processManager; + + setUp(() { + fs = LocalFileSystem.test(signals: Signals.test()); + root = fs.systemTempDirectory.createTempSync(); + logger = BufferLogger.test(); + processManager = const LocalProcessManager(); + }); + + tearDown(() { + root.deleteSync(recursive: true); + }); + + testUsingContext( + 'creates dependency_overrides for previewed project dependencies', + () async { + const kPackageProjectName = 'abcd'; + const kExampleProjectName = 'example'; + final workspace = WidgetPreviewWorkspace(workspaceRoot: root); + final WidgetPreviewProject packageProject = await workspace.createWorkspaceProject( + name: kPackageProjectName, + ); + final WidgetPreviewProject exampleProject = await workspace.createWorkspaceProject( + name: kExampleProjectName, + ); + + // Add a dependency on the package project from the example project. + exampleProject.writePubspec(''' +${exampleProject.initialPubspecContents} + ${packageProject.packageName}: # This resolves to the other package in the workspace +'''); + + final FlutterProject rootProject = FlutterProject.fromDirectory(root); + final pubspecBuilder = PreviewPubspecBuilder( + logger: logger, + verbose: true, + offline: false, + rootProject: rootProject, + previewManifest: FakePreviewManifest(), + ); + + // Bare minimum initialization of the widget_preview_scaffold project. + rootProject.widgetPreviewScaffold.childFile('pubspec.yaml') + ..createSync(recursive: true) + ..writeAsStringSync(''' +name: widget_preview_scaffold +environment: + sdk: ^3.7.0 + +dependencies: + flutter: + sdk: flutter + flutter_localizations: + sdk: flutter +'''); + + // Populate .dart_tool/widget_preview_scaffold/pubspec.yaml with the dependencies on the + // local projects. + await pubspecBuilder.populatePreviewPubspec(rootProject: rootProject); + final yaml = + loadYaml(rootProject.widgetPreviewScaffoldProject.pubspecFile.readAsStringSync()) + as YamlMap; + const expectedDependencies = { + 'abcd': {'path': '../../packages/$kPackageProjectName'}, + 'example': {'path': '../../packages/$kExampleProjectName'}, + }; + + // The generated pubspec.yaml should have path dependencies on both the package and example + // projects, but should also have dependency_overrides set to handle cases where one + // project in a workspace depends on another without explicitly specifying a path + // dependency (e.g., a dependency of the form "some_workspace_package: " with no version + // constraint or path). + if (yaml case { + 'dependencies': final YamlMap dependencies, + 'dependency_overrides': final YamlMap dependencyOverrides, + }) { + for (final MapEntry(key: package, value: constraint) in expectedDependencies.entries) { + expect(dependencies[package], constraint); + expect(dependencyOverrides[package], constraint); + } + } else { + fail(''' +Did not find the following dependencies for "dependencies" or "dependency_overrides": +$expectedDependencies + +Actual pubspec: +$yaml +'''); + } + }, + overrides: { + Pub: () => Pub.test( + fileSystem: fs, + logger: logger, + processManager: processManager, + botDetector: const FakeBotDetector(true), + platform: const LocalPlatform(), + stdio: Stdio.test(stdout: stdout, stderr: stderr), + ), + }, + ); + }); +} + +class FakePreviewManifest extends Fake implements PreviewManifest { + @override + void updatePubspecHash({String? updatedPubspecPath}) { + // Do nothing. + } +} diff --git a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/utils/preview_project.dart b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/utils/preview_project.dart index 48a73864e0ab3..7050517c13801 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/utils/preview_project.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/widget_preview/utils/preview_project.dart @@ -45,7 +45,7 @@ class WidgetPreviewWorkspace { inWorkspace: true, packageName: name, ); - project._writePubspec(project.pubspecContents); + project.writePubspec(project.initialPubspecContents); _packages[name] = project; await _updatePubspec(); return project; @@ -108,7 +108,7 @@ class WidgetPreviewProject { final String packageName; /// The initial contents of the pubspec.yaml for the project. - String get pubspecContents => + String get initialPubspecContents => ''' name: $packageName @@ -124,6 +124,9 @@ dependencies: sdk: flutter '''; + /// The current contents of the pubspec.yaml for the project. + String get pubspecContents => _pubspecYaml.readAsStringSync(); + /// The root of the fake project. /// /// This should always be set to [PreviewDetector.projectRoot]. @@ -155,7 +158,7 @@ dependencies: /// Writes `pubspec.yaml` and `.dart_tool/package_config.json` at [projectRoot]. Future initializePubspec() async { - _writePubspec(pubspecContents); + writePubspec(initialPubspecContents); final String flutterRoot = getFlutterRoot(); await savePackageConfig( PackageConfig( @@ -189,7 +192,7 @@ dependencies: } /// Updates the content of the project's pubspec.yaml. - void _writePubspec(String contents) { + void writePubspec(String contents) { projectRoot.childFile(_kPubspec) ..createSync(recursive: true) ..writeAsStringSync(contents); From 65aca3661b8fa943f801c4b2811af9d1e8e33dbe Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 2 Oct 2025 09:44:27 -0400 Subject: [PATCH 037/204] Roll Skia from 257c1f94afaa to 05c1f5803415 (4 revisions) (#176402) https://skia.googlesource.com/skia.git/+log/257c1f94afaa..05c1f5803415 2025-10-02 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 29b917fb5921 to 3964dd7c126b (4 revisions) 2025-10-02 skia-autoroll@skia-public.iam.gserviceaccount.com Roll ANGLE from ae02c3292a95 to 7994bf76d7a9 (8 revisions) 2025-10-02 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2025-10-02 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Skia Infra from a3b8ecf9f94a to 8d4953412be2 (6 revisions) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 50a7574c95669..2cfa6ad090a85 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': '257c1f94afaa47bd312a059d5b22d8ea7611557b', + 'skia_revision': '05c1f58034159ee906aa740552db93b8e18b8e02', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 1c15d39b1837b5cce29eb3ae2d2ee010d73d23cb Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Thu, 2 Oct 2025 10:32:09 -0500 Subject: [PATCH 038/204] Fix platform specific semantics for time picker buttons (#176373) Updated from https://github.com/flutter/flutter/pull/173418 Fixes https://github.com/flutter/flutter/issues/173302 Applied review feedback and modified for time licker refactor that landed somewhere in between. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../flutter/lib/src/material/time_picker.dart | 4 +- .../test/material/time_picker_test.dart | 52 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index 0d69d21953043..65c209b952457 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -802,7 +802,9 @@ class _AmPmButton extends StatelessWidget { final TextScaler buttonTextScaler = MediaQuery.textScalerOf(context).clamp(maxScaleFactor: 2.0); return Semantics( - checked: selected, + // Platform-specific semantics vary slightly here on iOS. + selected: defaultTargetPlatform == TargetPlatform.iOS ? selected : null, + checked: defaultTargetPlatform == TargetPlatform.iOS ? null : selected, inMutuallyExclusiveGroup: true, button: true, child: Padding( diff --git a/packages/flutter/test/material/time_picker_test.dart b/packages/flutter/test/material/time_picker_test.dart index 0cc2c9f1aeb89..5305dfd6cbd96 100644 --- a/packages/flutter/test/material/time_picker_test.dart +++ b/packages/flutter/test/material/time_picker_test.dart @@ -2559,6 +2559,58 @@ void main() { ); }, ); + + testWidgets( + 'AM/PM buttons have correct selected/checked semantics for platform variant', + (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/173302 + await tester.pumpWidget( + MaterialApp( + home: Builder( + builder: (BuildContext context) { + return TextButton( + onPressed: () { + showTimePicker( + context: context, + initialTime: const TimeOfDay(hour: 14, minute: 0), + ); + }, + child: const Text('Open Picker'), + ); + }, + ), + ), + ); + + await tester.tap(find.text('Open Picker')); + await tester.pumpAndSettle(); + + final Finder pmButtonSemantics = find.ancestor( + of: find.widgetWithText(InkWell, 'PM'), + matching: find.byWidgetPredicate( + (Widget widget) => widget is Semantics && (widget.properties.button ?? false), + ), + ); + + final Finder amButtonSemantics = find.ancestor( + of: find.widgetWithText(InkWell, 'AM'), + matching: find.byWidgetPredicate( + (Widget widget) => widget is Semantics && (widget.properties.button ?? false), + ), + ); + + bool? getPlatformSemanticProperty(Semantics semantics) { + return switch (defaultTargetPlatform) { + TargetPlatform.iOS => semantics.properties.selected, + _ => semantics.properties.checked, + }; + } + + expect(getPlatformSemanticProperty(tester.widget(pmButtonSemantics)), isTrue); + expect(getPlatformSemanticProperty(tester.widget(amButtonSemantics)), isFalse); + }, + variant: TargetPlatformVariant.all(), + ); } final Finder findDialPaint = find.descendant( From de64eed9802622ead706f3abb80229b92e16558e Mon Sep 17 00:00:00 2001 From: Matthew Kosarek Date: Thu, 2 Oct 2025 11:37:33 -0400 Subject: [PATCH 039/204] Windowing integration tests now await change futures if a changes is expected + commenting our erroneous icon in Runner.rc for win32 (#176312) ## What's new? - Update `Runner.rc` to not include the icon file, as this is what the other integration tests do - Await change notifications on the controller when necessary ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- .../windowing_test/lib/main.dart | 61 ++++++++++++++++--- .../windowing_test/test_driver/main_test.dart | 3 + .../windowing_test/windows/runner/Runner.rc | 2 +- 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/dev/integration_tests/windowing_test/lib/main.dart b/dev/integration_tests/windowing_test/lib/main.dart index 5ef97c973fc52..f0b056f3f11ab 100644 --- a/dev/integration_tests/windowing_test/lib/main.dart +++ b/dev/integration_tests/windowing_test/lib/main.dart @@ -38,6 +38,29 @@ void main() { throw ArgumentError('Message must contain a "type" field.'); } + /// This helper method registers a listener on the controller, + /// calls [act] to perform some action on the controller, waits for + /// the [predicate] to be satisified, and finally cleans up the listener. + Future awaitNotification( + VoidCallback act, + bool Function() predicate, + ) async { + final StreamController streamController = StreamController(); + void notificationHandler() { + streamController.add(true); + } + + controller.addListener(notificationHandler); + + act(); + await for (final _ in streamController.stream) { + if (predicate()) { + break; + } + } + controller.removeListener(notificationHandler); + } + if (jsonMap['type'] == 'get_size') { return jsonEncode({ 'width': controller.contentSize.width, @@ -48,7 +71,9 @@ void main() { jsonMap['width'].toDouble(), jsonMap['height'].toDouble(), ); - controller.setSize(size); + await awaitNotification(() { + controller.setSize(size); + }, () => controller.contentSize == size); } else if (jsonMap['type'] == 'set_constraints') { final BoxConstraints constraints = BoxConstraints( minWidth: jsonMap['min_width'].toDouble(), @@ -58,35 +83,51 @@ void main() { ); controller.setConstraints(constraints); } else if (jsonMap['type'] == 'set_fullscreen') { - controller.setFullscreen(true); + await awaitNotification(() { + controller.setFullscreen(true); + }, () => controller.isFullscreen); } else if (jsonMap['type'] == 'unset_fullscreen') { - controller.setFullscreen(false); + await awaitNotification(() { + controller.setFullscreen(false); + }, () => !controller.isFullscreen); } else if (jsonMap['type'] == 'get_fullscreen') { return jsonEncode({'isFullscreen': controller.isFullscreen}); } else if (jsonMap['type'] == 'set_maximized') { - controller.setMaximized(true); + await awaitNotification(() { + controller.setMaximized(true); + }, () => controller.isMaximized); } else if (jsonMap['type'] == 'unset_maximized') { - controller.setMaximized(false); + await awaitNotification(() { + controller.setMaximized(false); + }, () => !controller.isMaximized); } else if (jsonMap['type'] == 'get_maximized') { return jsonEncode({'isMaximized': controller.isMaximized}); } else if (jsonMap['type'] == 'set_minimized') { - controller.setMinimized(true); + await awaitNotification(() { + controller.setMinimized(true); + }, () => controller.isMinimized); } else if (jsonMap['type'] == 'unset_minimized') { - controller.setMinimized(false); + await awaitNotification(() { + controller.setMinimized(false); + }, () => !controller.isMinimized); } else if (jsonMap['type'] == 'get_minimized') { return jsonEncode({'isMinimized': controller.isMinimized}); } else if (jsonMap['type'] == 'set_title') { - controller.setTitle(jsonMap['title']); + final String title = jsonMap['title']; + await awaitNotification(() { + controller.setTitle(title); + }, () => controller.title == title); } else if (jsonMap['type'] == 'get_title') { return jsonEncode({'title': controller.title}); } else if (jsonMap['type'] == 'set_activated') { - controller.activate(); + await awaitNotification(() { + controller.activate(); + }, () => controller.isActivated); } else if (jsonMap['type'] == 'get_activated') { return jsonEncode({'isActivated': controller.isActivated}); } else { throw ArgumentError('Unknown message type: ${jsonMap['type']}'); } - return ''; }, ); diff --git a/dev/integration_tests/windowing_test/test_driver/main_test.dart b/dev/integration_tests/windowing_test/test_driver/main_test.dart index 872f360247653..3865dfb3e3929 100644 --- a/dev/integration_tests/windowing_test/test_driver/main_test.dart +++ b/dev/integration_tests/windowing_test/test_driver/main_test.dart @@ -118,6 +118,9 @@ void main() { }, timeout: Timeout.none); test('Can set and get activated', () async { + await driver.requestData( + jsonEncode({'type': 'set_minimized'}), + ); // Minimize first so that the window is not active await driver.requestData(jsonEncode({'type': 'set_activated'})); final response = await driver.requestData( jsonEncode({'type': 'get_activated'}), diff --git a/dev/integration_tests/windowing_test/windows/runner/Runner.rc b/dev/integration_tests/windowing_test/windows/runner/Runner.rc index 88669f87b3586..9101e69132623 100644 --- a/dev/integration_tests/windowing_test/windows/runner/Runner.rc +++ b/dev/integration_tests/windowing_test/windows/runner/Runner.rc @@ -52,7 +52,7 @@ END // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. -IDI_APP_ICON ICON "resources\\app_icon.ico" +// IDI_APP_ICON ICON "resources\\app_icon.ico" ///////////////////////////////////////////////////////////////////////////// From d674abf522846a6a7422f2e1c8b3c25b2e90e123 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 2 Oct 2025 12:03:41 -0400 Subject: [PATCH 040/204] Roll Packages from 321a5846838d to 5fd5f74dce46 (6 revisions) (#176409) https://github.com/flutter/packages/compare/321a5846838d...5fd5f74dce46 2025-10-02 mohellebiabdessalem@gmail.com [url_launcher] updates build files to use JVM 17 (flutter/packages#10130) 2025-10-02 mohellebiabdessalem@gmail.com [quick_actions] updates build files to use JVM 17 (flutter/packages#10132) 2025-10-01 engine-flutter-autoroll@skia.org Manual roll Flutter from c9608e28d01a to 7811e8982355 (22 revisions) (flutter/packages#10160) 2025-10-01 fishythefish@users.noreply.github.com [google_sign_in] Remove references to dart:js_util (flutter/packages#10148) 2025-10-01 stuartmorgan@google.com [ci] Re-enable all Android legacy emulator tests (flutter/packages#10141) 2025-10-01 engine-flutter-autoroll@skia.org Manual roll Flutter from 96fe3b3df509 to c9608e28d01a (26 revisions) (flutter/packages#10145) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC flutter-ecosystem@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- bin/internal/flutter_packages.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/flutter_packages.version b/bin/internal/flutter_packages.version index 5818c12807e6d..7860e48206780 100644 --- a/bin/internal/flutter_packages.version +++ b/bin/internal/flutter_packages.version @@ -1 +1 @@ -321a5846838d2bfa5f3dd463e3833b951da0db32 +5fd5f74dce46e15703b17cf1d56fbc89720b29b3 From d938e306f55859a1ea86e0eb23a4f25c41e26ff4 Mon Sep 17 00:00:00 2001 From: Mayank Patke Date: Thu, 2 Oct 2025 09:09:00 -0700 Subject: [PATCH 041/204] Remove references to dart:js_util (#176323) Flutter has been migrated from dart:js_util (which is unsupported by dart2wasm) to dart:js_interop, so we can remove/migrate all the remaining references to js_util. --- engine/src/flutter/sky/packages/sky_engine/BUILD.gn | 10 ---------- .../flutter/sky/packages/sky_engine/lib/_embedder.yaml | 1 - engine/src/flutter/web_sdk/sdk_rewriter.dart | 1 - engine/src/flutter/web_sdk/test/js_access_test.dart | 10 +++++++++- engine/src/flutter/web_sdk/test/sdk_rewriter_test.dart | 2 -- 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/engine/src/flutter/sky/packages/sky_engine/BUILD.gn b/engine/src/flutter/sky/packages/sky_engine/BUILD.gn index 513437ea8b6e0..3a731f0f81893 100644 --- a/engine/src/flutter/sky/packages/sky_engine/BUILD.gn +++ b/engine/src/flutter/sky/packages/sky_engine/BUILD.gn @@ -26,7 +26,6 @@ import("$dart_src/sdk/lib/js/js_annotations_sources.gni") import("$dart_src/sdk/lib/js/js_sources.gni") import("$dart_src/sdk/lib/js_interop/js_interop_sources.gni") import("$dart_src/sdk/lib/js_interop_unsafe/js_interop_unsafe_sources.gni") -import("$dart_src/sdk/lib/js_util/js_util_sources.gni") import("$dart_src/sdk/lib/math/math_sources.gni") import("$dart_src/sdk/lib/typed_data/typed_data_sources.gni") @@ -188,13 +187,6 @@ copy("js_interop_unsafe") { outputs = [ "$root_gen_dir/dart-pkg/sky_engine/lib/js_interop_unsafe/{{source_file_part}}" ] } -copy("js_util") { - lib_path = rebase_path("js_util", "", dart_sdk_lib_path) - sources = rebase_path(js_util_sdk_sources, "", lib_path) - outputs = - [ "$root_gen_dir/dart-pkg/sky_engine/lib/js_util/{{source_file_part}}" ] -} - copy("math") { lib_path = rebase_path("math", "", dart_sdk_lib_path) sources = rebase_path(math_sdk_sources, "", lib_path) @@ -249,7 +241,6 @@ group("copy_dart_sdk") { ":js", ":js_interop", ":js_interop_unsafe", - ":js_util", ":math", ":typed_data", ":vm_internal", @@ -278,7 +269,6 @@ generated_file("_embedder_yaml") { " \"dart:js\": \"js/js.dart\"", " \"dart:js_interop\": \"js_interop/js_interop.dart\"", " \"dart:js_interop_unsafe\": \"js_interop_unsafe/js_interop_unsafe.dart\"", - " \"dart:js_util\": \"js_util/js_util.dart\"", " \"dart:math\": \"math/math.dart\"", " \"dart:typed_data\": \"typed_data/typed_data.dart\"", " \"dart:ui\": \"ui/ui.dart\"", diff --git a/engine/src/flutter/sky/packages/sky_engine/lib/_embedder.yaml b/engine/src/flutter/sky/packages/sky_engine/lib/_embedder.yaml index c1ac4980994a3..8924603b72fd1 100644 --- a/engine/src/flutter/sky/packages/sky_engine/lib/_embedder.yaml +++ b/engine/src/flutter/sky/packages/sky_engine/lib/_embedder.yaml @@ -14,7 +14,6 @@ embedded_libs: "dart:js": "../../../../third_party/dart/sdk/lib/js/js.dart" "dart:js_interop": "../../../../third_party/dart/sdk/lib/js_interop/js_interop.dart" "dart:js_interop_unsafe": "../../../../third_party/dart/sdk/lib/js_interop_unsafe/js_interop_unsafe.dart" - "dart:js_util": "../../../../third_party/dart/sdk/lib/js_util/js_util.dart" "dart:math": "../../../../third_party/dart/sdk/lib/math/math.dart" "dart:typed_data": "../../../../third_party/dart/sdk/lib/typed_data/typed_data.dart" "dart:ui": "../../../../lib/ui/ui.dart" diff --git a/engine/src/flutter/web_sdk/sdk_rewriter.dart b/engine/src/flutter/web_sdk/sdk_rewriter.dart index 224f083809faf..a64c4b6c32c42 100644 --- a/engine/src/flutter/web_sdk/sdk_rewriter.dart +++ b/engine/src/flutter/web_sdk/sdk_rewriter.dart @@ -60,7 +60,6 @@ import 'dart:async'; import 'dart:collection'; import 'dart:convert' hide Codec; import 'dart:developer' as developer; -import 'dart:js_util' as js_util; import 'dart:js_interop'; import 'dart:js_interop_unsafe'; import 'dart:math' as math; diff --git a/engine/src/flutter/web_sdk/test/js_access_test.dart b/engine/src/flutter/web_sdk/test/js_access_test.dart index bf471082b95c4..8b3ea0c38faa1 100644 --- a/engine/src/flutter/web_sdk/test/js_access_test.dart +++ b/engine/src/flutter/web_sdk/test/js_access_test.dart @@ -17,7 +17,7 @@ import 'dart:io'; import 'package:test/test.dart'; // Libraries that allow making arbitrary calls to JavaScript. -const List _jsAccessLibraries = ['dart:js_util']; +const List _jsAccessLibraries = ['dart:js_interop_unsafe']; // Libraries that are allowed to make direct calls to JavaScript. These // libraries must be reviewed carefully to make sure JavaScript APIs are used @@ -25,6 +25,14 @@ const List _jsAccessLibraries = ['dart:js_util']; const List _auditedLibraries = [ 'lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart', 'lib/web_ui/lib/src/engine/safe_browser_api.dart', + + // TODO(176365): Clean up the following unaudited uses: + 'lib/web_ui/lib/src/engine/js_interop/js_loader.dart', + 'lib/web_ui/lib/src/engine/dom.dart', + 'lib/web_ui/lib/src/engine/pointer_binding.dart', + 'lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart', + 'lib/web_ui/lib/src/engine/text_editing/text_editing.dart', + 'lib/web_ui/lib/src/engine/js_interop/js_promise.dart', ]; Future main(List args) async { diff --git a/engine/src/flutter/web_sdk/test/sdk_rewriter_test.dart b/engine/src/flutter/web_sdk/test/sdk_rewriter_test.dart index f70890362c438..cbbc956ccf717 100644 --- a/engine/src/flutter/web_sdk/test/sdk_rewriter_test.dart +++ b/engine/src/flutter/web_sdk/test/sdk_rewriter_test.dart @@ -30,7 +30,6 @@ import 'dart:async'; import 'dart:collection'; import 'dart:convert' hide Codec; import 'dart:developer' as developer; -import 'dart:js_util' as js_util; import 'dart:js_interop'; import 'dart:js_interop_unsafe'; import 'dart:math' as math; @@ -68,7 +67,6 @@ import 'dart:async'; import 'dart:collection'; import 'dart:convert' hide Codec; import 'dart:developer' as developer; -import 'dart:js_util' as js_util; import 'dart:js_interop'; import 'dart:js_interop_unsafe'; import 'dart:math' as math; From f58eada931d4474073c4988b69f34ba27c68fddc Mon Sep 17 00:00:00 2001 From: jesswrd Date: Thu, 2 Oct 2025 10:02:09 -0700 Subject: [PATCH 042/204] Update Framework CI to Use NDK r28c (#176214) Updating Framework CI to use new SDK revision containing NDK r28c. Have to update the flutter.ndkVersion constant in this PR as well otherwise there will be a mismatch in versions. Partially Addresses https://github.com/flutter/flutter/issues/175022 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .ci.yaml | 264 +++++++++--------- .../src/main/kotlin/FlutterExtension.kt | 2 +- .../lib/src/android/gradle_utils.dart | 7 +- 3 files changed, 137 insertions(+), 136 deletions(-) diff --git a/.ci.yaml b/.ci.yaml index 3e985cec68083..292b8c3534dca 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -51,7 +51,7 @@ platform_properties: ] dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "android_virtual_device", "version": "android_36_google_apis_x64.textpb"}, {"dependency": "avd_cipd_version", "version": "build_id:8719362231152674241"}, {"dependency": "open_jdk", "version": "version:21"} @@ -71,7 +71,7 @@ platform_properties: ] dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "android_virtual_device", "version": "android_36_google_apis_x64.textpb"}, {"dependency": "avd_cipd_version", "version": "build_id:8719362231152674241"}, {"dependency": "open_jdk", "version": "version:21"} @@ -92,7 +92,7 @@ platform_properties: ] dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "android_virtual_device", "version": "android_35_google_apis_x64.textpb"}, {"dependency": "avd_cipd_version", "version": "build_id:8733065022087935185"}, {"dependency": "open_jdk", "version": "version:21"} @@ -106,7 +106,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "curl", "version": "version:7.64.0"} ] @@ -118,7 +118,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "curl", "version": "version:7.64.0"} ] @@ -129,7 +129,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "curl", "version": "version:7.64.0"} ] @@ -140,7 +140,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "curl", "version": "version:7.64.0"} ] @@ -243,7 +243,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -255,7 +255,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] os: Mac-14|Mac-15 @@ -333,7 +333,7 @@ platform_properties: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -366,7 +366,7 @@ targets: test_timeout_secs: "3600" # 1 hour dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "curl", "version": "version:7.64.0"} ] @@ -402,7 +402,7 @@ targets: # Requires Android SDK since we may re-generate Gradle lockfiles dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "gh_cli", "version": "version:2.8.0-2-g32256d38"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -423,7 +423,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -444,7 +444,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -463,7 +463,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -482,7 +482,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -501,7 +501,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -520,7 +520,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -680,7 +680,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -701,7 +701,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -786,7 +786,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] shard: framework_tests @@ -819,7 +819,7 @@ targets: {"dependency": "cmake", "version": "build_id:8787856497187628321"}, {"dependency": "ninja", "version": "version:1.9.0"}, {"dependency": "open_jdk", "version": "version:21"}, - {"dependency": "android_sdk", "version": "version:36v1"} + {"dependency": "android_sdk", "version": "version:36v3"} ] shard: framework_tests subshard: misc @@ -890,7 +890,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -910,7 +910,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -930,7 +930,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -950,7 +950,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -971,7 +971,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -992,7 +992,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -1013,7 +1013,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -1035,7 +1035,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -1057,7 +1057,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -1079,7 +1079,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -1100,7 +1100,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -1125,7 +1125,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -1231,7 +1231,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -1295,7 +1295,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"} ] tags: > @@ -1324,7 +1324,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1352,7 +1352,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1380,7 +1380,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1408,7 +1408,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1436,7 +1436,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1464,7 +1464,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1492,7 +1492,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1551,7 +1551,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1578,7 +1578,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:11"} ] @@ -1602,7 +1602,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:17"} ] @@ -1626,7 +1626,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "clang", "version": "git_revision:5d5aba78dbbee75508f01bcaa69aedb2ab79065a"}, {"dependency": "cmake", "version": "build_id:8787856497187628321"}, @@ -1651,7 +1651,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] shard: tool_tests @@ -1673,7 +1673,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] shard: tool_tests @@ -1719,7 +1719,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"} ] tags: > @@ -1761,7 +1761,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"} ] tags: > @@ -1781,7 +1781,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"} ] tags: > @@ -1800,7 +1800,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -1822,7 +1822,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -1844,7 +1844,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -1866,7 +1866,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -1888,7 +1888,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -1910,7 +1910,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -1933,7 +1933,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -1956,7 +1956,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -1979,7 +1979,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -2002,7 +2002,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -2025,7 +2025,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -2048,7 +2048,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -2071,7 +2071,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -2094,7 +2094,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -2117,7 +2117,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -2140,7 +2140,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -2163,7 +2163,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -2186,7 +2186,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -2209,7 +2209,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -2232,7 +2232,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -2255,7 +2255,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} ] @@ -2278,7 +2278,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} @@ -3764,7 +3764,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -3783,7 +3783,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -3802,7 +3802,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -3821,7 +3821,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -3841,7 +3841,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -3859,7 +3859,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -3877,7 +3877,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -3895,7 +3895,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -4085,7 +4085,7 @@ targets: {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "open_jdk", "version": "version:21"}, - {"dependency": "android_sdk", "version": "version:36v1"} + {"dependency": "android_sdk", "version": "version:36v3"} ] shard: framework_tests subshard: misc @@ -4116,7 +4116,7 @@ targets: {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, {"dependency": "open_jdk", "version": "version:21"}, - {"dependency": "android_sdk", "version": "version:36v1"} + {"dependency": "android_sdk", "version": "version:36v3"} ] shard: framework_tests subshard: misc @@ -4172,7 +4172,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -4211,7 +4211,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -4231,7 +4231,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -4251,7 +4251,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -4272,7 +4272,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -4348,7 +4348,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"} ] @@ -4410,7 +4410,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] tags: > @@ -4430,7 +4430,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -4534,7 +4534,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -4561,7 +4561,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -4588,7 +4588,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -4615,7 +4615,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -4642,7 +4642,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "ruby", "version": "ruby_3.1-pod_1.13"}, @@ -4668,7 +4668,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] shard: tool_tests @@ -4683,7 +4683,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] shard: tool_tests @@ -4698,7 +4698,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] shard: tool_tests @@ -4767,7 +4767,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} @@ -5708,7 +5708,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -5726,7 +5726,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -5744,7 +5744,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -5762,7 +5762,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -5780,7 +5780,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -5798,7 +5798,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -5816,7 +5816,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -5834,7 +5834,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -5852,7 +5852,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -5944,7 +5944,7 @@ targets: {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, {"dependency": "vs_build", "version": "version:vs2019"}, {"dependency": "open_jdk", "version": "version:21"}, - {"dependency": "android_sdk", "version": "version:36v1"} + {"dependency": "android_sdk", "version": "version:36v3"} ] shard: framework_tests subshard: misc @@ -6032,7 +6032,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -6078,7 +6078,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -6099,7 +6099,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -6120,7 +6120,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -6143,7 +6143,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -6191,7 +6191,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -6212,7 +6212,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -6233,7 +6233,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"} ] @@ -6375,7 +6375,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -6401,7 +6401,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -6427,7 +6427,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -6453,7 +6453,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -6479,7 +6479,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -6505,7 +6505,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -6531,7 +6531,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -6557,7 +6557,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -6583,7 +6583,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"}, @@ -6609,7 +6609,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "vs_build", "version": "version:vs2019"} ] @@ -6632,7 +6632,7 @@ targets: add_recipes_cq: "true" dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "open_jdk", "version": "version:21"} ] shard: tool_tests @@ -6653,7 +6653,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} @@ -6677,7 +6677,7 @@ targets: properties: dependencies: >- [ - {"dependency": "android_sdk", "version": "version:36v1"}, + {"dependency": "android_sdk", "version": "version:36v3"}, {"dependency": "chrome_and_driver", "version": "version:125.0.6422.141"}, {"dependency": "open_jdk", "version": "version:21"}, {"dependency": "goldctl", "version": "git_revision:2387d6fff449587eecbb7e45b2692ca0710b63b9"} diff --git a/packages/flutter_tools/gradle/src/main/kotlin/FlutterExtension.kt b/packages/flutter_tools/gradle/src/main/kotlin/FlutterExtension.kt index fe2e2679a0da0..dfce6d1deb8ff 100644 --- a/packages/flutter_tools/gradle/src/main/kotlin/FlutterExtension.kt +++ b/packages/flutter_tools/gradle/src/main/kotlin/FlutterExtension.kt @@ -39,7 +39,7 @@ open class FlutterExtension { * and in packages/flutter_tools/gradle/build.gradle.kts as found in * https://developer.android.com/studio/projects/install-ndk#default-ndk-per-agp. */ - val ndkVersion: String = "27.0.12077973" + val ndkVersion: String = "28.2.13676358" /** * Specifies the relative directory to the Flutter project directory. diff --git a/packages/flutter_tools/lib/src/android/gradle_utils.dart b/packages/flutter_tools/lib/src/android/gradle_utils.dart index 14b3245aa5b52..8f914c6cb21de 100644 --- a/packages/flutter_tools/lib/src/android/gradle_utils.dart +++ b/packages/flutter_tools/lib/src/android/gradle_utils.dart @@ -33,8 +33,6 @@ import 'android_sdk.dart'; const templateDefaultGradleVersion = '8.12'; // When bumping, also update: -// * ndkVersion constant in this file -// * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/kotlin/FlutterExtension.kt // * AGP version constants in packages/flutter_tools/gradle/build.gradle.kts // * AGP test constants in packages/flutter_tools/gradle/src/test/kotlin/DependencyVersionCheckerTest.kt // See https://mvnrepository.com/artifact/com.android.tools.build/gradle @@ -56,7 +54,10 @@ const compileSdkVersion = '$compileSdkVersionInt'; const minSdkVersionInt = 24; const minSdkVersion = '$minSdkVersionInt'; const targetSdkVersion = '36'; -const ndkVersion = '27.0.12077973'; +// When bumping, also update: +// * ndkVersion constant in this file +// * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/kotlin/FlutterExtension.kt +const ndkVersion = '28.2.13676358'; final minBuildToolsVersion = Version(28, 0, 3); // Align with packages/flutter_tools/gradle/src/main/kotlin/DependencyVersionChecker.kt. final errorJavaMinVersionAndroid = Version(17, 0, 0); From 17e2120be8a9a5edf110c5c9d426c57a5f18fd14 Mon Sep 17 00:00:00 2001 From: Qun Cheng <36861262+QuncCccccc@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:38:15 -0700 Subject: [PATCH 043/204] Update localization from translation console (#176324) Regular import of latest translations. Some .arb files(cupertino_ko.arb, and material_it.arb) are overwritten again and will cause translation issues. See comment https://github.com/flutter/flutter/pull/166496#issuecomment-2773987516. I manually reverted them back. This PR doesn't run the date_localization script. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- .../lib/src/l10n/cupertino_af.arb | 12 +- .../lib/src/l10n/cupertino_am.arb | 12 +- .../lib/src/l10n/cupertino_ar.arb | 12 +- .../lib/src/l10n/cupertino_as.arb | 18 +- .../lib/src/l10n/cupertino_az.arb | 12 +- .../lib/src/l10n/cupertino_be.arb | 12 +- .../lib/src/l10n/cupertino_bg.arb | 12 +- .../lib/src/l10n/cupertino_bn.arb | 12 +- .../lib/src/l10n/cupertino_bo.arb | 12 +- .../lib/src/l10n/cupertino_bs.arb | 12 +- .../lib/src/l10n/cupertino_ca.arb | 12 +- .../lib/src/l10n/cupertino_cs.arb | 12 +- .../lib/src/l10n/cupertino_cy.arb | 36 +- .../lib/src/l10n/cupertino_da.arb | 14 +- .../lib/src/l10n/cupertino_de.arb | 12 +- .../lib/src/l10n/cupertino_el.arb | 12 +- .../lib/src/l10n/cupertino_en_AU.arb | 6 + .../lib/src/l10n/cupertino_en_GB.arb | 6 + .../lib/src/l10n/cupertino_en_IE.arb | 6 + .../lib/src/l10n/cupertino_en_IN.arb | 6 + .../lib/src/l10n/cupertino_en_NZ.arb | 6 + .../lib/src/l10n/cupertino_en_SG.arb | 6 + .../lib/src/l10n/cupertino_en_ZA.arb | 6 + .../lib/src/l10n/cupertino_es.arb | 12 +- .../lib/src/l10n/cupertino_es_419.arb | 6 + .../lib/src/l10n/cupertino_es_AR.arb | 6 + .../lib/src/l10n/cupertino_es_BO.arb | 6 + .../lib/src/l10n/cupertino_es_CL.arb | 6 + .../lib/src/l10n/cupertino_es_CO.arb | 6 + .../lib/src/l10n/cupertino_es_CR.arb | 6 + .../lib/src/l10n/cupertino_es_DO.arb | 6 + .../lib/src/l10n/cupertino_es_EC.arb | 6 + .../lib/src/l10n/cupertino_es_GT.arb | 6 + .../lib/src/l10n/cupertino_es_HN.arb | 6 + .../lib/src/l10n/cupertino_es_MX.arb | 6 + .../lib/src/l10n/cupertino_es_NI.arb | 6 + .../lib/src/l10n/cupertino_es_PA.arb | 6 + .../lib/src/l10n/cupertino_es_PE.arb | 6 + .../lib/src/l10n/cupertino_es_PR.arb | 6 + .../lib/src/l10n/cupertino_es_PY.arb | 6 + .../lib/src/l10n/cupertino_es_SV.arb | 6 + .../lib/src/l10n/cupertino_es_US.arb | 6 + .../lib/src/l10n/cupertino_es_UY.arb | 6 + .../lib/src/l10n/cupertino_es_VE.arb | 6 + .../lib/src/l10n/cupertino_et.arb | 12 +- .../lib/src/l10n/cupertino_eu.arb | 12 +- .../lib/src/l10n/cupertino_fa.arb | 12 +- .../lib/src/l10n/cupertino_fi.arb | 12 +- .../lib/src/l10n/cupertino_fil.arb | 12 +- .../lib/src/l10n/cupertino_fr.arb | 12 +- .../lib/src/l10n/cupertino_fr_CA.arb | 6 + .../lib/src/l10n/cupertino_ga.arb | 12 +- .../lib/src/l10n/cupertino_gl.arb | 18 +- .../lib/src/l10n/cupertino_gsw.arb | 12 +- .../lib/src/l10n/cupertino_gu.arb | 12 +- .../lib/src/l10n/cupertino_he.arb | 12 +- .../lib/src/l10n/cupertino_hi.arb | 12 +- .../lib/src/l10n/cupertino_hr.arb | 12 +- .../lib/src/l10n/cupertino_hu.arb | 12 +- .../lib/src/l10n/cupertino_hy.arb | 12 +- .../lib/src/l10n/cupertino_id.arb | 12 +- .../lib/src/l10n/cupertino_is.arb | 12 +- .../lib/src/l10n/cupertino_it.arb | 12 +- .../lib/src/l10n/cupertino_ja.arb | 12 +- .../lib/src/l10n/cupertino_ka.arb | 12 +- .../lib/src/l10n/cupertino_kk.arb | 12 +- .../lib/src/l10n/cupertino_km.arb | 12 +- .../lib/src/l10n/cupertino_kn.arb | 12 +- .../lib/src/l10n/cupertino_ko.arb | 12 +- .../lib/src/l10n/cupertino_ky.arb | 12 +- .../lib/src/l10n/cupertino_lo.arb | 12 +- .../lib/src/l10n/cupertino_lt.arb | 12 +- .../lib/src/l10n/cupertino_lv.arb | 12 +- .../lib/src/l10n/cupertino_mk.arb | 12 +- .../lib/src/l10n/cupertino_ml.arb | 12 +- .../lib/src/l10n/cupertino_mn.arb | 12 +- .../lib/src/l10n/cupertino_mr.arb | 12 +- .../lib/src/l10n/cupertino_ms.arb | 12 +- .../lib/src/l10n/cupertino_my.arb | 12 +- .../lib/src/l10n/cupertino_nb.arb | 12 +- .../lib/src/l10n/cupertino_ne.arb | 12 +- .../lib/src/l10n/cupertino_nl.arb | 12 +- .../lib/src/l10n/cupertino_no.arb | 12 +- .../lib/src/l10n/cupertino_or.arb | 36 +- .../lib/src/l10n/cupertino_pa.arb | 12 +- .../lib/src/l10n/cupertino_pl.arb | 12 +- .../lib/src/l10n/cupertino_pt.arb | 12 +- .../lib/src/l10n/cupertino_pt_PT.arb | 6 + .../lib/src/l10n/cupertino_ro.arb | 12 +- .../lib/src/l10n/cupertino_ru.arb | 12 +- .../lib/src/l10n/cupertino_si.arb | 12 +- .../lib/src/l10n/cupertino_sk.arb | 12 +- .../lib/src/l10n/cupertino_sl.arb | 12 +- .../lib/src/l10n/cupertino_sq.arb | 12 +- .../lib/src/l10n/cupertino_sr.arb | 12 +- .../lib/src/l10n/cupertino_sr_Latn.arb | 6 + .../lib/src/l10n/cupertino_sv.arb | 12 +- .../lib/src/l10n/cupertino_sw.arb | 12 +- .../lib/src/l10n/cupertino_ta.arb | 12 +- .../lib/src/l10n/cupertino_te.arb | 12 +- .../lib/src/l10n/cupertino_th.arb | 12 +- .../lib/src/l10n/cupertino_tl.arb | 12 +- .../lib/src/l10n/cupertino_tr.arb | 12 +- .../lib/src/l10n/cupertino_ug.arb | 12 +- .../lib/src/l10n/cupertino_uk.arb | 12 +- .../lib/src/l10n/cupertino_ur.arb | 12 +- .../lib/src/l10n/cupertino_uz.arb | 12 +- .../lib/src/l10n/cupertino_vi.arb | 12 +- .../lib/src/l10n/cupertino_zh.arb | 12 +- .../lib/src/l10n/cupertino_zh_HK.arb | 6 + .../lib/src/l10n/cupertino_zh_TW.arb | 6 + .../lib/src/l10n/cupertino_zu.arb | 12 +- .../generated_cupertino_localizations.dart | 1376 +++++++++++------ .../generated_material_localizations.dart | 271 ++-- .../lib/src/l10n/material_as.arb | 98 +- .../lib/src/l10n/material_cy.arb | 18 +- .../lib/src/l10n/material_da.arb | 2 +- .../lib/src/l10n/material_fa.arb | 54 +- .../lib/src/l10n/material_gl.arb | 10 +- .../lib/src/l10n/material_or.arb | 84 +- .../lib/src/l10n/material_zh_HK.arb | 2 +- 121 files changed, 1836 insertions(+), 1293 deletions(-) diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_af.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_af.arb index 0df5375f8aacf..4421c6426619f 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_af.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_af.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Vee uit", "cancelButtonLabel": "Kanselleer", "backButtonLabel": "Terug", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "dubbeltik om in te vou", + "expansionTileCollapsedHint": "dubbeltik om uit te vou", + "expansionTileExpandedTapHint": "Vou in", + "expansionTileCollapsedTapHint": "Vou uit vir meer besonderhede", + "expandedHint": "Ingevou", + "collapsedHint": "Uitgevou" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_am.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_am.arb index c7a1a187e0348..7026a2ff10e09 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_am.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_am.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "አጽዳ", "cancelButtonLabel": "ይቅር", "backButtonLabel": "ተመለስ", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "ለመሰብሰብ ሁለቴ መታ ያድርጉ", + "expansionTileCollapsedHint": "ለመዘርጋት ድርብ ሁለቴ መታ ያድርጉ", + "expansionTileExpandedTapHint": "ሰብስብ", + "expansionTileCollapsedTapHint": "ለተጨማሪ ዝርዝሮች ይዘርጉ", + "expandedHint": "ተሰብስቧል", + "collapsedHint": "ተዘርግቷል" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ar.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ar.arb index aba06359389f0..9876585d0c8cd 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ar.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ar.arb @@ -50,10 +50,10 @@ "shareButtonLabel": "مشاركة…", "cancelButtonLabel": "الإلغاء", "backButtonLabel": "رجوع", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "يُرجى النقر مرّتين للتصغير.", + "expansionTileCollapsedHint": "انقر مرّتين للتوسيع", + "expansionTileExpandedTapHint": "تصغير", + "expansionTileCollapsedTapHint": "وسِّع المربّع لعرض مزيد من التفاصيل.", + "expandedHint": "مصغَّر", + "collapsedHint": "موسَّع" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_as.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_as.arb index 55a1c19fa3a37..593aa734d1bc3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_as.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_as.arb @@ -1,8 +1,8 @@ { "datePickerHourSemanticsLabelOne": "$hour বাজিছে", "datePickerHourSemanticsLabelOther": "$hour বাজিছে", - "datePickerMinuteSemanticsLabelOne": "১মিনিট", - "datePickerMinuteSemanticsLabelOther": "$minuteমিনিট", + "datePickerMinuteSemanticsLabelOne": "১ মিনিট", + "datePickerMinuteSemanticsLabelOther": "$minute মিনিট", "datePickerDateOrder": "mdy", "datePickerDateTimeOrder": "date_time_dayPeriod", "anteMeridiemAbbreviation": "পূৰ্বাহ্ন", @@ -19,7 +19,7 @@ "copyButtonLabel": "প্ৰতিলিপি কৰক", "pasteButtonLabel": "পে'ষ্ট কৰক", "clearButtonLabel": "মচক", - "selectAllButtonLabel": "সকলো বাছনি কৰক", + "selectAllButtonLabel": "আটাইবোৰ বাছনি কৰক", "tabSemanticsLabel": "$tabCount টা টেবৰ $tabIndex নম্বৰটো", "modalBarrierDismissLabel": "অগ্ৰাহ্য কৰক", "searchTextFieldPlaceholderLabel": "সন্ধান কৰক", @@ -30,10 +30,10 @@ "shareButtonLabel": "শ্বেয়াৰ কৰক…", "cancelButtonLabel": "বাতিল কৰক", "backButtonLabel": "উভতি যাওক", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "সংকোচন কৰিবলৈ দুবাৰ টিপক", + "expansionTileCollapsedHint": "বিস্তাৰ কৰিবলৈ দুবাৰ টিপক", + "expansionTileExpandedTapHint": "সংকোচন কৰক", + "expansionTileCollapsedTapHint": "অধিক সবিশেষ জানিবলৈ বিস্তাৰ কৰক", + "expandedHint": "সংকোচন কৰা আছে", + "collapsedHint": "বিস্তাৰ কৰা আছে" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_az.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_az.arb index 840b835b3e560..caf1305148062 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_az.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_az.arb @@ -30,10 +30,10 @@ "shareButtonLabel": "Paylaşın...", "cancelButtonLabel": "Ləğv edin", "backButtonLabel": "Geri", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "yığcamlaşdırmaq üçün iki dəfə toxunun", + "expansionTileCollapsedHint": "genişləndirmək üçün iki dəfə toxunun", + "expansionTileExpandedTapHint": "Yığcamlaşdırın", + "expansionTileCollapsedTapHint": "Daha çox detallar üçün genişləndirin", + "expandedHint": "Yığcamlaşdırıldı", + "collapsedHint": "Genişləndirildi" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_be.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_be.arb index f08c770bc98f1..9b584c9595017 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_be.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_be.arb @@ -40,10 +40,10 @@ "shareButtonLabel": "Абагуліць...", "cancelButtonLabel": "Скасаваць", "backButtonLabel": "Назад", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "двойчы націснуць, каб згарнуць", + "expansionTileCollapsedHint": "двойчы націснуць, каб разгарнуць", + "expansionTileExpandedTapHint": "Згарнуць", + "expansionTileCollapsedTapHint": "Разгарніце, каб даведацца больш", + "expandedHint": "Згорнута", + "collapsedHint": "Разгорнута" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_bg.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_bg.arb index 4308281f60b1a..f58a2068383ac 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_bg.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_bg.arb @@ -30,10 +30,10 @@ "shareButtonLabel": "Споделяне...", "cancelButtonLabel": "Отказ", "backButtonLabel": "Назад", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "докоснете два пъти за свиване", + "expansionTileCollapsedHint": "докоснете два пъти за разгъване", + "expansionTileExpandedTapHint": "Свиване", + "expansionTileCollapsedTapHint": "Разгъване за още подробности", + "expandedHint": "Свито", + "collapsedHint": "Разгънато" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_bn.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_bn.arb index 08810dc5918bd..ca5d3697ba07e 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_bn.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_bn.arb @@ -30,10 +30,10 @@ "shareButtonLabel": "শেয়ার করুন...", "cancelButtonLabel": "বাতিল করুন", "backButtonLabel": "ফিরে যান", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "আড়াল করতে ডবল ট্যাপ করুন", + "expansionTileCollapsedHint": "বড় করে দেখতে ডবল ট্যাপ করুন", + "expansionTileExpandedTapHint": "আড়াল করুন", + "expansionTileCollapsedTapHint": "আরও বিবরণ পেতে বড় করে দেখুন", + "expandedHint": "আড়াল করা হয়েছে", + "collapsedHint": "বড় করা হয়েছে" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_bo.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_bo.arb index 9c9610ff01b14..36981eb1f3660 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_bo.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_bo.arb @@ -30,10 +30,10 @@ "menuDismissLabel": "ཐོ་གཞུང་འདོར་བ།", "cancelButtonLabel": "ཕྱིར་འཐེན།", "backButtonLabel": "Back", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "ཕྱོགས་བསྡུའི་ཆེད་ཐེངས་གཉིས་གནོན།", + "expansionTileCollapsedHint": "ཁྱབ་སྤེལ་ཆེད་ཐེངས་གཉིས་གནོན།", + "expansionTileExpandedTapHint": "ཕྱོགས་བསྡུ།", + "expansionTileCollapsedTapHint": "ཞིབ་རྒྱས་ཆེད་ཁྱབ་སྤེལ།", + "expandedHint": "ཕྱོགས་བསྡུས།", + "collapsedHint": "ཁྱབ་སྤེལ་ཟིན།" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_bs.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_bs.arb index a014195b8474c..907da01dca82f 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_bs.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_bs.arb @@ -35,10 +35,10 @@ "shareButtonLabel": "Dijeli...", "cancelButtonLabel": "Otkaži", "backButtonLabel": "Nazad", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "sužavanje dvostrukim dodirom", + "expansionTileCollapsedHint": "proširivanje dvostrukim dodirom", + "expansionTileExpandedTapHint": "Sužavanje", + "expansionTileCollapsedTapHint": "Proširivanje za više detalja", + "expandedHint": "Suženo", + "collapsedHint": "Prošireno" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ca.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ca.arb index 2f136d875bd46..0fb5a52c7d41f 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ca.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ca.arb @@ -30,10 +30,10 @@ "shareButtonLabel": "Comparteix...", "cancelButtonLabel": "Cancel·la", "backButtonLabel": "Enrere", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "fes doble toc per replegar", + "expansionTileCollapsedHint": "fes doble toc per desplegar", + "expansionTileExpandedTapHint": "Replega", + "expansionTileCollapsedTapHint": "Desplega per obtenir més informació", + "expandedHint": "S'ha replegat", + "collapsedHint": "S'ha desplegat" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_cs.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_cs.arb index afd607db1fd50..a7ccd248b5011 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_cs.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_cs.arb @@ -40,10 +40,10 @@ "shareButtonLabel": "Sdílet…", "cancelButtonLabel": "Zrušit", "backButtonLabel": "Zpět", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "dvojitým klepnutím sbalíte", + "expansionTileCollapsedHint": "dvojitým klepnutím rozbalíte", + "expansionTileExpandedTapHint": "Sbalit", + "expansionTileCollapsedTapHint": "Rozbalte pro další podrobnosti", + "expandedHint": "Sbaleno", + "collapsedHint": "Rozbaleno" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_cy.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_cy.arb index c733439c25324..71506a1c1614d 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_cy.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_cy.arb @@ -4,20 +4,20 @@ "datePickerHourSemanticsLabelTwo": "$hour o'r gloch", "datePickerHourSemanticsLabelFew": "$hour o'r gloch", "datePickerHourSemanticsLabelMany": "$hour o'r gloch", - "timerPickerSecondLabelFew": "eiliad", - "timerPickerSecondLabelTwo": "eiliad", + "timerPickerSecondLabelFew": "eil.", + "timerPickerSecondLabelTwo": "eil.", "datePickerMinuteSemanticsLabelZero": "$minute munud", "datePickerMinuteSemanticsLabelTwo": "$minute funud", "datePickerMinuteSemanticsLabelFew": "$minute munud", "datePickerMinuteSemanticsLabelMany": "$minute munud", - "timerPickerSecondLabelZero": "eiliad", - "timerPickerMinuteLabelMany": "munud", - "timerPickerMinuteLabelTwo": "funud", - "timerPickerMinuteLabelZero": "munud", + "timerPickerSecondLabelZero": "eil.", + "timerPickerMinuteLabelMany": "mun.", + "timerPickerMinuteLabelTwo": "fun.", + "timerPickerMinuteLabelZero": "mun.", "timerPickerHourLabelMany": "awr", "timerPickerHourLabelFew": "awr", - "timerPickerMinuteLabelFew": "munud", - "timerPickerSecondLabelMany": "eiliad", + "timerPickerMinuteLabelFew": "mun.", + "timerPickerSecondLabelMany": "eil.", "timerPickerHourLabelZero": "awr", "datePickerHourSemanticsLabelOne": "$hour o'r gloch", "datePickerHourSemanticsLabelOther": "$hour o'r gloch", @@ -32,10 +32,10 @@ "tabSemanticsLabel": "Tab $tabIndex o $tabCount", "timerPickerHourLabelOne": "awr", "timerPickerHourLabelOther": "awr", - "timerPickerMinuteLabelOne": "funud", - "timerPickerMinuteLabelOther": "munud", - "timerPickerSecondLabelOne": "eiliad", - "timerPickerSecondLabelOther": "eiliad", + "timerPickerMinuteLabelOne": "fun.", + "timerPickerMinuteLabelOther": "mun.", + "timerPickerSecondLabelOne": "eil.", + "timerPickerSecondLabelOther": "eil.", "cutButtonLabel": "Torri", "copyButtonLabel": "Copïo", "pasteButtonLabel": "Gludo", @@ -50,10 +50,10 @@ "shareButtonLabel": "Rhannu...", "cancelButtonLabel": "Canslo", "backButtonLabel": "Nôl", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "tapiwch ddwywaith i grebachu", + "expansionTileCollapsedHint": "tapiwch ddwywaith i ehangu", + "expansionTileExpandedTapHint": "Crebachu", + "expansionTileCollapsedTapHint": "Ehangwch am ragor o fanylion", + "expandedHint": "Wedi'i grebachu", + "collapsedHint": "Wedi'i ehangu" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_da.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_da.arb index 379b8d833e928..6807ae5629bca 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_da.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_da.arb @@ -21,7 +21,7 @@ "clearButtonLabel": "Ryd", "selectAllButtonLabel": "Vælg alt", "tabSemanticsLabel": "Fane $tabIndex af $tabCount", - "modalBarrierDismissLabel": "Afvis", + "modalBarrierDismissLabel": "Luk", "searchTextFieldPlaceholderLabel": "Søg", "noSpellCheckReplacementsLabel": "Der blev ikke fundet nogen erstatninger", "menuDismissLabel": "Luk menu", @@ -30,10 +30,10 @@ "shareButtonLabel": "Del…", "cancelButtonLabel": "Annuller", "backButtonLabel": "Tilbage", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "tryk to gange for at skjule", + "expansionTileCollapsedHint": "tryk to gange for at udvide", + "expansionTileExpandedTapHint": "Skjul", + "expansionTileCollapsedTapHint": "Udvid for at få flere oplysninger", + "expandedHint": "Skjult", + "collapsedHint": "Udvidet" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_de.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_de.arb index 4427b0f60476e..35d88a0d39f92 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_de.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_de.arb @@ -30,10 +30,10 @@ "shareButtonLabel": "Teilen…", "cancelButtonLabel": "Abbrechen", "backButtonLabel": "Zurück", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "Zum Minimieren doppeltippen", + "expansionTileCollapsedHint": "Zum Maximieren doppeltippen", + "expansionTileExpandedTapHint": "Minimieren", + "expansionTileCollapsedTapHint": "Für weitere Details maximieren", + "expandedHint": "Minimiert", + "collapsedHint": "Maximiert" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_el.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_el.arb index 87afea1b67dba..ed7be3b5fda3f 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_el.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_el.arb @@ -30,10 +30,10 @@ "shareButtonLabel": "Κοινοποίηση…", "cancelButtonLabel": "Ακύρωση", "backButtonLabel": "Πίσω", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "πατήστε δύο φορές για σύμπτυξη", + "expansionTileCollapsedHint": "πατήστε δύο φορές για ανάπτυξη", + "expansionTileExpandedTapHint": "Σύμπτυξη", + "expansionTileCollapsedTapHint": "Ανάπτυξη για περισσότερες λεπτομέρειες", + "expandedHint": "Συμπτύχθηκε", + "collapsedHint": "Αναπτύχθηκε" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_en_AU.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_en_AU.arb index e9fdf48bed6b0..b0cfccef37169 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_en_AU.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_en_AU.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "double-tap to collapse", + "collapsedHint": "Expanded", + "expandedHint": "Collapsed", + "expansionTileCollapsedTapHint": "Expand for more details", + "expansionTileExpandedTapHint": "Collapse", + "expansionTileCollapsedHint": "double-tap to expand", "cancelButtonLabel": "Cancel", "backButtonLabel": "Back", "searchWebButtonLabel": "Search Web", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_en_GB.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_en_GB.arb index e9fdf48bed6b0..b0cfccef37169 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_en_GB.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_en_GB.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "double-tap to collapse", + "collapsedHint": "Expanded", + "expandedHint": "Collapsed", + "expansionTileCollapsedTapHint": "Expand for more details", + "expansionTileExpandedTapHint": "Collapse", + "expansionTileCollapsedHint": "double-tap to expand", "cancelButtonLabel": "Cancel", "backButtonLabel": "Back", "searchWebButtonLabel": "Search Web", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_en_IE.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_en_IE.arb index e9fdf48bed6b0..b0cfccef37169 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_en_IE.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_en_IE.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "double-tap to collapse", + "collapsedHint": "Expanded", + "expandedHint": "Collapsed", + "expansionTileCollapsedTapHint": "Expand for more details", + "expansionTileExpandedTapHint": "Collapse", + "expansionTileCollapsedHint": "double-tap to expand", "cancelButtonLabel": "Cancel", "backButtonLabel": "Back", "searchWebButtonLabel": "Search Web", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_en_IN.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_en_IN.arb index e9fdf48bed6b0..b0cfccef37169 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_en_IN.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_en_IN.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "double-tap to collapse", + "collapsedHint": "Expanded", + "expandedHint": "Collapsed", + "expansionTileCollapsedTapHint": "Expand for more details", + "expansionTileExpandedTapHint": "Collapse", + "expansionTileCollapsedHint": "double-tap to expand", "cancelButtonLabel": "Cancel", "backButtonLabel": "Back", "searchWebButtonLabel": "Search Web", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_en_NZ.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_en_NZ.arb index e9fdf48bed6b0..b0cfccef37169 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_en_NZ.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_en_NZ.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "double-tap to collapse", + "collapsedHint": "Expanded", + "expandedHint": "Collapsed", + "expansionTileCollapsedTapHint": "Expand for more details", + "expansionTileExpandedTapHint": "Collapse", + "expansionTileCollapsedHint": "double-tap to expand", "cancelButtonLabel": "Cancel", "backButtonLabel": "Back", "searchWebButtonLabel": "Search Web", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_en_SG.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_en_SG.arb index e9fdf48bed6b0..b0cfccef37169 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_en_SG.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_en_SG.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "double-tap to collapse", + "collapsedHint": "Expanded", + "expandedHint": "Collapsed", + "expansionTileCollapsedTapHint": "Expand for more details", + "expansionTileExpandedTapHint": "Collapse", + "expansionTileCollapsedHint": "double-tap to expand", "cancelButtonLabel": "Cancel", "backButtonLabel": "Back", "searchWebButtonLabel": "Search Web", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_en_ZA.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_en_ZA.arb index e9fdf48bed6b0..b0cfccef37169 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_en_ZA.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_en_ZA.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "double-tap to collapse", + "collapsedHint": "Expanded", + "expandedHint": "Collapsed", + "expansionTileCollapsedTapHint": "Expand for more details", + "expansionTileExpandedTapHint": "Collapse", + "expansionTileCollapsedHint": "double-tap to expand", "cancelButtonLabel": "Cancel", "backButtonLabel": "Back", "searchWebButtonLabel": "Search Web", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es.arb index b833e53d0b103..d14e731098ccc 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Borrar", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "toca dos veces para contraer", + "expansionTileCollapsedHint": "toca dos veces para desplegar", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedTapHint": "Desplegar para ver más detalles", + "expandedHint": "Contraído", + "collapsedHint": "Desplegado" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_419.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_419.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_419.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_419.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_AR.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_AR.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_AR.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_AR.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_BO.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_BO.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_BO.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_BO.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_CL.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_CL.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_CL.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_CL.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_CO.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_CO.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_CO.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_CO.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_CR.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_CR.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_CR.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_CR.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_DO.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_DO.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_DO.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_DO.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_EC.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_EC.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_EC.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_EC.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_GT.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_GT.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_GT.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_GT.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_HN.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_HN.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_HN.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_HN.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_MX.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_MX.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_MX.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_MX.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_NI.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_NI.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_NI.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_NI.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PA.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PA.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PA.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PA.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PE.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PE.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PE.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PE.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PR.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PR.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PR.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PR.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PY.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PY.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_PY.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_PY.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_SV.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_SV.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_SV.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_SV.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_US.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_US.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_US.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_US.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_UY.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_UY.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_UY.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_UY.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_es_VE.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_es_VE.arb index 4fee31d8592a3..5e222e923f215 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_es_VE.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_es_VE.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "presiona dos veces para contraer", + "collapsedHint": "Expandido", + "expandedHint": "Contraído", + "expansionTileCollapsedTapHint": "Expandir para ver más detalles", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedHint": "presiona dos veces para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", "clearButtonLabel": "Borrar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_et.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_et.arb index 53afaad720c15..e43a159c54bb1 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_et.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_et.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Kustutamine", "cancelButtonLabel": "Tühista", "backButtonLabel": "Tagasi", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "topeltpuudutage ahendamiseks", + "expansionTileCollapsedHint": "topeltpuudutage laiendamiseks", + "expansionTileExpandedTapHint": "Ahenda", + "expansionTileCollapsedTapHint": "Laiendage lisateabe nägemiseks", + "expandedHint": "Ahendatud", + "collapsedHint": "Laiendatud" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_eu.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_eu.arb index 865855effc1b6..4997bff7d6044 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_eu.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_eu.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Garbitu", "cancelButtonLabel": "Utzi", "backButtonLabel": "Atzera", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "tolesteko, sakatu birritan", + "expansionTileCollapsedHint": "zabaltzeko, sakatu birritan", + "expansionTileExpandedTapHint": "Tolestu", + "expansionTileCollapsedTapHint": "Zabaldu hau xehetasun gehiago lortzeko", + "expandedHint": "Tolestuta", + "collapsedHint": "Zabalduta" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_fa.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_fa.arb index d36b040561efe..22d5f93856c3b 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_fa.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_fa.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "پاک کردن", "cancelButtonLabel": "لغو", "backButtonLabel": "برگشتن", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "برای جمع کردن، دو تک‌ضرب بزنید", + "expansionTileCollapsedHint": "برای ازهم بازکردن، دو تک‌ضرب بزنید", + "expansionTileExpandedTapHint": "جمع کردن", + "expansionTileCollapsedTapHint": "ازهم بازکردن برای جزئیات بیشتر", + "expandedHint": "جمع‌شده", + "collapsedHint": "ازهم بازشده" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_fi.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_fi.arb index 0d82a024ec3b1..042b0b524987f 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_fi.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_fi.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Tyhjennä", "cancelButtonLabel": "Peru", "backButtonLabel": "Takaisin", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "tiivistä kaksoisnapauttamalla", + "expansionTileCollapsedHint": "laajenna kaksoisnapauttamalla", + "expansionTileExpandedTapHint": "Tiivistä", + "expansionTileCollapsedTapHint": "Katso lisätietoja laajentamalla", + "expandedHint": "Tiivistetty", + "collapsedHint": "Laajennettu" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_fil.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_fil.arb index a3a92ecbc9f48..9415c62c8ec2e 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_fil.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_fil.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "I-clear", "cancelButtonLabel": "Kanselahin", "backButtonLabel": "Bumalik", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "i-double tap para i-collapse", + "expansionTileCollapsedHint": "i-double tap para i-expand", + "expansionTileExpandedTapHint": "I-collapse", + "expansionTileCollapsedTapHint": "I-expand para sa higit pang detalye", + "expandedHint": "Naka-collapse", + "collapsedHint": "Naka-expand" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_fr.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_fr.arb index 7ca16f0420eee..cb1a367975b38 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_fr.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_fr.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Effacer", "cancelButtonLabel": "Annuler", "backButtonLabel": "Retour", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "appuyez deux fois pour réduire", + "expansionTileCollapsedHint": "appuyez deux fois pour développer", + "expansionTileExpandedTapHint": "Réduire", + "expansionTileCollapsedTapHint": "Développer pour en savoir plus", + "expandedHint": "Réduit", + "collapsedHint": "Développé" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_fr_CA.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_fr_CA.arb index b32fd5646c5c7..27c6a46c6fdd9 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_fr_CA.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_fr_CA.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "toucher deux fois pour réduire", + "collapsedHint": "Développé", + "expandedHint": "Réduit", + "expansionTileCollapsedTapHint": "Développer le panneau pour plus de détails", + "expansionTileExpandedTapHint": "Réduire", + "expansionTileCollapsedHint": "toucher deux fois pour développer", "cancelButtonLabel": "Annuler", "backButtonLabel": "Retour", "clearButtonLabel": "Effacer", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ga.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ga.arb index 1f5cfe0559009..80ffb72c6e183 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ga.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ga.arb @@ -45,10 +45,10 @@ "menuDismissLabel": "Ruaig an roghchlár", "cancelButtonLabel": "Cealaigh", "backButtonLabel": "Siar", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "tapáil faoi dhó chun laghdú", + "expansionTileCollapsedHint": "tapáil faoi dhó chun leathnú", + "expansionTileExpandedTapHint": "Laghdaigh", + "expansionTileCollapsedTapHint": "Leathnaigh chun tuilleadh sonraí a fháil", + "expandedHint": "Laghdaithe", + "collapsedHint": "Leathnaithe" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_gl.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_gl.arb index d250bccd7da1d..334bdb786bca7 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_gl.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_gl.arb @@ -1,8 +1,8 @@ { "datePickerHourSemanticsLabelOne": "$hour en punto", "datePickerHourSemanticsLabelOther": "$hour en punto", - "datePickerMinuteSemanticsLabelOne": "1 minuto", - "datePickerMinuteSemanticsLabelOther": "$minute minutos", + "datePickerMinuteSemanticsLabelOne": "1 minuto", + "datePickerMinuteSemanticsLabelOther": "$minute minutos", "datePickerDateOrder": "dmy", "datePickerDateTimeOrder": "date_time_dayPeriod", "anteMeridiemAbbreviation": "a.m.", @@ -20,7 +20,7 @@ "pasteButtonLabel": "Pegar", "selectAllButtonLabel": "Seleccionar todo", "tabSemanticsLabel": "Pestana $tabIndex de $tabCount", - "modalBarrierDismissLabel": "Ignorar", + "modalBarrierDismissLabel": "Pechar", "searchTextFieldPlaceholderLabel": "Fai unha busca", "noSpellCheckReplacementsLabel": "Non se encontrou ningunha substitución", "menuDismissLabel": "Pechar menú", @@ -30,10 +30,10 @@ "clearButtonLabel": "Borrar", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Atrás", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "tocar dúas veces para contraer", + "expansionTileCollapsedHint": "tocar dúas veces para despregar", + "expansionTileExpandedTapHint": "Contraer", + "expansionTileCollapsedTapHint": "Despregar para obter máis detalles", + "expandedHint": "Contraído", + "collapsedHint": "Despregado" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_gsw.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_gsw.arb index 0150c2249575c..68737efa2f9cc 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_gsw.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_gsw.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Löschen", "cancelButtonLabel": "Abbrechen", "backButtonLabel": "Zurück", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "Zum Minimieren doppeltippen", + "expansionTileCollapsedHint": "Zum Maximieren doppeltippen", + "expansionTileExpandedTapHint": "Minimieren", + "expansionTileCollapsedTapHint": "Für weitere Details maximieren", + "expandedHint": "Minimiert", + "collapsedHint": "Maximiert" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_gu.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_gu.arb index 16a79a08cf918..4be06fadbeccd 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_gu.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_gu.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "સાફ કરો", "cancelButtonLabel": "રદ કરો", "backButtonLabel": "પાછળ", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "નાની કરવા માટે બે વાર ટૅપ કરો", + "expansionTileCollapsedHint": "મોટી કરવા માટે બે વાર ટૅપ કરો", + "expansionTileExpandedTapHint": "નાની કરો", + "expansionTileCollapsedTapHint": "વધુ વિગતો માટે મોટી કરો", + "expandedHint": "નાની કરી", + "collapsedHint": "મોટી કરી" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_he.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_he.arb index 86bf9ad76b42b..8db786c787050 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_he.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_he.arb @@ -40,10 +40,10 @@ "clearButtonLabel": "ניקוי", "cancelButtonLabel": "ביטול", "backButtonLabel": "למסך הקודם", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "כדי לכווץ, יש ללחוץ לחיצה כפולה", + "expansionTileCollapsedHint": "כדי להרחיב, יש ללחוץ לחיצה כפולה", + "expansionTileExpandedTapHint": "כיווץ", + "expansionTileCollapsedTapHint": "ניתן להרחיב להצגת פרטים נוספים", + "expandedHint": "מכווץ", + "collapsedHint": "מורחב" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_hi.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_hi.arb index 1b2ee4596f596..c6953e94c1488 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_hi.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_hi.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "मिटाएं", "cancelButtonLabel": "रद्द करें", "backButtonLabel": "वापस जाएं", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "छोटा करने के लिए दो बार टैप करें", + "expansionTileCollapsedHint": "बड़ा करने के लिए दो बार टैप करें", + "expansionTileExpandedTapHint": "छोटा करें", + "expansionTileCollapsedTapHint": "ज़्यादा जानकारी के लिए बड़ा करें", + "expandedHint": "छोटा किया गया", + "collapsedHint": "बड़ा किया गया" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_hr.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_hr.arb index eeeff1f5dd0cd..cdec4c98af81f 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_hr.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_hr.arb @@ -35,10 +35,10 @@ "clearButtonLabel": "Izbriši", "cancelButtonLabel": "Odustani", "backButtonLabel": "Natrag", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "dvaput dodirnite za sažimanje", + "expansionTileCollapsedHint": "dvaput dodirnite za proširivanje", + "expansionTileExpandedTapHint": "Sažmi", + "expansionTileCollapsedTapHint": "Proširite da biste saznali više", + "expandedHint": "Sažeto", + "collapsedHint": "Prošireno" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_hu.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_hu.arb index ed0cb2477e65c..f8394d91cd713 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_hu.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_hu.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Törlés", "cancelButtonLabel": "Mégse", "backButtonLabel": "Vissza", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "duplán koppintva összecsukhatja", + "expansionTileCollapsedHint": "duplán koppintva kibonthatja", + "expansionTileExpandedTapHint": "Összecsukás", + "expansionTileCollapsedTapHint": "Bontsa ki a további részletek megtekintéséhez", + "expandedHint": "Összecsukva", + "collapsedHint": "Kibontva" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_hy.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_hy.arb index 9c0901c75307c..a0f0397e9e2c2 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_hy.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_hy.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Մաքրել", "cancelButtonLabel": "Չեղարկել", "backButtonLabel": "Հետ", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "կրկնակի հպեք ծալելու համար", + "expansionTileCollapsedHint": "կրկնակի հպեք ծավալելու համար", + "expansionTileExpandedTapHint": "Ծալել", + "expansionTileCollapsedTapHint": "ծավալեք՝ մանրամասները տեսնելու համար", + "expandedHint": "Ծալված է", + "collapsedHint": "Ծավալված է" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_id.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_id.arb index 384eb84558c44..021cc61544cf4 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_id.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_id.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Hapus", "cancelButtonLabel": "Batal", "backButtonLabel": "Kembali", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "ketuk dua kali untuk menciutkan", + "expansionTileCollapsedHint": "ketuk dua kali untuk meluaskan", + "expansionTileExpandedTapHint": "Ciutkan", + "expansionTileCollapsedTapHint": "Luaskan untuk mengetahui detail selengkapnya", + "expandedHint": "Diciutkan", + "collapsedHint": "Diluaskan" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_is.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_is.arb index 889446f47597d..9da2d57b53313 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_is.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_is.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Hreinsa", "cancelButtonLabel": "Hætta við", "backButtonLabel": "Til baka", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "ýttu tvisvar til að minnka", + "expansionTileCollapsedHint": "ýttu tvisvar til að stækka", + "expansionTileExpandedTapHint": "Minnka", + "expansionTileCollapsedTapHint": "Stækka til að sjá frekari upplýsingar", + "expandedHint": "Minnkað", + "collapsedHint": "Stækkað" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_it.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_it.arb index dff9dc5d47281..d2d90f3f903f7 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_it.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_it.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Cancella", "cancelButtonLabel": "Annulla", "backButtonLabel": "Indietro", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "tocca due volte per comprimere", + "expansionTileCollapsedHint": "Tocca due volte per espandere", + "expansionTileExpandedTapHint": "comprimere", + "expansionTileCollapsedTapHint": "espandere e visualizzare altri dettagli", + "expandedHint": "Compresso", + "collapsedHint": "Espanso" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ja.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ja.arb index c74e2c9dd254d..750f96f77e0c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ja.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ja.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "消去", "cancelButtonLabel": "キャンセル", "backButtonLabel": "戻る", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "ダブルタップすると閉じます", + "expansionTileCollapsedHint": "開くにはダブルタップします", + "expansionTileExpandedTapHint": "閉じる", + "expansionTileCollapsedTapHint": "開いて詳細を表示", + "expandedHint": "閉じました", + "collapsedHint": "開きました" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ka.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ka.arb index 3386fd74f22b9..03ba29fe9ba73 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ka.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ka.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "გასუფთავება", "cancelButtonLabel": "გაუქმება", "backButtonLabel": "უკან", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "ორმაგად შეეხეთ ჩასაკეცად", + "expansionTileCollapsedHint": "გასაფართოებლად ორჯერ შეეხეთ", + "expansionTileExpandedTapHint": "ჩაკეცვა", + "expansionTileCollapsedTapHint": "მეტი დეტალებისთვის გააფართოეთ", + "expandedHint": "ჩაკეცილია", + "collapsedHint": "გაფართოებულია" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_kk.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_kk.arb index 1f0c140eec577..521502e54e604 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_kk.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_kk.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Өшіру", "cancelButtonLabel": "Бас тарту", "backButtonLabel": "Артқа", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "жию үшін екі рет түртіңіз", + "expansionTileCollapsedHint": "жаю үшін екі рет түртіңіз", + "expansionTileExpandedTapHint": "Жию", + "expansionTileCollapsedTapHint": "Толық мәлімет алу үшін жайыңыз.", + "expandedHint": "Жиылды", + "collapsedHint": "Жайылды" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_km.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_km.arb index 569dc4e293e35..5a309d9f8496f 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_km.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_km.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "សម្អាត", "cancelButtonLabel": "បោះបង់", "backButtonLabel": "ថយក្រោយ", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "ចុចពីរដង ដើម្បីបង្រួម", + "expansionTileCollapsedHint": "ចុចពីរដង ដើម្បីពង្រីក", + "expansionTileExpandedTapHint": "បង្រួម", + "expansionTileCollapsedTapHint": "ពង្រីក​ដើម្បីទទួលបាន​ព័ត៌មានលម្អិត​បន្ថែម", + "expandedHint": "បាន​បង្រួម", + "collapsedHint": "បាន​ពង្រីក" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_kn.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_kn.arb index d594bbfbd2bbf..71d6c8ed9066d 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_kn.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_kn.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "\u0ca4\u0cc6\u0cb0\u0cb5\u0cc1\u0c97\u0cca\u0cb3\u0cbf\u0cb8\u0cbf", "cancelButtonLabel": "\u0cb0\u0ca6\u0ccd\u0ca6\u0cc1\u0cae\u0cbe\u0ca1\u0cbf", "backButtonLabel": "\u0cb9\u0cbf\u0c82\u0ca6\u0cc6", - "expansionTileExpandedHint": "\u0064\u006f\u0075\u0062\u006c\u0065\u0020\u0074\u0061\u0070\u0020\u0074\u006f\u0020\u0063\u006f\u006c\u006c\u0061\u0070\u0073\u0065\u0027", - "expansionTileCollapsedHint": "\u0064\u006f\u0075\u0062\u006c\u0065\u0020\u0074\u0061\u0070\u0020\u0074\u006f\u0020\u0065\u0078\u0070\u0061\u006e\u0064", - "expansionTileExpandedTapHint": "\u0043\u006f\u006c\u006c\u0061\u0070\u0073\u0065", - "expansionTileCollapsedTapHint": "\u0045\u0078\u0070\u0061\u006e\u0064\u0020\u0066\u006f\u0072\u0020\u006d\u006f\u0072\u0065\u0020\u0064\u0065\u0074\u0061\u0069\u006c\u0073", - "expandedHint": "\u0043\u006f\u006c\u006c\u0061\u0070\u0073\u0065\u0064", - "collapsedHint": "\u0045\u0078\u0070\u0061\u006e\u0064\u0065\u0064" + "expansionTileExpandedHint": "\u0c95\u0cc1\u0c97\u0ccd\u0c97\u0cbf\u0cb8\u0cb2\u0cc1\u0020\u0ca1\u0cac\u0cb2\u0ccd\u0020\u0c9f\u0ccd\u0caf\u0cbe\u0caa\u0ccd\u0020\u0cae\u0cbe\u0ca1\u0cbf", + "expansionTileCollapsedHint": "\u0cb5\u0cbf\u0cb8\u0ccd\u0ca4\u0cb0\u0cbf\u0cb8\u0cb2\u0cc1\u0020\u0ca1\u0cac\u0cb2\u0ccd\u0020\u0c9f\u0ccd\u0caf\u0cbe\u0caa\u0ccd\u0020\u0cae\u0cbe\u0ca1\u0cbf", + "expansionTileExpandedTapHint": "\u0c95\u0cc1\u0c97\u0ccd\u0c97\u0cbf\u0cb8\u0cbf", + "expansionTileCollapsedTapHint": "\u0c87\u0ca8\u0ccd\u0ca8\u0cb7\u0ccd\u0c9f\u0cc1\u0020\u0cb5\u0cbf\u0cb5\u0cb0\u0c97\u0cb3\u0cbf\u0c97\u0cbe\u0c97\u0cbf\u0020\u0cb5\u0cbf\u0cb8\u0ccd\u0ca4\u0cb0\u0cbf\u0cb8\u0cbf", + "expandedHint": "\u0c95\u0cc1\u0c97\u0ccd\u0c97\u0cbf\u0cb8\u0cb2\u0cbe\u0c97\u0cbf\u0ca6\u0cc6", + "collapsedHint": "\u0cb5\u0cbf\u0cb8\u0ccd\u0ca4\u0cb0\u0cbf\u0cb8\u0cb2\u0cbe\u0c97\u0cbf\u0ca6\u0cc6" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ko.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ko.arb index fc9833da99fa0..26acce64fce3c 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ko.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ko.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "삭제", "cancelButtonLabel": "취소", "backButtonLabel": "뒤로", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "두 번 탭하여 접기", + "expansionTileCollapsedHint": "두 번 탭하여 펼치기", + "expansionTileExpandedTapHint": "접기", + "expansionTileCollapsedTapHint": "자세히 알아보려면 펼치기", + "expandedHint": "접힘", + "collapsedHint": "펼침" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ky.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ky.arb index 8eb3c7790d8b5..a2f7f4c9fe89f 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ky.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ky.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Тазалоо", "cancelButtonLabel": "Токтотуу", "backButtonLabel": "Артка", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "жыйыштыруу үчүн эки жолу таптаңыз", + "expansionTileCollapsedHint": "жайып көрсөтүү үчүн эки жолу таптаңыз", + "expansionTileExpandedTapHint": "Жыйыштыруу", + "expansionTileCollapsedTapHint": "Толук маалымат алуу үчүн жайып көрүңүз", + "expandedHint": "Жыйыштырылды", + "collapsedHint": "Жайылып көрсөтүлдү" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_lo.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_lo.arb index 08c46d775db4b..43450bd436f17 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_lo.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_lo.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "ລຶບລ້າງ", "cancelButtonLabel": "ຍົກເລີກ", "backButtonLabel": "ກັບຄືນ", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "ແຕະສອງເທື່ອເພື່ອຫຍໍ້ລົງ", + "expansionTileCollapsedHint": "ແຕະສອງເທື່ອເພື່ອຂະຫຍາຍ", + "expansionTileExpandedTapHint": "ຫຍໍ້ລົງ", + "expansionTileCollapsedTapHint": "ຂະຫຍາຍສຳລັບຂໍ້ມູນເພີ່ມເຕີມ", + "expandedHint": "ຫຍໍ້ລົງແລ້ວ", + "collapsedHint": "ຂະຫຍາຍແລ້ວ" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_lt.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_lt.arb index aebe71af95438..5366cdb3adb9e 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_lt.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_lt.arb @@ -40,10 +40,10 @@ "clearButtonLabel": "Išvalyti", "cancelButtonLabel": "Atšaukti", "backButtonLabel": "Atgal", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "dukart palieskite, kad sutrauktumėte", + "expansionTileCollapsedHint": "dukart palieskite, kad išskleistumėte", + "expansionTileExpandedTapHint": "Sutraukti", + "expansionTileCollapsedTapHint": "Išskleiskite, jei reikia daugiau išsamios informacijos", + "expandedHint": "Sutraukta", + "collapsedHint": "Išskleista" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_lv.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_lv.arb index 02bed5aa68127..0ac8abb132783 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_lv.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_lv.arb @@ -35,10 +35,10 @@ "clearButtonLabel": "Notīrīt", "cancelButtonLabel": "Atcelt", "backButtonLabel": "Atpakaļ", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "dubultskāriens, lai sakļautu", + "expansionTileCollapsedHint": "dubultskāriens, lai izvērstu", + "expansionTileExpandedTapHint": "Sakļaut", + "expansionTileCollapsedTapHint": "Izvērst, lai iegūtu plašāku informāciju", + "expandedHint": "Sakļauts", + "collapsedHint": "Izvērsts" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_mk.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_mk.arb index c33c97340f868..10ea394ca2560 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_mk.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_mk.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Избриши", "cancelButtonLabel": "Откажи", "backButtonLabel": "Назад", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "допрете двапати за собирање", + "expansionTileCollapsedHint": "допри двапати за проширување", + "expansionTileExpandedTapHint": "собирање", + "expansionTileCollapsedTapHint": "проширување за повеќе детали", + "expandedHint": "Собрано", + "collapsedHint": "Проширено" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ml.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ml.arb index b7cfc039af660..9121a903b773c 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ml.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ml.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "മായ്ക്കുക", "cancelButtonLabel": "റദ്ദാക്കുക", "backButtonLabel": "മടങ്ങുക", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "ചുരുക്കാൻ ഡബിൾ ടാപ്പ് ചെയ്യുക", + "expansionTileCollapsedHint": "വികസിപ്പിക്കാൻ ഡബിൾ ടാപ്പ് ചെയ്യുക", + "expansionTileExpandedTapHint": "ചുരുക്കുക", + "expansionTileCollapsedTapHint": "കൂടുതൽ വിശദാംശങ്ങൾക്ക് വികസിപ്പിക്കുക", + "expandedHint": "ചുരുക്കി", + "collapsedHint": "വികസിപ്പിച്ചു" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_mn.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_mn.arb index b592db2944da2..7167357b490d4 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_mn.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_mn.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Арилгах", "cancelButtonLabel": "Цуцлах", "backButtonLabel": "Буцах", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "хураахын тулд хоёр товшино уу", + "expansionTileCollapsedHint": "дэлгэхийн тулд хоёр товшино уу", + "expansionTileExpandedTapHint": "Хураах", + "expansionTileCollapsedTapHint": "Илүү дэлгэрэнгүй авах бол дэлгэнэ үү", + "expandedHint": "Хураасан", + "collapsedHint": "Дэлгэсэн" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_mr.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_mr.arb index f0fba2b566670..e20a2d51315e2 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_mr.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_mr.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "साफ करा", "cancelButtonLabel": "रद्द करा", "backButtonLabel": "मागे जा", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "कोलॅप्स करण्यासाठी दोनदा टॅप करा", + "expansionTileCollapsedHint": "विस्तार करण्‍यासाठी दोनदा टॅप करा", + "expansionTileExpandedTapHint": "कोलॅप्स करा", + "expansionTileCollapsedTapHint": "आणखी तपशिलांसाठी विस्तार करा", + "expandedHint": "कोलॅप्स केले", + "collapsedHint": "विस्तार केले" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ms.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ms.arb index f8045157bd9d8..7a96112ccaccc 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ms.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ms.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Kosongkan", "cancelButtonLabel": "Batal", "backButtonLabel": "Kembali", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "ketik dua kali untuk kuncupkan", + "expansionTileCollapsedHint": "ketik dua kali untuk kembangkan", + "expansionTileExpandedTapHint": "Kuncupkan", + "expansionTileCollapsedTapHint": "Kembangkan untuk mendapatkan butiran lanjut", + "expandedHint": "Dikuncupkan", + "collapsedHint": "Dikembangkan" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_my.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_my.arb index 1408793a74de7..38f6276601444 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_my.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_my.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "ဖယ်ရှားရန်", "cancelButtonLabel": "မလုပ်တော့", "backButtonLabel": "နောက်သို့", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "ခေါက်ရန် နှစ်ချက်တို့ပါ", + "expansionTileCollapsedHint": "ဖြန့်ရန် နှစ်ချက်တို့ပါ", + "expansionTileExpandedTapHint": "ခေါက်ရန်", + "expansionTileCollapsedTapHint": "အသေးစိတ်အတွက် ဖြန့်ရန်", + "expandedHint": "ခေါက်ထားသည်", + "collapsedHint": "ဖြန့်ထားသည်" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_nb.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_nb.arb index cd708169e29d3..7b412246bd962 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_nb.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_nb.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Slett", "cancelButtonLabel": "Avbryt", "backButtonLabel": "Tilbake", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "dobbelttrykk for å skjule", + "expansionTileCollapsedHint": "dobbelttrykk for å vise", + "expansionTileExpandedTapHint": "Skjul", + "expansionTileCollapsedTapHint": "Vis for å se mer informasjon", + "expandedHint": "Skjules", + "collapsedHint": "Vises" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ne.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ne.arb index 31236897422c8..5b75ad298fe8a 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ne.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ne.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "हटाउनुहोस्", "cancelButtonLabel": "रद्द गर्नुहोस्", "backButtonLabel": "पछाडि", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "कोल्याप्स गर्न डबल ट्याप गर्नुहोस्", + "expansionTileCollapsedHint": "एक्स्पान्ड गर्न डबल ट्याप गर्नुहोस्", + "expansionTileExpandedTapHint": "कोल्याप्स गर्नुहोस्", + "expansionTileCollapsedTapHint": "थप विवरण हेर्न एक्स्पान्ड गर्नुहोस्", + "expandedHint": "कोल्याप्स गरियो", + "collapsedHint": "एक्स्पान्ड गरियो" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_nl.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_nl.arb index 490a63e23c77c..3e4ff68b978c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_nl.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_nl.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Wissen", "cancelButtonLabel": "Annuleren", "backButtonLabel": "Terug", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "dubbeltik om samen te vouwen", + "expansionTileCollapsedHint": "dubbeltik om uit te vouwen", + "expansionTileExpandedTapHint": "Samenvouwen", + "expansionTileCollapsedTapHint": "Uitvouwen voor meer informatie", + "expandedHint": "Samengevouwen", + "collapsedHint": "Uitgevouwen" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_no.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_no.arb index cd708169e29d3..7b412246bd962 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_no.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_no.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Slett", "cancelButtonLabel": "Avbryt", "backButtonLabel": "Tilbake", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "dobbelttrykk for å skjule", + "expansionTileCollapsedHint": "dobbelttrykk for å vise", + "expansionTileExpandedTapHint": "Skjul", + "expansionTileCollapsedTapHint": "Vis for å se mer informasjon", + "expandedHint": "Skjules", + "collapsedHint": "Vises" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_or.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_or.arb index ceb24a125b593..279e5078c2dd6 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_or.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_or.arb @@ -1,9 +1,9 @@ { - "datePickerHourSemanticsLabelOne": "$hourଟା", - "datePickerHourSemanticsLabelOther": "$hourଟା", - "datePickerMinuteSemanticsLabelOne": "1 ମିନିଟ୍", - "datePickerMinuteSemanticsLabelOther": "$minute ମିନିଟ୍", - "datePickerDateOrder": "mdy", + "datePickerHourSemanticsLabelOne": "$hourଟା ସମୟରେ", + "datePickerHourSemanticsLabelOther": "$hourଟା ସମୟରେ", + "datePickerMinuteSemanticsLabelOne": "1 ମିନିଟ", + "datePickerMinuteSemanticsLabelOther": "$minute ମିନିଟ", + "datePickerDateOrder": "dmy", "datePickerDateTimeOrder": "date_time_dayPeriod", "anteMeridiemAbbreviation": "AM", "postMeridiemAbbreviation": "PM", @@ -11,29 +11,29 @@ "alertDialogLabel": "ଆଲର୍ଟ", "timerPickerHourLabelOne": "ଘଣ୍ଟା", "timerPickerHourLabelOther": "ଘଣ୍ଟା", - "timerPickerMinuteLabelOne": "ମିନିଟ୍", - "timerPickerMinuteLabelOther": "ମିନିଟ୍", + "timerPickerMinuteLabelOne": "ମିନିଟ", + "timerPickerMinuteLabelOther": "ମିନିଟ", "timerPickerSecondLabelOne": "ସେକେଣ୍ଡ", "timerPickerSecondLabelOther": "ସେକେଣ୍ଡ", - "cutButtonLabel": "କଟ୍ କରନ୍ତୁ", + "cutButtonLabel": "କଟ କରନ୍ତୁ", "copyButtonLabel": "କପି କରନ୍ତୁ", "pasteButtonLabel": "ପେଷ୍ଟ କରନ୍ତୁ", - "selectAllButtonLabel": "ସମସ୍ତ ଚୟନ କରନ୍ତୁ", - "tabSemanticsLabel": "$tabCountର $tabIndex ଟାବ୍", + "selectAllButtonLabel": "ସବୁ ଚୟନ କରନ୍ତୁ", + "tabSemanticsLabel": "$tabCountର $tabIndex ଟାବ", "modalBarrierDismissLabel": "ଖାରଜ କରନ୍ତୁ", - "searchTextFieldPlaceholderLabel": "ସନ୍ଧାନ କରନ୍ତୁ", + "searchTextFieldPlaceholderLabel": "ସର୍ଚ୍ଚ କରନ୍ତୁ", "noSpellCheckReplacementsLabel": "କୌଣସି ରିପ୍ଲେସମେଣ୍ଟ ମିଳିଲା ନାହିଁ", "menuDismissLabel": "ମେନୁ ଖାରଜ କରନ୍ତୁ", "lookUpButtonLabel": "ଉପରକୁ ଦେଖନ୍ତୁ", "searchWebButtonLabel": "ୱେବ ସର୍ଚ୍ଚ କରନ୍ତୁ", - "shareButtonLabel": "ସେୟାର୍ କରନ୍ତୁ...", + "shareButtonLabel": "ସେୟାର କରନ୍ତୁ...", "clearButtonLabel": "ଖାଲି କରନ୍ତୁ", "cancelButtonLabel": "ବାତିଲ କରନ୍ତୁ", "backButtonLabel": "Back", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "ସଙ୍କୁଚିତ କରିବା ପାଇଁ ଦୁଇଥର ଟାପ କରନ୍ତୁ", + "expansionTileCollapsedHint": "ବିସ୍ତାର କରିବା ପାଇଁ ଦୁଇଥର ଟାପ କରନ୍ତୁ", + "expansionTileExpandedTapHint": "ସଙ୍କୁଚିତ କରନ୍ତୁ", + "expansionTileCollapsedTapHint": "ଅଧିକ ବିବରଣୀ ପାଇଁ ବିସ୍ତାର କରନ୍ତୁ", + "expandedHint": "ସଙ୍କୁଚିତ କରାଯାଇଛି", + "collapsedHint": "ବିସ୍ତାର କରାଯାଇଛି" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_pa.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_pa.arb index 8c9b89782a280..db84b970884f2 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_pa.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_pa.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "ਕਲੀਅਰ ਕਰੋ", "cancelButtonLabel": "ਰੱਦ ਕਰੋ", "backButtonLabel": "ਪਿੱਛੇ", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "ਸਮੇਟਣ ਲਈ ਡਬਲ ਟੈਪ ਕਰੋ", + "expansionTileCollapsedHint": "ਵਿਸਤਾਰ ਕਰਨ ਲਈ ਡਬਲ ਟੈਪ ਕਰੋ", + "expansionTileExpandedTapHint": "ਸਮੇਟੋ", + "expansionTileCollapsedTapHint": "ਹੋਰ ਵੇਰਵਿਆਂ ਲਈ ਵਿਸਤਾਰ ਕਰੋ", + "expandedHint": "ਸਮੇਟਿਆ ਗਿਆ", + "collapsedHint": "ਵਿਸਤਾਰ ਕੀਤਾ ਗਿਆ" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_pl.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_pl.arb index d4a4c0866de76..c6431b0a69cc6 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_pl.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_pl.arb @@ -40,10 +40,10 @@ "clearButtonLabel": "Wyczyść", "cancelButtonLabel": "Anuluj", "backButtonLabel": "Wstecz", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "kliknij dwukrotnie, aby zwinąć", + "expansionTileCollapsedHint": "kliknij dwukrotnie, aby rozwinąć", + "expansionTileExpandedTapHint": "Zwiń", + "expansionTileCollapsedTapHint": "Rozwiń, aby wyświetlić więcej informacji", + "expandedHint": "Zwinięto", + "collapsedHint": "Rozwinięto" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_pt.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_pt.arb index 93eac0f814493..37246440ef3ef 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_pt.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_pt.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Limpar", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Voltar", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "toque duas vezes para fechar", + "expansionTileCollapsedHint": "Toque duas vezes para abrir", + "expansionTileExpandedTapHint": "Feche", + "expansionTileCollapsedTapHint": "Abra para mostrar mais detalhes", + "expandedHint": "Fechado.", + "collapsedHint": "Aberto." } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_pt_PT.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_pt_PT.arb index 46d131d6ecec9..8f3f6d05b2a75 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_pt_PT.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_pt_PT.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "toque duas vezes para reduzir", + "collapsedHint": "Expandido", + "expandedHint": "Reduzido", + "expansionTileCollapsedTapHint": "Expandir para obter mais detalhes", + "expansionTileExpandedTapHint": "Reduzir", + "expansionTileCollapsedHint": "toque duas vezes para expandir", "cancelButtonLabel": "Cancelar", "backButtonLabel": "Anterior", "clearButtonLabel": "Limpar", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ro.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ro.arb index d01175fb1cdd7..af0bfc7437787 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ro.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ro.arb @@ -35,10 +35,10 @@ "clearButtonLabel": "Ștergeți", "cancelButtonLabel": "Anulați", "backButtonLabel": "Înapoi", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "atingeți de două ori pentru a restrânge", + "expansionTileCollapsedHint": "atingeți de două ori pentru a extinde", + "expansionTileExpandedTapHint": "Restrângeți", + "expansionTileCollapsedTapHint": "Extindeți pentru mai multe detalii", + "expandedHint": "Restrâns", + "collapsedHint": "Extins" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ru.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ru.arb index c95d03458678d..814dcbd8b9428 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ru.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ru.arb @@ -40,10 +40,10 @@ "clearButtonLabel": "Очистить", "cancelButtonLabel": "Отмена", "backButtonLabel": "Назад", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "нажмите дважды, чтобы свернуть", + "expansionTileCollapsedHint": "нажмите дважды, чтобы развернуть", + "expansionTileExpandedTapHint": "Свернуть", + "expansionTileCollapsedTapHint": "Развернуть дополнительные сведения", + "expandedHint": "Свернуто", + "collapsedHint": "Развернуто" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_si.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_si.arb index ef2ec2035dc5f..781b42e158422 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_si.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_si.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "හිස් කරන්න", "cancelButtonLabel": "අවලංගු කරන්න", "backButtonLabel": "Back", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "හැකිළවීමට දෙවරක් තට්ටු කරන්න", + "expansionTileCollapsedHint": "විහිදුවීමට දෙවරක් තට්ටු කරන්න", + "expansionTileExpandedTapHint": "හකුළන්න", + "expansionTileCollapsedTapHint": "වැඩි විස්තර සඳහා පුළුල් කරන්න", + "expandedHint": "හකුළන ලදි", + "collapsedHint": "දිග හරින ලදි" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_sk.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_sk.arb index 3defe0a39d6fe..564978b85c45b 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_sk.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_sk.arb @@ -40,10 +40,10 @@ "clearButtonLabel": "Vymazať", "cancelButtonLabel": "Zrušiť", "backButtonLabel": "Späť", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "zbalíte dvojitým klepnutím", + "expansionTileCollapsedHint": "rozbalíte dvojitým klepnutím", + "expansionTileExpandedTapHint": "Zbaliť", + "expansionTileCollapsedTapHint": "Rozbaliť a zobraziť ďalšie podrobnosti", + "expandedHint": "Zbalené", + "collapsedHint": "Rozbalené" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_sl.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_sl.arb index 52343c15588f0..8438831ffcecd 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_sl.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_sl.arb @@ -40,10 +40,10 @@ "clearButtonLabel": "Počisti", "cancelButtonLabel": "Prekliči", "backButtonLabel": "Nazaj", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "za strnitev se dvakrat dotaknite", + "expansionTileCollapsedHint": "za razširitev se dvakrat dotaknite", + "expansionTileExpandedTapHint": "Strni", + "expansionTileCollapsedTapHint": "Razširitev za več podrobnosti", + "expandedHint": "Strnjeno", + "collapsedHint": "Razširjeno" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_sq.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_sq.arb index 6e099ee4f5222..a56105a6ad220 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_sq.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_sq.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Pastro", "cancelButtonLabel": "Anulo", "backButtonLabel": "Prapa", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "trokit dy herë për ta palosur", + "expansionTileCollapsedHint": "trokit dy herë për ta zgjeruar", + "expansionTileExpandedTapHint": "Palos", + "expansionTileCollapsedTapHint": "Zgjero për më shumë detaje", + "expandedHint": "U palos", + "collapsedHint": "U zgjerua" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_sr.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_sr.arb index a8a3f9e8ac3fd..91a85a857b5d6 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_sr.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_sr.arb @@ -35,10 +35,10 @@ "clearButtonLabel": "Обриши", "cancelButtonLabel": "Откажи", "backButtonLabel": "Назад", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "двапут додирните да бисте скупили", + "expansionTileCollapsedHint": "двапут додирните да бисте проширили", + "expansionTileExpandedTapHint": "Скупите", + "expansionTileCollapsedTapHint": "Проширите за још детаља", + "expandedHint": "Скупљено је", + "collapsedHint": "Проширено је" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_sr_Latn.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_sr_Latn.arb index ac0447b96054c..bba449f6bb7fc 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_sr_Latn.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_sr_Latn.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "dvaput dodirnite da biste skupili", + "collapsedHint": "Prošireno je", + "expandedHint": "Skupljeno je", + "expansionTileCollapsedTapHint": "Proširite za još detalja", + "expansionTileExpandedTapHint": "Skupite", + "expansionTileCollapsedHint": "dvaput dodirnite da biste proširili", "cancelButtonLabel": "Otkaži", "backButtonLabel": "Nazad", "clearButtonLabel": "Obriši", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_sv.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_sv.arb index aa27436f73792..a7217082c81b7 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_sv.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_sv.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Rensa", "cancelButtonLabel": "Avbryt", "backButtonLabel": "Tillbaka", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "dubbeltryck för att komprimera", + "expansionTileCollapsedHint": "dubbeltryck för att utöka", + "expansionTileExpandedTapHint": "Komprimera", + "expansionTileCollapsedTapHint": "Utöka för mer information", + "expandedHint": "Komprimerades", + "collapsedHint": "Utökades" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_sw.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_sw.arb index c46db1e3b2fd7..cdcd13fccc0c4 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_sw.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_sw.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Futa", "cancelButtonLabel": "Ghairi", "backButtonLabel": "Nyuma", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "gusa mara mbili ili ukunje", + "expansionTileCollapsedHint": "gusa mara mbili ili upanue", + "expansionTileExpandedTapHint": "Kunja", + "expansionTileCollapsedTapHint": "Panua ili upate maelezo zaidi", + "expandedHint": "Imekunjwa", + "collapsedHint": "Imepanuliwa" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ta.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ta.arb index ee15f12eb9686..cba1ecb1163c3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ta.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ta.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "அழி", "cancelButtonLabel": "ரத்துசெய்", "backButtonLabel": "பின்செல்", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "சுருக்க இருமுறை தட்டவும்", + "expansionTileCollapsedHint": "விரிவாக்க இருமுறை தட்டுங்கள்", + "expansionTileExpandedTapHint": "சுருக்கும்", + "expansionTileCollapsedTapHint": "கூடுதல் விவரங்களுக்கு விரிவாக்கலாம்", + "expandedHint": "சுருக்கப்பட்டது", + "collapsedHint": "விரிவாக்கப்பட்டது" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_te.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_te.arb index f6cc87e02ca01..691993c227361 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_te.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_te.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "క్లియర్ చేయండి", "cancelButtonLabel": "రద్దు చేయండి", "backButtonLabel": "Back", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "కుదించడానికి డబుల్ ట్యాప్ చేయండి", + "expansionTileCollapsedHint": "విస్తరించడానికి డబుల్ ట్యాప్ చేయండి", + "expansionTileExpandedTapHint": "కుదించండి", + "expansionTileCollapsedTapHint": "మరిన్ని వివరాల కోసం విస్తరించండి", + "expandedHint": "కుదించబడింది", + "collapsedHint": "విస్తరించబడింది" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_th.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_th.arb index b6af99eaf06d0..0317f94806aa7 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_th.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_th.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "ล้าง", "cancelButtonLabel": "ยกเลิก", "backButtonLabel": "กลับ", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "แตะสองครั้งเพื่อยุบ", + "expansionTileCollapsedHint": "แตะสองครั้งเพื่อขยาย", + "expansionTileExpandedTapHint": "ยุบ", + "expansionTileCollapsedTapHint": "ขยายเพื่อดูรายละเอียดเพิ่มเติม", + "expandedHint": "ยุบ", + "collapsedHint": "ขยาย" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_tl.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_tl.arb index a3a92ecbc9f48..9415c62c8ec2e 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_tl.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_tl.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "I-clear", "cancelButtonLabel": "Kanselahin", "backButtonLabel": "Bumalik", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "i-double tap para i-collapse", + "expansionTileCollapsedHint": "i-double tap para i-expand", + "expansionTileExpandedTapHint": "I-collapse", + "expansionTileCollapsedTapHint": "I-expand para sa higit pang detalye", + "expandedHint": "Naka-collapse", + "collapsedHint": "Naka-expand" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_tr.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_tr.arb index b50394e019f2c..baca16af6580a 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_tr.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_tr.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Temizle", "cancelButtonLabel": "İptal", "backButtonLabel": "Geri", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "daraltmak için iki kez dokunun", + "expansionTileCollapsedHint": "genişletmek için iki kez dokunun", + "expansionTileExpandedTapHint": "Daralt", + "expansionTileCollapsedTapHint": "Daha fazla ayrıntı için genişletin", + "expandedHint": "Daraltıldı", + "collapsedHint": "Genişletildi" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ug.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ug.arb index 58231bc1df36a..ee1d5b777e7e3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ug.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ug.arb @@ -30,10 +30,10 @@ "menuDismissLabel": "تىزىملىكنى بىكار قىلىش", "cancelButtonLabel": "بىكار قىلىش", "backButtonLabel": "Back", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "يىغىش ئۈچۈن قوش چېكىڭ", + "expansionTileCollapsedHint": "يېيىش ئۈچۈن قوش چېكىڭ", + "expansionTileExpandedTapHint": "يىغىش", + "expansionTileCollapsedTapHint": "تېخىمۇ كۆپ تەپسىلاتلار ئۈچۈن يېيىڭ", + "expandedHint": "يىغىلدى", + "collapsedHint": "يېيىلدى" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_uk.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_uk.arb index 000552986cfdf..bd4a43d43bab3 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_uk.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_uk.arb @@ -40,10 +40,10 @@ "clearButtonLabel": "Очистити", "cancelButtonLabel": "Скасувати", "backButtonLabel": "Назад", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "двічі торкніться, щоб згорнути", + "expansionTileCollapsedHint": "двічі торкніться, щоб розгорнути", + "expansionTileExpandedTapHint": "Згорнути", + "expansionTileCollapsedTapHint": "Розгорнути й дізнатися більше", + "expandedHint": "Згорнуто", + "collapsedHint": "Розгорнуто" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_ur.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_ur.arb index 90ee3422e05a7..603043dce7947 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_ur.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_ur.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "صاف کریں", "cancelButtonLabel": "منسوخ کریں", "backButtonLabel": "Back", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "سکیڑنے کے لیے دوبار تھپتھپائیں", + "expansionTileCollapsedHint": "پھیلانے کے لیے دوبار تھپتھپائیں", + "expansionTileExpandedTapHint": "سکیڑیں", + "expansionTileCollapsedTapHint": "مزید تفصیلات کے لیے پھیلائیں", + "expandedHint": "سکڑا ہوا", + "collapsedHint": "پھیلا ہوا" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_uz.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_uz.arb index 6517a9f6721e2..da72d562971cc 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_uz.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_uz.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Tozalash", "cancelButtonLabel": "Bekor qilish", "backButtonLabel": "Orqaga", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "yigʻish uchun ikki marta bosing", + "expansionTileCollapsedHint": "yoyish uchun ikki marta bosing", + "expansionTileExpandedTapHint": "Yigʻish", + "expansionTileCollapsedTapHint": "Batafsil koʻrish uchun yoying", + "expandedHint": "Yigʻilgan", + "collapsedHint": "Yoyilgan" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_vi.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_vi.arb index 1976282db4ded..bf1d7bef9af02 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_vi.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_vi.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Xoá", "cancelButtonLabel": "Huỷ", "backButtonLabel": "Quay lại", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "nhấn đúp để thu gọn", + "expansionTileCollapsedHint": "nhấn đúp để mở rộng", + "expansionTileExpandedTapHint": "Thu gọn", + "expansionTileCollapsedTapHint": "Mở rộng để xem thêm chi tiết", + "expandedHint": "Đã thu gọn", + "collapsedHint": "Đã mở rộng" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_zh.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_zh.arb index 8c58f0e7da39d..f4d0d3da6e421 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_zh.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_zh.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "清除", "cancelButtonLabel": "取消", "backButtonLabel": "返回", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "点按两次即可收起", + "expansionTileCollapsedHint": "点按两次即可展开", + "expansionTileExpandedTapHint": "收起", + "expansionTileCollapsedTapHint": "展开查看更多详情", + "expandedHint": "已收起", + "collapsedHint": "已展开" } diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_zh_HK.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_zh_HK.arb index c9bf32612cc03..cee2ca4a3d822 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_zh_HK.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_zh_HK.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "㩒兩下就可以收合", + "collapsedHint": "已展開", + "expandedHint": "已收合", + "expansionTileCollapsedTapHint": "展開就可以查看詳情", + "expansionTileExpandedTapHint": "收合", + "expansionTileCollapsedHint": "㩒兩下就可以展開", "cancelButtonLabel": "取消", "backButtonLabel": "返回", "clearButtonLabel": "清除", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_zh_TW.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_zh_TW.arb index 08e9eab14e431..2ccac7944664d 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_zh_TW.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_zh_TW.arb @@ -1,4 +1,10 @@ { + "expansionTileExpandedHint": "輕觸兩下即可收合", + "collapsedHint": "已展開", + "expandedHint": "已收合", + "expansionTileCollapsedTapHint": "展開更多詳細資料", + "expansionTileExpandedTapHint": "收合", + "expansionTileCollapsedHint": "輕觸兩下即可展開", "cancelButtonLabel": "取消", "backButtonLabel": "返回", "clearButtonLabel": "清除", diff --git a/packages/flutter_localizations/lib/src/l10n/cupertino_zu.arb b/packages/flutter_localizations/lib/src/l10n/cupertino_zu.arb index 15be4c9c34d7d..05c9506b387e9 100644 --- a/packages/flutter_localizations/lib/src/l10n/cupertino_zu.arb +++ b/packages/flutter_localizations/lib/src/l10n/cupertino_zu.arb @@ -30,10 +30,10 @@ "clearButtonLabel": "Sula", "cancelButtonLabel": "Khansela", "backButtonLabel": "Emuva", - "expansionTileExpandedHint": "double tap to collapse", - "expansionTileCollapsedHint": "double tap to expand", - "expansionTileExpandedTapHint": "Collapse", - "expansionTileCollapsedTapHint": "Expand for more details", - "expandedHint": "Collapsed", - "collapsedHint": "Expanded" + "expansionTileExpandedHint": "thepha kabili ukuze ugoqe", + "expansionTileCollapsedHint": "Thepha kabili ukuze unwebe", + "expansionTileExpandedTapHint": "Goqa", + "expansionTileCollapsedTapHint": "Nweba ukuze uthole imininingwane eyengeziwe", + "expandedHint": "Kugoqiwe", + "collapsedHint": "Kunwetshiwe" } diff --git a/packages/flutter_localizations/lib/src/l10n/generated_cupertino_localizations.dart b/packages/flutter_localizations/lib/src/l10n/generated_cupertino_localizations.dart index b45580603872c..42d740464fe44 100644 --- a/packages/flutter_localizations/lib/src/l10n/generated_cupertino_localizations.dart +++ b/packages/flutter_localizations/lib/src/l10n/generated_cupertino_localizations.dart @@ -57,7 +57,7 @@ class CupertinoLocalizationAf extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Vee uit'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Uitgevou'; @override String get copyButtonLabel => 'Kopieer'; @@ -108,19 +108,19 @@ class CupertinoLocalizationAf extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Ingevou'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'dubbeltik om uit te vou'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Vou uit vir meer besonderhede'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'dubbeltik om in te vou'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Vou in'; @override String get lookUpButtonLabel => 'Kyk op'; @@ -247,7 +247,7 @@ class CupertinoLocalizationAm extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'አጽዳ'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'ተዘርግቷል'; @override String get copyButtonLabel => 'ቅዳ'; @@ -298,19 +298,19 @@ class CupertinoLocalizationAm extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'ተሰብስቧል'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'ለመዘርጋት ድርብ ሁለቴ መታ ያድርጉ'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'ለተጨማሪ ዝርዝሮች ይዘርጉ'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'ለመሰብሰብ ሁለቴ መታ ያድርጉ'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'ሰብስብ'; @override String get lookUpButtonLabel => 'ይመልከቱ'; @@ -437,7 +437,7 @@ class CupertinoLocalizationAr extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'محو'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'موسَّع'; @override String get copyButtonLabel => 'نسخ'; @@ -488,19 +488,19 @@ class CupertinoLocalizationAr extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => r'$minute دقيقة​'; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'مصغَّر'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'انقر مرّتين للتوسيع'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'وسِّع المربّع لعرض مزيد من التفاصيل.'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'يُرجى النقر مرّتين للتصغير.'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'تصغير'; @override String get lookUpButtonLabel => 'بحث عام'; @@ -627,7 +627,7 @@ class CupertinoLocalizationAs extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'মচক'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'বিস্তাৰ কৰা আছে'; @override String get copyButtonLabel => 'প্ৰতিলিপি কৰক'; @@ -666,10 +666,10 @@ class CupertinoLocalizationAs extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelMany => null; @override - String? get datePickerMinuteSemanticsLabelOne => '১মিনিট'; + String? get datePickerMinuteSemanticsLabelOne => '১ মিনিট'; @override - String get datePickerMinuteSemanticsLabelOther => r'$minuteমিনিট'; + String get datePickerMinuteSemanticsLabelOther => r'$minute মিনিট'; @override String? get datePickerMinuteSemanticsLabelTwo => null; @@ -678,19 +678,19 @@ class CupertinoLocalizationAs extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'সংকোচন কৰা আছে'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'বিস্তাৰ কৰিবলৈ দুবাৰ টিপক'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'অধিক সবিশেষ জানিবলৈ বিস্তাৰ কৰক'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'সংকোচন কৰিবলৈ দুবাৰ টিপক'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'সংকোচন কৰক'; @override String get lookUpButtonLabel => 'ওপৰলৈ চাওক'; @@ -717,7 +717,7 @@ class CupertinoLocalizationAs extends GlobalCupertinoLocalizations { String get searchWebButtonLabel => 'ৱেবত সন্ধান কৰক'; @override - String get selectAllButtonLabel => 'সকলো বাছনি কৰক'; + String get selectAllButtonLabel => 'আটাইবোৰ বাছনি কৰক'; @override String get shareButtonLabel => 'শ্বেয়াৰ কৰক…'; @@ -817,7 +817,7 @@ class CupertinoLocalizationAz extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Silin'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Genişləndirildi'; @override String get copyButtonLabel => 'Kopyalayın'; @@ -868,19 +868,19 @@ class CupertinoLocalizationAz extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Yığcamlaşdırıldı'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'genişləndirmək üçün iki dəfə toxunun'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Daha çox detallar üçün genişləndirin'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'yığcamlaşdırmaq üçün iki dəfə toxunun'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Yığcamlaşdırın'; @override String get lookUpButtonLabel => 'Axtarın'; @@ -1007,7 +1007,7 @@ class CupertinoLocalizationBe extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Ачысціць'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Разгорнута'; @override String get copyButtonLabel => 'Капіраваць'; @@ -1058,19 +1058,19 @@ class CupertinoLocalizationBe extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Згорнута'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'двойчы націснуць, каб разгарнуць'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Разгарніце, каб даведацца больш'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'двойчы націснуць, каб згарнуць'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Згарнуць'; @override String get lookUpButtonLabel => 'Знайсці'; @@ -1197,7 +1197,7 @@ class CupertinoLocalizationBg extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Изчистване'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Разгънато'; @override String get copyButtonLabel => 'Копиране'; @@ -1248,19 +1248,19 @@ class CupertinoLocalizationBg extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Свито'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'докоснете два пъти за разгъване'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Разгъване за още подробности'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'докоснете два пъти за свиване'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Свиване'; @override String get lookUpButtonLabel => 'Look Up'; @@ -1387,7 +1387,7 @@ class CupertinoLocalizationBn extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'মুছুন'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'বড় করা হয়েছে'; @override String get copyButtonLabel => 'কপি করুন'; @@ -1438,19 +1438,19 @@ class CupertinoLocalizationBn extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'আড়াল করা হয়েছে'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'বড় করে দেখতে ডবল ট্যাপ করুন'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'আরও বিবরণ পেতে বড় করে দেখুন'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'আড়াল করতে ডবল ট্যাপ করুন'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'আড়াল করুন'; @override String get lookUpButtonLabel => 'লুক-আপ'; @@ -1577,7 +1577,7 @@ class CupertinoLocalizationBo extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'གཙང་བཟོ།'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'ཁྱབ་སྤེལ་ཟིན།'; @override String get copyButtonLabel => 'བཤུས།'; @@ -1628,19 +1628,19 @@ class CupertinoLocalizationBo extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'ཕྱོགས་བསྡུས།'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'ཁྱབ་སྤེལ་ཆེད་ཐེངས་གཉིས་གནོན།'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'ཞིབ་རྒྱས་ཆེད་ཁྱབ་སྤེལ།'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'ཕྱོགས་བསྡུའི་ཆེད་ཐེངས་གཉིས་གནོན།'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'ཕྱོགས་བསྡུ།'; @override String get lookUpButtonLabel => 'འཚོལ་བ།'; @@ -1767,7 +1767,7 @@ class CupertinoLocalizationBs extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Obriši'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Prošireno'; @override String get copyButtonLabel => 'Kopiraj'; @@ -1818,19 +1818,19 @@ class CupertinoLocalizationBs extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Suženo'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'proširivanje dvostrukim dodirom'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Proširivanje za više detalja'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'sužavanje dvostrukim dodirom'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Sužavanje'; @override String get lookUpButtonLabel => 'Pogled nagore'; @@ -1957,7 +1957,7 @@ class CupertinoLocalizationCa extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Esborra'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => "S'ha desplegat"; @override String get copyButtonLabel => 'Copia'; @@ -2008,19 +2008,19 @@ class CupertinoLocalizationCa extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => "S'ha replegat"; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'fes doble toc per desplegar'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Desplega per obtenir més informació'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'fes doble toc per replegar'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Replega'; @override String get lookUpButtonLabel => 'Mira amunt'; @@ -2147,7 +2147,7 @@ class CupertinoLocalizationCs extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Vymazat'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Rozbaleno'; @override String get copyButtonLabel => 'Kopírovat'; @@ -2198,19 +2198,19 @@ class CupertinoLocalizationCs extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Sbaleno'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'dvojitým klepnutím rozbalíte'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Rozbalte pro další podrobnosti'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'dvojitým klepnutím sbalíte'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Sbalit'; @override String get lookUpButtonLabel => 'Vyhledat'; @@ -2337,7 +2337,7 @@ class CupertinoLocalizationCy extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Clirio'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => "Wedi'i ehangu"; @override String get copyButtonLabel => 'Copïo'; @@ -2388,19 +2388,19 @@ class CupertinoLocalizationCy extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => r'$minute munud'; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => "Wedi'i grebachu"; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'tapiwch ddwywaith i ehangu'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Ehangwch am ragor o fanylion'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'tapiwch ddwywaith i grebachu'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Crebachu'; @override String get lookUpButtonLabel => 'Chwilio'; @@ -2454,40 +2454,40 @@ class CupertinoLocalizationCy extends GlobalCupertinoLocalizations { String? get timerPickerHourLabelZero => 'awr'; @override - String? get timerPickerMinuteLabelFew => 'munud'; + String? get timerPickerMinuteLabelFew => 'mun.'; @override - String? get timerPickerMinuteLabelMany => 'munud'; + String? get timerPickerMinuteLabelMany => 'mun.'; @override - String? get timerPickerMinuteLabelOne => 'funud'; + String? get timerPickerMinuteLabelOne => 'fun.'; @override - String get timerPickerMinuteLabelOther => 'munud'; + String get timerPickerMinuteLabelOther => 'mun.'; @override - String? get timerPickerMinuteLabelTwo => 'funud'; + String? get timerPickerMinuteLabelTwo => 'fun.'; @override - String? get timerPickerMinuteLabelZero => 'munud'; + String? get timerPickerMinuteLabelZero => 'mun.'; @override - String? get timerPickerSecondLabelFew => 'eiliad'; + String? get timerPickerSecondLabelFew => 'eil.'; @override - String? get timerPickerSecondLabelMany => 'eiliad'; + String? get timerPickerSecondLabelMany => 'eil.'; @override - String? get timerPickerSecondLabelOne => 'eiliad'; + String? get timerPickerSecondLabelOne => 'eil.'; @override - String get timerPickerSecondLabelOther => 'eiliad'; + String get timerPickerSecondLabelOther => 'eil.'; @override - String? get timerPickerSecondLabelTwo => 'eiliad'; + String? get timerPickerSecondLabelTwo => 'eil.'; @override - String? get timerPickerSecondLabelZero => 'eiliad'; + String? get timerPickerSecondLabelZero => 'eil.'; @override String get todayLabel => 'Heddiw'; @@ -2527,7 +2527,7 @@ class CupertinoLocalizationDa extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Ryd'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Udvidet'; @override String get copyButtonLabel => 'Kopiér'; @@ -2578,19 +2578,19 @@ class CupertinoLocalizationDa extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Skjult'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'tryk to gange for at udvide'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Udvid for at få flere oplysninger'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'tryk to gange for at skjule'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Skjul'; @override String get lookUpButtonLabel => 'Slå op'; @@ -2599,7 +2599,7 @@ class CupertinoLocalizationDa extends GlobalCupertinoLocalizations { String get menuDismissLabel => 'Luk menu'; @override - String get modalBarrierDismissLabel => 'Afvis'; + String get modalBarrierDismissLabel => 'Luk'; @override String get noSpellCheckReplacementsLabel => 'Der blev ikke fundet nogen erstatninger'; @@ -2717,7 +2717,7 @@ class CupertinoLocalizationDe extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Löschen'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Maximiert'; @override String get copyButtonLabel => 'Kopieren'; @@ -2768,19 +2768,19 @@ class CupertinoLocalizationDe extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Minimiert'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'Zum Maximieren doppeltippen'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Für weitere Details maximieren'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'Zum Minimieren doppeltippen'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Minimieren'; @override String get lookUpButtonLabel => 'Nachschlagen'; @@ -2935,7 +2935,7 @@ class CupertinoLocalizationEl extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Διαγραφή'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Αναπτύχθηκε'; @override String get copyButtonLabel => 'Αντιγραφή'; @@ -2986,19 +2986,19 @@ class CupertinoLocalizationEl extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Συμπτύχθηκε'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'πατήστε δύο φορές για ανάπτυξη'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Ανάπτυξη για περισσότερες λεπτομέρειες'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'πατήστε δύο φορές για σύμπτυξη'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Σύμπτυξη'; @override String get lookUpButtonLabel => 'Look Up'; @@ -3299,6 +3299,12 @@ class CupertinoLocalizationEnAu extends CupertinoLocalizationEn { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'double-tap to collapse'; + + @override + String get expansionTileCollapsedHint => 'double-tap to expand'; + @override String get lookUpButtonLabel => 'Look up'; @@ -3355,6 +3361,12 @@ class CupertinoLocalizationEnGb extends CupertinoLocalizationEn { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'double-tap to collapse'; + + @override + String get expansionTileCollapsedHint => 'double-tap to expand'; + @override String get lookUpButtonLabel => 'Look up'; @@ -3386,6 +3398,12 @@ class CupertinoLocalizationEnIe extends CupertinoLocalizationEn { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'double-tap to collapse'; + + @override + String get expansionTileCollapsedHint => 'double-tap to expand'; + @override String get lookUpButtonLabel => 'Look up'; @@ -3417,6 +3435,12 @@ class CupertinoLocalizationEnIn extends CupertinoLocalizationEn { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'double-tap to collapse'; + + @override + String get expansionTileCollapsedHint => 'double-tap to expand'; + @override String get lookUpButtonLabel => 'Look up'; @@ -3448,6 +3472,12 @@ class CupertinoLocalizationEnNz extends CupertinoLocalizationEn { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'double-tap to collapse'; + + @override + String get expansionTileCollapsedHint => 'double-tap to expand'; + @override String get lookUpButtonLabel => 'Look up'; @@ -3479,6 +3509,12 @@ class CupertinoLocalizationEnSg extends CupertinoLocalizationEn { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'double-tap to collapse'; + + @override + String get expansionTileCollapsedHint => 'double-tap to expand'; + @override String get lookUpButtonLabel => 'Look up'; @@ -3510,6 +3546,12 @@ class CupertinoLocalizationEnZa extends CupertinoLocalizationEn { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'double-tap to collapse'; + + @override + String get expansionTileCollapsedHint => 'double-tap to expand'; + @override String get lookUpButtonLabel => 'Look up'; @@ -3557,7 +3599,7 @@ class CupertinoLocalizationEs extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Borrar'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Desplegado'; @override String get copyButtonLabel => 'Copiar'; @@ -3608,19 +3650,19 @@ class CupertinoLocalizationEs extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Contraído'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'toca dos veces para desplegar'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Desplegar para ver más detalles'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'toca dos veces para contraer'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Contraer'; @override String get lookUpButtonLabel => 'Buscador visual'; @@ -3731,6 +3773,18 @@ class CupertinoLocalizationEs419 extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -3777,6 +3831,18 @@ class CupertinoLocalizationEsAr extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -3823,6 +3889,18 @@ class CupertinoLocalizationEsBo extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -3869,6 +3947,18 @@ class CupertinoLocalizationEsCl extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -3915,6 +4005,18 @@ class CupertinoLocalizationEsCo extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -3961,6 +4063,18 @@ class CupertinoLocalizationEsCr extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4007,6 +4121,18 @@ class CupertinoLocalizationEsDo extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4053,6 +4179,18 @@ class CupertinoLocalizationEsEc extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4099,6 +4237,18 @@ class CupertinoLocalizationEsGt extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4145,6 +4295,18 @@ class CupertinoLocalizationEsHn extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4191,6 +4353,18 @@ class CupertinoLocalizationEsMx extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4237,6 +4411,18 @@ class CupertinoLocalizationEsNi extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4283,6 +4469,18 @@ class CupertinoLocalizationEsPa extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4329,6 +4527,18 @@ class CupertinoLocalizationEsPe extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4375,6 +4585,18 @@ class CupertinoLocalizationEsPr extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4421,6 +4643,18 @@ class CupertinoLocalizationEsPy extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4467,6 +4701,18 @@ class CupertinoLocalizationEsSv extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4513,6 +4759,18 @@ class CupertinoLocalizationEsUs extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4559,6 +4817,18 @@ class CupertinoLocalizationEsUy extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4605,6 +4875,18 @@ class CupertinoLocalizationEsVe extends CupertinoLocalizationEs { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'presiona dos veces para contraer'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para ver más detalles'; + + @override + String get expansionTileCollapsedHint => 'presiona dos veces para expandir'; + @override String get shareButtonLabel => 'Compartir…'; @@ -4667,7 +4949,7 @@ class CupertinoLocalizationEt extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Kustutamine'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Laiendatud'; @override String get copyButtonLabel => 'Kopeeri'; @@ -4718,19 +5000,19 @@ class CupertinoLocalizationEt extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Ahendatud'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'topeltpuudutage laiendamiseks'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Laiendage lisateabe nägemiseks'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'topeltpuudutage ahendamiseks'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Ahenda'; @override String get lookUpButtonLabel => 'Look Up'; @@ -4857,7 +5139,7 @@ class CupertinoLocalizationEu extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Garbitu'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Zabalduta'; @override String get copyButtonLabel => 'Kopiatu'; @@ -4908,19 +5190,19 @@ class CupertinoLocalizationEu extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Tolestuta'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'zabaltzeko, sakatu birritan'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Zabaldu hau xehetasun gehiago lortzeko'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'tolesteko, sakatu birritan'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Tolestu'; @override String get lookUpButtonLabel => 'Bilatu'; @@ -5047,7 +5329,7 @@ class CupertinoLocalizationFa extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'پاک کردن'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'ازهم بازشده'; @override String get copyButtonLabel => 'کپی'; @@ -5098,19 +5380,19 @@ class CupertinoLocalizationFa extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'جمع‌شده'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'برای ازهم بازکردن، دو تک‌ضرب بزنید'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'ازهم بازکردن برای جزئیات بیشتر'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'برای جمع کردن، دو تک‌ضرب بزنید'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'جمع کردن'; @override String get lookUpButtonLabel => 'جستجو'; @@ -5237,7 +5519,7 @@ class CupertinoLocalizationFi extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Tyhjennä'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Laajennettu'; @override String get copyButtonLabel => 'Kopioi'; @@ -5288,19 +5570,19 @@ class CupertinoLocalizationFi extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Tiivistetty'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'laajenna kaksoisnapauttamalla'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Katso lisätietoja laajentamalla'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'tiivistä kaksoisnapauttamalla'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Tiivistä'; @override String get lookUpButtonLabel => 'Hae'; @@ -5427,7 +5709,7 @@ class CupertinoLocalizationFil extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'I-clear'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Naka-expand'; @override String get copyButtonLabel => 'Kopyahin'; @@ -5478,19 +5760,19 @@ class CupertinoLocalizationFil extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Naka-collapse'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'i-double tap para i-expand'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'I-expand para sa higit pang detalye'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'i-double tap para i-collapse'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'I-collapse'; @override String get lookUpButtonLabel => 'Tumingin sa Itaas'; @@ -5617,7 +5899,7 @@ class CupertinoLocalizationFr extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Effacer'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Développé'; @override String get copyButtonLabel => 'Copier'; @@ -5668,19 +5950,19 @@ class CupertinoLocalizationFr extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Réduit'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'appuyez deux fois pour développer'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Développer pour en savoir plus'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'appuyez deux fois pour réduire'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Réduire'; @override String get lookUpButtonLabel => 'Recherche visuelle'; @@ -5791,6 +6073,15 @@ class CupertinoLocalizationFrCa extends CupertinoLocalizationFr { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'toucher deux fois pour réduire'; + + @override + String get expansionTileCollapsedTapHint => 'Développer le panneau pour plus de détails'; + + @override + String get expansionTileCollapsedHint => 'toucher deux fois pour développer'; + @override String get lookUpButtonLabel => 'Regarder en haut'; @@ -5853,7 +6144,7 @@ class CupertinoLocalizationGa extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Glan'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Leathnaithe'; @override String get copyButtonLabel => 'Cóipeáil'; @@ -5904,19 +6195,19 @@ class CupertinoLocalizationGa extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Laghdaithe'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'tapáil faoi dhó chun leathnú'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Leathnaigh chun tuilleadh sonraí a fháil'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'tapáil faoi dhó chun laghdú'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Laghdaigh'; @override String get lookUpButtonLabel => 'Cuardaigh'; @@ -6043,7 +6334,7 @@ class CupertinoLocalizationGl extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Borrar'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Despregado'; @override String get copyButtonLabel => 'Copiar'; @@ -6082,10 +6373,10 @@ class CupertinoLocalizationGl extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelMany => null; @override - String? get datePickerMinuteSemanticsLabelOne => '1 minuto'; + String? get datePickerMinuteSemanticsLabelOne => '1 minuto'; @override - String get datePickerMinuteSemanticsLabelOther => r'$minute minutos'; + String get datePickerMinuteSemanticsLabelOther => r'$minute minutos'; @override String? get datePickerMinuteSemanticsLabelTwo => null; @@ -6094,19 +6385,19 @@ class CupertinoLocalizationGl extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Contraído'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'tocar dúas veces para despregar'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Despregar para obter máis detalles'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'tocar dúas veces para contraer'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Contraer'; @override String get lookUpButtonLabel => 'Mirar cara arriba'; @@ -6115,7 +6406,7 @@ class CupertinoLocalizationGl extends GlobalCupertinoLocalizations { String get menuDismissLabel => 'Pechar menú'; @override - String get modalBarrierDismissLabel => 'Ignorar'; + String get modalBarrierDismissLabel => 'Pechar'; @override String get noSpellCheckReplacementsLabel => 'Non se encontrou ningunha substitución'; @@ -6233,7 +6524,7 @@ class CupertinoLocalizationGsw extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Löschen'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Maximiert'; @override String get copyButtonLabel => 'Kopieren'; @@ -6284,19 +6575,19 @@ class CupertinoLocalizationGsw extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Minimiert'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'Zum Maximieren doppeltippen'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Für weitere Details maximieren'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'Zum Minimieren doppeltippen'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Minimieren'; @override String get lookUpButtonLabel => 'Nachschlagen'; @@ -6423,7 +6714,7 @@ class CupertinoLocalizationGu extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'સાફ કરો'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'મોટી કરી'; @override String get copyButtonLabel => 'કૉપિ કરો'; @@ -6474,19 +6765,19 @@ class CupertinoLocalizationGu extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'નાની કરી'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'મોટી કરવા માટે બે વાર ટૅપ કરો'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'વધુ વિગતો માટે મોટી કરો'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'નાની કરવા માટે બે વાર ટૅપ કરો'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'નાની કરો'; @override String get lookUpButtonLabel => 'શોધો'; @@ -6613,7 +6904,7 @@ class CupertinoLocalizationHe extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'ניקוי'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'מורחב'; @override String get copyButtonLabel => 'העתקה'; @@ -6664,19 +6955,19 @@ class CupertinoLocalizationHe extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'מכווץ'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'כדי להרחיב, יש ללחוץ לחיצה כפולה'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'ניתן להרחיב להצגת פרטים נוספים'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'כדי לכווץ, יש ללחוץ לחיצה כפולה'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'כיווץ'; @override String get lookUpButtonLabel => 'חיפוש'; @@ -6803,7 +7094,7 @@ class CupertinoLocalizationHi extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'मिटाएं'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'बड़ा किया गया'; @override String get copyButtonLabel => 'कॉपी करें'; @@ -6854,19 +7145,19 @@ class CupertinoLocalizationHi extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'छोटा किया गया'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'बड़ा करने के लिए दो बार टैप करें'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'ज़्यादा जानकारी के लिए बड़ा करें'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'छोटा करने के लिए दो बार टैप करें'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'छोटा करें'; @override String get lookUpButtonLabel => 'लुक अप बटन'; @@ -6993,7 +7284,7 @@ class CupertinoLocalizationHr extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Izbriši'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Prošireno'; @override String get copyButtonLabel => 'Kopiraj'; @@ -7044,19 +7335,19 @@ class CupertinoLocalizationHr extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Sažeto'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'dvaput dodirnite za proširivanje'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Proširite da biste saznali više'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'dvaput dodirnite za sažimanje'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Sažmi'; @override String get lookUpButtonLabel => 'Pogled prema gore'; @@ -7183,7 +7474,7 @@ class CupertinoLocalizationHu extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Törlés'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Kibontva'; @override String get copyButtonLabel => 'Másolás'; @@ -7234,19 +7525,19 @@ class CupertinoLocalizationHu extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Összecsukva'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'duplán koppintva kibonthatja'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Bontsa ki a további részletek megtekintéséhez'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'duplán koppintva összecsukhatja'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Összecsukás'; @override String get lookUpButtonLabel => 'Felfelé nézés'; @@ -7373,7 +7664,7 @@ class CupertinoLocalizationHy extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Մաքրել'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Ծավալված է'; @override String get copyButtonLabel => 'Պատճենել'; @@ -7424,19 +7715,19 @@ class CupertinoLocalizationHy extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Ծալված է'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'կրկնակի հպեք ծավալելու համար'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'ծավալեք՝ մանրամասները տեսնելու համար'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'կրկնակի հպեք ծալելու համար'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Ծալել'; @override String get lookUpButtonLabel => 'Փնտրել'; @@ -7563,7 +7854,7 @@ class CupertinoLocalizationId extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Hapus'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Diluaskan'; @override String get copyButtonLabel => 'Salin'; @@ -7614,19 +7905,19 @@ class CupertinoLocalizationId extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Diciutkan'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'ketuk dua kali untuk meluaskan'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Luaskan untuk mengetahui detail selengkapnya'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'ketuk dua kali untuk menciutkan'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Ciutkan'; @override String get lookUpButtonLabel => 'Cari'; @@ -7753,7 +8044,7 @@ class CupertinoLocalizationIs extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Hreinsa'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Stækkað'; @override String get copyButtonLabel => 'Afrita'; @@ -7804,19 +8095,19 @@ class CupertinoLocalizationIs extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Minnkað'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'ýttu tvisvar til að stækka'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Stækka til að sjá frekari upplýsingar'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'ýttu tvisvar til að minnka'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Minnka'; @override String get lookUpButtonLabel => 'Look Up'; @@ -7943,7 +8234,7 @@ class CupertinoLocalizationIt extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Cancella'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Espanso'; @override String get copyButtonLabel => 'Copia'; @@ -7994,19 +8285,19 @@ class CupertinoLocalizationIt extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Compresso'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'Tocca due volte per espandere'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'espandere e visualizzare altri dettagli'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'tocca due volte per comprimere'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'comprimere'; @override String get lookUpButtonLabel => 'Cerca'; @@ -8133,7 +8424,7 @@ class CupertinoLocalizationJa extends GlobalCupertinoLocalizations { String get clearButtonLabel => '消去'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => '開きました'; @override String get copyButtonLabel => 'コピー'; @@ -8184,19 +8475,19 @@ class CupertinoLocalizationJa extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => '閉じました'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => '開くにはダブルタップします'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => '開いて詳細を表示'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'ダブルタップすると閉じます'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => '閉じる'; @override String get lookUpButtonLabel => '調べる'; @@ -8323,7 +8614,7 @@ class CupertinoLocalizationKa extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'გასუფთავება'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'გაფართოებულია'; @override String get copyButtonLabel => 'კოპირება'; @@ -8374,19 +8665,19 @@ class CupertinoLocalizationKa extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'ჩაკეცილია'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'გასაფართოებლად ორჯერ შეეხეთ'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'მეტი დეტალებისთვის გააფართოეთ'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'ორმაგად შეეხეთ ჩასაკეცად'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'ჩაკეცვა'; @override String get lookUpButtonLabel => 'აიხედეთ ზემოთ'; @@ -8513,7 +8804,7 @@ class CupertinoLocalizationKk extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Өшіру'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Жайылды'; @override String get copyButtonLabel => 'Көшіру'; @@ -8564,19 +8855,19 @@ class CupertinoLocalizationKk extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Жиылды'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'жаю үшін екі рет түртіңіз'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Толық мәлімет алу үшін жайыңыз.'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'жию үшін екі рет түртіңіз'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Жию'; @override String get lookUpButtonLabel => 'Іздеу'; @@ -8703,7 +8994,7 @@ class CupertinoLocalizationKm extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'សម្អាត'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'បាន​ពង្រីក'; @override String get copyButtonLabel => 'ចម្លង'; @@ -8754,19 +9045,19 @@ class CupertinoLocalizationKm extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'បាន​បង្រួម'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'ចុចពីរដង ដើម្បីពង្រីក'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'ពង្រីក​ដើម្បីទទួលបាន​ព័ត៌មានលម្អិត​បន្ថែម'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'ចុចពីរដង ដើម្បីបង្រួម'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'បង្រួម'; @override String get lookUpButtonLabel => 'រកមើល'; @@ -8893,7 +9184,7 @@ class CupertinoLocalizationKn extends GlobalCupertinoLocalizations { String get clearButtonLabel => '\u{ca4}\u{cc6}\u{cb0}\u{cb5}\u{cc1}\u{c97}\u{cca}\u{cb3}\u{cbf}\u{cb8}\u{cbf}'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => '\u{cb5}\u{cbf}\u{cb8}\u{ccd}\u{ca4}\u{cb0}\u{cbf}\u{cb8}\u{cb2}\u{cbe}\u{c97}\u{cbf}\u{ca6}\u{cc6}'; @override String get copyButtonLabel => '\u{c95}\u{cbe}\u{caa}\u{cbf}\u{20}\u{cae}\u{cbe}\u{ca1}\u{cbf}'; @@ -8944,19 +9235,19 @@ class CupertinoLocalizationKn extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => '\u{c95}\u{cc1}\u{c97}\u{ccd}\u{c97}\u{cbf}\u{cb8}\u{cb2}\u{cbe}\u{c97}\u{cbf}\u{ca6}\u{cc6}'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => '\u{cb5}\u{cbf}\u{cb8}\u{ccd}\u{ca4}\u{cb0}\u{cbf}\u{cb8}\u{cb2}\u{cc1}\u{20}\u{ca1}\u{cac}\u{cb2}\u{ccd}\u{20}\u{c9f}\u{ccd}\u{caf}\u{cbe}\u{caa}\u{ccd}\u{20}\u{cae}\u{cbe}\u{ca1}\u{cbf}'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => '\u{c87}\u{ca8}\u{ccd}\u{ca8}\u{cb7}\u{ccd}\u{c9f}\u{cc1}\u{20}\u{cb5}\u{cbf}\u{cb5}\u{cb0}\u{c97}\u{cb3}\u{cbf}\u{c97}\u{cbe}\u{c97}\u{cbf}\u{20}\u{cb5}\u{cbf}\u{cb8}\u{ccd}\u{ca4}\u{cb0}\u{cbf}\u{cb8}\u{cbf}'; @override - String get expansionTileExpandedHint => "double tap to collapse'"; + String get expansionTileExpandedHint => '\u{c95}\u{cc1}\u{c97}\u{ccd}\u{c97}\u{cbf}\u{cb8}\u{cb2}\u{cc1}\u{20}\u{ca1}\u{cac}\u{cb2}\u{ccd}\u{20}\u{c9f}\u{ccd}\u{caf}\u{cbe}\u{caa}\u{ccd}\u{20}\u{cae}\u{cbe}\u{ca1}\u{cbf}'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => '\u{c95}\u{cc1}\u{c97}\u{ccd}\u{c97}\u{cbf}\u{cb8}\u{cbf}'; @override String get lookUpButtonLabel => '\u{cae}\u{cc7}\u{cb2}\u{cc6}\u{20}\u{ca8}\u{ccb}\u{ca1}\u{cbf}'; @@ -9083,7 +9374,7 @@ class CupertinoLocalizationKo extends GlobalCupertinoLocalizations { String get clearButtonLabel => '삭제'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => '펼침'; @override String get copyButtonLabel => '복사'; @@ -9134,19 +9425,19 @@ class CupertinoLocalizationKo extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => '접힘'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => '두 번 탭하여 펼치기'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => '자세히 알아보려면 펼치기'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => '두 번 탭하여 접기'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => '접기'; @override String get lookUpButtonLabel => '찾기'; @@ -9273,7 +9564,7 @@ class CupertinoLocalizationKy extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Тазалоо'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Жайылып көрсөтүлдү'; @override String get copyButtonLabel => 'Көчүрүү'; @@ -9324,19 +9615,19 @@ class CupertinoLocalizationKy extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Жыйыштырылды'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'жайып көрсөтүү үчүн эки жолу таптаңыз'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Толук маалымат алуу үчүн жайып көрүңүз'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'жыйыштыруу үчүн эки жолу таптаңыз'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Жыйыштыруу'; @override String get lookUpButtonLabel => 'Издөө'; @@ -9463,7 +9754,7 @@ class CupertinoLocalizationLo extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'ລຶບລ້າງ'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'ຂະຫຍາຍແລ້ວ'; @override String get copyButtonLabel => 'ສຳເນົາ'; @@ -9514,19 +9805,19 @@ class CupertinoLocalizationLo extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'ຫຍໍ້ລົງແລ້ວ'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'ແຕະສອງເທື່ອເພື່ອຂະຫຍາຍ'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'ຂະຫຍາຍສຳລັບຂໍ້ມູນເພີ່ມເຕີມ'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'ແຕະສອງເທື່ອເພື່ອຫຍໍ້ລົງ'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'ຫຍໍ້ລົງ'; @override String get lookUpButtonLabel => 'ຊອກຫາຂໍ້ມູນ'; @@ -9653,7 +9944,7 @@ class CupertinoLocalizationLt extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Išvalyti'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Išskleista'; @override String get copyButtonLabel => 'Kopijuoti'; @@ -9704,19 +9995,19 @@ class CupertinoLocalizationLt extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Sutraukta'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'dukart palieskite, kad išskleistumėte'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Išskleiskite, jei reikia daugiau išsamios informacijos'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'dukart palieskite, kad sutrauktumėte'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Sutraukti'; @override String get lookUpButtonLabel => 'Ieškoti'; @@ -9843,7 +10134,7 @@ class CupertinoLocalizationLv extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Notīrīt'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Izvērsts'; @override String get copyButtonLabel => 'Kopēt'; @@ -9894,19 +10185,19 @@ class CupertinoLocalizationLv extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => r'$minute minūtes'; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Sakļauts'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'dubultskāriens, lai izvērstu'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Izvērst, lai iegūtu plašāku informāciju'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'dubultskāriens, lai sakļautu'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Sakļaut'; @override String get lookUpButtonLabel => 'Meklēt'; @@ -10033,7 +10324,7 @@ class CupertinoLocalizationMk extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Избриши'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Проширено'; @override String get copyButtonLabel => 'Копирај'; @@ -10084,19 +10375,19 @@ class CupertinoLocalizationMk extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Собрано'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'допри двапати за проширување'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'проширување за повеќе детали'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'допрете двапати за собирање'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'собирање'; @override String get lookUpButtonLabel => 'Погледнете нагоре'; @@ -10223,7 +10514,7 @@ class CupertinoLocalizationMl extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'മായ്ക്കുക'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'വികസിപ്പിച്ചു'; @override String get copyButtonLabel => 'പകർത്തുക'; @@ -10274,19 +10565,19 @@ class CupertinoLocalizationMl extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'ചുരുക്കി'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'വികസിപ്പിക്കാൻ ഡബിൾ ടാപ്പ് ചെയ്യുക'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'കൂടുതൽ വിശദാംശങ്ങൾക്ക് വികസിപ്പിക്കുക'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'ചുരുക്കാൻ ഡബിൾ ടാപ്പ് ചെയ്യുക'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'ചുരുക്കുക'; @override String get lookUpButtonLabel => 'മുകളിലേക്ക് നോക്കുക'; @@ -10413,7 +10704,7 @@ class CupertinoLocalizationMn extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Арилгах'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Дэлгэсэн'; @override String get copyButtonLabel => 'Хуулах'; @@ -10464,19 +10755,19 @@ class CupertinoLocalizationMn extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Хураасан'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'дэлгэхийн тулд хоёр товшино уу'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Илүү дэлгэрэнгүй авах бол дэлгэнэ үү'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'хураахын тулд хоёр товшино уу'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Хураах'; @override String get lookUpButtonLabel => 'Дээшээ харах'; @@ -10603,7 +10894,7 @@ class CupertinoLocalizationMr extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'साफ करा'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'विस्तार केले'; @override String get copyButtonLabel => 'कॉपी करा'; @@ -10654,19 +10945,19 @@ class CupertinoLocalizationMr extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'कोलॅप्स केले'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'विस्तार करण्‍यासाठी दोनदा टॅप करा'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'आणखी तपशिलांसाठी विस्तार करा'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'कोलॅप्स करण्यासाठी दोनदा टॅप करा'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'कोलॅप्स करा'; @override String get lookUpButtonLabel => 'शोध घ्या'; @@ -10793,7 +11084,7 @@ class CupertinoLocalizationMs extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Kosongkan'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Dikembangkan'; @override String get copyButtonLabel => 'Salin'; @@ -10844,19 +11135,19 @@ class CupertinoLocalizationMs extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Dikuncupkan'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'ketik dua kali untuk kembangkan'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Kembangkan untuk mendapatkan butiran lanjut'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'ketik dua kali untuk kuncupkan'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Kuncupkan'; @override String get lookUpButtonLabel => 'Lihat ke Atas'; @@ -10983,7 +11274,7 @@ class CupertinoLocalizationMy extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'ဖယ်ရှားရန်'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'ဖြန့်ထားသည်'; @override String get copyButtonLabel => 'မိတ္တူကူးရန်'; @@ -11034,19 +11325,19 @@ class CupertinoLocalizationMy extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'ခေါက်ထားသည်'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'ဖြန့်ရန် နှစ်ချက်တို့ပါ'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'အသေးစိတ်အတွက် ဖြန့်ရန်'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'ခေါက်ရန် နှစ်ချက်တို့ပါ'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'ခေါက်ရန်'; @override String get lookUpButtonLabel => 'အပေါ်ကြည့်ရန်'; @@ -11173,7 +11464,7 @@ class CupertinoLocalizationNb extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Slett'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Vises'; @override String get copyButtonLabel => 'Kopiér'; @@ -11224,19 +11515,19 @@ class CupertinoLocalizationNb extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Skjules'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'dobbelttrykk for å vise'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Vis for å se mer informasjon'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'dobbelttrykk for å skjule'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Skjul'; @override String get lookUpButtonLabel => 'Slå opp'; @@ -11363,7 +11654,7 @@ class CupertinoLocalizationNe extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'हटाउनुहोस्'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'एक्स्पान्ड गरियो'; @override String get copyButtonLabel => 'कपी गर्नुहोस्'; @@ -11414,19 +11705,19 @@ class CupertinoLocalizationNe extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'कोल्याप्स गरियो'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'एक्स्पान्ड गर्न डबल ट्याप गर्नुहोस्'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'थप विवरण हेर्न एक्स्पान्ड गर्नुहोस्'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'कोल्याप्स गर्न डबल ट्याप गर्नुहोस्'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'कोल्याप्स गर्नुहोस्'; @override String get lookUpButtonLabel => 'माथितिर हेर्नुहोस्'; @@ -11553,7 +11844,7 @@ class CupertinoLocalizationNl extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Wissen'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Uitgevouwen'; @override String get copyButtonLabel => 'Kopiëren'; @@ -11604,19 +11895,19 @@ class CupertinoLocalizationNl extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Samengevouwen'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'dubbeltik om uit te vouwen'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Uitvouwen voor meer informatie'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'dubbeltik om samen te vouwen'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Samenvouwen'; @override String get lookUpButtonLabel => 'Opzoeken'; @@ -11743,7 +12034,7 @@ class CupertinoLocalizationNo extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Slett'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Vises'; @override String get copyButtonLabel => 'Kopiér'; @@ -11794,19 +12085,19 @@ class CupertinoLocalizationNo extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Skjules'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'dobbelttrykk for å vise'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Vis for å se mer informasjon'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'dobbelttrykk for å skjule'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Skjul'; @override String get lookUpButtonLabel => 'Slå opp'; @@ -11933,16 +12224,16 @@ class CupertinoLocalizationOr extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'ଖାଲି କରନ୍ତୁ'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'ବିସ୍ତାର କରାଯାଇଛି'; @override String get copyButtonLabel => 'କପି କରନ୍ତୁ'; @override - String get cutButtonLabel => 'କଟ୍ କରନ୍ତୁ'; + String get cutButtonLabel => 'କଟ କରନ୍ତୁ'; @override - String get datePickerDateOrderString => 'mdy'; + String get datePickerDateOrderString => 'dmy'; @override String get datePickerDateTimeOrderString => 'date_time_dayPeriod'; @@ -11954,10 +12245,10 @@ class CupertinoLocalizationOr extends GlobalCupertinoLocalizations { String? get datePickerHourSemanticsLabelMany => null; @override - String? get datePickerHourSemanticsLabelOne => r'$hourଟା'; + String? get datePickerHourSemanticsLabelOne => r'$hourଟା ସମୟରେ'; @override - String get datePickerHourSemanticsLabelOther => r'$hourଟା'; + String get datePickerHourSemanticsLabelOther => r'$hourଟା ସମୟରେ'; @override String? get datePickerHourSemanticsLabelTwo => null; @@ -11972,10 +12263,10 @@ class CupertinoLocalizationOr extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelMany => null; @override - String? get datePickerMinuteSemanticsLabelOne => '1 ମିନିଟ୍'; + String? get datePickerMinuteSemanticsLabelOne => '1 ମିନିଟ'; @override - String get datePickerMinuteSemanticsLabelOther => r'$minute ମିନିଟ୍'; + String get datePickerMinuteSemanticsLabelOther => r'$minute ମିନିଟ'; @override String? get datePickerMinuteSemanticsLabelTwo => null; @@ -11984,19 +12275,19 @@ class CupertinoLocalizationOr extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'ସଙ୍କୁଚିତ କରାଯାଇଛି'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'ବିସ୍ତାର କରିବା ପାଇଁ ଦୁଇଥର ଟାପ କରନ୍ତୁ'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'ଅଧିକ ବିବରଣୀ ପାଇଁ ବିସ୍ତାର କରନ୍ତୁ'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'ସଙ୍କୁଚିତ କରିବା ପାଇଁ ଦୁଇଥର ଟାପ କରନ୍ତୁ'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'ସଙ୍କୁଚିତ କରନ୍ତୁ'; @override String get lookUpButtonLabel => 'ଉପରକୁ ଦେଖନ୍ତୁ'; @@ -12017,19 +12308,19 @@ class CupertinoLocalizationOr extends GlobalCupertinoLocalizations { String get postMeridiemAbbreviation => 'PM'; @override - String get searchTextFieldPlaceholderLabel => 'ସନ୍ଧାନ କରନ୍ତୁ'; + String get searchTextFieldPlaceholderLabel => 'ସର୍ଚ୍ଚ କରନ୍ତୁ'; @override String get searchWebButtonLabel => 'ୱେବ ସର୍ଚ୍ଚ କରନ୍ତୁ'; @override - String get selectAllButtonLabel => 'ସମସ୍ତ ଚୟନ କରନ୍ତୁ'; + String get selectAllButtonLabel => 'ସବୁ ଚୟନ କରନ୍ତୁ'; @override - String get shareButtonLabel => 'ସେୟାର୍ କରନ୍ତୁ...'; + String get shareButtonLabel => 'ସେୟାର କରନ୍ତୁ...'; @override - String get tabSemanticsLabelRaw => r'$tabCountର $tabIndex ଟାବ୍'; + String get tabSemanticsLabelRaw => r'$tabCountର $tabIndex ଟାବ'; @override String? get timerPickerHourLabelFew => null; @@ -12056,10 +12347,10 @@ class CupertinoLocalizationOr extends GlobalCupertinoLocalizations { String? get timerPickerMinuteLabelMany => null; @override - String? get timerPickerMinuteLabelOne => 'ମିନିଟ୍'; + String? get timerPickerMinuteLabelOne => 'ମିନିଟ'; @override - String get timerPickerMinuteLabelOther => 'ମିନିଟ୍'; + String get timerPickerMinuteLabelOther => 'ମିନିଟ'; @override String? get timerPickerMinuteLabelTwo => null; @@ -12123,7 +12414,7 @@ class CupertinoLocalizationPa extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'ਕਲੀਅਰ ਕਰੋ'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'ਵਿਸਤਾਰ ਕੀਤਾ ਗਿਆ'; @override String get copyButtonLabel => 'ਕਾਪੀ ਕਰੋ'; @@ -12174,19 +12465,19 @@ class CupertinoLocalizationPa extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'ਸਮੇਟਿਆ ਗਿਆ'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'ਵਿਸਤਾਰ ਕਰਨ ਲਈ ਡਬਲ ਟੈਪ ਕਰੋ'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'ਹੋਰ ਵੇਰਵਿਆਂ ਲਈ ਵਿਸਤਾਰ ਕਰੋ'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'ਸਮੇਟਣ ਲਈ ਡਬਲ ਟੈਪ ਕਰੋ'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'ਸਮੇਟੋ'; @override String get lookUpButtonLabel => 'ਖੋਜੋ'; @@ -12313,7 +12604,7 @@ class CupertinoLocalizationPl extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Wyczyść'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Rozwinięto'; @override String get copyButtonLabel => 'Kopiuj'; @@ -12364,19 +12655,19 @@ class CupertinoLocalizationPl extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Zwinięto'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'kliknij dwukrotnie, aby rozwinąć'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Rozwiń, aby wyświetlić więcej informacji'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'kliknij dwukrotnie, aby zwinąć'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Zwiń'; @override String get lookUpButtonLabel => 'Sprawdź'; @@ -12503,7 +12794,7 @@ class CupertinoLocalizationPt extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Limpar'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Aberto.'; @override String get copyButtonLabel => 'Copiar'; @@ -12554,19 +12845,19 @@ class CupertinoLocalizationPt extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Fechado.'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'Toque duas vezes para abrir'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Abra para mostrar mais detalhes'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'toque duas vezes para fechar'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Feche'; @override String get lookUpButtonLabel => 'Pesquisar'; @@ -12677,6 +12968,24 @@ class CupertinoLocalizationPtPt extends CupertinoLocalizationPt { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => 'toque duas vezes para reduzir'; + + @override + String get collapsedHint => 'Expandido'; + + @override + String get expandedHint => 'Reduzido'; + + @override + String get expansionTileCollapsedTapHint => 'Expandir para obter mais detalhes'; + + @override + String get expansionTileExpandedTapHint => 'Reduzir'; + + @override + String get expansionTileCollapsedHint => 'toque duas vezes para expandir'; + @override String get backButtonLabel => 'Anterior'; @@ -12745,7 +13054,7 @@ class CupertinoLocalizationRo extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Ștergeți'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Extins'; @override String get copyButtonLabel => 'Copiați'; @@ -12796,19 +13105,19 @@ class CupertinoLocalizationRo extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Restrâns'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'atingeți de două ori pentru a extinde'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Extindeți pentru mai multe detalii'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'atingeți de două ori pentru a restrânge'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Restrângeți'; @override String get lookUpButtonLabel => 'Privire în sus'; @@ -12935,7 +13244,7 @@ class CupertinoLocalizationRu extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Очистить'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Развернуто'; @override String get copyButtonLabel => 'Копировать'; @@ -12986,19 +13295,19 @@ class CupertinoLocalizationRu extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Свернуто'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'нажмите дважды, чтобы развернуть'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Развернуть дополнительные сведения'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'нажмите дважды, чтобы свернуть'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Свернуть'; @override String get lookUpButtonLabel => 'Найти'; @@ -13125,7 +13434,7 @@ class CupertinoLocalizationSi extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'හිස් කරන්න'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'දිග හරින ලදි'; @override String get copyButtonLabel => 'පිටපත් කරන්න'; @@ -13176,19 +13485,19 @@ class CupertinoLocalizationSi extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'හකුළන ලදි'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'විහිදුවීමට දෙවරක් තට්ටු කරන්න'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'වැඩි විස්තර සඳහා පුළුල් කරන්න'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'හැකිළවීමට දෙවරක් තට්ටු කරන්න'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'හකුළන්න'; @override String get lookUpButtonLabel => 'උඩ බලන්න'; @@ -13315,7 +13624,7 @@ class CupertinoLocalizationSk extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Vymazať'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Rozbalené'; @override String get copyButtonLabel => 'Kopírovať'; @@ -13366,19 +13675,19 @@ class CupertinoLocalizationSk extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Zbalené'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'rozbalíte dvojitým klepnutím'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Rozbaliť a zobraziť ďalšie podrobnosti'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'zbalíte dvojitým klepnutím'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Zbaliť'; @override String get lookUpButtonLabel => 'Pohľad nahor'; @@ -13505,7 +13814,7 @@ class CupertinoLocalizationSl extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Počisti'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Razširjeno'; @override String get copyButtonLabel => 'Kopiraj'; @@ -13556,19 +13865,19 @@ class CupertinoLocalizationSl extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Strnjeno'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'za razširitev se dvakrat dotaknite'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Razširitev za več podrobnosti'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'za strnitev se dvakrat dotaknite'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Strni'; @override String get lookUpButtonLabel => 'Pogled gor'; @@ -13695,7 +14004,7 @@ class CupertinoLocalizationSq extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Pastro'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'U zgjerua'; @override String get copyButtonLabel => 'Kopjo'; @@ -13746,19 +14055,19 @@ class CupertinoLocalizationSq extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'U palos'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'trokit dy herë për ta zgjeruar'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Zgjero për më shumë detaje'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'trokit dy herë për ta palosur'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Palos'; @override String get lookUpButtonLabel => 'Kërko'; @@ -13885,7 +14194,7 @@ class CupertinoLocalizationSr extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Обриши'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Проширено је'; @override String get copyButtonLabel => 'Копирај'; @@ -13936,19 +14245,19 @@ class CupertinoLocalizationSr extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Скупљено је'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'двапут додирните да бисте проширили'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Проширите за још детаља'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'двапут додирните да бисте скупили'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Скупите'; @override String get lookUpButtonLabel => 'Поглед нагоре'; @@ -14093,6 +14402,9 @@ class CupertinoLocalizationSrLatn extends CupertinoLocalizationSr { @override String get clearButtonLabel => 'Obriši'; + @override + String get collapsedHint => 'Prošireno je'; + @override String get copyButtonLabel => 'Kopiraj'; @@ -14117,6 +14429,21 @@ class CupertinoLocalizationSrLatn extends CupertinoLocalizationSr { @override String get datePickerMinuteSemanticsLabelOther => r'$minute minuta'; + @override + String get expandedHint => 'Skupljeno je'; + + @override + String get expansionTileCollapsedHint => 'dvaput dodirnite da biste proširili'; + + @override + String get expansionTileCollapsedTapHint => 'Proširite za još detalja'; + + @override + String get expansionTileExpandedHint => 'dvaput dodirnite da biste skupili'; + + @override + String get expansionTileExpandedTapHint => 'Skupite'; + @override String get lookUpButtonLabel => 'Pogled nagore'; @@ -14215,7 +14542,7 @@ class CupertinoLocalizationSv extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Rensa'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Utökades'; @override String get copyButtonLabel => 'Kopiera'; @@ -14266,19 +14593,19 @@ class CupertinoLocalizationSv extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Komprimerades'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'dubbeltryck för att utöka'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Utöka för mer information'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'dubbeltryck för att komprimera'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Komprimera'; @override String get lookUpButtonLabel => 'Titta upp'; @@ -14405,7 +14732,7 @@ class CupertinoLocalizationSw extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Futa'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Imepanuliwa'; @override String get copyButtonLabel => 'Nakili'; @@ -14456,19 +14783,19 @@ class CupertinoLocalizationSw extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Imekunjwa'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'gusa mara mbili ili upanue'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Panua ili upate maelezo zaidi'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'gusa mara mbili ili ukunje'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Kunja'; @override String get lookUpButtonLabel => 'Tafuta'; @@ -14595,7 +14922,7 @@ class CupertinoLocalizationTa extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'அழி'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'விரிவாக்கப்பட்டது'; @override String get copyButtonLabel => 'நகலெடு'; @@ -14646,19 +14973,19 @@ class CupertinoLocalizationTa extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'சுருக்கப்பட்டது'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'விரிவாக்க இருமுறை தட்டுங்கள்'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'கூடுதல் விவரங்களுக்கு விரிவாக்கலாம்'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'சுருக்க இருமுறை தட்டவும்'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'சுருக்கும்'; @override String get lookUpButtonLabel => 'தேடு'; @@ -14785,7 +15112,7 @@ class CupertinoLocalizationTe extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'క్లియర్ చేయండి'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'విస్తరించబడింది'; @override String get copyButtonLabel => 'కాపీ చేయి'; @@ -14836,19 +15163,19 @@ class CupertinoLocalizationTe extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'కుదించబడింది'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'విస్తరించడానికి డబుల్ ట్యాప్ చేయండి'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'మరిన్ని వివరాల కోసం విస్తరించండి'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'కుదించడానికి డబుల్ ట్యాప్ చేయండి'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'కుదించండి'; @override String get lookUpButtonLabel => 'వెతకండి'; @@ -14975,7 +15302,7 @@ class CupertinoLocalizationTh extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'ล้าง'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'ขยาย'; @override String get copyButtonLabel => 'คัดลอก'; @@ -15026,19 +15353,19 @@ class CupertinoLocalizationTh extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'ยุบ'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'แตะสองครั้งเพื่อขยาย'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'ขยายเพื่อดูรายละเอียดเพิ่มเติม'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'แตะสองครั้งเพื่อยุบ'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'ยุบ'; @override String get lookUpButtonLabel => 'ค้นหา'; @@ -15165,7 +15492,7 @@ class CupertinoLocalizationTl extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'I-clear'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Naka-expand'; @override String get copyButtonLabel => 'Kopyahin'; @@ -15216,19 +15543,19 @@ class CupertinoLocalizationTl extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Naka-collapse'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'i-double tap para i-expand'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'I-expand para sa higit pang detalye'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'i-double tap para i-collapse'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'I-collapse'; @override String get lookUpButtonLabel => 'Tumingin sa Itaas'; @@ -15355,7 +15682,7 @@ class CupertinoLocalizationTr extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Temizle'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Genişletildi'; @override String get copyButtonLabel => 'Kopyala'; @@ -15406,19 +15733,19 @@ class CupertinoLocalizationTr extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Daraltıldı'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'genişletmek için iki kez dokunun'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Daha fazla ayrıntı için genişletin'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'daraltmak için iki kez dokunun'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Daralt'; @override String get lookUpButtonLabel => 'Ara'; @@ -15545,7 +15872,7 @@ class CupertinoLocalizationUg extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'تازىلاش'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'يېيىلدى'; @override String get copyButtonLabel => 'كۆچۈرۈش'; @@ -15596,19 +15923,19 @@ class CupertinoLocalizationUg extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'يىغىلدى'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'يېيىش ئۈچۈن قوش چېكىڭ'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'تېخىمۇ كۆپ تەپسىلاتلار ئۈچۈن يېيىڭ'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'يىغىش ئۈچۈن قوش چېكىڭ'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'يىغىش'; @override String get lookUpButtonLabel => 'ئىزدەش'; @@ -15735,7 +16062,7 @@ class CupertinoLocalizationUk extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Очистити'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Розгорнуто'; @override String get copyButtonLabel => 'Копіювати'; @@ -15786,19 +16113,19 @@ class CupertinoLocalizationUk extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Згорнуто'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'двічі торкніться, щоб розгорнути'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Розгорнути й дізнатися більше'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'двічі торкніться, щоб згорнути'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Згорнути'; @override String get lookUpButtonLabel => 'Шукати'; @@ -15925,7 +16252,7 @@ class CupertinoLocalizationUr extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'صاف کریں'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'پھیلا ہوا'; @override String get copyButtonLabel => 'کاپی کریں'; @@ -15976,19 +16303,19 @@ class CupertinoLocalizationUr extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'سکڑا ہوا'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'پھیلانے کے لیے دوبار تھپتھپائیں'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'مزید تفصیلات کے لیے پھیلائیں'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'سکیڑنے کے لیے دوبار تھپتھپائیں'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'سکیڑیں'; @override String get lookUpButtonLabel => 'تفصیل دیکھیں'; @@ -16115,7 +16442,7 @@ class CupertinoLocalizationUz extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Tozalash'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Yoyilgan'; @override String get copyButtonLabel => 'Nusxa olish'; @@ -16166,19 +16493,19 @@ class CupertinoLocalizationUz extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Yigʻilgan'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'yoyish uchun ikki marta bosing'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Batafsil koʻrish uchun yoying'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'yigʻish uchun ikki marta bosing'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Yigʻish'; @override String get lookUpButtonLabel => 'Tepaga qarang'; @@ -16305,7 +16632,7 @@ class CupertinoLocalizationVi extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Xoá'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Đã mở rộng'; @override String get copyButtonLabel => 'Sao chép'; @@ -16356,19 +16683,19 @@ class CupertinoLocalizationVi extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Đã thu gọn'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'nhấn đúp để mở rộng'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Mở rộng để xem thêm chi tiết'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'nhấn đúp để thu gọn'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Thu gọn'; @override String get lookUpButtonLabel => 'Tra cứu'; @@ -16495,7 +16822,7 @@ class CupertinoLocalizationZh extends GlobalCupertinoLocalizations { String get clearButtonLabel => '清除'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => '已展开'; @override String get copyButtonLabel => '复制'; @@ -16546,19 +16873,19 @@ class CupertinoLocalizationZh extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => '已收起'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => '点按两次即可展开'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => '展开查看更多详情'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => '点按两次即可收起'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => '收起'; @override String get lookUpButtonLabel => '查询'; @@ -16691,6 +17018,9 @@ class CupertinoLocalizationZhHant extends CupertinoLocalizationZh { @override String get alertDialogLabel => '通知'; + @override + String get collapsedHint => '已展開'; + @override String get copyButtonLabel => '複製'; @@ -16712,6 +17042,21 @@ class CupertinoLocalizationZhHant extends CupertinoLocalizationZh { @override String get datePickerMinuteSemanticsLabelOther => r'$minute 分鐘'; + @override + String get expandedHint => '已收合'; + + @override + String get expansionTileCollapsedHint => '㩒兩下就可以展開'; + + @override + String get expansionTileCollapsedTapHint => '展開就可以查看詳情'; + + @override + String get expansionTileExpandedHint => '㩒兩下就可以收合'; + + @override + String get expansionTileExpandedTapHint => '收合'; + @override String get lookUpButtonLabel => '查詢'; @@ -16792,6 +17137,15 @@ class CupertinoLocalizationZhHantTw extends CupertinoLocalizationZhHant { required super.decimalFormat, }); + @override + String get expansionTileExpandedHint => '輕觸兩下即可收合'; + + @override + String get expansionTileCollapsedTapHint => '展開更多詳細資料'; + + @override + String get expansionTileCollapsedHint => '輕觸兩下即可展開'; + @override String get noSpellCheckReplacementsLabel => '找不到替代文字'; @@ -16857,7 +17211,7 @@ class CupertinoLocalizationZu extends GlobalCupertinoLocalizations { String get clearButtonLabel => 'Sula'; @override - String get collapsedHint => 'Expanded'; + String get collapsedHint => 'Kunwetshiwe'; @override String get copyButtonLabel => 'Kopisha'; @@ -16908,19 +17262,19 @@ class CupertinoLocalizationZu extends GlobalCupertinoLocalizations { String? get datePickerMinuteSemanticsLabelZero => null; @override - String get expandedHint => 'Collapsed'; + String get expandedHint => 'Kugoqiwe'; @override - String get expansionTileCollapsedHint => 'double tap to expand'; + String get expansionTileCollapsedHint => 'Thepha kabili ukuze unwebe'; @override - String get expansionTileCollapsedTapHint => 'Expand for more details'; + String get expansionTileCollapsedTapHint => 'Nweba ukuze uthole imininingwane eyengeziwe'; @override - String get expansionTileExpandedHint => 'double tap to collapse'; + String get expansionTileExpandedHint => 'thepha kabili ukuze ugoqe'; @override - String get expansionTileExpandedTapHint => 'Collapse'; + String get expansionTileExpandedTapHint => 'Goqa'; @override String get lookUpButtonLabel => 'Bheka Phezulu'; diff --git a/packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart b/packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart index e3d4b6820b2de..fab654deb483e 100644 --- a/packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart +++ b/packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart @@ -1581,10 +1581,10 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String get dateHelpText => 'mm/dd/yyyy'; @override - String get dateInputLabel => 'তাৰিখটো দিয়ক'; + String get dateInputLabel => 'তাৰিখ দিয়ক'; @override - String get dateOutOfRangeLabel => 'সীমাৰ বাহিৰত।'; + String get dateOutOfRangeLabel => 'পৰিসৰৰ বাহিৰত।'; @override String get datePickerHelpText => 'তাৰিখ বাছনি কৰক'; @@ -1614,7 +1614,7 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String get dialModeButtonLabel => 'ডায়েল বাছনিকৰ্তাৰ ম’ডলৈ সলনি কৰক'; @override - String get dialogLabel => "ডায়ল'গ"; + String get dialogLabel => 'ডায়লগ'; @override String get drawerLabel => 'নেভিগেশ্বন মেনু'; @@ -1665,16 +1665,16 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String get keyboardKeyAltGraph => 'AltGr'; @override - String get keyboardKeyBackspace => 'বেকস্পেচ'; + String get keyboardKeyBackspace => 'Backspace'; @override - String get keyboardKeyCapsLock => 'কেপ্‌ছ লক'; + String get keyboardKeyCapsLock => 'Caps Lock'; @override - String get keyboardKeyChannelDown => 'চেনেল ডাউন'; + String get keyboardKeyChannelDown => 'Channel Down'; @override - String get keyboardKeyChannelUp => 'চেনেল আপ'; + String get keyboardKeyChannelUp => 'Channel Up'; @override String get keyboardKeyControl => 'Ctrl'; @@ -1683,7 +1683,7 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String get keyboardKeyDelete => 'Del'; @override - String get keyboardKeyEject => 'ইজেক্ট'; + String get keyboardKeyEject => 'Eject'; @override String get keyboardKeyEnd => 'End'; @@ -1701,7 +1701,7 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String get keyboardKeyInsert => 'Insert'; @override - String get keyboardKeyMeta => 'মেটা'; + String get keyboardKeyMeta => 'Meta'; @override String get keyboardKeyMetaMacOs => 'Command'; @@ -1713,64 +1713,64 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String get keyboardKeyNumLock => 'Num Lock'; @override - String get keyboardKeyNumpad0 => 'নং ০'; + String get keyboardKeyNumpad0 => 'Num 0'; @override - String get keyboardKeyNumpad1 => 'নং ১'; + String get keyboardKeyNumpad1 => 'Num 1'; @override - String get keyboardKeyNumpad2 => 'নং ২'; + String get keyboardKeyNumpad2 => 'Num 2'; @override - String get keyboardKeyNumpad3 => 'নং ৩'; + String get keyboardKeyNumpad3 => 'Num 3'; @override - String get keyboardKeyNumpad4 => 'নং ৪'; + String get keyboardKeyNumpad4 => 'Num 4'; @override - String get keyboardKeyNumpad5 => 'নং ৫'; + String get keyboardKeyNumpad5 => 'Num 5'; @override - String get keyboardKeyNumpad6 => 'নং ৬'; + String get keyboardKeyNumpad6 => 'Num 6'; @override - String get keyboardKeyNumpad7 => 'নং ৭'; + String get keyboardKeyNumpad7 => 'Num 7'; @override - String get keyboardKeyNumpad8 => 'নং ৮'; + String get keyboardKeyNumpad8 => 'Num 8'; @override - String get keyboardKeyNumpad9 => 'নং ৯'; + String get keyboardKeyNumpad9 => 'Num 9'; @override - String get keyboardKeyNumpadAdd => 'নং +'; + String get keyboardKeyNumpadAdd => 'Num +'; @override - String get keyboardKeyNumpadComma => 'নং ,'; + String get keyboardKeyNumpadComma => 'Num ,'; @override - String get keyboardKeyNumpadDecimal => 'নং .'; + String get keyboardKeyNumpadDecimal => 'Num .'; @override - String get keyboardKeyNumpadDivide => 'নং /'; + String get keyboardKeyNumpadDivide => 'Num /'; @override String get keyboardKeyNumpadEnter => 'Num Enter'; @override - String get keyboardKeyNumpadEqual => 'নং ='; + String get keyboardKeyNumpadEqual => 'Num ='; @override - String get keyboardKeyNumpadMultiply => 'নং *'; + String get keyboardKeyNumpadMultiply => 'Num *'; @override - String get keyboardKeyNumpadParenLeft => 'নং ('; + String get keyboardKeyNumpadParenLeft => 'Num ('; @override - String get keyboardKeyNumpadParenRight => 'নং )'; + String get keyboardKeyNumpadParenRight => 'Num )'; @override - String get keyboardKeyNumpadSubtract => 'নং -'; + String get keyboardKeyNumpadSubtract => 'Num -'; @override String get keyboardKeyPageDown => 'PgDown'; @@ -1779,25 +1779,25 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String get keyboardKeyPageUp => 'PgUp'; @override - String get keyboardKeyPower => 'পাৱাৰ'; + String get keyboardKeyPower => 'Power'; @override - String get keyboardKeyPowerOff => 'পাৱাৰ অফ'; + String get keyboardKeyPowerOff => 'Power Off'; @override - String get keyboardKeyPrintScreen => 'প্ৰিণ্ট স্ক্ৰীন'; + String get keyboardKeyPrintScreen => 'Print Screen'; @override - String get keyboardKeyScrollLock => 'স্ক্ৰ’ল লক'; + String get keyboardKeyScrollLock => 'Scroll Lock'; @override - String get keyboardKeySelect => 'ছিলেক্ট'; + String get keyboardKeySelect => 'Select'; @override - String get keyboardKeyShift => 'শ্বিফ্ট'; + String get keyboardKeyShift => 'Shift'; @override - String get keyboardKeySpace => 'স্পেচ'; + String get keyboardKeySpace => 'Space'; @override String get lastPageTooltip => 'অন্তিম পৃষ্ঠা'; @@ -1821,7 +1821,7 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String? get licensesPackageDetailTextZero => 'No licenses'; @override - String get licensesPageTitle => 'অনুজ্ঞাপত্ৰসমূহ'; + String get licensesPageTitle => 'অনুজ্ঞাপত্ৰ'; @override String get lookUpButtonLabel => 'ওপৰলৈ চাওক'; @@ -1854,13 +1854,13 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String get pageRowsInfoTitleRaw => r'$rowCountৰ $firstRow–$lastRow'; @override - String get pageRowsInfoTitleApproximateRaw => r'$rowCountৰ $firstRow–$lastRow'; + String get pageRowsInfoTitleApproximateRaw => r'প্ৰায় $rowCountৰ $firstRow–$lastRow'; @override String get pasteButtonLabel => "পে'ষ্ট কৰক"; @override - String get popupMenuLabel => "প'পআপ মেনু"; + String get popupMenuLabel => 'পপআপ মেনু'; @override String get postMeridiemAbbreviation => 'অপৰাহ্ন'; @@ -1881,10 +1881,10 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String? get remainingTextFieldCharacterCountMany => null; @override - String? get remainingTextFieldCharacterCountOne => '১টা বর্ণ বাকী আছে'; + String? get remainingTextFieldCharacterCountOne => '১ টা বর্ণসংখ্যা বাকী আছে'; @override - String get remainingTextFieldCharacterCountOther => r'$remainingCountটা বর্ণ বাকী আছে'; + String get remainingTextFieldCharacterCountOther => r'$remainingCount টা বর্ণসংখ্যা বাকী আছে'; @override String? get remainingTextFieldCharacterCountTwo => null; @@ -1893,19 +1893,19 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String? get remainingTextFieldCharacterCountZero => null; @override - String get reorderItemDown => 'তললৈ স্থানান্তৰ কৰক'; + String get reorderItemDown => 'তললৈ নিয়ক'; @override String get reorderItemLeft => 'বাওঁফাললৈ স্থানান্তৰ কৰক'; @override - String get reorderItemRight => 'সোঁফাললৈ স্থানান্তৰ কৰক'; + String get reorderItemRight => 'সোঁফাললৈ নিয়ক'; @override - String get reorderItemToEnd => 'শেষলৈ স্থানান্তৰ কৰক'; + String get reorderItemToEnd => 'শেষলৈ নিয়ক'; @override - String get reorderItemToStart => 'আৰম্ভণিলৈ স্থানান্তৰ কৰক'; + String get reorderItemToStart => 'আৰম্ভণিলৈ নিয়ক'; @override String get reorderItemUp => 'ওপৰলৈ নিয়ক'; @@ -1935,7 +1935,7 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String get searchWebButtonLabel => 'ৱেবত সন্ধান কৰক'; @override - String get selectAllButtonLabel => 'সকলো বাছনি কৰক'; + String get selectAllButtonLabel => 'আটাইবোৰ বাছনি কৰক'; @override String get selectYearSemanticsLabel => 'বছৰ বাছনি কৰক'; @@ -1950,10 +1950,10 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String? get selectedRowCountTitleMany => null; @override - String? get selectedRowCountTitleOne => "১টা বস্তু বাছনি কৰা হ'ল"; + String? get selectedRowCountTitleOne => '১ টা বস্তু বাছনি কৰা হ’ল'; @override - String get selectedRowCountTitleOther => r'$selectedRowCountটা বস্তু বাছনি কৰা হ’ল'; + String get selectedRowCountTitleOther => r'$selectedRowCount টা বস্তু বাছনি কৰা হ’ল'; @override String? get selectedRowCountTitleTwo => null; @@ -1974,7 +1974,7 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String get signedInLabel => 'ছাইন ইন কৰা হ’ল'; @override - String get tabLabelRaw => r'$tabCountৰ $tabIndexটা টেব'; + String get tabLabelRaw => r'$tabCount টাৰ ভিতৰত $tabIndex টা টেব'; @override TimeOfDayFormat get timeOfDayFormatRaw => TimeOfDayFormat.H_colon_mm; @@ -1986,7 +1986,7 @@ class MaterialLocalizationAs extends GlobalMaterialLocalizations { String get timePickerHourLabel => 'ঘণ্টা'; @override - String get timePickerHourModeAnnouncement => 'সময় বাছনি কৰক'; + String get timePickerHourModeAnnouncement => 'ঘণ্টা বাছনি কৰক'; @override String get timePickerInputHelpText => 'সময় দিয়ক'; @@ -5994,7 +5994,7 @@ class MaterialLocalizationCy extends GlobalMaterialLocalizations { }); @override - String get aboutListTileTitleRaw => r'Ynghylch $applicationName'; + String get aboutListTileTitleRaw => r'Ynglŷn â $applicationName'; @override String get alertDialogLabel => 'Rhybudd'; @@ -6150,7 +6150,7 @@ class MaterialLocalizationCy extends GlobalMaterialLocalizations { String get keyboardKeyEject => 'Eject'; @override - String get keyboardKeyEnd => 'Gorffen'; + String get keyboardKeyEnd => 'End'; @override String get keyboardKeyEscape => 'Esc'; @@ -6273,7 +6273,7 @@ class MaterialLocalizationCy extends GlobalMaterialLocalizations { String? get licensesPackageDetailTextMany => r'$licenseCount thrwydded'; @override - String? get licensesPackageDetailTextOne => '1 trwydded'; + String? get licensesPackageDetailTextOne => '1 drwydded'; @override String get licensesPackageDetailTextOther => r'$licenseCount trwydded'; @@ -6399,7 +6399,7 @@ class MaterialLocalizationCy extends GlobalMaterialLocalizations { String get searchWebButtonLabel => "Chwilio'r We"; @override - String get selectAllButtonLabel => 'Dewis y Cyfan'; + String get selectAllButtonLabel => 'Dewis y cyfan'; @override String get selectYearSemanticsLabel => 'Dewiswch flwyddyn'; @@ -6408,19 +6408,19 @@ class MaterialLocalizationCy extends GlobalMaterialLocalizations { String get selectedDateLabel => "Wedi'i ddewis"; @override - String? get selectedRowCountTitleFew => r"Mae $selectedRowCount eitem wedi'u dewis"; + String? get selectedRowCountTitleFew => r"$selectedRowCount eitem wedi'u dewis"; @override - String? get selectedRowCountTitleMany => r"Mae $selectedRowCount eitem wedi'u dewis"; + String? get selectedRowCountTitleMany => r"$selectedRowCount eitem wedi'u dewis"; @override - String? get selectedRowCountTitleOne => "Mae 1 eitem wedi'i dewis"; + String? get selectedRowCountTitleOne => "1 eitem wedi'i dewis"; @override - String get selectedRowCountTitleOther => r"Mae $selectedRowCount eitem wedi'u dewis"; + String get selectedRowCountTitleOther => r"$selectedRowCount eitem wedi'u dewis"; @override - String? get selectedRowCountTitleTwo => r"Mae $selectedRowCount eitem wedi'u dewis"; + String? get selectedRowCountTitleTwo => r"$selectedRowCount eitem wedi'u dewis"; @override String? get selectedRowCountTitleZero => "Nid oes unrhyw eitemau wedi'u dewis"; @@ -6793,7 +6793,7 @@ class MaterialLocalizationDa extends GlobalMaterialLocalizations { String get menuDismissLabel => 'Luk menu'; @override - String get modalBarrierDismissLabel => 'Afvis'; + String get modalBarrierDismissLabel => 'Luk'; @override String get moreButtonTooltip => 'Mere'; @@ -14671,7 +14671,7 @@ class MaterialLocalizationFa extends GlobalMaterialLocalizations { String get keyboardKeyAlt => 'دگرساز'; @override - String get keyboardKeyAltGraph => 'دگرساز راست'; + String get keyboardKeyAltGraph => 'AltGr'; @override String get keyboardKeyBackspace => 'پس‌بَر'; @@ -14686,7 +14686,7 @@ class MaterialLocalizationFa extends GlobalMaterialLocalizations { String get keyboardKeyChannelUp => 'کانال بالا'; @override - String get keyboardKeyControl => 'مهار'; + String get keyboardKeyControl => 'Ctrl'; @override String get keyboardKeyDelete => 'حذف'; @@ -14698,7 +14698,7 @@ class MaterialLocalizationFa extends GlobalMaterialLocalizations { String get keyboardKeyEnd => 'پایان'; @override - String get keyboardKeyEscape => 'گریز'; + String get keyboardKeyEscape => 'Esc'; @override String get keyboardKeyFn => 'عملکرد'; @@ -14707,79 +14707,79 @@ class MaterialLocalizationFa extends GlobalMaterialLocalizations { String get keyboardKeyHome => 'صفحه اصلی'; @override - String get keyboardKeyInsert => 'درج'; + String get keyboardKeyInsert => 'Insert'; @override String get keyboardKeyMeta => 'متا'; @override - String get keyboardKeyMetaMacOs => 'فرمان'; + String get keyboardKeyMetaMacOs => 'Command'; @override String get keyboardKeyMetaWindows => 'Win'; @override - String get keyboardKeyNumLock => 'قفل اعداد'; + String get keyboardKeyNumLock => 'Num Lock'; @override - String get keyboardKeyNumpad0 => 'عدد ۰'; + String get keyboardKeyNumpad0 => 'Numpad 0'; @override - String get keyboardKeyNumpad1 => 'عدد ۱'; + String get keyboardKeyNumpad1 => 'Numpad 1'; @override - String get keyboardKeyNumpad2 => 'عدد ۲'; + String get keyboardKeyNumpad2 => 'Numpad 2'; @override - String get keyboardKeyNumpad3 => 'عدد ۳'; + String get keyboardKeyNumpad3 => 'Numpad 3'; @override - String get keyboardKeyNumpad4 => 'عدد ۴'; + String get keyboardKeyNumpad4 => 'Numpad 4'; @override - String get keyboardKeyNumpad5 => 'عدد ۵'; + String get keyboardKeyNumpad5 => 'Numpad 5'; @override - String get keyboardKeyNumpad6 => 'عدد ۶'; + String get keyboardKeyNumpad6 => 'Numpad 6'; @override - String get keyboardKeyNumpad7 => 'عدد ۷'; + String get keyboardKeyNumpad7 => 'Numpad 7'; @override - String get keyboardKeyNumpad8 => 'عدد ۸'; + String get keyboardKeyNumpad8 => 'Numpad 8'; @override - String get keyboardKeyNumpad9 => 'عدد ۹'; + String get keyboardKeyNumpad9 => 'Numpad 9'; @override - String get keyboardKeyNumpadAdd => 'عدد +'; + String get keyboardKeyNumpadAdd => 'Numpad +‎'; @override - String get keyboardKeyNumpadComma => 'عدد ,'; + String get keyboardKeyNumpadComma => 'Numpad ,‎'; @override - String get keyboardKeyNumpadDecimal => 'عدد .'; + String get keyboardKeyNumpadDecimal => 'Numpad .‎'; @override - String get keyboardKeyNumpadDivide => 'عدد /'; + String get keyboardKeyNumpadDivide => 'Numpad /‎'; @override - String get keyboardKeyNumpadEnter => 'ورود اعداد'; + String get keyboardKeyNumpadEnter => 'Numpad Enter'; @override - String get keyboardKeyNumpadEqual => 'عدد ='; + String get keyboardKeyNumpadEqual => 'Numpad =‎'; @override - String get keyboardKeyNumpadMultiply => 'عدد *'; + String get keyboardKeyNumpadMultiply => 'Numpad *‎'; @override - String get keyboardKeyNumpadParenLeft => 'عدد ('; + String get keyboardKeyNumpadParenLeft => 'Numpad (‎'; @override - String get keyboardKeyNumpadParenRight => 'عدد )'; + String get keyboardKeyNumpadParenRight => 'Numpad )‎'; @override - String get keyboardKeyNumpadSubtract => 'عدد -'; + String get keyboardKeyNumpadSubtract => 'Numpad -‎'; @override String get keyboardKeyPageDown => 'صفحه پایین'; @@ -14803,7 +14803,7 @@ class MaterialLocalizationFa extends GlobalMaterialLocalizations { String get keyboardKeySelect => 'انتخاب'; @override - String get keyboardKeyShift => 'کلید تبدیل'; + String get keyboardKeyShift => 'Shift'; @override String get keyboardKeySpace => 'فاصله'; @@ -17257,7 +17257,7 @@ class MaterialLocalizationGl extends GlobalMaterialLocalizations { String get deleteButtonTooltip => 'Eliminar'; @override - String get dialModeButtonLabel => 'Cambiar a modo de selector en esfera'; + String get dialModeButtonLabel => 'Cambiar ao modo de selector en esfera'; @override String get dialogLabel => 'Cadro de diálogo'; @@ -17278,7 +17278,7 @@ class MaterialLocalizationGl extends GlobalMaterialLocalizations { String get expansionTileCollapsedTapHint => 'Despregar para obter máis detalles'; @override - String get expansionTileExpandedHint => 'toca dúas veces para contraer'; + String get expansionTileExpandedHint => 'tocar dúas veces para contraer'; @override String get expansionTileExpandedTapHint => 'Contraer'; @@ -17293,7 +17293,7 @@ class MaterialLocalizationGl extends GlobalMaterialLocalizations { String get inputDateModeButtonLabel => 'Cambiar ao modo de introdución de texto'; @override - String get inputTimeModeButtonLabel => 'Cambiar ao modo de escritura dos números'; + String get inputTimeModeButtonLabel => 'Cambiar ao modo de introdución de texto'; @override String get invalidDateFormatLabel => 'O formato non é válido.'; @@ -17479,7 +17479,7 @@ class MaterialLocalizationGl extends GlobalMaterialLocalizations { String get menuDismissLabel => 'Pechar menú'; @override - String get modalBarrierDismissLabel => 'Ignorar'; + String get modalBarrierDismissLabel => 'Pechar'; @override String get moreButtonTooltip => 'Máis'; @@ -17575,7 +17575,7 @@ class MaterialLocalizationGl extends GlobalMaterialLocalizations { ScriptCategory get scriptCategory => ScriptCategory.englishLike; @override - String get searchFieldLabel => 'Buscar'; + String get searchFieldLabel => 'Fai unha busca'; @override String get searchWebButtonLabel => 'Buscar na Web'; @@ -32567,7 +32567,7 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { String get bottomSheetLabel => 'ବଟମ ସିଟ'; @override - String get calendarModeButtonLabel => 'କ୍ୟାଲେଣ୍ଡରକୁ ସ୍ୱିଚ୍ କରନ୍ତୁ'; + String get calendarModeButtonLabel => 'କେଲେଣ୍ଡରକୁ ସ୍ୱିଚ କରନ୍ତୁ'; @override String get cancelButtonLabel => 'ବାତିଲ କରନ୍ତୁ'; @@ -32585,7 +32585,7 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { String get collapsedHint => 'ବିସ୍ତାର କରାଯାଇଛି'; @override - String get collapsedIconTapHint => 'ପ୍ରସାରିତ କରନ୍ତୁ'; + String get collapsedIconTapHint => 'ବିସ୍ତାର କରନ୍ତୁ'; @override String get continueButtonLabel => 'ଜାରି ରଖନ୍ତୁ'; @@ -32597,16 +32597,16 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { String get currentDateLabel => 'ଆଜି'; @override - String get cutButtonLabel => 'କଟ୍ କରନ୍ତୁ'; + String get cutButtonLabel => 'କଟ କରନ୍ତୁ'; @override - String get dateHelpText => 'mm/dd/yyyy'; + String get dateHelpText => 'dd/mm/yyyy'; @override String get dateInputLabel => 'ତାରିଖ ଲେଖନ୍ତୁ'; @override - String get dateOutOfRangeLabel => 'ସୀମା ବାହାରେ।'; + String get dateOutOfRangeLabel => 'ରେଞ୍ଜ ବାହାରେ।'; @override String get datePickerHelpText => 'ତାରିଖ ଚୟନ କରନ୍ତୁ'; @@ -32630,16 +32630,16 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { String get dateSeparator => '/'; @override - String get deleteButtonTooltip => 'ଡିଲିଟ୍ କରନ୍ତୁ'; + String get deleteButtonTooltip => 'ଡିଲିଟ କରନ୍ତୁ'; @override - String get dialModeButtonLabel => 'ଡାଏଲ୍ ପିକର୍ ମୋଡକୁ ସ୍ୱିଚ୍ କରନ୍ତୁ'; + String get dialModeButtonLabel => 'ଡାଏଲ ପିକର ମୋଡକୁ ସ୍ୱିଚ କରନ୍ତୁ'; @override - String get dialogLabel => 'ଡାୟଲଗ୍'; + String get dialogLabel => 'ଡାଏଲଗ'; @override - String get drawerLabel => 'ନେଭିଗେସନ୍ ମେନୁ'; + String get drawerLabel => 'ନାଭିଗେସନ ମେନୁ'; @override String get expandedHint => 'ସଙ୍କୁଚିତ କରାଯାଇଛି'; @@ -32666,16 +32666,16 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { String get hideAccountsLabel => 'ଆକାଉଣ୍ଟଗୁଡ଼ିକୁ ଲୁଚାନ୍ତୁ'; @override - String get inputDateModeButtonLabel => 'ଇନପୁଟକୁ ସ୍ୱିଚ୍ କରନ୍ତୁ'; + String get inputDateModeButtonLabel => 'ଇନପୁଟକୁ ସ୍ୱିଚ କରନ୍ତୁ'; @override - String get inputTimeModeButtonLabel => 'ଟେକ୍ସଟ୍ ଇନପୁଟ୍ ମୋଡକୁ ସ୍ୱିଚ୍ କରନ୍ତୁ'; + String get inputTimeModeButtonLabel => 'ଟେକ୍ସଟ ଇନପୁଟ ମୋଡକୁ ସ୍ୱିଚ କରନ୍ତୁ'; @override - String get invalidDateFormatLabel => 'ଅବୈଧ ଫର୍ମାଟ୍।'; + String get invalidDateFormatLabel => 'ଅବୈଧ ଫର୍ମାଟ।'; @override - String get invalidDateRangeLabel => 'ଅବୈଧ ସୀମା।'; + String get invalidDateRangeLabel => 'ଅବୈଧ ରେଞ୍ଜ।'; @override String get invalidTimeLabel => 'ଏକ ବୈଧ ସମୟ ଲେଖନ୍ତୁ'; @@ -32831,10 +32831,10 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { String? get licensesPackageDetailTextMany => null; @override - String? get licensesPackageDetailTextOne => '1ଟି ଲାଇସେନ୍ସ'; + String? get licensesPackageDetailTextOne => '1 ଲାଇସେନ୍ସ'; @override - String get licensesPackageDetailTextOther => r'$licenseCountଟି ଲାଇସେନ୍ସ'; + String get licensesPackageDetailTextOther => r'$licenseCount ଲାଇସେନ୍ସ'; @override String? get licensesPackageDetailTextTwo => null; @@ -32864,37 +32864,37 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { String get nextMonthTooltip => 'ପରବର୍ତ୍ତୀ ମାସ'; @override - String get nextPageTooltip => 'ପରବର୍ତ୍ତୀ ପେଜ୍'; + String get nextPageTooltip => 'ପରବର୍ତ୍ତୀ ପୃଷ୍ଠା'; @override - String get okButtonLabel => 'ଠିକ୍ ଅଛି'; + String get okButtonLabel => 'ଠିକ ଅଛି'; @override - String get openAppDrawerTooltip => 'ନାଭିଗେସନ୍ ମେନୁ ଖୋଲନ୍ତୁ'; + String get openAppDrawerTooltip => 'ନାଭିଗେସନ ମେନୁ ଖୋଲନ୍ତୁ'; @override String get pageRowsInfoTitleRaw => r'$rowCountର $firstRow–$lastRow'; @override - String get pageRowsInfoTitleApproximateRaw => r'ପାଖାପାଖି $rowCountର $firstRow–$lastRow'; + String get pageRowsInfoTitleApproximateRaw => r'$rowCountର ପାଖାପାଖି $firstRow–$lastRow'; @override String get pasteButtonLabel => 'ପେଷ୍ଟ କରନ୍ତୁ'; @override - String get popupMenuLabel => 'ପପ୍-ଅପ୍ ମେନୁ'; + String get popupMenuLabel => 'ପପଅପ ମେନୁ'; @override String get postMeridiemAbbreviation => 'PM'; @override - String get previousMonthTooltip => 'ପୂର୍ବ ମାସ'; + String get previousMonthTooltip => 'ପୂର୍ବବର୍ତ୍ତୀ ମାସ'; @override - String get previousPageTooltip => 'ପୂର୍ବବର୍ତ୍ତୀ ପେଜ୍'; + String get previousPageTooltip => 'ପୂର୍ବବର୍ତ୍ତୀ ପୃଷ୍ଠା'; @override - String get refreshIndicatorSemanticLabel => 'ରିଫ୍ରେସ୍ କରନ୍ତୁ'; + String get refreshIndicatorSemanticLabel => 'ରିଫ୍ରେସ କରନ୍ତୁ'; @override String? get remainingTextFieldCharacterCountFew => null; @@ -32903,10 +32903,10 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { String? get remainingTextFieldCharacterCountMany => null; @override - String? get remainingTextFieldCharacterCountOne => '1ଟି ଅକ୍ଷର ବାକି ଅଛି'; + String? get remainingTextFieldCharacterCountOne => '1 କେରେକ୍ଟର ବାକି ଅଛି'; @override - String get remainingTextFieldCharacterCountOther => r'$remainingCountଟି ଅକ୍ଷର ବାକି ଅଛି'; + String get remainingTextFieldCharacterCountOther => r'$remainingCount କେରେକ୍ଟର ବାକି ଅଛି'; @override String? get remainingTextFieldCharacterCountTwo => null; @@ -32915,31 +32915,31 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { String? get remainingTextFieldCharacterCountZero => null; @override - String get reorderItemDown => 'ତଳକୁ ଯାଆନ୍ତୁ'; + String get reorderItemDown => 'ତଳକୁ ମୁଭ କରନ୍ତୁ'; @override - String get reorderItemLeft => 'ବାମକୁ ଯାଆନ୍ତୁ'; + String get reorderItemLeft => 'ବାମକୁ ମୁଭ କରନ୍ତୁ'; @override - String get reorderItemRight => 'ଡାହାଣକୁ ଯାଆନ୍ତୁ'; + String get reorderItemRight => 'ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ'; @override - String get reorderItemToEnd => 'ଶେଷକୁ ଯାଆନ୍ତୁ'; + String get reorderItemToEnd => 'ଶେଷକୁ ମୁଭ କରନ୍ତୁ'; @override - String get reorderItemToStart => 'ଆରମ୍ଭକୁ ଯାଆନ୍ତୁ'; + String get reorderItemToStart => 'ଆରମ୍ଭକୁ ମୁଭ କରନ୍ତୁ'; @override - String get reorderItemUp => 'ଉପରକୁ ନିଅନ୍ତୁ'; + String get reorderItemUp => 'ଉପରକୁ ମୁଭ କରନ୍ତୁ'; @override - String get rowsPerPageTitle => 'ପୃଷ୍ଠା ପିଛା ଧାଡ଼ି:'; + String get rowsPerPageTitle => 'ପ୍ରତି ପୃଷ୍ଠାରେ ଧାଡ଼ି:'; @override String get saveButtonLabel => 'ସେଭ କରନ୍ତୁ'; @override - String get scanTextButtonLabel => 'ଟେକ୍ସଟ୍ ସ୍କାନ୍ କରନ୍ତୁ'; + String get scanTextButtonLabel => 'ଟେକ୍ସଟ ସ୍କାନ କରନ୍ତୁ'; @override String get scrimLabel => 'ସ୍କ୍ରିମ'; @@ -32951,7 +32951,7 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { ScriptCategory get scriptCategory => ScriptCategory.tall; @override - String get searchFieldLabel => 'ସନ୍ଧାନ କରନ୍ତୁ'; + String get searchFieldLabel => 'ସର୍ଚ୍ଚ କରନ୍ତୁ'; @override String get searchWebButtonLabel => 'ୱେବ ସର୍ଚ୍ଚ କରନ୍ତୁ'; @@ -32972,10 +32972,10 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { String? get selectedRowCountTitleMany => null; @override - String? get selectedRowCountTitleOne => '1ଟି ଆଇଟମ୍ ଚୟନ କରାଯାଇଛି'; + String? get selectedRowCountTitleOne => '1 ଆଇଟମ ଚୟନ କରାଯାଇଛି'; @override - String get selectedRowCountTitleOther => r'$selectedRowCountଟି ଆଇଟମ୍ ଚୟନ କରାଯାଇଛି'; + String get selectedRowCountTitleOther => r'$selectedRowCountଟି ଆଇଟମ ଚୟନ କରାଯାଇଛି'; @override String? get selectedRowCountTitleTwo => null; @@ -32993,10 +32993,10 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { String get showMenuTooltip => 'ମେନୁ ଦେଖାନ୍ତୁ'; @override - String get signedInLabel => 'ସାଇନ୍ ଇନ୍ କରାଯାଇଛି'; + String get signedInLabel => 'ସାଇନ ଇନ କରାଯାଇଛି'; @override - String get tabLabelRaw => r'$tabCountର $tabIndex ଟାବ୍'; + String get tabLabelRaw => r'$tabCountର $tabIndex ଟାବ'; @override TimeOfDayFormat get timeOfDayFormatRaw => TimeOfDayFormat.H_colon_mm; @@ -33014,19 +33014,19 @@ class MaterialLocalizationOr extends GlobalMaterialLocalizations { String get timePickerInputHelpText => 'ସମୟ ଲେଖନ୍ତୁ'; @override - String get timePickerMinuteLabel => 'ମିନିଟ୍'; + String get timePickerMinuteLabel => 'ମିନିଟ'; @override - String get timePickerMinuteModeAnnouncement => 'ମିନିଟ୍ ଚୟନ କରନ୍ତୁ'; + String get timePickerMinuteModeAnnouncement => 'ମିନିଟ ଚୟନ କରନ୍ତୁ'; @override String get unspecifiedDate => 'ତାରିଖ'; @override - String get unspecifiedDateRange => 'ତାରିଖ ସୀମା'; + String get unspecifiedDateRange => 'ତାରିଖ ରେଞ୍ଜ'; @override - String get viewLicensesButtonLabel => 'ଲାଇସେନ୍ସ ଦେଖନ୍ତୁ'; + String get viewLicensesButtonLabel => 'ଲାଇସେନ୍ସ ଭ୍ୟୁ କରନ୍ତୁ'; } /// The translations for Panjabi Punjabi (`pa`). @@ -45808,7 +45808,7 @@ class MaterialLocalizationZhHant extends MaterialLocalizationZh { String get searchWebButtonLabel => '搜尋'; @override - String get selectAllButtonLabel => '全部選取'; + String get selectAllButtonLabel => '全選'; @override String get selectYearSemanticsLabel => '揀年份'; @@ -45983,9 +45983,6 @@ class MaterialLocalizationZhHantTw extends MaterialLocalizationZhHant { @override String get pageRowsInfoTitleApproximateRaw => r'第 $firstRow - $lastRow 列 (總共約 $rowCount 列)'; - @override - String get selectAllButtonLabel => '全選'; - @override String get timePickerHourModeAnnouncement => '選取小時數'; diff --git a/packages/flutter_localizations/lib/src/l10n/material_as.arb b/packages/flutter_localizations/lib/src/l10n/material_as.arb index 3131bbe538478..9e98139a1d445 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_as.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_as.arb @@ -13,13 +13,13 @@ "previousPageTooltip": "পূৰ্বৱৰ্তী পৃষ্ঠা", "showMenuTooltip": "মেনুখন দেখুৱাওক", "aboutListTileTitle": "$applicationNameৰ বিষয়ে", - "licensesPageTitle": "অনুজ্ঞাপত্ৰসমূহ", + "licensesPageTitle": "অনুজ্ঞাপত্ৰ", "pageRowsInfoTitle": "$rowCountৰ $firstRow–$lastRow", - "pageRowsInfoTitleApproximate": "$rowCountৰ $firstRow–$lastRow", + "pageRowsInfoTitleApproximate": "প্ৰায় $rowCountৰ $firstRow–$lastRow", "rowsPerPageTitle": "প্ৰতিটো পৃষ্ঠাত থকা শাৰী:", - "tabLabel": "$tabCountৰ $tabIndexটা টেব", - "selectedRowCountTitleOne": "১টা বস্তু বাছনি কৰা হ'ল", - "selectedRowCountTitleOther": "$selectedRowCountটা বস্তু বাছনি কৰা হ’ল", + "tabLabel": "$tabCount টাৰ ভিতৰত $tabIndex টা টেব", + "selectedRowCountTitleOne": "১ টা বস্তু বাছনি কৰা হ’ল", + "selectedRowCountTitleOther": "$selectedRowCount টা বস্তু বাছনি কৰা হ’ল", "cancelButtonLabel": "বাতিল কৰক", "closeButtonLabel": "বন্ধ কৰক", "continueButtonLabel": "অব্যাহত ৰাখক", @@ -28,31 +28,31 @@ "scanTextButtonLabel": "পাঠ স্কেন কৰক", "okButtonLabel": "ঠিক আছে", "pasteButtonLabel": "পে'ষ্ট কৰক", - "selectAllButtonLabel": "সকলো বাছনি কৰক", + "selectAllButtonLabel": "আটাইবোৰ বাছনি কৰক", "viewLicensesButtonLabel": "অনুজ্ঞাপত্ৰসমূহ চাওক", "anteMeridiemAbbreviation": "পূৰ্বাহ্ন", "postMeridiemAbbreviation": "অপৰাহ্ন", - "timePickerHourModeAnnouncement": "সময় বাছনি কৰক", + "timePickerHourModeAnnouncement": "ঘণ্টা বাছনি কৰক", "timePickerMinuteModeAnnouncement": "মিনিট বাছনি কৰক", "modalBarrierDismissLabel": "অগ্ৰাহ্য কৰক", "signedInLabel": "ছাইন ইন কৰা হ’ল", "hideAccountsLabel": "একাউণ্টসমূহ লুকুৱাওক", "showAccountsLabel": "একাউণ্টসমূহ দেখুৱাওক", "drawerLabel": "নেভিগেশ্বন মেনু", - "popupMenuLabel": "প'পআপ মেনু", - "dialogLabel": "ডায়ল'গ", + "popupMenuLabel": "পপআপ মেনু", + "dialogLabel": "ডায়লগ", "alertDialogLabel": "সতৰ্কবাৰ্তা", "searchFieldLabel": "সন্ধান কৰক", - "reorderItemToStart": "আৰম্ভণিলৈ স্থানান্তৰ কৰক", - "reorderItemToEnd": "শেষলৈ স্থানান্তৰ কৰক", + "reorderItemToStart": "আৰম্ভণিলৈ নিয়ক", + "reorderItemToEnd": "শেষলৈ নিয়ক", "reorderItemUp": "ওপৰলৈ নিয়ক", - "reorderItemDown": "তললৈ স্থানান্তৰ কৰক", + "reorderItemDown": "তললৈ নিয়ক", "reorderItemLeft": "বাওঁফাললৈ স্থানান্তৰ কৰক", - "reorderItemRight": "সোঁফাললৈ স্থানান্তৰ কৰক", + "reorderItemRight": "সোঁফাললৈ নিয়ক", "expandedIconTapHint": "সংকোচন কৰক", "collapsedIconTapHint": "বিস্তাৰ কৰক", - "remainingTextFieldCharacterCountOne": "১টা বর্ণ বাকী আছে", - "remainingTextFieldCharacterCountOther": "$remainingCountটা বর্ণ বাকী আছে", + "remainingTextFieldCharacterCountOne": "১ টা বর্ণসংখ্যা বাকী আছে", + "remainingTextFieldCharacterCountOther": "$remainingCount টা বর্ণসংখ্যা বাকী আছে", "refreshIndicatorSemanticLabel": "ৰিফ্ৰেশ্ব কৰক", "moreButtonTooltip": "অধিক", "dateSeparator": "/", @@ -60,14 +60,14 @@ "selectYearSemanticsLabel": "বছৰ বাছনি কৰক", "unspecifiedDate": "তাৰিখ", "unspecifiedDateRange": "তাৰিখৰ পৰিসৰ", - "dateInputLabel": "তাৰিখটো দিয়ক", + "dateInputLabel": "তাৰিখ দিয়ক", "dateRangeStartLabel": "আৰম্ভণিৰ তাৰিখ", "dateRangeEndLabel": "সমাপ্তিৰ তাৰিখ", "dateRangeStartDateSemanticLabel": "আৰম্ভণিৰ তাৰিখ $fullDate", "dateRangeEndDateSemanticLabel": "সমাপ্তিৰ তাৰিখ $fullDate", "invalidDateFormatLabel": "অমান্য ফৰ্মেট।", "invalidDateRangeLabel": "অমান্য পৰিসৰ।", - "dateOutOfRangeLabel": "সীমাৰ বাহিৰত।", + "dateOutOfRangeLabel": "পৰিসৰৰ বাহিৰত।", "saveButtonLabel": "ছেভ কৰক", "datePickerHelpText": "তাৰিখ বাছনি কৰক", "dateRangePickerHelpText": "পৰিসৰ বাছনি কৰক", @@ -85,48 +85,48 @@ "licensesPackageDetailTextOther": "$licenseCount খন অনুজ্ঞাপত্ৰ", "keyboardKeyAlt": "Alt", "keyboardKeyAltGraph": "AltGr", - "keyboardKeyBackspace": "বেকস্পেচ", - "keyboardKeyCapsLock": "কেপ্‌ছ লক", - "keyboardKeyChannelDown": "চেনেল ডাউন", - "keyboardKeyChannelUp": "চেনেল আপ", + "keyboardKeyBackspace": "Backspace", + "keyboardKeyCapsLock": "Caps Lock", + "keyboardKeyChannelDown": "Channel Down", + "keyboardKeyChannelUp": "Channel Up", "keyboardKeyControl": "Ctrl", "keyboardKeyDelete": "Del", - "keyboardKeyEject": "ইজেক্ট", + "keyboardKeyEject": "Eject", "keyboardKeyEnd": "End", "keyboardKeyEscape": "Esc", "keyboardKeyFn": "Fn", "keyboardKeyHome": "Home", "keyboardKeyInsert": "Insert", - "keyboardKeyMeta": "মেটা", + "keyboardKeyMeta": "Meta", "keyboardKeyNumLock": "Num Lock", - "keyboardKeyNumpad1": "নং ১", - "keyboardKeyNumpad2": "নং ২", - "keyboardKeyNumpad3": "নং ৩", - "keyboardKeyNumpad4": "নং ৪", - "keyboardKeyNumpad5": "নং ৫", - "keyboardKeyNumpad6": "নং ৬", - "keyboardKeyNumpad7": "নং ৭", - "keyboardKeyNumpad8": "নং ৮", - "keyboardKeyNumpad9": "নং ৯", - "keyboardKeyNumpad0": "নং ০", - "keyboardKeyNumpadAdd": "নং +", - "keyboardKeyNumpadComma": "নং ,", - "keyboardKeyNumpadDecimal": "নং .", - "keyboardKeyNumpadDivide": "নং /", + "keyboardKeyNumpad1": "Num 1", + "keyboardKeyNumpad2": "Num 2", + "keyboardKeyNumpad3": "Num 3", + "keyboardKeyNumpad4": "Num 4", + "keyboardKeyNumpad5": "Num 5", + "keyboardKeyNumpad6": "Num 6", + "keyboardKeyNumpad7": "Num 7", + "keyboardKeyNumpad8": "Num 8", + "keyboardKeyNumpad9": "Num 9", + "keyboardKeyNumpad0": "Num 0", + "keyboardKeyNumpadAdd": "Num +", + "keyboardKeyNumpadComma": "Num ,", + "keyboardKeyNumpadDecimal": "Num .", + "keyboardKeyNumpadDivide": "Num /", "keyboardKeyNumpadEnter": "Num Enter", - "keyboardKeyNumpadEqual": "নং =", - "keyboardKeyNumpadMultiply": "নং *", - "keyboardKeyNumpadParenLeft": "নং (", - "keyboardKeyNumpadParenRight": "নং )", - "keyboardKeyNumpadSubtract": "নং -", + "keyboardKeyNumpadEqual": "Num =", + "keyboardKeyNumpadMultiply": "Num *", + "keyboardKeyNumpadParenLeft": "Num (", + "keyboardKeyNumpadParenRight": "Num )", + "keyboardKeyNumpadSubtract": "Num -", "keyboardKeyPageDown": "PgDown", "keyboardKeyPageUp": "PgUp", - "keyboardKeyPower": "পাৱাৰ", - "keyboardKeyPowerOff": "পাৱাৰ অফ", - "keyboardKeyPrintScreen": "প্ৰিণ্ট স্ক্ৰীন", - "keyboardKeyScrollLock": "স্ক্ৰ’ল লক", - "keyboardKeySelect": "ছিলেক্ট", - "keyboardKeySpace": "স্পেচ", + "keyboardKeyPower": "Power", + "keyboardKeyPowerOff": "Power Off", + "keyboardKeyPrintScreen": "Print Screen", + "keyboardKeyScrollLock": "Scroll Lock", + "keyboardKeySelect": "Select", + "keyboardKeySpace": "Space", "keyboardKeyMetaMacOs": "Command", "keyboardKeyMetaWindows": "Win", "menuBarMenuLabel": "মেনু বাৰ মেনু", @@ -134,7 +134,7 @@ "scrimLabel": "স্ক্ৰিম", "bottomSheetLabel": "তলৰ শ্বীট", "scrimOnTapHint": "$modalRouteContentName বন্ধ কৰক", - "keyboardKeyShift": "শ্বিফ্ট", + "keyboardKeyShift": "Shift", "expansionTileExpandedHint": "সংকোচন কৰিবলৈ দুবাৰ টিপক", "expansionTileCollapsedHint": "বিস্তাৰ কৰিবলৈ দুবাৰ টিপক", "expansionTileExpandedTapHint": "সংকোচন কৰক", diff --git a/packages/flutter_localizations/lib/src/l10n/material_cy.arb b/packages/flutter_localizations/lib/src/l10n/material_cy.arb index 6103588a0d7b1..1f56c0c8c96fc 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_cy.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_cy.arb @@ -5,9 +5,9 @@ "licensesPackageDetailTextTwo": "$licenseCount drwydded", "licensesPackageDetailTextFew": "$licenseCount trwydded", "licensesPackageDetailTextMany": "$licenseCount thrwydded", - "selectedRowCountTitleTwo": "Mae $selectedRowCount eitem wedi'u dewis", - "selectedRowCountTitleFew": "Mae $selectedRowCount eitem wedi'u dewis", - "selectedRowCountTitleMany": "Mae $selectedRowCount eitem wedi'u dewis", + "selectedRowCountTitleTwo": "$selectedRowCount eitem wedi'u dewis", + "selectedRowCountTitleFew": "$selectedRowCount eitem wedi'u dewis", + "selectedRowCountTitleMany": "$selectedRowCount eitem wedi'u dewis", "remainingTextFieldCharacterCountFew": "$remainingCount nod ar ôl", "remainingTextFieldCharacterCountTwo": "$remainingCount nod ar ôl", "openAppDrawerTooltip": "Agor y ddewislen llywio", @@ -25,18 +25,18 @@ "scrimLabel": "Scrim", "bottomSheetLabel": "Taflen Gwaelod", "scrimOnTapHint": "Cau $modalRouteContentName", - "aboutListTileTitle": "Ynghylch $applicationName", + "aboutListTileTitle": "Ynglŷn â $applicationName", "licensesPageTitle": "Trwyddedau", "licensesPackageDetailTextZero": "Dim trwydded", - "licensesPackageDetailTextOne": "1 trwydded", + "licensesPackageDetailTextOne": "1 drwydded", "licensesPackageDetailTextOther": "$licenseCount trwydded", "pageRowsInfoTitle": "$firstRow–$lastRow o $rowCount", "pageRowsInfoTitleApproximate": "$firstRow–$lastRow o tua $rowCount", "rowsPerPageTitle": "Rhesi fesul tudalen:", "tabLabel": "Tab $tabIndex o $tabCount", "selectedRowCountTitleZero": "Nid oes unrhyw eitemau wedi'u dewis", - "selectedRowCountTitleOne": "Mae 1 eitem wedi'i dewis", - "selectedRowCountTitleOther": "Mae $selectedRowCount eitem wedi'u dewis", + "selectedRowCountTitleOne": "1 eitem wedi'i dewis", + "selectedRowCountTitleOther": "$selectedRowCount eitem wedi'u dewis", "cancelButtonLabel": "Canslo", "closeButtonLabel": "Cau", "continueButtonLabel": "Parhau", @@ -44,7 +44,7 @@ "cutButtonLabel": "Torri", "okButtonLabel": "Iawn", "pasteButtonLabel": "Gludo", - "selectAllButtonLabel": "Dewis y Cyfan", + "selectAllButtonLabel": "Dewis y cyfan", "viewLicensesButtonLabel": "Gweld trwyddedau", "anteMeridiemAbbreviation": "AM", "postMeridiemAbbreviation": "PM", @@ -107,7 +107,7 @@ "keyboardKeyControl": "Ctrl", "keyboardKeyDelete": "Del", "keyboardKeyEject": "Eject", - "keyboardKeyEnd": "Gorffen", + "keyboardKeyEnd": "End", "keyboardKeyEscape": "Esc", "keyboardKeyFn": "Fn", "keyboardKeyHome": "Home", diff --git a/packages/flutter_localizations/lib/src/l10n/material_da.arb b/packages/flutter_localizations/lib/src/l10n/material_da.arb index 7b6e8f9b6bb86..1c70dabf81cc3 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_da.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_da.arb @@ -34,7 +34,7 @@ "postMeridiemAbbreviation": "PM", "timePickerHourModeAnnouncement": "Vælg timer", "timePickerMinuteModeAnnouncement": "Vælg minutter", - "modalBarrierDismissLabel": "Afvis", + "modalBarrierDismissLabel": "Luk", "signedInLabel": "Logget ind", "hideAccountsLabel": "Skjul konti", "showAccountsLabel": "Vis konti", diff --git a/packages/flutter_localizations/lib/src/l10n/material_fa.arb b/packages/flutter_localizations/lib/src/l10n/material_fa.arb index 4e3ccaadb11d7..0683ff7d8bbc1 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_fa.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_fa.arb @@ -84,41 +84,41 @@ "licensesPackageDetailTextOne": "۱ پروانه", "licensesPackageDetailTextOther": "$licenseCount پروانه", "keyboardKeyAlt": "دگرساز", - "keyboardKeyAltGraph": "دگرساز راست", + "keyboardKeyAltGraph": "AltGr", "keyboardKeyBackspace": "پس‌بَر", "keyboardKeyCapsLock": "Caps Lock", "keyboardKeyChannelDown": "کانال پایین", "keyboardKeyChannelUp": "کانال بالا", - "keyboardKeyControl": "مهار", + "keyboardKeyControl": "Ctrl", "keyboardKeyDelete": "حذف", "keyboardKeyEject": "خارج کردن", "keyboardKeyEnd": "پایان", - "keyboardKeyEscape": "گریز", + "keyboardKeyEscape": "Esc", "keyboardKeyFn": "عملکرد", "keyboardKeyHome": "صفحه اصلی", - "keyboardKeyInsert": "درج", + "keyboardKeyInsert": "Insert", "keyboardKeyMeta": "متا", - "keyboardKeyNumLock": "قفل اعداد", - "keyboardKeyNumpad1": "عدد ۱", - "keyboardKeyNumpad2": "عدد ۲", - "keyboardKeyNumpad3": "عدد ۳", - "keyboardKeyNumpad4": "عدد ۴", - "keyboardKeyNumpad5": "عدد ۵", - "keyboardKeyNumpad6": "عدد ۶", - "keyboardKeyNumpad7": "عدد ۷", - "keyboardKeyNumpad8": "عدد ۸", - "keyboardKeyNumpad9": "عدد ۹", - "keyboardKeyNumpad0": "عدد ۰", - "keyboardKeyNumpadAdd": "عدد +", - "keyboardKeyNumpadComma": "عدد ,", - "keyboardKeyNumpadDecimal": "عدد .", - "keyboardKeyNumpadDivide": "عدد /", - "keyboardKeyNumpadEnter": "ورود اعداد", - "keyboardKeyNumpadEqual": "عدد =", - "keyboardKeyNumpadMultiply": "عدد *", - "keyboardKeyNumpadParenLeft": "عدد (", - "keyboardKeyNumpadParenRight": "عدد )", - "keyboardKeyNumpadSubtract": "عدد -", + "keyboardKeyNumLock": "Num Lock", + "keyboardKeyNumpad1": "Numpad 1", + "keyboardKeyNumpad2": "Numpad 2", + "keyboardKeyNumpad3": "Numpad 3", + "keyboardKeyNumpad4": "Numpad 4", + "keyboardKeyNumpad5": "Numpad 5", + "keyboardKeyNumpad6": "Numpad 6", + "keyboardKeyNumpad7": "Numpad 7", + "keyboardKeyNumpad8": "Numpad 8", + "keyboardKeyNumpad9": "Numpad 9", + "keyboardKeyNumpad0": "Numpad 0", + "keyboardKeyNumpadAdd": "Numpad +‎", + "keyboardKeyNumpadComma": "Numpad ,‎", + "keyboardKeyNumpadDecimal": "Numpad .‎", + "keyboardKeyNumpadDivide": "Numpad /‎", + "keyboardKeyNumpadEnter": "Numpad Enter", + "keyboardKeyNumpadEqual": "Numpad =‎", + "keyboardKeyNumpadMultiply": "Numpad *‎", + "keyboardKeyNumpadParenLeft": "Numpad (‎", + "keyboardKeyNumpadParenRight": "Numpad )‎", + "keyboardKeyNumpadSubtract": "Numpad -‎", "keyboardKeyPageDown": "صفحه پایین", "keyboardKeyPageUp": "صفحه بالا", "keyboardKeyPower": "روشن/ خاموش", @@ -127,14 +127,14 @@ "keyboardKeyScrollLock": "قفل پیمایش", "keyboardKeySelect": "انتخاب", "keyboardKeySpace": "فاصله", - "keyboardKeyMetaMacOs": "فرمان", + "keyboardKeyMetaMacOs": "Command", "keyboardKeyMetaWindows": "Win", "menuBarMenuLabel": "منوی نوار منو", "currentDateLabel": "امروز", "scrimLabel": "رویه", "bottomSheetLabel": "برگ زیرین", "scrimOnTapHint": "بستن $modalRouteContentName", - "keyboardKeyShift": "کلید تبدیل", + "keyboardKeyShift": "Shift", "expansionTileExpandedHint": "برای جمع کردن، دو تک‌ضرب بزنید", "expansionTileCollapsedHint": "برای ازهم بازکردن، دو تک‌ضرب بزنید", "expansionTileExpandedTapHint": "جمع کردن", diff --git a/packages/flutter_localizations/lib/src/l10n/material_gl.arb b/packages/flutter_localizations/lib/src/l10n/material_gl.arb index c2ccc0d197b84..a4d99cb53cbe7 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_gl.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_gl.arb @@ -38,12 +38,12 @@ "signedInLabel": "Sesión iniciada", "hideAccountsLabel": "Ocultar contas", "showAccountsLabel": "Mostrar contas", - "modalBarrierDismissLabel": "Ignorar", + "modalBarrierDismissLabel": "Pechar", "drawerLabel": "Menú de navegación", "popupMenuLabel": "Menú emerxente", "dialogLabel": "Cadro de diálogo", "alertDialogLabel": "Alerta", - "searchFieldLabel": "Buscar", + "searchFieldLabel": "Fai unha busca", "reorderItemToStart": "Mover ao inicio", "reorderItemToEnd": "Mover ao final", "reorderItemUp": "Mover cara arriba", @@ -79,8 +79,8 @@ "timePickerHourLabel": "Hora", "timePickerMinuteLabel": "Minuto", "invalidTimeLabel": "Escribe unha hora válida", - "dialModeButtonLabel": "Cambiar a modo de selector en esfera", - "inputTimeModeButtonLabel": "Cambiar ao modo de escritura dos números", + "dialModeButtonLabel": "Cambiar ao modo de selector en esfera", + "inputTimeModeButtonLabel": "Cambiar ao modo de introdución de texto", "licensesPackageDetailTextZero": "No licenses", "licensesPackageDetailTextOne": "1 licenza", "licensesPackageDetailTextOther": "$licenseCount licenzas", @@ -136,7 +136,7 @@ "bottomSheetLabel": "Panel inferior", "scrimOnTapHint": "Pechar $modalRouteContentName", "keyboardKeyShift": "Maiús", - "expansionTileExpandedHint": "toca dúas veces para contraer", + "expansionTileExpandedHint": "tocar dúas veces para contraer", "expansionTileCollapsedHint": "tocar dúas veces para despregar", "expansionTileExpandedTapHint": "Contraer", "expansionTileCollapsedTapHint": "Despregar para obter máis detalles", diff --git a/packages/flutter_localizations/lib/src/l10n/material_or.arb b/packages/flutter_localizations/lib/src/l10n/material_or.arb index 4ab3c025c9d7b..9576cac66b90c 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_or.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_or.arb @@ -1,88 +1,88 @@ { "scriptCategory": "tall", "timeOfDayFormat": "H:mm", - "openAppDrawerTooltip": "ନାଭିଗେସନ୍ ମେନୁ ଖୋଲନ୍ତୁ", + "openAppDrawerTooltip": "ନାଭିଗେସନ ମେନୁ ଖୋଲନ୍ତୁ", "backButtonTooltip": "ପଛକୁ ଫେରନ୍ତୁ", "closeButtonTooltip": "ବନ୍ଦ କରନ୍ତୁ", - "deleteButtonTooltip": "ଡିଲିଟ୍ କରନ୍ତୁ", + "deleteButtonTooltip": "ଡିଲିଟ କରନ୍ତୁ", "nextMonthTooltip": "ପରବର୍ତ୍ତୀ ମାସ", - "previousMonthTooltip": "ପୂର୍ବ ମାସ", - "nextPageTooltip": "ପରବର୍ତ୍ତୀ ପେଜ୍", - "previousPageTooltip": "ପୂର୍ବବର୍ତ୍ତୀ ପେଜ୍", + "previousMonthTooltip": "ପୂର୍ବବର୍ତ୍ତୀ ମାସ", + "nextPageTooltip": "ପରବର୍ତ୍ତୀ ପୃଷ୍ଠା", + "previousPageTooltip": "ପୂର୍ବବର୍ତ୍ତୀ ପୃଷ୍ଠା", "firstPageTooltip": "ପ୍ରଥମ ପୃଷ୍ଠା", "lastPageTooltip": "ଶେଷ ପୃଷ୍ଠା", "showMenuTooltip": "ମେନୁ ଦେଖାନ୍ତୁ", "aboutListTileTitle": "$applicationName ବିଷୟରେ", "licensesPageTitle": "ଲାଇସେନ୍ସ", "pageRowsInfoTitle": "$rowCountର $firstRow–$lastRow", - "pageRowsInfoTitleApproximate": "ପାଖାପାଖି $rowCountର $firstRow–$lastRow", - "rowsPerPageTitle": "ପୃଷ୍ଠା ପିଛା ଧାଡ଼ି:", - "tabLabel": "$tabCountର $tabIndex ଟାବ୍", - "selectedRowCountTitleOne": "1ଟି ଆଇଟମ୍ ଚୟନ କରାଯାଇଛି", - "selectedRowCountTitleOther": "$selectedRowCountଟି ଆଇଟମ୍ ଚୟନ କରାଯାଇଛି", + "pageRowsInfoTitleApproximate": "$rowCountର ପାଖାପାଖି $firstRow–$lastRow", + "rowsPerPageTitle": "ପ୍ରତି ପୃଷ୍ଠାରେ ଧାଡ଼ି:", + "tabLabel": "$tabCountର $tabIndex ଟାବ", + "selectedRowCountTitleOne": "1 ଆଇଟମ ଚୟନ କରାଯାଇଛି", + "selectedRowCountTitleOther": "$selectedRowCountଟି ଆଇଟମ ଚୟନ କରାଯାଇଛି", "cancelButtonLabel": "ବାତିଲ କରନ୍ତୁ", "closeButtonLabel": "ବନ୍ଦ କରନ୍ତୁ", "continueButtonLabel": "ଜାରି ରଖନ୍ତୁ", "copyButtonLabel": "କପି କରନ୍ତୁ", - "cutButtonLabel": "କଟ୍ କରନ୍ତୁ", - "scanTextButtonLabel": "ଟେକ୍ସଟ୍ ସ୍କାନ୍ କରନ୍ତୁ", - "okButtonLabel": "ଠିକ୍ ଅଛି", + "cutButtonLabel": "କଟ କରନ୍ତୁ", + "scanTextButtonLabel": "ଟେକ୍ସଟ ସ୍କାନ କରନ୍ତୁ", + "okButtonLabel": "ଠିକ ଅଛି", "pasteButtonLabel": "ପେଷ୍ଟ କରନ୍ତୁ", "selectAllButtonLabel": "ସବୁ ଚୟନ କରନ୍ତୁ", - "viewLicensesButtonLabel": "ଲାଇସେନ୍ସ ଦେଖନ୍ତୁ", + "viewLicensesButtonLabel": "ଲାଇସେନ୍ସ ଭ୍ୟୁ କରନ୍ତୁ", "anteMeridiemAbbreviation": "AM", "postMeridiemAbbreviation": "PM", "timePickerHourModeAnnouncement": "ଘଣ୍ଟା ଚୟନ କରନ୍ତୁ", - "timePickerMinuteModeAnnouncement": "ମିନିଟ୍ ଚୟନ କରନ୍ତୁ", + "timePickerMinuteModeAnnouncement": "ମିନିଟ ଚୟନ କରନ୍ତୁ", "modalBarrierDismissLabel": "ଖାରଜ କରନ୍ତୁ", - "signedInLabel": "ସାଇନ୍ ଇନ୍ କରାଯାଇଛି", + "signedInLabel": "ସାଇନ ଇନ କରାଯାଇଛି", "hideAccountsLabel": "ଆକାଉଣ୍ଟଗୁଡ଼ିକୁ ଲୁଚାନ୍ତୁ", "showAccountsLabel": "ଆକାଉଣ୍ଟ ଦେଖାନ୍ତୁ", - "drawerLabel": "ନେଭିଗେସନ୍ ମେନୁ", - "popupMenuLabel": "ପପ୍-ଅପ୍ ମେନୁ", - "dialogLabel": "ଡାୟଲଗ୍", + "drawerLabel": "ନାଭିଗେସନ ମେନୁ", + "popupMenuLabel": "ପପଅପ ମେନୁ", + "dialogLabel": "ଡାଏଲଗ", "alertDialogLabel": "ଆଲର୍ଟ", - "searchFieldLabel": "ସନ୍ଧାନ କରନ୍ତୁ", - "reorderItemToStart": "ଆରମ୍ଭକୁ ଯାଆନ୍ତୁ", - "reorderItemToEnd": "ଶେଷକୁ ଯାଆନ୍ତୁ", - "reorderItemUp": "ଉପରକୁ ନିଅନ୍ତୁ", - "reorderItemDown": "ତଳକୁ ଯାଆନ୍ତୁ", - "reorderItemLeft": "ବାମକୁ ଯାଆନ୍ତୁ", - "reorderItemRight": "ଡାହାଣକୁ ଯାଆନ୍ତୁ", + "searchFieldLabel": "ସର୍ଚ୍ଚ କରନ୍ତୁ", + "reorderItemToStart": "ଆରମ୍ଭକୁ ମୁଭ କରନ୍ତୁ", + "reorderItemToEnd": "ଶେଷକୁ ମୁଭ କରନ୍ତୁ", + "reorderItemUp": "ଉପରକୁ ମୁଭ କରନ୍ତୁ", + "reorderItemDown": "ତଳକୁ ମୁଭ କରନ୍ତୁ", + "reorderItemLeft": "ବାମକୁ ମୁଭ କରନ୍ତୁ", + "reorderItemRight": "ଡାହାଣକୁ ମୁଭ କରନ୍ତୁ", "expandedIconTapHint": "ସଙ୍କୁଚିତ କରନ୍ତୁ", - "collapsedIconTapHint": "ପ୍ରସାରିତ କରନ୍ତୁ", - "remainingTextFieldCharacterCountOne": "1ଟି ଅକ୍ଷର ବାକି ଅଛି", - "remainingTextFieldCharacterCountOther": "$remainingCountଟି ଅକ୍ଷର ବାକି ଅଛି", - "refreshIndicatorSemanticLabel": "ରିଫ୍ରେସ୍ କରନ୍ତୁ", + "collapsedIconTapHint": "ବିସ୍ତାର କରନ୍ତୁ", + "remainingTextFieldCharacterCountOne": "1 କେରେକ୍ଟର ବାକି ଅଛି", + "remainingTextFieldCharacterCountOther": "$remainingCount କେରେକ୍ଟର ବାକି ଅଛି", + "refreshIndicatorSemanticLabel": "ରିଫ୍ରେସ କରନ୍ତୁ", "moreButtonTooltip": "ଅଧିକ", "dateSeparator": "/", - "dateHelpText": "mm/dd/yyyy", + "dateHelpText": "dd/mm/yyyy", "selectYearSemanticsLabel": "ବର୍ଷ ଚୟନ କରନ୍ତୁ", "unspecifiedDate": "ତାରିଖ", - "unspecifiedDateRange": "ତାରିଖ ସୀମା", + "unspecifiedDateRange": "ତାରିଖ ରେଞ୍ଜ", "dateInputLabel": "ତାରିଖ ଲେଖନ୍ତୁ", "dateRangeStartLabel": "ଆରମ୍ଭ ତାରିଖ", "dateRangeEndLabel": "ଶେଷ ତାରିଖ", "dateRangeStartDateSemanticLabel": "ଆରମ୍ଭ ତାରିଖ $fullDate", "dateRangeEndDateSemanticLabel": "ଶେଷ ତାରିଖ $fullDate", - "invalidDateFormatLabel": "ଅବୈଧ ଫର୍ମାଟ୍।", - "invalidDateRangeLabel": "ଅବୈଧ ସୀମା।", - "dateOutOfRangeLabel": "ସୀମା ବାହାରେ।", + "invalidDateFormatLabel": "ଅବୈଧ ଫର୍ମାଟ।", + "invalidDateRangeLabel": "ଅବୈଧ ରେଞ୍ଜ।", + "dateOutOfRangeLabel": "ରେଞ୍ଜ ବାହାରେ।", "saveButtonLabel": "ସେଭ କରନ୍ତୁ", "datePickerHelpText": "ତାରିଖ ଚୟନ କରନ୍ତୁ", "dateRangePickerHelpText": "ରେଞ୍ଜ ଚୟନ କରନ୍ତୁ", - "calendarModeButtonLabel": "କ୍ୟାଲେଣ୍ଡରକୁ ସ୍ୱିଚ୍ କରନ୍ତୁ", - "inputDateModeButtonLabel": "ଇନପୁଟକୁ ସ୍ୱିଚ୍ କରନ୍ତୁ", + "calendarModeButtonLabel": "କେଲେଣ୍ଡରକୁ ସ୍ୱିଚ କରନ୍ତୁ", + "inputDateModeButtonLabel": "ଇନପୁଟକୁ ସ୍ୱିଚ କରନ୍ତୁ", "timePickerDialHelpText": "ସମୟ ଚୟନ କରନ୍ତୁ", "timePickerInputHelpText": "ସମୟ ଲେଖନ୍ତୁ", "timePickerHourLabel": "ଘଣ୍ଟା", - "timePickerMinuteLabel": "ମିନିଟ୍", + "timePickerMinuteLabel": "ମିନିଟ", "invalidTimeLabel": "ଏକ ବୈଧ ସମୟ ଲେଖନ୍ତୁ", - "dialModeButtonLabel": "ଡାଏଲ୍ ପିକର୍ ମୋଡକୁ ସ୍ୱିଚ୍ କରନ୍ତୁ", - "inputTimeModeButtonLabel": "ଟେକ୍ସଟ୍ ଇନପୁଟ୍ ମୋଡକୁ ସ୍ୱିଚ୍ କରନ୍ତୁ", + "dialModeButtonLabel": "ଡାଏଲ ପିକର ମୋଡକୁ ସ୍ୱିଚ କରନ୍ତୁ", + "inputTimeModeButtonLabel": "ଟେକ୍ସଟ ଇନପୁଟ ମୋଡକୁ ସ୍ୱିଚ କରନ୍ତୁ", "licensesPackageDetailTextZero": "No licenses", - "licensesPackageDetailTextOne": "1ଟି ଲାଇସେନ୍ସ", - "licensesPackageDetailTextOther": "$licenseCountଟି ଲାଇସେନ୍ସ", + "licensesPackageDetailTextOne": "1 ଲାଇସେନ୍ସ", + "licensesPackageDetailTextOther": "$licenseCount ଲାଇସେନ୍ସ", "keyboardKeyAlt": "Alt", "keyboardKeyAltGraph": "AltGr", "keyboardKeyBackspace": "Backspace", diff --git a/packages/flutter_localizations/lib/src/l10n/material_zh_HK.arb b/packages/flutter_localizations/lib/src/l10n/material_zh_HK.arb index 17b0a698ce542..44681d7ab408e 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_zh_HK.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_zh_HK.arb @@ -124,7 +124,7 @@ "cutButtonLabel": "剪下", "okButtonLabel": "確定", "pasteButtonLabel": "貼上", - "selectAllButtonLabel": "全部選取", + "selectAllButtonLabel": "全選", "viewLicensesButtonLabel": "查看授權", "anteMeridiemAbbreviation": "上午", "postMeridiemAbbreviation": "下午", From c9f10779f400edfc9664cd04a3c13cacdd3e007d Mon Sep 17 00:00:00 2001 From: Slava Egorov Date: Thu, 2 Oct 2025 20:01:21 +0200 Subject: [PATCH 044/204] Upgrade packages (#176411) Changes to the Dart IO require picking up newer version of `package:watcher`. --- packages/flutter_tools/pubspec.yaml | 4 ++-- pubspec.lock | 12 ++++++------ pubspec.yaml | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml index fb669482f8a79..2135c4f31d010 100644 --- a/packages/flutter_tools/pubspec.yaml +++ b/packages/flutter_tools/pubspec.yaml @@ -110,7 +110,7 @@ dependencies: term_glyph: 1.2.2 typed_data: 1.4.0 vm_service_interface: 2.0.1 - watcher: 1.1.3 + watcher: 1.1.4 web: 1.1.1 web_socket: 1.0.1 yaml_edit: 2.2.2 @@ -127,4 +127,4 @@ dev_dependencies: dartdoc: # Exclude this package from the hosted API docs. nodoc: true -# PUBSPEC CHECKSUM: 37t3vr +# PUBSPEC CHECKSUM: ho7nke diff --git a/pubspec.lock b/pubspec.lock index a31d0bd525ea0..abb0a9d12b623 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -510,10 +510,10 @@ packages: dependency: "direct main" description: name: native_toolchain_c - sha256: "7e8358a4f6ec69a4f2d366bb971af298aca50d6c2e8a07be7c12d7f6d40460aa" + sha256: f8872ea6c7a50ce08db9ae280ca2b8efdd973157ce462826c82f3c3051d154ce url: "https://pub.dev" source: hosted - version: "0.17.1" + version: "0.17.2" nested: dependency: "direct main" description: @@ -939,10 +939,10 @@ packages: dependency: "direct main" description: name: video_player_android - sha256: "59e5a457ddcc1688f39e9aef0efb62aa845cf0cbbac47e44ac9730dc079a2385" + sha256: "6cfe0b1e102522eda1e139b82bf00602181c5844fd2885340f595fb213d74842" url: "https://pub.dev" source: hosted - version: "2.8.13" + version: "2.8.14" video_player_avfoundation: dependency: "direct main" description: @@ -987,10 +987,10 @@ packages: dependency: "direct main" description: name: watcher - sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" + sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.1.4" web: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c4b523044cf0d..d3253b25ca662 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -133,7 +133,7 @@ dependencies: meta: 1.17.0 metrics_center: 1.0.13 mime: 2.0.0 - native_toolchain_c: 0.17.1 + native_toolchain_c: 0.17.2 nested: 1.0.0 node_preamble: 2.0.2 package_config: 2.2.0 @@ -184,13 +184,13 @@ dependencies: url_launcher_windows: 3.1.4 vector_math: 2.2.0 video_player: 2.10.0 - video_player_android: 2.8.13 + video_player_android: 2.8.14 video_player_avfoundation: 2.8.4 video_player_platform_interface: 6.4.0 video_player_web: 2.4.0 vm_service: 15.0.2 vm_snapshot_analysis: 0.7.6 - watcher: 1.1.3 + watcher: 1.1.4 web: 1.1.1 web_socket: 1.0.1 web_socket_channel: 3.0.3 @@ -212,4 +212,4 @@ dependencies: pedantic: 1.11.1 quiver: 3.2.2 yaml_edit: 2.2.2 -# PUBSPEC CHECKSUM: 9hqic +# PUBSPEC CHECKSUM: pb0r15 From 76776081c6c2f8d4a31eff5d6135e3883c685d57 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Thu, 2 Oct 2025 15:22:25 -0500 Subject: [PATCH 045/204] Add deeplinking for UIScene migration (#176303) When an iOS app migrates to UIScene, the application events for handling deeplinks are no longer called by UIKit. Instead, scene events in the SceneDelegate must be used. This PR adds support for handling deeplinks in UIScene while still maintaining support for unmigrated apps, as well. Fixes https://github.com/flutter/flutter/issues/174403. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../framework/Headers/FlutterSceneLifeCycle.h | 16 +- .../framework/Source/FlutterAppDelegate.mm | 27 +- .../Source/FlutterAppDelegateTest.mm | 5 +- .../ios/framework/Source/FlutterEngine.mm | 29 ++ .../ios/framework/Source/FlutterEngineTest.mm | 57 +++ .../framework/Source/FlutterEngine_Internal.h | 2 + .../framework/Source/FlutterSceneLifeCycle.mm | 164 +++++--- .../Source/FlutterSceneLifeCycleTest.mm | 356 ++++++++++++++++-- .../Source/FlutterSharedApplication.h | 2 + .../Source/FlutterSharedApplication.mm | 9 + .../Source/FlutterSharedApplicationTest.mm | 32 ++ .../framework/Source/FlutterViewController.mm | 30 -- .../Source/FlutterViewController_Internal.h | 1 - 13 files changed, 590 insertions(+), 140 deletions(-) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneLifeCycle.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneLifeCycle.h index 4125460937bdc..d83637594ef16 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneLifeCycle.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneLifeCycle.h @@ -112,10 +112,8 @@ API_AVAILABLE(ios(13.0)) /** * Calls all plugins registered for `UIWindowScene` callbacks in order of registration until * a plugin handles the request. - * - * @return `YES` if any plugin handles the request. */ -- (BOOL)scene:(UIScene*)scene +- (void)scene:(UIScene*)scene willConnectToSession:(UISceneSession*)session options:(UISceneConnectionOptions*)connectionOptions; @@ -138,30 +136,24 @@ API_AVAILABLE(ios(13.0)) /** * Calls all plugins registered for `UIWindowScene` callbacks in order of registration until * a plugin handles the request. - * - * @return `YES` if any plugin handles the request. */ -- (BOOL)scene:(UIScene*)scene openURLContexts:(NSSet*)URLContexts; +- (void)scene:(UIScene*)scene openURLContexts:(NSSet*)URLContexts; #pragma mark - Continuing user activities /** * Calls all plugins registered for `UIWindowScene` callbacks in order of registration until * a plugin handles the request. - * - * @return `YES` if any plugin handles the request. */ -- (BOOL)scene:(UIScene*)scene continueUserActivity:(NSUserActivity*)userActivity; +- (void)scene:(UIScene*)scene continueUserActivity:(NSUserActivity*)userActivity; #pragma mark - Performing tasks /** * Calls all plugins registered for `UIWindowScene` callbacks in order of registration until * a plugin handles the request. - * - * @return `YES` if any plugin handles the request. */ -- (BOOL)windowScene:(UIWindowScene*)windowScene +- (void)windowScene:(UIWindowScene*)windowScene performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm index 03e5e7ac5b1c9..f8d5817bcea99 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm @@ -142,13 +142,6 @@ - (void)userNotificationCenter:(UNUserNotificationCenter*)center } } -- (BOOL)isFlutterDeepLinkingEnabled { - NSNumber* isDeepLinkingEnabled = - [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"]; - // if not set, return YES - return isDeepLinkingEnabled ? [isDeepLinkingEnabled boolValue] : YES; -} - // This method is called when opening an URL with custom schemes. - (BOOL)application:(UIApplication*)application openURL:(NSURL*)url @@ -169,21 +162,21 @@ - (BOOL)handleOpenURL:(NSURL*)url if (flutterApplication == nil) { return NO; } - if (![self isFlutterDeepLinkingEnabled]) { + if (!FlutterSharedApplication.isFlutterDeepLinkingEnabled) { return NO; } FlutterViewController* flutterViewController = [self rootFlutterViewController]; if (flutterViewController) { - [flutterViewController sendDeepLinkToFramework:url - completionHandler:^(BOOL success) { - if (!success && throwBack) { - // throw it back to iOS - [flutterApplication openURL:url - options:@{} - completionHandler:nil]; - } - }]; + [flutterViewController.engine sendDeepLinkToFramework:url + completionHandler:^(BOOL success) { + if (!success && throwBack) { + // throw it back to iOS + [flutterApplication openURL:url + options:@{} + completionHandler:nil]; + } + }]; } else { [FlutterLogger logError:@"Attempting to open an URL without a Flutter RootViewController."]; return NO; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm index 7088b18f35bd4..35da19d55123b 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegateTest.mm @@ -10,6 +10,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Test.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h" FLUTTER_ASSERT_ARC @@ -19,6 +20,7 @@ @interface FlutterAppDelegateTest : XCTestCase @property(strong) FlutterViewController* viewController; @property(strong) id mockMainBundle; @property(strong) id mockNavigationChannel; +@property(strong) FlutterEngine* engine; // Retain callback until the tests are done. // https://github.com/flutter/flutter/issues/74267 @@ -46,6 +48,7 @@ - (void)setUp { FlutterEngine* engine = OCMClassMock([FlutterEngine class]); OCMStub([engine navigationChannel]).andReturn(navigationChannel); OCMStub([viewController engine]).andReturn(engine); + self.engine = engine; id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@NO, nil]; self.mockEngineFirstFrameCallback = mockEngineFirstFrameCallback; @@ -194,7 +197,7 @@ - (void)testUseNonDeprecatedOpenURLAPI { .andReturn(@YES); NSUserActivity* userActivity = [[NSUserActivity alloc] initWithActivityType:@"com.example.test"]; userActivity.webpageURL = [NSURL URLWithString:@"http://myApp/custom/route?query=nonexist"]; - OCMStub([self.viewController sendDeepLinkToFramework:[OCMArg any] completionHandler:[OCMArg any]]) + OCMStub([self.engine sendDeepLinkToFramework:[OCMArg any] completionHandler:[OCMArg any]]) .andDo(^(NSInvocation* invocation) { void (^handler)(BOOL success); [invocation getArgument:&handler atIndex:3]; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index a23d3b0f32d05..afe9c0baae78a 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -1528,6 +1528,35 @@ - (FlutterDartProject*)project { return self.dartProject; } +- (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(void (^)(BOOL success))completion { + __weak FlutterEngine* weakSelf = self; + [self waitForFirstFrame:3.0 + callback:^(BOOL didTimeout) { + if (didTimeout) { + [FlutterLogger + logError:@"Timeout waiting for first frame when launching a URL."]; + completion(NO); + } else { + // invove the method and get the result + [weakSelf.navigationChannel + invokeMethod:@"pushRouteInformation" + arguments:@{ + @"location" : url.absoluteString ?: [NSNull null], + } + result:^(id _Nullable result) { + BOOL success = + [result isKindOfClass:[NSNumber class]] && [result boolValue]; + if (!success) { + // Logging the error if the result is not successful + [FlutterLogger + logError:@"Failed to handle route information in Flutter."]; + } + completion(success); + }]; + } + }]; +} + @end @implementation FlutterEngineRegistrar { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm index d7a3f829d635c..48f84e3b8533d 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm @@ -584,4 +584,61 @@ - (void)testAddSceneDelegateToRegistrar { OCMVerify(times(1), [mockEngine addSceneLifeCycleDelegate:[OCMArg any]]); } +- (void)testSendDeepLinkToFrameworkTimesOut { + FlutterDartProject* project = [[FlutterDartProject alloc] init]; + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project]; + id mockEngine = OCMPartialMock(engine); + id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@YES, nil]; + OCMStub([mockEngine waitForFirstFrame:3.0 callback:mockEngineFirstFrameCallback]); + + NSURL* url = [NSURL URLWithString:@"example.com"]; + + [mockEngine sendDeepLinkToFramework:url + completionHandler:^(BOOL success) { + XCTAssertFalse(success); + }]; +} + +- (void)testSendDeepLinkToFrameworkUsingNavigationChannel { + NSString* urlString = @"example.com"; + NSURL* url = [NSURL URLWithString:urlString]; + FlutterDartProject* project = [[FlutterDartProject alloc] init]; + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project]; + id mockEngine = OCMPartialMock(engine); + id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@NO, nil]; + OCMStub([mockEngine waitForFirstFrame:3.0 callback:mockEngineFirstFrameCallback]); + id mockNavigationChannel = OCMClassMock([FlutterMethodChannel class]); + OCMStub([mockEngine navigationChannel]).andReturn(mockNavigationChannel); + id mockNavigationChannelCallback = [OCMArg invokeBlockWithArgs:@1, nil]; + OCMStub([mockNavigationChannel invokeMethod:@"pushRouteInformation" + arguments:@{@"location" : urlString} + result:mockNavigationChannelCallback]); + + [mockEngine sendDeepLinkToFramework:url + completionHandler:^(BOOL success) { + XCTAssertTrue(success); + }]; +} + +- (void)testSendDeepLinkToFrameworkUsingNavigationChannelFails { + NSString* urlString = @"example.com"; + NSURL* url = [NSURL URLWithString:urlString]; + FlutterDartProject* project = [[FlutterDartProject alloc] init]; + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project]; + id mockEngine = OCMPartialMock(engine); + id mockEngineFirstFrameCallback = [OCMArg invokeBlockWithArgs:@NO, nil]; + OCMStub([mockEngine waitForFirstFrame:3.0 callback:mockEngineFirstFrameCallback]); + id mockNavigationChannel = OCMClassMock([FlutterMethodChannel class]); + OCMStub([mockEngine navigationChannel]).andReturn(mockNavigationChannel); + id mockNavigationChannelCallback = [OCMArg invokeBlockWithArgs:@0, nil]; + OCMStub([mockNavigationChannel invokeMethod:@"pushRouteInformation" + arguments:@{@"location" : urlString} + result:mockNavigationChannelCallback]); + + [mockEngine sendDeepLinkToFramework:url + completionHandler:^(BOOL success) { + XCTAssertFalse(success); + }]; +} + @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h index 25edc1e796600..fddb2af72bbec 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h @@ -107,6 +107,8 @@ NS_ASSUME_NONNULL_BEGIN - (void)addSceneLifeCycleDelegate:(NSObject*)delegate; +- (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(void (^)(BOOL success))completion; + @end NS_ASSUME_NONNULL_END diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm index f9bc82e39a8c4..0b17e5a24b5f5 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm @@ -117,28 +117,36 @@ - (void)engine:(FlutterEngine*)engine receivedConnectNotificationFor:(UIScene*)s // `scene:willConnectToSession:options:` event. In which case, we can wait for the actual event. [self addFlutterEngine:engine]; if (self.connectionOptions != nil) { - [self scene:scene willConnectToSession:scene.session options:self.connectionOptions]; + [self scene:scene + willConnectToSession:scene.session + flutterEngine:engine + options:self.connectionOptions]; } } -- (BOOL)scene:(UIScene*)scene +- (void)scene:(UIScene*)scene willConnectToSession:(UISceneSession*)session options:(UISceneConnectionOptions*)connectionOptions { self.connectionOptions = connectionOptions; [self updateEnginesInScene:scene]; - BOOL consumedByPlugin = NO; for (FlutterEngine* engine in _engines.allObjects) { - BOOL result = [engine.sceneLifeCycleDelegate scene:scene - willConnectToSession:session - options:connectionOptions]; - if (result) { - consumedByPlugin = YES; - } + [self scene:scene willConnectToSession:session flutterEngine:engine options:connectionOptions]; + } +} + +- (void)scene:(UIScene*)scene + willConnectToSession:(UISceneSession*)session + flutterEngine:(FlutterEngine*)engine + options:(UISceneConnectionOptions*)connectionOptions { + BOOL handledByPlugin = [engine.sceneLifeCycleDelegate scene:scene + willConnectToSession:session + options:connectionOptions]; + if (!handledByPlugin) { + // Only process deeplinks if a plugin has not already done something to handle this event. + [self handleDeeplinkingForEngine:engine options:connectionOptions]; } - return consumedByPlugin; - // There is no application equivalent for this event and therefore no fallback. } - (void)sceneDidDisconnect:(UIScene*)scene { @@ -187,70 +195,138 @@ - (void)sceneDidEnterBackground:(UIScene*)scene { #pragma mark - Opening URLs -- (BOOL)scene:(UIScene*)scene openURLContexts:(NSSet*)URLContexts { +- (void)scene:(UIScene*)scene openURLContexts:(NSSet*)URLContexts { [self updateEnginesInScene:scene]; - BOOL consumedByPlugin = NO; + + // Track engines that had this event handled by a plugin. + NSMutableSet* enginesHandledByPlugin = [NSMutableSet set]; for (FlutterEngine* engine in _engines.allObjects) { - BOOL result = [engine.sceneLifeCycleDelegate scene:scene openURLContexts:URLContexts]; - if (result) { - consumedByPlugin = YES; + if ([engine.sceneLifeCycleDelegate scene:scene openURLContexts:URLContexts]) { + [enginesHandledByPlugin addObject:engine]; } } - if (!consumedByPlugin) { - BOOL result = [[self applicationLifeCycleDelegate] sceneFallbackOpenURLContexts:URLContexts]; - if (result) { - consumedByPlugin = YES; + + // If no plugins handled this, give the application fallback a chance to handle it. + if (enginesHandledByPlugin.count == 0) { + if ([[self applicationLifeCycleDelegate] sceneFallbackOpenURLContexts:URLContexts]) { + // If the application fallback handles it, don't do any deeplinking. + return; + } + } + + // For any engine that was not handled by a plugin, do deeplinking. + for (FlutterEngine* engine in _engines.allObjects) { + if ([enginesHandledByPlugin containsObject:engine]) { + continue; + } + for (UIOpenURLContext* urlContext in URLContexts) { + if ([self handleDeeplink:urlContext.URL flutterEngine:engine relayToSystemIfUnhandled:NO]) { + break; + } } } - return consumedByPlugin; } #pragma mark - Continuing user activities -- (BOOL)scene:(UIScene*)scene continueUserActivity:(NSUserActivity*)userActivity { +- (void)scene:(UIScene*)scene continueUserActivity:(NSUserActivity*)userActivity { [self updateEnginesInScene:scene]; - BOOL consumedByPlugin = NO; + + // Track engines that had this event handled by a plugin. + NSMutableSet* enginesHandledByPlugin = [NSMutableSet set]; for (FlutterEngine* engine in _engines.allObjects) { - BOOL result = [engine.sceneLifeCycleDelegate scene:scene continueUserActivity:userActivity]; - if (result) { - consumedByPlugin = YES; + if ([engine.sceneLifeCycleDelegate scene:scene continueUserActivity:userActivity]) { + [enginesHandledByPlugin addObject:engine]; } } - if (!consumedByPlugin) { - BOOL result = - [[self applicationLifeCycleDelegate] sceneFallbackContinueUserActivity:userActivity]; - if (result) { - consumedByPlugin = YES; + + // If no plugins handled this, give the application fallback a chance to handle it. + if (enginesHandledByPlugin.count == 0) { + if ([[self applicationLifeCycleDelegate] sceneFallbackContinueUserActivity:userActivity]) { + // If the application fallback handles it, don't do any deeplinking. + return; } } - return consumedByPlugin; + + // For any engine that was not handled by a plugin, do deeplinking. + for (FlutterEngine* engine in _engines.allObjects) { + if ([enginesHandledByPlugin containsObject:engine]) { + continue; + } + [self handleDeeplink:userActivity.webpageURL flutterEngine:engine relayToSystemIfUnhandled:YES]; + } } #pragma mark - Performing tasks -- (BOOL)windowScene:(UIWindowScene*)windowScene +- (void)windowScene:(UIWindowScene*)windowScene performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler { [self updateEnginesInScene:windowScene]; - BOOL consumedByPlugin = NO; + BOOL handledByPlugin = NO; for (FlutterEngine* engine in _engines.allObjects) { BOOL result = [engine.sceneLifeCycleDelegate windowScene:windowScene performActionForShortcutItem:shortcutItem completionHandler:completionHandler]; if (result) { - consumedByPlugin = YES; + handledByPlugin = YES; } } - if (!consumedByPlugin) { - BOOL result = [[self applicationLifeCycleDelegate] + if (!handledByPlugin) { + [[self applicationLifeCycleDelegate] sceneFallbackPerformActionForShortcutItem:shortcutItem completionHandler:completionHandler]; - if (result) { - consumedByPlugin = YES; + } +} + +#pragma mark - Helpers + +- (void)handleDeeplinkingForEngine:(FlutterEngine*)engine + options:(UISceneConnectionOptions*)connectionOptions { + // If your app has opted into Scenes, and your app is not running, the system delivers the + // universal link to the scene(_:willConnectTo:options:) delegate method after launch, and to + // scene(_:continue:) when the universal link is tapped while your app is running or suspended in + // memory. + for (NSUserActivity* userActivity in connectionOptions.userActivities) { + if ([self handleDeeplink:userActivity.webpageURL + flutterEngine:engine + relayToSystemIfUnhandled:YES]) { + return; + } + } + + // If your app has opted into Scenes, and your app isn’t running, the system delivers the URL to + // the scene:willConnectToSession:options: delegate method after launch, and to + // scene:openURLContexts: when your app opens a URL while running or suspended in memory. + for (UIOpenURLContext* urlContext in connectionOptions.URLContexts) { + if ([self handleDeeplink:urlContext.URL flutterEngine:engine relayToSystemIfUnhandled:YES]) { + return; } } - return consumedByPlugin; +} + +- (BOOL)handleDeeplink:(NSURL*)url + flutterEngine:(FlutterEngine*)engine + relayToSystemIfUnhandled:(BOOL)throwBack { + if (!url) { + return NO; + } + // Don't process the link if deep linking is disabled. + if (!FlutterSharedApplication.isFlutterDeepLinkingEnabled) { + return NO; + } + // if deep linking is enabled, send it to the framework + [engine sendDeepLinkToFramework:url + completionHandler:^(BOOL success) { + if (!success && throwBack) { + // throw it back to iOS + [FlutterSharedApplication.application openURL:url + options:@{} + completionHandler:nil]; + } + }]; + return YES; } + (FlutterPluginSceneLifeCycleDelegate*)fromScene:(UIScene*)scene { @@ -304,19 +380,19 @@ - (void)addDelegate:(NSObject*)delegate { - (BOOL)scene:(UIScene*)scene willConnectToSession:(UISceneSession*)session options:(UISceneConnectionOptions*)connectionOptions { - BOOL consumedByPlugin = NO; + BOOL handledByPlugin = NO; for (NSObject* delegate in _delegates.allObjects) { if ([delegate respondsToSelector:_cmd]) { // If this event has already been consumed by a plugin, send the event with nil options. // Only allow one plugin to process the connection options. if ([delegate scene:scene willConnectToSession:session - options:(consumedByPlugin ? nil : connectionOptions)]) { - consumedByPlugin = YES; + options:(handledByPlugin ? nil : connectionOptions)]) { + handledByPlugin = YES; } } } - return consumedByPlugin; + return handledByPlugin; } - (void)sceneDidDisconnect:(UIScene*)scene { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm index 4c4ffb7762295..f875079616cc6 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm @@ -7,6 +7,7 @@ #import #include +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Test.h" @@ -181,10 +182,21 @@ - (void)testEngineReceivedConnectNotificationForSceneBeforeActualEvent { options:[OCMArg any]]) .andReturn(YES); + id mocks2 = [self mocksForEvents]; + id mockEngine2 = mocks2[@"mockEngine"]; + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate2 = + (FlutterEnginePluginSceneLifeCycleDelegate*)mocks2[@"mockLifecycleDelegate"]; + OCMStub([mockLifecycleDelegate2 scene:[OCMArg any] + willConnectToSession:[OCMArg any] + options:[OCMArg any]]) + .andReturn(YES); + // received notification [mockDelegate engine:mockEngine receivedConnectNotificationFor:mockScene]; + [mockDelegate engine:mockEngine2 receivedConnectNotificationFor:mockScene]; OCMVerify(times(1), [mockDelegate addFlutterEngine:mockEngine]); - XCTAssertEqual(delegate.engines.count, 1.0); + OCMVerify(times(1), [mockDelegate addFlutterEngine:mockEngine2]); + XCTAssertEqual(delegate.engines.count, 2.0); OCMVerify(times(0), [mockDelegate scene:[OCMArg any] willConnectToSession:[OCMArg any] options:[OCMArg any]]); @@ -196,7 +208,10 @@ - (void)testEngineReceivedConnectNotificationForSceneBeforeActualEvent { OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene willConnectToSession:session options:options]); - XCTAssertEqual(delegate.engines.count, 1.0); + OCMVerify(times(1), [mockLifecycleDelegate2 scene:mockScene + willConnectToSession:session + options:options]); + XCTAssertEqual(delegate.engines.count, 2.0); } - (void)testEngineReceivedConnectNotificationForSceneAfterActualEvent { @@ -212,6 +227,14 @@ - (void)testEngineReceivedConnectNotificationForSceneAfterActualEvent { willConnectToSession:[OCMArg any] options:[OCMArg any]]) .andReturn(YES); + id mocks2 = [self mocksForEvents]; + id mockEngine2 = mocks2[@"mockEngine"]; + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate2 = + (FlutterEnginePluginSceneLifeCycleDelegate*)mocks2[@"mockLifecycleDelegate"]; + OCMStub([mockLifecycleDelegate2 scene:[OCMArg any] + willConnectToSession:[OCMArg any] + options:[OCMArg any]]) + .andReturn(YES); // actual event id session = OCMClassMock([UISceneSession class]); @@ -222,22 +245,31 @@ - (void)testEngineReceivedConnectNotificationForSceneAfterActualEvent { OCMVerify(times(0), [mockLifecycleDelegate scene:mockScene willConnectToSession:session options:options]); + OCMVerify(times(0), [mockLifecycleDelegate2 scene:mockScene + willConnectToSession:session + options:options]); OCMStub([mockDelegate connectionOptions]).andReturn(options); // received notification [mockDelegate engine:mockEngine receivedConnectNotificationFor:mockScene]; + [mockDelegate engine:mockEngine2 receivedConnectNotificationFor:mockScene]; + OCMVerify(times(1), [mockDelegate addFlutterEngine:mockEngine]); - XCTAssertEqual(delegate.engines.count, 1.0); - OCMVerify(times(2), [mockDelegate scene:mockScene + OCMVerify(times(1), [mockDelegate addFlutterEngine:mockEngine2]); + XCTAssertEqual(delegate.engines.count, 2.0); + OCMVerify(times(1), [mockDelegate scene:mockScene willConnectToSession:session options:options]); // This is called twice because once is // within the test itself. OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene willConnectToSession:session options:options]); + OCMVerify(times(1), [mockLifecycleDelegate2 scene:mockScene + willConnectToSession:session + options:options]); } -- (void)testSceneWillConnectToSessionOptionsConsumedByScenePlugin { +- (void)testSceneWillConnectToSessionOptionsHandledByScenePlugin { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -257,13 +289,13 @@ - (void)testSceneWillConnectToSessionOptionsConsumedByScenePlugin { [delegate addFlutterEngine:mockEngine]; XCTAssertEqual(delegate.engines.count, 1.0); - XCTAssertTrue([delegate scene:mockScene willConnectToSession:session options:options]); + [delegate scene:mockScene willConnectToSession:session options:options]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene willConnectToSession:session options:options]); } -- (void)testSceneWillConnectToSessionOptionsConsumedByNoPlugin { +- (void)testSceneWillConnectToSessionOptionsHandledByUniversalLinks { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -279,14 +311,81 @@ - (void)testSceneWillConnectToSessionOptionsConsumedByNoPlugin { id session = OCMClassMock([UISceneSession class]); id options = OCMClassMock([UISceneConnectionOptions class]); + id userActivity = OCMClassMock([NSUserActivity class]); + id flutterApp = OCMClassMock([FlutterSharedApplication class]); + NSURL* url = [NSURL URLWithString:@"example.com"]; + OCMStub([userActivity webpageURL]).andReturn(url); + NSSet* userActivities = [NSSet setWithObjects:userActivity, nil]; + OCMStub([options userActivities]).andReturn(userActivities); + OCMStub([flutterApp isFlutterDeepLinkingEnabled]).andReturn(YES); [delegate addFlutterEngine:mockEngine]; XCTAssertEqual(delegate.engines.count, 1.0); - XCTAssertFalse([delegate scene:mockScene willConnectToSession:session options:options]); - OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene - willConnectToSession:session - options:options]); + [delegate scene:mockScene willConnectToSession:session options:options]; + OCMVerify(times(1), [mockEngine sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); +} + +- (void)testSceneWillConnectToSessionOptionsHandledByDeepLinks { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mocks = [self mocksForEvents]; + id mockEngine = mocks[@"mockEngine"]; + id mockScene = mocks[@"mockScene"]; + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate = + (FlutterEnginePluginSceneLifeCycleDelegate*)mocks[@"mockLifecycleDelegate"]; + OCMStub([mockLifecycleDelegate scene:[OCMArg any] + willConnectToSession:[OCMArg any] + options:[OCMArg any]]) + .andReturn(NO); + + id session = OCMClassMock([UISceneSession class]); + id options = OCMClassMock([UISceneConnectionOptions class]); + id flutterApp = OCMClassMock([FlutterSharedApplication class]); + NSURL* url = [NSURL URLWithString:@"example.com"]; + OCMStub([flutterApp isFlutterDeepLinkingEnabled]).andReturn(YES); + id urlContext = OCMClassMock([UIOpenURLContext class]); + OCMStub([urlContext URL]).andReturn(url); + NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; + OCMStub([options URLContexts]).andReturn(urlContexts); + + [delegate addFlutterEngine:mockEngine]; + XCTAssertEqual(delegate.engines.count, 1.0); + + [delegate scene:mockScene willConnectToSession:session options:options]; + OCMVerify(times(1), [mockEngine sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); +} + +- (void)testSceneWillConnectToSessionOptionsHandledByNoPlugin { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mocks = [self mocksForEvents]; + id mockEngine = mocks[@"mockEngine"]; + id mockScene = mocks[@"mockScene"]; + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate = + (FlutterEnginePluginSceneLifeCycleDelegate*)mocks[@"mockLifecycleDelegate"]; + OCMStub([mockLifecycleDelegate scene:[OCMArg any] + willConnectToSession:[OCMArg any] + options:[OCMArg any]]) + .andReturn(NO); + + id session = OCMClassMock([UISceneSession class]); + id options = OCMClassMock([UISceneConnectionOptions class]); + id userActivity = OCMClassMock([NSUserActivity class]); + id flutterApp = OCMClassMock([FlutterSharedApplication class]); + NSURL* url = [NSURL URLWithString:@"example.com"]; + OCMStub([userActivity webpageURL]).andReturn(url); + NSSet* userActivities = [NSSet setWithObjects:userActivity, nil]; + OCMStub([options userActivities]).andReturn(userActivities); + OCMStub([flutterApp isFlutterDeepLinkingEnabled]).andReturn(NO); + + [delegate addFlutterEngine:mockEngine]; + XCTAssertEqual(delegate.engines.count, 1.0); + + [delegate scene:mockScene willConnectToSession:session options:options]; + OCMVerify(times(0), [mockEngine sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); } - (void)testSceneDidDisconnect { @@ -377,7 +476,7 @@ - (void)testSceneDidEnterBackground { OCMVerify(times(1), [mockAppLifecycleDelegate sceneDidEnterBackgroundFallback]); } -- (void)testSceneOpenURLContextsConsumedByScenePlugin { +- (void)testSceneOpenURLContextsHandledByScenePlugin { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -396,12 +495,12 @@ - (void)testSceneOpenURLContextsConsumedByScenePlugin { [delegate addFlutterEngine:mockEngine]; XCTAssertEqual(delegate.engines.count, 1.0); - XCTAssertTrue([delegate scene:mockScene openURLContexts:urlContexts]); + [delegate scene:mockScene openURLContexts:urlContexts]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene openURLContexts:urlContexts]); OCMVerify(times(0), [mockAppLifecycleDelegate sceneFallbackOpenURLContexts:urlContexts]); } -- (void)testSceneOpenURLContextsConsumedByApplicationPlugin { +- (void)testSceneOpenURLContextsHandledByApplicationPlugin { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -420,12 +519,41 @@ - (void)testSceneOpenURLContextsConsumedByApplicationPlugin { [delegate addFlutterEngine:mockEngine]; XCTAssertEqual(delegate.engines.count, 1.0); - XCTAssertTrue([delegate scene:mockScene openURLContexts:urlContexts]); + [delegate scene:mockScene openURLContexts:urlContexts]; + OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene openURLContexts:urlContexts]); + OCMVerify(times(1), [mockAppLifecycleDelegate sceneFallbackOpenURLContexts:urlContexts]); +} + +- (void)testSceneOpenURLContextsHandledByDeeplink { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mocks = [self mocksForEvents]; + id mockEngine = mocks[@"mockEngine"]; + id mockScene = mocks[@"mockScene"]; + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate = + (FlutterEnginePluginSceneLifeCycleDelegate*)mocks[@"mockLifecycleDelegate"]; + id mockAppLifecycleDelegate = mocks[@"mockAppLifecycleDelegate"]; + OCMStub([mockLifecycleDelegate scene:[OCMArg any] openURLContexts:[OCMArg any]]).andReturn(NO); + OCMStub([mockAppLifecycleDelegate sceneFallbackOpenURLContexts:[OCMArg any]]).andReturn(NO); + id flutterApp = OCMClassMock([FlutterSharedApplication class]); + OCMStub([flutterApp isFlutterDeepLinkingEnabled]).andReturn(YES); + + NSURL* url = [NSURL URLWithString:@"example.com"]; + id urlContext = OCMClassMock([UIOpenURLContext class]); + OCMStub([urlContext URL]).andReturn(url); + NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; + + [delegate addFlutterEngine:mockEngine]; + XCTAssertEqual(delegate.engines.count, 1.0); + + [delegate scene:mockScene openURLContexts:urlContexts]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene openURLContexts:urlContexts]); OCMVerify(times(1), [mockAppLifecycleDelegate sceneFallbackOpenURLContexts:urlContexts]); + OCMVerify(times(1), [mockEngine sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); } -- (void)testSceneOpenURLContextsConsumedByNoPlugin { +- (void)testSceneOpenURLContextsHandledByNoPlugin { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -437,6 +565,8 @@ - (void)testSceneOpenURLContextsConsumedByNoPlugin { id mockAppLifecycleDelegate = mocks[@"mockAppLifecycleDelegate"]; OCMStub([mockLifecycleDelegate scene:[OCMArg any] openURLContexts:[OCMArg any]]).andReturn(NO); OCMStub([mockAppLifecycleDelegate sceneFallbackOpenURLContexts:[OCMArg any]]).andReturn(NO); + id flutterApp = OCMClassMock([FlutterSharedApplication class]); + OCMStub([flutterApp isFlutterDeepLinkingEnabled]).andReturn(NO); id urlContext = OCMClassMock([UIOpenURLContext class]); NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; @@ -444,12 +574,134 @@ - (void)testSceneOpenURLContextsConsumedByNoPlugin { [delegate addFlutterEngine:mockEngine]; XCTAssertEqual(delegate.engines.count, 1.0); - XCTAssertFalse([delegate scene:mockScene openURLContexts:urlContexts]); + [delegate scene:mockScene openURLContexts:urlContexts]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene openURLContexts:urlContexts]); OCMVerify(times(1), [mockAppLifecycleDelegate sceneFallbackOpenURLContexts:urlContexts]); } -- (void)testSceneContinueUserActivityConsumedByScenePlugin { +- (void)testSceneOpenURLContextsWithMultipleEnginesSomeHandledByPlugin { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mocks = [self mocksForEvents]; + id mockScene = mocks[@"mockScene"]; + id mockAppLifecycleDelegate = mocks[@"mockAppLifecycleDelegate"]; + + id mockEngine1 = OCMClassMock([FlutterEngine class]); + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate1 = + OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); + OCMStub([mockEngine1 sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate1); + OCMStub([mockLifecycleDelegate1 scene:[OCMArg any] openURLContexts:[OCMArg any]]).andReturn(YES); + + id mockEngine2 = OCMClassMock([FlutterEngine class]); + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate2 = + OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); + OCMStub([mockEngine2 sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate2); + OCMStub([mockLifecycleDelegate2 scene:[OCMArg any] openURLContexts:[OCMArg any]]).andReturn(NO); + + id flutterApp = OCMClassMock([FlutterSharedApplication class]); + OCMStub([flutterApp isFlutterDeepLinkingEnabled]).andReturn(YES); + + NSURL* url = [NSURL URLWithString:@"example.com"]; + id urlContext = OCMClassMock([UIOpenURLContext class]); + OCMStub([urlContext URL]).andReturn(url); + NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; + + [delegate addFlutterEngine:mockEngine1]; + [delegate addFlutterEngine:mockEngine2]; + + [delegate scene:mockScene openURLContexts:urlContexts]; + + OCMVerify(times(1), [mockLifecycleDelegate1 scene:mockScene openURLContexts:urlContexts]); + OCMVerify(times(1), [mockLifecycleDelegate2 scene:mockScene openURLContexts:urlContexts]); + OCMVerify(times(0), [mockAppLifecycleDelegate sceneFallbackOpenURLContexts:urlContexts]); + OCMVerify(times(0), [mockEngine1 sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); + OCMVerify(times(1), [mockEngine2 sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); +} + +- (void)testSceneOpenURLContextsWithMultipleEnginesHandledByApplication { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mocks = [self mocksForEvents]; + id mockScene = mocks[@"mockScene"]; + id mockAppLifecycleDelegate = mocks[@"mockAppLifecycleDelegate"]; + OCMStub([mockAppLifecycleDelegate sceneFallbackOpenURLContexts:[OCMArg any]]).andReturn(YES); + + id mockEngine1 = OCMClassMock([FlutterEngine class]); + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate1 = + OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); + OCMStub([mockEngine1 sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate1); + OCMStub([mockLifecycleDelegate1 scene:[OCMArg any] openURLContexts:[OCMArg any]]).andReturn(NO); + + id mockEngine2 = OCMClassMock([FlutterEngine class]); + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate2 = + OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); + OCMStub([mockEngine2 sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate2); + OCMStub([mockLifecycleDelegate2 scene:[OCMArg any] openURLContexts:[OCMArg any]]).andReturn(NO); + + id flutterApp = OCMClassMock([FlutterSharedApplication class]); + OCMStub([flutterApp isFlutterDeepLinkingEnabled]).andReturn(YES); + + NSURL* url = [NSURL URLWithString:@"example.com"]; + id urlContext = OCMClassMock([UIOpenURLContext class]); + OCMStub([urlContext URL]).andReturn(url); + NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; + + [delegate addFlutterEngine:mockEngine1]; + [delegate addFlutterEngine:mockEngine2]; + + [delegate scene:mockScene openURLContexts:urlContexts]; + + OCMVerify(times(1), [mockLifecycleDelegate1 scene:mockScene openURLContexts:urlContexts]); + OCMVerify(times(1), [mockLifecycleDelegate2 scene:mockScene openURLContexts:urlContexts]); + OCMVerify(times(1), [mockAppLifecycleDelegate sceneFallbackOpenURLContexts:urlContexts]); + OCMVerify(times(0), [mockEngine1 sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); + OCMVerify(times(0), [mockEngine2 sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); +} + +- (void)testSceneOpenURLContextsWithMultipleEnginesHandledByDeeplink { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mocks = [self mocksForEvents]; + id mockScene = mocks[@"mockScene"]; + id mockAppLifecycleDelegate = mocks[@"mockAppLifecycleDelegate"]; + OCMStub([mockAppLifecycleDelegate sceneFallbackOpenURLContexts:[OCMArg any]]).andReturn(NO); + + id mockEngine1 = OCMClassMock([FlutterEngine class]); + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate1 = + OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); + OCMStub([mockEngine1 sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate1); + OCMStub([mockLifecycleDelegate1 scene:[OCMArg any] openURLContexts:[OCMArg any]]).andReturn(NO); + + id mockEngine2 = OCMClassMock([FlutterEngine class]); + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate2 = + OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); + OCMStub([mockEngine2 sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate2); + OCMStub([mockLifecycleDelegate2 scene:[OCMArg any] openURLContexts:[OCMArg any]]).andReturn(NO); + + id flutterApp = OCMClassMock([FlutterSharedApplication class]); + OCMStub([flutterApp isFlutterDeepLinkingEnabled]).andReturn(YES); + + NSURL* url = [NSURL URLWithString:@"example.com"]; + id urlContext = OCMClassMock([UIOpenURLContext class]); + OCMStub([urlContext URL]).andReturn(url); + NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; + + [delegate addFlutterEngine:mockEngine1]; + [delegate addFlutterEngine:mockEngine2]; + + [delegate scene:mockScene openURLContexts:urlContexts]; + + OCMVerify(times(1), [mockLifecycleDelegate1 scene:mockScene openURLContexts:urlContexts]); + OCMVerify(times(1), [mockLifecycleDelegate2 scene:mockScene openURLContexts:urlContexts]); + OCMVerify(times(1), [mockAppLifecycleDelegate sceneFallbackOpenURLContexts:urlContexts]); + OCMVerify(times(1), [mockEngine1 sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); + OCMVerify(times(1), [mockEngine2 sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); +} + +- (void)testSceneContinueUserActivityHandledByScenePlugin { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -468,12 +720,12 @@ - (void)testSceneContinueUserActivityConsumedByScenePlugin { [delegate addFlutterEngine:mockEngine]; XCTAssertEqual(delegate.engines.count, 1.0); - XCTAssertTrue([delegate scene:mockScene continueUserActivity:userActivity]); + [delegate scene:mockScene continueUserActivity:userActivity]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene continueUserActivity:userActivity]); OCMVerify(times(0), [mockAppLifecycleDelegate sceneFallbackContinueUserActivity:userActivity]); } -- (void)testSceneContinueUserActivityConsumedByApplicationPlugin { +- (void)testSceneContinueUserActivityHandledByApplicationPlugin { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -492,12 +744,41 @@ - (void)testSceneContinueUserActivityConsumedByApplicationPlugin { [delegate addFlutterEngine:mockEngine]; XCTAssertEqual(delegate.engines.count, 1.0); - XCTAssertTrue([delegate scene:mockScene continueUserActivity:userActivity]); + [delegate scene:mockScene continueUserActivity:userActivity]; + OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene continueUserActivity:userActivity]); + OCMVerify(times(1), [mockAppLifecycleDelegate sceneFallbackContinueUserActivity:userActivity]); +} + +- (void)testSceneContinueUserActivityHandledByUniversalLinks { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mocks = [self mocksForEvents]; + id mockEngine = mocks[@"mockEngine"]; + id mockScene = mocks[@"mockScene"]; + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate = + (FlutterEnginePluginSceneLifeCycleDelegate*)mocks[@"mockLifecycleDelegate"]; + id mockAppLifecycleDelegate = mocks[@"mockAppLifecycleDelegate"]; + OCMStub([mockLifecycleDelegate scene:[OCMArg any] continueUserActivity:[OCMArg any]]) + .andReturn(NO); + OCMStub([mockAppLifecycleDelegate sceneFallbackContinueUserActivity:[OCMArg any]]).andReturn(NO); + id flutterApp = OCMClassMock([FlutterSharedApplication class]); + OCMStub([flutterApp isFlutterDeepLinkingEnabled]).andReturn(YES); + + NSURL* url = [NSURL URLWithString:@"example.com"]; + id userActivity = OCMClassMock([NSUserActivity class]); + OCMStub([userActivity webpageURL]).andReturn(url); + + [delegate addFlutterEngine:mockEngine]; + XCTAssertEqual(delegate.engines.count, 1.0); + + [delegate scene:mockScene continueUserActivity:userActivity]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene continueUserActivity:userActivity]); OCMVerify(times(1), [mockAppLifecycleDelegate sceneFallbackContinueUserActivity:userActivity]); + OCMVerify(times(1), [mockEngine sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); } -- (void)testSceneContinueUserActivityConsumedByNoPlugin { +- (void)testSceneContinueUserActivityHandledByNoPlugin { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -510,18 +791,23 @@ - (void)testSceneContinueUserActivityConsumedByNoPlugin { OCMStub([mockLifecycleDelegate scene:[OCMArg any] continueUserActivity:[OCMArg any]]) .andReturn(NO); OCMStub([mockAppLifecycleDelegate sceneFallbackContinueUserActivity:[OCMArg any]]).andReturn(NO); + id flutterApp = OCMClassMock([FlutterSharedApplication class]); + OCMStub([flutterApp isFlutterDeepLinkingEnabled]).andReturn(NO); + NSURL* url = [NSURL URLWithString:@"example.com"]; id userActivity = OCMClassMock([NSUserActivity class]); + OCMStub([userActivity webpageURL]).andReturn(url); [delegate addFlutterEngine:mockEngine]; XCTAssertEqual(delegate.engines.count, 1.0); - XCTAssertFalse([delegate scene:mockScene continueUserActivity:userActivity]); + [delegate scene:mockScene continueUserActivity:userActivity]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene continueUserActivity:userActivity]); OCMVerify(times(1), [mockAppLifecycleDelegate sceneFallbackContinueUserActivity:userActivity]); + OCMVerify(times(0), [mockEngine sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); } -- (void)testWindowScenePerformActionForShortcutItemConsumedByScenePlugin { +- (void)testWindowScenePerformActionForShortcutItemHandledByScenePlugin { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -546,9 +832,9 @@ - (void)testWindowScenePerformActionForShortcutItemConsumedByScenePlugin { [delegate addFlutterEngine:mockEngine]; XCTAssertEqual(delegate.engines.count, 1.0); - XCTAssertTrue([delegate windowScene:mockScene - performActionForShortcutItem:shortcutItem - completionHandler:handler]); + [delegate windowScene:mockScene + performActionForShortcutItem:shortcutItem + completionHandler:handler]; OCMVerify(times(1), [mockLifecycleDelegate windowScene:mockScene performActionForShortcutItem:shortcutItem completionHandler:handler]); @@ -557,7 +843,7 @@ - (void)testWindowScenePerformActionForShortcutItemConsumedByScenePlugin { completionHandler:handler]); } -- (void)testWindowScenePerformActionForShortcutItemConsumedByApplicationPlugin { +- (void)testWindowScenePerformActionForShortcutItemHandledByApplicationPlugin { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -582,9 +868,9 @@ - (void)testWindowScenePerformActionForShortcutItemConsumedByApplicationPlugin { [delegate addFlutterEngine:mockEngine]; XCTAssertEqual(delegate.engines.count, 1.0); - XCTAssertTrue([delegate windowScene:mockScene - performActionForShortcutItem:shortcutItem - completionHandler:handler]); + [delegate windowScene:mockScene + performActionForShortcutItem:shortcutItem + completionHandler:handler]; OCMVerify(times(1), [mockLifecycleDelegate windowScene:mockScene performActionForShortcutItem:shortcutItem completionHandler:handler]); @@ -593,7 +879,7 @@ - (void)testWindowScenePerformActionForShortcutItemConsumedByApplicationPlugin { completionHandler:handler]); } -- (void)testWindowScenePerformActionForShortcutItemConsumedByNoPlugin { +- (void)testWindowScenePerformActionForShortcutItemHandledByNoPlugin { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -618,9 +904,9 @@ - (void)testWindowScenePerformActionForShortcutItemConsumedByNoPlugin { [delegate addFlutterEngine:mockEngine]; XCTAssertEqual(delegate.engines.count, 1.0); - XCTAssertFalse([delegate windowScene:mockScene - performActionForShortcutItem:shortcutItem - completionHandler:handler]); + [delegate windowScene:mockScene + performActionForShortcutItem:shortcutItem + completionHandler:handler]; OCMVerify(times(1), [mockLifecycleDelegate windowScene:mockScene performActionForShortcutItem:shortcutItem completionHandler:handler]); diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h index 0d5ff76b01b62..4dde0c3b2c05e 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h @@ -29,6 +29,8 @@ */ @property(class, nonatomic, readonly) BOOL hasSceneDelegate; ++ (BOOL)isFlutterDeepLinkingEnabled; + @end #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERSHAREDAPPLICATION_H_ diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.mm index 20701c336f5c5..ec70e79200d45 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.mm @@ -45,4 +45,13 @@ + (BOOL)hasSceneDelegate { return NO; } ++ (BOOL)isFlutterDeepLinkingEnabled { + // Developers may disable deep linking through their Info.plist if they are using a plugin that + // handles deeplinking instead. + NSNumber* isDeepLinkingEnabled = + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"]; + // if not set, return YES + return isDeepLinkingEnabled ? [isDeepLinkingEnabled boolValue] : YES; +} + @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplicationTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplicationTest.mm index 85aea3f13263d..881fbd286fbbc 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplicationTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplicationTest.mm @@ -97,4 +97,36 @@ - (void)testHasNoSceneDelegate { [mockBundle stopMocking]; } +- (void)testFlutterDeeplinkingEnabledWhenNil { + id mockBundle = OCMPartialMock([NSBundle mainBundle]); + OCMStub([mockBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"]).andReturn(nil); + + XCTAssertTrue(FlutterSharedApplication.isFlutterDeepLinkingEnabled); + [mockBundle stopMocking]; +} + +- (void)testFlutterDeeplinkingEnabledWhenYes { + id mockBundle = OCMPartialMock([NSBundle mainBundle]); + OCMStub([mockBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"]).andReturn(@YES); + + XCTAssertTrue(FlutterSharedApplication.isFlutterDeepLinkingEnabled); + [mockBundle stopMocking]; +} + +- (void)testFlutterDeeplinkingEnabledWhenNo { + id mockBundle = OCMPartialMock([NSBundle mainBundle]); + OCMStub([mockBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"]).andReturn(@NO); + + XCTAssertFalse(FlutterSharedApplication.isFlutterDeepLinkingEnabled); + [mockBundle stopMocking]; +} + +- (void)testFlutterDeeplinkingEnabledWhenBogus { + id mockBundle = OCMPartialMock([NSBundle mainBundle]); + OCMStub([mockBundle objectForInfoDictionaryKey:@"FlutterDeepLinkingEnabled"]).andReturn(@"hello"); + + XCTAssertFalse(FlutterSharedApplication.isFlutterDeepLinkingEnabled); + [mockBundle stopMocking]; +} + @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index f7fd6cd518db6..6f706115e0b85 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -1905,36 +1905,6 @@ - (void)handlePressEvent:(FlutterUIPressProxy*)press [self.keyboardManager handlePress:press nextAction:next]; } -- (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(void (^)(BOOL success))completion { - __weak FlutterViewController* weakSelf = self; - [self.engine - waitForFirstFrame:3.0 - callback:^(BOOL didTimeout) { - if (didTimeout) { - [FlutterLogger - logError:@"Timeout waiting for first frame when launching a URL."]; - completion(NO); - } else { - // invove the method and get the result - [weakSelf.engine.navigationChannel - invokeMethod:@"pushRouteInformation" - arguments:@{ - @"location" : url.absoluteString ?: [NSNull null], - } - result:^(id _Nullable result) { - BOOL success = - [result isKindOfClass:[NSNumber class]] && [result boolValue]; - if (!success) { - // Logging the error if the result is not successful - [FlutterLogger - logError:@"Failed to handle route information in Flutter."]; - } - completion(success); - }]; - } - }]; -} - // The documentation for presses* handlers (implemented below) is entirely // unclear about how to handle the case where some, but not all, of the presses // are handled here. I've elected to call super separately for each of the diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h index 8c61b980e0789..bc47d4aba617a 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h @@ -68,7 +68,6 @@ typedef void (^FlutterKeyboardAnimationCallback)(fml::TimePoint); // handled. - (void)handlePressEvent:(FlutterUIPressProxy*)press nextAction:(void (^)())nextAction API_AVAILABLE(ios(13.4)); -- (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(void (^)(BOOL success))completion; - (void)addInternalPlugins; - (void)deregisterNotifications; - (int32_t)accessibilityFlags; From db0e8098315e6bb3cfb3190a03a178a73d9ffbc3 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 2 Oct 2025 17:48:43 -0400 Subject: [PATCH 046/204] Roll Skia from 05c1f5803415 to cf339ab390c2 (11 revisions) (#176426) https://skia.googlesource.com/skia.git/+log/05c1f5803415..cf339ab390c2 2025-10-02 michaelludwig@google.com Make SkAlignTo templated; optimize SkAlignNonPow2 2025-10-02 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2025-10-02 thomsmit@google.com [graphite] Add drawContext pointer to task dumping 2025-10-02 mike@reedtribe.org use pathbuilder or factories, rather than mutate SkPath 2025-10-02 kjlubick@google.com Update pixmap asserts to identify negative values explicitly 2025-10-02 louhi-prod-1-6316342352543744@louhi-prod-1.iam.gserviceaccount.com Update fiddler-base for 020ae52af0e7 2025-10-02 fmalita@google.com Reset convexity in SkPathBuilder::addPath 2025-10-02 bungeman@google.com Use Skia's ninja when building viewer with gradle 2025-10-02 borenet@google.com Add Louhi service account to AUTHORS 2025-10-02 mike@reedtribe.org Remove friend pathref hacking 2025-10-02 lukasza@chromium.org [rust png] Copy `EncodePngAsSkData` from Chromium into Skia. If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 2cfa6ad090a85..8d86327b5a8ad 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': '05c1f58034159ee906aa740552db93b8e18b8e02', + 'skia_revision': 'cf339ab390c24968ac6760846f50c695585e3342', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 02c902e929b574c674a59dc37898abd4de25a851 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 2 Oct 2025 21:09:12 -0400 Subject: [PATCH 047/204] Roll Dart SDK from 4f90a06328cb to fdd90f38d6a0 (7 revisions) (#176431) https://dart.googlesource.com/sdk.git/+log/4f90a06328cb..fdd90f38d6a0 2025-10-02 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-275.0.dev 2025-10-02 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-274.0.dev 2025-10-02 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-273.0.dev 2025-10-02 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-272.0.dev 2025-10-02 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-271.0.dev 2025-10-02 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-270.0.dev 2025-10-01 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-269.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jimgraham@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 8d86327b5a8ad..94668c28a2b06 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '4f90a06328cb4bdfbbf97e3ef344e0b96dddf177', + 'dart_revision': 'fdd90f38d6a0f2115891346b9baa3d10f9a66d32', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From ddbc920ace5e9536ac7726327f4964623ddd18e9 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Thu, 2 Oct 2025 22:15:32 -0400 Subject: [PATCH 048/204] Roll Skia from cf339ab390c2 to 1720a85a507e (4 revisions) (#176439) https://skia.googlesource.com/skia.git/+log/cf339ab390c2..1720a85a507e 2025-10-03 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2025-10-02 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 3964dd7c126b to 7bc87a4a45cf (7 revisions) 2025-10-02 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2025-10-02 kjlubick@google.com Add tests for SkSGMerge If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 94668c28a2b06..b2f66655d19f1 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'cf339ab390c24968ac6760846f50c695585e3342', + 'skia_revision': '1720a85a507eb620984627a5ba7add612f8b17d2', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From a3196760908cd89cefd5f746c34899bf760b52de Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 3 Oct 2025 02:46:34 -0400 Subject: [PATCH 049/204] Roll Fuchsia Linux SDK from Vnoygds8HtDUvGLCK... to HUhTcRn-LUXa2Salu... (#176442) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-linux-sdk-flutter Please CC jimgraham@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index b2f66655d19f1..3c2061123a3b8 100644 --- a/DEPS +++ b/DEPS @@ -807,7 +807,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'Vnoygds8HtDUvGLCKWF3Vz-HKH4CCwNOXv86GX09s9oC' + 'version': 'HUhTcRn-LUXa2Salu3L5Y7tBlXgCO-PziyhhTArrFxkC' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', From 018897e3f12c5783fb025bcbd92515a69d4d5c32 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 3 Oct 2025 03:22:28 -0400 Subject: [PATCH 050/204] Roll Skia from 1720a85a507e to f86ae4113254 (1 revision) (#176443) https://skia.googlesource.com/skia.git/+log/1720a85a507e..f86ae4113254 2025-10-03 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from 603a5155599a to 01940842b667 (22 revisions) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 3c2061123a3b8..715b7ddf8fc61 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': '1720a85a507eb620984627a5ba7add612f8b17d2', + 'skia_revision': 'f86ae41132540cdb8f0a5cec6f9628510b462c4e', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 5bc7a9c1309b48b41efab6fb63237466f93d7824 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 3 Oct 2025 09:15:43 -0400 Subject: [PATCH 051/204] Roll Skia from f86ae4113254 to b842026480e0 (3 revisions) (#176458) https://skia.googlesource.com/skia.git/+log/f86ae4113254..b842026480e0 2025-10-03 skia-autoroll@skia-public.iam.gserviceaccount.com Roll ANGLE from 7994bf76d7a9 to 62b00f866364 (11 revisions) 2025-10-03 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2025-10-03 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Skia Infra from 8d4953412be2 to 228c951bd699 (11 revisions) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 715b7ddf8fc61..2d7d9924d519b 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'f86ae41132540cdb8f0a5cec6f9628510b462c4e', + 'skia_revision': 'b842026480e0d5e5547e5d6359a6a44e5692aff1', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From dca709fbbf0b2dc2afc5087c0969f362dfbd7f89 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 3 Oct 2025 11:23:22 -0400 Subject: [PATCH 052/204] Roll Dart SDK from fdd90f38d6a0 to 0009748aed50 (3 revisions) (#176461) https://dart.googlesource.com/sdk.git/+log/fdd90f38d6a0..0009748aed50 2025-10-03 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-278.0.dev 2025-10-03 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-277.0.dev 2025-10-03 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-276.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jimgraham@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 2d7d9924d519b..7232f6c7070e7 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': 'fdd90f38d6a0f2115891346b9baa3d10f9a66d32', + 'dart_revision': '0009748aed5088a399c1e1929d7a440f96c2164c', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From 5c0c9e9e9ad2eec7dc28b216fa55b8c5bf6d7ad9 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 3 Oct 2025 11:49:25 -0400 Subject: [PATCH 053/204] Roll Packages from 5fd5f74dce46 to e401aeb3aae4 (4 revisions) (#176466) https://github.com/flutter/packages/compare/5fd5f74dce46...e401aeb3aae4 2025-10-02 engine-flutter-autoroll@skia.org Roll Flutter from 7811e8982355 to 65aca3661b8f (12 revisions) (flutter/packages#10161) 2025-10-02 34327253+lenzpaul@users.noreply.github.com [Camera] Add lens type information (iOS) (flutter/packages#7653) 2025-10-02 mohellebiabdessalem@gmail.com [webview_flutter] updates build files to use JVM 17 (flutter/packages#10129) 2025-10-02 mohellebiabdessalem@gmail.com [shared_preferences] updates build files to use JVM 17 (flutter/packages#10131) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC flutter-ecosystem@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- bin/internal/flutter_packages.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/flutter_packages.version b/bin/internal/flutter_packages.version index 7860e48206780..57fe12eb8be38 100644 --- a/bin/internal/flutter_packages.version +++ b/bin/internal/flutter_packages.version @@ -1 +1 @@ -5fd5f74dce46e15703b17cf1d56fbc89720b29b3 +e401aeb3aae46b82ce26096d960900748a13131a From 22a220388823d771ba88ca6564c5bb2f05bb7bd8 Mon Sep 17 00:00:00 2001 From: LouiseHsu Date: Fri, 3 Oct 2025 11:02:35 -0700 Subject: [PATCH 054/204] Fix Voiceover traversal for OutlinedButton.icon (#175810) Fixes #172489 When an OutlinedButton.icon is initially built with a null icon and later updated to display an icon, the underlying widget implementation changes. The button switches from a standard OutlinedButton to a private _OutlinedButtonWithIcon widget. This switch causes the button's entire semantic node to be destroyed and a new one created in its place. For screen readers like VoiceOver, this behavior is disruptive. If an accessibility service is focused on the button when its icon appears, that focus is lost because the original semantic node is discarded, leading to strange behaviour as it makes an best effort to focus on an existing node. This PR resolves the issue by ensuring the same widget is used regardless of whether the icon is present. The logic for _OutlinedButtonWithIcon has been merged into the base class OutlinedButton, ensuring that no matter if you call OutlinedButton.icon or OutlinedButton you recieve a widget of type OutlinedButton. Demo: https://github.com/user-attachments/assets/e012bac9-823e-46f1-8eba-ec70e6b260a1 --- .../test/demo/material/buttons_demo_test.dart | 4 +- .../lib/src/material/outlined_button.dart | 149 +++++++----------- .../test/material/outlined_button_test.dart | 54 +++++++ 3 files changed, 109 insertions(+), 98 deletions(-) diff --git a/dev/integration_tests/flutter_gallery/test/demo/material/buttons_demo_test.dart b/dev/integration_tests/flutter_gallery/test/demo/material/buttons_demo_test.dart index ca1dc2d217964..5304e0e1a9f64 100644 --- a/dev/integration_tests/flutter_gallery/test/demo/material/buttons_demo_test.dart +++ b/dev/integration_tests/flutter_gallery/test/demo/material/buttons_demo_test.dart @@ -35,9 +35,9 @@ void main() { { await tester.tap(find.text('OUTLINED')); await tester.pumpAndSettle(); - expect(find.byType(OutlinedButton).evaluate().length, 2); + expect(find.byType(OutlinedButton).evaluate().length, 4); final Offset topLeft1 = tester.getTopLeft(find.byType(OutlinedButton).first); - final Offset topLeft2 = tester.getTopLeft(find.byType(OutlinedButton).last); + final Offset topLeft2 = tester.getTopLeft(find.byType(OutlinedButton).at(1)); expect(topLeft1.dx, 203); expect(topLeft2.dx, 453); expect(topLeft1.dy, topLeft2.dy); diff --git a/packages/flutter/lib/src/material/outlined_button.dart b/packages/flutter/lib/src/material/outlined_button.dart index 514e921577e8b..295954d509a9b 100644 --- a/packages/flutter/lib/src/material/outlined_button.dart +++ b/packages/flutter/lib/src/material/outlined_button.dart @@ -84,7 +84,7 @@ class OutlinedButton extends ButtonStyleButton { super.clipBehavior, super.statesController, required super.child, - }); + }) : _addPadding = false; /// Create a text button from a pair of widgets that serve as the button's /// [icon] and [label]. @@ -96,52 +96,33 @@ class OutlinedButton extends ButtonStyleButton { /// /// {@macro flutter.material.ButtonStyleButton.iconAlignment} /// - factory OutlinedButton.icon({ - Key? key, - required VoidCallback? onPressed, - VoidCallback? onLongPress, - ValueChanged? onHover, - ValueChanged? onFocusChange, - ButtonStyle? style, - FocusNode? focusNode, - bool? autofocus, - Clip? clipBehavior, - MaterialStatesController? statesController, + OutlinedButton.icon({ + super.key, + required super.onPressed, + super.onLongPress, + super.onHover, + super.onFocusChange, + super.style, + super.focusNode, + super.autofocus = false, + super.clipBehavior, + super.statesController, Widget? icon, required Widget label, IconAlignment? iconAlignment, - }) { - if (icon == null) { - return OutlinedButton( - key: key, - onPressed: onPressed, - onLongPress: onLongPress, - onHover: onHover, - onFocusChange: onFocusChange, - style: style, - focusNode: focusNode, - autofocus: autofocus ?? false, - clipBehavior: clipBehavior ?? Clip.none, - statesController: statesController, - child: label, - ); - } - return _OutlinedButtonWithIcon( - key: key, - onPressed: onPressed, - onLongPress: onLongPress, - onHover: onHover, - onFocusChange: onFocusChange, - style: style, - focusNode: focusNode, - autofocus: autofocus ?? false, - clipBehavior: clipBehavior ?? Clip.none, - statesController: statesController, - icon: icon, - label: label, - iconAlignment: iconAlignment, - ); - } + }) : _addPadding = icon != null, + super( + child: icon != null + ? _OutlinedButtonWithIconChild( + iconAlignment: iconAlignment, + label: label, + buttonStyle: style, + icon: icon, + ) + : label, + ); + + final bool _addPadding; /// A static convenience method that constructs an outlined button /// [ButtonStyle] given simple values. @@ -371,8 +352,7 @@ class OutlinedButton extends ButtonStyleButton { ButtonStyle defaultStyleOf(BuildContext context) { final ThemeData theme = Theme.of(context); final ColorScheme colorScheme = theme.colorScheme; - - return Theme.of(context).useMaterial3 + final ButtonStyle buttonStyle = theme.useMaterial3 ? _OutlinedButtonDefaultsM3(context) : styleFrom( foregroundColor: colorScheme.primary, @@ -396,6 +376,25 @@ class OutlinedButton extends ButtonStyleButton { alignment: Alignment.center, splashFactory: InkRipple.splashFactory, ); + + // Only apply paddings when OutlinedButton has an Icon + if (_addPadding && theme.useMaterial3) { + final double defaultFontSize = + buttonStyle.textStyle?.resolve(const {})?.fontSize ?? 14.0; + final double effectiveTextScale = + MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0; + final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding( + const EdgeInsetsDirectional.fromSTEB(16, 0, 24, 0), + const EdgeInsetsDirectional.fromSTEB(8, 0, 12, 0), + const EdgeInsetsDirectional.fromSTEB(4, 0, 6, 0), + effectiveTextScale, + ); + return buttonStyle.copyWith( + padding: MaterialStatePropertyAll(scaledPadding), + ); + } + + return buttonStyle; } @override @@ -417,64 +416,16 @@ EdgeInsetsGeometry _scaledPadding(BuildContext context) { ); } -class _OutlinedButtonWithIcon extends OutlinedButton { - _OutlinedButtonWithIcon({ - super.key, - required super.onPressed, - super.onLongPress, - super.onHover, - super.onFocusChange, - super.style, - super.focusNode, - bool? autofocus, - super.clipBehavior, - super.statesController, - required Widget icon, - required Widget label, - IconAlignment? iconAlignment, - }) : super( - autofocus: autofocus ?? false, - child: _OutlinedButtonWithIconChild( - icon: icon, - label: label, - buttonStyle: style, - iconAlignment: iconAlignment, - ), - ); - - @override - ButtonStyle defaultStyleOf(BuildContext context) { - final bool useMaterial3 = Theme.of(context).useMaterial3; - if (!useMaterial3) { - return super.defaultStyleOf(context); - } - final ButtonStyle buttonStyle = super.defaultStyleOf(context); - final double defaultFontSize = - buttonStyle.textStyle?.resolve(const {})?.fontSize ?? 14.0; - final double effectiveTextScale = - MediaQuery.textScalerOf(context).scale(defaultFontSize) / 14.0; - final EdgeInsetsGeometry scaledPadding = ButtonStyleButton.scaledPadding( - const EdgeInsetsDirectional.fromSTEB(16, 0, 24, 0), - const EdgeInsetsDirectional.fromSTEB(8, 0, 12, 0), - const EdgeInsetsDirectional.fromSTEB(4, 0, 6, 0), - effectiveTextScale, - ); - return buttonStyle.copyWith( - padding: MaterialStatePropertyAll(scaledPadding), - ); - } -} - class _OutlinedButtonWithIconChild extends StatelessWidget { const _OutlinedButtonWithIconChild({ required this.label, - required this.icon, + this.icon, required this.buttonStyle, required this.iconAlignment, }); final Widget label; - final Widget icon; + final Widget? icon; final ButtonStyle? buttonStyle; final IconAlignment? iconAlignment; @@ -491,6 +442,12 @@ class _OutlinedButtonWithIconChild extends StatelessWidget { outlinedButtonTheme.style?.iconAlignment ?? buttonStyle?.iconAlignment ?? IconAlignment.start; + final Widget? icon = this.icon; + + if (icon == null) { + return label; + } + return Row( mainAxisSize: MainAxisSize.min, spacing: lerpDouble(8, 4, scale)!, diff --git a/packages/flutter/test/material/outlined_button_test.dart b/packages/flutter/test/material/outlined_button_test.dart index 1abbbdedb1b4f..792b7f20d4f25 100644 --- a/packages/flutter/test/material/outlined_button_test.dart +++ b/packages/flutter/test/material/outlined_button_test.dart @@ -1260,6 +1260,60 @@ void main() { semantics.dispose(); }); + testWidgets('When an OutlinedButton gains an icon, preserves the same SemanticsNode id', ( + WidgetTester tester, + ) async { + bool toggled = false; + + const Key key = Key('button'); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Row( + children: [ + OutlinedButton.icon( + key: key, + onPressed: () { + setState(() { + toggled = true; + }); + }, + icon: toggled ? const Icon(Icons.favorite) : null, + label: const Text('Button'), + ), + ], + ); + }, + ), + ), + ), + ); + + // Initially, no icons are present. + expect(find.byIcon(Icons.favorite), findsNothing); + + // Find the original OutlinedButton with no icon and get it's SemanticsNode + final Finder outlinedButton = find.bySemanticsLabel('Button'); + expect(outlinedButton, findsOneWidget); + + final SemanticsNode origSemanticsNode = tester.getSemantics(outlinedButton); + + // Tap the button. It should receive an icon now. + await tester.tap(outlinedButton); + await tester.pump(); + + // Now one icon should be present. + expect(find.byIcon(Icons.favorite), findsOneWidget); + + // Check if the semantics has change + final SemanticsNode semanticsNodeWithIcon = tester.getSemantics(outlinedButton); + + expect(semanticsNodeWithIcon, origSemanticsNode); + }); + testWidgets('OutlinedButton scales textScaleFactor', (WidgetTester tester) async { await tester.pumpWidget( Theme( From b3ecbb9034564eada70b24f64f8fa4948abbc7e8 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:08:08 -0500 Subject: [PATCH 055/204] Add state restoration for UIScene migration (#176305) When an iOS app migrates to UIScene, the application events for handling state restoration are no longer called by UIKit. Instead, scene events in the SceneDelegate must be used. This PR adds support for handling state restoration in UIScene while still maintaining support for unmigrated apps, as well. Fixes https://github.com/flutter/flutter/issues/174402. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../bin/tasks/module_uiscene_test_ios.dart | 20 +++ .../flutterapp/lib/main-StateRestorationTest | 81 ++++++++++++ ...terViewController-RestorationId.storyboard | 32 +++++ .../native/UITests-StateRestoration.swift | 32 +++++ .../framework/Source/FlutterAppDelegate.mm | 21 +-- .../ios/framework/Source/FlutterEngine_Test.h | 1 + .../framework/Source/FlutterSceneDelegate.mm | 12 ++ .../Source/FlutterSceneDelegateTest.m | 37 ++++++ .../framework/Source/FlutterSceneLifeCycle.mm | 56 ++++++++ .../Source/FlutterSceneLifeCycleTest.mm | 123 ++++++++++++++++++ .../Source/FlutterSceneLifeCycle_Internal.h | 5 + .../Source/FlutterSharedApplication.h | 4 + .../Source/FlutterSharedApplication.mm | 12 ++ 13 files changed, 421 insertions(+), 15 deletions(-) create mode 100644 dev/integration_tests/ios_add2app_uiscene/flutterapp/lib/main-StateRestorationTest create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/Main-FlutterViewController-RestorationId.storyboard create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/UITests-StateRestoration.swift diff --git a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart index 51c03c98904b3..2a847c40c2ee0 100644 --- a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart +++ b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart @@ -436,6 +436,14 @@ class Scenarios { r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-AppNotMigrated.swift': r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', }, + + // State restoration work both when migrated and when not. + 'AppMigrated-StateRestoration': {...sharedStateRestorationFiles}, + 'AppNotMigrated-StateRestoration': { + ...sharedStateRestorationFiles, + r'$TEMPLATE_DIR/native/Info-unmigrated.plist': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/Info.plist', + }, }; late Map> swiftUIScenarios = >{ @@ -490,4 +498,16 @@ class Scenarios { r'$TEMPLATE_DIR/flutterplugin/lib/lifecycle_plugin_platform_interface': r'$PLUGIN_DIR/lib/my_plugin_platform_interface.dart', }; + + late Map sharedStateRestorationFiles = { + r'$TEMPLATE_DIR/flutterapp/lib/main-StateRestorationTest': r'$APP_DIR/lib/main.dart', + r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate-FlutterEngine.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', + r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$TEMPLATE_DIR/native/Main-FlutterViewController-RestorationId.storyboard': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/Base.lproj/Main.storyboard', + r'$TEMPLATE_DIR/native/UITests-StateRestoration.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + }; } diff --git a/dev/integration_tests/ios_add2app_uiscene/flutterapp/lib/main-StateRestorationTest b/dev/integration_tests/ios_add2app_uiscene/flutterapp/lib/main-StateRestorationTest new file mode 100644 index 0000000000000..eb86207a72fdc --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/flutterapp/lib/main-StateRestorationTest @@ -0,0 +1,81 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +void main() => runApp( + const RootRestorationScope(restorationId: 'restorationId1', child: MyApp()), +); + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + initialRoute: '/', + restorationScopeId: 'restorationId2', + routes: { + '/': (BuildContext context) => + MyHomePage(title: 'Flutter Demo First Page'), + '/second': (BuildContext context) => + SecondPage(title: 'Flutter Demo Second Page'), + }, + title: 'Flutter Demo', + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({super.key, required this.title}); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(widget.title)), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () { + Navigator.restorablePushNamed(context, '/second'); + }, + child: const Text('Next Page'), + ), + ], + ), + ), + ); + } +} + +class SecondPage extends StatelessWidget { + const SecondPage({super.key, required this.title}); + + final String title; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text(title)), + body: Center( + child: ElevatedButton( + onPressed: () { + Navigator.restorablePushNamed(context, '/'); + }, + child: const Text('Return to home page'), + ), + ), + ); + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/Main-FlutterViewController-RestorationId.storyboard b/dev/integration_tests/ios_add2app_uiscene/native/Main-FlutterViewController-RestorationId.storyboard new file mode 100644 index 0000000000000..e54a5b2824300 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/Main-FlutterViewController-RestorationId.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-StateRestoration.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-StateRestoration.swift new file mode 100644 index 0000000000000..1a6335c8db015 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-StateRestoration.swift @@ -0,0 +1,32 @@ +// 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 XCTest + +final class xcode_uikit_swiftUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + @MainActor + func testStateRestoration() throws { + let app = XCUIApplication() + app.launch() + + let button = app.buttons["Next Page"].firstMatch + XCTAssertTrue(button.waitForExistence(timeout: 5)) + button.tap() + + // Suspend the app and the stop it, then on next launch the state should be restored. + // See https://developer.apple.com/documentation/uikit/restoring-your-app-s-state?#Test-state-restoration-on-a-device + XCUIDevice.shared.press(.home) + app.wait(for: XCUIApplication.State.runningBackgroundSuspended, timeout: 5) + app.terminate() + + app.launch() + let nextPageTitle = app.otherElements["Flutter Demo Second Page"].firstMatch + XCTAssertTrue(nextPageTitle.waitForExistence(timeout: 5)) + } +} diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm index f8d5817bcea99..03bf0b3db1de3 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate.mm @@ -19,7 +19,6 @@ static NSString* const kUIBackgroundMode = @"UIBackgroundModes"; static NSString* const kRemoteNotificationCapabitiliy = @"remote-notification"; static NSString* const kBackgroundFetchCapatibility = @"fetch"; -static NSString* const kRestorationStateAppModificationKey = @"mod-date"; @interface FlutterAppDelegate () { __weak NSObject* _weakRegistrant; @@ -333,34 +332,26 @@ - (void)logCapabilityConfigurationWarningIfNeeded:(SEL)selector { #pragma mark - State Restoration - (BOOL)application:(UIApplication*)application shouldSaveApplicationState:(NSCoder*)coder { - [coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey]; + [coder encodeInt64:FlutterSharedApplication.lastAppModificationTime + forKey:kRestorationStateAppModificationKey]; return YES; } - (BOOL)application:(UIApplication*)application shouldRestoreApplicationState:(NSCoder*)coder { int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey]; - return self.lastAppModificationTime == stateDate; + return FlutterSharedApplication.lastAppModificationTime == stateDate; } - (BOOL)application:(UIApplication*)application shouldSaveSecureApplicationState:(NSCoder*)coder { - [coder encodeInt64:self.lastAppModificationTime forKey:kRestorationStateAppModificationKey]; + [coder encodeInt64:FlutterSharedApplication.lastAppModificationTime + forKey:kRestorationStateAppModificationKey]; return YES; } - (BOOL)application:(UIApplication*)application shouldRestoreSecureApplicationState:(NSCoder*)coder { int64_t stateDate = [coder decodeInt64ForKey:kRestorationStateAppModificationKey]; - return self.lastAppModificationTime == stateDate; -} - -- (int64_t)lastAppModificationTime { - NSDate* fileDate; - NSError* error = nil; - [[[NSBundle mainBundle] executableURL] getResourceValue:&fileDate - forKey:NSURLContentModificationDateKey - error:&error]; - NSAssert(error == nil, @"Cannot obtain modification date of main bundle: %@", error); - return [fileDate timeIntervalSince1970]; + return FlutterSharedApplication.lastAppModificationTime == stateDate; } @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h index b20ef41764920..8f5759674b73d 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h @@ -23,6 +23,7 @@ class ThreadHost; @property(readonly, nonatomic) FlutterEngineProcTable& embedderAPI; @property(readonly, nonatomic) BOOL enableEmbedderAPI; +@property(nonatomic, strong) FlutterRestorationPlugin* restorationPlugin; - (flutter::Shell&)shell; - (flutter::PlatformViewIOS*)platformView; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegate.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegate.mm index 6387191c946e6..1a898dab9ed6a 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegate.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegate.mm @@ -79,6 +79,18 @@ - (void)scene:(UIScene*)scene continueUserActivity:(NSUserActivity*)userActivity [self.sceneLifeCycleDelegate scene:scene continueUserActivity:userActivity]; } +#pragma mark - Saving the state of the scene + +- (NSUserActivity*)stateRestorationActivityForScene:(UIScene*)scene { + return [self.sceneLifeCycleDelegate stateRestorationActivityForScene:scene]; +} + +- (void)scene:(UIScene*)scene + restoreInteractionStateWithUserActivity:(NSUserActivity*)stateRestorationActivity { + [self.sceneLifeCycleDelegate scene:scene + restoreInteractionStateWithUserActivity:stateRestorationActivity]; +} + #pragma mark - Performing tasks - (void)windowScene:(UIWindowScene*)windowScene diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegateTest.m b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegateTest.m index d08d52f26429c..f137dd0d46636 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegateTest.m +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegateTest.m @@ -212,6 +212,43 @@ - (void)testSceneContinueUserActivity { OCMVerify(times(1), [mockLifecycleDelegate scene:scene continueUserActivity:userActivity]); } +- (void)testStateRestorationActivityForScene { + [self setupMockApplication]; + + FlutterSceneDelegate* sceneDelegate = [[FlutterSceneDelegate alloc] init]; + id mockSceneDelegate = OCMPartialMock(sceneDelegate); + + FlutterPluginSceneLifeCycleDelegate* mockLifecycleDelegate = + OCMClassMock([FlutterPluginSceneLifeCycleDelegate class]); + OCMStub([mockSceneDelegate sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); + + id scene = OCMClassMock([UIWindowScene class]); + + [((FlutterSceneDelegate*)mockSceneDelegate) stateRestorationActivityForScene:scene]; + + OCMVerify(times(1), [mockLifecycleDelegate stateRestorationActivityForScene:scene]); +} + +- (void)testSceneRestoreInteractionStateWithUserActivity { + [self setupMockApplication]; + + FlutterSceneDelegate* sceneDelegate = [[FlutterSceneDelegate alloc] init]; + id mockSceneDelegate = OCMPartialMock(sceneDelegate); + + FlutterPluginSceneLifeCycleDelegate* mockLifecycleDelegate = + OCMClassMock([FlutterPluginSceneLifeCycleDelegate class]); + OCMStub([mockSceneDelegate sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); + + id scene = OCMClassMock([UIWindowScene class]); + id userActivity = OCMClassMock([NSUserActivity class]); + + [((FlutterSceneDelegate*)mockSceneDelegate) scene:scene + restoreInteractionStateWithUserActivity:userActivity]; + + OCMVerify(times(1), [mockLifecycleDelegate scene:scene + restoreInteractionStateWithUserActivity:userActivity]); +} + - (void)testWindowScenePerformActionForShortcutItem { [self setupMockApplication]; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm index 0b17e5a24b5f5..bb84bebddfa78 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm @@ -257,6 +257,62 @@ - (void)scene:(UIScene*)scene continueUserActivity:(NSUserActivity*)userActivity } } +#pragma mark - Saving the state of the scene + +- (NSUserActivity*)stateRestorationActivityForScene:(UIScene*)scene { + // Saves state per FlutterViewController. + NSUserActivity* activity = scene.userActivity; + if (!activity) { + activity = [[NSUserActivity alloc] initWithActivityType:scene.session.configuration.name]; + } + + [self updateEnginesInScene:scene]; + int64_t appBundleModifiedTime = FlutterSharedApplication.lastAppModificationTime; + for (FlutterEngine* engine in [_engines allObjects]) { + FlutterViewController* vc = (FlutterViewController*)engine.viewController; + NSString* restorationId = vc.restorationIdentifier; + if (restorationId) { + NSData* restorationData = [engine.restorationPlugin restorationData]; + if (restorationData) { + [activity addUserInfoEntriesFromDictionary:@{restorationId : restorationData}]; + [activity addUserInfoEntriesFromDictionary:@{ + kRestorationStateAppModificationKey : [NSNumber numberWithLongLong:appBundleModifiedTime] + }]; + } + } + } + + return activity; +} + +- (void)scene:(UIScene*)scene + restoreInteractionStateWithUserActivity:(NSUserActivity*)stateRestorationActivity { + // Restores state per FlutterViewController. + NSDictionary* userInfo = stateRestorationActivity.userInfo; + [self updateEnginesInScene:scene]; + int64_t appBundleModifiedTime = FlutterSharedApplication.lastAppModificationTime; + NSNumber* stateDateNumber = userInfo[kRestorationStateAppModificationKey]; + int64_t stateDate = 0; + if (stateDateNumber && [stateDateNumber isKindOfClass:[NSNumber class]]) { + stateDate = [stateDateNumber longLongValue]; + } + if (appBundleModifiedTime != stateDate) { + // Don't restore state if the app has been re-installed since the state was last saved + return; + } + + for (FlutterEngine* engine in [_engines allObjects]) { + UIViewController* vc = (UIViewController*)engine.viewController; + NSString* restorationId = vc.restorationIdentifier; + if (restorationId) { + NSData* restorationData = userInfo[restorationId]; + if ([restorationData isKindOfClass:[NSData class]]) { + [engine.restorationPlugin setRestorationData:restorationData]; + } + } + } +} + #pragma mark - Performing tasks - (void)windowScene:(UIWindowScene*)windowScene diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm index f875079616cc6..482f4e0ebcb66 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm @@ -4,11 +4,14 @@ #import #import +#import #import #include #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterRestorationPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Test.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h" @@ -778,6 +781,126 @@ - (void)testSceneContinueUserActivityHandledByUniversalLinks { OCMVerify(times(1), [mockEngine sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); } +- (void)testStateRestorationActivityForScene { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mocks = [self mocksForEvents]; + id mockEngine = mocks[@"mockEngine"]; + id mockViewController = OCMClassMock([FlutterViewController class]); + id mockRestorationPlugin = OCMClassMock([FlutterRestorationPlugin class]); + OCMStub([mockEngine viewController]).andReturn(mockViewController); + OCMStub([mockEngine restorationPlugin]).andReturn(mockRestorationPlugin); + + NSString* restorationId = @"restorationId"; + NSString* mockDataString = @"mockData"; + NSString* configName = @"ConfigurationName"; + NSData* mockData = [mockDataString dataUsingEncoding:NSUTF8StringEncoding]; + OCMStub([mockViewController restorationIdentifier]).andReturn(restorationId); + OCMStub([mockRestorationPlugin restorationData]).andReturn(mockData); + + id mockScene = mocks[@"mockScene"]; + id mockSession = OCMClassMock([UISceneSession class]); + id mockConfiguration = OCMClassMock([UISceneConfiguration class]); + OCMStub([mockScene session]).andReturn(mockSession); + OCMStub([mockSession configuration]).andReturn(mockConfiguration); + OCMStub([mockConfiguration name]).andReturn(configName); + + [delegate addFlutterEngine:mockEngine]; + XCTAssertEqual(delegate.engines.count, 1.0); + NSUserActivity* state = [delegate stateRestorationActivityForScene:mockScene]; + XCTAssertEqual(state.userInfo[restorationId], mockData); + XCTAssertEqual(state.activityType, configName); +} + +- (void)testSceneRestoreInteractionStateWithUserActivity { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + id mockBundle = OCMPartialMock([NSBundle mainBundle]); + id mockURL = OCMClassMock([NSURL class]); + id mockFileDate = OCMClassMock([NSDate class]); + int64_t mockBundleDateNum = 1; + + OCMStub([mockBundle executableURL]).andReturn(mockURL); + OCMStub([mockURL getResourceValue:[OCMArg setTo:mockFileDate] + forKey:NSURLContentModificationDateKey + error:[OCMArg setTo:nil]]); + OCMStub([mockFileDate timeIntervalSince1970]).andReturn(mockBundleDateNum); + + id mocks = [self mocksForEvents]; + id mockEngine = mocks[@"mockEngine"]; + id mockViewController = OCMClassMock([FlutterViewController class]); + id mockRestorationPlugin = OCMClassMock([FlutterRestorationPlugin class]); + OCMStub([mockEngine viewController]).andReturn(mockViewController); + OCMStub([mockEngine restorationPlugin]).andReturn(mockRestorationPlugin); + + NSString* restorationId = @"restorationId"; + NSString* mockDataString = @"teststring"; + NSNumber* mockBundleStateDateNum = @1.0; + NSData* mockData = [mockDataString dataUsingEncoding:NSUTF8StringEncoding]; + OCMStub([mockViewController restorationIdentifier]).andReturn(restorationId); + OCMStub([mockRestorationPlugin restorationData]).andReturn(mockData); + + id mockScene = mocks[@"mockScene"]; + + id userActivity = OCMClassMock([NSUserActivity class]); + NSDictionary* mockUserInfo = @{ + @"mod-date" : mockBundleStateDateNum, + restorationId : mockData, + }; + OCMStub([userActivity userInfo]).andReturn(mockUserInfo); + + [delegate addFlutterEngine:mockEngine]; + XCTAssertEqual(delegate.engines.count, 1.0); + [delegate scene:mockScene restoreInteractionStateWithUserActivity:userActivity]; + OCMVerify(times(1), [mockRestorationPlugin setRestorationData:mockData]); + [mockBundle stopMocking]; +} + +- (void)testSceneDoesNotRestoreInteractionStateWithUserActivity { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + id mockBundle = OCMPartialMock([NSBundle mainBundle]); + id mockURL = OCMClassMock([NSURL class]); + id mockFileDate = OCMClassMock([NSDate class]); + int64_t mockBundleDateNum = 1; + + OCMStub([mockBundle executableURL]).andReturn(mockURL); + OCMStub([mockURL getResourceValue:[OCMArg setTo:mockFileDate] + forKey:NSURLContentModificationDateKey + error:[OCMArg setTo:nil]]); + OCMStub([mockFileDate timeIntervalSince1970]).andReturn(mockBundleDateNum); + + id mocks = [self mocksForEvents]; + id mockEngine = mocks[@"mockEngine"]; + id mockViewController = OCMClassMock([FlutterViewController class]); + id mockRestorationPlugin = OCMClassMock([FlutterRestorationPlugin class]); + OCMStub([mockEngine viewController]).andReturn(mockViewController); + OCMStub([mockEngine restorationPlugin]).andReturn(mockRestorationPlugin); + + NSString* restorationId = @"restorationId"; + NSString* mockDataString = @"teststring"; + NSNumber* mockBundleStateDateNum = @2.0; + NSData* mockData = [mockDataString dataUsingEncoding:NSUTF8StringEncoding]; + OCMStub([mockViewController restorationIdentifier]).andReturn(restorationId); + OCMStub([mockRestorationPlugin restorationData]).andReturn(mockData); + + id mockScene = mocks[@"mockScene"]; + + id userActivity = OCMClassMock([NSUserActivity class]); + NSDictionary* mockUserInfo = @{ + @"mod-date" : mockBundleStateDateNum, + restorationId : mockData, + }; + OCMStub([userActivity userInfo]).andReturn(mockUserInfo); + + [delegate addFlutterEngine:mockEngine]; + XCTAssertEqual(delegate.engines.count, 1.0); + [delegate scene:mockScene restoreInteractionStateWithUserActivity:userActivity]; + OCMVerify(times(0), [mockRestorationPlugin setRestorationData:mockData]); + [mockBundle stopMocking]; +} + - (void)testSceneContinueUserActivityHandledByNoPlugin { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h index c88a1be3607e8..c7ac6bb35455c 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h @@ -20,6 +20,11 @@ - (void)engine:(FlutterEngine*)engine receivedConnectNotificationFor:(UIScene*)scene; +- (NSUserActivity*)stateRestorationActivityForScene:(UIScene*)scene; + +- (void)scene:(UIScene*)scene + restoreInteractionStateWithUserActivity:(NSUserActivity*)stateRestorationActivity; + + (FlutterPluginSceneLifeCycleDelegate*)fromScene:(UIScene*)scene; @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h index 4dde0c3b2c05e..2d3d6484755dc 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h @@ -7,6 +7,8 @@ #import +extern NSString* const kRestorationStateAppModificationKey; + @interface FlutterSharedApplication : NSObject /** @@ -29,6 +31,8 @@ */ @property(class, nonatomic, readonly) BOOL hasSceneDelegate; +@property(class, nonatomic, readonly) int64_t lastAppModificationTime; + + (BOOL)isFlutterDeepLinkingEnabled; @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.mm index ec70e79200d45..6589a825dd0ef 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.mm @@ -9,6 +9,8 @@ FLUTTER_ASSERT_ARC +NSString* const kRestorationStateAppModificationKey = @"mod-date"; + @implementation FlutterSharedApplication + (BOOL)isAppExtension { @@ -45,6 +47,16 @@ + (BOOL)hasSceneDelegate { return NO; } ++ (int64_t)lastAppModificationTime { + NSDate* fileDate; + NSError* error = nil; + [[[NSBundle mainBundle] executableURL] getResourceValue:&fileDate + forKey:NSURLContentModificationDateKey + error:&error]; + NSAssert(error == nil, @"Cannot obtain modification date of main bundle: %@", error); + return [fileDate timeIntervalSince1970]; +} + + (BOOL)isFlutterDeepLinkingEnabled { // Developers may disable deep linking through their Info.plist if they are using a plugin that // handles deeplinking instead. From bd81ae6f02668e28846d964ed86cb22b4c3ded1e Mon Sep 17 00:00:00 2001 From: "John \"codefu\" McDole" Date: Fri, 3 Oct 2025 12:20:09 -0700 Subject: [PATCH 056/204] fix: delay exiting microbenchmark (#176477) Give the collecting process a chance to catch Done --- .../microbenchmarks/lib/benchmark_collection.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dev/benchmarks/microbenchmarks/lib/benchmark_collection.dart b/dev/benchmarks/microbenchmarks/lib/benchmark_collection.dart index b749306385e9c..ecca22fa0d35c 100644 --- a/dev/benchmarks/microbenchmarks/lib/benchmark_collection.dart +++ b/dev/benchmarks/microbenchmarks/lib/benchmark_collection.dart @@ -119,5 +119,12 @@ Future main() async { } print('\n\n╡ ••• Done ••• ╞\n\n'); + + // Ensure stdout buffers are flushed so the collecting process gets Done + await stdout.flush(); + + // Now we're just being paranoid here and letting the process churn through + // log lines before handling the exit code. + await Future.delayed(const Duration(seconds: 5)); exit(0); } From 4fbe0fb450d44db3381f914e06a7b393b30ce7d7 Mon Sep 17 00:00:00 2001 From: Alexander Aprelev Date: Fri, 3 Oct 2025 13:51:08 -0700 Subject: [PATCH 057/204] Align flutter dependencies with ones coming from dart. (#176475) Manual workaround for https://github.com/flutter/flutter/issues/176472 --- DEPS | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/DEPS b/DEPS index 7232f6c7070e7..f0283f0e13ec4 100644 --- a/DEPS +++ b/DEPS @@ -60,26 +60,26 @@ vars = { # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py - 'dart_ai_rev': '901e2cea44bde868061cae58269cf8cc583a8a78', + 'dart_ai_rev': 'ec5d6aa38c8a6a9e687cc5223e2592ac91f2d5fe', 'dart_binaryen_rev': '1d2e23d5e55788091a51420ba3a9889d4efe7509', - 'dart_boringssl_rev': '40e035a9e5d721b3b7c15c46259d782ffe7d9e96', + 'dart_boringssl_rev': 'db638238d29708a20b991af3b2488e45a8bbcf71', 'dart_core_rev': '5c3e2c38df268be2347f3aad30ced0147dd012bb', 'dart_devtools_rev': '5e1792245005088a0a0dfe28f207bd22045ba783', 'dart_ecosystem_rev': '36e514d52c5d056227e4cea98b784ade50b5b4f1', - 'dart_http_rev': 'e0dadd16e01bb4611036f4946ed480dac2d59dca', - 'dart_i18n_rev': '09627d28f97e05858e1ba5c6e45ff45d56b1e786', + 'dart_http_rev': '2c53fa3c558ec5d1dd9fce4360d435113dba11e5', + 'dart_i18n_rev': '34d1832b7e65d9aef1f7f6a82c22f6e53476191c', 'dart_libprotobuf_rev': '24487dd1045c7f3d64a21f38a3f0c06cc4cf2edb', 'dart_perfetto_rev': '13ce0c9e13b0940d2476cd0cff2301708a9a2e2b', 'dart_protobuf_gn_rev': 'ca669f79945418f6229e4fef89b666b2a88cbb10', 'dart_protobuf_rev': '14bbd0bd7ff9b7e322ff4e85bd243f6905170b92', 'dart_pub_rev': 'f7f1891e2de3d795532f45ec214f88ac912ffcd6', 'dart_sync_http_rev': '6666fff944221891182e1f80bf56569338164d72', - 'dart_tools_rev': '2ef298e48450de9d2443d466ec4d61172a71b113', - 'dart_vector_math_rev': '3939545edc38ed657381381d33acde02c49ff827', - 'dart_web_rev': '0baaea4de4dfd60db85dd112d8cc7480d0dd8bd8', - 'dart_webdev_rev': '23aefebea46c1f94c27703743b2c8db1f651bf29', + 'dart_tools_rev': '19f91a030f5cef908820efade7e7638aea0248ac', + 'dart_vector_math_rev': 'a7b7e9ccb931348dbfa669e0f8fea1bf97705b16', + 'dart_web_rev': '816abcc1bf186f61c7e66e7f4c56d1554a61ab27', + 'dart_webdev_rev': '0b2a408f6f64a29cd0d18ac7d2d407a4e1db8e0f', 'dart_webdriver_rev': '09104f459ed834d48b132f6b7734923b1fbcf2e9', - 'dart_webkit_inspection_protocol_rev': 'effa75205516757795683d527c3dea9546eb0c32', + 'dart_webkit_inspection_protocol_rev': '0f7685804d77ec02c6564d7ac1a6c8a2341c5bdf', 'ocmock_rev': 'c4ec0e3a7a9f56cfdbd0aa01f4f97bb4b75c5ef8', # v3.7.1 @@ -329,7 +329,7 @@ deps = { Var('dart_git') + '/leak_tracker.git@f5620600a5ce1c44f65ddaa02001e200b096e14c', 'engine/src/flutter/third_party/dart/third_party/pkg/native': - Var('dart_git') + '/native.git@400c7001de7a3cddca341e728163bd64029ab288', + Var('dart_git') + '/native.git@3ec573500f743d4a1393f7802143aef50fec0a47', 'engine/src/flutter/third_party/dart/third_party/pkg/protobuf': Var('dart_git') + '/protobuf.git' + '@' + Var('dart_protobuf_rev'), @@ -338,7 +338,7 @@ deps = { Var('dart_git') + '/pub.git' + '@' + Var('dart_pub_rev'), 'engine/src/flutter/third_party/dart/third_party/pkg/shelf': - Var('dart_git') + '/shelf.git@de91a5b8c1de05f622c0c6f1eab38d5f31e0113f', + Var('dart_git') + '/shelf.git@f30d65034a868530a9aa9ada7c3067d22fa01185', 'engine/src/flutter/third_party/dart/third_party/pkg/sync_http': Var('dart_git') + '/sync_http.git' + '@' + Var('dart_sync_http_rev'), @@ -347,7 +347,7 @@ deps = { Var('dart_git') + '/external/github.com/simolus3/tar.git@13479f7c2a18f499e840ad470cfcca8c579f6909', 'engine/src/flutter/third_party/dart/third_party/pkg/test': - Var('dart_git') + '/test.git@b99d556ec6096965eb177111299c0783678200f6', + Var('dart_git') + '/test.git@a16f14975c5625ef99abc71f7e91bca5d8e55054', 'engine/src/flutter/third_party/dart/third_party/pkg/tools': Var('dart_git') + '/tools.git' + '@' + Var('dart_tools_rev'), From 4ff3ce67abcd44f025d88a6761df29841d609149 Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Fri, 3 Oct 2025 14:11:00 -0700 Subject: [PATCH 058/204] Starts updating the DEPS in preupload. (#176485) fixes https://github.com/flutter/flutter/issues/176472 testing: there is no automated testing for the preupload script. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .autoroller-preupload.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.autoroller-preupload.sh b/.autoroller-preupload.sh index 1701ce4e078d8..c725772eb52b5 100755 --- a/.autoroller-preupload.sh +++ b/.autoroller-preupload.sh @@ -21,6 +21,11 @@ WORKING_DIR="$REPO_PATH/engine/src/flutter" LICENSES_PATH="$REPO_PATH/engine/src/flutter/sky/packages/sky_engine/LICENSE" DATA_PATH="$REPO_PATH/engine/src/flutter/tools/licenses_cpp/data" +cd "$REPO_PATH/engine/src" +./tools/dart/create_updated_flutter_deps.py +cd "$REPO_PATH" +gclient sync -D + # This calls `gn gen`. "$GN" --runtime-mode profile --no-goma --no-rbe --enable-minimal-linux ninja -C "$PROFILE_PATH" licenses_cpp From 9537c54ae7660d515ad6531edc29ccbffe2228cb Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 3 Oct 2025 17:15:07 -0400 Subject: [PATCH 059/204] Roll Skia from b842026480e0 to a454242c3934 (3 revisions) (#176484) https://skia.googlesource.com/skia.git/+log/b842026480e0..a454242c3934 2025-10-03 thomsmit@google.com Reland "Reland "[graphite] Extracts early in drawGeometry"" 2025-10-03 thomsmit@google.com [graphite] Add all blends to NotifyInUseTests 2025-10-03 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 7bc87a4a45cf to edacf5135c8d (4 revisions) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index f0283f0e13ec4..5059fc407f7be 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'b842026480e0d5e5547e5d6359a6a44e5692aff1', + 'skia_revision': 'a454242c39341d8866fb76dad426318b1b01296e', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From a23805768d680317d32e0b3f57d847cc0e9b82e8 Mon Sep 17 00:00:00 2001 From: davidhicks980 <59215665+davidhicks980@users.noreply.github.com> Date: Fri, 3 Oct 2025 19:32:30 -0400 Subject: [PATCH 060/204] [material/menu_anchor.dart] Check for reserved padding updates on layout delegate. (#176457) This PR diffs reservedPadding when checking whether a menu anchor's SingleChildLayoutDelegate should relayout. Resolves https://github.com/flutter/flutter/issues/176456 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../flutter/lib/src/material/menu_anchor.dart | 1 + .../test/material/menu_anchor_test.dart | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/packages/flutter/lib/src/material/menu_anchor.dart b/packages/flutter/lib/src/material/menu_anchor.dart index 7dca841f764c3..89144d7f959f7 100644 --- a/packages/flutter/lib/src/material/menu_anchor.dart +++ b/packages/flutter/lib/src/material/menu_anchor.dart @@ -3085,6 +3085,7 @@ class _MenuLayout extends SingleChildLayoutDelegate { menuPadding != oldDelegate.menuPadding || orientation != oldDelegate.orientation || parentOrientation != oldDelegate.parentOrientation || + reservedPadding != oldDelegate.reservedPadding || !setEquals(avoidBounds, oldDelegate.avoidBounds); } diff --git a/packages/flutter/test/material/menu_anchor_test.dart b/packages/flutter/test/material/menu_anchor_test.dart index 7ef98382433cd..2297977e66621 100644 --- a/packages/flutter/test/material/menu_anchor_test.dart +++ b/packages/flutter/test/material/menu_anchor_test.dart @@ -5096,6 +5096,34 @@ void main() { expect(tester.getRect(findMenuPanels()).width, 800.0 - reservedPadding.horizontal); }); + + testWidgets('Layout updates when reserved padding changes', (WidgetTester tester) async { + const EdgeInsetsGeometry reservedPadding = EdgeInsets.symmetric(horizontal: 13.0); + + await tester.pumpWidget( + MaterialApp( + home: MenuAnchor( + controller: controller, + menuChildren: const [SizedBox(width: 800, height: 24)], + ), + ), + ); + + controller.open(position: Offset.zero); + await tester.pump(); + + await tester.pumpWidget( + MaterialApp( + home: MenuAnchor( + controller: controller, + reservedPadding: reservedPadding, + menuChildren: const [SizedBox(width: 800, height: 24)], + ), + ), + ); + + expect(tester.getRect(findMenuPanels()).width, 800.0 - reservedPadding.horizontal); + }); } List createTestMenus({ From 31c7177fab3eab45a5d069371f909e4d174997ed Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 3 Oct 2025 21:06:33 -0400 Subject: [PATCH 061/204] Roll Skia from a454242c3934 to 9cda1a2050c4 (2 revisions) (#176499) https://skia.googlesource.com/skia.git/+log/a454242c3934..9cda1a2050c4 2025-10-03 michaelludwig@google.com [graphite] Remove assert in VellComputeStep 2025-10-03 michaelludwig@google.com [graphite] Move functionality into base BufferWriter If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 5059fc407f7be..e8c1b026d8633 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'a454242c39341d8866fb76dad426318b1b01296e', + 'skia_revision': '9cda1a2050c4773e9956ba2dcf5a57e2ee286f63', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From a9ea4b412622bb49be103a48483952de24aeb7d7 Mon Sep 17 00:00:00 2001 From: "John \"codefu\" McDole" Date: Fri, 3 Oct 2025 19:43:22 -0700 Subject: [PATCH 062/204] fix: support older git (ubuntu 22.04) in content hash (#176321) content aware hash was using --format which isn't present in older, but still supported operating systems. this pr removes the format string which basically reduces the output before hasing to: ```shell 100644 blob 198d80926b6e873c327f71350a0cdefee6a8402f DEPS 040000 tree 139c1f10f92e4b9d4ac3ec7d4d27b2aa9775c5cd engine ``` this format is still stable across all platforms and passed into `git hash-object` - which produces the actual hash fingerprint of the engine. safety: this is the only scripts that produce this hash, so all downstream consumers keep consuming a sha1 output. Since this changes the sha, an engine version shouldn't exist for it and cocoon will build the artifacts for it. fixes: #175265 --- bin/internal/content_aware_hash.ps1 | 2 +- bin/internal/content_aware_hash.sh | 2 +- dev/tools/test/content_aware_hash_test.dart | 46 ++++++++++----------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/bin/internal/content_aware_hash.ps1 b/bin/internal/content_aware_hash.ps1 index 4b315d7493895..a8439ae879190 100644 --- a/bin/internal/content_aware_hash.ps1 +++ b/bin/internal/content_aware_hash.ps1 @@ -79,6 +79,6 @@ if (($currentBranch -ne "main") -and # 3. Out-File -NoNewline -Encoding ascii outputs 8bit ascii # 4. git hash-object with stdin from a pipeline consumes UTF-16, so consume #. the contents of hash.txt -(git -C "$flutterRoot" ls-tree --format "%(objectname) %(path)" "$baseRef" -- $trackedFiles | Out-String) -replace "`r`n", "`n" | Out-File -NoNewline -Encoding ascii hash.txt +(git -C "$flutterRoot" ls-tree "$baseRef" -- $trackedFiles | Out-String) -replace "`r`n", "`n" | Out-File -NoNewline -Encoding ascii hash.txt git hash-object hash.txt Remove-Item hash.txt diff --git a/bin/internal/content_aware_hash.sh b/bin/internal/content_aware_hash.sh index 07c67869ae752..b5e0945c63961 100755 --- a/bin/internal/content_aware_hash.sh +++ b/bin/internal/content_aware_hash.sh @@ -68,4 +68,4 @@ if [[ "$CURRENT_BRANCH" != "main" && \ fi fi -git -C "$FLUTTER_ROOT" ls-tree --format "%(objectname) %(path)" "$BASEREF" -- "${TRACKEDFILES[@]}" | git hash-object --stdin +git -C "$FLUTTER_ROOT" ls-tree "$BASEREF" -- "${TRACKEDFILES[@]}" | git hash-object --stdin diff --git a/dev/tools/test/content_aware_hash_test.dart b/dev/tools/test/content_aware_hash_test.dart index a24220034de3f..a29fda95a8adf 100644 --- a/dev/tools/test/content_aware_hash_test.dart +++ b/dev/tools/test/content_aware_hash_test.dart @@ -198,22 +198,22 @@ void main() { test('generates a hash or upstream/master', () async { initGitRepoWithBlankInitialCommit(); - expect(runContentAwareHash(), processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814')); + expect(runContentAwareHash(), processStdout('fa69812cddffc076be3aa477a93942cb8d233ccc')); }); test('generates a hash for origin/master', () { initGitRepoWithBlankInitialCommit(remote: 'origin'); - expect(runContentAwareHash(), processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814')); + expect(runContentAwareHash(), processStdout('fa69812cddffc076be3aa477a93942cb8d233ccc')); }); test('generates a hash for origin/main', () { initGitRepoWithBlankInitialCommit(remote: 'origin', branch: 'main'); - expect(runContentAwareHash(), processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814')); + expect(runContentAwareHash(), processStdout('fa69812cddffc076be3aa477a93942cb8d233ccc')); }); test('generates a hash for upstream/main', () { initGitRepoWithBlankInitialCommit(branch: 'main'); - expect(runContentAwareHash(), processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814')); + expect(runContentAwareHash(), processStdout('fa69812cddffc076be3aa477a93942cb8d233ccc')); }); test('generates a hash for CI/CD from HEAD', () { @@ -232,7 +232,7 @@ void main() { // Simulate being in a LUCI environment. environment['LUCI_CI'] = 'true'; - expect(runContentAwareHash(), processStdout('f049fdcd4300c8c0d5041b5e35b3d11c2d289bdf')); + expect(runContentAwareHash(), processStdout('63a6c6dc494d9a2fc3e78e8505e878d129429246')); }); test('generates a hash based on merge-base in local detached HEAD', () { @@ -249,14 +249,14 @@ void main() { equals('HEAD'), ); - expect(runContentAwareHash(), processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814')); + expect(runContentAwareHash(), processStdout('fa69812cddffc076be3aa477a93942cb8d233ccc')); }); group('stable branches calculate hash locally', () { test('with no changes', () { initGitRepoWithBlankInitialCommit(branch: 'main'); gitSwitchBranch('stable'); - expect(runContentAwareHash(), processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814')); + expect(runContentAwareHash(), processStdout('fa69812cddffc076be3aa477a93942cb8d233ccc')); }); test('with engine changes', () { @@ -264,7 +264,7 @@ void main() { gitSwitchBranch('stable'); writeFileAndCommit(testRoot.deps, 'deps changed'); - expect(runContentAwareHash(), processStdout('f049fdcd4300c8c0d5041b5e35b3d11c2d289bdf')); + expect(runContentAwareHash(), processStdout('63a6c6dc494d9a2fc3e78e8505e878d129429246')); }); }); @@ -272,7 +272,7 @@ void main() { test('with no changes', () { initGitRepoWithBlankInitialCommit(branch: 'main'); gitSwitchBranch('beta'); - expect(runContentAwareHash(), processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814')); + expect(runContentAwareHash(), processStdout('fa69812cddffc076be3aa477a93942cb8d233ccc')); }); test('with engine changes', () { @@ -280,7 +280,7 @@ void main() { gitSwitchBranch('beta'); writeFileAndCommit(testRoot.deps, 'deps changed'); - expect(runContentAwareHash(), processStdout('f049fdcd4300c8c0d5041b5e35b3d11c2d289bdf')); + expect(runContentAwareHash(), processStdout('63a6c6dc494d9a2fc3e78e8505e878d129429246')); }); }); @@ -288,7 +288,7 @@ void main() { test('with no changes', () { initGitRepoWithBlankInitialCommit(branch: 'main'); gitSwitchBranch('flutter-4.35-candidate.2'); - expect(runContentAwareHash(), processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814')); + expect(runContentAwareHash(), processStdout('fa69812cddffc076be3aa477a93942cb8d233ccc')); }); test('with engine changes', () { @@ -296,7 +296,7 @@ void main() { gitSwitchBranch('flutter-4.35-candidate.2'); writeFileAndCommit(testRoot.deps, 'deps changed'); - expect(runContentAwareHash(), processStdout('f049fdcd4300c8c0d5041b5e35b3d11c2d289bdf')); + expect(runContentAwareHash(), processStdout('63a6c6dc494d9a2fc3e78e8505e878d129429246')); }); }); @@ -307,7 +307,7 @@ void main() { ); writeFileAndCommit(testRoot.deps, 'deps changed'); - expect(runContentAwareHash(), processStdout('f049fdcd4300c8c0d5041b5e35b3d11c2d289bdf')); + expect(runContentAwareHash(), processStdout('63a6c6dc494d9a2fc3e78e8505e878d129429246')); }); test('generates a hash for shallow clones', () { @@ -317,7 +317,7 @@ void main() { .childFile(localFs.path.joinAll('.git/shallow'.split('/'))) .writeAsStringSync(headSha); writeFileAndCommit(testRoot.deps, 'deps changed'); - expect(runContentAwareHash(), processStdout('f049fdcd4300c8c0d5041b5e35b3d11c2d289bdf')); + expect(runContentAwareHash(), processStdout('63a6c6dc494d9a2fc3e78e8505e878d129429246')); }); group('ignores local engine for', () { @@ -327,14 +327,14 @@ void main() { testRoot.deps.writeAsStringSync('deps changed'); expect( runContentAwareHash(), - processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814'), + processStdout('fa69812cddffc076be3aa477a93942cb8d233ccc'), reason: 'content hash from master for non-committed file', ); writeFileAndCommit(testRoot.deps, 'deps changed'); expect( runContentAwareHash(), - processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814'), + processStdout('fa69812cddffc076be3aa477a93942cb8d233ccc'), reason: 'content hash from master for committed file', ); }); @@ -345,14 +345,14 @@ void main() { testRoot.deps.writeAsStringSync('deps changed'); expect( runContentAwareHash(), - processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814'), + processStdout('fa69812cddffc076be3aa477a93942cb8d233ccc'), reason: 'content hash from master for non-committed file', ); writeFileAndCommit(testRoot.deps, 'deps changed'); expect( runContentAwareHash(), - processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814'), + processStdout('fa69812cddffc076be3aa477a93942cb8d233ccc'), reason: 'content hash from master for committed file', ); }); @@ -365,12 +365,12 @@ void main() { test('DEPS is changed', () async { writeFileAndCommit(testRoot.deps, 'deps changed'); - expect(runContentAwareHash(), processStdout('f049fdcd4300c8c0d5041b5e35b3d11c2d289bdf')); + expect(runContentAwareHash(), processStdout('63a6c6dc494d9a2fc3e78e8505e878d129429246')); }); test('an engine file changes', () async { writeFileAndCommit(testRoot.engineReadMe, 'engine file changed'); - expect(runContentAwareHash(), processStdout('49e58f425cb039e745614d7ea10c369387c43681')); + expect(runContentAwareHash(), processStdout('bc993ee46320d3831092bc2c3dd86881d5c15d5f')); }); test('a new engine file is added', () async { @@ -394,14 +394,14 @@ void main() { testRoot.contentAwareHashPs1.parent.childFile('release-candidate-branch.version'), 'sup', ); - expect(runContentAwareHash(), processStdout('3b81cd2164f26a8db3271d46c7022c159193417d')); + expect(runContentAwareHash(), processStdout('ec994692b9e9610655484436cecd691cecee4c78')); }); }); test('does not hash non-engine files', () async { initGitRepoWithBlankInitialCommit(); testRoot.flutterReadMe.writeAsStringSync('codefu was here'); - expect(runContentAwareHash(), processStdout('3bbeb6a394378478683ece4f8e8663c42f8dc814')); + expect(runContentAwareHash(), processStdout('fa69812cddffc076be3aa477a93942cb8d233ccc')); }); test('missing merge-base defaults to HEAD', () { @@ -413,7 +413,7 @@ void main() { writeFileAndCommit(testRoot.deps, 'deps changed'); expect( runContentAwareHash(), - processStdout('f049fdcd4300c8c0d5041b5e35b3d11c2d289bdf'), + processStdout('63a6c6dc494d9a2fc3e78e8505e878d129429246'), reason: 'content hash from HEAD when no merge-base', ); }); From 71164f3d8d907c8fe7a884cec0e8f73be288572c Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Fri, 3 Oct 2025 23:35:34 -0400 Subject: [PATCH 063/204] Roll Skia from 9cda1a2050c4 to f316de3d47b4 (2 revisions) (#176504) https://skia.googlesource.com/skia.git/+log/9cda1a2050c4..f316de3d47b4 2025-10-04 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2025-10-03 mike@reedtribe.org Return optional raw for path/builder If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index e8c1b026d8633..e73354656746c 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': '9cda1a2050c4773e9956ba2dcf5a57e2ee286f63', + 'skia_revision': 'f316de3d47b43f9f7ed6a241f7df2e6aba5f5940', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From a2110f40357f516ad99938fed62a7d596055f904 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sat, 4 Oct 2025 02:47:17 -0400 Subject: [PATCH 064/204] Roll Dart SDK from 0009748aed50 to 9bc52df78b67 (4 revisions) (#176506) https://dart.googlesource.com/sdk.git/+log/0009748aed50..9bc52df78b67 2025-10-04 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-282.0.dev 2025-10-04 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-281.0.dev 2025-10-03 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-280.0.dev 2025-10-03 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-279.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jimgraham@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index e73354656746c..5efe9fdd18f6a 100644 --- a/DEPS +++ b/DEPS @@ -56,13 +56,13 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '0009748aed5088a399c1e1929d7a440f96c2164c', + 'dart_revision': '9bc52df78b67abd6d4666cef655f6caa739803ff', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py 'dart_ai_rev': 'ec5d6aa38c8a6a9e687cc5223e2592ac91f2d5fe', 'dart_binaryen_rev': '1d2e23d5e55788091a51420ba3a9889d4efe7509', - 'dart_boringssl_rev': 'db638238d29708a20b991af3b2488e45a8bbcf71', + 'dart_boringssl_rev': '706742e482d89214f13a642ccfcdad596a24a32f', 'dart_core_rev': '5c3e2c38df268be2347f3aad30ced0147dd012bb', 'dart_devtools_rev': '5e1792245005088a0a0dfe28f207bd22045ba783', 'dart_ecosystem_rev': '36e514d52c5d056227e4cea98b784ade50b5b4f1', From 067d793084d114bea9ba85780c68264aaf01be91 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Sat, 4 Oct 2025 08:55:27 +0200 Subject: [PATCH 065/204] Fix TextFormField does not inherit local InputDecorationTheme (#176397) ## Description This PR replaces global `ThemeData.inputDecorationTheme` usage in `TextFormField` with `InputDecorationTheme.of ` which returns the ambient `InputDecorationTheme`. It is a follow up to https://github.com/flutter/flutter/pull/168981 which introduces `InputDecorationTheme.of `. ## Related Issue Fixes [TextFormField does not inherit local InputDecorationTheme](https://github.com/flutter/flutter/issues/176391) ## Tests - Adds 1 test --- .../input_decoration.widget_state.1.dart | 17 +++++------- .../lib/src/material/text_form_field.dart | 5 ++-- .../test/material/text_form_field_test.dart | 27 +++++++++++++++++++ 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/examples/api/lib/material/input_decorator/input_decoration.widget_state.1.dart b/examples/api/lib/material/input_decorator/input_decoration.widget_state.1.dart index 7a2ba13a4efb4..d9568c1ec604d 100644 --- a/examples/api/lib/material/input_decorator/input_decoration.widget_state.1.dart +++ b/examples/api/lib/material/input_decorator/input_decoration.widget_state.1.dart @@ -27,17 +27,12 @@ class MaterialStateExample extends StatelessWidget { @override Widget build(BuildContext context) { - final ThemeData themeData = Theme.of(context); - return Theme( - data: themeData.copyWith( - inputDecorationTheme: themeData.inputDecorationTheme.copyWith( - prefixIconColor: const WidgetStateColor.fromMap({ - WidgetState.error: Colors.red, - WidgetState.focused: Colors.blue, - WidgetState.any: Colors.grey, - }), - ), - ), + return InputDecorationTheme( + prefixIconColor: const WidgetStateColor.fromMap({ + WidgetState.error: Colors.red, + WidgetState.focused: Colors.blue, + WidgetState.any: Colors.grey, + }), child: TextFormField( initialValue: 'example.com', decoration: const InputDecoration(prefixIcon: Icon(Icons.web)), diff --git a/packages/flutter/lib/src/material/text_form_field.dart b/packages/flutter/lib/src/material/text_form_field.dart index 3815b2aff5140..7b8b1106dfbac 100644 --- a/packages/flutter/lib/src/material/text_form_field.dart +++ b/packages/flutter/lib/src/material/text_form_field.dart @@ -12,7 +12,6 @@ import 'adaptive_text_selection_toolbar.dart'; import 'input_decorator.dart'; import 'material_state.dart'; import 'text_field.dart'; -import 'theme.dart'; export 'package:flutter/services.dart' show SmartDashesType, SmartQuotesType; @@ -42,7 +41,7 @@ export 'package:flutter/services.dart' show SmartDashesType, SmartQuotesType; /// when it is no longer needed. This will ensure any resources used by the object /// are discarded. /// -/// By default, `decoration` will apply the [ThemeData.inputDecorationTheme] for +/// By default, `decoration` will apply the ambient [InputDecorationThemeData] for /// the current context to the [InputDecoration], see /// [InputDecoration.applyDefaults]. /// @@ -213,7 +212,7 @@ class TextFormField extends FormField { builder: (FormFieldState field) { final _TextFormFieldState state = field as _TextFormFieldState; InputDecoration effectiveDecoration = (decoration ?? const InputDecoration()) - .applyDefaults(Theme.of(field.context).inputDecorationTheme); + .applyDefaults(InputDecorationTheme.of(field.context)); final String? errorText = field.errorText; if (errorText != null) { diff --git a/packages/flutter/test/material/text_form_field_test.dart b/packages/flutter/test/material/text_form_field_test.dart index b87c8aa14ad2b..7eb3c20be1293 100644 --- a/packages/flutter/test/material/text_form_field_test.dart +++ b/packages/flutter/test/material/text_form_field_test.dart @@ -1848,4 +1848,31 @@ void main() { variant: TargetPlatformVariant.all(), skip: kIsWeb, // [intended] on web the browser handles the context menu. ); + + // Regression test for https://github.com/flutter/flutter/issues/176391. + testWidgets('TextFormField can inherit decoration from local InputDecorationThemeData', ( + WidgetTester tester, + ) async { + const InputDecoration decoration = InputDecoration(labelText: 'Label'); + const InputDecorationThemeData decorationTheme = InputDecorationThemeData( + labelStyle: TextStyle(color: Colors.green), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: InputDecorationTheme( + data: decorationTheme, + child: TextFormField(decoration: decoration), + ), + ), + ), + ); + + final InputDecorator decorator = tester.widget(find.byType(InputDecorator)); + final InputDecoration expectedDecoration = decoration + .applyDefaults(decorationTheme) + .copyWith(enabled: true, hintMaxLines: 1); + expect(decorator.decoration, expectedDecoration); + }); } From 49cca6ab8e664549a1104ce616b6e60d23dc61b9 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sat, 4 Oct 2025 06:44:35 -0400 Subject: [PATCH 066/204] Roll Fuchsia Linux SDK from HUhTcRn-LUXa2Salu... to oWcBvgdpdlGvaqiDg... (#176515) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-linux-sdk-flutter Please CC jimgraham@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 5efe9fdd18f6a..2539d710d1a76 100644 --- a/DEPS +++ b/DEPS @@ -807,7 +807,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'HUhTcRn-LUXa2Salu3L5Y7tBlXgCO-PziyhhTArrFxkC' + 'version': 'oWcBvgdpdlGvaqiDgn03ryyJQHiGYFX4lUksSWjfsx0C' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', From d15d198799e5a3f63c516eee69ff705451f50f58 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sat, 4 Oct 2025 14:30:27 -0400 Subject: [PATCH 067/204] Roll Dart SDK from 9bc52df78b67 to 53aeaeb2454c (1 revision) (#176525) https://dart.googlesource.com/sdk.git/+log/9bc52df78b67..53aeaeb2454c 2025-10-04 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-283.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jimgraham@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 2539d710d1a76..86c0178c7f403 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '9bc52df78b67abd6d4666cef655f6caa739803ff', + 'dart_revision': '53aeaeb2454cbcf3edb6a9c97881a92414bfe01e', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From f80d8169acf4c3fb97c46e0f5574acd40a2894da Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sat, 4 Oct 2025 16:41:26 -0400 Subject: [PATCH 068/204] Roll Skia from f316de3d47b4 to 5479115ef5bf (4 revisions) (#176529) https://skia.googlesource.com/skia.git/+log/f316de3d47b4..5479115ef5bf 2025-10-04 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from a45aa5b3843e to 207fff6486db (1 revision) 2025-10-04 michaelludwig@google.com [graphite] Add findOrCreateScratchBuffer to ResourceProvider 2025-10-04 thomsmit@google.com [graphite] add missing include in VelloComputeSteps.h 2025-10-04 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from edacf5135c8d to a45aa5b3843e (8 revisions) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC jimgraham@google.com,kjlubick@google.com,maxhudnell@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 86c0178c7f403..87c1b4a2dd317 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'f316de3d47b43f9f7ed6a241f7df2e6aba5f5940', + 'skia_revision': '5479115ef5bfc73266e94e69459da8e1da872fbe', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 6814c32a86384b5bf58d4dec882142a780a61191 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sat, 4 Oct 2025 22:19:37 -0400 Subject: [PATCH 069/204] Roll Dart SDK from 53aeaeb2454c to 016a8c0045fd (1 revision) (#176531) https://dart.googlesource.com/sdk.git/+log/53aeaeb2454c..016a8c0045fd 2025-10-05 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-284.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jimgraham@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 87c1b4a2dd317..8e55dc9d39445 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '53aeaeb2454cbcf3edb6a9c97881a92414bfe01e', + 'dart_revision': '016a8c0045fddf5be8f26a62037a088f150628e3', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From 3325024e6a1d3e4528d5dd9db39e6263a014427f Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sun, 5 Oct 2025 08:49:36 -0400 Subject: [PATCH 070/204] Roll Fuchsia Linux SDK from oWcBvgdpdlGvaqiDg... to Zm6K_3gP3VCaMy9rH... (#176538) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-linux-sdk-flutter Please CC jimgraham@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 8e55dc9d39445..d13974cb1fdcb 100644 --- a/DEPS +++ b/DEPS @@ -807,7 +807,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'oWcBvgdpdlGvaqiDgn03ryyJQHiGYFX4lUksSWjfsx0C' + 'version': 'Zm6K_3gP3VCaMy9rHnamFTPU-TenhAyCaja-ASWNSNIC' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', From 908012d58baa8f1b36b9bf324853bf77a963df8e Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sun, 5 Oct 2025 12:23:42 -0400 Subject: [PATCH 071/204] Roll Skia from 5479115ef5bf to 1fd0ca1f2120 (1 revision) (#176541) https://skia.googlesource.com/skia.git/+log/5479115ef5bf..1fd0ca1f2120 2025-10-05 skia-autoroll@skia-public.iam.gserviceaccount.com Roll SKP CIPD package from 533 to 534 If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC egdaniel@google.com,jimgraham@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index d13974cb1fdcb..619beacb65041 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': '5479115ef5bfc73266e94e69459da8e1da872fbe', + 'skia_revision': '1fd0ca1f2120982cb4adb145e765d5f933b61d22', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 5fcb8025b0705f1096a23d23243c77942a60e16d Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Sun, 5 Oct 2025 18:31:27 -0400 Subject: [PATCH 072/204] Roll Dart SDK from 016a8c0045fd to 898380a41c90 (1 revision) (#176549) https://dart.googlesource.com/sdk.git/+log/016a8c0045fd..898380a41c90 2025-10-05 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-285.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jimgraham@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DEPS b/DEPS index 619beacb65041..8a7460f0f2cd6 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '016a8c0045fddf5be8f26a62037a088f150628e3', + 'dart_revision': '898380a41c90a54dad599c582c9352490cd07b9d', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py @@ -74,7 +74,7 @@ vars = { 'dart_protobuf_rev': '14bbd0bd7ff9b7e322ff4e85bd243f6905170b92', 'dart_pub_rev': 'f7f1891e2de3d795532f45ec214f88ac912ffcd6', 'dart_sync_http_rev': '6666fff944221891182e1f80bf56569338164d72', - 'dart_tools_rev': '19f91a030f5cef908820efade7e7638aea0248ac', + 'dart_tools_rev': 'ce9d2ad5d246329af04cf03e1203ee665886741b', 'dart_vector_math_rev': 'a7b7e9ccb931348dbfa669e0f8fea1bf97705b16', 'dart_web_rev': '816abcc1bf186f61c7e66e7f4c56d1554a61ab27', 'dart_webdev_rev': '0b2a408f6f64a29cd0d18ac7d2d407a4e1db8e0f', From b95ab963a4ca4137cf665ee35534e38b31b1a4cb Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Mon, 6 Oct 2025 03:11:29 -0400 Subject: [PATCH 073/204] Roll Skia from 1fd0ca1f2120 to 45191c22b15c (3 revisions) (#176556) https://skia.googlesource.com/skia.git/+log/1fd0ca1f2120..45191c22b15c 2025-10-06 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from 01940842b667 to d8f0feef7c17 (14 revisions) 2025-10-06 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Skia Infra from 228c951bd699 to b57557664701 (8 revisions) 2025-10-05 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 207fff6486db to 7b14f3ac9637 (1 revision) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC egdaniel@google.com,jimgraham@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 8a7460f0f2cd6..8df64e8946de2 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': '1fd0ca1f2120982cb4adb145e765d5f933b61d22', + 'skia_revision': '45191c22b15c28c27b94a605d083bae7f648bf83', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 0400e2cf77b228ff44aafc5ea230054fdd397d7d Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Mon, 6 Oct 2025 05:19:29 -0700 Subject: [PATCH 074/204] [ Widget Preview ] Fix type error when retrieving flags from persistent preferences (#176546) Also moves `dtd_services_test.dart` to actually use the `WidgetPreviewScaffoldDtdServices` implementation, which would have caught the typecast issue in the implementation. --- .../lib/src/dtd/dtd_services.dart.tmpl | 21 ++- .../widget_preview/dtd_services_test.dart | 146 ------------------ .../lib/src/dtd/dtd_services.dart | 21 ++- .../widget_preview_scaffold/pubspec.yaml | 11 +- .../test/dtd_services_test.dart | 140 +++++++++++++++++ .../widget_preview_scaffold_test_utils.dart | 8 +- pubspec.lock | 4 +- pubspec.yaml | 4 +- 8 files changed, 178 insertions(+), 177 deletions(-) delete mode 100644 packages/flutter_tools/test/commands.shard/permeable/widget_preview/dtd_services_test.dart create mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/dtd_services_test.dart diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl index 76469ef7b1e6f..ed5352dbd4181 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl @@ -92,12 +92,14 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { /// preferences map. /// /// Returns null if [key] is not in the map. - Future getPreference(String key) async { + Future getPreference(String key) async { try { - final response = StringResponse.fromDTDResponse( - (await _call(kGetPreference, params: {'key': key}))!, - ); - return response.value; + final response = await _call(kGetPreference, params: {'key': key}); + return switch (response?.type) { + 'StringResponse' => StringResponse.fromDTDResponse(response!).value, + 'BoolResponse' => BoolResponse.fromDTDResponse(response!).value, + _ => throw StateError('Unexpected response type: ${response?.type}'), + }; } on RpcException catch (e) { if (e.code == kNoValueForKey) { return null; @@ -110,15 +112,12 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { /// /// If [key] is not set, [defaultValue] is returned. Future getFlag(String key, {bool defaultValue = false}) async { - final result = await getPreference(key); - if (result == null) { - return defaultValue; - } - return bool.tryParse(result) ?? defaultValue; + final result = await getPreference(key) as bool?; + return result ?? defaultValue; } /// Sets [key] to [value] in the persistent preferences map. - Future setPreference(String key, Object value) async { + Future setPreference(String key, Object? value) async { await _call(kSetPreference, params: {'key': key, 'value': value}); } diff --git a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/dtd_services_test.dart b/packages/flutter_tools/test/commands.shard/permeable/widget_preview/dtd_services_test.dart deleted file mode 100644 index d7ce8f9b926f7..0000000000000 --- a/packages/flutter_tools/test/commands.shard/permeable/widget_preview/dtd_services_test.dart +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:dtd/dtd.dart'; -import 'package:file/memory.dart'; -import 'package:flutter_tools/src/base/file_system.dart'; -import 'package:flutter_tools/src/base/logger.dart'; -import 'package:flutter_tools/src/base/process.dart'; -import 'package:flutter_tools/src/convert.dart'; -import 'package:flutter_tools/src/globals.dart' as globals; -import 'package:flutter_tools/src/project.dart'; -import 'package:flutter_tools/src/widget_preview/dtd_services.dart'; -import 'package:flutter_tools/src/widget_preview/persistent_preferences.dart'; -import 'package:json_rpc_2/json_rpc_2.dart'; -import 'package:test/fake.dart'; - -import '../../../src/common.dart'; -import '../../../src/context.dart'; -import '../../../src/test_flutter_command_runner.dart'; -import '../utils/project_testing_utils.dart'; - -class FakeFlutterProject extends Fake implements FlutterProject { - FakeFlutterProject(); -} - -void main() { - late WidgetPreviewDtdServices dtdServer; - late LoggingProcessManager loggingProcessManager; - late Logger logger; - - setUp(() async { - loggingProcessManager = LoggingProcessManager(); - logger = BufferLogger.test(); - }); - - tearDown(() async { - await dtdServer.shutdownHooks.runShutdownHooks(logger); - }); - - group('$WidgetPreviewDtdServices', () { - testUsingContext( - 'handles ${WidgetPreviewDtdServices.kHotRestartPreviewer} invocations', - () async { - // Start DTD and register the widget preview DTD services with a custom handler for hot - // restart requests. - final hotRestartRequestCompleter = Completer(); - dtdServer = WidgetPreviewDtdServices( - fs: MemoryFileSystem.test(), - logger: logger, - shutdownHooks: ShutdownHooks(), - dtdLauncher: DtdLauncher( - logger: logger, - artifacts: globals.artifacts!, - processManager: globals.processManager, - ), - onHotRestartPreviewerRequest: hotRestartRequestCompleter.complete, - project: FakeFlutterProject(), - ); - await dtdServer.launchAndConnect(); - - // Connect to the DTD instance and invoke the hot restart endpoint. - final DartToolingDaemon dtd = await DartToolingDaemon.connect(dtdServer.dtdUri!); - final DTDResponse response = await dtd.call( - WidgetPreviewDtdServices.kWidgetPreviewService, - WidgetPreviewDtdServices.kHotRestartPreviewer, - ); - - // This will throw if the response is not an instance of Success. - expect(() => Success.fromDTDResponse(response), returnsNormally); - - // Ensure the custom handler is actually invoked. - await hotRestartRequestCompleter.future; - }, - overrides: {ProcessManager: () => loggingProcessManager}, - ); - - testUsingContext('can set and retreive values from $PersistentPreferences', () async { - dtdServer = WidgetPreviewDtdServices( - fs: MemoryFileSystem.test(), - logger: logger, - shutdownHooks: ShutdownHooks(), - dtdLauncher: DtdLauncher( - logger: logger, - artifacts: globals.artifacts!, - processManager: globals.processManager, - ), - onHotRestartPreviewerRequest: () {}, - project: FakeFlutterProject(), - ); - await dtdServer.launchAndConnect(); - - // The properties file should be created by the PersistentProperties constructor. - final File preferencesFile = dtdServer.preferences.file; - expect(preferencesFile.existsSync(), true); - - // Connect to the DTD instance. - final DartToolingDaemon dtd = await DartToolingDaemon.connect(dtdServer.dtdUri!); - - Future getPreference(String key) async { - try { - return StringResponse.fromDTDResponse( - await dtd.call( - WidgetPreviewDtdServices.kWidgetPreviewService, - WidgetPreviewDtdServices.kGetPreference, - params: {'key': key}, - ), - ).value; - } on RpcException catch (e) { - if (e.code == WidgetPreviewDtdServices.kNoValueForKey) { - return null; - } - rethrow; - } - } - - Future setPreference(String key, String? value) async { - await dtd.call( - WidgetPreviewDtdServices.kWidgetPreviewService, - WidgetPreviewDtdServices.kSetPreference, - params: {'key': key, 'value': value}, - ); - } - - const kTestKey = 'myKey'; - - // The preferences file should be empty. - expect(await getPreference(kTestKey), null); - expect(preferencesFile.readAsStringSync(), isEmpty); - - // Set a preference and ensure it's read back. - const kFirstValue = 'foo'; - await setPreference(kTestKey, kFirstValue); - expect(await getPreference(kTestKey), kFirstValue); - expect(json.decode(preferencesFile.readAsStringSync()), {kTestKey: kFirstValue}); - - // Overwrite kTestKey and ensure it's read back. - const kSecondValue = 'bar'; - await setPreference(kTestKey, kSecondValue); - expect(await getPreference(kTestKey), kSecondValue); - expect(json.decode(preferencesFile.readAsStringSync()), {kTestKey: kSecondValue}); - }); - }); -} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart index 76469ef7b1e6f..ed5352dbd4181 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/dtd/dtd_services.dart @@ -92,12 +92,14 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { /// preferences map. /// /// Returns null if [key] is not in the map. - Future getPreference(String key) async { + Future getPreference(String key) async { try { - final response = StringResponse.fromDTDResponse( - (await _call(kGetPreference, params: {'key': key}))!, - ); - return response.value; + final response = await _call(kGetPreference, params: {'key': key}); + return switch (response?.type) { + 'StringResponse' => StringResponse.fromDTDResponse(response!).value, + 'BoolResponse' => BoolResponse.fromDTDResponse(response!).value, + _ => throw StateError('Unexpected response type: ${response?.type}'), + }; } on RpcException catch (e) { if (e.code == kNoValueForKey) { return null; @@ -110,15 +112,12 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService { /// /// If [key] is not set, [defaultValue] is returned. Future getFlag(String key, {bool defaultValue = false}) async { - final result = await getPreference(key); - if (result == null) { - return defaultValue; - } - return bool.tryParse(result) ?? defaultValue; + final result = await getPreference(key) as bool?; + return result ?? defaultValue; } /// Sets [key] to [value] in the persistent preferences map. - Future setPreference(String key, Object value) async { + Future setPreference(String key, Object? value) async { await _call(kSetPreference, params: {'key': key, 'value': value}); } diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/pubspec.yaml b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/pubspec.yaml index 25648b0958999..cca81f2d021a7 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/pubspec.yaml +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/pubspec.yaml @@ -45,8 +45,8 @@ dependencies: term_glyph: 1.2.2 test_api: 0.7.7 typed_data: 1.4.0 - unified_analytics: 8.0.1 - url_launcher_android: 6.3.22 + unified_analytics: 8.0.5 + url_launcher_android: 6.3.23 url_launcher_ios: 6.3.4 url_launcher_linux: 3.2.1 url_launcher_macos: 3.2.3 @@ -59,6 +59,11 @@ dependencies: web_socket: 1.0.1 web_socket_channel: 3.0.3 +dev_dependencies: + flutter_tools: + path: ../../../ + test: 1.26.3 + flutter: uses-material-design: true -# PUBSPEC CHECKSUM: fdcqn8 +# PUBSPEC CHECKSUM: 130obr diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/dtd_services_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/dtd_services_test.dart new file mode 100644 index 0000000000000..120cc064d1472 --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/dtd_services_test.dart @@ -0,0 +1,140 @@ +// 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:file/memory.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/process.dart'; +import 'package:flutter_tools/src/convert.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/project.dart'; +import 'package:flutter_tools/src/widget_preview/dtd_services.dart'; +import 'package:flutter_tools/src/widget_preview/persistent_preferences.dart'; +import 'package:test/fake.dart'; +import 'package:widget_preview_scaffold/src/dtd/dtd_services.dart'; + +import '../../../src/common.dart'; +import '../../../src/context.dart'; +import '../../../src/test_flutter_command_runner.dart'; +import '../../../commands.shard/permeable/utils/project_testing_utils.dart'; + +class FakeFlutterProject extends Fake implements FlutterProject { + FakeFlutterProject(); +} + +void main() { + late WidgetPreviewDtdServices dtdServer; + late LoggingProcessManager loggingProcessManager; + late Logger logger; + + setUp(() async { + loggingProcessManager = LoggingProcessManager(); + logger = BufferLogger.test(); + }); + + tearDown(() async { + await dtdServer.shutdownHooks.runShutdownHooks(logger); + }); + + group('$WidgetPreviewDtdServices', () { + testUsingContext( + 'handles ${WidgetPreviewDtdServices.kHotRestartPreviewer} invocations', + () async { + // Start DTD and register the widget preview DTD services with a custom handler for hot + // restart requests. + final hotRestartRequestCompleter = Completer(); + dtdServer = WidgetPreviewDtdServices( + fs: MemoryFileSystem.test(), + logger: logger, + shutdownHooks: ShutdownHooks(), + dtdLauncher: DtdLauncher( + logger: logger, + artifacts: globals.artifacts!, + processManager: globals.processManager, + ), + onHotRestartPreviewerRequest: hotRestartRequestCompleter.complete, + project: FakeFlutterProject(), + ); + await dtdServer.launchAndConnect(); + + // Connect to the DTD instance and invoke the hot restart endpoint. + final dtd = WidgetPreviewScaffoldDtdServices(); + await dtd.connect(dtdUri: dtdServer.dtdUri); + + await dtd.hotRestartPreviewer(); + + // Ensure the custom handler is actually invoked. + await hotRestartRequestCompleter.future; + }, + overrides: {ProcessManager: () => loggingProcessManager}, + ); + + testUsingContext( + 'can set and retreive values from $PersistentPreferences', + () async { + dtdServer = WidgetPreviewDtdServices( + fs: MemoryFileSystem.test(), + logger: logger, + shutdownHooks: ShutdownHooks(), + dtdLauncher: DtdLauncher( + logger: logger, + artifacts: globals.artifacts!, + processManager: globals.processManager, + ), + onHotRestartPreviewerRequest: () {}, + project: FakeFlutterProject(), + ); + await dtdServer.launchAndConnect(); + + // The properties file should be created by the PersistentProperties constructor. + final File preferencesFile = dtdServer.preferences.file; + expect(preferencesFile.existsSync(), true); + + // Connect to the DTD instance. + final dtd = WidgetPreviewScaffoldDtdServices(); + await dtd.connect(dtdUri: dtdServer.dtdUri); + + const kTestKey = 'myKey'; + + // The preferences file should be empty. + expect(await dtd.getPreference(kTestKey), null); + expect(preferencesFile.readAsStringSync(), isEmpty); + + // Set a preference and ensure it's read back. + const kFirstValue = 'foo'; + await dtd.setPreference(kTestKey, kFirstValue); + expect(await dtd.getPreference(kTestKey), kFirstValue); + expect(json.decode(preferencesFile.readAsStringSync()), { + kTestKey: kFirstValue, + }); + + // Overwrite kTestKey and ensure it's read back. + const kSecondValue = 'bar'; + await dtd.setPreference(kTestKey, kSecondValue); + expect(await dtd.getPreference(kTestKey), kSecondValue); + expect(json.decode(preferencesFile.readAsStringSync()), { + kTestKey: kSecondValue, + }); + + // Write a flag. + const kFlagKey = 'flag'; + await dtd.setPreference(kFlagKey, true); + expect(await dtd.getFlag(kFlagKey), true); + expect(json.decode(preferencesFile.readAsStringSync()), { + kFlagKey: true, + kTestKey: kSecondValue, + }); + + // Remove an entry. + await dtd.setPreference(kTestKey, null); + expect(await dtd.getPreference(kTestKey), null); + expect(json.decode(preferencesFile.readAsStringSync()), { + kFlagKey: true, + }); + }, + ); + }); +} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/utils/widget_preview_scaffold_test_utils.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/utils/widget_preview_scaffold_test_utils.dart index 9eaaecd5d0f6d..104eca32a405b 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/utils/widget_preview_scaffold_test_utils.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/utils/widget_preview_scaffold_test_utils.dart @@ -62,7 +62,7 @@ class FakeWidgetPreviewScaffoldDtdServices extends Fake FakeWidgetPreviewScaffoldDtdServices({this.isWindows = false}); final navigationEvents = []; - final preferences = {}; + final preferences = {}; @override Future connect({Uri? dtdUri}) async {} @@ -117,7 +117,11 @@ class FakeWidgetPreviewScaffoldDtdServices extends Fake /// Sets [key] to [value] in the persistent preferences map. @override - Future setPreference(String key, Object value) async { + Future setPreference(String key, Object? value) async { + if (value == null) { + preferences.remove(key); + return; + } preferences[key] = value; } } diff --git a/pubspec.lock b/pubspec.lock index abb0a9d12b623..e280aad529159 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -867,10 +867,10 @@ packages: dependency: "direct main" description: name: url_launcher_android - sha256: "199bc33e746088546a39cc5f36bac5a278c5e53b40cb3196f99e7345fdcfae6b" + sha256: c0fb544b9ac7efa10254efaf00a951615c362d1ea1877472f8f6c0fa00fcf15b url: "https://pub.dev" source: hosted - version: "6.3.22" + version: "6.3.23" url_launcher_ios: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index d3253b25ca662..adf11359f111b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -175,7 +175,7 @@ dependencies: test_core: 0.6.12 typed_data: 1.4.0 url_launcher: 6.3.2 - url_launcher_android: 6.3.22 + url_launcher_android: 6.3.23 url_launcher_ios: 6.3.4 url_launcher_linux: 3.2.1 url_launcher_macos: 3.2.3 @@ -212,4 +212,4 @@ dependencies: pedantic: 1.11.1 quiver: 3.2.2 yaml_edit: 2.2.2 -# PUBSPEC CHECKSUM: pb0r15 +# PUBSPEC CHECKSUM: 5h4lnl From 8579f67adffda07f95f715b7afdb53b9ed78f607 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Mon, 6 Oct 2025 08:41:29 -0400 Subject: [PATCH 075/204] Roll Skia from 45191c22b15c to bc7cf194f4ee (2 revisions) (#176572) https://skia.googlesource.com/skia.git/+log/45191c22b15c..bc7cf194f4ee 2025-10-06 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from 7b14f3ac9637 to ea4cd2b85ec4 (2 revisions) 2025-10-06 skia-autoroll@skia-public.iam.gserviceaccount.com Roll ANGLE from 62b00f866364 to fc98b3f62a98 (11 revisions) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC egdaniel@google.com,jimgraham@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 8df64e8946de2..f92d14e33c198 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': '45191c22b15c28c27b94a605d083bae7f648bf83', + 'skia_revision': 'bc7cf194f4ee78095014b55b1370d902119dad60', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 01474413c5aa843d9156e0e821b06452dfef0e9d Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Mon, 6 Oct 2025 09:22:27 -0500 Subject: [PATCH 076/204] Add an AppDelegate callback for implicit FlutterEngines (#176240) This PR introduces a new protocol `FlutterImplicitEngineDelegate`, which adds a callback called `didInitializeImplicitFlutterEngine`, which returns a new interface called `FlutterImplicitEngineBridge`. This was added to expose parts of an implicit `FlutterEngine`, such as when created from a `FlutterViewController` in a storyboard. ``` swift diff @objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { // Register plugins with `engineBridge.pluginRegistry` GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) // Create method channels with `engineBridge.applicationRegistrar.messenger()` let batteryChannel = FlutterMethodChannel( name: "samples.flutter.dev/battery", binaryMessenger: engineBridge.applicationRegistrar.messenger() ) ... // Create platform views with `engineBridge.applicationRegistrar.messenger()` let factory = FLNativeViewFactory(messenger: engineBridge.applicationRegistrar.messenger()) ... } ``` This PR also refactors `FlutterPluginRegistrar` into 3 separate protocols: * `FlutterBaseRegistrar` * `FlutterApplicationRegistrar` * `FlutterPluginRegistrar` Most methods are moved from `FlutterPluginRegistrar` to `FlutterBaseRegistrar` and then `FlutterPluginRegistrar` and `FlutterApplicationRegistrar` inherit from `FlutterBaseRegistrar`. `FlutterPluginRegistrar` also has additional methods specific to it - there are no meaningful/breaking changes to `FlutterPluginRegistrar`. Fixes https://github.com/flutter/flutter/issues/173357. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../bin/tasks/module_uiscene_test_ios.dart | 77 ++++++++++ ...legate-FlutterImplicitEngineDelegate.swift | 33 ++++ ...plicitEngineDelegateWithLaunchEngine.swift | 33 ++++ .../Main-FlutterViewController.storyboard | 32 ++++ ...Events-FlutterImplicitEngineDelegate.swift | 55 +++++++ ...-SceneEvents-ApplicationLaunchEvents.swift | 57 +++++++ ...eEventsNoConnect-NoApplicationEvents.swift | 53 +++++++ ...ViewController-ImplicitFlutterEngine.swift | 21 +++ .../ios/framework/Headers/FlutterEngine.h | 43 ++++++ .../ios/framework/Headers/FlutterPlugin.h | 71 +++++---- .../ios/framework/Source/FlutterEngine.mm | 141 +++++++++++++----- .../ios/framework/Source/FlutterEngineTest.mm | 124 ++++++++++++++- .../framework/Source/FlutterEngine_Internal.h | 16 ++ .../ios/framework/Source/FlutterEngine_Test.h | 3 + .../FlutterPluginAppLifeCycleDelegate.mm | 34 +++++ .../FlutterPluginAppLifeCycleDelegateTest.mm | 78 ++++++++++ ...utterPluginAppLifeCycleDelegate_internal.h | 16 ++ .../framework/Source/FlutterViewController.mm | 36 ++++- .../Source/FlutterViewControllerTest.mm | 94 +++++++++++- 19 files changed, 942 insertions(+), 75 deletions(-) create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/AppDelegate-FlutterImplicitEngineDelegate.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/AppDelegate-FlutterImplicitEngineDelegateWithLaunchEngine.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/Main-FlutterViewController.storyboard create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-FlutterImplicitEngineDelegate.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-ApplicationLaunchEvents.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEventsNoConnect-NoApplicationEvents.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/ViewController-ImplicitFlutterEngine.swift diff --git a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart index 2a847c40c2ee0..b4bfa1fce818a 100644 --- a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart +++ b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart @@ -437,6 +437,83 @@ class Scenarios { r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', }, + // When using an implicit FlutterEngine created by the storyboard, we expect plugins to + // receive application launch events and scene events. + 'FlutterImplicitEngineDelegate-AppMigrated-StoryboardFlutterViewController': { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/Base.lproj/Main.storyboard', + r'$TEMPLATE_DIR/native/AppDelegate-FlutterImplicitEngineDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', + r'$TEMPLATE_DIR/native/UITests-SceneEvents-ApplicationLaunchEvents.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + }, + + // When registering plugins with the AppDelegate's self (and therefore the FlutterLaunchEngine) + // alongside the FlutterImplicitEngineDelegate, we expect application events starting where + // registration occurs, such as `application:didFinishingLaunchingWithOptions`. + 'FlutterImplicitEngineDelegateWithLaunchEngine-AppMigrated-StoryboardFlutterViewController': + { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/Base.lproj/Main.storyboard', + r'$TEMPLATE_DIR/native/AppDelegate-FlutterImplicitEngineDelegateWithLaunchEngine.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', + r'$TEMPLATE_DIR/native/UITests-SceneEvents.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + }, + + // When the app has not migrated to scenes, storyboard is instantiated earlier in the lifecycle. + // So when using an implicit FlutterEngine created by the storyboard, we expect plugins to + // receive all application events. + 'FlutterImplicitEngineDelegate-AppNotMigrated-StoryboardFlutterViewController': + { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/Info-unmigrated.plist': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/Info.plist', + r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/AppDelegate-FlutterImplicitEngineDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', + r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/Base.lproj/Main.storyboard', + r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-FlutterImplicitEngineDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + }, + + // When using an implicit FlutterEngine, created by the FlutterViewController in another + // ViewController, we expect plugins to be registered after the FlutterViewController is + // created, which results in the `application:didFinishLaunchingWithOptions:` and + // `scene:willConnectToSession:options:` events being missed. This is not a expected use case + // but it could be utilized. + 'FlutterImplicitEngineDelegate-AppMigrated-ImplicitFlutterEngine': { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/AppDelegate-FlutterImplicitEngineDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', + r'$TEMPLATE_DIR/native/ViewController-ImplicitFlutterEngine.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/ViewController.swift', + r'$TEMPLATE_DIR/native/UITests-SceneEventsNoConnect-NoApplicationEvents.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + }, + // State restoration work both when migrated and when not. 'AppMigrated-StateRestoration': {...sharedStateRestorationFiles}, 'AppNotMigrated-StateRestoration': { diff --git a/dev/integration_tests/ios_add2app_uiscene/native/AppDelegate-FlutterImplicitEngineDelegate.swift b/dev/integration_tests/ios_add2app_uiscene/native/AppDelegate-FlutterImplicitEngineDelegate.swift new file mode 100644 index 0000000000000..7669b8abed4a2 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/AppDelegate-FlutterImplicitEngineDelegate.swift @@ -0,0 +1,33 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Flutter +import FlutterPluginRegistrant +import UIKit + +@main +class AppDelegate: FlutterAppDelegate, + FlutterImplicitEngineDelegate +{ + func didInitializeImplicitFlutterEngine( + _ engineBridge: FlutterImplicitEngineBridge + ) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + let batteryChannel = FlutterMethodChannel( + name: "samples.flutter.dev/battery", + binaryMessenger: engineBridge.applicationRegistrar.messenger() + ) + } + + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication + .LaunchOptionsKey: Any]? + ) -> Bool { + return super.application( + application, + didFinishLaunchingWithOptions: launchOptions + ) + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/AppDelegate-FlutterImplicitEngineDelegateWithLaunchEngine.swift b/dev/integration_tests/ios_add2app_uiscene/native/AppDelegate-FlutterImplicitEngineDelegateWithLaunchEngine.swift new file mode 100644 index 0000000000000..af8a364b4f0c4 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/AppDelegate-FlutterImplicitEngineDelegateWithLaunchEngine.swift @@ -0,0 +1,33 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Flutter +import FlutterPluginRegistrant +import UIKit + +@main +class AppDelegate: FlutterAppDelegate, + FlutterImplicitEngineDelegate +{ + func didInitializeImplicitFlutterEngine( + _ engineBridge: FlutterImplicitEngineBridge + ) { + let batteryChannel = FlutterMethodChannel( + name: "samples.flutter.dev/battery", + binaryMessenger: engineBridge.applicationRegistrar.messenger() + ) + } + + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication + .LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application( + application, + didFinishLaunchingWithOptions: launchOptions + ) + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/Main-FlutterViewController.storyboard b/dev/integration_tests/ios_add2app_uiscene/native/Main-FlutterViewController.storyboard new file mode 100644 index 0000000000000..0596309c6dd6d --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/Main-FlutterViewController.storyboard @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-FlutterImplicitEngineDelegate.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-FlutterImplicitEngineDelegate.swift new file mode 100644 index 0000000000000..092b2cb599326 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-FlutterImplicitEngineDelegate.swift @@ -0,0 +1,55 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import XCTest + +final class xcode_uikit_swiftUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + @MainActor + func testLifecycleEvents() throws { + let app = XCUIApplication() + app.launch() + let button = app.buttons["Get Lifecycle Events"].firstMatch + XCTAssertTrue(button.waitForExistence(timeout: 5)) + button.tap() + + let expectedStartEvents = [ + "applicationWillFinishLaunchingWithOptions", + "applicationDidFinishLaunchingWithOptions", "applicationDidBecomeActive", + ] + let startEventsPredicate = NSPredicate( + format: "label == %@", + expectedStartEvents.joined(separator: "\n") + ) + let startEventsElement = app.staticTexts.element( + matching: startEventsPredicate + ) + XCTAssertTrue(startEventsElement.waitForExistence(timeout: 5)) + + // Background the app, then reactivate it and check the events again + XCUIDevice.shared.press(.home) + app.activate() + XCTAssertTrue(button.waitForExistence(timeout: 5)) + button.tap() + + let expectedEventsAfterBackgroundAndReactivate = [ + "applicationWillFinishLaunchingWithOptions", + "applicationDidFinishLaunchingWithOptions", "applicationDidBecomeActive", + "applicationWillResignActive", "applicationDidEnterBackground", + "applicationWillEnterForeground", "applicationDidBecomeActive", + ] + let backgroundEventsPredicate = NSPredicate( + format: "label == %@", + expectedEventsAfterBackgroundAndReactivate.joined(separator: "\n") + ) + let backgroundEventsElement = app.staticTexts.element( + matching: backgroundEventsPredicate + ) + XCTAssertTrue(backgroundEventsElement.waitForExistence(timeout: 5)) + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-ApplicationLaunchEvents.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-ApplicationLaunchEvents.swift new file mode 100644 index 0000000000000..d3a2dfd0978ab --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-ApplicationLaunchEvents.swift @@ -0,0 +1,57 @@ +// 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 XCTest + +final class xcode_uikit_swiftUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + @MainActor + func testLifecycleEvents() throws { + let app = XCUIApplication() + app.launch() + let button = app.buttons["Get Lifecycle Events"].firstMatch + XCTAssertTrue(button.waitForExistence(timeout: 5)) + button.tap() + + let expectedStartEvents = [ + "applicationWillFinishLaunchingWithOptions", + "applicationDidFinishLaunchingWithOptions", "sceneWillConnect", + "sceneWillEnterForeground", "sceneDidBecomeActive", + ] + let startEventsPredicate = NSPredicate( + format: "label == %@", + expectedStartEvents.joined(separator: "\n") + ) + let startEventsElement = app.staticTexts.element( + matching: startEventsPredicate + ) + XCTAssertTrue(startEventsElement.waitForExistence(timeout: 5)) + + // Background the app, then reactivate it and check the events again + XCUIDevice.shared.press(.home) + app.activate() + XCTAssertTrue(button.waitForExistence(timeout: 5)) + button.tap() + + let expectedEventsAfterBackgroundAndReactivate = [ + "applicationWillFinishLaunchingWithOptions", + "applicationDidFinishLaunchingWithOptions", "sceneWillConnect", + "sceneWillEnterForeground", "sceneDidBecomeActive", + "sceneWillResignActive", "sceneDidEnterBackground", + "sceneWillEnterForeground", "sceneDidBecomeActive", + ] + let backgroundEventsPredicate = NSPredicate( + format: "label == %@", + expectedEventsAfterBackgroundAndReactivate.joined(separator: "\n") + ) + let backgroundEventsElement = app.staticTexts.element( + matching: backgroundEventsPredicate + ) + XCTAssertTrue(backgroundEventsElement.waitForExistence(timeout: 5)) + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEventsNoConnect-NoApplicationEvents.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEventsNoConnect-NoApplicationEvents.swift new file mode 100644 index 0000000000000..60c0f53277403 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEventsNoConnect-NoApplicationEvents.swift @@ -0,0 +1,53 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import XCTest + +final class xcode_uikit_swiftUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + @MainActor + func testLifecycleEvents() throws { + let app = XCUIApplication() + app.launch() + let button = app.buttons["Get Lifecycle Events"].firstMatch + XCTAssertTrue(button.waitForExistence(timeout: 5)) + button.tap() + + let expectedStartEvents = [ + "sceneWillEnterForeground", "sceneDidBecomeActive", + ] + let startEventsPredicate = NSPredicate( + format: "label == %@", + expectedStartEvents.joined(separator: "\n") + ) + let startEventsElement = app.staticTexts.element( + matching: startEventsPredicate + ) + XCTAssertTrue(startEventsElement.waitForExistence(timeout: 5)) + + // Background the app, then reactivate it and check the events again + XCUIDevice.shared.press(.home) + app.activate() + XCTAssertTrue(button.waitForExistence(timeout: 5)) + button.tap() + + let expectedEventsAfterBackgroundAndReactivate = [ + "sceneWillEnterForeground", "sceneDidBecomeActive", + "sceneWillResignActive", "sceneDidEnterBackground", + "sceneWillEnterForeground", "sceneDidBecomeActive", + ] + let backgroundEventsPredicate = NSPredicate( + format: "label == %@", + expectedEventsAfterBackgroundAndReactivate.joined(separator: "\n") + ) + let backgroundEventsElement = app.staticTexts.element( + matching: backgroundEventsPredicate + ) + XCTAssertTrue(backgroundEventsElement.waitForExistence(timeout: 5)) + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/ViewController-ImplicitFlutterEngine.swift b/dev/integration_tests/ios_add2app_uiscene/native/ViewController-ImplicitFlutterEngine.swift new file mode 100644 index 0000000000000..17519d175590d --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/ViewController-ImplicitFlutterEngine.swift @@ -0,0 +1,21 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Flutter +import UIKit + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + let flutterViewController = FlutterViewController( + project: nil, + nibName: nil, + bundle: nil + ) + addChild(flutterViewController) + flutterViewController.view.frame = self.view.frame + self.view.addSubview(flutterViewController.view) + } +} diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h index 0a67fd8d9be54..c11e2c72f8e07 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h @@ -447,6 +447,49 @@ FLUTTER_DARWIN_EXPORT @end +/** + * Exposes parts of a `FlutterEngine` for registration purposes. + * + * This is used when the engine is created implicitly to allow registering + * plugins, application-level method channels, platform views, etc. + */ +@protocol FlutterImplicitEngineBridge + +/** + * The `FlutterPluginRegistry` for the created `FlutterEngine`. + * + * This can be used to vend `FlutterPluginRegistrar`s for plugins. + */ +@property(nonatomic, readonly) NSObject* pluginRegistry; + +/** + * The `FlutterApplicationRegistrar` for the created `FlutterEngine`. + * + * This registrar provides access to application-level services, such as the engine's + * `FlutterBinaryMessenger` or `FlutterTextureRegistry`. + */ +@property(nonatomic, readonly) NSObject* applicationRegistrar; + +@end + +/** + * Protocol for receiving a callback when an implicit engine is initialized, such as when created by + * a FlutterViewController from a storyboard. + * + * This provides the engine bridge to the listener. + */ +@protocol FlutterImplicitEngineDelegate +@required + +/** + * Called once the implicit `FlutterEngine` is initialized. + * + * The `FlutterImplicitEngineBridge` can then be used to register plugins, + * application-level method channels, platform views, etc. + */ +- (void)didInitializeImplicitFlutterEngine:(NSObject*)engineBridge; +@end + NS_ASSUME_NONNULL_END #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_HEADERS_FLUTTERENGINE_H_ diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h index b4728fc844817..24e83756b316f 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterPlugin.h @@ -273,18 +273,14 @@ typedef enum { #pragma mark - /** - * Registration context for a single `FlutterPlugin`, providing a one stop shop - * for the plugin to access contextual information and register callbacks for - * various application events. + * The base interface for `FlutterPluginRegistrar` and `FlutterApplicationRegistrar`. * - * Registrars are obtained from a `FlutterPluginRegistry` which keeps track of - * the identity of registered plugins and provides basic support for cross-plugin - * coordination. + * Provides registration context for the application or plugins. */ -@protocol FlutterPluginRegistrar +@protocol FlutterBaseRegistrar /** * Returns a `FlutterBinaryMessenger` for creating Dart/iOS communication - * channels to be used by the plugin. + * channels to be used by the application or a plugin. * * @return The messenger. */ @@ -292,30 +288,17 @@ typedef enum { /** * Returns a `FlutterTextureRegistry` for registering textures - * provided by the plugin. + * provided by the application or a plugin. * * @return The texture registry. */ - (NSObject*)textures; -/** - * The `UIViewController` whose view is displaying Flutter content. - * - * The plugin typically should not store a strong reference to this view - * controller. - * - * This property is provided for backwards compatibility for apps that assume - * a single view, and will eventually be replaced by the multi-view API variant. - * - * This property may be |nil|, for instance in a headless environment, or when - * the underlying Flutter engine is deallocated. - */ -@property(nullable, readonly) UIViewController* viewController; - /** * Registers a `FlutterPlatformViewFactory` for creation of platform views. * - * Plugins expose `UIView` for embedding in Flutter apps by registering a view factory. + * Applications or plugins can expose `UIView` for embedding in Flutter apps by registering a view + * factory. * * @param factory The view factory that will be registered. * @param factoryId A unique identifier for the factory, the Dart code of the Flutter app can use @@ -327,7 +310,8 @@ typedef enum { /** * Registers a `FlutterPlatformViewFactory` for creation of platform views. * - * Plugins can expose a `UIView` for embedding in Flutter apps by registering a view factory. + * Applications or plugins can expose a `UIView` for embedding in Flutter apps by registering a view + * factory. * * @param factory The view factory that will be registered. * @param factoryId A unique identifier for the factory, the Dart code of the Flutter app can use @@ -340,6 +324,43 @@ typedef enum { withId:(NSString*)factoryId gestureRecognizersBlockingPolicy: (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy; +@end + +/** + * A registrar for Flutter applications. + * + * This registrar provides access to application-level services, such as the binary messenger and + * texture registry. + * + * See also `FlutterBaseRegistrar`. + */ +@protocol FlutterApplicationRegistrar +@end + +/** + * Registration context for a single `FlutterPlugin`, providing a one stop shop + * for the plugin to access contextual information and register callbacks for + * various application events. + * + * Registrars are obtained from a `FlutterPluginRegistry` which keeps track of + * the identity of registered plugins and provides basic support for cross-plugin + * coordination. + */ +@protocol FlutterPluginRegistrar + +/** + * The `UIViewController` whose view is displaying Flutter content. + * + * The plugin typically should not store a strong reference to this view + * controller. + * + * This property is provided for backwards compatibility for apps that assume + * a single view, and will eventually be replaced by the multi-view API variant. + * + * This property may be |nil|, for instance in a headless environment, or when + * the underlying Flutter engine is deallocated. + */ +@property(nullable, readonly) UIViewController* viewController; /** * Publishes a value for external use of the plugin. diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index afe9c0baae78a..90035fef8b832 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -89,10 +89,22 @@ static void IOSPlatformThreadConfigSetter(const fml::Thread::ThreadConfig& confi NSString* const kFlutterKeyDataChannel = @"flutter/keydata"; static constexpr int kNumProfilerSamplesPerSec = 5; +NSString* const kFlutterApplicationRegistrarKey = @"io.flutter.flutter.application_registrar"; + +@interface FlutterEngineBaseRegistrar : NSObject -@interface FlutterEngineRegistrar : NSObject @property(nonatomic, weak) FlutterEngine* flutterEngine; -- (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine; +@property(nonatomic, readonly) NSString* key; + +- (instancetype)initWithKey:(NSString*)key flutterEngine:(FlutterEngine*)flutterEngine; + +@end + +@interface FlutterEngineApplicationRegistrar + : FlutterEngineBaseRegistrar +@end + +@interface FlutterEnginePluginRegistrar : FlutterEngineBaseRegistrar @end @interface FlutterEngine () * registrars; +@property(nonatomic, readonly) + NSMutableDictionary* registrars; @property(nonatomic, readwrite, copy) NSString* isolateId; @property(nonatomic, copy) NSString* initialRoute; @@ -153,6 +166,29 @@ @interface FlutterEngine () * _appRegistrar; +} + +- (instancetype)initWithEngine:(FlutterEngine*)engine { + self = [super init]; + if (self) { + _engine = engine; + _appRegistrar = [engine registrarForApplication:kFlutterApplicationRegistrarKey]; + } + return self; +} + +- (NSObject*)pluginRegistry { + return _engine; +} + +- (NSObject*)applicationRegistrar { + return _appRegistrar; +} +@end + @implementation FlutterEngine { std::shared_ptr _threadHost; std::unique_ptr _shell; @@ -306,18 +342,20 @@ - (void)dealloc { /// plugins may be talking to things like the binaryMessenger. [_pluginPublications enumerateKeysAndObjectsUsingBlock:^(id key, id object, BOOL* stop) { if ([object respondsToSelector:@selector(detachFromEngineForRegistrar:)]) { - NSObject* registrar = self.registrars[key]; - [object detachFromEngineForRegistrar:registrar]; + FlutterEngineBaseRegistrar* registrar = self.registrars[key]; + if ([registrar conformsToProtocol:@protocol(FlutterPluginRegistrar)]) { + [object detachFromEngineForRegistrar:((id)registrar)]; + } } }]; // nil out weak references. // TODO(cbracken): https://github.com/flutter/flutter/issues/156222 - // Ensure that FlutterEngineRegistrar is using weak pointers, then eliminate this code. - [_registrars - enumerateKeysAndObjectsUsingBlock:^(id key, FlutterEngineRegistrar* registrar, BOOL* stop) { - registrar.flutterEngine = nil; - }]; + // Ensure that FlutterEnginePluginRegistrar is using weak pointers, then eliminate this code. + [_registrars enumerateKeysAndObjectsUsingBlock:^(id key, FlutterEngineBaseRegistrar* registrar, + BOOL* stop) { + registrar.flutterEngine = nil; + }]; _binaryMessenger.parent = nil; _textureRegistry.parent = nil; @@ -903,6 +941,17 @@ - (BOOL)createShell:(NSString*)entrypoint return _shell != nullptr; } +- (BOOL)performImplicitEngineCallback { + id appDelegate = FlutterSharedApplication.application.delegate; + if ([appDelegate conformsToProtocol:@protocol(FlutterImplicitEngineDelegate)]) { + id provider = (id)appDelegate; + [provider didInitializeImplicitFlutterEngine:[[FlutterImplicitEngineBridgeImpl alloc] + initWithEngine:self]]; + return YES; + } + return NO; +} + - (void)updateDisplays { if (!_shell) { // Tests may do this. @@ -1342,12 +1391,21 @@ - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package { - (NSObject*)registrarForPlugin:(NSString*)pluginKey { NSAssert(self.pluginPublications[pluginKey] == nil, @"Duplicate plugin key: %@", pluginKey); self.pluginPublications[pluginKey] = [NSNull null]; - FlutterEngineRegistrar* result = [[FlutterEngineRegistrar alloc] initWithPlugin:pluginKey - flutterEngine:self]; + FlutterEnginePluginRegistrar* result = [[FlutterEnginePluginRegistrar alloc] initWithKey:pluginKey + flutterEngine:self]; self.registrars[pluginKey] = result; return result; } +- (NSObject*)registrarForApplication:(NSString*)key { + NSAssert(self.pluginPublications[key] == nil, @"Duplicate key: %@", key); + self.pluginPublications[key] = [NSNull null]; + FlutterEngineApplicationRegistrar* result = + [[FlutterEngineApplicationRegistrar alloc] initWithKey:key flutterEngine:self]; + self.registrars[key] = result; + return result; +} + - (BOOL)hasPlugin:(NSString*)pluginKey { return _pluginPublications[pluginKey] != nil; } @@ -1559,14 +1617,12 @@ - (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(void (^)(BOOL suc @end -@implementation FlutterEngineRegistrar { - NSString* _pluginKey; -} +@implementation FlutterEngineBaseRegistrar -- (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine*)flutterEngine { +- (instancetype)initWithKey:(NSString*)key flutterEngine:(FlutterEngine*)flutterEngine { self = [super init]; NSAssert(self, @"Super init cannot be nil"); - _pluginKey = [pluginKey copy]; + _key = [key copy]; _flutterEngine = flutterEngine; return self; } @@ -1578,12 +1634,32 @@ - (instancetype)initWithPlugin:(NSString*)pluginKey flutterEngine:(FlutterEngine return _flutterEngine.textureRegistry; } +- (void)registerViewFactory:(NSObject*)factory + withId:(NSString*)factoryId { + [self registerViewFactory:factory + withId:factoryId + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; +} + +- (void)registerViewFactory:(NSObject*)factory + withId:(NSString*)factoryId + gestureRecognizersBlockingPolicy: + (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy { + [_flutterEngine.platformViewsController registerViewFactory:factory + withId:factoryId + gestureRecognizersBlockingPolicy:gestureRecognizersBlockingPolicy]; +} + +@end + +@implementation FlutterEnginePluginRegistrar + - (nullable UIViewController*)viewController { - return _flutterEngine.viewController; + return self.flutterEngine.viewController; } - (void)publish:(NSObject*)value { - _flutterEngine.pluginPublications[_pluginKey] = value; + self.flutterEngine.pluginPublications[self.key] = value; } - (void)addMethodCallDelegate:(NSObject*)delegate @@ -1604,37 +1680,24 @@ - (void)addApplicationDelegate:(NSObject*)delegate { // TODO(vashworth): If the plugin doesn't conform to the FlutterSceneLifeCycleDelegate, // print a warning pointing to documentation: https://github.com/flutter/flutter/issues/175956 // [FlutterLogger logWarning:[NSString stringWithFormat:@"Plugin %@ has not migrated to - // scenes.", _pluginKey]]; + // scenes.", self.key]]; } } - (void)addSceneDelegate:(NSObject*)delegate { // If the plugin conforms to FlutterSceneLifeCycleDelegate, add it to the engine. - [_flutterEngine addSceneLifeCycleDelegate:delegate]; + [self.flutterEngine addSceneLifeCycleDelegate:delegate]; } - (NSString*)lookupKeyForAsset:(NSString*)asset { - return [_flutterEngine lookupKeyForAsset:asset]; + return [self.flutterEngine lookupKeyForAsset:asset]; } - (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package { - return [_flutterEngine lookupKeyForAsset:asset fromPackage:package]; + return [self.flutterEngine lookupKeyForAsset:asset fromPackage:package]; } -- (void)registerViewFactory:(NSObject*)factory - withId:(NSString*)factoryId { - [self registerViewFactory:factory - withId:factoryId - gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; -} - -- (void)registerViewFactory:(NSObject*)factory - withId:(NSString*)factoryId - gestureRecognizersBlockingPolicy: - (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy { - [_flutterEngine.platformViewsController registerViewFactory:factory - withId:factoryId - gestureRecognizersBlockingPolicy:gestureRecognizersBlockingPolicy]; -} +@end +@implementation FlutterEngineApplicationRegistrar @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm index 48f84e3b8533d..b54da9089eda2 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngineTest.mm @@ -183,12 +183,11 @@ - (void)testNilSetMessageHandlerBeforeRun { - (void)testNotifyPluginOfDealloc { id plugin = OCMProtocolMock(@protocol(FlutterPlugin)); OCMStub([plugin detachFromEngineForRegistrar:[OCMArg any]]); - { + @autoreleasepool { FlutterDartProject* project = [[FlutterDartProject alloc] init]; FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project]; NSObject* registrar = [engine registrarForPlugin:@"plugin"]; [registrar publish:plugin]; - engine = nil; } OCMVerify([plugin detachFromEngineForRegistrar:[OCMArg any]]); } @@ -584,6 +583,126 @@ - (void)testAddSceneDelegateToRegistrar { OCMVerify(times(1), [mockEngine addSceneLifeCycleDelegate:[OCMArg any]]); } +- (void)testNotifyAppDelegateOfEngineInitialization { + FlutterDartProject* project = [[FlutterDartProject alloc] init]; + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project]; + + id mockApplication = OCMClassMock([UIApplication class]); + OCMStub([mockApplication sharedApplication]).andReturn(mockApplication); + id mockAppDelegate = OCMProtocolMock(@protocol(FlutterImplicitEngineDelegate)); + OCMStub([mockApplication delegate]).andReturn(mockAppDelegate); + + [engine performImplicitEngineCallback]; + OCMVerify(times(1), [mockAppDelegate didInitializeImplicitFlutterEngine:[OCMArg any]]); +} + +- (void)testRegistrarForPlugin { + FlutterDartProject* project = [[FlutterDartProject alloc] init]; + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project]; + FlutterEngine* mockEngine = OCMPartialMock(engine); + id mockViewController = OCMClassMock([FlutterViewController class]); + id mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + id mockTextureRegistry = OCMProtocolMock(@protocol(FlutterTextureRegistry)); + id mockPlatformViewController = OCMClassMock([FlutterPlatformViewsController class]); + OCMStub([mockEngine viewController]).andReturn(mockViewController); + OCMStub([mockEngine binaryMessenger]).andReturn(mockBinaryMessenger); + OCMStub([mockEngine textureRegistry]).andReturn(mockTextureRegistry); + OCMStub([mockEngine platformViewsController]).andReturn(mockPlatformViewController); + + NSString* pluginKey = @"plugin"; + NSString* assetKey = @"asset"; + NSString* factoryKey = @"platform_view_factory"; + + NSObject* registrar = [mockEngine registrarForPlugin:pluginKey]; + + XCTAssertTrue([registrar respondsToSelector:@selector(messenger)]); + XCTAssertTrue([registrar respondsToSelector:@selector(textures)]); + XCTAssertTrue([registrar respondsToSelector:@selector(registerViewFactory:withId:)]); + XCTAssertTrue([registrar + respondsToSelector:@selector(registerViewFactory:withId:gestureRecognizersBlockingPolicy:)]); + XCTAssertTrue([registrar respondsToSelector:@selector(viewController)]); + XCTAssertTrue([registrar respondsToSelector:@selector(publish:)]); + XCTAssertTrue([registrar respondsToSelector:@selector(addMethodCallDelegate:channel:)]); + XCTAssertTrue([registrar respondsToSelector:@selector(addApplicationDelegate:)]); + XCTAssertTrue([registrar respondsToSelector:@selector(lookupKeyForAsset:)]); + XCTAssertTrue([registrar respondsToSelector:@selector(lookupKeyForAsset:fromPackage:)]); + + // Verify messenger, textures, and viewController forwards to FlutterEngine + XCTAssertEqual(registrar.messenger, mockBinaryMessenger); + XCTAssertEqual(registrar.textures, mockTextureRegistry); + XCTAssertEqual(registrar.viewController, mockViewController); + + // Verify registerViewFactory:withId:, registerViewFactory:withId:gestureRecognizersBlockingPolicy + // forwards to FlutterEngine + id mockPlatformViewFactory = OCMProtocolMock(@protocol(FlutterPlatformViewFactory)); + [registrar registerViewFactory:mockPlatformViewFactory withId:factoryKey]; + [registrar registerViewFactory:mockPlatformViewFactory + withId:factoryKey + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; + OCMVerify(times(2), [mockPlatformViewController registerViewFactory:mockPlatformViewFactory + withId:factoryKey + gestureRecognizersBlockingPolicy: + FlutterPlatformViewGestureRecognizersBlockingPolicyEager]); + + // Verify publish forwards to FlutterEngine + id plugin = OCMProtocolMock(@protocol(FlutterPlugin)); + [registrar publish:plugin]; + XCTAssertEqual(mockEngine.pluginPublications[pluginKey], plugin); + + // Verify lookupKeyForAsset:, lookupKeyForAsset:fromPackage forward to engine + [registrar lookupKeyForAsset:assetKey]; + OCMVerify(times(1), [mockEngine lookupKeyForAsset:assetKey]); + [registrar lookupKeyForAsset:assetKey fromPackage:pluginKey]; + OCMVerify(times(1), [mockEngine lookupKeyForAsset:assetKey fromPackage:pluginKey]); +} + +- (void)testRegistrarForApplication { + FlutterDartProject* project = [[FlutterDartProject alloc] init]; + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project]; + FlutterEngine* mockEngine = OCMPartialMock(engine); + id mockViewController = OCMClassMock([FlutterViewController class]); + id mockBinaryMessenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + id mockTextureRegistry = OCMProtocolMock(@protocol(FlutterTextureRegistry)); + id mockPlatformViewController = OCMClassMock([FlutterPlatformViewsController class]); + OCMStub([mockEngine viewController]).andReturn(mockViewController); + OCMStub([mockEngine binaryMessenger]).andReturn(mockBinaryMessenger); + OCMStub([mockEngine textureRegistry]).andReturn(mockTextureRegistry); + OCMStub([mockEngine platformViewsController]).andReturn(mockPlatformViewController); + + NSString* pluginKey = @"plugin"; + NSString* factoryKey = @"platform_view_factory"; + + NSObject* registrar = [mockEngine registrarForApplication:pluginKey]; + + XCTAssertTrue([registrar respondsToSelector:@selector(messenger)]); + XCTAssertTrue([registrar respondsToSelector:@selector(textures)]); + XCTAssertTrue([registrar respondsToSelector:@selector(registerViewFactory:withId:)]); + XCTAssertTrue([registrar + respondsToSelector:@selector(registerViewFactory:withId:gestureRecognizersBlockingPolicy:)]); + XCTAssertFalse([registrar respondsToSelector:@selector(viewController)]); + XCTAssertFalse([registrar respondsToSelector:@selector(publish:)]); + XCTAssertFalse([registrar respondsToSelector:@selector(addMethodCallDelegate:channel:)]); + XCTAssertFalse([registrar respondsToSelector:@selector(addApplicationDelegate:)]); + XCTAssertFalse([registrar respondsToSelector:@selector(lookupKeyForAsset:)]); + XCTAssertFalse([registrar respondsToSelector:@selector(lookupKeyForAsset:fromPackage:)]); + + // Verify messenger and textures forwards to FlutterEngine + XCTAssertEqual(registrar.messenger, mockBinaryMessenger); + XCTAssertEqual(registrar.textures, mockTextureRegistry); + + // Verify registerViewFactory:withId:, registerViewFactory:withId:gestureRecognizersBlockingPolicy + // forwards to FlutterEngine + id mockPlatformViewFactory = OCMProtocolMock(@protocol(FlutterPlatformViewFactory)); + [registrar registerViewFactory:mockPlatformViewFactory withId:factoryKey]; + [registrar registerViewFactory:mockPlatformViewFactory + withId:factoryKey + gestureRecognizersBlockingPolicy:FlutterPlatformViewGestureRecognizersBlockingPolicyEager]; + OCMVerify(times(2), [mockPlatformViewController registerViewFactory:mockPlatformViewFactory + withId:factoryKey + gestureRecognizersBlockingPolicy: + FlutterPlatformViewGestureRecognizersBlockingPolicyEager]); +} + - (void)testSendDeepLinkToFrameworkTimesOut { FlutterDartProject* project = [[FlutterDartProject alloc] init]; FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"engine" project:project]; @@ -640,5 +759,4 @@ - (void)testSendDeepLinkToFrameworkUsingNavigationChannelFails { XCTAssertFalse(success); }]; } - @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h index fddb2af72bbec..2a037d994ff0f 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h @@ -107,10 +107,26 @@ NS_ASSUME_NONNULL_BEGIN - (void)addSceneLifeCycleDelegate:(NSObject*)delegate; +/* + * Performs AppDelegate callback provided through the `FlutterImplicitEngineDelegate` protocol to + * inform apps that the implicit `FlutterEngine` has initialized. + */ +- (BOOL)performImplicitEngineCallback; + +/* + * Creates a `FlutterEngineApplicationRegistrar` that can be used to access application-level + * services, such as the engine's `FlutterBinaryMessenger` or `FlutterTextureRegistry`. + */ +- (NSObject*)registrarForApplication:(NSString*)key; + - (void)sendDeepLinkToFramework:(NSURL*)url completionHandler:(void (^)(BOOL success))completion; @end +@interface FlutterImplicitEngineBridgeImpl : NSObject + +@end + NS_ASSUME_NONNULL_END #endif // FLUTTER_SHELL_PLATFORM_DARWIN_IOS_FRAMEWORK_SOURCE_FLUTTERENGINE_INTERNAL_H_ diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h index 8f5759674b73d..c3d64636e0712 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h @@ -23,6 +23,7 @@ class ThreadHost; @property(readonly, nonatomic) FlutterEngineProcTable& embedderAPI; @property(readonly, nonatomic) BOOL enableEmbedderAPI; +@property(nonatomic, readonly) NSMutableDictionary* pluginPublications; @property(nonatomic, strong) FlutterRestorationPlugin* restorationPlugin; - (flutter::Shell&)shell; @@ -44,6 +45,8 @@ class ThreadHost; - (void)sceneDidEnterBackground:(NSNotification*)notification API_AVAILABLE(ios(13.0)); - (void)applicationWillEnterForeground:(NSNotification*)notification; - (void)applicationDidEnterBackground:(NSNotification*)notification; +- (NSString*)lookupKeyForAsset:(NSString*)asset; +- (NSString*)lookupKeyForAsset:(NSString*)asset fromPackage:(NSString*)package; @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm index e4f79ef77e37c..56c0574f0fe31 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm @@ -30,6 +30,9 @@ - (void)handleDidBecomeActive:(NSNotification*)notification NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions"); - (void)handleWillTerminate:(NSNotification*)notification NS_EXTENSION_UNAVAILABLE_IOS("Disallowed in app extensions"); + +@property(nonatomic, assign) BOOL didForwardApplicationWillLaunch; +@property(nonatomic, assign) BOOL didForwardApplicationDidLaunch; @end @implementation FlutterPluginAppLifeCycleDelegate { @@ -115,8 +118,36 @@ - (void)addDelegate:(NSObject*)delegate { } } +- (void)sceneFallbackDidFinishLaunchingApplication:(UIApplication*)application { + // If the application:didFinishingLaunchingWithOptions: event has already been sent to plugins, do + // not send again. + if (self.didForwardApplicationDidLaunch) { + return; + } + // Send nil launchOptions since UIKit sends nil when UIScene is enabled. + [self application:application didFinishLaunchingWithOptions:@{}]; +} + +- (void)sceneFallbackWillFinishLaunchingApplication:(UIApplication*)application { + // If the application:willFinishLaunchingWithOptions: event has already been sent to plugins, do + // not send again. + if (self.didForwardApplicationWillLaunch) { + return; + } + // If the application:didFinishingLaunchingWithOptions: event has already been sent to plugins, do + // not send willFinishLaunchingWithOptions since it should happen before, not after. + if (self.didForwardApplicationDidLaunch) { + return; + } + // Send nil launchOptions since UIKit sends nil when UIScene is enabled. + [self application:application willFinishLaunchingWithOptions:@{}]; +} + - (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + if (_delegates.count > 0) { + self.didForwardApplicationDidLaunch = YES; + } for (NSObject* delegate in [_delegates allObjects]) { if (!delegate) { continue; @@ -132,6 +163,9 @@ - (BOOL)application:(UIApplication*)application - (BOOL)application:(UIApplication*)application willFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + if (_delegates.count > 0) { + self.didForwardApplicationWillLaunch = YES; + } flutter::DartCallbackCache::LoadCacheFromDisk(); for (NSObject* delegate in [_delegates allObjects]) { if (!delegate) { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm index 84e17e2fa9442..000d61aa4cd40 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm @@ -64,6 +64,16 @@ - (BOOL)application:(UIApplication*)application API_AVAILABLE(ios(9.0)) { return YES; } + +- (BOOL)application:(UIApplication*)application + didFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + return YES; +} + +- (BOOL)application:(UIApplication*)application + willFinishLaunchingWithOptions:(NSDictionary*)launchOptions { + return YES; +} @end @interface FlutterPluginAppLifeCycleDelegateTest : XCTestCase @@ -425,4 +435,72 @@ - (void)testReleasesPluginOnDealloc { XCTAssertNil(weakDelegate); } +- (void)testApplicationWillFinishLaunchingSceneFallbackForwards { + FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; + id plugin = [[FakePlugin alloc] init]; + id mockPlugin = OCMPartialMock(plugin); + [delegate addDelegate:mockPlugin]; + id mockApplication = OCMClassMock([UIApplication class]); + NSDictionary* options = @{}; + + [delegate sceneFallbackWillFinishLaunchingApplication:mockApplication]; + OCMVerify(times(1), [mockPlugin application:mockApplication + willFinishLaunchingWithOptions:options]); +} + +- (void)testApplicationWillFinishLaunchingSceneFallbackNoForwardAfterWillLaunch { + FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; + id plugin = [[FakePlugin alloc] init]; + id mockPlugin = OCMPartialMock(plugin); + [delegate addDelegate:mockPlugin]; + id mockApplication = OCMClassMock([UIApplication class]); + NSDictionary* options = @{@"key" : @"value"}; + + [delegate application:mockApplication willFinishLaunchingWithOptions:options]; + [delegate sceneFallbackWillFinishLaunchingApplication:mockApplication]; + OCMVerify(times(1), [mockPlugin application:mockApplication + willFinishLaunchingWithOptions:options]); +} + +- (void)testApplicationWillFinishLaunchingSceneFallbackNoForwardAfterDidLaunch { + FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; + id plugin = [[FakePlugin alloc] init]; + id mockPlugin = OCMPartialMock(plugin); + [delegate addDelegate:mockPlugin]; + id mockApplication = OCMClassMock([UIApplication class]); + NSDictionary* options = @{@"key" : @"value"}; + + [delegate application:mockApplication didFinishLaunchingWithOptions:options]; + [delegate sceneFallbackWillFinishLaunchingApplication:mockApplication]; + OCMVerify(times(0), [mockPlugin application:mockApplication + willFinishLaunchingWithOptions:options]); +} + +- (void)testApplicationDidFinishLaunchingSceneFallbackForwards { + FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; + id plugin = [[FakePlugin alloc] init]; + id mockPlugin = OCMPartialMock(plugin); + [delegate addDelegate:mockPlugin]; + id mockApplication = OCMClassMock([UIApplication class]); + NSDictionary* options = @{}; + + [delegate sceneFallbackDidFinishLaunchingApplication:mockApplication]; + OCMVerify(times(1), [mockPlugin application:mockApplication + didFinishLaunchingWithOptions:options]); +} + +- (void)testApplicationDidFinishLaunchingSceneFallbackNoForward { + FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; + id plugin = [[FakePlugin alloc] init]; + id mockPlugin = OCMPartialMock(plugin); + [delegate addDelegate:mockPlugin]; + id mockApplication = OCMClassMock([UIApplication class]); + NSDictionary* options = @{@"key" : @"value"}; + + [delegate application:mockApplication didFinishLaunchingWithOptions:options]; + [delegate sceneFallbackDidFinishLaunchingApplication:mockApplication]; + OCMVerify(times(1), [mockPlugin application:mockApplication + didFinishLaunchingWithOptions:options]); +} + @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h index 29b6cf79ca143..529371a97c5cc 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h @@ -19,6 +19,22 @@ */ - (BOOL)hasPluginThatRespondsToSelector:(SEL)selector; +/** + * Forwards the `application:didFinishLaunchingWithOptions:` lifecycle event to plugins if they have + * not received it yet. This compensates for the UIScene migration, which causes storyboards (and + * thus the plugin registration via `FlutterImplicitEngineDelegate`) to be instantiated after the + * application launch events. + */ +- (void)sceneFallbackDidFinishLaunchingApplication:(UIApplication*)application; + +/** + * Forwards the `application:willFinishLaunchingWithOptions:` lifecycle event to plugins if they + * have not received it yet. This compensates for the UIScene migration, which causes storyboards + * (and thus the plugin registration via `FlutterImplicitEngineDelegate`) to be instantiated after + * the application launch events. + */ +- (void)sceneFallbackWillFinishLaunchingApplication:(UIApplication*)application; + /** * Forwards the application equivalent lifecycle event of * `sceneWillEnterForeground:` -> `applicationWillEnterForeground:` to plugins that have not adopted diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm index 6f706115e0b85..371611319d991 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm @@ -26,6 +26,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterLaunchEngine.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPlatformViews_Internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" @@ -78,6 +79,7 @@ @interface FlutterViewController () * pluginRegistrant = [FlutterSharedApplication.application.delegate performSelector:@selector(pluginRegistrant)]; [pluginRegistrant registerWithRegistry:self]; + performedCallback = YES; } + // When migrated to scenes, the FlutterViewController from the storyboard is initialized after the + // application launch events. Therefore, plugins may not be registered yet since they're expected + // to be registered during the implicit engine callbacks. As a workaround, send the app launch + // events after the application callbacks. + if (self.awokenFromNib && performedCallback && FlutterSharedApplication.hasSceneDelegate && + [appDelegate isKindOfClass:[FlutterAppDelegate class]]) { + id applicationLifeCycleDelegate = ((FlutterAppDelegate*)appDelegate).lifeCycleDelegate; + [applicationLifeCycleDelegate + sceneFallbackWillFinishLaunchingApplication:FlutterSharedApplication.application]; + [applicationLifeCycleDelegate + sceneFallbackDidFinishLaunchingApplication:FlutterSharedApplication.application]; + } + + _engineNeedsLaunch = YES; + _ongoingTouches = [[NSMutableSet alloc] init]; + + // TODO(cbracken): https://github.com/flutter/flutter/issues/157140 + // Eliminate method calls in initializers and dealloc. + [self loadDefaultSplashScreenView]; + [self performCommonViewControllerInitialization]; } - (BOOL)isViewOpaque { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm index 30f4881cbb5f3..97d105f4422b4 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewControllerTest.mm @@ -17,6 +17,8 @@ #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEmbedderKeyResponder.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterFakeKeyEvents.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h" +#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h" @@ -131,6 +133,7 @@ @interface FlutterViewController (Tests) @property(nonatomic, assign) BOOL keyboardAnimationIsShowing; @property(nonatomic, strong) VSyncClient* keyboardAnimationVSyncClient; @property(nonatomic, strong) VSyncClient* touchRateCorrectionVSyncClient; +@property(nonatomic, assign) BOOL awokenFromNib; - (void)createTouchRateCorrectionVSyncClientIfNeeded; - (void)surfaceUpdated:(BOOL)appeared; @@ -2477,14 +2480,101 @@ - (void)testStateIsActiveAndBackgroundWhenSceneStateIsInactive { [mockVC stopMocking]; } -- (void)testAppDelegatePluginRegistrant { +- (void)testPerformImplicitEngineCallbacks { id mockRegistrant = OCMProtocolMock(@protocol(FlutterPluginRegistrant)); id appDelegate = [[UIApplication sharedApplication] delegate]; + [appDelegate setMockLaunchEngine:self.mockEngine]; + UIStoryboard* storyboard = [UIStoryboard storyboardWithName:@"Flutter" bundle:nil]; XCTAssertTrue([appDelegate respondsToSelector:@selector(setPluginRegistrant:)]); [appDelegate setPluginRegistrant:mockRegistrant]; - FlutterViewController* viewController = [[FlutterViewController alloc] init]; + FlutterViewController* viewController = + (FlutterViewController*)[storyboard instantiateInitialViewController]; [appDelegate setPluginRegistrant:nil]; OCMVerify([mockRegistrant registerWithRegistry:viewController]); + OCMVerify([self.mockEngine performImplicitEngineCallback]); + [appDelegate setMockLaunchEngine:nil]; +} + +- (void)testPerformImplicitEngineCallbacksUsesAppLaunchEventFallbacks { + id mockEngine = OCMClassMock([FlutterEngine class]); + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine + nibName:nil + bundle:nil]; + FlutterViewController* viewControllerMock = OCMPartialMock(viewController); + OCMStub([mockEngine performImplicitEngineCallback]).andReturn(YES); + OCMStub([viewControllerMock awokenFromNib]).andReturn(YES); + + id mockApplication = OCMClassMock([UIApplication class]); + OCMStub([mockApplication sharedApplication]).andReturn(mockApplication); + FlutterAppDelegate* mockApplicationDelegate = OCMClassMock([FlutterAppDelegate class]); + OCMStub([mockApplication delegate]).andReturn(mockApplicationDelegate); + OCMStub([mockApplicationDelegate takeLaunchEngine]).andReturn(mockEngine); + + id mockScene = OCMClassMock([UIScene class]); + id mockSceneDelegate = OCMProtocolMock(@protocol(UISceneDelegate)); + OCMStub([mockScene delegate]).andReturn(mockSceneDelegate); + OCMStub([mockApplication connectedScenes]).andReturn([NSSet setWithObject:mockScene]); + + FlutterPluginAppLifeCycleDelegate* mockLifecycleDelegate = + OCMClassMock([FlutterPluginAppLifeCycleDelegate class]); + OCMStub([mockApplicationDelegate lifeCycleDelegate]).andReturn(mockLifecycleDelegate); + + [viewControllerMock sharedSetupWithProject:nil initialRoute:nil]; + OCMVerify([mockLifecycleDelegate sceneFallbackWillFinishLaunchingApplication:mockApplication]); + OCMVerify([mockLifecycleDelegate sceneFallbackDidFinishLaunchingApplication:mockApplication]); +} + +- (void)testPerformImplicitEngineCallbacksNoAppLaunchEventFallbacksWhenNoStoryboard { + id mockEngine = OCMClassMock([FlutterEngine class]); + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine + nibName:nil + bundle:nil]; + FlutterViewController* viewControllerMock = OCMPartialMock(viewController); + OCMStub([mockEngine performImplicitEngineCallback]).andReturn(YES); + OCMStub([viewControllerMock awokenFromNib]).andReturn(NO); + + id mockApplication = OCMClassMock([UIApplication class]); + OCMStub([mockApplication sharedApplication]).andReturn(mockApplication); + FlutterAppDelegate* mockApplicationDelegate = OCMClassMock([FlutterAppDelegate class]); + OCMStub([mockApplication delegate]).andReturn(mockApplicationDelegate); + OCMStub([mockApplicationDelegate takeLaunchEngine]).andReturn(mockEngine); + + id mockScene = OCMClassMock([UIScene class]); + id mockSceneDelegate = OCMProtocolMock(@protocol(UISceneDelegate)); + OCMStub([mockScene delegate]).andReturn(mockSceneDelegate); + OCMStub([mockApplication connectedScenes]).andReturn([NSSet setWithObject:mockScene]); + + FlutterPluginAppLifeCycleDelegate* mockLifecycleDelegate = + OCMClassMock([FlutterPluginAppLifeCycleDelegate class]); + OCMStub([mockApplicationDelegate lifeCycleDelegate]).andReturn(mockLifecycleDelegate); + + [viewControllerMock sharedSetupWithProject:nil initialRoute:nil]; + OCMReject([mockLifecycleDelegate sceneFallbackWillFinishLaunchingApplication:mockApplication]); + OCMReject([mockLifecycleDelegate sceneFallbackDidFinishLaunchingApplication:mockApplication]); +} + +- (void)testPerformImplicitEngineCallbacksNoAppLaunchEventFallbacksWhenNoScenes { + id mockEngine = OCMClassMock([FlutterEngine class]); + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:mockEngine + nibName:nil + bundle:nil]; + FlutterViewController* viewControllerMock = OCMPartialMock(viewController); + OCMStub([mockEngine performImplicitEngineCallback]).andReturn(YES); + OCMStub([viewControllerMock awokenFromNib]).andReturn(YES); + + id mockApplication = OCMClassMock([UIApplication class]); + OCMStub([mockApplication sharedApplication]).andReturn(mockApplication); + FlutterAppDelegate* mockApplicationDelegate = OCMClassMock([FlutterAppDelegate class]); + OCMStub([mockApplication delegate]).andReturn(mockApplicationDelegate); + OCMStub([mockApplicationDelegate takeLaunchEngine]).andReturn(mockEngine); + + FlutterPluginAppLifeCycleDelegate* mockLifecycleDelegate = + OCMClassMock([FlutterPluginAppLifeCycleDelegate class]); + OCMStub([mockApplicationDelegate lifeCycleDelegate]).andReturn(mockLifecycleDelegate); + + [viewControllerMock sharedSetupWithProject:nil initialRoute:nil]; + OCMReject([mockLifecycleDelegate sceneFallbackWillFinishLaunchingApplication:mockApplication]); + OCMReject([mockLifecycleDelegate sceneFallbackDidFinishLaunchingApplication:mockApplication]); } - (void)testGrabLaunchEngine { From 003eb6523ce8946b42aa22bbe7984924cc912476 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Mon, 6 Oct 2025 07:28:11 -0700 Subject: [PATCH 077/204] Roll vulkan-deps to a9e2ca3b (#176322) The build script for this version of the Vulkan headers defines VK_USE_PLATFORM_XLIB_KHR. This brings in some X11 headers that define macros with commonly used names. Files that include Vulkan headers may need to undefine some of these macros to prevent conflicts with other code that uses the same names. --- DEPS | 4 +- engine/src/build/config/compiler/BUILD.gn | 4 +- .../vulkan_validation_layers/BUILD.gn | 181 ++- .../contents/conical_gradient_contents.cc | 3 +- .../contents/linear_gradient_contents.cc | 3 +- .../contents/radial_gradient_contents.cc | 3 +- .../contents/runtime_effect_contents.cc | 4 +- .../contents/sweep_gradient_contents.cc | 3 +- .../backend/vulkan/capabilities_vk.cc | 6 + .../renderer/backend/vulkan/capabilities_vk.h | 4 + .../backend/vulkan/command_pool_vk.cc | 10 +- .../swapchain/khr/khr_swapchain_impl_vk.cc | 19 +- .../swapchain/khr/khr_swapchain_impl_vk.h | 1 + .../impeller/renderer/backend/vulkan/vk.h | 7 + .../flutter/impeller/renderer/capabilities.cc | 4 + .../flutter/impeller/renderer/capabilities.h | 3 + .../embedder/tests/embedder_gl_unittests.cc | 2 - .../flutter/sky/packages/sky_engine/LICENSE | 1251 ++++++++++------- .../flutter/vulkan/procs/vulkan_interface.h | 7 + 19 files changed, 931 insertions(+), 588 deletions(-) diff --git a/DEPS b/DEPS index f92d14e33c198..015f7117fbdd7 100644 --- a/DEPS +++ b/DEPS @@ -253,7 +253,7 @@ deps = { Var('chromium_git') + '/external/github.com/google/shaderc' + '@' + '37e25539ce199ecaf19fb7f7d27818716d36686d', 'engine/src/flutter/third_party/vulkan-deps': - Var('chromium_git') + '/vulkan-deps' + '@' + '938de304bdcb33049ec39ce45f16223eb6a960b6', + Var('chromium_git') + '/vulkan-deps' + '@' + 'a9e2ca3b57aba86a22a2df1b84bf12f8cc98806e', 'engine/src/flutter/third_party/flatbuffers': Var('chromium_git') + '/external/github.com/google/flatbuffers' + '@' + '067bfdbde9b10c1beb5d6b02d67ae9db8b96f736', @@ -522,7 +522,7 @@ deps = { Var('swiftshader_git') + '/SwiftShader.git' + '@' + 'd040a5bab638bf7c226235c95787ba6288bb6416', 'engine/src/flutter/third_party/angle': - Var('chromium_git') + '/angle/angle.git' + '@' + '6a09e41ce6ea8c93524faae1a925eb01562f53b1', + Var('flutter_git') + '/third_party/angle' + '@' + 'e28922c5e71eb32594c2562076cd5b15383e24d4', 'engine/src/flutter/third_party/vulkan_memory_allocator': Var('chromium_git') + '/external/github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator' + '@' + 'c788c52156f3ef7bc7ab769cb03c110a53ac8fcb', diff --git a/engine/src/build/config/compiler/BUILD.gn b/engine/src/build/config/compiler/BUILD.gn index 372945f8d6fc7..50f1b60eab1a4 100644 --- a/engine/src/build/config/compiler/BUILD.gn +++ b/engine/src/build/config/compiler/BUILD.gn @@ -98,6 +98,8 @@ config("compiler") { "/guard:cf", # Enable control flow guard security checks. ] } else { + cflags_c += [ "-std=c11" ] + # Common GCC compiler flags setup. # -------------------------------- cflags += [ "-fno-strict-aliasing" ] # See http://crbug.com/32204 @@ -213,8 +215,6 @@ config("compiler") { # an Objective C struct won't be called, which is very bad. cflags_objcc += [ "-fobjc-call-cxx-cdtors" ] - cflags_c += [ "-std=c99" ] - ldflags += common_mac_flags } else if (is_posix) { # CPU architecture. We may or may not be doing a cross compile now, so for diff --git a/engine/src/flutter/build/secondary/third_party/vulkan_validation_layers/BUILD.gn b/engine/src/flutter/build/secondary/third_party/vulkan_validation_layers/BUILD.gn index 9769e77d86c05..a036421b03074 100644 --- a/engine/src/flutter/build/secondary/third_party/vulkan_validation_layers/BUILD.gn +++ b/engine/src/flutter/build/secondary/third_party/vulkan_validation_layers/BUILD.gn @@ -61,11 +61,11 @@ config("vulkan_layer_config") { } vvl_sources = [ - "$_checkout_dir/layers/best_practices/best_practices_utils.cpp", "$_checkout_dir/layers/best_practices/best_practices_validation.h", "$_checkout_dir/layers/best_practices/bp_buffer.cpp", "$_checkout_dir/layers/best_practices/bp_cmd_buffer.cpp", "$_checkout_dir/layers/best_practices/bp_cmd_buffer_nv.cpp", + "$_checkout_dir/layers/best_practices/bp_constants.h", "$_checkout_dir/layers/best_practices/bp_copy_blit_resolve.cpp", "$_checkout_dir/layers/best_practices/bp_descriptor.cpp", "$_checkout_dir/layers/best_practices/bp_device_memory.cpp", @@ -76,8 +76,12 @@ vvl_sources = [ "$_checkout_dir/layers/best_practices/bp_pipeline.cpp", "$_checkout_dir/layers/best_practices/bp_ray_tracing.cpp", "$_checkout_dir/layers/best_practices/bp_render_pass.cpp", + "$_checkout_dir/layers/best_practices/bp_state_tracker.cpp", "$_checkout_dir/layers/best_practices/bp_state.h", + "$_checkout_dir/layers/best_practices/bp_state.cpp", "$_checkout_dir/layers/best_practices/bp_synchronization.cpp", + "$_checkout_dir/layers/best_practices/bp_utils.cpp", + "$_checkout_dir/layers/best_practices/bp_utils.h", "$_checkout_dir/layers/best_practices/bp_video.cpp", "$_checkout_dir/layers/best_practices/bp_wsi.cpp", "$_checkout_dir/layers/chassis/chassis.h", @@ -86,10 +90,18 @@ vvl_sources = [ "$_checkout_dir/layers/chassis/chassis_modification_state.h", "$_checkout_dir/layers/chassis/dispatch_object.h", "$_checkout_dir/layers/chassis/dispatch_object_manual.cpp", + "$_checkout_dir/layers/chassis/layer_object_id.h", "$_checkout_dir/layers/chassis/validation_object.h", + "$_checkout_dir/layers/containers/container_utils.h", "$_checkout_dir/layers/containers/custom_containers.h", - "$_checkout_dir/layers/containers/qfo_transfer.h", - "$_checkout_dir/layers/containers/range_vector.h", + "$_checkout_dir/layers/containers/limits.h", + "$_checkout_dir/layers/containers/small_container.h", + "$_checkout_dir/layers/containers/small_range_map.h", + "$_checkout_dir/layers/containers/small_vector.h", + "$_checkout_dir/layers/containers/span.h", + "$_checkout_dir/layers/containers/tls_guard.h", + "$_checkout_dir/layers/containers/range.h", + "$_checkout_dir/layers/containers/range_map.h", "$_checkout_dir/layers/containers/subresource_adapter.cpp", "$_checkout_dir/layers/containers/subresource_adapter.h", "$_checkout_dir/layers/core_checks/cc_android.cpp", @@ -98,10 +110,11 @@ vvl_sources = [ "$_checkout_dir/layers/core_checks/cc_cmd_buffer.cpp", "$_checkout_dir/layers/core_checks/cc_cmd_buffer_dynamic.cpp", "$_checkout_dir/layers/core_checks/cc_copy_blit_resolve.cpp", + "$_checkout_dir/layers/core_checks/cc_data_graph.cpp", "$_checkout_dir/layers/core_checks/cc_descriptor.cpp", "$_checkout_dir/layers/core_checks/cc_device.cpp", - "$_checkout_dir/layers/core_checks/cc_device_generated_commands.cpp", "$_checkout_dir/layers/core_checks/cc_device_memory.cpp", + "$_checkout_dir/layers/core_checks/cc_device_generated_commands.cpp", "$_checkout_dir/layers/core_checks/cc_drawdispatch.cpp", "$_checkout_dir/layers/core_checks/cc_external_object.cpp", "$_checkout_dir/layers/core_checks/cc_image.cpp", @@ -121,8 +134,11 @@ vvl_sources = [ "$_checkout_dir/layers/core_checks/cc_state_tracker.h", "$_checkout_dir/layers/core_checks/cc_submit.cpp", "$_checkout_dir/layers/core_checks/cc_submit.h", + "$_checkout_dir/layers/core_checks/cc_sync_vuid_maps.h", + "$_checkout_dir/layers/core_checks/cc_sync_vuid_maps.cpp", "$_checkout_dir/layers/core_checks/cc_synchronization.cpp", "$_checkout_dir/layers/core_checks/cc_synchronization.h", + "$_checkout_dir/layers/core_checks/cc_tensor.cpp", "$_checkout_dir/layers/core_checks/cc_video.cpp", "$_checkout_dir/layers/core_checks/cc_vuid_maps.cpp", "$_checkout_dir/layers/core_checks/cc_vuid_maps.h", @@ -136,10 +152,10 @@ vvl_sources = [ "$_checkout_dir/layers/error_message/error_location.cpp", "$_checkout_dir/layers/error_message/error_location.h", "$_checkout_dir/layers/error_message/error_strings.h", - "$_checkout_dir/layers/error_message/log_message_type.h", "$_checkout_dir/layers/error_message/logging.cpp", "$_checkout_dir/layers/error_message/logging.h", "$_checkout_dir/layers/error_message/record_object.h", + "$_checkout_dir/layers/error_message/log_message_type.h", "$_checkout_dir/layers/error_message/spirv_logging.cpp", "$_checkout_dir/layers/error_message/spirv_logging.h", "$_checkout_dir/layers/external/inplace_function.h", @@ -147,84 +163,92 @@ vvl_sources = [ "$_checkout_dir/layers/external/vma/vma.cpp", "$_checkout_dir/layers/external/vma/vma.h", "$_checkout_dir/layers/external/xxhash.h", + "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_validation_cmd_common.cpp", + "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_validation_cmd_common.h", + "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_copy_buffer_to_image.cpp", + "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_copy_buffer_to_image.h", + "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_dispatch.cpp", + "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_dispatch.h", + "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_draw.cpp", + "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_draw.h", + "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_trace_rays.cpp", + "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_trace_rays.h", + "$_checkout_dir/layers/gpuav/core/gpuav_settings.h", "$_checkout_dir/layers/gpuav/core/gpuav.h", "$_checkout_dir/layers/gpuav/core/gpuav_constants.h", "$_checkout_dir/layers/gpuav/core/gpuav_record.cpp", + "$_checkout_dir/layers/gpuav/core/gpuav_settings.cpp", "$_checkout_dir/layers/gpuav/core/gpuav_settings.h", "$_checkout_dir/layers/gpuav/core/gpuav_setup.cpp", + "$_checkout_dir/layers/gpuav/core/gpuav_features.cpp", + "$_checkout_dir/layers/gpuav/core/gpuav_validation_pipeline.h", + "$_checkout_dir/layers/gpuav/core/gpuav_validation_pipeline.cpp", "$_checkout_dir/layers/gpuav/debug_printf/debug_printf.cpp", "$_checkout_dir/layers/gpuav/debug_printf/debug_printf.h", "$_checkout_dir/layers/gpuav/descriptor_validation/gpuav_descriptor_set.cpp", "$_checkout_dir/layers/gpuav/descriptor_validation/gpuav_descriptor_set.h", "$_checkout_dir/layers/gpuav/descriptor_validation/gpuav_descriptor_validation.cpp", "$_checkout_dir/layers/gpuav/descriptor_validation/gpuav_descriptor_validation.h", - "$_checkout_dir/layers/gpuav/descriptor_validation/gpuav_image_layout.cpp", - "$_checkout_dir/layers/gpuav/descriptor_validation/gpuav_image_layout.h", "$_checkout_dir/layers/gpuav/error_message/gpuav_vuids.cpp", "$_checkout_dir/layers/gpuav/error_message/gpuav_vuids.h", - "$_checkout_dir/layers/gpuav/instrumentation/gpuav_instrumentation.cpp", - "$_checkout_dir/layers/gpuav/instrumentation/gpuav_instrumentation.h", "$_checkout_dir/layers/gpuav/instrumentation/gpuav_shader_instrumentor.cpp", "$_checkout_dir/layers/gpuav/instrumentation/gpuav_shader_instrumentor.h", + "$_checkout_dir/layers/gpuav/instrumentation/gpuav_instrumentation.cpp", + "$_checkout_dir/layers/gpuav/instrumentation/gpuav_instrumentation.h", + "$_checkout_dir/layers/gpuav/instrumentation/buffer_device_address.cpp", + "$_checkout_dir/layers/gpuav/instrumentation/buffer_device_address.h", + "$_checkout_dir/layers/gpuav/instrumentation/descriptor_checks.cpp", + "$_checkout_dir/layers/gpuav/instrumentation/descriptor_checks.h", + "$_checkout_dir/layers/gpuav/instrumentation/post_process_descriptor_indexing.cpp", + "$_checkout_dir/layers/gpuav/instrumentation/post_process_descriptor_indexing.h", + "$_checkout_dir/layers/gpuav/resources/gpuav_vulkan_objects.cpp", + "$_checkout_dir/layers/gpuav/resources/gpuav_vulkan_objects.h", "$_checkout_dir/layers/gpuav/resources/gpuav_shader_resources.h", "$_checkout_dir/layers/gpuav/resources/gpuav_state_trackers.cpp", "$_checkout_dir/layers/gpuav/resources/gpuav_state_trackers.h", - "$_checkout_dir/layers/gpuav/resources/gpuav_vulkan_objects.cpp", - "$_checkout_dir/layers/gpuav/resources/gpuav_vulkan_objects.h", + "$_checkout_dir/layers/gpuav/shaders/validation_cmd/push_data.h", "$_checkout_dir/layers/gpuav/shaders/gpuav_error_codes.h", "$_checkout_dir/layers/gpuav/shaders/gpuav_error_header.h", "$_checkout_dir/layers/gpuav/shaders/gpuav_shaders_constants.h", - "$_checkout_dir/layers/gpuav/shaders/validation_cmd/draw_push_data.h", - "$_checkout_dir/layers/gpuav/spirv/buffer_device_address_pass.cpp", - "$_checkout_dir/layers/gpuav/spirv/buffer_device_address_pass.h", - "$_checkout_dir/layers/gpuav/spirv/debug_printf_pass.cpp", - "$_checkout_dir/layers/gpuav/spirv/debug_printf_pass.h", + "$_checkout_dir/layers/gpuav/spirv/descriptor_indexing_oob_pass.cpp", + "$_checkout_dir/layers/gpuav/spirv/descriptor_indexing_oob_pass.h", "$_checkout_dir/layers/gpuav/spirv/descriptor_class_general_buffer_pass.cpp", "$_checkout_dir/layers/gpuav/spirv/descriptor_class_general_buffer_pass.h", "$_checkout_dir/layers/gpuav/spirv/descriptor_class_texel_buffer_pass.cpp", "$_checkout_dir/layers/gpuav/spirv/descriptor_class_texel_buffer_pass.h", - "$_checkout_dir/layers/gpuav/spirv/descriptor_indexing_oob_pass.cpp", - "$_checkout_dir/layers/gpuav/spirv/descriptor_indexing_oob_pass.h", + "$_checkout_dir/layers/gpuav/spirv/buffer_device_address_pass.cpp", + "$_checkout_dir/layers/gpuav/spirv/buffer_device_address_pass.h", + "$_checkout_dir/layers/gpuav/spirv/post_process_descriptor_indexing_pass.cpp", + "$_checkout_dir/layers/gpuav/spirv/post_process_descriptor_indexing_pass.h", + "$_checkout_dir/layers/gpuav/spirv/vertex_attribute_fetch_oob.cpp", + "$_checkout_dir/layers/gpuav/spirv/vertex_attribute_fetch_oob.h", + "$_checkout_dir/layers/gpuav/spirv/log_error_pass.cpp", + "$_checkout_dir/layers/gpuav/spirv/log_error_pass.h", "$_checkout_dir/layers/gpuav/spirv/function_basic_block.cpp", "$_checkout_dir/layers/gpuav/spirv/function_basic_block.h", - "$_checkout_dir/layers/gpuav/spirv/inject_conditional_function_pass.cpp", - "$_checkout_dir/layers/gpuav/spirv/inject_conditional_function_pass.h", - "$_checkout_dir/layers/gpuav/spirv/inject_function_pass.cpp", - "$_checkout_dir/layers/gpuav/spirv/inject_function_pass.h", - "$_checkout_dir/layers/gpuav/spirv/instruction.cpp", - "$_checkout_dir/layers/gpuav/spirv/instruction.h", "$_checkout_dir/layers/gpuav/spirv/interface.h", "$_checkout_dir/layers/gpuav/spirv/link.h", "$_checkout_dir/layers/gpuav/spirv/module.cpp", "$_checkout_dir/layers/gpuav/spirv/module.h", "$_checkout_dir/layers/gpuav/spirv/pass.cpp", "$_checkout_dir/layers/gpuav/spirv/pass.h", - "$_checkout_dir/layers/gpuav/spirv/post_process_descriptor_indexing_pass.cpp", - "$_checkout_dir/layers/gpuav/spirv/post_process_descriptor_indexing_pass.h", "$_checkout_dir/layers/gpuav/spirv/ray_query_pass.cpp", "$_checkout_dir/layers/gpuav/spirv/ray_query_pass.h", + "$_checkout_dir/layers/gpuav/spirv/debug_printf_pass.cpp", + "$_checkout_dir/layers/gpuav/spirv/debug_printf_pass.h", "$_checkout_dir/layers/gpuav/spirv/type_manager.cpp", "$_checkout_dir/layers/gpuav/spirv/type_manager.h", - "$_checkout_dir/layers/gpuav/spirv/vertex_attribute_fetch_oob.cpp", - "$_checkout_dir/layers/gpuav/spirv/vertex_attribute_fetch_oob.h", - "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_copy_buffer_to_image.cpp", - "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_copy_buffer_to_image.h", - "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_dispatch.cpp", - "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_dispatch.h", - "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_draw.cpp", - "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_draw.h", - "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_trace_rays.cpp", - "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_trace_rays.h", - "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_validation_cmd_common.cpp", - "$_checkout_dir/layers/gpuav/validation_cmd/gpuav_validation_cmd_common.h", "$_checkout_dir/layers/layer_options.cpp", "$_checkout_dir/layers/layer_options.h", + "$_checkout_dir/layers/layer_options_validation.h", "$_checkout_dir/layers/object_tracker/object_lifetime_validation.h", "$_checkout_dir/layers/object_tracker/object_tracker_utils.cpp", "$_checkout_dir/layers/state_tracker/buffer_state.cpp", "$_checkout_dir/layers/state_tracker/buffer_state.h", "$_checkout_dir/layers/state_tracker/cmd_buffer_state.cpp", "$_checkout_dir/layers/state_tracker/cmd_buffer_state.h", + "$_checkout_dir/layers/state_tracker/data_graph_pipeline_session_state.cpp", + "$_checkout_dir/layers/state_tracker/data_graph_pipeline_session_state.h", "$_checkout_dir/layers/state_tracker/descriptor_sets.cpp", "$_checkout_dir/layers/state_tracker/descriptor_sets.h", "$_checkout_dir/layers/state_tracker/device_generated_commands_state.cpp", @@ -233,18 +257,23 @@ vvl_sources = [ "$_checkout_dir/layers/state_tracker/device_memory_state.h", "$_checkout_dir/layers/state_tracker/device_state.cpp", "$_checkout_dir/layers/state_tracker/device_state.h", + "$_checkout_dir/layers/state_tracker/event_map.h", "$_checkout_dir/layers/state_tracker/fence_state.cpp", "$_checkout_dir/layers/state_tracker/fence_state.h", "$_checkout_dir/layers/state_tracker/image_layout_map.cpp", "$_checkout_dir/layers/state_tracker/image_layout_map.h", "$_checkout_dir/layers/state_tracker/image_state.cpp", "$_checkout_dir/layers/state_tracker/image_state.h", + "$_checkout_dir/layers/state_tracker/last_bound_state.cpp", + "$_checkout_dir/layers/state_tracker/last_bound_state.h", "$_checkout_dir/layers/state_tracker/pipeline_layout_state.cpp", "$_checkout_dir/layers/state_tracker/pipeline_layout_state.h", "$_checkout_dir/layers/state_tracker/pipeline_state.cpp", "$_checkout_dir/layers/state_tracker/pipeline_state.h", - "$_checkout_dir/layers/state_tracker/pipeline_sub_state.cpp", - "$_checkout_dir/layers/state_tracker/pipeline_sub_state.h", + "$_checkout_dir/layers/state_tracker/pipeline_library_state.cpp", + "$_checkout_dir/layers/state_tracker/pipeline_library_state.h", + "$_checkout_dir/layers/state_tracker/push_constant_data.h", + "$_checkout_dir/layers/state_tracker/query_state.cpp", "$_checkout_dir/layers/state_tracker/query_state.h", "$_checkout_dir/layers/state_tracker/queue_state.cpp", "$_checkout_dir/layers/state_tracker/queue_state.h", @@ -262,14 +291,19 @@ vvl_sources = [ "$_checkout_dir/layers/state_tracker/shader_object_state.h", "$_checkout_dir/layers/state_tracker/shader_stage_state.cpp", "$_checkout_dir/layers/state_tracker/shader_stage_state.h", + "$_checkout_dir/layers/state_tracker/special_supported.h", "$_checkout_dir/layers/state_tracker/state_object.cpp", "$_checkout_dir/layers/state_tracker/state_object.h", "$_checkout_dir/layers/state_tracker/state_tracker.cpp", "$_checkout_dir/layers/state_tracker/state_tracker.h", "$_checkout_dir/layers/state_tracker/submission_reference.h", + "$_checkout_dir/layers/state_tracker/tensor_state.cpp", + "$_checkout_dir/layers/state_tracker/tensor_state.h", "$_checkout_dir/layers/state_tracker/vertex_index_buffer_state.h", "$_checkout_dir/layers/state_tracker/video_session_state.cpp", "$_checkout_dir/layers/state_tracker/video_session_state.h", + "$_checkout_dir/layers/state_tracker/wsi_state.cpp", + "$_checkout_dir/layers/state_tracker/wsi_state.h", "$_checkout_dir/layers/stateless/sl_buffer.cpp", "$_checkout_dir/layers/stateless/sl_cmd_buffer.cpp", "$_checkout_dir/layers/stateless/sl_cmd_buffer_dynamic.cpp", @@ -284,7 +318,10 @@ vvl_sources = [ "$_checkout_dir/layers/stateless/sl_ray_tracing.cpp", "$_checkout_dir/layers/stateless/sl_render_pass.cpp", "$_checkout_dir/layers/stateless/sl_shader_object.cpp", + "$_checkout_dir/layers/stateless/sl_spirv.cpp", + "$_checkout_dir/layers/stateless/sl_spirv.h", "$_checkout_dir/layers/stateless/sl_synchronization.cpp", + "$_checkout_dir/layers/stateless/sl_tensor.cpp", "$_checkout_dir/layers/stateless/sl_utils.cpp", "$_checkout_dir/layers/stateless/sl_vuid_maps.cpp", "$_checkout_dir/layers/stateless/sl_vuid_maps.h", @@ -300,6 +337,7 @@ vvl_sources = [ "$_checkout_dir/layers/sync/sync_common.h", "$_checkout_dir/layers/sync/sync_error_messages.cpp", "$_checkout_dir/layers/sync/sync_error_messages.h", + "$_checkout_dir/layers/sync/sync_image.cpp", "$_checkout_dir/layers/sync/sync_image.h", "$_checkout_dir/layers/sync/sync_op.cpp", "$_checkout_dir/layers/sync/sync_op.h", @@ -312,34 +350,44 @@ vvl_sources = [ "$_checkout_dir/layers/sync/sync_stats.h", "$_checkout_dir/layers/sync/sync_submit.cpp", "$_checkout_dir/layers/sync/sync_submit.h", - "$_checkout_dir/layers/sync/sync_utils.cpp", - "$_checkout_dir/layers/sync/sync_utils.h", "$_checkout_dir/layers/sync/sync_validation.cpp", "$_checkout_dir/layers/sync/sync_validation.h", - "$_checkout_dir/layers/sync/sync_vuid_maps.cpp", - "$_checkout_dir/layers/sync/sync_vuid_maps.h", "$_checkout_dir/layers/thread_tracker/thread_safety_validation.cpp", "$_checkout_dir/layers/thread_tracker/thread_safety_validation.h", "$_checkout_dir/layers/utils/android_ndk_types.h", - "$_checkout_dir/layers/utils/android_ndk_types.h", + "$_checkout_dir/layers/utils/action_command_utils.h", + "$_checkout_dir/layers/utils/assert_utils.h", "$_checkout_dir/layers/utils/cast_utils.h", "$_checkout_dir/layers/utils/convert_utils.cpp", "$_checkout_dir/layers/utils/convert_utils.h", + "$_checkout_dir/layers/utils/dispatch_utils.cpp", + "$_checkout_dir/layers/utils/dispatch_utils.h", + "$_checkout_dir/layers/utils/file_system_utils.cpp", + "$_checkout_dir/layers/utils/file_system_utils.h", "$_checkout_dir/layers/utils/hash_util.cpp", "$_checkout_dir/layers/utils/hash_util.h", "$_checkout_dir/layers/utils/hash_vk_types.h", + "$_checkout_dir/layers/utils/image_utils.cpp", + "$_checkout_dir/layers/utils/image_utils.h", "$_checkout_dir/layers/utils/image_layout_utils.cpp", "$_checkout_dir/layers/utils/image_layout_utils.h", + "$_checkout_dir/layers/utils/lock_utils.h", + "$_checkout_dir/layers/utils/math_utils.h", + "$_checkout_dir/layers/utils/keyboard.cpp", + "$_checkout_dir/layers/utils/keyboard.h", "$_checkout_dir/layers/utils/ray_tracing_utils.cpp", "$_checkout_dir/layers/utils/ray_tracing_utils.h", "$_checkout_dir/layers/utils/shader_utils.cpp", "$_checkout_dir/layers/utils/shader_utils.h", + "$_checkout_dir/layers/utils/sync_utils.cpp", + "$_checkout_dir/layers/utils/sync_utils.h", + "$_checkout_dir/layers/utils/text_utils.cpp", + "$_checkout_dir/layers/utils/text_utils.h", "$_checkout_dir/layers/utils/vk_layer_extension_utils.cpp", "$_checkout_dir/layers/utils/vk_layer_extension_utils.h", - "$_checkout_dir/layers/utils/vk_layer_utils.cpp", - "$_checkout_dir/layers/utils/vk_layer_utils.h", "$_checkout_dir/layers/utils/vk_struct_compare.cpp", "$_checkout_dir/layers/utils/vk_struct_compare.h", + "$_checkout_dir/layers/utils/vk_api_utils.h", "$_checkout_dir/layers/vk_layer_config.cpp", "$_checkout_dir/layers/vk_layer_config.h", "$_checkout_dir/layers/vulkan/generated/best_practices.cpp", @@ -347,6 +395,8 @@ vvl_sources = [ "$_checkout_dir/layers/vulkan/generated/best_practices_instance_methods.h", "$_checkout_dir/layers/vulkan/generated/chassis.cpp", "$_checkout_dir/layers/vulkan/generated/command_validation.cpp", + "$_checkout_dir/layers/vulkan/generated/deprecation.cpp", + "$_checkout_dir/layers/vulkan/generated/deprecation.h", "$_checkout_dir/layers/vulkan/generated/device_features.cpp", "$_checkout_dir/layers/vulkan/generated/device_features.h", "$_checkout_dir/layers/vulkan/generated/dispatch_functions.h", @@ -362,22 +412,9 @@ vvl_sources = [ "$_checkout_dir/layers/vulkan/generated/error_location_helper.h", "$_checkout_dir/layers/vulkan/generated/feature_requirements_helper.cpp", "$_checkout_dir/layers/vulkan/generated/feature_requirements_helper.h", - "$_checkout_dir/layers/vulkan/generated/instrumentation_buffer_device_address_comp.cpp", - "$_checkout_dir/layers/vulkan/generated/instrumentation_buffer_device_address_comp.h", - "$_checkout_dir/layers/vulkan/generated/instrumentation_descriptor_class_general_buffer_comp.cpp", - "$_checkout_dir/layers/vulkan/generated/instrumentation_descriptor_class_general_buffer_comp.h", - "$_checkout_dir/layers/vulkan/generated/instrumentation_descriptor_class_texel_buffer_comp.cpp", - "$_checkout_dir/layers/vulkan/generated/instrumentation_descriptor_class_texel_buffer_comp.h", - "$_checkout_dir/layers/vulkan/generated/instrumentation_descriptor_indexing_oob_bindless_comp.cpp", - "$_checkout_dir/layers/vulkan/generated/instrumentation_descriptor_indexing_oob_bindless_comp.h", - "$_checkout_dir/layers/vulkan/generated/instrumentation_descriptor_indexing_oob_non_bindless_comp.cpp", - "$_checkout_dir/layers/vulkan/generated/instrumentation_descriptor_indexing_oob_non_bindless_comp.h", - "$_checkout_dir/layers/vulkan/generated/instrumentation_post_process_descriptor_index_comp.cpp", - "$_checkout_dir/layers/vulkan/generated/instrumentation_post_process_descriptor_index_comp.h", - "$_checkout_dir/layers/vulkan/generated/instrumentation_ray_query_comp.cpp", - "$_checkout_dir/layers/vulkan/generated/instrumentation_ray_query_comp.h", - "$_checkout_dir/layers/vulkan/generated/instrumentation_vertex_attribute_fetch_oob_vert.cpp", - "$_checkout_dir/layers/vulkan/generated/instrumentation_vertex_attribute_fetch_oob_vert.h", + "$_checkout_dir/layers/vulkan/generated/feature_not_present.cpp", + "$_checkout_dir/layers/vulkan/generated/gpuav_offline_spirv.cpp", + "$_checkout_dir/layers/vulkan/generated/gpuav_offline_spirv.h", "$_checkout_dir/layers/vulkan/generated/object_tracker.cpp", "$_checkout_dir/layers/vulkan/generated/object_tracker_device_methods.h", "$_checkout_dir/layers/vulkan/generated/object_tracker_instance_methods.h", @@ -387,6 +424,7 @@ vvl_sources = [ "$_checkout_dir/layers/vulkan/generated/spirv_grammar_helper.h", "$_checkout_dir/layers/vulkan/generated/spirv_tools_commit_id.h", "$_checkout_dir/layers/vulkan/generated/spirv_validation_helper.cpp", + "$_checkout_dir/layers/vulkan/generated/spirv_validation_helper.h", "$_checkout_dir/layers/vulkan/generated/stateless_device_methods.h", "$_checkout_dir/layers/vulkan/generated/stateless_instance_methods.h", "$_checkout_dir/layers/vulkan/generated/stateless_validation_helper.cpp", @@ -398,22 +436,9 @@ vvl_sources = [ "$_checkout_dir/layers/vulkan/generated/valid_enum_values.cpp", "$_checkout_dir/layers/vulkan/generated/valid_enum_values.h", "$_checkout_dir/layers/vulkan/generated/valid_flag_values.cpp", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_copy_buffer_to_image_comp.cpp", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_copy_buffer_to_image_comp.h", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_count_buffer_comp.cpp", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_count_buffer_comp.h", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_dispatch_comp.cpp", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_dispatch_comp.h", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_draw_indexed_indirect_index_buffer_comp.cpp", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_draw_indexed_indirect_index_buffer_comp.h", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_draw_mesh_indirect_comp.cpp", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_draw_mesh_indirect_comp.h", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_first_instance_comp.cpp", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_first_instance_comp.h", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_trace_rays_rgen.cpp", - "$_checkout_dir/layers/vulkan/generated/validation_cmd_trace_rays_rgen.h", "$_checkout_dir/layers/vulkan/generated/validation_object.cpp", - "$_checkout_dir/layers/vulkan/generated/validation_object_methods.h", + "$_checkout_dir/layers/vulkan/generated/validation_object_device_methods.h", + "$_checkout_dir/layers/vulkan/generated/validation_object_instance_methods.h", "$_checkout_dir/layers/vulkan/generated/vk_api_version.h", "$_checkout_dir/layers/vulkan/generated/vk_api_version.h", "$_checkout_dir/layers/vulkan/generated/vk_dispatch_table_helper.cpp", diff --git a/engine/src/flutter/impeller/entity/contents/conical_gradient_contents.cc b/engine/src/flutter/impeller/entity/contents/conical_gradient_contents.cc index 9ab21851b4e50..da8afb318755b 100644 --- a/engine/src/flutter/impeller/entity/contents/conical_gradient_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/conical_gradient_contents.cc @@ -131,7 +131,8 @@ bool ConicalGradientContents::RenderSSBO(const ContentContext& renderer, frag_info.colors_length = colors.size(); auto color_buffer = data_host_buffer.Emplace( colors.data(), colors.size() * sizeof(StopData), - data_host_buffer.GetMinimumUniformAlignment()); + renderer.GetDeviceCapabilities() + .GetMinimumStorageBufferAlignment()); FS::BindFragInfo(pass, data_host_buffer.EmplaceUniform(frag_info)); FS::BindColorData(pass, color_buffer); diff --git a/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.cc b/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.cc index e3667abcf74aa..8c0daa6d41d71 100644 --- a/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.cc @@ -311,7 +311,8 @@ bool LinearGradientContents::RenderSSBO(const ContentContext& renderer, frag_info.colors_length = colors.size(); auto color_buffer = data_host_buffer.Emplace( colors.data(), colors.size() * sizeof(StopData), - data_host_buffer.GetMinimumUniformAlignment()); + renderer.GetDeviceCapabilities() + .GetMinimumStorageBufferAlignment()); pass.SetCommandLabel("LinearGradientSSBOFill"); diff --git a/engine/src/flutter/impeller/entity/contents/radial_gradient_contents.cc b/engine/src/flutter/impeller/entity/contents/radial_gradient_contents.cc index 1e2044f2f891e..add68aed41149 100644 --- a/engine/src/flutter/impeller/entity/contents/radial_gradient_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/radial_gradient_contents.cc @@ -107,7 +107,8 @@ bool RadialGradientContents::RenderSSBO(const ContentContext& renderer, frag_info.colors_length = colors.size(); auto color_buffer = data_host_buffer.Emplace( colors.data(), colors.size() * sizeof(StopData), - data_host_buffer.GetMinimumUniformAlignment()); + renderer.GetDeviceCapabilities() + .GetMinimumStorageBufferAlignment()); pass.SetCommandLabel("RadialGradientSSBOFill"); FS::BindFragInfo(pass, data_host_buffer.EmplaceUniform(frag_info)); diff --git a/engine/src/flutter/impeller/entity/contents/runtime_effect_contents.cc b/engine/src/flutter/impeller/entity/contents/runtime_effect_contents.cc index 7ea27af2ed400..026cc29ded407 100644 --- a/engine/src/flutter/impeller/entity/contents/runtime_effect_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/runtime_effect_contents.cc @@ -49,12 +49,10 @@ BufferView RuntimeEffectContents::EmplaceVulkanUniform( input_data->data())[uniform_byte_index++]); } } - size_t alignment = std::max(sizeof(float) * uniform_buffer.size(), - minimum_uniform_alignment); return data_host_buffer.Emplace( reinterpret_cast(uniform_buffer.data()), - sizeof(float) * uniform_buffer.size(), alignment); + sizeof(float) * uniform_buffer.size(), minimum_uniform_alignment); } void RuntimeEffectContents::SetRuntimeStage( diff --git a/engine/src/flutter/impeller/entity/contents/sweep_gradient_contents.cc b/engine/src/flutter/impeller/entity/contents/sweep_gradient_contents.cc index 666dbd5e23a83..464432dccc22c 100644 --- a/engine/src/flutter/impeller/entity/contents/sweep_gradient_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/sweep_gradient_contents.cc @@ -116,7 +116,8 @@ bool SweepGradientContents::RenderSSBO(const ContentContext& renderer, frag_info.colors_length = colors.size(); auto color_buffer = data_host_buffer.Emplace( colors.data(), colors.size() * sizeof(StopData), - data_host_buffer.GetMinimumUniformAlignment()); + renderer.GetDeviceCapabilities() + .GetMinimumStorageBufferAlignment()); pass.SetCommandLabel("SweepGradientSSBOFill"); diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc index 478fc2364eaf4..5a13cd97df947 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.cc @@ -647,6 +647,8 @@ bool CapabilitiesVK::SetPhysicalDevice( minimum_uniform_alignment_ = device_properties_.limits.minUniformBufferOffsetAlignment; + minimum_storage_alignment_ = + device_properties_.limits.minStorageBufferOffsetAlignment; return true; } @@ -730,6 +732,10 @@ size_t CapabilitiesVK::GetMinimumUniformAlignment() const { return minimum_uniform_alignment_; } +size_t CapabilitiesVK::GetMinimumStorageBufferAlignment() const { + return minimum_storage_alignment_; +} + bool CapabilitiesVK::NeedsPartitionedHostBuffer() const { return false; } diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.h index b7740f0a33d6f..9835ca836351b 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/capabilities_vk.h @@ -281,6 +281,9 @@ class CapabilitiesVK final : public Capabilities, // |Capabilities| size_t GetMinimumUniformAlignment() const override; + // |Capabilities| + size_t GetMinimumStorageBufferAlignment() const override; + // |Capabilities| bool NeedsPartitionedHostBuffer() const override; @@ -326,6 +329,7 @@ class CapabilitiesVK final : public Capabilities, vk::PhysicalDevice physical_device_; vk::PhysicalDeviceProperties device_properties_; size_t minimum_uniform_alignment_ = 256; + size_t minimum_storage_alignment_ = 256; bool supports_compute_subgroups_ = false; bool supports_device_transient_textures_ = false; bool supports_texture_fixed_rate_compression_ = false; diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk.cc index 883f101a35a9c..a96dac463577c 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/command_pool_vk.cc @@ -272,12 +272,14 @@ void CommandPoolRecyclerVK::Reclaim( return; } auto device = strong_context->GetDevice(); + vk::CommandPoolResetFlags flags; if (should_trim) { buffers.clear(); - device.resetCommandPool(pool.get(), - vk::CommandPoolResetFlagBits::eReleaseResources); - } else { - device.resetCommandPool(pool.get(), {}); + flags = vk::CommandPoolResetFlagBits::eReleaseResources; + } + const auto result = device.resetCommandPool(pool.get(), flags); + if (result != vk::Result::eSuccess) { + VALIDATION_LOG << "Could not reset command pool: " << vk::to_string(result); } // Move the pool to the recycled list. diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.cc b/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.cc index 84ebc9e944df9..d1a8bd3b0869e 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.cc +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.cc @@ -23,7 +23,6 @@ static constexpr size_t kMaxFramesInFlight = 2u; struct KHRFrameSynchronizerVK { vk::UniqueFence acquire; vk::UniqueSemaphore render_ready; - vk::UniqueSemaphore present_ready; std::shared_ptr final_cmd_buffer; bool is_valid = false; // Whether the renderer attached an onscreen command buffer to render to. @@ -33,16 +32,13 @@ struct KHRFrameSynchronizerVK { auto acquire_res = device.createFenceUnique( vk::FenceCreateInfo{vk::FenceCreateFlagBits::eSignaled}); auto render_res = device.createSemaphoreUnique({}); - auto present_res = device.createSemaphoreUnique({}); if (acquire_res.result != vk::Result::eSuccess || - render_res.result != vk::Result::eSuccess || - present_res.result != vk::Result::eSuccess) { + render_res.result != vk::Result::eSuccess) { VALIDATION_LOG << "Could not create synchronizer."; return; } acquire = std::move(acquire_res.value); render_ready = std::move(render_res.value); - present_ready = std::move(present_res.value); is_valid = true; } @@ -224,6 +220,7 @@ KHRSwapchainImplVK::KHRSwapchainImplVK(const std::shared_ptr& context, swapchain_info.imageExtent.height); std::vector> swapchain_images; + std::vector present_semaphores; for (const auto& image : images) { auto swapchain_image = std::make_shared( texture_desc, // texture descriptor @@ -242,6 +239,13 @@ KHRSwapchainImplVK::KHRSwapchainImplVK(const std::shared_ptr& context, "SwapchainImageView" + std::to_string(swapchain_images.size())); swapchain_images.emplace_back(swapchain_image); + + auto present_res = vk_context.GetDevice().createSemaphoreUnique({}); + if (present_res.result != vk::Result::eSuccess) { + VALIDATION_LOG << "Could not create presentation semaphore."; + return; + } + present_semaphores.push_back(std::move(present_res.value)); } std::vector> synchronizers; @@ -264,6 +268,7 @@ KHRSwapchainImplVK::KHRSwapchainImplVK(const std::shared_ptr& context, enable_msaa); images_ = std::move(swapchain_images); synchronizers_ = std::move(synchronizers); + present_semaphores_ = std::move(present_semaphores); current_frame_ = synchronizers_.size() - 1u; size_ = size; enable_msaa_ = enable_msaa; @@ -471,7 +476,7 @@ bool KHRSwapchainImplVK::Present( vk::PipelineStageFlagBits::eColorAttachmentOutput; submit_info.setWaitDstStageMask(wait_stage); submit_info.setWaitSemaphores(*sync->render_ready); - submit_info.setSignalSemaphores(*sync->present_ready); + submit_info.setSignalSemaphores(*present_semaphores_[index]); submit_info.setCommandBuffers(vk_final_cmd_buffer); auto result = context.GetGraphicsQueue()->Submit(submit_info, *sync->acquire); @@ -490,7 +495,7 @@ bool KHRSwapchainImplVK::Present( vk::PresentInfoKHR present_info; present_info.setSwapchains(*swapchain_); present_info.setImageIndices(indices); - present_info.setWaitSemaphores(*sync->present_ready); + present_info.setWaitSemaphores(*present_semaphores_[index]); auto result = context.GetGraphicsQueue()->Present(present_info); diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.h index 14dd47775022f..48003faa5efd2 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/swapchain/khr/khr_swapchain_impl_vk.h @@ -74,6 +74,7 @@ class KHRSwapchainImplVK final std::shared_ptr transients_; std::vector> images_; std::vector> synchronizers_; + std::vector present_semaphores_; size_t current_frame_ = 0u; ISize size_; bool enable_msaa_ = true; diff --git a/engine/src/flutter/impeller/renderer/backend/vulkan/vk.h b/engine/src/flutter/impeller/renderer/backend/vulkan/vk.h index a688985c60b3f..5187614905578 100644 --- a/engine/src/flutter/impeller/renderer/backend/vulkan/vk.h +++ b/engine/src/flutter/impeller/renderer/backend/vulkan/vk.h @@ -74,6 +74,13 @@ #include "vulkan/vulkan.hpp" // IWYU pragma: keep. +// The Vulkan headers may bring in X11 headers which define some macros that +// conflict with other code. Undefine these macros after including Vulkan. +#undef Bool +#undef None +#undef Status +#undef Success + static_assert(VK_HEADER_VERSION >= 215, "Vulkan headers must not be too old."); #endif // FLUTTER_IMPELLER_RENDERER_BACKEND_VULKAN_VK_H_ diff --git a/engine/src/flutter/impeller/renderer/capabilities.cc b/engine/src/flutter/impeller/renderer/capabilities.cc index c4dc33e74a389..a0fb11ca97e90 100644 --- a/engine/src/flutter/impeller/renderer/capabilities.cc +++ b/engine/src/flutter/impeller/renderer/capabilities.cc @@ -11,6 +11,10 @@ Capabilities::Capabilities() = default; Capabilities::~Capabilities() = default; +size_t Capabilities::GetMinimumStorageBufferAlignment() const { + return GetMinimumUniformAlignment(); +} + class StandardCapabilities final : public Capabilities { public: // |Capabilities| diff --git a/engine/src/flutter/impeller/renderer/capabilities.h b/engine/src/flutter/impeller/renderer/capabilities.h index f74e81e05eba1..f3874b3990842 100644 --- a/engine/src/flutter/impeller/renderer/capabilities.h +++ b/engine/src/flutter/impeller/renderer/capabilities.h @@ -126,6 +126,9 @@ class Capabilities { /// @brief The minimum alignment of uniform value offsets in bytes. virtual size_t GetMinimumUniformAlignment() const = 0; + /// @brief The minimum alignment of storage buffer value offsets in bytes. + virtual size_t GetMinimumStorageBufferAlignment() const; + /// @brief Whether the host buffer should use separate device buffers /// for indexes from other data. virtual bool NeedsPartitionedHostBuffer() const = 0; diff --git a/engine/src/flutter/shell/platform/embedder/tests/embedder_gl_unittests.cc b/engine/src/flutter/shell/platform/embedder/tests/embedder_gl_unittests.cc index 7b7f5a7e9b9d7..e9711f53d871f 100644 --- a/engine/src/flutter/shell/platform/embedder/tests/embedder_gl_unittests.cc +++ b/engine/src/flutter/shell/platform/embedder/tests/embedder_gl_unittests.cc @@ -9,8 +9,6 @@ #include #include -#include "vulkan/vulkan.h" - #include "GLES3/gl3.h" #include "flutter/flow/raster_cache.h" #include "flutter/fml/file.h" diff --git a/engine/src/flutter/sky/packages/sky_engine/LICENSE b/engine/src/flutter/sky/packages/sky_engine/LICENSE index 5860afe071a2e..394afa048283d 100644 --- a/engine/src/flutter/sky/packages/sky_engine/LICENSE +++ b/engine/src/flutter/sky/packages/sky_engine/LICENSE @@ -19,6 +19,34 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +=========================================================================================== +File: layers/external/inplace_function.h +File: layers/external/parallel_hashmap/phmap_utils.h + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- fuchsia_sdk @@ -1490,31 +1518,9 @@ limitations under the License. vulkan-validation-layers -Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 Valve Corporation -Copyright (c) 2015-2024 LunarG, Inc. -Copyright (c) 2015-2024 Google Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. --------------------------------------------------------------------------------- -vulkan-validation-layers - - -Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 Valve Corporation -Copyright (c) 2015-2024 LunarG, Inc. -Copyright (c) 2015-2024 Google Inc. -Copyright (c) 2023-2024 RasterGrid Kft. +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -1534,6 +1540,8 @@ vulkan-validation-layers Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. +Copyright (c) 2015-2024 Google Inc. +Copyright (c) 2023-2024 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -1553,8 +1561,7 @@ vulkan-validation-layers Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. -Copyright (c) 2015-2024 Google Inc. -Copyright (c) 2023-2024 RasterGrid Kft. +Copyright (c) 2015-2025 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -1575,6 +1582,7 @@ Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. Copyright (c) 2015-2025 Google Inc. +Copyright (c) 2015-2025 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -1595,7 +1603,7 @@ Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. Copyright (c) 2015-2025 Google Inc. -Copyright (c) 2015-2025 RasterGrid Kft. +Copyright (c) 2023-2025 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -1811,7 +1819,7 @@ THE SOFTWARE. vulkan-validation-layers -Copyright (c) 2020-2024 The Khronos Group Inc. +Copyright (c) 2020-2025 The Khronos Group Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -1845,8 +1853,9 @@ limitations under the License. vulkan-validation-layers -Copyright (c) 2021-2024 Valve Corporation -Copyright (c) 2021-2024 LunarG, Inc. +Copyright (c) 2021-2025 The Khronos Group Inc. +Copyright (c) 2021-2025 Valve Corporation +Copyright (c) 2021-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -1863,7 +1872,6 @@ limitations under the License. vulkan-validation-layers -Copyright (c) 2021-2025 The Khronos Group Inc. Copyright (c) 2021-2025 Valve Corporation Copyright (c) 2021-2025 LunarG, Inc. @@ -1882,8 +1890,8 @@ limitations under the License. vulkan-validation-layers -Copyright (c) 2023-2024 Valve Corporation -Copyright (c) 2023-2024 LunarG, Inc. +Copyright (c) 2023-2025 Google Inc. +Copyright (c) 2023-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -1900,8 +1908,8 @@ limitations under the License. vulkan-validation-layers -Copyright (c) 2023-2025 Google Inc. -Copyright (c) 2023-2025 LunarG, Inc. +Copyright (c) 2023-2025 The Khronos Group Inc. +Copyright (c) 2023-2025 Valve Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -1920,6 +1928,7 @@ vulkan-validation-layers Copyright (c) 2023-2025 The Khronos Group Inc. Copyright (c) 2023-2025 Valve Corporation +Copyright (c) 2023-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -1936,7 +1945,6 @@ limitations under the License. vulkan-validation-layers -Copyright (c) 2023-2025 The Khronos Group Inc. Copyright (c) 2023-2025 Valve Corporation Copyright (c) 2023-2025 LunarG, Inc. @@ -1976,6 +1984,24 @@ Copyright (c) 2025 The Khronos Group Inc. Copyright (c) 2025 Valve Corporation Copyright (c) 2025 LunarG, Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +vulkan-validation-layers + + +Copyright (c) 2025 Valve Corporation +Copyright (c) 2025 LunarG, Inc. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -7402,7 +7428,7 @@ POSSIBILITY OF SUCH DAMAGE. glslang Copyright (C) 2014-2015 LunarG, Inc. -Copyright (C) 2022-2024 Arm Limited. +Copyright (C) 2022-2025 Arm Limited. Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. All rights reserved. @@ -7481,7 +7507,7 @@ glslang Copyright (C) 2014-2016 LunarG, Inc. Copyright (C) 2015-2020 Google, Inc. -Copyright (C) 2017, 2022-2024 Arm Limited. +Copyright (C) 2017, 2022-2025 Arm Limited. Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. All rights reserved. @@ -7558,41 +7584,6 @@ Copyright (C) 2018-2020 Google, Inc. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - Neither the name of 3Dlabs Inc. Ltd. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------------- -glslang - -Copyright (C) 2015 LunarG, Inc. - -All rights reserved. - Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -8021,41 +8012,6 @@ Copyright (C) 2016 LunarG, Inc. All rights reserved. -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - - Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - Neither the name of Google Inc. nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------------- -glslang - -Copyright (C) 2016 LunarG, Inc. - -All rights reserved. - Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -9064,6 +9020,97 @@ glslang Copyright (C) 2024 The Khronos Group Inc. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + Neither the name of 3Dlabs Inc. Ltd. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- +glslang + +Copyright (C) 2025 Jan Kelemen + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + Neither the name of 3Dlabs Inc. Ltd. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- +glslang + +Copyright (C) 2025 NVIDIA Corporation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +-------------------------------------------------------------------------------- +glslang + +Copyright (C) 2025 The Khronos Group Inc. +All rights reserved. + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -10309,34 +10356,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. -------------------------------------------------------------------------------- -glslang - -Copyright (c) 2014-2024 The Khronos Group Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and/or associated documentation files (the "Materials"), -to deal in the Materials without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Materials, and to permit persons to whom the -Materials are furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Materials. - -MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS KHRONOS -STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS SPECIFICATIONS AND -HEADER INFORMATION ARE LOCATED AT https://www.khronos.org/registry/ - -THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS -IN THE MATERIALS. --------------------------------------------------------------------------------- -dart -skia +dart +skia Copyright (c) 2015 The Chromium Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be @@ -10469,42 +10490,6 @@ Copyright (c) 2015-2016, 2020-2025 The Khronos Group Inc. Copyright (c) 2015-2016, 2020-2025 Valve Corporation Copyright (c) 2015-2016, 2020-2025 LunarG, Inc. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. --------------------------------------------------------------------------------- -lunarg-vulkantools - -Copyright (c) 2015-2016, 2021 Valve Corporation -Copyright (c) 2015-2016, 2021 LunarG, Inc. -Copyright (c) 2015-2016, 2021 Google Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. --------------------------------------------------------------------------------- -vulkan-validation-layers - -Copyright (c) 2015-2017, 2019-2024 The Khronos Group Inc. -Copyright (c) 2015-2017, 2019-2024 Valve Corporation -Copyright (c) 2015-2017, 2019-2024 LunarG, Inc. - Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -10593,6 +10578,25 @@ Copyright (c) 2015-2019 The Khronos Group Inc. Copyright (c) 2015-2019 Valve Corporation Copyright (c) 2015-2019 LunarG, Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +vulkan-tools + +Copyright (c) 2015-2019 The Khronos Group Inc. +Copyright (c) 2015-2019 Valve Corporation +Copyright (c) 2015-2019 LunarG, Inc. +Copyright (c) 2025 The Fuchsia Authors. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -10734,11 +10738,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -lunarg-vulkantools +vulkan-validation-layers -Copyright (c) 2015-2023 Valve Corporation -Copyright (c) 2015-2023 LunarG, Inc. -Copyright (c) 2015-2016, 2019 Google Inc. +Copyright (c) 2015-2024 The Khronos Group Inc. +Copyright (C) 2025 Arm Limited. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -10752,11 +10755,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -lunarg-vulkantools +vulkan-validation-layers -Copyright (c) 2015-2023 Valve Corporation -Copyright (c) 2015-2023 LunarG, Inc. -Copyright (c) 2015-2017, 2019 Google Inc. +Copyright (c) 2015-2024 The Khronos Group Inc. +Copyright (c) 2015-2024 Valve Corporation +Copyright (c) 2015-2024 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -10775,6 +10778,7 @@ vulkan-validation-layers Copyright (c) 2015-2024 The Khronos Group Inc. Copyright (c) 2015-2024 Valve Corporation Copyright (c) 2015-2024 LunarG, Inc. +Copyright (C) 2015-2023 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -10793,7 +10797,7 @@ vulkan-validation-layers Copyright (c) 2015-2024 The Khronos Group Inc. Copyright (c) 2015-2024 Valve Corporation Copyright (c) 2015-2024 LunarG, Inc. -Copyright (C) 2015-2022 Google Inc. +Copyright (C) 2015-2024 Google Inc. Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); @@ -10808,32 +10812,21 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-validation-layers +vulkan-utility-libraries Copyright (c) 2015-2024 The Khronos Group Inc. Copyright (c) 2015-2024 Valve Corporation Copyright (c) 2015-2024 LunarG, Inc. -Copyright (C) 2015-2023 Google Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 +Copyright (c) 2015-2024 Google Inc. -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +SPDX-License-Identifier: Apache-2.0 -------------------------------------------------------------------------------- vulkan-validation-layers Copyright (c) 2015-2024 The Khronos Group Inc. Copyright (c) 2015-2024 Valve Corporation Copyright (c) 2015-2024 LunarG, Inc. -Copyright (C) 2015-2023 Google Inc. -Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. +Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. Modifications Copyright (C) 2022 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); @@ -10851,9 +10844,10 @@ limitations under the License. vulkan-validation-layers Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 Valve Corporation -Copyright (c) 2015-2024 LunarG, Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. Copyright (C) 2015-2024 Google Inc. +Modifications Copyright (C) 2020-2024 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -10867,13 +10861,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-validation-layers +vulkan-tools -Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 Valve Corporation -Copyright (c) 2015-2024 LunarG, Inc. -Copyright (C) 2015-2024 Google Inc. -Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. +Copyright (c) 2015-2025 The Khronos Group Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -10889,12 +10879,9 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 Valve Corporation -Copyright (c) 2015-2024 LunarG, Inc. -Copyright (C) 2015-2024 Google Inc. -Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. -Modifications Copyright (C) 2022 RasterGrid Kft. +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -10910,11 +10897,10 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 Valve Corporation -Copyright (c) 2015-2024 LunarG, Inc. -Copyright (C) 2015-2024 Google Inc. -Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. +Copyright (C) 2015-2023 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -10930,10 +10916,10 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 Valve Corporation -Copyright (c) 2015-2024 LunarG, Inc. -Copyright (C) 2015-2024 Google Inc. +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. +Copyright (C) 2015-2023 Google Inc. Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. Modifications Copyright (C) 2022 RasterGrid Kft. @@ -10951,11 +10937,10 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 Valve Corporation -Copyright (c) 2015-2024 LunarG, Inc. +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. Copyright (C) 2015-2024 Google Inc. -Modifications Copyright (C) 2020-2024 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -10969,22 +10954,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-utility-libraries - -Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 Valve Corporation -Copyright (c) 2015-2024 LunarG, Inc. -Copyright (c) 2015-2024 Google Inc. - -SPDX-License-Identifier: Apache-2.0 --------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 Valve Corporation -Copyright (c) 2015-2024 LunarG, Inc. -Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. -Modifications Copyright (C) 2022 RasterGrid Kft. +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. +Copyright (C) 2015-2024 Google Inc. +Copyright (c) 2025 Arm Limited. +Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11000,11 +10977,11 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2015-2024 The Khronos Group Inc. -Copyright (c) 2015-2024 Valve Corporation -Copyright (c) 2015-2024 LunarG, Inc. -Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. -Modifications Copyright (C) 2022 RasterGrid Kft. +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. +Copyright (C) 2015-2024 Google Inc. +Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11018,9 +10995,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-tools +vulkan-validation-layers Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. +Copyright (C) 2015-2024 Google Inc. +Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. +Modifications Copyright (C) 2022 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11039,6 +11021,8 @@ vulkan-validation-layers Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. +Copyright (C) 2015-2024 Google Inc. +Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11057,7 +11041,8 @@ vulkan-validation-layers Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. -Copyright (C) 2015-2023 Google Inc. +Copyright (C) 2015-2024 Google Inc. +Modifications Copyright (C) 2020-2024 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11076,7 +11061,7 @@ vulkan-validation-layers Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. -Copyright (C) 2015-2024 Google Inc. +Copyright (C) 2015-2025 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11095,9 +11080,8 @@ vulkan-validation-layers Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. -Copyright (C) 2015-2024 Google Inc. -Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. -Modifications Copyright (C) 2022 RasterGrid Kft. +Copyright (C) 2015-2025 Google Inc. +Copyright (C) 2025 Arm Limited. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11116,8 +11100,10 @@ vulkan-validation-layers Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. -Copyright (C) 2015-2024 Google Inc. -Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. +Copyright (C) 2015-2025 Google Inc. +Copyright (C) 2025 Arm Limited. +Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. +Modifications Copyright (C) 2022 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11136,8 +11122,10 @@ vulkan-validation-layers Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. -Copyright (C) 2015-2024 Google Inc. -Modifications Copyright (C) 2020-2024 Advanced Micro Devices, Inc. All rights reserved. +Copyright (C) 2015-2025 Google Inc. +Copyright (C) 2025 Arm Limited. +Modifications Copyright (C) 2020-2025 Advanced Micro Devices, Inc. All rights reserved. +Modifications Copyright (C) 2022-2025 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11157,6 +11145,7 @@ Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. Copyright (C) 2015-2025 Google Inc. +Copyright (c) 2025 Arm Limited. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11176,6 +11165,7 @@ Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. Copyright (C) 2015-2025 Google Inc. +Copyright (c) 2025 Arm Limited. Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); @@ -11196,6 +11186,7 @@ Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. Copyright (C) 2015-2025 Google Inc. +Copyright (c) 2025 Arm Limited. Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. Modifications Copyright (C) 2022 RasterGrid Kft. @@ -11217,6 +11208,7 @@ Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. Copyright (C) 2015-2025 Google Inc. +Copyright (c) 2025 Arm Limited. Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); @@ -11237,8 +11229,8 @@ Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. Copyright (C) 2015-2025 Google Inc. -Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. -Modifications Copyright (C) 2022 RasterGrid Kft. +Copyright (c) 2025 Arm Limited. +Modifications Copyright (C) 2020-2024 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11258,7 +11250,7 @@ Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. Copyright (C) 2015-2025 Google Inc. -Modifications Copyright (C) 2020-2024 Advanced Micro Devices, Inc. All rights reserved. +Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11278,8 +11270,8 @@ Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. Copyright (C) 2015-2025 Google Inc. -Modifications Copyright (C) 2020-2024 Advanced Micro Devices, Inc. All rights reserved. -Modifications Copyright (C) 2022-2024 RasterGrid Kft. +Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. +Modifications Copyright (C) 2022 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11299,8 +11291,7 @@ Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. Copyright (C) 2015-2025 Google Inc. -Modifications Copyright (C) 2020-2025 Advanced Micro Devices, Inc. All rights reserved. -Modifications Copyright (C) 2022-2025 RasterGrid Kft. +Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11319,7 +11310,9 @@ vulkan-validation-layers Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. -Copyright (c) 2015-2024 Google Inc. +Copyright (C) 2015-2025 Google Inc. +Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. +Copyright (c) 2025 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11338,7 +11331,8 @@ vulkan-validation-layers Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. -Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. +Copyright (C) 2015-2025 Google Inc. +Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. Modifications Copyright (C) 2022 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); @@ -11358,8 +11352,9 @@ vulkan-validation-layers Copyright (c) 2015-2025 The Khronos Group Inc. Copyright (c) 2015-2025 Valve Corporation Copyright (c) 2015-2025 LunarG, Inc. -Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. -Modifications Copyright (C) 2022 RasterGrid Kft. +Copyright (C) 2015-2025 Google Inc. +Modifications Copyright (C) 2020-2024 Advanced Micro Devices, Inc. All rights reserved. +Modifications Copyright (C) 2022-2024 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11373,10 +11368,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -lunarg-vulkantools +vulkan-validation-layers -Copyright (c) 2016 Advanced Micro Devices, Inc. All rights reserved. -Copyright (C) 2015-2021 LunarG, Inc. +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. +Copyright (c) 2015-2024 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -11390,15 +11387,99 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -spirv-tools - -Copyright (c) 2016 Google Inc. +vulkan-utility-libraries -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. +Copyright (c) 2015-2025 Google Inc. - http://www.apache.org/licenses/LICENSE-2.0 +SPDX-License-Identifier: Apache-2.0 +-------------------------------------------------------------------------------- +vulkan-validation-layers + +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. +Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. +Modifications Copyright (C) 2022 RasterGrid Kft. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +vulkan-validation-layers + +Copyright (c) 2015-2025 The Khronos Group Inc. +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. +Modifications Copyright (C) 2020-2022 Advanced Micro Devices, Inc. All rights reserved. +Modifications Copyright (C) 2022 RasterGrid Kft. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +lunarg-vulkantools + +Copyright (c) 2015-2025 Valve Corporation +Copyright (c) 2015-2025 LunarG, Inc. +Copyright (c) 2015-2017, 2019, 2021 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +lunarg-vulkantools + +Copyright (c) 2016 Advanced Micro Devices, Inc. All rights reserved. +Copyright (C) 2015-2021 LunarG, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +spirv-tools + +Copyright (c) 2016 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -11456,6 +11537,23 @@ freely, subject to the following restrictions: -------------------------------------------------------------------------------- spirv-tools +Copyright (c) 2016 Google Inc. +Copyright (c) 2025 Arm Ltd. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +spirv-tools + Copyright (c) 2016 Google Inc. Modifications Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved. @@ -11523,23 +11621,6 @@ freely, subject to the following restrictions: 3. This notice may not be removed or altered from any source distribution. -------------------------------------------------------------------------------- -lunarg-vulkantools - -Copyright (c) 2016-2021 Valve Corporation -Copyright (c) 2016-2021 LunarG, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. --------------------------------------------------------------------------------- spirv-tools Copyright (c) 2017 Google Inc. @@ -12126,6 +12207,25 @@ Copyright (c) 2018-2025 The Khronos Group Inc. Copyright (c) 2018-2025 Valve Corporation Copyright (c) 2018-2025 LunarG, Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +vulkan-validation-layers + +Copyright (c) 2018-2025 The Khronos Group Inc. +Copyright (c) 2018-2025 Valve Corporation +Copyright (c) 2018-2025 LunarG, Inc. +Copyright (c) 2025 Arm Limited. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -12284,9 +12384,9 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2019, 2021, 2023-2024 The Khronos Group Inc. -Copyright (c) 2019, 2021, 2023-2024 Valve Corporation -Copyright (c) 2019, 2021, 2023-2024 LunarG, Inc. +Copyright (c) 2019, 2021, 2023-2025 The Khronos Group Inc. +Copyright (c) 2019, 2021, 2023-2025 Valve Corporation +Copyright (c) 2019, 2021, 2023-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12354,16 +12454,18 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -lunarg-vulkantools +vulkan-tools -Copyright (c) 2019-2021 Valve Corporation -Copyright (c) 2019-2021 LunarG, Inc. +Copyright (c) 2019-2022 The Khronos Group Inc. +Copyright (c) 2019-2022 Valve Corporation +Copyright (c) 2019-2022 LunarG, Inc. +Copyright (c) 2023-2024 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -12371,12 +12473,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-tools +vulkan-validation-layers -Copyright (c) 2019-2022 The Khronos Group Inc. -Copyright (c) 2019-2022 Valve Corporation -Copyright (c) 2019-2022 LunarG, Inc. -Copyright (c) 2023-2024 RasterGrid Kft. +Copyright (c) 2019-2024 The Khronos Group Inc. +Copyright (c) 2019-2024 Valve Corporation +Copyright (c) 2019-2024 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12390,11 +12491,20 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-validation-layers +vulkan-utility-libraries Copyright (c) 2019-2024 The Khronos Group Inc. Copyright (c) 2019-2024 Valve Corporation Copyright (c) 2019-2024 LunarG, Inc. +Copyright (C) 2019-2024 Google Inc. + +SPDX-License-Identifier: Apache-2.0 +-------------------------------------------------------------------------------- +vulkan-validation-layers + +Copyright (c) 2019-2025 The Khronos Group Inc. +Copyright (c) 2019-2025 Valve Corporation +Copyright (c) 2019-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12410,10 +12520,10 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2019-2024 The Khronos Group Inc. -Copyright (c) 2019-2024 Valve Corporation -Copyright (c) 2019-2024 LunarG, Inc. -Copyright (C) 2019-2024 Google Inc. +Copyright (c) 2019-2025 The Khronos Group Inc. +Copyright (c) 2019-2025 Valve Corporation +Copyright (c) 2019-2025 LunarG, Inc. +Copyright (C) 2019-2025 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12427,19 +12537,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-utility-libraries - -Copyright (c) 2019-2024 The Khronos Group Inc. -Copyright (c) 2019-2024 Valve Corporation -Copyright (c) 2019-2024 LunarG, Inc. -Copyright (C) 2019-2024 Google Inc. - -SPDX-License-Identifier: Apache-2.0 --------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2019-2024 Valve Corporation -Copyright (c) 2019-2024 LunarG, Inc. +Copyright (c) 2019-2025 The Khronos Group Inc. +Copyright (c) 2019-2025 Valve Corporation +Copyright (c) 2019-2025 LunarG, Inc. +Copyright (C) 2025 Arm Limited. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12458,6 +12561,7 @@ vulkan-validation-layers Copyright (c) 2019-2025 The Khronos Group Inc. Copyright (c) 2019-2025 Valve Corporation Copyright (c) 2019-2025 LunarG, Inc. +Modifications Copyright (C) 2022 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -12790,24 +12894,6 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2020-2024 The Khronos Group Inc. -Copyright (c) 2020-2024 Valve Corporation -Copyright (c) 2020-2024 LunarG, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. --------------------------------------------------------------------------------- -vulkan-validation-layers - Copyright (c) 2020-2025 The Khronos Group Inc. Copyright (c) 2020-2025 Valve Corporation Copyright (c) 2020-2025 LunarG, Inc. @@ -13064,9 +13150,9 @@ BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2021-2024 The Khronos Group Inc. -Copyright (c) 2021-2024 Valve Corporation -Copyright (c) 2021-2024 LunarG, Inc. +Copyright (c) 2021-2025 The Khronos Group Inc. +Copyright (c) 2021-2025 Valve Corporation +Copyright (c) 2021-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13082,9 +13168,9 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2021-2024 The Khronos Group Inc. -Copyright (c) 2021-2024 Valve Corporation -Copyright (c) 2021-2024 LunarG, Inc. +Copyright (c) 2021-2025 The Khronos Group Inc. +Copyright (c) 2021-2025 Valve Corporation +Copyright (c) 2021-2025 LunarG, Inc. Copyright (C) 2021-2022 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); @@ -13101,10 +13187,10 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2021-2024 The Khronos Group Inc. -Copyright (c) 2021-2024 Valve Corporation -Copyright (c) 2021-2024 LunarG, Inc. -Copyright (C) 2021-2024 Google Inc. +Copyright (c) 2021-2025 The Khronos Group Inc. +Copyright (c) 2021-2025 Valve Corporation +Copyright (c) 2021-2025 LunarG, Inc. +Copyright (C) 2021-2025 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13121,6 +13207,10 @@ limitations under the License. vulkan-validation-layers Copyright (c) 2021-2025 The Khronos Group Inc. +Copyright (c) 2021-2025 Valve Corporation +Copyright (c) 2021-2025 LunarG, Inc. +Copyright (C) 2021-2025 Google Inc. +Modifications Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13139,6 +13229,7 @@ vulkan-validation-layers Copyright (c) 2021-2025 The Khronos Group Inc. Copyright (c) 2021-2025 Valve Corporation Copyright (c) 2021-2025 LunarG, Inc. +Copyright (c) 2025 Arm Limited. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13155,10 +13246,7 @@ limitations under the License. vulkan-validation-layers Copyright (c) 2021-2025 The Khronos Group Inc. -Copyright (c) 2021-2025 Valve Corporation -Copyright (c) 2021-2025 LunarG, Inc. -Copyright (C) 2021-2025 Google Inc. -Modifications Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved. +Copyright (c) 2025 Arm Limited. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13172,9 +13260,129 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- +spirv-tools + +Copyright (c) 2022 Advanced Micro Devices, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +glfw + +Copyright (c) 2022 Camilla Löwy + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would + be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not + be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. +-------------------------------------------------------------------------------- +spirv-tools + +Copyright (c) 2022 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +spirv-tools + +Copyright (c) 2022 Google LLC. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +skia + +Copyright (c) 2022 Google LLC. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +-------------------------------------------------------------------------------- +spirv-tools + +Copyright (c) 2022 The Khronos Group Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +spirv-tools + +Copyright (c) 2022 The Khronos Group Inc. +Copyright (c) 2022 LunarG Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +test_shaders + +Copyright (c) 2022 by Selman Ay (https://codepen.io/selmanays/pen/yLVmEqY) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +-------------------------------------------------------------------------------- glslang -Copyright (c) 2022 ARM Limited +Copyright (c) 2022, 2025 ARM Limited Permission is hereby granted, free of charge, to any person obtaining a copy of this software and/or associated documentation files (the "Materials"), @@ -13198,9 +13406,15 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. -------------------------------------------------------------------------------- -spirv-tools +dart -Copyright (c) 2022 Advanced Micro Devices, Inc. +Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. Use of this source code is governed by a +BSD-style license that can be found in the LICENSE file. +-------------------------------------------------------------------------------- +vulkan-validation-layers + +Copyright (c) 2022-2023 The Khronos Group Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13214,32 +13428,63 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -glfw +vulkan-validation-layers -Copyright (c) 2022 Camilla Löwy +Copyright (c) 2022-2024 The Khronos Group Inc. +Copyright (c) 2022-2024 RasterGrid Kft. +Modifications Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved. -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: + http://www.apache.org/licenses/LICENSE-2.0 -1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would - be appreciated but is not required. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +vulkan-validation-layers -2. Altered source versions must be plainly marked as such, and must not - be misrepresented as being the original software. +Copyright (c) 2022-2025 The Khronos Group Inc. +Copyright (c) 2022-2025 Valve Corporation +Copyright (c) 2022-2025 LunarG, Inc. +Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. -3. This notice may not be removed or altered from any source - distribution. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +lunarg-vulkantools + +Copyright (c) 2022-2025 Valve Corporation +Copyright (c) 2022-2025 LunarG, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. -------------------------------------------------------------------------------- spirv-tools -Copyright (c) 2022 Google LLC +Copyright (c) 2023 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13255,7 +13500,7 @@ limitations under the License. -------------------------------------------------------------------------------- spirv-tools -Copyright (c) 2022 Google LLC. +Copyright (c) 2023 Google LLC. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13269,15 +13514,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -skia - -Copyright (c) 2022 Google LLC. All rights reserved. -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. --------------------------------------------------------------------------------- spirv-tools -Copyright (c) 2022 The Khronos Group Inc. +Copyright (c) 2023 LunarG Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13291,10 +13530,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -spirv-tools +vulkan-validation-layers -Copyright (c) 2022 The Khronos Group Inc. -Copyright (c) 2022 LunarG Inc. +Copyright (c) 2023 The Khronos Group Inc. +Copyright (c) 2023 Valve Corporation +Copyright (c) 2023 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13308,25 +13548,22 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -test_shaders - -Copyright (c) 2022 by Selman Ay (https://codepen.io/selmanays/pen/yLVmEqY) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +dart -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. Use of this source code is governed by a +BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- dart -Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file +Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file for details. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2022-2023 The Khronos Group Inc. +Copyright (c) 2023-2024 LunarG, Inc. +Copyright (c) 2023-2024 Valve Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13342,9 +13579,8 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2022-2024 The Khronos Group Inc. -Copyright (c) 2022-2024 RasterGrid Kft. -Modifications Copyright (C) 2024 Advanced Micro Devices, Inc. All rights reserved. +Copyright (c) 2023-2024 Nintendo +Copyright (c) 2023-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13358,12 +13594,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-validation-layers +spirv-tools -Copyright (c) 2022-2024 The Khronos Group Inc. -Copyright (c) 2022-2024 Valve Corporation -Copyright (c) 2022-2024 LunarG, Inc. -Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved. +Copyright (c) 2023-2025 Arm Ltd. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13377,16 +13610,17 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -lunarg-vulkantools +vulkan-validation-layers -Copyright (c) 2022-2024 Valve Corporation -Copyright (c) 2022-2024 LunarG, Inc. +Copyright (c) 2023-2025 LunarG, Inc. +Copyright (c) 2023-2025 Valve Corporation +Copyright (c) 2025 Arm Limited. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -13394,9 +13628,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -spirv-tools +vulkan-validation-layers -Copyright (c) 2023 Google Inc. +Copyright (c) 2023-2025 Nintendo +Copyright (c) 2023-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13410,9 +13645,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -spirv-tools +vulkan-validation-layers -Copyright (c) 2023 Google LLC. +Copyright (c) 2023-2025 The Khronos Group Inc. +Copyright (c) 2023-2025 Valve Corporation +Copyright (c) 2023-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13426,9 +13663,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -spirv-tools +vulkan-validation-layers -Copyright (c) 2023 LunarG Inc. +Copyright (c) 2023-2025 The Khronos Group Inc. +Copyright (c) 2023-2025 Valve Corporation +Copyright (c) 2023-2025 LunarG, Inc. +Copyright (c) 2025 Arm Limited. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13444,9 +13684,8 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2023 The Khronos Group Inc. -Copyright (c) 2023 Valve Corporation -Copyright (c) 2023 LunarG, Inc. +Copyright (c) 2023-2025 Valve Corporation +Copyright (c) 2023-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13460,10 +13699,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-validation-layers +spirv-tools -Copyright (c) 2023 Valve Corporation -Copyright (c) 2023 LunarG, Inc. +Copyright (c) 2024 Epic Games, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13477,22 +13715,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -dart - -Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file -for details. All rights reserved. Use of this source code is governed by a -BSD-style license that can be found in the LICENSE file. --------------------------------------------------------------------------------- -dart - -Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file -for details. All rights reserved. Use of this source code is governed by a -BSD-style license that can be found in the LICENSE file. --------------------------------------------------------------------------------- -vulkan-validation-layers +spirv-tools -Copyright (c) 2023-2024 LunarG, Inc. -Copyright (c) 2023-2024 Valve Corporation +Copyright (c) 2024 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13506,10 +13731,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-validation-layers +spirv-tools -Copyright (c) 2023-2024 Nintendo -Copyright (c) 2023-2024 LunarG, Inc. +Copyright (c) 2024 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13523,10 +13747,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-validation-layers +spirv-tools -Copyright (c) 2023-2024 Nintendo -Copyright (c) 2023-2025 LunarG, Inc. +Copyright (c) 2024 NVIDIA Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13542,9 +13765,9 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2023-2024 The Khronos Group Inc. -Copyright (c) 2023-2024 Valve Corporation -Copyright (c) 2023-2024 LunarG, Inc. +Copyright (c) 2024 The Khronos Group Inc. +Copyright (c) 2024 LunarG, Inc. +Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13558,10 +13781,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- +vulkan-tools vulkan-validation-layers -Copyright (c) 2023-2024 Valve Corporation -Copyright (c) 2023-2024 LunarG, Inc. +Copyright (c) 2024 The Khronos Group Inc. +Copyright (c) 2024 Valve Corporation +Copyright (c) 2024 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13577,8 +13802,9 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2023-2025 LunarG, Inc. -Copyright (c) 2023-2025 Valve Corporation +Copyright (c) 2024 The Khronos Group Inc. +Copyright (c) 2025 Valve Corporation +Copyright (c) 2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13594,8 +13820,8 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2023-2025 Nintendo -Copyright (c) 2023-2025 LunarG, Inc. +Copyright (c) 2024 Valve Corporation +Copyright (c) 2024 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13609,11 +13835,21 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- +dart + +Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. Use of this source code is governed by a +BSD-style license that can be found in the LICENSE file. +-------------------------------------------------------------------------------- +dart + +Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. Use of this source code is governed by a +BSD-style license that can be found in the LICENSE file. +-------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2023-2025 The Khronos Group Inc. -Copyright (c) 2023-2025 Valve Corporation -Copyright (c) 2023-2025 LunarG, Inc. +Copyright (c) 2024-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13627,9 +13863,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -spirv-tools +vulkan-validation-layers -Copyright (c) 2024 Epic Games, Inc. +Copyright (c) 2024-2025 The Khronos Group Inc. +Copyright (c) 2024-2025 Valve Corporation +Copyright (c) 2024-2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13643,9 +13881,12 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -spirv-tools +vulkan-validation-layers -Copyright (c) 2024 Google Inc. +Copyright (c) 2024-2025 The Khronos Group Inc. +Copyright (c) 2024-2025 Valve Corporation +Copyright (c) 2024-2025 LunarG, Inc. +Copyright (c) 2025 Arm Limited. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13661,7 +13902,7 @@ limitations under the License. -------------------------------------------------------------------------------- spirv-tools -Copyright (c) 2024 Google LLC +Copyright (c) 2025 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13675,9 +13916,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-validation-layers +spirv-tools -Copyright (c) 2024 LunarG, Inc. +Copyright (c) 2025 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13693,7 +13934,7 @@ limitations under the License. -------------------------------------------------------------------------------- spirv-tools -Copyright (c) 2024 NVIDIA Corporation +Copyright (c) 2025 LunarG Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13709,9 +13950,7 @@ limitations under the License. -------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2024 The Khronos Group Inc. -Copyright (c) 2024 LunarG, Inc. -Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. +Copyright (c) 2025 LunarG, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13726,11 +13965,8 @@ See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- vulkan-tools -vulkan-validation-layers -Copyright (c) 2024 The Khronos Group Inc. -Copyright (c) 2024 Valve Corporation -Copyright (c) 2024 LunarG, Inc. +Copyright (c) 2025 The Fuchsia Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13744,10 +13980,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-validation-layers +glslang -Copyright (c) 2024 Valve Corporation -Copyright (c) 2024 LunarG, Inc. +Copyright (c) 2025 The Khronos Group Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13761,21 +13996,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -dart - -Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file -for details. All rights reserved. Use of this source code is governed by a -BSD-style license that can be found in the LICENSE file. --------------------------------------------------------------------------------- -dart - -Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file -for details. All rights reserved. Use of this source code is governed by a -BSD-style license that can be found in the LICENSE file. --------------------------------------------------------------------------------- vulkan-validation-layers -Copyright (c) 2024-2025 LunarG, Inc. +Copyright (c) 2025 The Khronos Group Inc. +Copyright (C) 2025 Arm Limited. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13789,11 +14013,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- -vulkan-validation-layers +spirv-tools -Copyright (c) 2024-2025 The Khronos Group Inc. -Copyright (c) 2024-2025 Valve Corporation -Copyright (c) 2024-2025 LunarG, Inc. +Copyright (c) 2025 The Khronos Group Inc. +Copyright (c) 2025 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13807,12 +14030,29 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- +vulkan-tools vulkan-validation-layers Copyright (c) 2025 The Khronos Group Inc. Copyright (c) 2025 Valve Corporation Copyright (c) 2025 LunarG, Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- +spirv-tools + +Copyright (c) 2025 The Khronos Group Inc. + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -15207,8 +15447,8 @@ limitations under the License. vulkan-validation-layers Copyright 2017 The Glslang Authors. All rights reserved. -Copyright (c) 2018-2024 Valve Corporation -Copyright (c) 2018-2024 LunarG, Inc. +Copyright (c) 2018-2025 Valve Corporation +Copyright (c) 2018-2025 LunarG, Inc. Copyright (c) 2023-2023 RasterGrid Kft. Licensed under the Apache License, Version 2.0 (the "License"); @@ -16661,6 +16901,13 @@ Copyright 2023-2024 The Khronos Group Inc. Copyright 2023-2024 Valve Corporation Copyright 2023-2024 LunarG, Inc. +SPDX-License-Identifier: Apache-2.0 +-------------------------------------------------------------------------------- +lunarg-vulkantools +vulkan-headers + +Copyright 2023-2025 The Khronos Group Inc. + SPDX-License-Identifier: Apache-2.0 -------------------------------------------------------------------------------- vulkan-utility-libraries @@ -16772,6 +17019,22 @@ Copyright 2025 Google Inc. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- +spirv-tools + +Copyright 2025 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- dawn skia @@ -16826,6 +17089,22 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- +spirv-tools + +Copyright 2025 The Khronos Group Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +-------------------------------------------------------------------------------- angle Copyright The ANGLE Project Authors. All rights reserved. diff --git a/engine/src/flutter/vulkan/procs/vulkan_interface.h b/engine/src/flutter/vulkan/procs/vulkan_interface.h index 6343af80885b6..15f9271d23013 100644 --- a/engine/src/flutter/vulkan/procs/vulkan_interface.h +++ b/engine/src/flutter/vulkan/procs/vulkan_interface.h @@ -27,6 +27,13 @@ #include +// The Vulkan headers may bring in X11 headers which define some macros that +// conflict with other code. Undefine these macros after including Vulkan. +#undef Bool +#undef None +#undef Status +#undef Success + #define VK_CALL_LOG_ERROR(expression) VK_CALL_LOG(expression, ERROR) #define VK_CALL_LOG_FATAL(expression) VK_CALL_LOG(expression, FATAL) From 4fc08edcbba4bc2c2284a0cd375788d1f519eaa5 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Mon, 6 Oct 2025 11:13:42 -0400 Subject: [PATCH 078/204] Roll Skia from bc7cf194f4ee to d09786dfb854 (1 revision) (#176577) https://skia.googlesource.com/skia.git/+log/bc7cf194f4ee..d09786dfb854 2025-10-06 fmalita@google.com Conics support for SkPathBuilder::incReserve If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC egdaniel@google.com,jimgraham@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 015f7117fbdd7..bcab87d145c63 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'bc7cf194f4ee78095014b55b1370d902119dad60', + 'skia_revision': 'd09786dfb85445bfca55df40ab3b9c1af9c25161', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From c2e9e02b89994c46e3708b84a8d7aef09ad8719f Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Mon, 6 Oct 2025 11:27:56 -0400 Subject: [PATCH 079/204] Roll Dart SDK from 898380a41c90 to 6b0193498f09 (2 revisions) (#176576) https://dart.googlesource.com/sdk.git/+log/898380a41c90..6b0193498f09 2025-10-06 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-287.0.dev 2025-10-06 dart-internal-merge@dart-ci-internal.iam.gserviceaccount.com Version 3.10.0-286.0.dev If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/dart-sdk-flutter Please CC dart-vm-team@google.com,jimgraham@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index bcab87d145c63..75c64407ace7c 100644 --- a/DEPS +++ b/DEPS @@ -56,7 +56,7 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '898380a41c90a54dad599c582c9352490cd07b9d', + 'dart_revision': '6b0193498f09e59e25d75233d0558143caa61174', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py From d903fa2d9d1149cf90f69a803f040bf40e3b1be9 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Mon, 6 Oct 2025 11:52:28 -0400 Subject: [PATCH 080/204] Roll Packages from e401aeb3aae4 to d3ef88b5feb8 (4 revisions) (#176582) https://github.com/flutter/packages/compare/e401aeb3aae4...d3ef88b5feb8 2025-10-06 engine-flutter-autoroll@skia.org Roll Flutter from 5c0c9e9e9ad2 to 908012d58baa (18 revisions) (flutter/packages#10175) 2025-10-03 engine-flutter-autoroll@skia.org Manual roll Flutter from 65aca3661b8f to 5c0c9e9e9ad2 (16 revisions) (flutter/packages#10170) 2025-10-03 stuartmorgan@google.com Instruct agents to create a repo root alias (flutter/packages#10165) 2025-10-03 jessiewong401@gmail.com [Gradle 9] Fixed Gradle 9 Deprecations in Packages (flutter/packages#10016) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC flutter-ecosystem@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- bin/internal/flutter_packages.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/flutter_packages.version b/bin/internal/flutter_packages.version index 57fe12eb8be38..0005e047c24d9 100644 --- a/bin/internal/flutter_packages.version +++ b/bin/internal/flutter_packages.version @@ -1 +1 @@ -e401aeb3aae46b82ce26096d960900748a13131a +d3ef88b5feb85fe3092921e01edf72f845058063 From b5aef9c366b50682cea58a99e3f643a8a07055b4 Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Mon, 6 Oct 2025 18:39:19 +0200 Subject: [PATCH 081/204] Fix NavigatorBar lacks visual feedback (#175182) ## Description This PR fixes NavigationBar lacking visual feedback on the active destination indicator. This is a reland of https://github.com/flutter/flutter/pull/164484 which was reverted in https://github.com/flutter/flutter/pull/169497. After investigation, I narrowed down the regression introduced in https://github.com/flutter/flutter/pull/164484 to the usage of a RepaintBoundary. I added one test to verify that the regression which led to the revert of https://github.com/flutter/flutter/pull/164484 is no more reproducible. ### Before: The navigation indicator does not change color when hovered or focused: https://github.com/user-attachments/assets/a1e67dee-4a38-4711-ba90-bdcd9bed3226 ### After: The navigation indicator color changes (slightly darker): https://github.com/user-attachments/assets/1b1cc335-2cf4-4c41-9c53-696537707c72 ## Related Issue Fixes [NavigationBar lacks visual feedback when focused or hovered](https://github.com/flutter/flutter/issues/163871) ## Tests - Updates several helper functions which are used by several tests. - Updates several test: adding an Ink widget changes the coordinates used in several tests because these coordinates are now relative to the Ink offset. - Add one test to check that active destination moves to the correct destination (that was not the case after https://github.com/flutter/flutter/pull/164484). This new test is a golden test. --- .../lib/src/material/navigation_bar.dart | 14 ++--- .../test/material/navigation_bar_test.dart | 45 ++++++++++++++- .../material/navigation_bar_theme_test.dart | 4 +- .../test/material/navigation_drawer_test.dart | 9 +-- .../navigation_drawer_theme_test.dart | 4 +- .../test/material/navigation_rail_test.dart | 57 ++++++------------- .../material/navigation_rail_theme_test.dart | 4 +- 7 files changed, 77 insertions(+), 60 deletions(-) diff --git a/packages/flutter/lib/src/material/navigation_bar.dart b/packages/flutter/lib/src/material/navigation_bar.dart index 213b0888cface..6499dc6a62a44 100644 --- a/packages/flutter/lib/src/material/navigation_bar.dart +++ b/packages/flutter/lib/src/material/navigation_bar.dart @@ -16,6 +16,7 @@ import 'package:flutter/widgets.dart'; import 'color_scheme.dart'; import 'colors.dart'; import 'elevation_overlay.dart'; +import 'ink_decoration.dart'; import 'ink_well.dart'; import 'material.dart'; import 'material_localizations.dart'; @@ -866,7 +867,7 @@ class NavigationIndicator extends StatelessWidget { builder: (BuildContext context, Animation fadeAnimation) { return FadeTransition( opacity: fadeAnimation, - child: Container( + child: Ink( width: width, height: height, decoration: ShapeDecoration( @@ -916,8 +917,6 @@ class _NavigationBarDestinationLayout extends StatelessWidget { /// See [NavigationDestination.label]. final Widget label; - static final Key _labelKey = UniqueKey(); - @override Widget build(BuildContext context) { return _DestinationLayoutAnimationBuilder( @@ -927,15 +926,12 @@ class _NavigationBarDestinationLayout extends StatelessWidget { children: [ LayoutId( id: _NavigationDestinationLayoutDelegate.iconId, - child: RepaintBoundary(key: iconKey, child: icon), + // The key is used by the _IndicatorInkWell to query the icon position. + child: KeyedSubtree(key: iconKey, child: icon), ), LayoutId( id: _NavigationDestinationLayoutDelegate.labelId, - child: FadeTransition( - alwaysIncludeSemantics: true, - opacity: animation, - child: RepaintBoundary(key: _labelKey, child: label), - ), + child: FadeTransition(alwaysIncludeSemantics: true, opacity: animation, child: label), ), ], ); diff --git a/packages/flutter/test/material/navigation_bar_test.dart b/packages/flutter/test/material/navigation_bar_test.dart index 67d1e2f307da7..dcbbdf6d41a82 100644 --- a/packages/flutter/test/material/navigation_bar_test.dart +++ b/packages/flutter/test/material/navigation_bar_test.dart @@ -953,6 +953,47 @@ void main() { ); }); + // Regression test for https://github.com/flutter/flutter/issues/169249. + testWidgets('Material3 - Navigation indicator moves to selected item', ( + WidgetTester tester, + ) async { + final ThemeData theme = ThemeData(); + int index = 0; + + Widget buildNavigationBar({Color? indicatorColor, ShapeBorder? indicatorShape}) { + return MaterialApp( + theme: theme, + home: Scaffold( + bottomNavigationBar: RepaintBoundary( + child: NavigationBar( + indicatorColor: indicatorColor, + indicatorShape: indicatorShape, + selectedIndex: index, + destinations: const [ + NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'), + NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'), + ], + onDestinationSelected: (int i) {}, + ), + ), + ), + ); + } + + await tester.pumpWidget(buildNavigationBar()); + + // Move the selection to the second destination. + index = 1; + await tester.pumpWidget(buildNavigationBar()); + await tester.pumpAndSettle(); + + // The navigation indicator should be on the second item. + await expectLater( + find.byType(NavigationBar), + matchesGoldenFile('m3.navigation_bar.indicator.ink.position.png'), + ); + }); + testWidgets('Navigation indicator scale transform', (WidgetTester tester) async { int selectedIndex = 0; @@ -1718,8 +1759,8 @@ Material _getMaterial(WidgetTester tester) { ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) { return tester - .firstWidget( - find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)), + .firstWidget( + find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)), ) .decoration as ShapeDecoration?; diff --git a/packages/flutter/test/material/navigation_bar_theme_test.dart b/packages/flutter/test/material/navigation_bar_theme_test.dart index e932e7d0420d3..8506b13058987 100644 --- a/packages/flutter/test/material/navigation_bar_theme_test.dart +++ b/packages/flutter/test/material/navigation_bar_theme_test.dart @@ -365,8 +365,8 @@ Material _barMaterial(WidgetTester tester) { ShapeDecoration? _indicator(WidgetTester tester) { return tester - .firstWidget( - find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)), + .firstWidget( + find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)), ) .decoration as ShapeDecoration?; diff --git a/packages/flutter/test/material/navigation_drawer_test.dart b/packages/flutter/test/material/navigation_drawer_test.dart index 96ca683a7467e..585c87f5dd831 100644 --- a/packages/flutter/test/material/navigation_drawer_test.dart +++ b/packages/flutter/test/material/navigation_drawer_test.dart @@ -123,14 +123,15 @@ void main() { await tester.tap(find.text('AC')); await tester.pump(); - expect(findDestinationInk('AC'), findsNothing); + // When no background color is set, only the non-visible indicator Ink is expected. + expect(findDestinationInk('AC'), findsOne); // Destination with a custom background color. await tester.tap(find.byIcon(Icons.access_alarm)); await tester.pump(); // A Material is added with the custom color. - expect(findDestinationInk('Alarm'), findsOne); + expect(findDestinationInk('Alarm'), findsNWidgets(2)); final BoxDecoration destinationDecoration = tester.firstWidget(findDestinationInk('Alarm')).decoration! as BoxDecoration; expect(destinationDecoration.color, color); @@ -549,8 +550,8 @@ InkWell? _getInkWell(WidgetTester tester) { ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) { return tester - .firstWidget( - find.descendant(of: find.byType(NavigationIndicator), matching: find.byType(Container)), + .firstWidget( + find.descendant(of: find.byType(NavigationIndicator), matching: find.byType(Ink)), ) .decoration as ShapeDecoration?; diff --git a/packages/flutter/test/material/navigation_drawer_theme_test.dart b/packages/flutter/test/material/navigation_drawer_theme_test.dart index 3dcf1a9564023..b81d82c279a5b 100644 --- a/packages/flutter/test/material/navigation_drawer_theme_test.dart +++ b/packages/flutter/test/material/navigation_drawer_theme_test.dart @@ -283,8 +283,8 @@ Material _getMaterial(WidgetTester tester) { ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) { return tester - .firstWidget( - find.descendant(of: find.byType(NavigationIndicator), matching: find.byType(Container)), + .firstWidget( + find.descendant(of: find.byType(NavigationIndicator), matching: find.byType(Ink)), ) .decoration as ShapeDecoration?; diff --git a/packages/flutter/test/material/navigation_rail_test.dart b/packages/flutter/test/material/navigation_rail_test.dart index 84058931d83c7..faf488b56339d 100644 --- a/packages/flutter/test/material/navigation_rail_test.dart +++ b/packages/flutter/test/material/navigation_rail_test.dart @@ -3123,7 +3123,7 @@ void main() { ) ..rect(rect: indicatorRect, color: const Color(0x0a6750a4)) ..rrect( - rrect: RRect.fromLTRBR(12.0, 72.0, 68.0, 104.0, const Radius.circular(16)), + rrect: RRect.fromLTRBR(12.0, 0.0, 68.0, 32.0, const Radius.circular(16)), color: const Color(0xffe8def8), ), ); @@ -3185,7 +3185,7 @@ void main() { ) ..rect(rect: indicatorRect, color: const Color(0x0a6750a4)) ..rrect( - rrect: RRect.fromLTRBR(12.0, 58.0, 68.0, 90.0, const Radius.circular(16)), + rrect: RRect.fromLTRBR(12.0, 6.0, 68.0, 38.0, const Radius.circular(16)), color: const Color(0xffe8def8), ), ); @@ -3251,7 +3251,7 @@ void main() { ) ..rect(rect: indicatorRect, color: const Color(0x0a6750a4)) ..rrect( - rrect: RRect.fromLTRBR(30.0, 96.0, 86.0, 128.0, const Radius.circular(16)), + rrect: RRect.fromLTRBR(30.0, 24.0, 86.0, 56.0, const Radius.circular(16)), color: const Color(0xffe8def8), ), ); @@ -3316,7 +3316,7 @@ void main() { ) ..rect(rect: indicatorRect, color: const Color(0x0a6750a4)) ..rrect( - rrect: RRect.fromLTRBR(0.0, 58.0, 50.0, 90.0, const Radius.circular(16)), + rrect: RRect.fromLTRBR(0.0, 6.0, 50.0, 38.0, const Radius.circular(16)), color: const Color(0xffe8def8), ), ); @@ -3383,7 +3383,7 @@ void main() { ) ..rect(rect: indicatorRect, color: const Color(0x0a6750a4)) ..rrect( - rrect: RRect.fromLTRBR(140.0, 96.0, 196.0, 128.0, const Radius.circular(16)), + rrect: RRect.fromLTRBR(140.0, 24.0, 196.0, 56.0, const Radius.circular(16)), color: const Color(0xffe8def8), ), ); @@ -3423,13 +3423,11 @@ void main() { ); // Default values from M3 specification. + const double railMinWidth = 80.0; const double indicatorHeight = 32.0; const double destinationWidth = 72.0; const double destinationHorizontalPadding = 8.0; const double indicatorWidth = destinationWidth - 2 * destinationHorizontalPadding; // 56.0 - const double verticalSpacer = 8.0; - const double verticalIconLabelSpacing = 4.0; - const double verticalDestinationSpacing = 12.0; // The navigation rail width is larger than default because of the first destination long label. final double railWidth = tester.getSize(find.byType(NavigationRail)).width; @@ -3440,13 +3438,7 @@ void main() { final Rect indicatorRect = Rect.fromLTRB(indicatorLeft, 0.0, indicatorRight, indicatorHeight); final Rect includedRect = indicatorRect; final Rect excludedRect = includedRect.inflate(10); - - // Compute the vertical position for the selected destination (the one with 'bookmark' icon). - const double labelHeight = 16; // fontSize is 12 and height is 1.3. - const double destinationHeight = - indicatorHeight + verticalIconLabelSpacing + labelHeight + verticalDestinationSpacing; - const double secondDestinationVerticalOffset = verticalSpacer + destinationHeight; - const double secondIndicatorVerticalOffset = secondDestinationVerticalOffset; + const double indicatorHorizontalPadding = (railMinWidth - indicatorWidth) / 2; // 12.0 expect( inkFeatures, @@ -3470,10 +3462,10 @@ void main() { ..rect(rect: indicatorRect, color: const Color(0x0a6750a4)) ..rrect( rrect: RRect.fromLTRBR( - indicatorLeft, - secondIndicatorVerticalOffset, - indicatorRight, - secondIndicatorVerticalOffset + indicatorHeight, + indicatorHorizontalPadding, + 0.0, + indicatorHorizontalPadding + indicatorWidth, + indicatorHeight, const Radius.circular(16), ), color: const Color(0xffe8def8), @@ -3526,9 +3518,6 @@ void main() { const double destinationWidth = 72.0; const double destinationHorizontalPadding = 8.0; const double indicatorWidth = destinationWidth - 2 * destinationHorizontalPadding; // 56.0 - const double verticalSpacer = 8.0; - const double verticalIconLabelSpacing = 4.0; - const double verticalDestinationSpacing = 12.0; // The navigation rail width is the default one because labels are short. final double railWidth = tester.getSize(find.byType(NavigationRail)).width; @@ -3548,13 +3537,8 @@ void main() { final Rect includedRect = indicatorRect; final Rect excludedRect = includedRect.inflate(10); - // Compute the vertical position for the selected destination (the one with 'bookmark' icon). - const double labelHeight = 16; // fontSize is 12 and height is 1.3. - const double destinationHeight = - iconSize + verticalIconLabelSpacing + labelHeight + verticalDestinationSpacing; - const double secondDestinationVerticalOffset = verticalSpacer + destinationHeight; - const double indicatorOffset = (iconSize - indicatorHeight) / 2; - const double secondIndicatorVerticalOffset = secondDestinationVerticalOffset + indicatorOffset; + // Icon height is greater than indicator height so the indicator has a vertical offset. + const double secondIndicatorVerticalOffset = (iconSize - indicatorHeight) / 2; expect( inkFeatures, @@ -3633,7 +3617,6 @@ void main() { const double destinationWidth = 72.0; const double destinationHorizontalPadding = 8.0; const double indicatorWidth = destinationWidth - 2 * destinationHorizontalPadding; // 56.0 - const double verticalSpacer = 8.0; const double verticalDestinationSpacingM3 = 12.0; // The navigation rail width is the default one because labels are short. @@ -3653,11 +3636,7 @@ void main() { final Rect excludedRect = includedRect.inflate(10); // Compute the vertical position for the selected destination (the one with 'bookmark' icon). - const double destinationHeight = indicatorHeight + verticalDestinationSpacingM3; - const double secondDestinationVerticalOffset = verticalSpacer + destinationHeight; - const double secondIndicatorVerticalOffset = - secondDestinationVerticalOffset + verticalDestinationSpacingM3 / 2; - const double secondDestinationHorizontalOffset = 800 - railMinExtendedWidth; // RTL. + const double secondIndicatorVerticalOffset = verticalDestinationSpacingM3 / 2; expect( inkFeatures, @@ -3683,9 +3662,9 @@ void main() { // Indicator for the selected destination (the one with 'bookmark' icon). ..rrect( rrect: RRect.fromLTRBR( - secondDestinationHorizontalOffset + indicatorLeft, + indicatorLeft, secondIndicatorVerticalOffset, - secondDestinationHorizontalOffset + indicatorRight, + indicatorRight, secondIndicatorVerticalOffset + indicatorHeight, const Radius.circular(16), ), @@ -6369,8 +6348,8 @@ Widget _buildWidget(Widget child, {bool useMaterial3 = true, bool isRTL = false} ShapeDecoration? _getIndicatorDecoration(WidgetTester tester) { return tester - .firstWidget( - find.descendant(of: find.byType(FadeTransition), matching: find.byType(Container)), + .firstWidget( + find.descendant(of: find.byType(FadeTransition), matching: find.byType(Ink)), ) .decoration as ShapeDecoration?; diff --git a/packages/flutter/test/material/navigation_rail_theme_test.dart b/packages/flutter/test/material/navigation_rail_theme_test.dart index 52a055e4f73bf..d8c96f0a8278d 100644 --- a/packages/flutter/test/material/navigation_rail_theme_test.dart +++ b/packages/flutter/test/material/navigation_rail_theme_test.dart @@ -324,8 +324,8 @@ Material _railMaterial(WidgetTester tester) { ShapeDecoration? _indicatorDecoration(WidgetTester tester) { return tester - .firstWidget( - find.descendant(of: find.byType(NavigationIndicator), matching: find.byType(Container)), + .firstWidget( + find.descendant(of: find.byType(NavigationIndicator), matching: find.byType(Ink)), ) .decoration as ShapeDecoration?; From 04f323450df4f282387f709db2da1ea10dae4e99 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Mon, 6 Oct 2025 12:48:09 -0400 Subject: [PATCH 082/204] [ Widget Preview ] Fix `WidgetInspectorService` override (#176550) When registering the `WidgetPreviewScaffoldInspectorService`, we were originally setting it after the bindings were initialized. This meant that the widget inspector service extensions were registered with the original `WidgetInspectorService` and were not taking the custom codepaths in the override. This change moves the `WidgetPreviewScaffoldInspectorService` initialization to before the bindings are initialized. --- .../templates/template_manifest.json | 1 + ...widget_preview_inspector_service.dart.tmpl | 87 +++++++++++++++++++ .../src/widget_preview_rendering.dart.tmpl | 11 ++- ...dget_preview_scaffold_controller.dart.tmpl | 82 +---------------- .../src/widget_preview_inspector_service.dart | 87 +++++++++++++++++++ .../lib/src/widget_preview_rendering.dart | 11 ++- .../widget_preview_scaffold_controller.dart | 82 +---------------- ...idget_inspector_service_override_test.dart | 56 ++++++++++++ .../test/widget_inspector_test.dart | 10 ++- 9 files changed, 259 insertions(+), 168 deletions(-) create mode 100644 packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart.tmpl create mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart create mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/widget_inspector_service_override_test.dart diff --git a/packages/flutter_tools/templates/template_manifest.json b/packages/flutter_tools/templates/template_manifest.json index 81325121dc5de..d27b35e4b6236 100644 --- a/packages/flutter_tools/templates/template_manifest.json +++ b/packages/flutter_tools/templates/template_manifest.json @@ -341,6 +341,7 @@ "templates/widget_preview_scaffold/lib/main.dart.tmpl", "templates/widget_preview_scaffold/lib/src/widget_preview.dart.tmpl", + "templates/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart.tmpl", "templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl", "templates/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart.tmpl", "templates/widget_preview_scaffold/lib/src/controls.dart.tmpl", diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart.tmpl new file mode 100644 index 0000000000000..d91e39e752138 --- /dev/null +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart.tmpl @@ -0,0 +1,87 @@ +// 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/widgets.dart'; +import 'package:widget_preview_scaffold/src/dtd/dtd_services.dart'; +import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; +import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart'; + +/// A custom [WidgetInspectorService] responsible for routing navigation events +/// to the IDE. +/// +/// IMPORTANT NOTE: this **must** be called before WidgetsFlutterBinding.ensureInitialized() +/// is called, otherwise the inspector service extensions will be registered against +/// the default WidgetInspectorService, causing overrides to not be invoked. +class WidgetPreviewScaffoldInspectorService with WidgetInspectorService { + WidgetPreviewScaffoldInspectorService({required this.dtdServices}) { + WidgetInspectorService.instance = this; + } + + /// The DTD services instance used to communicate with the tool. + final WidgetPreviewScaffoldDtdServices dtdServices; + + // Keys used to specify the creation location of a widget when serializing a + // DiagnosticsNode to JSON. This location is used by the widget inspector + // to jump to the creation location of a selected widget. + static const kFile = 'fileUri'; + static const kLine = 'line'; + static const kColumn = 'column'; + + CodeLocation? _nextNavigationLocation; + + @protected + @override + bool setSelection(Object? object, [String? groupName]) { + // The next navigation event sent to `postEvent` will be for this selection. + // Save the location of preview annotation applications so we can override + // the navigation target in `postEvent`. + if (object is PreviewWidgetElement) { + final previewData = (object.widget as PreviewWidget).preview; + _nextNavigationLocation = CodeLocation( + uri: previewData.scriptUri, + line: previewData.line, + column: previewData.column, + ); + } + final result = super.setSelection(object, groupName); + _nextNavigationLocation = null; + return result; + } + + @override + void postEvent( + String eventKind, + Map eventData, { + String stream = 'Extension', + }) { + // It's unlikely that the widget previewer will be connected to directly by + // an IDE via the VM service, so we forward navigation events via the + // Editor DTD service. + if (eventKind == 'navigate') { + CodeLocation? location = _nextNavigationLocation; + if (eventData case { + kFile: final String file, + kLine: final int line, + kColumn: final int column, + } when location == null) { + location = CodeLocation(uri: file, line: line, column: column); + } else if (location != null) { + // If a [PreviewWidgetElement] was selected, we're not navigating to the + // creation location of the widget. Override the location details in the + // event data, just in case an IDE is attached and listening for + // navigation events through the VM service. + // TODO(bkonyi): determine if this is necessary + eventData.addAll({ + kFile: location.uri, + kLine: location.line!, + kColumn: location.column!, + }); + } + if (location != null) { + dtdServices.navigateToCode(location); + } + } + super.postEvent(eventKind, eventData, stream: stream); + } +} diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl index 47faa6c5c1051..2d3c43ae74c57 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl @@ -16,6 +16,7 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:stack_trace/stack_trace.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; +import 'package:widget_preview_scaffold/src/widget_preview_inspector_service.dart'; import 'controls.dart'; import 'generated_preview.dart'; @@ -928,14 +929,20 @@ class PreviewAssetBundle extends PlatformAssetBundle { /// the preview scaffold project which prevents us from being able to use hot /// restart to iterate on this file. Future mainImpl() async { + final controller = WidgetPreviewScaffoldController(previews: previews); + await controller.initialize(); + // WARNING: do not move this line. This constructor sets + // [WidgetInspectorService.instance] to the custom service for the widget + // previewer. If [WidgetsFlutterBinding.ensureInitialized()] is invoked before + // the custom service is set, inspector service extensions will be registered + // against the wrong service. + WidgetPreviewScaffoldInspectorService(dtdServices: controller.dtdServices); final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); // Disable the injection of [WidgetInspector] into the widget tree built by // [WidgetsApp]. [WidgetInspector] instances will be created for each // individual preview so the widget inspector won't allow for users to select // widgets that make up the widget preview scaffolding. binding.debugExcludeRootWidgetInspector = true; - final controller = WidgetPreviewScaffoldController(previews: previews); - await controller.initialize(); runWidget( DisableWidgetInspectorScope( child: binding.wrapWithDefaultView( diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart.tmpl index 2444abfb7bd79..527ad8edf9767 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart.tmpl @@ -3,9 +3,7 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; import 'package:path/path.dart' as path; -import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart'; import 'dtd/dtd_services.dart'; import 'widget_preview.dart'; @@ -24,11 +22,7 @@ class WidgetPreviewScaffoldController { required PreviewsCallback previews, @visibleForTesting WidgetPreviewScaffoldDtdServices? dtdServicesOverride, }) : _previews = previews, - dtdServices = dtdServicesOverride ?? WidgetPreviewScaffoldDtdServices() { - // Overrides the default WidgetInspectorService instance to handle selection - // events. - _WidgetPreviewScaffoldInspectorService(dtdServices: dtdServices); - } + dtdServices = dtdServicesOverride ?? WidgetPreviewScaffoldDtdServices(); @visibleForTesting static const kFilterBySelectedFilePreference = 'filterBySelectedFile'; @@ -170,77 +164,3 @@ class WidgetPreviewScaffoldController { } } } - -/// A custom [WidgetInspectorService] responsible for routing navigation events -/// to the IDE. -class _WidgetPreviewScaffoldInspectorService with WidgetInspectorService { - _WidgetPreviewScaffoldInspectorService({required this.dtdServices}) { - WidgetInspectorService.instance = this; - } - - final WidgetPreviewScaffoldDtdServices dtdServices; - - // Keys used to specify the creation location of a widget when serializing a - // DiagnosticsNode to JSON. This location is used by the widget inspector - // to jump to the creation location of a selected widget. - static const kFile = 'fileUri'; - static const kLine = 'line'; - static const kColumn = 'column'; - - CodeLocation? _nextNavigationLocation; - - @protected - @override - bool setSelection(Object? object, [String? groupName]) { - // The next navigation event sent to `postEvent` will be for this selection. - // Save the location of preview annotation applications so we can override - // the navigation target in `postEvent`. - if (object is PreviewWidgetElement) { - final previewData = (object.widget as PreviewWidget).preview; - _nextNavigationLocation = CodeLocation( - uri: previewData.scriptUri, - line: previewData.line, - column: previewData.column, - ); - } - final result = super.setSelection(object, groupName); - _nextNavigationLocation = null; - return result; - } - - @override - void postEvent( - String eventKind, - Map eventData, { - String stream = 'Extension', - }) { - // It's unlikely that the widget previewer will be connected to directly by - // an IDE via the VM service, so we forward navigation events via the - // Editor DTD service. - if (eventKind == 'navigate') { - CodeLocation? location = _nextNavigationLocation; - if (eventData case { - kFile: final String file, - kLine: final int line, - kColumn: final int column, - } when location == null) { - location = CodeLocation(uri: file, line: line, column: column); - } else if (location != null) { - // If a [PreviewWidgetElement] was selected, we're not navigating to the - // creation location of the widget. Override the location details in the - // event data, just in case an IDE is attached and listening for - // navigation events through the VM service. - // TODO(bkonyi): determine if this is necessary - eventData.addAll({ - kFile: location.uri, - kLine: location.line!, - kColumn: location.column!, - }); - } - if (location != null) { - dtdServices.navigateToCode(location); - } - } - super.postEvent(eventKind, eventData, stream: stream); - } -} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart new file mode 100644 index 0000000000000..d91e39e752138 --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_inspector_service.dart @@ -0,0 +1,87 @@ +// 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/widgets.dart'; +import 'package:widget_preview_scaffold/src/dtd/dtd_services.dart'; +import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; +import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart'; + +/// A custom [WidgetInspectorService] responsible for routing navigation events +/// to the IDE. +/// +/// IMPORTANT NOTE: this **must** be called before WidgetsFlutterBinding.ensureInitialized() +/// is called, otherwise the inspector service extensions will be registered against +/// the default WidgetInspectorService, causing overrides to not be invoked. +class WidgetPreviewScaffoldInspectorService with WidgetInspectorService { + WidgetPreviewScaffoldInspectorService({required this.dtdServices}) { + WidgetInspectorService.instance = this; + } + + /// The DTD services instance used to communicate with the tool. + final WidgetPreviewScaffoldDtdServices dtdServices; + + // Keys used to specify the creation location of a widget when serializing a + // DiagnosticsNode to JSON. This location is used by the widget inspector + // to jump to the creation location of a selected widget. + static const kFile = 'fileUri'; + static const kLine = 'line'; + static const kColumn = 'column'; + + CodeLocation? _nextNavigationLocation; + + @protected + @override + bool setSelection(Object? object, [String? groupName]) { + // The next navigation event sent to `postEvent` will be for this selection. + // Save the location of preview annotation applications so we can override + // the navigation target in `postEvent`. + if (object is PreviewWidgetElement) { + final previewData = (object.widget as PreviewWidget).preview; + _nextNavigationLocation = CodeLocation( + uri: previewData.scriptUri, + line: previewData.line, + column: previewData.column, + ); + } + final result = super.setSelection(object, groupName); + _nextNavigationLocation = null; + return result; + } + + @override + void postEvent( + String eventKind, + Map eventData, { + String stream = 'Extension', + }) { + // It's unlikely that the widget previewer will be connected to directly by + // an IDE via the VM service, so we forward navigation events via the + // Editor DTD service. + if (eventKind == 'navigate') { + CodeLocation? location = _nextNavigationLocation; + if (eventData case { + kFile: final String file, + kLine: final int line, + kColumn: final int column, + } when location == null) { + location = CodeLocation(uri: file, line: line, column: column); + } else if (location != null) { + // If a [PreviewWidgetElement] was selected, we're not navigating to the + // creation location of the widget. Override the location details in the + // event data, just in case an IDE is attached and listening for + // navigation events through the VM service. + // TODO(bkonyi): determine if this is necessary + eventData.addAll({ + kFile: location.uri, + kLine: location.line!, + kColumn: location.column!, + }); + } + if (location != null) { + dtdServices.navigateToCode(location); + } + } + super.postEvent(eventKind, eventData, stream: stream); + } +} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart index 47faa6c5c1051..2d3c43ae74c57 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart @@ -16,6 +16,7 @@ import 'package:google_fonts/google_fonts.dart'; import 'package:stack_trace/stack_trace.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; +import 'package:widget_preview_scaffold/src/widget_preview_inspector_service.dart'; import 'controls.dart'; import 'generated_preview.dart'; @@ -928,14 +929,20 @@ class PreviewAssetBundle extends PlatformAssetBundle { /// the preview scaffold project which prevents us from being able to use hot /// restart to iterate on this file. Future mainImpl() async { + final controller = WidgetPreviewScaffoldController(previews: previews); + await controller.initialize(); + // WARNING: do not move this line. This constructor sets + // [WidgetInspectorService.instance] to the custom service for the widget + // previewer. If [WidgetsFlutterBinding.ensureInitialized()] is invoked before + // the custom service is set, inspector service extensions will be registered + // against the wrong service. + WidgetPreviewScaffoldInspectorService(dtdServices: controller.dtdServices); final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized(); // Disable the injection of [WidgetInspector] into the widget tree built by // [WidgetsApp]. [WidgetInspector] instances will be created for each // individual preview so the widget inspector won't allow for users to select // widgets that make up the widget preview scaffolding. binding.debugExcludeRootWidgetInspector = true; - final controller = WidgetPreviewScaffoldController(previews: previews); - await controller.initialize(); runWidget( DisableWidgetInspectorScope( child: binding.wrapWithDefaultView( diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart index 2444abfb7bd79..527ad8edf9767 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_scaffold_controller.dart @@ -3,9 +3,7 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; import 'package:path/path.dart' as path; -import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart'; import 'dtd/dtd_services.dart'; import 'widget_preview.dart'; @@ -24,11 +22,7 @@ class WidgetPreviewScaffoldController { required PreviewsCallback previews, @visibleForTesting WidgetPreviewScaffoldDtdServices? dtdServicesOverride, }) : _previews = previews, - dtdServices = dtdServicesOverride ?? WidgetPreviewScaffoldDtdServices() { - // Overrides the default WidgetInspectorService instance to handle selection - // events. - _WidgetPreviewScaffoldInspectorService(dtdServices: dtdServices); - } + dtdServices = dtdServicesOverride ?? WidgetPreviewScaffoldDtdServices(); @visibleForTesting static const kFilterBySelectedFilePreference = 'filterBySelectedFile'; @@ -170,77 +164,3 @@ class WidgetPreviewScaffoldController { } } } - -/// A custom [WidgetInspectorService] responsible for routing navigation events -/// to the IDE. -class _WidgetPreviewScaffoldInspectorService with WidgetInspectorService { - _WidgetPreviewScaffoldInspectorService({required this.dtdServices}) { - WidgetInspectorService.instance = this; - } - - final WidgetPreviewScaffoldDtdServices dtdServices; - - // Keys used to specify the creation location of a widget when serializing a - // DiagnosticsNode to JSON. This location is used by the widget inspector - // to jump to the creation location of a selected widget. - static const kFile = 'fileUri'; - static const kLine = 'line'; - static const kColumn = 'column'; - - CodeLocation? _nextNavigationLocation; - - @protected - @override - bool setSelection(Object? object, [String? groupName]) { - // The next navigation event sent to `postEvent` will be for this selection. - // Save the location of preview annotation applications so we can override - // the navigation target in `postEvent`. - if (object is PreviewWidgetElement) { - final previewData = (object.widget as PreviewWidget).preview; - _nextNavigationLocation = CodeLocation( - uri: previewData.scriptUri, - line: previewData.line, - column: previewData.column, - ); - } - final result = super.setSelection(object, groupName); - _nextNavigationLocation = null; - return result; - } - - @override - void postEvent( - String eventKind, - Map eventData, { - String stream = 'Extension', - }) { - // It's unlikely that the widget previewer will be connected to directly by - // an IDE via the VM service, so we forward navigation events via the - // Editor DTD service. - if (eventKind == 'navigate') { - CodeLocation? location = _nextNavigationLocation; - if (eventData case { - kFile: final String file, - kLine: final int line, - kColumn: final int column, - } when location == null) { - location = CodeLocation(uri: file, line: line, column: column); - } else if (location != null) { - // If a [PreviewWidgetElement] was selected, we're not navigating to the - // creation location of the widget. Override the location details in the - // event data, just in case an IDE is attached and listening for - // navigation events through the VM service. - // TODO(bkonyi): determine if this is necessary - eventData.addAll({ - kFile: location.uri, - kLine: location.line!, - kColumn: location.column!, - }); - } - if (location != null) { - dtdServices.navigateToCode(location); - } - } - super.postEvent(eventKind, eventData, stream: stream); - } -} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/widget_inspector_service_override_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/widget_inspector_service_override_test.dart new file mode 100644 index 0000000000000..1b28afc90488a --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/widget_inspector_service_override_test.dart @@ -0,0 +1,56 @@ +// 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/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:widget_preview_scaffold/src/widget_preview_inspector_service.dart'; + +import 'utils/widget_preview_scaffold_test_utils.dart'; + +void main() { + test( + 'Ensure $WidgetPreviewScaffoldInspectorService is initialized correctly', + () async { + expect( + WidgetInspectorService.instance, + isNot(isA()), + ); + final dtdServices = FakeWidgetPreviewScaffoldDtdServices(); + // Override the original WidgetInspectorService with our custom version. + TestWidgetPreviewScaffoldInspectorService(dtdServices: dtdServices); + expect( + WidgetInspectorService.instance, + isA(), + ); + // Initialize the bindings and verify that the inspector service hasn't been + // changed and that initServiceExtensions has been invoked. This indicates + // that the inspector service extensions have been initialized with the custom + // inspector service instance and will be routed through the overridden methods. + WidgetsFlutterBinding.ensureInitialized(); + expect( + WidgetInspectorService.instance, + isA(), + ); + expect( + TestWidgetPreviewScaffoldInspectorService.serviceExtensionsRegistered, + true, + ); + }, + ); +} + +final class TestWidgetPreviewScaffoldInspectorService + extends WidgetPreviewScaffoldInspectorService { + TestWidgetPreviewScaffoldInspectorService({required super.dtdServices}); + + static bool serviceExtensionsRegistered = false; + + @override + void initServiceExtensions( + RegisterServiceExtensionCallback registerExtension, + ) { + super.initServiceExtensions(registerExtension); + serviceExtensionsRegistered = true; + } +} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/widget_inspector_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/widget_inspector_test.dart index fdd17cb97fc3a..e466adbb57943 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/widget_inspector_test.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/widget_inspector_test.dart @@ -6,6 +6,7 @@ import 'package:flutter/widget_previews.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:widget_preview_scaffold/src/widget_preview.dart'; +import 'package:widget_preview_scaffold/src/widget_preview_inspector_service.dart'; import 'package:widget_preview_scaffold/src/widget_preview_rendering.dart'; import 'utils/widget_preview_scaffold_test_utils.dart'; @@ -89,9 +90,14 @@ void main() { testWidgets('WidgetInspector navigates to Preview application location', ( tester, ) async { - final controller = FakeWidgetPreviewScaffoldController(); + final dtd = FakeWidgetPreviewScaffoldDtdServices(); + // Install the WidgetInspectorService override (this is done in the + // constructor body). + WidgetPreviewScaffoldInspectorService(dtdServices: dtd); + final controller = FakeWidgetPreviewScaffoldController( + dtdServicesOverride: dtd, + ); await controller.initialize(); - final dtd = controller.dtdServices as FakeWidgetPreviewScaffoldDtdServices; final service = WidgetInspectorService.instance; service.isSelectMode = true; From 6baea94c1c5e7dda5ce4697b8d745d00cae9f1d6 Mon Sep 17 00:00:00 2001 From: gaaclarke <30870216+gaaclarke@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:50:20 -0700 Subject: [PATCH 083/204] updates docs for flutter engine footprint (#176217) ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- docs/engine/Engine-disk-footprint.md | 47 +++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/docs/engine/Engine-disk-footprint.md b/docs/engine/Engine-disk-footprint.md index 0ce647128c755..1969f69a12158 100644 --- a/docs/engine/Engine-disk-footprint.md +++ b/docs/engine/Engine-disk-footprint.md @@ -1,16 +1,48 @@ +# Engine disk footprint + +Here are all the tools to help track and debug the disk size of the Flutter +engine. + ## Treemaps -For each commit to [flutter/engine](https://github.com/flutter/engine) the Chromebots generate treemaps illustrating the sizes of the individual components within release builds of `libflutter.so`. The treemap is uploaded to Google Cloud Storage and linked from the [LUCI](https://ci.chromium.org/p/flutter/g/engine/console) console: Select a "Linux aot" build and search for "Open Treemap". +For each commit to [flutter/flutter](https://github.com/flutter/flutter) the +Chromebots generate treemaps illustrating the sizes of the individual components +within release builds of `libflutter.so`. The treemap is uploaded to Google +Cloud Storage and linked from the +[LUCI](https://ci.chromium.org/p/flutter/g/engine/console) console. + +To find a treemap for a given commit follow these steps: + +1) go to the list of all flutter commits: + +1) find the commit you want to evaluate, then click on the green checkbox to see + the checks +1) find the check titled `Linux linux_android_aot_engine`, click on `details` +1) Click on `View more details on flutter-dashboard` to get access to the LUCI + build page (example: + ). +1) expand the section called `launch builds` +1) find the launched build named something like `Linux Production Engine Drone + for ci/android_release_arm64`, click it (example + ). +1) look for the build section called `log links`, click the `index.html` link + under it (example: + ). -Alternatively, a link to a treemap can be constructed as follows: +Treemaps can also be generated locally with the following call: -`https://storage.googleapis.com/flutter_infra_release/flutter///sizes/index.html` where: -* `` is the git hash from [flutter/engine](https://github.com/flutter/engine) for which you want the treemap, and -* `` can be any android release build, e.g. `android-arm-release` or `android-arm64-release`. +```shell +flutter/ci/binary_size_treemap.sh +``` ## Benchmarks -In [devicelab](https://github.com/flutter/flutter/tree/main/dev/devicelab) we run various benchmarks to track the APK/IPA sizes and various (engine) artifacts contained within. These benchmarks run for every commit to [flutter/flutter](https://github.com/flutter/flutter) and are visible on our [build dashboard](https://flutter-dashboard.appspot.com/). The most relevant benchmarks for engine size are: +In [devicelab](https://github.com/flutter/flutter/tree/main/dev/devicelab) we +run various benchmarks to track the APK/IPA sizes and various (engine) artifacts +contained within. These benchmarks run for every commit to +[flutter/flutter](https://github.com/flutter/flutter) and are visible on our +[build dashboard](https://flutter-dashboard.appspot.com/). The most relevant +benchmarks for engine size are: * APK/IPA size of Flutter Gallery * Android: `flutter_gallery_android__compile/release_size_bytes` @@ -30,4 +62,5 @@ In [devicelab](https://github.com/flutter/flutter/tree/main/dev/devicelab) we ru ## Comparing AOT Snapshot Sizes -A detailed comparison of AOT snapshot sizes can be performed using the [instructions documented here](./benchmarks/Comparing-AOT-Snapshot-Sizes.md). \ No newline at end of file +A detailed comparison of AOT snapshot sizes can be performed using the +[instructions documented here](./benchmarks/Comparing-AOT-Snapshot-Sizes.md). From c18530cbaac6eb01666538754a38993cb61843e9 Mon Sep 17 00:00:00 2001 From: alexskobozev Date: Mon, 6 Oct 2025 21:54:11 +0400 Subject: [PATCH 084/204] Fix deprecated configureStatusBarForFullscreenFlutterExperience for Android 15+ (#175501) This PR addresses [issue #175507] by fixing FlutterActivity.configureStatusBarForFullscreenFlutterExperience() which no longer works on Android 15 (API 35) because setStatusBarColor is deprecated and ignored. ## Changes - Wrapped window.setStatusBarColor in a version check (if (Build.VERSION.SDK_INT < 35)). - Removed the @Deprecated annotation from configureStatusBarForFullscreenFlutterExperience, since it still works fine on API <35 and gives a consistent experience with iOS. ## Why - On API 35+, the call is ignored and the status bar is fully transparent. - This is visually acceptable (fullscreen experience looks good), and matches iOS behavior. ## Screenshots ### API 27 api27_legacy ### API 35 api35_legacy ### iOS ios ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../flutter/embedding/android/FlutterActivity.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index 7819bd09fc92d..2433dda089ff0 100644 --- a/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/engine/src/flutter/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -810,15 +810,18 @@ private View createFlutterView() { } /** - * @deprecated This method is outdated because it calls {@code setStatusBarColor}, which is - * deprecated in Android 15 and above. Consider using the new WindowInsetsController or other - * Android 15+ APIs for system UI styling. + * Configures the status bar for a fullscreen Flutter experience. + * + *

On API levels before 35, this sets a translucent status bar. On API level 35 and above, this + * is a no-op as the system handles the status bar appearance, resulting in a fully transparent + * status bar. */ - @Deprecated private void configureStatusBarForFullscreenFlutterExperience() { Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.setStatusBarColor(0x40000000); + if (Build.VERSION.SDK_INT < API_LEVELS.API_35) { + window.setStatusBarColor(0x40000000); + } window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI); } From b48b3fe0c1d595be296926874253147cc1bc01b3 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Mon, 6 Oct 2025 14:30:46 -0400 Subject: [PATCH 085/204] Roll Fuchsia Linux SDK from Zm6K_3gP3VCaMy9rH... to jJr3my9C6TwYWPygi... (#176591) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/fuchsia-linux-sdk-flutter Please CC jimgraham@google.com,zra@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 75c64407ace7c..0152ab17cfe17 100644 --- a/DEPS +++ b/DEPS @@ -807,7 +807,7 @@ deps = { 'packages': [ { 'package': 'fuchsia/sdk/core/linux-amd64', - 'version': 'Zm6K_3gP3VCaMy9rHnamFTPU-TenhAyCaja-ASWNSNIC' + 'version': 'jJr3my9C6TwYWPygiuoFk4CPP7MdRW4msLZw1Ul2h70C' } ], 'condition': 'download_fuchsia_deps and not download_fuchsia_sdk', From fafb7a2a313da0430a06e0af6c21a8d722d98b06 Mon Sep 17 00:00:00 2001 From: Kishan Rathore <34465683+rkishan516@users.noreply.github.com> Date: Tue, 7 Oct 2025 00:39:24 +0530 Subject: [PATCH 086/204] Fix: Update anchorRect for overlayBuilder when anchor moves (#169814) Fix: Update anchorRect for overlayBuilder when anchor moves fixes: #169457 part of https://github.com/flutter/flutter/issues/173440 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- .../lib/src/widgets/raw_menu_anchor.dart | 23 ++++------ .../test/widgets/raw_menu_anchor_test.dart | 44 +++++++++++++++++++ 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/packages/flutter/lib/src/widgets/raw_menu_anchor.dart b/packages/flutter/lib/src/widgets/raw_menu_anchor.dart index 064a4c969fbbe..cac784a5c2bf8 100644 --- a/packages/flutter/lib/src/widgets/raw_menu_anchor.dart +++ b/packages/flutter/lib/src/widgets/raw_menu_anchor.dart @@ -781,21 +781,16 @@ class _RawMenuAnchorState extends State with _RawMenuAnchorBaseMi } } - Widget _buildOverlay(BuildContext context) { - final BuildContext anchorContext = _anchorKey.currentContext!; - final RenderBox overlay = - Overlay.of(anchorContext, rootOverlay: useRootOverlay).context.findRenderObject()! - as RenderBox; - final RenderBox anchorBox = anchorContext.findRenderObject()! as RenderBox; - final ui.Offset upperLeft = anchorBox.localToGlobal(Offset.zero, ancestor: overlay); - final ui.Offset bottomRight = anchorBox.localToGlobal( - anchorBox.size.bottomRight(Offset.zero), - ancestor: overlay, - ); + Widget _buildOverlay(BuildContext context, OverlayChildLayoutInfo layoutInfo) { + final Matrix4 transform = layoutInfo.childPaintTransform; + final Size anchorSize = layoutInfo.childSize; + + // Transform the anchor rectangle using the full transform matrix. + final Rect anchorRect = MatrixUtils.transformRect(transform, Offset.zero & anchorSize); final RawMenuOverlayInfo info = RawMenuOverlayInfo( - anchorRect: Rect.fromPoints(upperLeft, bottomRight), - overlaySize: overlay.size, + anchorRect: anchorRect, + overlaySize: layoutInfo.overlaySize, position: _menuPosition, tapRegionGroupId: root.menuController, ); @@ -823,7 +818,7 @@ class _RawMenuAnchorState extends State with _RawMenuAnchorBaseMi ), ); - return OverlayPortal( + return OverlayPortal.overlayChildLayoutBuilder( controller: _overlayController, overlayChildBuilder: _buildOverlay, overlayLocation: useRootOverlay diff --git a/packages/flutter/test/widgets/raw_menu_anchor_test.dart b/packages/flutter/test/widgets/raw_menu_anchor_test.dart index 88d157fe32dae..ee118be6b93cd 100644 --- a/packages/flutter/test/widgets/raw_menu_anchor_test.dart +++ b/packages/flutter/test/widgets/raw_menu_anchor_test.dart @@ -2902,6 +2902,50 @@ void main() { expect(customController.closeCallCount, equals(1)); expect(find.text(Tag.a.text), findsNothing); }); + + testWidgets('RawMenuAnchor correctly updates anchorRect for overlayBuilder when anchor moves', ( + WidgetTester tester, + ) async { + RawMenuOverlayInfo? overlayPosition; + late StateSetter setState; + bool showAdditionalWidget = true; + + await tester.pumpWidget( + App( + StatefulBuilder( + builder: (BuildContext context, StateSetter setter) { + setState = setter; + return Column( + children: [ + if (showAdditionalWidget) const SizedBox(width: 100, height: 100), + RawMenuAnchor( + overlayBuilder: (BuildContext context, RawMenuOverlayInfo position) { + overlayPosition = position; + return const SizedBox(); + }, + controller: controller, + child: AnchorButton(Tag.anchor, onPressed: onPressed), + ), + ], + ); + }, + ), + ), + ); + + await tester.tap(find.text(Tag.anchor.text)); + await tester.pump(); + + expect(overlayPosition!.anchorRect, tester.getRect(find.byType(Button))); + + setState(() { + showAdditionalWidget = false; + }); + + await tester.pumpAndSettle(); + + expect(overlayPosition!.anchorRect, tester.getRect(find.byType(Button))); + }); } // Custom MenuController that extends the base MenuController From 5a6ba3c3550c146880da40b495c8dd1ffdc73636 Mon Sep 17 00:00:00 2001 From: Thomas Duffin <43959652+TDuffinNTU@users.noreply.github.com> Date: Mon, 6 Oct 2025 20:46:19 +0100 Subject: [PATCH 087/204] Fix typo in pages.dart (#176438) Fixes a minor typo in a doc comment for `PageRouteBuilder`. There's no specific issue for this as I wasn't sure that was a necessary step for typo PRs, but one can be made if necessary. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [?] I listed at least one issue that this PR fixes in the description above. - Do I need an issue opened for a minor typo? - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- packages/flutter/lib/src/widgets/pages.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/widgets/pages.dart b/packages/flutter/lib/src/widgets/pages.dart index 0b9a0d715673e..3ef119ac56f16 100644 --- a/packages/flutter/lib/src/widgets/pages.dart +++ b/packages/flutter/lib/src/widgets/pages.dart @@ -105,7 +105,7 @@ class PageRouteBuilder extends PageRoute { }); /// {@template flutter.widgets.pageRouteBuilder.pageBuilder} - /// Used build the route's primary contents. + /// Used to build the route's primary contents. /// /// See [ModalRoute.buildPage] for complete definition of the parameters. /// {@endtemplate} From 8f9d55e7afcb0c9186d13af219018d50bcc35a63 Mon Sep 17 00:00:00 2001 From: Danny Tuppeny Date: Mon, 6 Oct 2025 21:35:10 +0100 Subject: [PATCH 088/204] Bump customer tests.version to 986c4326b4e4bb4e37bc963c2cc2aaa10b943859 (#176594) This bumps the customer tests from [20af51f6c62f7134c0c387d0ec2e13db33b1b9e4](https://github.com/flutter/tests/commit/20af51f6c62f7134c0c387d0ec2e13db33b1b9e4) to [986c4326b4e4bb4e37bc963c2cc2aaa10b943859](https://github.com/flutter/tests/commit/986c4326b4e4bb4e37bc963c2cc2aaa10b943859) to get some fixes to unblock some analysis server changes that are failing on the Flutter customer testing bots. Related changes: - https://dart-review.googlesource.com/c/sdk/+/448063 - https://github.com/flutter/tests/pull/470 - https://github.com/flutter/tests/pull/472 - https://dart-review.googlesource.com/c/sdk/+/452528 --- dev/customer_testing/tests.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/customer_testing/tests.version b/dev/customer_testing/tests.version index 9c7000d0e4da1..4622eeed1e3b8 100644 --- a/dev/customer_testing/tests.version +++ b/dev/customer_testing/tests.version @@ -1 +1 @@ -20af51f6c62f7134c0c387d0ec2e13db33b1b9e4 +986c4326b4e4bb4e37bc963c2cc2aaa10b943859 From af8e9801a415839263d18b309f72b48a467eda20 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Mon, 6 Oct 2025 21:12:26 -0500 Subject: [PATCH 089/204] Add tooling to migrate to UIScene (#176427) Migrates Flutter iOS apps to be compatible with UIScene lifecycle by migrating the AppDelegate and Info.plist. If the AppDelegate does not match Flutter's original template exactly, or if the Info.plist is not found or can't be updated, then prompt the user to migrate manually. This is hidden behind a feature flag so we can test it out first. Fixes https://github.com/flutter/flutter/issues/170167. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../lib/src/build_system/targets/ios.dart | 9 +- packages/flutter_tools/lib/src/features.dart | 19 + .../lib/src/flutter_features.dart | 3 + packages/flutter_tools/lib/src/ios/mac.dart | 8 + .../lib/src/ios/plist_parser.dart | 20 + .../lib/src/migrations/uiscene_migration.dart | 267 +++++++++++ .../flutter_tools/lib/src/xcode_project.dart | 6 +- .../general.shard/flutter_validator_test.dart | 3 + .../migrations/uiscene_migration_test.dart | 439 ++++++++++++++++++ .../uiscene_migration_test.dart | 375 +++++++++++++++ packages/flutter_tools/test/src/fakes.dart | 11 + 11 files changed, 1156 insertions(+), 4 deletions(-) create mode 100644 packages/flutter_tools/lib/src/migrations/uiscene_migration.dart create mode 100644 packages/flutter_tools/test/general.shard/migrations/uiscene_migration_test.dart create mode 100644 packages/flutter_tools/test/integration.shard/uiscene_migration_test.dart diff --git a/packages/flutter_tools/lib/src/build_system/targets/ios.dart b/packages/flutter_tools/lib/src/build_system/targets/ios.dart index b9d8058dfd215..3b70fc732b790 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/ios.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/ios.dart @@ -459,10 +459,10 @@ class _IssueLaunchRootViewControllerAccess extends Target { flutterProject.ios.appDelegateSwift, ); } - if (flutterProject.ios.appDelegateObjc.existsSync()) { + if (flutterProject.ios.appDelegateObjcImplementation.existsSync()) { await checkForLaunchRootViewControllerAccessDeprecationObjc( environment.logger, - flutterProject.ios.appDelegateObjc, + flutterProject.ios.appDelegateObjcImplementation, ); } } @@ -476,7 +476,10 @@ class _IssueLaunchRootViewControllerAccess extends Target { const Source.pattern( '{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart', ), - Source.fromProject((FlutterProject project) => project.ios.appDelegateObjc, optional: true), + Source.fromProject( + (FlutterProject project) => project.ios.appDelegateObjcImplementation, + optional: true, + ), Source.fromProject((FlutterProject project) => project.ios.appDelegateSwift, optional: true), ]; } diff --git a/packages/flutter_tools/lib/src/features.dart b/packages/flutter_tools/lib/src/features.dart index c375078c4dfae..fdbfbfdca2e66 100644 --- a/packages/flutter_tools/lib/src/features.dart +++ b/packages/flutter_tools/lib/src/features.dart @@ -73,6 +73,9 @@ abstract class FeatureFlags { /// Whether physical iOS devices are debugging with LLDB. bool get isLLDBDebuggingEnabled; + /// Whether UIScene migration is enabled. + bool get isUISceneMigrationEnabled; + /// Whether a particular feature is enabled for the current channel. /// /// Prefer using one of the specific getters above instead of this API. @@ -95,6 +98,7 @@ abstract class FeatureFlags { omitLegacyVersionFile, windowingFeature, lldbDebugging, + uiSceneMigration, ]; /// All current Flutter feature flags that can be configured. @@ -249,6 +253,21 @@ const lldbDebugging = Feature( stable: FeatureChannelSetting(available: true, enabledByDefault: true), ); +/// Enable UIScene lifecycle migration for iOS apps. When enabled, if possible the tool will +/// attempt to auto-migrate the app. Otherwise, it will print a warning with instructions on how to +/// migrate manually. +const uiSceneMigration = Feature( + name: 'support for migrating to UIScene lifecycle', + extraHelpText: + 'If enabled, Flutter will migrate your app to iOS UIScene lifecycle if possible or ' + 'otherwise instruct you to migrate manually.', + configSetting: 'enable-uiscene-migration', + environmentOverride: 'FLUTTER_UISCENE_MIGRATION', + master: FeatureChannelSetting(available: true), + beta: FeatureChannelSetting(available: true), + stable: FeatureChannelSetting(available: true), +); + /// A [Feature] is a process for conditionally enabling tool features. /// /// All settings are optional, and if not provided will generally default to diff --git a/packages/flutter_tools/lib/src/flutter_features.dart b/packages/flutter_tools/lib/src/flutter_features.dart index fbc339d4b07fc..c110278034623 100644 --- a/packages/flutter_tools/lib/src/flutter_features.dart +++ b/packages/flutter_tools/lib/src/flutter_features.dart @@ -63,6 +63,9 @@ mixin FlutterFeatureFlagsIsEnabled implements FeatureFlags { @override bool get isLLDBDebuggingEnabled => isEnabled(lldbDebugging); + + @override + bool get isUISceneMigrationEnabled => isEnabled(uiSceneMigration); } interface class FlutterFeatureFlags extends FeatureFlags with FlutterFeatureFlagsIsEnabled { diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart index cb9ab083fc987..200120f8dde89 100644 --- a/packages/flutter_tools/lib/src/ios/mac.dart +++ b/packages/flutter_tools/lib/src/ios/mac.dart @@ -19,6 +19,7 @@ import '../build_info.dart'; import '../cache.dart'; import '../darwin/darwin.dart'; import '../device.dart'; +import '../features.dart'; import '../flutter_manifest.dart'; import '../flutter_plugins.dart'; import '../globals.dart' as globals; @@ -28,6 +29,7 @@ import '../macos/xcode.dart'; import '../migrations/lldb_init_migration.dart'; import '../migrations/swift_package_manager_gitignore_migration.dart'; import '../migrations/swift_package_manager_integration_migration.dart'; +import '../migrations/uiscene_migration.dart'; import '../migrations/xcode_project_object_version_migration.dart'; import '../migrations/xcode_script_build_phase_migration.dart'; import '../migrations/xcode_thin_binary_build_phase_input_paths_migration.dart'; @@ -177,6 +179,12 @@ Future buildXcodeProject({ fileSystem: globals.fs, environmentType: environmentType, ), + UISceneMigration( + app.project, + globals.logger, + isMigrationFeatureEnabled: featureFlags.isUISceneMigrationEnabled, + plistParser: globals.plistParser, + ), ]; final migration = ProjectMigration(migrators); diff --git a/packages/flutter_tools/lib/src/ios/plist_parser.dart b/packages/flutter_tools/lib/src/ios/plist_parser.dart index fa9e6dbf23399..ce6f099bc9544 100644 --- a/packages/flutter_tools/lib/src/ios/plist_parser.dart +++ b/packages/flutter_tools/lib/src/ios/plist_parser.dart @@ -103,6 +103,26 @@ class PlistParser { return true; } + bool insertKeyWithJson(String plistFilePath, {required String key, required String json}) { + if (!_fileSystem.isFileSync(_plutilExecutable)) { + throw const FileNotFoundException(_plutilExecutable); + } + try { + _processUtils.runSync([ + _plutilExecutable, + '-insert', + key, + '-json', + json, + plistFilePath, + ], throwOnError: true); + } on ProcessException catch (error) { + _logger.printTrace('$error'); + return false; + } + return true; + } + /// Parses the plist file located at [plistFilePath] and returns the /// associated map of key/value property list pairs. /// diff --git a/packages/flutter_tools/lib/src/migrations/uiscene_migration.dart b/packages/flutter_tools/lib/src/migrations/uiscene_migration.dart new file mode 100644 index 0000000000000..ae16cf9bc8d84 --- /dev/null +++ b/packages/flutter_tools/lib/src/migrations/uiscene_migration.dart @@ -0,0 +1,267 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:meta/meta.dart'; + +import '../base/io.dart'; +import '../base/project_migrator.dart'; +import '../ios/plist_parser.dart'; +import '../xcode_project.dart'; + +/// Migrates Xcode's Info.plist and AppDelegate to support UIScene if matches original templates. +/// Otherwise, provides link to documentation to migrate manually. +/// +/// Only migrates if +class UISceneMigration extends ProjectMigrator { + UISceneMigration( + IosProject project, + super.logger, { + required bool isMigrationFeatureEnabled, + required PlistParser plistParser, + }) : _isMigrationFeatureEnabled = isMigrationFeatureEnabled, + _project = project, + _plistParser = plistParser; + + final bool _isMigrationFeatureEnabled; + final PlistParser _plistParser; + final IosProject _project; + + @visibleForTesting + static const originalSwiftAppDelegate = ''' +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} +'''; + + static const secondaryOriginalSwiftAppDelegate = ''' +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterPluginRegistrant { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + pluginRegistrant = self + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func register(with registry: any FlutterPluginRegistry) { + GeneratedPluginRegistrant.register(with: registry) + } +} +'''; + + @visibleForTesting + static const originalObjCAppDelegateHeader = ''' +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end +'''; + + @visibleForTesting + static const originalObjCAppDelegateImplementation = ''' +#import "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end +'''; + + @visibleForTesting + static const newObjCAppDelegateHeader = ''' +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end +'''; + + @visibleForTesting + static const newObjCAppDelegateImplementation = ''' +#import "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +- (void)didInitializeImplicitFlutterEngine:(NSObject*)engineBridge { + [GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry]; +} + +@end +'''; + + @visibleForTesting + static const newSwiftAppDelegate = ''' +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } + + func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) { + GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry) + } +} +'''; + + @override + Future migrate() async { + if (!_isMigrationFeatureEnabled) { + return; + } + + // If we can't find their Info.plist, they will need to migrate manually and update the config + // to no longer see this warning. + if (!_project.defaultHostInfoPlist.existsSync()) { + logger.printTrace('UIScene migration: unable to find Info.plist'); + _printErrorMessage(withConfigInstructions: true); + return; + } + + // Consider it already migrated if the Info.plist has UIApplicationSceneManifest settings. + final String originalInfoPlist = _project.defaultHostInfoPlist.readAsStringSync(); + if (originalInfoPlist.contains('UIApplicationSceneManifest')) { + return; + } + + // If UIMainStoryboardFile is missing or not "Main", don't auto-migrate. + try { + final String? storyboardName = _plistParser.getValueFromFile( + _project.defaultHostInfoPlist.path, + 'UIMainStoryboardFile', + ); + if (storyboardName == null || storyboardName != 'Main') { + logger.printTrace('UIScene migration: unable to find matching storyboard'); + _printErrorMessage(); + return; + } + } on ProcessException { + logger.printTrace('UIScene migration: unable to find matching storyboard'); + _printErrorMessage(); + return; + } + + final bool autoMigratedAppDelegate = _migrateAppDelegate(); + + var autoMigratedInfoPlist = false; + if (autoMigratedAppDelegate) { + autoMigratedInfoPlist = _migrateInfoPlist(); + } + + if (!autoMigratedAppDelegate || !autoMigratedInfoPlist) { + _printErrorMessage(); + return; + } + + logger.printStatus( + 'Finished migration to UIScene lifecycle. See https://flutter.dev/to/uiscene-migration for details.', + ); + } + + bool _migrateAppDelegate() { + if (_project.appDelegateSwift.existsSync()) { + final String projectAppDelegate = _project.appDelegateSwift.readAsStringSync().trim(); + if (projectAppDelegate == originalSwiftAppDelegate.trim() || + projectAppDelegate == secondaryOriginalSwiftAppDelegate.trim()) { + _project.appDelegateSwift.writeAsStringSync(newSwiftAppDelegate); + return true; + } + logger.printTrace('UIScene migration: AppDelegate does not match original template.'); + return false; + } else if (_project.appDelegateObjcImplementation.existsSync() && + _project.appDelegateObjcHeader.existsSync()) { + final String projectAppDelegateImplementation = _project.appDelegateObjcImplementation + .readAsStringSync() + .trim(); + final String projectAppDelegateHeader = _project.appDelegateObjcHeader + .readAsStringSync() + .trim(); + if (projectAppDelegateImplementation == originalObjCAppDelegateImplementation.trim() && + projectAppDelegateHeader == originalObjCAppDelegateHeader.trim()) { + _project.appDelegateObjcImplementation.writeAsStringSync(newObjCAppDelegateImplementation); + _project.appDelegateObjcHeader.writeAsStringSync(newObjCAppDelegateHeader); + return true; + } + logger.printTrace('UIScene migration: AppDelegate does not match original template.'); + return false; + } + logger.printTrace('UIScene migration: unable to find AppDelegate'); + return false; + } + + bool _migrateInfoPlist() { + if (_plistParser.insertKeyWithJson( + _project.defaultHostInfoPlist.path, + key: 'UIApplicationSceneManifest', + json: ''' +{ + "UIApplicationSupportsMultipleScenes": false, + "UISceneConfigurations": { + "UIWindowSceneSessionRoleApplication": [{ + "UISceneClassName": "UIWindowScene", + "UISceneDelegateClassName": "FlutterSceneDelegate", + "UISceneConfigurationName": "flutter", + "UISceneStoryboardFile": "Main" + }] + } +}''', + )) { + return true; + } + logger.printTrace('UIScene migration: unable to insert into Info.plist'); + return false; + } + + void _printErrorMessage({bool withConfigInstructions = false}) { + final buffer = StringBuffer(); + buffer.writeln( + 'To ensure your app continues to launch on upcoming iOS versions, UIScene lifecycle ' + 'support will soon be required. Please see https://flutter.dev/to/uiscene-migration ' + 'for the migration guide.', + ); + if (withConfigInstructions) { + buffer.writeln( + 'See https://flutter.dev/to/uiscene-migration/#hide-migration-warning for instructions to ' + 'hide this warning.', + ); + } + logger.printError(buffer.toString()); + } +} diff --git a/packages/flutter_tools/lib/src/xcode_project.dart b/packages/flutter_tools/lib/src/xcode_project.dart index afa85e5f48f67..c9a3e023e4129 100644 --- a/packages/flutter_tools/lib/src/xcode_project.dart +++ b/packages/flutter_tools/lib/src/xcode_project.dart @@ -461,9 +461,13 @@ def __lldb_init_module(debugger: lldb.SBDebugger, _): _editableDirectory.childDirectory('Runner').childFile('AppDelegate.swift'); /// The 'AppDelegate.m' file of the host app. This file might not exist if the app project uses Swift. - File get appDelegateObjc => + File get appDelegateObjcImplementation => _editableDirectory.childDirectory('Runner').childFile('AppDelegate.m'); + /// The 'AppDelegate.h' file of the host app. This file might not exist if the app project uses Swift. + File get appDelegateObjcHeader => + _editableDirectory.childDirectory('Runner').childFile('AppDelegate.h'); + File get infoPlist => _editableDirectory.childDirectory('Runner').childFile('Info.plist'); Directory get symlinks => _flutterLibRoot.childDirectory('.symlinks'); diff --git a/packages/flutter_tools/test/general.shard/flutter_validator_test.dart b/packages/flutter_tools/test/general.shard/flutter_validator_test.dart index 2f96ce4e71fd9..050bd3d6c6d16 100644 --- a/packages/flutter_tools/test/general.shard/flutter_validator_test.dart +++ b/packages/flutter_tools/test/general.shard/flutter_validator_test.dart @@ -821,6 +821,9 @@ class FakeFlutterFeatures extends FeatureFlags { @override bool get isLLDBDebuggingEnabled => _enabled; + @override + bool get isUISceneMigrationEnabled => _enabled; + @override final List allFeatures; diff --git a/packages/flutter_tools/test/general.shard/migrations/uiscene_migration_test.dart b/packages/flutter_tools/test/general.shard/migrations/uiscene_migration_test.dart new file mode 100644 index 0000000000000..d6de9f7af7a44 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/migrations/uiscene_migration_test.dart @@ -0,0 +1,439 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/ios/plist_parser.dart'; +import 'package:flutter_tools/src/migrations/uiscene_migration.dart'; + +import 'package:flutter_tools/src/project.dart'; +import 'package:test/fake.dart'; + +import '../../src/common.dart'; + +void main() { + group('UISceneMigration', () { + testWithoutContext('fails if Info.plist is not found', () async { + final logger = BufferLogger.test(); + final fileSystem = MemoryFileSystem.test(); + final plistParser = FakePlistParser(); + + final migration = UISceneMigration( + setupFakeIosProject(fileSystem), + logger, + plistParser: plistParser, + isMigrationFeatureEnabled: true, + ); + + await migration.migrate(); + expect(logger.traceText, contains('UIScene migration: unable to find Info.plist')); + expect( + logger.errorText, + contains( + 'To ensure your app continues to launch on upcoming iOS versions, UIScene lifecycle ' + 'support will soon be required. Please see https://flutter.dev/to/uiscene-migration ' + 'for the migration guide.\nSee https://flutter.dev/to/uiscene-migration/#hide-migration-warning' + ' for instructions to hide this warning.', + ), + ); + }); + + testWithoutContext('skips if already migrated', () async { + final logger = BufferLogger.test(); + final fileSystem = MemoryFileSystem.test(); + final plistParser = FakePlistParser(); + + final migration = UISceneMigration( + setupFakeIosProject(fileSystem, infoPlistContent: validMigratedInfoPlist), + logger, + isMigrationFeatureEnabled: true, + plistParser: plistParser, + ); + + await migration.migrate(); + expect(logger.traceText, isEmpty); + expect(logger.errorText, isEmpty); + }); + + testWithoutContext('skips if feature disabled', () async { + final logger = BufferLogger.test(); + final fileSystem = MemoryFileSystem.test(); + final plistParser = FakePlistParser(); + + final migration = UISceneMigration( + setupFakeIosProject(fileSystem), + logger, + plistParser: plistParser, + isMigrationFeatureEnabled: false, + ); + + await migration.migrate(); + expect(logger.traceText, isEmpty); + expect(logger.errorText, isEmpty); + }); + + testWithoutContext('Fails if unable to find storyboard', () async { + final logger = BufferLogger.test(); + final fileSystem = MemoryFileSystem.test(); + final plistParser = FakePlistParser(storyboardName: null); + + final migration = UISceneMigration( + setupFakeIosProject(fileSystem, infoPlistContent: ''), + logger, + isMigrationFeatureEnabled: true, + plistParser: plistParser, + ); + + await migration.migrate(); + expect(logger.traceText, contains('UIScene migration: unable to find matching storyboard')); + expect( + logger.errorText, + contains( + 'To ensure your app continues to launch on upcoming iOS versions, UIScene lifecycle ' + 'support will soon be required. Please see https://flutter.dev/to/uiscene-migration ' + 'for the migration guide.', + ), + ); + }); + + testWithoutContext('fails if storyboard does not match default', () async { + final logger = BufferLogger.test(); + final fileSystem = MemoryFileSystem.test(); + final plistParser = FakePlistParser(storyboardName: 'notMain'); + + final migration = UISceneMigration( + setupFakeIosProject(fileSystem, infoPlistContent: ''), + logger, + isMigrationFeatureEnabled: true, + plistParser: plistParser, + ); + + await migration.migrate(); + expect(logger.traceText, contains('UIScene migration: unable to find matching storyboard')); + expect( + logger.errorText, + contains( + 'To ensure your app continues to launch on upcoming iOS versions, UIScene lifecycle ' + 'support will soon be required. Please see https://flutter.dev/to/uiscene-migration ' + 'for the migration guide.', + ), + ); + }); + + group('for Swift', () { + testWithoutContext('fails if AppDelegate.swift is not exact match', () async { + final logger = BufferLogger.test(); + final fileSystem = MemoryFileSystem.test(); + final plistParser = FakePlistParser(); + + final migration = UISceneMigration( + setupFakeIosProject( + fileSystem, + infoPlistContent: validUnmigratedInfoPlist, + swiftAppDelegateConent: 'not matching content', + ), + logger, + isMigrationFeatureEnabled: true, + plistParser: plistParser, + ); + + await migration.migrate(); + expect( + logger.traceText, + contains('UIScene migration: AppDelegate does not match original template.'), + ); + expect( + logger.errorText, + contains( + 'To ensure your app continues to launch on upcoming iOS versions, UIScene lifecycle ' + 'support will soon be required. Please see https://flutter.dev/to/uiscene-migration ' + 'for the migration guide.', + ), + ); + }); + + testWithoutContext('replaces if AppDelegate.swift is exact match', () async { + final logger = BufferLogger.test(); + final fileSystem = MemoryFileSystem.test(); + final plistParser = FakePlistParser(); + + final migration = UISceneMigration( + setupFakeIosProject( + fileSystem, + infoPlistContent: validUnmigratedInfoPlist, + swiftAppDelegateConent: UISceneMigration.originalSwiftAppDelegate, + ), + logger, + isMigrationFeatureEnabled: true, + plistParser: plistParser, + ); + + await migration.migrate(); + expect(logger.traceText, isEmpty); + expect(logger.errorText, isEmpty); + expect(plistParser.insertKeyCalled, isTrue); + final File appDelegateSwift = fileSystem.systemTempDirectory.childFile('AppDelegate.swift'); + expect(appDelegateSwift.readAsStringSync(), equals(UISceneMigration.newSwiftAppDelegate)); + }); + }); + + group('for ObjC', () { + testWithoutContext('fails if AppDelegate.h is not exact match', () async { + final logger = BufferLogger.test(); + final fileSystem = MemoryFileSystem.test(); + final plistParser = FakePlistParser(); + + final migration = UISceneMigration( + setupFakeIosProject( + fileSystem, + infoPlistContent: validUnmigratedInfoPlist, + objcAppDelegateHeaderContent: 'not a match', + objcAppDelegateContent: UISceneMigration.originalObjCAppDelegateImplementation, + ), + logger, + isMigrationFeatureEnabled: true, + plistParser: plistParser, + ); + + await migration.migrate(); + expect( + logger.traceText, + contains('UIScene migration: AppDelegate does not match original template.'), + ); + expect( + logger.errorText, + contains( + 'To ensure your app continues to launch on upcoming iOS versions, UIScene lifecycle ' + 'support will soon be required. Please see https://flutter.dev/to/uiscene-migration ' + 'for the migration guide.', + ), + ); + }); + + testWithoutContext('fails if AppDelegate.m is not exact match', () async { + final logger = BufferLogger.test(); + final fileSystem = MemoryFileSystem.test(); + final plistParser = FakePlistParser(); + + final migration = UISceneMigration( + setupFakeIosProject( + fileSystem, + infoPlistContent: validUnmigratedInfoPlist, + objcAppDelegateHeaderContent: UISceneMigration.originalObjCAppDelegateHeader, + objcAppDelegateContent: 'not a match', + ), + logger, + isMigrationFeatureEnabled: true, + plistParser: plistParser, + ); + + await migration.migrate(); + expect( + logger.traceText, + contains('UIScene migration: AppDelegate does not match original template.'), + ); + expect( + logger.errorText, + contains( + 'To ensure your app continues to launch on upcoming iOS versions, UIScene lifecycle ' + 'support will soon be required. Please see https://flutter.dev/to/uiscene-migration ' + 'for the migration guide.', + ), + ); + }); + + testWithoutContext('replaces if AppDelegate.h and AppDelegate.m is exact match', () async { + final logger = BufferLogger.test(); + final fileSystem = MemoryFileSystem.test(); + final plistParser = FakePlistParser(); + + final migration = UISceneMigration( + setupFakeIosProject( + fileSystem, + infoPlistContent: validUnmigratedInfoPlist, + objcAppDelegateHeaderContent: UISceneMigration.originalObjCAppDelegateHeader, + objcAppDelegateContent: UISceneMigration.originalObjCAppDelegateImplementation, + ), + logger, + isMigrationFeatureEnabled: true, + plistParser: plistParser, + ); + + await migration.migrate(); + expect(logger.traceText, isEmpty); + expect(logger.errorText, isEmpty); + expect(plistParser.insertKeyCalled, isTrue); + final File appDelegateHeader = fileSystem.systemTempDirectory.childFile('AppDelegate.h'); + expect( + appDelegateHeader.readAsStringSync(), + equals(UISceneMigration.newObjCAppDelegateHeader), + ); + final File appDelegateImplementation = fileSystem.systemTempDirectory.childFile( + 'AppDelegate.m', + ); + expect( + appDelegateImplementation.readAsStringSync(), + equals(UISceneMigration.newObjCAppDelegateImplementation), + ); + }); + }); + + testWithoutContext("fails if can't insert into Info.plist", () async { + final logger = BufferLogger.test(); + final fileSystem = MemoryFileSystem.test(); + final plistParser = FakePlistParser(insertKeySucceeds: false); + + final migration = UISceneMigration( + setupFakeIosProject( + fileSystem, + infoPlistContent: validUnmigratedInfoPlist, + objcAppDelegateHeaderContent: UISceneMigration.originalObjCAppDelegateHeader, + objcAppDelegateContent: UISceneMigration.originalObjCAppDelegateImplementation, + ), + logger, + isMigrationFeatureEnabled: true, + plistParser: plistParser, + ); + + await migration.migrate(); + expect(logger.traceText, contains('UIScene migration: unable to insert into Info.plist')); + expect( + logger.errorText, + contains( + 'To ensure your app continues to launch on upcoming iOS versions, UIScene lifecycle ' + 'support will soon be required. Please see https://flutter.dev/to/uiscene-migration ' + 'for the migration guide.', + ), + ); + }); + }); +} + +FakeIosProject setupFakeIosProject( + MemoryFileSystem fileSystem, { + String? infoPlistContent, + String? swiftAppDelegateConent, + String? objcAppDelegateHeaderContent, + String? objcAppDelegateContent, +}) { + final File infoPlist = fileSystem.systemTempDirectory.childFile('Info.plist'); + if (infoPlistContent != null) { + infoPlist + ..createSync() + ..writeAsStringSync(infoPlistContent); + } + final File appDelegateSwift = fileSystem.systemTempDirectory.childFile('AppDelegate.swift'); + if (swiftAppDelegateConent != null) { + appDelegateSwift + ..createSync() + ..writeAsStringSync(swiftAppDelegateConent); + } + + final File appDelegateObjcImplementation = fileSystem.systemTempDirectory.childFile( + 'AppDelegate.m', + ); + if (objcAppDelegateContent != null) { + appDelegateObjcImplementation + ..createSync() + ..writeAsStringSync(objcAppDelegateContent); + } + final File appDelegateObjcHeader = fileSystem.systemTempDirectory.childFile('AppDelegate.h'); + if (objcAppDelegateHeaderContent != null) { + appDelegateObjcHeader + ..createSync() + ..writeAsStringSync(objcAppDelegateHeaderContent); + } + + return FakeIosProject( + defaultHostInfoPlist: infoPlist, + appDelegateSwift: appDelegateSwift, + appDelegateObjcImplementation: appDelegateObjcImplementation, + appDelegateObjcHeader: appDelegateObjcHeader, + ); +} + +const validUnmigratedInfoPlist = ''' + + + + + UIMainStoryboardFile + Main + +'''; + +const validMigratedInfoPlist = ''' + + + + + UIMainStoryboardFile + Main + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneDelegateClassName + FlutterSceneDelegate + UISceneConfigurationName + flutter + UISceneStoryboardFile + Main + + + + + +'''; + +class FakeIosProject extends Fake implements IosProject { + FakeIosProject({ + required this.defaultHostInfoPlist, + required this.appDelegateSwift, + required this.appDelegateObjcImplementation, + required this.appDelegateObjcHeader, + }); + + @override + File defaultHostInfoPlist; + + @override + File appDelegateSwift; + + @override + File appDelegateObjcImplementation; + + @override + File appDelegateObjcHeader; +} + +class FakePlistParser extends Fake implements PlistParser { + FakePlistParser({this.storyboardName = 'Main', this.insertKeySucceeds = true}); + + final String? storyboardName; + final bool insertKeySucceeds; + var insertKeyCalled = false; + + @override + T? getValueFromFile(String plistFilePath, String key) { + if (key == 'UIMainStoryboardFile') { + return storyboardName as T?; + } + return null; + } + + @override + bool insertKeyWithJson(String plistPath, {required String key, required String json}) { + insertKeyCalled = true; + return insertKeySucceeds; + } +} diff --git a/packages/flutter_tools/test/integration.shard/uiscene_migration_test.dart b/packages/flutter_tools/test/integration.shard/uiscene_migration_test.dart new file mode 100644 index 0000000000000..1572158b90f8f --- /dev/null +++ b/packages/flutter_tools/test/integration.shard/uiscene_migration_test.dart @@ -0,0 +1,375 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:file_testing/file_testing.dart'; +import 'package:flutter_tools/src/base/error_handling_io.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/io.dart'; +import 'package:flutter_tools/src/migrations/uiscene_migration.dart'; + +import '../src/common.dart'; +import 'test_utils.dart'; + +void main() { + test( + 'Auto migrate Swift app', + () async { + final Directory workingDirectory = fileSystem.systemTempDirectory.createTempSync( + 'uiscene_migration.', + ); + final String workingDirectoryPath = workingDirectory.path; + + addTearDown(() async { + await _disableUISceneMigration(flutterBin, workingDirectoryPath); + ErrorHandlingFileSystem.deleteIfExists(workingDirectory, recursive: true); + }); + + await _enableUISceneMigration(flutterBin, workingDirectoryPath); + + // Create app + final String appDirectoryPath = await _createApp(flutterBin, workingDirectoryPath); + + // Replace with old template + final File appDelegate = fileSystem.file( + fileSystem.path.join(appDirectoryPath, 'ios', 'Runner', 'AppDelegate.swift'), + ); + final File infoPlist = fileSystem.file( + fileSystem.path.join(appDirectoryPath, 'ios', 'Runner', 'Info.plist'), + ); + expect(appDelegate, exists); + expect(infoPlist, exists); + + // Make sure it migrates and builds + await _buildApp(flutterBin, appDirectoryPath); + expect(appDelegate.readAsStringSync(), UISceneMigration.newSwiftAppDelegate); + expect( + infoPlist.readAsStringSync(), + _newInfoPlistTemplate('Uiscene Migration App', 'uiscene_migration_app'), + ); + + // Turn off config + await _disableUISceneMigration(flutterBin, workingDirectoryPath); + + // Replace with old template + final String oldInfoPlistTemplate = _oldInfoPlistTemplate( + 'Uiscene Migration App', + 'uiscene_migration_app', + ); + appDelegate.writeAsStringSync(_oldSwiftAppDelegate); + infoPlist.writeAsStringSync(oldInfoPlistTemplate); + + // Make sure it doesn't migrate + await _buildApp(flutterBin, appDirectoryPath); + + expect(appDelegate.readAsStringSync(), _oldSwiftAppDelegate); + expect(infoPlist.readAsStringSync(), oldInfoPlistTemplate); + }, + skip: !platform.isMacOS, // [intended] macOS builds only work on macos. + ); + + test( + 'Auto migrate ObjC app', + () async { + final String appDirectoryPath = fileSystem.path.join( + getFlutterRoot(), + 'dev', + 'integration_tests', + 'spell_check', + ); + + final File appDelegateHeader = fileSystem.file( + fileSystem.path.join(appDirectoryPath, 'ios', 'Runner', 'AppDelegate.h'), + ); + final File appDelegateImpl = fileSystem.file( + fileSystem.path.join(appDirectoryPath, 'ios', 'Runner', 'AppDelegate.m'), + ); + final File infoPlist = fileSystem.file( + fileSystem.path.join(appDirectoryPath, 'ios', 'Runner', 'Info.plist'), + ); + expect(appDelegateHeader, exists); + expect(appDelegateImpl, exists); + expect(infoPlist, exists); + + final String originalAppDelegateHeader = appDelegateHeader.readAsStringSync(); + final String originalAppDelegateImpl = appDelegateImpl.readAsStringSync(); + final String originalInfoPlist = infoPlist.readAsStringSync(); + + addTearDown(() async { + await _disableUISceneMigration(flutterBin, appDirectoryPath); + appDelegateHeader.writeAsStringSync(originalAppDelegateHeader); + appDelegateImpl.writeAsStringSync(originalAppDelegateImpl); + infoPlist.writeAsStringSync(originalInfoPlist); + }); + + await _enableUISceneMigration(flutterBin, appDirectoryPath); + + // Remove license so will match + appDelegateHeader.writeAsStringSync( + originalAppDelegateHeader.replaceAll(flutter2014License, ''), + ); + appDelegateImpl.writeAsStringSync(originalAppDelegateImpl.replaceAll(flutter2014License, '')); + + // Make sure it migrates and builds + await _buildApp(flutterBin, appDirectoryPath); + expect(appDelegateHeader.readAsStringSync(), UISceneMigration.newObjCAppDelegateHeader); + expect(appDelegateImpl.readAsStringSync(), UISceneMigration.newObjCAppDelegateImplementation); + expect(infoPlist.readAsStringSync(), _newInfoPlistTemplate('Spell Check', 'spell_check')); + + // Turn off config + await _disableUISceneMigration(flutterBin, appDirectoryPath); + + // Replace with old template + appDelegateHeader.writeAsStringSync(originalAppDelegateHeader); + appDelegateImpl.writeAsStringSync(originalAppDelegateImpl); + infoPlist.writeAsStringSync(originalInfoPlist); + + // Make sure it doesn't migrate + await _buildApp(flutterBin, appDirectoryPath); + + expect(appDelegateHeader.readAsStringSync(), originalAppDelegateHeader); + expect(appDelegateImpl.readAsStringSync(), originalAppDelegateImpl); + expect(infoPlist.readAsStringSync(), originalInfoPlist); + }, + skip: !platform.isMacOS, // [intended] macOS builds only work on macos. + ); +} + +const flutter2014License = ''' +// 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. +'''; + +String _oldInfoPlistTemplate(String titleCaseProjectName, String projectName) { + return ''' + + + + + CFBundleDevelopmentRegion + \$(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + $titleCaseProjectName + CFBundleExecutable + \$(EXECUTABLE_NAME) + CFBundleIdentifier + \$(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $projectName + CFBundlePackageType + APPL + CFBundleShortVersionString + \$(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + \$(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + +'''; +} + +const _oldSwiftAppDelegate = r''' +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} +'''; + +String _newInfoPlistTemplate(String titleCaseProjectName, String projectName) { + return ''' + + + + + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + \$(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + $titleCaseProjectName + CFBundleExecutable + \$(EXECUTABLE_NAME) + CFBundleIdentifier + \$(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $projectName + CFBundlePackageType + APPL + CFBundleShortVersionString + \$(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + \$(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + flutter + UISceneDelegateClassName + FlutterSceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + +'''; +} + +Future _createApp( + String flutterBin, + String workingDirectory, { + List options = const [], +}) async { + const appName = 'uiscene_migration_app'; + final ProcessResult result = await processManager.run([ + flutterBin, + ...getLocalEngineArguments(), + 'create', + '--org', + 'io.flutter.devicelab', + '--platforms=ios', + ...options, + appName, + ], workingDirectory: workingDirectory); + + expect( + result.exitCode, + 0, + reason: + 'Failed to create app: \n' + 'stdout: \n${result.stdout}\n' + 'stderr: \n${result.stderr}\n', + ); + + return fileSystem.path.join(workingDirectory, appName); +} + +Future _buildApp( + String flutterBin, + String appDirectory, { + List options = const [], +}) async { + final ProcessResult result = await processManager.run([ + flutterBin, + ...getLocalEngineArguments(), + 'build', + 'ios', + ...options, + ], workingDirectory: appDirectory); + + expect( + result.exitCode, + 0, + reason: + 'Failed to build app: \n' + 'stdout: \n${result.stdout}\n' + 'stderr: \n${result.stderr}\n', + ); +} + +Future _enableUISceneMigration(String flutterBin, String workingDirectory) async { + final ProcessResult result = await processManager.run([ + flutterBin, + ...getLocalEngineArguments(), + 'config', + '--enable-uiscene-migration', + '-v', + ], workingDirectory: workingDirectory); + expect( + result.exitCode, + 0, + reason: + 'Failed to enable Swift Package Manager: \n' + 'stdout: \n${result.stdout}\n' + 'stderr: \n${result.stderr}\n', + ); +} + +Future _disableUISceneMigration(String flutterBin, String workingDirectory) async { + final ProcessResult result = await processManager.run([ + flutterBin, + ...getLocalEngineArguments(), + 'config', + '--no-enable-uiscene-migration', + '-v', + ], workingDirectory: workingDirectory); + expect( + result.exitCode, + 0, + reason: + 'Failed to enable Swift Package Manager: \n' + 'stdout: \n${result.stdout}\n' + 'stderr: \n${result.stderr}\n', + ); +} diff --git a/packages/flutter_tools/test/src/fakes.dart b/packages/flutter_tools/test/src/fakes.dart index 8f728bbaeddf8..fe3e8730e1e45 100644 --- a/packages/flutter_tools/test/src/fakes.dart +++ b/packages/flutter_tools/test/src/fakes.dart @@ -369,6 +369,11 @@ class FakePlistParser implements PlistParser { setProperty(key, value); return true; } + + @override + bool insertKeyWithJson(String plistFilePath, {required String key, required String json}) { + return false; + } } class FakeBotDetector implements BotDetector { @@ -526,6 +531,7 @@ class TestFeatureFlags implements FeatureFlags { this.isOmitLegacyVersionFileEnabled = false, this.isWindowingEnabled = false, this.isLLDBDebuggingEnabled = false, + this.isUISceneMigrationEnabled = false, }); @override @@ -573,6 +579,9 @@ class TestFeatureFlags implements FeatureFlags { @override final bool isLLDBDebuggingEnabled; + @override + final bool isUISceneMigrationEnabled; + @override bool isEnabled(Feature feature) { return switch (feature) { @@ -590,6 +599,7 @@ class TestFeatureFlags implements FeatureFlags { omitLegacyVersionFile => isOmitLegacyVersionFileEnabled, windowingFeature => isWindowingEnabled, lldbDebugging => isLLDBDebuggingEnabled, + uiSceneMigration => isUISceneMigrationEnabled, _ => false, }; } @@ -611,6 +621,7 @@ class TestFeatureFlags implements FeatureFlags { omitLegacyVersionFile, windowingFeature, lldbDebugging, + uiSceneMigration, ]; @override From bdbffa11cd4b0b5848265b6094a17eeeb6b16b78 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Tue, 7 Oct 2025 15:14:21 +1300 Subject: [PATCH 090/204] Fix code style in Linux embedder template (#176256) As noticed by Gemini in another PR --- .../linux.tmpl/runner/my_application.cc.tmpl | 34 +++++++++++-------- .../app/linux.tmpl/runner/my_application.h | 5 ++- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/flutter_tools/templates/app/linux.tmpl/runner/my_application.cc.tmpl b/packages/flutter_tools/templates/app/linux.tmpl/runner/my_application.cc.tmpl index 5ba23fe0b6d07..3b8611caea9fa 100644 --- a/packages/flutter_tools/templates/app/linux.tmpl/runner/my_application.cc.tmpl +++ b/packages/flutter_tools/templates/app/linux.tmpl/runner/my_application.cc.tmpl @@ -15,8 +15,7 @@ struct _MyApplication { G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) // Called when first Flutter frame received. -static void first_frame_cb(MyApplication* self, FlView *view) -{ +static void first_frame_cb(MyApplication* self, FlView* view) { gtk_widget_show(gtk_widget_get_toplevel(GTK_WIDGET(view))); } @@ -56,11 +55,13 @@ static void my_application_activate(GApplication* application) { gtk_window_set_default_size(window, 1280, 720); g_autoptr(FlDartProject) project = fl_dart_project_new(); - fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + fl_dart_project_set_dart_entrypoint_arguments( + project, self->dart_entrypoint_arguments); FlView* view = fl_view_new(project); GdkRGBA background_color; - // Background defaults to black, override it here if necessary, e.g. #00000000 for transparent. + // Background defaults to black, override it here if necessary, e.g. #00000000 + // for transparent. gdk_rgba_parse(&background_color, "#000000"); fl_view_set_background_color(view, &background_color); gtk_widget_show(GTK_WIDGET(view)); @@ -68,7 +69,8 @@ static void my_application_activate(GApplication* application) { // Show the window when Flutter renders. // Requires the view to be realized so we can start rendering. - g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), self); + g_signal_connect_swapped(view, "first-frame", G_CALLBACK(first_frame_cb), + self); gtk_widget_realize(GTK_WIDGET(view)); fl_register_plugins(FL_PLUGIN_REGISTRY(view)); @@ -77,16 +79,18 @@ static void my_application_activate(GApplication* application) { } // Implements GApplication::local_command_line. -static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { +static gboolean my_application_local_command_line(GApplication* application, + gchar*** arguments, + int* exit_status) { MyApplication* self = MY_APPLICATION(application); // Strip out the first argument as it is the binary name. self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); g_autoptr(GError) error = nullptr; if (!g_application_register(application, nullptr, &error)) { - g_warning("Failed to register: %s", error->message); - *exit_status = 1; - return TRUE; + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; } g_application_activate(application); @@ -97,7 +101,7 @@ static gboolean my_application_local_command_line(GApplication* application, gch // Implements GApplication::startup. static void my_application_startup(GApplication* application) { - //MyApplication* self = MY_APPLICATION(object); + // MyApplication* self = MY_APPLICATION(object); // Perform any actions required at application startup. @@ -106,7 +110,7 @@ static void my_application_startup(GApplication* application) { // Implements GApplication::shutdown. static void my_application_shutdown(GApplication* application) { - //MyApplication* self = MY_APPLICATION(object); + // MyApplication* self = MY_APPLICATION(object); // Perform any actions required at application shutdown. @@ -122,7 +126,8 @@ static void my_application_dispose(GObject* object) { static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; - G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->local_command_line = + my_application_local_command_line; G_APPLICATION_CLASS(klass)->startup = my_application_startup; G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; @@ -138,7 +143,6 @@ MyApplication* my_application_new() { g_set_prgname(APPLICATION_ID); return MY_APPLICATION(g_object_new(my_application_get_type(), - "application-id", APPLICATION_ID, - "flags", G_APPLICATION_NON_UNIQUE, - nullptr)); + "application-id", APPLICATION_ID, "flags", + G_APPLICATION_NON_UNIQUE, nullptr)); } diff --git a/packages/flutter_tools/templates/app/linux.tmpl/runner/my_application.h b/packages/flutter_tools/templates/app/linux.tmpl/runner/my_application.h index 72271d5e41701..db16367a77d06 100644 --- a/packages/flutter_tools/templates/app/linux.tmpl/runner/my_application.h +++ b/packages/flutter_tools/templates/app/linux.tmpl/runner/my_application.h @@ -3,7 +3,10 @@ #include -G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, +G_DECLARE_FINAL_TYPE(MyApplication, + my_application, + MY, + APPLICATION, GtkApplication) /** From 5c66c53a3159eb1001ac66b18a73ecff384a8e95 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Mon, 6 Oct 2025 21:40:02 -0500 Subject: [PATCH 091/204] Handle FlutterEngine registration when embedded in Multi-Scene apps (#176490) When Multi-Scene is enabled, Flutter cannot automatically associate the engine with a scene during the scene connection phase in most cases. However, the `scene:willConnectToSession:options:` event is critical for some Flutter plugins that rely on scene connection options (the scene-equivalent of application launch options). To workaround this, this PR adds two things: * Adds a way to manually register a `FlutterEngine` with a `FlutterPluginSceneLifeCycleDelegate`. Manually registered engines must also be manually unregistered. * This use case is for add to app * Automatically registers a `FlutterEngine` if the `rootViewController` of the scene is a `FlutterViewController` * This use case is for if a Flutter app (not add-to-app) were to enable multi-scene since the `rootViewController` of the storyboard is a `FlutterViewController` This PR also makes a fix for when a `FlutterView` is removed from the scene: * If a `FlutterView` is removed from a scene, keep the `FlutterEngine` associated with the scene until it is assigned to a new scene ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../bin/tasks/module_uiscene_test_ios.dart | 150 +++++--- .../AppDelegate-FlutterAppDelegate.swift | 15 + .../Info-MultiSceneEnabled-NoStoryboard.plist | 23 ++ .../Info-MultiSceneEnabled-Storyboard.plist | 25 ++ ...ceneDelegate-MultiScene-NoStoryboard.swift | 30 ++ ...rSceneDelegate-MultiScene-Storyboard.swift | 26 ++ .../UITests-SceneEvents-MultiScenes.swift | 131 +++++++ ...EngineFromSceneDelegate-NoStoryboard.swift | 28 ++ ...erEngineFromSceneDelegate-Storyboard.swift | 19 + .../xcode_swiftui.xcodeproj/project.pbxproj | 4 +- .../framework/Headers/FlutterSceneDelegate.h | 5 +- .../framework/Headers/FlutterSceneLifeCycle.h | 43 ++- .../framework/Source/FlutterEngine_Internal.h | 3 + .../framework/Source/FlutterSceneDelegate.mm | 9 + .../Source/FlutterSceneDelegateTest.m | 30 ++ .../framework/Source/FlutterSceneLifeCycle.mm | 175 ++++++--- .../Source/FlutterSceneLifeCycleTest.mm | 346 +++++++++++++----- .../Source/FlutterSceneLifeCycle_Internal.h | 16 +- .../Source/FlutterSceneLifeCycle_Test.h | 10 +- .../ios/framework/Source/FlutterView.mm | 38 +- .../ios/framework/Source/FlutterViewTest.mm | 70 ++-- 21 files changed, 945 insertions(+), 251 deletions(-) create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/AppDelegate-FlutterAppDelegate.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/Info-MultiSceneEnabled-NoStoryboard.plist create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/Info-MultiSceneEnabled-Storyboard.plist create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/SceneDelegate-FlutterSceneDelegate-MultiScene-NoStoryboard.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/SceneDelegate-FlutterSceneDelegate-MultiScene-Storyboard.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-MultiScenes.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/ViewController-FlutterEngineFromSceneDelegate-NoStoryboard.swift create mode 100644 dev/integration_tests/ios_add2app_uiscene/native/ViewController-FlutterEngineFromSceneDelegate-Storyboard.swift diff --git a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart index b4bfa1fce818a..144d0a1e8e2dd 100644 --- a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart +++ b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart @@ -79,58 +79,62 @@ Future main(List args) async { ); bool testFailed = false; - await testWithNewIOSSimulator('TestAdd2AppSim', (String deviceId) async { - for (final XcodeProjectType xcodeProjectType in projectTypesToTest) { - final (String xcodeProjectName, Directory xcodeProjectDir) = await _createNativeApp( - destinationDir: destinationDir, - templatesDir: templatesDir, - xcodeProjectType: xcodeProjectType, - ); - - simulatorDeviceId = deviceId; - final Scenarios scenarios = Scenarios(); - final Map> scenariosMap = scenarios.scenarios( - xcodeProjectType, - ); - for (final String scenarioName in scenariosMap.keys) { - if (testName != null && scenarioName != testName) { - continue; - } - final List replacements = FileReplacements.fromScenario( - scenariosMap[scenarioName]!, + await testWithNewIOSSimulator( + 'TestAdd2AppSim', + deviceTypeId: 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-3rd-generation', + (String deviceId) async { + for (final XcodeProjectType xcodeProjectType in projectTypesToTest) { + final (String xcodeProjectName, Directory xcodeProjectDir) = await _createNativeApp( + destinationDir: destinationDir, templatesDir: templatesDir, - xcodeProjectDir: xcodeProjectDir, - pluginDir: pluginDir, - appDir: appDir, + xcodeProjectType: xcodeProjectType, ); - for (final FileReplacements replacement in replacements) { - replacement.replace(); - } - - section('Test Scenario $scenarioName'); - - await _installPlugins(appDir: appDir, xcodeProjectDir: xcodeProjectDir); - final int result = await _testNativeApp( - deviceId: simulatorDeviceId!, - scenarioName: scenarioName, - templatesDir: templatesDir, - xcodeProjectDir: xcodeProjectDir, - xcodeProjectName: xcodeProjectName, + simulatorDeviceId = deviceId; + final Scenarios scenarios = Scenarios(); + final Map> scenariosMap = scenarios.scenarios( + xcodeProjectType, ); - if (result != 0) { - testFailed = true; - } + for (final String scenarioName in scenariosMap.keys) { + if (testName != null && scenarioName != testName) { + continue; + } + final List replacements = FileReplacements.fromScenario( + scenariosMap[scenarioName]!, + templatesDir: templatesDir, + xcodeProjectDir: xcodeProjectDir, + pluginDir: pluginDir, + appDir: appDir, + ); - // Reset files to original between scenarios unless we're targetting a specific test. - if (testName == null) { for (final FileReplacements replacement in replacements) { - replacement.reset(); + replacement.replace(); + } + + section('Test Scenario $scenarioName'); + + await _installPlugins(appDir: appDir, xcodeProjectDir: xcodeProjectDir); + final int result = await _testNativeApp( + deviceId: simulatorDeviceId!, + scenarioName: scenarioName, + templatesDir: templatesDir, + xcodeProjectDir: xcodeProjectDir, + xcodeProjectName: xcodeProjectName, + ); + if (result != 0) { + testFailed = true; + } + + // Reset files to original between scenarios unless we're targetting a specific test. + if (testName == null) { + for (final FileReplacements replacement in replacements) { + replacement.reset(); + } } } } - } - }); + }, + ); if (testFailed) { return TaskResult.failure( @@ -436,6 +440,7 @@ class Scenarios { r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-AppNotMigrated.swift': r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', }, + ...multiSceneScenarios, // When using an implicit FlutterEngine created by the storyboard, we expect plugins to // receive application launch events and scene events. @@ -554,6 +559,65 @@ class Scenarios { }, }; + late Map> multiSceneScenarios = >{ + // When multi scene is enabled and the rootViewController is a FlutterViewController, we + // expect all scene events without manual registration. + 'MultiSceneEnabled-FlutterSceneDelegate-RootViewController': { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/Info-MultiSceneEnabled-Storyboard.plist': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/Info.plist', + r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', + r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/Base.lproj/Main.storyboard', + r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/UITests-SceneEvents.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + }, + + // When multi scene is enabled and the ViewController is created programatically with a + // manually registered FlutterEngine, we expect all scene events. + 'MultiSceneEnabled-FlutterSceneDelegate-ManualRegistration-NoStoryboard': { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/Info-MultiSceneEnabled-NoStoryboard.plist': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/Info.plist', + r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', + r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate-MultiScene-NoStoryboard.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$TEMPLATE_DIR/native/ViewController-FlutterEngineFromSceneDelegate-NoStoryboard.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/ViewController.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + }, + + // When multi scene is enabled and the ViewController is created via Storyboard with a + // manually registered FlutterEngine, we expect all scene events. + 'MultiSceneEnabled-FlutterSceneDelegate-ManualRegistration-Storyboard': { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/Info-MultiSceneEnabled-Storyboard.plist': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/Info.plist', + r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', + r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate-MultiScene-Storyboard.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$TEMPLATE_DIR/native/ViewController-FlutterEngineFromSceneDelegate-Storyboard.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swift/ViewController.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift': + r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + }, + }; + late Map sharedLifecycleFiles = { ...sharedAppLifecycleFiles, ...sharedPluginLifecycleFiles, diff --git a/dev/integration_tests/ios_add2app_uiscene/native/AppDelegate-FlutterAppDelegate.swift b/dev/integration_tests/ios_add2app_uiscene/native/AppDelegate-FlutterAppDelegate.swift new file mode 100644 index 0000000000000..6bffa611ddaa7 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/AppDelegate-FlutterAppDelegate.swift @@ -0,0 +1,15 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import UIKit +import Flutter +import FlutterPluginRegistrant + +@main +class AppDelegate: FlutterAppDelegate { + override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + GeneratedPluginRegistrant.register(with: self); + return super.application(application, didFinishLaunchingWithOptions: launchOptions); + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/Info-MultiSceneEnabled-NoStoryboard.plist b/dev/integration_tests/ios_add2app_uiscene/native/Info-MultiSceneEnabled-NoStoryboard.plist new file mode 100644 index 0000000000000..0b0521734845b --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/Info-MultiSceneEnabled-NoStoryboard.plist @@ -0,0 +1,23 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + + + + + + diff --git a/dev/integration_tests/ios_add2app_uiscene/native/Info-MultiSceneEnabled-Storyboard.plist b/dev/integration_tests/ios_add2app_uiscene/native/Info-MultiSceneEnabled-Storyboard.plist new file mode 100644 index 0000000000000..8963569056c03 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/Info-MultiSceneEnabled-Storyboard.plist @@ -0,0 +1,25 @@ + + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + + diff --git a/dev/integration_tests/ios_add2app_uiscene/native/SceneDelegate-FlutterSceneDelegate-MultiScene-NoStoryboard.swift b/dev/integration_tests/ios_add2app_uiscene/native/SceneDelegate-FlutterSceneDelegate-MultiScene-NoStoryboard.swift new file mode 100644 index 0000000000000..e1a024c0ad8ad --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/SceneDelegate-FlutterSceneDelegate-MultiScene-NoStoryboard.swift @@ -0,0 +1,30 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import Flutter +import FlutterPluginRegistrant +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + let flutterEngine = FlutterEngine(name: "my flutter engine") + + override func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + // Confirm the scene is a window scene in iOS or iPadOS. + guard let windowScene = scene as? UIWindowScene else { return } + window = UIWindow(windowScene: windowScene) + + flutterEngine.run() + GeneratedPluginRegistrant.register(with: flutterEngine) + self.registerSceneLifeCycle(with: flutterEngine) + let viewController = ViewController(engine: flutterEngine) + + window?.rootViewController = viewController + window?.makeKeyAndVisible() + super.scene(scene, willConnectTo: session, options: connectionOptions) + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/SceneDelegate-FlutterSceneDelegate-MultiScene-Storyboard.swift b/dev/integration_tests/ios_add2app_uiscene/native/SceneDelegate-FlutterSceneDelegate-MultiScene-Storyboard.swift new file mode 100644 index 0000000000000..63073666e5f14 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/SceneDelegate-FlutterSceneDelegate-MultiScene-Storyboard.swift @@ -0,0 +1,26 @@ +// 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 Flutter +import FlutterPluginRegistrant +import UIKit + +class SceneDelegate: FlutterSceneDelegate { + let flutterEngine = FlutterEngine(name: "my flutter engine") + + override func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + flutterEngine.run() + GeneratedPluginRegistrant.register(with: flutterEngine) + self.registerSceneLifeCycle(with: flutterEngine) + + if let viewController = window?.rootViewController as? ViewController { + viewController.flutterEngine = flutterEngine + } + super.scene(scene, willConnectTo: session, options: connectionOptions) + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-MultiScenes.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-MultiScenes.swift new file mode 100644 index 0000000000000..40a355ea4087c --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-MultiScenes.swift @@ -0,0 +1,131 @@ +// 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 XCTest + +final class xcode_uikit_swiftUITests: XCTestCase { + + override func setUpWithError() throws { + continueAfterFailure = false + } + + @MainActor + @available(iOS 26.0, *) + func testMultipleScenes() throws { + let app = XCUIApplication() + app.terminate() + app.launch() + + // Resize app so it's not full screen + + let springboardApp = XCUIApplication( + bundleIdentifier: "com.apple.springboard" + ) + let resizer = springboardApp.otherElements["resize-grabber"].firstMatch + let start = resizer.coordinate(withNormalizedOffset: CGVectorMake(0, 0)) + let end = resizer.coordinate(withNormalizedOffset: CGVectorMake(-3, -3)) + start.press(forDuration: 1, thenDragTo: end) + + // Click button to create a new scene + let createSceneButton = app.buttons["New Scene"].firstMatch + XCTAssertTrue(createSceneButton.waitForExistence(timeout: 5)) + createSceneButton.tap() + + // Minimize one scene + springboardApp.buttons[ + "window-controls:io.flutter.devicelab.xcode-uikit-swift" + ].firstMatch.tap() + springboardApp.buttons["Minimize-button"].firstMatch.tap() + + // Validate lifecycle events of original scene do not contain background event + let buttons = app.buttons.matching(identifier: "Get Lifecycle Events") + let originalSceneButton = buttons.element(boundBy: 1) + originalSceneButton.tap() + let expectedOriginalSceneEvents = [ + "sceneWillConnect", + "sceneWillEnterForeground", "sceneDidBecomeActive", + "sceneWillResignActive", "sceneDidBecomeActive", + "sceneWillResignActive", "sceneDidBecomeActive", + ] + let originalSceneEventsPredicate = NSPredicate( + format: "label == %@", + expectedOriginalSceneEvents.joined(separator: "\n") + ) + let originalSceneEvents = app.staticTexts.element( + matching: originalSceneEventsPredicate + ) + XCTAssertTrue(originalSceneEvents.waitForExistence(timeout: 5)) + + // Reopen it + springboardApp.icons["xcode_uikit_swift"].firstMatch.tap() + let predicate = NSPredicate(format: "label CONTAINS 'xcode_uikit_swift'") + let scenes = springboardApp.otherElements.matching(predicate) + let newScene = scenes.element(boundBy: 0) + let originalScene = scenes.element(boundBy: 1) + newScene.tap() + + // Validate lifecycle events of new scene do contain background event + let newSceneButton = buttons.element(boundBy: 0) + XCTAssertTrue(newSceneButton.waitForExistence(timeout: 5)) + newSceneButton.tap() + let expectedNewSceneEvents = [ + "sceneWillConnect", + "sceneWillEnterForeground", "sceneDidBecomeActive", + "sceneWillResignActive", "sceneDidEnterBackground", + "sceneWillEnterForeground", "sceneDidBecomeActive", + ] + + let newSceneEventsPredicate = NSPredicate( + format: "label == %@", + expectedNewSceneEvents.joined(separator: "\n") + ) + let newSceneEvents = app.staticTexts.element( + matching: newSceneEventsPredicate + ) + XCTAssertTrue(newSceneEvents.waitForExistence(timeout: 5)) + } + + @MainActor + func testLifecycleEvents() throws { + let app = XCUIApplication() + app.launch() + let button = app.buttons["Get Lifecycle Events"].firstMatch + XCTAssertTrue(button.waitForExistence(timeout: 5)) + button.tap() + + let expectedStartEvents = [ + "applicationDidFinishLaunchingWithOptions", "sceneWillConnect", + "sceneWillEnterForeground", "sceneDidBecomeActive", + ] + let startEventsPredicate = NSPredicate( + format: "label == %@", + expectedStartEvents.joined(separator: "\n") + ) + let startEventsElement = app.staticTexts.element( + matching: startEventsPredicate + ) + XCTAssertTrue(startEventsElement.waitForExistence(timeout: 5)) + + // Background the app, then reactivate it and check the events again + XCUIDevice.shared.press(.home) + app.activate() + XCTAssertTrue(button.waitForExistence(timeout: 5)) + button.tap() + + let expectedEventsAfterBackgroundAndReactivate = [ + "applicationDidFinishLaunchingWithOptions", "sceneWillConnect", + "sceneWillEnterForeground", "sceneDidBecomeActive", + "sceneWillResignActive", "sceneDidEnterBackground", + "sceneWillEnterForeground", "sceneDidBecomeActive", + ] + let backgroundEventsPredicate = NSPredicate( + format: "label == %@", + expectedEventsAfterBackgroundAndReactivate.joined(separator: "\n") + ) + let backgroundEventsElement = app.staticTexts.element( + matching: backgroundEventsPredicate + ) + XCTAssertTrue(backgroundEventsElement.waitForExistence(timeout: 5)) + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/ViewController-FlutterEngineFromSceneDelegate-NoStoryboard.swift b/dev/integration_tests/ios_add2app_uiscene/native/ViewController-FlutterEngineFromSceneDelegate-NoStoryboard.swift new file mode 100644 index 0000000000000..d9ddb41aac4ec --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/ViewController-FlutterEngineFromSceneDelegate-NoStoryboard.swift @@ -0,0 +1,28 @@ +// 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 Flutter +import UIKit + +class ViewController: UIViewController { + let flutterEngine: FlutterEngine + + init(engine: FlutterEngine) { + self.flutterEngine = engine + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + let flutterViewController = + FlutterViewController(engine: self.flutterEngine, nibName: nil, bundle: nil) + addChild(flutterViewController) + flutterViewController.view.frame = self.view.frame + self.view.addSubview(flutterViewController.view) + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/native/ViewController-FlutterEngineFromSceneDelegate-Storyboard.swift b/dev/integration_tests/ios_add2app_uiscene/native/ViewController-FlutterEngineFromSceneDelegate-Storyboard.swift new file mode 100644 index 0000000000000..96371b3628e64 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/native/ViewController-FlutterEngineFromSceneDelegate-Storyboard.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 Flutter +import UIKit + +class ViewController: UIViewController { + var flutterEngine: FlutterEngine? + + override func viewDidLoad() { + super.viewDidLoad() + let flutterViewController = + FlutterViewController(engine: self.flutterEngine!, nibName: nil, bundle: nil) + addChild(flutterViewController) + flutterViewController.view.frame = self.view.frame + self.view.addSubview(flutterViewController.view) + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.pbxproj b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.pbxproj index 657179a24e02d..42fa8661b3648 100644 --- a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.pbxproj +++ b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.pbxproj @@ -419,7 +419,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "xcode-swiftui-Info.plist"; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = NO; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; @@ -453,7 +453,7 @@ ENABLE_PREVIEWS = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "xcode-swiftui-Info.plist"; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = NO; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneDelegate.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneDelegate.h index b80655552d535..2238d73338dee 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneDelegate.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneDelegate.h @@ -7,6 +7,7 @@ #import #import "FlutterMacros.h" +#import "FlutterSceneLifeCycle.h" NS_ASSUME_NONNULL_BEGIN @@ -16,8 +17,10 @@ NS_ASSUME_NONNULL_BEGIN * This class is typically specified as the UISceneDelegate in the Info.plist. */ FLUTTER_DARWIN_EXPORT -@interface FlutterSceneDelegate : NSObject +@interface FlutterSceneDelegate + : NSObject @property(nonatomic, strong, nullable) UIWindow* window; + @end NS_ASSUME_NONNULL_END diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneLifeCycle.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneLifeCycle.h index d83637594ef16..70898c840f680 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneLifeCycle.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneLifeCycle.h @@ -10,6 +10,8 @@ NS_ASSUME_NONNULL_BEGIN +@class FlutterEngine; + /** * A protocol for delegates that handle `UISceneDelegate` and `UIWindowSceneDelegate` life-cycle * events. @@ -96,6 +98,45 @@ API_AVAILABLE(ios(13.0)) @end +/** + * A protocol for manually registering a `FlutterEngine` to receive scene life cycle events. + */ +@protocol FlutterSceneLifeCycleEngineRegistration +/** + * Registers a `FlutterEngine` to receive scene life cycle events. + * + * This method is **only** necessary when the following conditions are true: + * 1. Multiple Scenes (UIApplicationSupportsMultipleScenes) is enabled. + * 2. The `UIWindowSceneDelegate` `window.rootViewController` is not a `FlutterViewController` + * initialized with the target `FlutterEngine`. + * + * When multiple scenes is enabled (UIApplicationSupportsMultipleScenes), Flutter cannot + * automatically associate a `FlutterEngine` with a scene during the scene connection phase. In + * order for plugins to receive launch connection information, the `FlutterEngine` must be manually + * registered with either the `FlutterSceneDelegate` or `FlutterPluginSceneLifeCycleDelegate` during + * `scene:willConnectToSession:options:`. + * + * In all other cases, or once the `FlutterViewController.view` associated with the `FlutterEngine` + * is added to the view hierarchy, Flutter will automatically handle registration for scene events. + * + * Manually registered engines must also be manually deregistered and re-registered if they + * switch scenes. Use `unregisterSceneLifeCycleWithFlutterEngine:`. + * + * @param engine The `FlutterEngine` to register for scene life cycle events. + * @return `NO` if already manually registered. + */ +- (BOOL)registerSceneLifeCycleWithFlutterEngine:(FlutterEngine*)engine; + +/** + * Use this method to unregister a `FlutterEngine` from the scene's life cycle events. + * + * @param engine The `FlutterEngine` to unregister for scene life cycle events. + * @return `NO` if the engine was not found among the manually registered engines and could not be + * unregistered. + */ +- (BOOL)unregisterSceneLifeCycleWithFlutterEngine:(FlutterEngine*)engine; +@end + /** * Forwards `UISceneDelegate` and `UIWindowSceneDelegate` callbacks to plugins that register for * them. @@ -105,7 +146,7 @@ API_AVAILABLE(ios(13.0)) */ FLUTTER_DARWIN_EXPORT API_AVAILABLE(ios(13.0)) -@interface FlutterPluginSceneLifeCycleDelegate : NSObject +@interface FlutterPluginSceneLifeCycleDelegate : NSObject #pragma mark - Connecting and disconnecting the scene diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h index 2a037d994ff0f..c7e061d7be6b0 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h @@ -32,6 +32,9 @@ NS_ASSUME_NONNULL_BEGIN @interface FlutterEngine () +// Indicates whether this engine has **ever** been manually registered to a scene. +@property(nonatomic, assign) BOOL manuallyRegisteredToScene; + - (void)updateViewportMetrics:(flutter::ViewportMetrics)viewportMetrics; - (void)dispatchPointerDataPacket:(std::unique_ptr)packet; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegate.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegate.mm index 1a898dab9ed6a..8973bd91a38b0 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegate.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegate.mm @@ -5,6 +5,7 @@ #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneDelegate.h" #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" +#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneLifeCycle.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterAppDelegate_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h" #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSharedApplication.h" @@ -103,6 +104,14 @@ - (void)windowScene:(UIWindowScene*)windowScene #pragma mark - Helpers +- (BOOL)registerSceneLifeCycleWithFlutterEngine:(FlutterEngine*)engine { + return [self.sceneLifeCycleDelegate registerSceneLifeCycleWithFlutterEngine:engine]; +} + +- (BOOL)unregisterSceneLifeCycleWithFlutterEngine:(FlutterEngine*)engine { + return [self.sceneLifeCycleDelegate unregisterSceneLifeCycleWithFlutterEngine:engine]; +} + - (void)moveRootViewControllerFrom:(NSObject*)appDelegate to:(UIWindowScene*)windowScene { self.window = [[UIWindow alloc] initWithWindowScene:windowScene]; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegateTest.m b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegateTest.m index f137dd0d46636..bdd52fd0121bf 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegateTest.m +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneDelegateTest.m @@ -272,6 +272,36 @@ - (void)testWindowScenePerformActionForShortcutItem { completionHandler:[OCMArg any]]); } +- (void)testRegisterSceneLifeCycleWithFlutterEngine { + [self setupMockApplication]; + + id mockEngine = OCMClassMock([FlutterEngine class]); + FlutterSceneDelegate* sceneDelegate = [[FlutterSceneDelegate alloc] init]; + id mockSceneDelegate = OCMPartialMock(sceneDelegate); + + id mockLifecycleDelegate = OCMClassMock([FlutterPluginSceneLifeCycleDelegate class]); + OCMStub([mockSceneDelegate sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); + + [mockSceneDelegate registerSceneLifeCycleWithFlutterEngine:mockEngine]; + + OCMVerify(times(1), [mockLifecycleDelegate registerSceneLifeCycleWithFlutterEngine:mockEngine]); +} + +- (void)testUnregisterSceneLifeCycleWithFlutterEngine { + [self setupMockApplication]; + + id mockEngine = OCMClassMock([FlutterEngine class]); + FlutterSceneDelegate* sceneDelegate = [[FlutterSceneDelegate alloc] init]; + id mockSceneDelegate = OCMPartialMock(sceneDelegate); + + id mockLifecycleDelegate = OCMClassMock([FlutterPluginSceneLifeCycleDelegate class]); + OCMStub([mockSceneDelegate sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); + + [mockSceneDelegate unregisterSceneLifeCycleWithFlutterEngine:mockEngine]; + + OCMVerify(times(1), [mockLifecycleDelegate unregisterSceneLifeCycleWithFlutterEngine:mockEngine]); +} + - (NSDictionary*)setupMockApplication { id mockApplication = OCMClassMock([UIApplication class]); OCMStub([mockApplication sharedApplication]).andReturn(mockApplication); diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm index bb84bebddfa78..561582d3abb6a 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm @@ -15,12 +15,21 @@ @interface FlutterPluginSceneLifeCycleDelegate () /** - * An array of weak pointers to `FlutterEngine`s that have views within this scene. + * An array of weak pointers to `FlutterEngine`s that have views within this scene. Flutter + * automatically adds engines to this array. * - * This array is lazily cleaned up. `updateEnginesInScene:` should be called before use to ensure it - * is up-to-date. + * This array is lazily cleaned up. `updateFlutterManagedEnginesInScene:` should be called before + * use to ensure it is up-to-date. */ -@property(nonatomic, strong) NSPointerArray* engines; +@property(nonatomic, strong) NSPointerArray* flutterManagedEngines; + +/** + * An array of weak pointers to `FlutterEngine`s that have views within this scene. Developers + * manually add engines to this array. + * + * It is up to the developer to keep this list up-to-date. + */ +@property(nonatomic, strong) NSPointerArray* developerManagedEngines; @property(nonatomic, strong) UISceneConnectionOptions* connectionOptions; @end @@ -28,44 +37,84 @@ @interface FlutterPluginSceneLifeCycleDelegate () @implementation FlutterPluginSceneLifeCycleDelegate - (instancetype)init { if (self = [super init]) { - _engines = [NSPointerArray weakObjectsPointerArray]; + _flutterManagedEngines = [NSPointerArray weakObjectsPointerArray]; + _developerManagedEngines = [NSPointerArray weakObjectsPointerArray]; } return self; } -- (void)addFlutterEngine:(FlutterEngine*)engine { +#pragma mark - Manual Engine Registration + +- (BOOL)registerSceneLifeCycleWithFlutterEngine:(FlutterEngine*)engine { + // If the engine is Flutter-managed, remove it, since the developer as opted to manually register + // it + [self removeFlutterManagedEngine:engine]; + // Check if the engine is already in the array to avoid duplicates. - if ([self.engines.allObjects containsObject:engine]) { - return; + if ([self manuallyRegisteredEngine:engine]) { + return NO; } - [self.engines addPointer:(__bridge void*)engine]; + [self.developerManagedEngines addPointer:(__bridge void*)engine]; - // NSPointerArray is clever and assumes that unless a mutation operation has occurred on it that - // has set one of its values to nil, nothing could have changed and it can skip compaction. - // That's reasonable behaviour on a regular NSPointerArray but not for a weakObjectPointerArray. - // As a workaround, we mutate it first. See: http://www.openradar.me/15396578 - [self.engines addPointer:nil]; - [self.engines compact]; + [self compactNSPointerArray:self.developerManagedEngines]; + + engine.manuallyRegisteredToScene = YES; + + return YES; } -- (void)removeFlutterEngine:(FlutterEngine*)engine { - NSUInteger index = [self.engines.allObjects indexOfObject:engine]; +- (BOOL)unregisterSceneLifeCycleWithFlutterEngine:(FlutterEngine*)engine { + NSUInteger index = [self.developerManagedEngines.allObjects indexOfObject:engine]; if (index != NSNotFound) { - [self.engines removePointerAtIndex:index]; + [self.developerManagedEngines removePointerAtIndex:index]; + return YES; } + return NO; +} + +- (BOOL)manuallyRegisteredEngine:(FlutterEngine*)engine { + return [self.developerManagedEngines.allObjects containsObject:engine]; } -- (void)updateEnginesInScene:(UIScene*)scene { +#pragma mark - Automatic Flutter Engine Registration + +- (BOOL)addFlutterManagedEngine:(FlutterEngine*)engine { + // Check if the engine is already in the array to avoid duplicates. + if ([self.flutterManagedEngines.allObjects containsObject:engine]) { + return NO; + } + + // If a manually registered engine, do not add, as it is being handled manually. + if (engine.manuallyRegisteredToScene) { + return NO; + } + + [self.flutterManagedEngines addPointer:(__bridge void*)engine]; + + [self compactNSPointerArray:self.flutterManagedEngines]; + return YES; +} + +- (BOOL)removeFlutterManagedEngine:(FlutterEngine*)engine { + NSUInteger index = [self.flutterManagedEngines.allObjects indexOfObject:engine]; + if (index != NSNotFound) { + [self.flutterManagedEngines removePointerAtIndex:index]; + return YES; + } + return NO; +} + +- (void)updateFlutterManagedEnginesInScene:(UIScene*)scene { // Removes engines that are no longer in the scene or have been deallocated. // // This also handles the case where a FlutterEngine's view has been moved to a different scene. - for (NSUInteger i = 0; i < self.engines.count; i++) { - FlutterEngine* engine = (FlutterEngine*)[self.engines pointerAtIndex:i]; + for (NSUInteger i = 0; i < self.flutterManagedEngines.count; i++) { + FlutterEngine* engine = (FlutterEngine*)[self.flutterManagedEngines pointerAtIndex:i]; // The engine may be nil if it has been deallocated. if (engine == nil) { - [self.engines removePointerAtIndex:i]; + [self.flutterManagedEngines removePointerAtIndex:i]; i--; continue; } @@ -81,19 +130,24 @@ - (void)updateEnginesInScene:(UIScene*)scene { // the scene. UIWindowScene* actualScene = engine.viewController.view.window.windowScene; if (actualScene != nil && actualScene != scene) { - [self.engines removePointerAtIndex:i]; + [self.flutterManagedEngines removePointerAtIndex:i]; i--; if ([actualScene.delegate conformsToProtocol:@protocol(FlutterSceneLifeCycleProvider)]) { id lifeCycleProvider = (id)actualScene.delegate; - [lifeCycleProvider.sceneLifeCycleDelegate addFlutterEngine:engine]; + [lifeCycleProvider.sceneLifeCycleDelegate addFlutterManagedEngine:engine]; } continue; } } } +- (NSArray*)allEngines { + return [_flutterManagedEngines.allObjects + arrayByAddingObjectsFromArray:_developerManagedEngines.allObjects]; +} + /** * Makes a best effort to get the FlutterPluginAppLifeCycleDelegate from the AppDelegate if * available. It may not be available if embedded in an iOS app extension or the AppDelegate doesn't @@ -115,7 +169,12 @@ - (FlutterPluginAppLifeCycleDelegate*)applicationLifeCycleDelegate { - (void)engine:(FlutterEngine*)engine receivedConnectNotificationFor:(UIScene*)scene { // Connection options may be nil if the notification was received before the // `scene:willConnectToSession:options:` event. In which case, we can wait for the actual event. - [self addFlutterEngine:engine]; + BOOL added = [self addFlutterManagedEngine:engine]; + if (!added) { + // Don't send willConnectToSession event if engine is already tracked as it will be handled by + // the actual event. + return; + } if (self.connectionOptions != nil) { [self scene:scene willConnectToSession:scene.session @@ -128,10 +187,19 @@ - (void)scene:(UIScene*)scene willConnectToSession:(UISceneSession*)session options:(UISceneConnectionOptions*)connectionOptions { self.connectionOptions = connectionOptions; + if ([scene.delegate conformsToProtocol:@protocol(UIWindowSceneDelegate)]) { + NSObject* sceneDelegate = + (NSObject*)scene.delegate; + if ([sceneDelegate.window.rootViewController isKindOfClass:[FlutterViewController class]]) { + FlutterViewController* rootViewController = + (FlutterViewController*)sceneDelegate.window.rootViewController; + [self addFlutterManagedEngine:rootViewController.engine]; + } + } - [self updateEnginesInScene:scene]; + [self updateFlutterManagedEnginesInScene:scene]; - for (FlutterEngine* engine in _engines.allObjects) { + for (FlutterEngine* engine in [self allEngines]) { [self scene:scene willConnectToSession:session flutterEngine:engine options:connectionOptions]; } } @@ -150,8 +218,8 @@ - (void)scene:(UIScene*)scene } - (void)sceneDidDisconnect:(UIScene*)scene { - [self updateEnginesInScene:scene]; - for (FlutterEngine* engine in _engines.allObjects) { + [self updateFlutterManagedEnginesInScene:scene]; + for (FlutterEngine* engine in [self allEngines]) { [engine.sceneLifeCycleDelegate sceneDidDisconnect:scene]; } // There is no application equivalent for this event and therefore no fallback. @@ -160,16 +228,16 @@ - (void)sceneDidDisconnect:(UIScene*)scene { #pragma mark - Transitioning to the foreground - (void)sceneWillEnterForeground:(UIScene*)scene { - [self updateEnginesInScene:scene]; - for (FlutterEngine* engine in _engines.allObjects) { + [self updateFlutterManagedEnginesInScene:scene]; + for (FlutterEngine* engine in [self allEngines]) { [engine.sceneLifeCycleDelegate sceneWillEnterForeground:scene]; } [[self applicationLifeCycleDelegate] sceneWillEnterForegroundFallback]; } - (void)sceneDidBecomeActive:(UIScene*)scene { - [self updateEnginesInScene:scene]; - for (FlutterEngine* engine in _engines.allObjects) { + [self updateFlutterManagedEnginesInScene:scene]; + for (FlutterEngine* engine in [self allEngines]) { [engine.sceneLifeCycleDelegate sceneDidBecomeActive:scene]; } [[self applicationLifeCycleDelegate] sceneDidBecomeActiveFallback]; @@ -178,16 +246,16 @@ - (void)sceneDidBecomeActive:(UIScene*)scene { #pragma mark - Transitioning to the background - (void)sceneWillResignActive:(UIScene*)scene { - [self updateEnginesInScene:scene]; - for (FlutterEngine* engine in _engines.allObjects) { + [self updateFlutterManagedEnginesInScene:scene]; + for (FlutterEngine* engine in [self allEngines]) { [engine.sceneLifeCycleDelegate sceneWillResignActive:scene]; } [[self applicationLifeCycleDelegate] sceneWillResignActiveFallback]; } - (void)sceneDidEnterBackground:(UIScene*)scene { - [self updateEnginesInScene:scene]; - for (FlutterEngine* engine in _engines.allObjects) { + [self updateFlutterManagedEnginesInScene:scene]; + for (FlutterEngine* engine in [self allEngines]) { [engine.sceneLifeCycleDelegate sceneDidEnterBackground:scene]; } [[self applicationLifeCycleDelegate] sceneDidEnterBackgroundFallback]; @@ -196,11 +264,11 @@ - (void)sceneDidEnterBackground:(UIScene*)scene { #pragma mark - Opening URLs - (void)scene:(UIScene*)scene openURLContexts:(NSSet*)URLContexts { - [self updateEnginesInScene:scene]; + [self updateFlutterManagedEnginesInScene:scene]; // Track engines that had this event handled by a plugin. NSMutableSet* enginesHandledByPlugin = [NSMutableSet set]; - for (FlutterEngine* engine in _engines.allObjects) { + for (FlutterEngine* engine in [self allEngines]) { if ([engine.sceneLifeCycleDelegate scene:scene openURLContexts:URLContexts]) { [enginesHandledByPlugin addObject:engine]; } @@ -215,7 +283,7 @@ - (void)scene:(UIScene*)scene openURLContexts:(NSSet*)URLCont } // For any engine that was not handled by a plugin, do deeplinking. - for (FlutterEngine* engine in _engines.allObjects) { + for (FlutterEngine* engine in [self allEngines]) { if ([enginesHandledByPlugin containsObject:engine]) { continue; } @@ -230,11 +298,11 @@ - (void)scene:(UIScene*)scene openURLContexts:(NSSet*)URLCont #pragma mark - Continuing user activities - (void)scene:(UIScene*)scene continueUserActivity:(NSUserActivity*)userActivity { - [self updateEnginesInScene:scene]; + [self updateFlutterManagedEnginesInScene:scene]; // Track engines that had this event handled by a plugin. NSMutableSet* enginesHandledByPlugin = [NSMutableSet set]; - for (FlutterEngine* engine in _engines.allObjects) { + for (FlutterEngine* engine in [self allEngines]) { if ([engine.sceneLifeCycleDelegate scene:scene continueUserActivity:userActivity]) { [enginesHandledByPlugin addObject:engine]; } @@ -249,7 +317,7 @@ - (void)scene:(UIScene*)scene continueUserActivity:(NSUserActivity*)userActivity } // For any engine that was not handled by a plugin, do deeplinking. - for (FlutterEngine* engine in _engines.allObjects) { + for (FlutterEngine* engine in [self allEngines]) { if ([enginesHandledByPlugin containsObject:engine]) { continue; } @@ -266,9 +334,9 @@ - (NSUserActivity*)stateRestorationActivityForScene:(UIScene*)scene { activity = [[NSUserActivity alloc] initWithActivityType:scene.session.configuration.name]; } - [self updateEnginesInScene:scene]; + [self updateFlutterManagedEnginesInScene:scene]; int64_t appBundleModifiedTime = FlutterSharedApplication.lastAppModificationTime; - for (FlutterEngine* engine in [_engines allObjects]) { + for (FlutterEngine* engine in [self allEngines]) { FlutterViewController* vc = (FlutterViewController*)engine.viewController; NSString* restorationId = vc.restorationIdentifier; if (restorationId) { @@ -289,7 +357,7 @@ - (void)scene:(UIScene*)scene restoreInteractionStateWithUserActivity:(NSUserActivity*)stateRestorationActivity { // Restores state per FlutterViewController. NSDictionary* userInfo = stateRestorationActivity.userInfo; - [self updateEnginesInScene:scene]; + [self updateFlutterManagedEnginesInScene:scene]; int64_t appBundleModifiedTime = FlutterSharedApplication.lastAppModificationTime; NSNumber* stateDateNumber = userInfo[kRestorationStateAppModificationKey]; int64_t stateDate = 0; @@ -301,7 +369,7 @@ - (void)scene:(UIScene*)scene return; } - for (FlutterEngine* engine in [_engines allObjects]) { + for (FlutterEngine* engine in [self allEngines]) { UIViewController* vc = (UIViewController*)engine.viewController; NSString* restorationId = vc.restorationIdentifier; if (restorationId) { @@ -318,10 +386,10 @@ - (void)scene:(UIScene*)scene - (void)windowScene:(UIWindowScene*)windowScene performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem completionHandler:(void (^)(BOOL succeeded))completionHandler { - [self updateEnginesInScene:windowScene]; + [self updateFlutterManagedEnginesInScene:windowScene]; BOOL handledByPlugin = NO; - for (FlutterEngine* engine in _engines.allObjects) { + for (FlutterEngine* engine in [self allEngines]) { BOOL result = [engine.sceneLifeCycleDelegate windowScene:windowScene performActionForShortcutItem:shortcutItem completionHandler:completionHandler]; @@ -406,6 +474,15 @@ + (FlutterPluginSceneLifeCycleDelegate*)fromScene:(UIScene*)scene { } return nil; } + +- (void)compactNSPointerArray:(NSPointerArray*)array { + // NSPointerArray is clever and assumes that unless a mutation operation has occurred on it that + // has set one of its values to nil, nothing could have changed and it can skip compaction. + // That's reasonable behaviour on a regular NSPointerArray but not for a weakObjectPointerArray. + // As a workaround, we mutate it first. See: http://www.openradar.me/15396578 + [array addPointer:nil]; + [array compact]; +} @end @implementation FlutterEnginePluginSceneLifeCycleDelegate { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm index 482f4e0ebcb66..bb2488ba94deb 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm @@ -48,7 +48,7 @@ - (void)tearDown { #pragma mark - FlutterPluginSceneLifeCycleDelegate -- (void)testAddFlutterEngine { +- (void)testAddFlutterManagedEngine { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -56,8 +56,8 @@ - (void)testAddFlutterEngine { id mockLifecycleDelegate = OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); OCMStub([mockEngine sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); } - (void)testAddDuplicateFlutterEngine { @@ -68,10 +68,10 @@ - (void)testAddDuplicateFlutterEngine { id mockLifecycleDelegate = OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); OCMStub([mockEngine sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); - [delegate addFlutterEngine:mockEngine]; - [delegate addFlutterEngine:mockEngine]; - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + [delegate addFlutterManagedEngine:mockEngine]; + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); } - (void)testAddMultipleFlutterEngine { @@ -81,26 +81,26 @@ - (void)testAddMultipleFlutterEngine { id mockEngine = OCMClassMock([FlutterEngine class]); id mockLifecycleDelegate = OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); OCMStub([mockEngine sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); - [delegate addFlutterEngine:mockEngine]; + [delegate addFlutterManagedEngine:mockEngine]; id mockEngine2 = OCMClassMock([FlutterEngine class]); id mockLifecycleDelegate2 = OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); OCMStub([mockEngine2 sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate2); - [delegate addFlutterEngine:mockEngine2]; + [delegate addFlutterManagedEngine:mockEngine2]; - XCTAssertEqual(delegate.engines.count, 2.0); + XCTAssertEqual(delegate.flutterManagedEngines.count, 2.0); } -- (void)testRemoveFlutterEngine { +- (void)testRemoveFlutterManagedEngine { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; id mockEngine = OCMClassMock([FlutterEngine class]); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); - [delegate removeFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 0.0); + [delegate removeFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 0.0); } - (void)testRemoveNotFoundFlutterEngine { @@ -108,13 +108,13 @@ - (void)testRemoveNotFoundFlutterEngine { [[FlutterPluginSceneLifeCycleDelegate alloc] init]; id mockEngine = OCMClassMock([FlutterEngine class]); - XCTAssertEqual(delegate.engines.count, 0.0); + XCTAssertEqual(delegate.flutterManagedEngines.count, 0.0); - [delegate removeFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 0.0); + [delegate removeFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 0.0); } -- (void)testUpdateEnginesInSceneRemovesDeallocatedEngine { +- (void)testupdateFlutterManagedEnginesInSceneRemovesDeallocatedEngine { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -122,15 +122,15 @@ - (void)testUpdateEnginesInSceneRemovesDeallocatedEngine { @autoreleasepool { id mockEngine = OCMClassMock([FlutterEngine class]); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); } - [delegate updateEnginesInScene:mockWindowScene]; - XCTAssertEqual(delegate.engines.count, 0.0); + [delegate updateFlutterManagedEnginesInScene:mockWindowScene]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 0.0); } -- (void)testUpdateEnginesInSceneRemovesEngineNotInScene { +- (void)testupdateFlutterManagedEnginesInSceneRemovesEngineNotInScene { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -149,26 +149,126 @@ - (void)testUpdateEnginesInSceneRemovesEngineNotInScene { OCMStub([mockWindowScene delegate]).andReturn(mockLifecycleProvider); OCMStub([mockLifecycleProvider sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); id mockWindowScene2 = OCMClassMock([UIWindowScene class]); - [delegate updateEnginesInScene:mockWindowScene2]; - OCMVerify(times(1), [mockLifecycleDelegate addFlutterEngine:mockEngine]); - XCTAssertEqual(delegate.engines.count, 0.0); + [delegate updateFlutterManagedEnginesInScene:mockWindowScene2]; + OCMVerify(times(1), [mockLifecycleDelegate addFlutterManagedEngine:mockEngine]); + XCTAssertEqual(delegate.flutterManagedEngines.count, 0.0); } -- (void)testUpdateEnginesInSceneDoesNotRemoveEngineWithNilScene { +- (void)testupdateFlutterManagedEnginesInSceneDoesNotRemoveEngineWithNilScene { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; id mockEngine = OCMClassMock([FlutterEngine class]); id mockWindowScene = OCMClassMock([UIWindowScene class]); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); - [delegate updateEnginesInScene:mockWindowScene]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate updateFlutterManagedEnginesInScene:mockWindowScene]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); +} + +- (void)testManuallyRegisterSceneLifeCycleWithFlutterEngine { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mockEngine = OCMClassMock([FlutterEngine class]); + id mockLifecycleDelegate = OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); + OCMStub([mockEngine sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); + + [delegate registerSceneLifeCycleWithFlutterEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 0.0); + XCTAssertEqual(delegate.developerManagedEngines.count, 1.0); +} + +- (void)testManuallyUnregisterSceneLifeCycleWithFlutterEngine { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mockEngine = OCMClassMock([FlutterEngine class]); + id mockLifecycleDelegate = OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); + OCMStub([mockEngine sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); + + [delegate registerSceneLifeCycleWithFlutterEngine:mockEngine]; + [delegate unregisterSceneLifeCycleWithFlutterEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 0.0); + XCTAssertEqual(delegate.developerManagedEngines.count, 0.0); +} + +- (void)testManuallyRegisterSceneLifeCycleWithFlutterEngineCannotBeAutoAdded { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + FlutterEngine* engine = [[FlutterEngine alloc] init]; + id mockEngine = OCMPartialMock(engine); + id mockLifecycleDelegate = OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); + OCMStub([mockEngine sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); + + [delegate registerSceneLifeCycleWithFlutterEngine:mockEngine]; + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 0.0); + XCTAssertEqual(delegate.developerManagedEngines.count, 1.0); + + [delegate unregisterSceneLifeCycleWithFlutterEngine:mockEngine]; + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 0.0); + XCTAssertEqual(delegate.developerManagedEngines.count, 0.0); + + [delegate registerSceneLifeCycleWithFlutterEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 0.0); + XCTAssertEqual(delegate.developerManagedEngines.count, 1.0); +} + +- (void)testManuallyRegisterSceneLifeCycleWithFlutterEngineCannotBeAutoRemoved { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mockEngine = OCMClassMock([FlutterEngine class]); + id mockLifecycleDelegate = OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); + OCMStub([mockEngine sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); + + [delegate registerSceneLifeCycleWithFlutterEngine:mockEngine]; + [delegate removeFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 0.0); + XCTAssertEqual(delegate.developerManagedEngines.count, 1.0); +} + +- (void)testManuallyRegisterSceneLifeCycleWithFlutterEngineRemovesAutomatic { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mockEngine = OCMClassMock([FlutterEngine class]); + id mockLifecycleDelegate = OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); + OCMStub([mockEngine sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); + + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); + [delegate registerSceneLifeCycleWithFlutterEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 0.0); + XCTAssertEqual(delegate.developerManagedEngines.count, 1.0); +} + +- (void)testAllEnginesContainsManualAndAutomatic { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mockEngine = OCMClassMock([FlutterEngine class]); + id mockLifecycleDelegate = OCMClassMock([FlutterEnginePluginSceneLifeCycleDelegate class]); + OCMStub([mockEngine sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate); + + id mockEngine2 = OCMClassMock([FlutterEngine class]); + + [delegate addFlutterManagedEngine:mockEngine]; + [delegate registerSceneLifeCycleWithFlutterEngine:mockEngine2]; + + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); + XCTAssertEqual(delegate.developerManagedEngines.count, 1.0); + XCTAssertEqual(delegate.allEngines.count, 2.0); + XCTAssertEqual([delegate.allEngines objectAtIndex:0], mockEngine); + XCTAssertEqual([delegate.allEngines objectAtIndex:1], mockEngine2); } - (void)testEngineReceivedConnectNotificationForSceneBeforeActualEvent { @@ -197,9 +297,9 @@ - (void)testEngineReceivedConnectNotificationForSceneBeforeActualEvent { // received notification [mockDelegate engine:mockEngine receivedConnectNotificationFor:mockScene]; [mockDelegate engine:mockEngine2 receivedConnectNotificationFor:mockScene]; - OCMVerify(times(1), [mockDelegate addFlutterEngine:mockEngine]); - OCMVerify(times(1), [mockDelegate addFlutterEngine:mockEngine2]); - XCTAssertEqual(delegate.engines.count, 2.0); + OCMVerify(times(1), [mockDelegate addFlutterManagedEngine:mockEngine]); + OCMVerify(times(1), [mockDelegate addFlutterManagedEngine:mockEngine2]); + XCTAssertEqual(delegate.flutterManagedEngines.count, 2.0); OCMVerify(times(0), [mockDelegate scene:[OCMArg any] willConnectToSession:[OCMArg any] options:[OCMArg any]]); @@ -214,7 +314,7 @@ - (void)testEngineReceivedConnectNotificationForSceneBeforeActualEvent { OCMVerify(times(1), [mockLifecycleDelegate2 scene:mockScene willConnectToSession:session options:options]); - XCTAssertEqual(delegate.engines.count, 2.0); + XCTAssertEqual(delegate.flutterManagedEngines.count, 2.0); } - (void)testEngineReceivedConnectNotificationForSceneAfterActualEvent { @@ -244,7 +344,7 @@ - (void)testEngineReceivedConnectNotificationForSceneAfterActualEvent { id options = OCMClassMock([UISceneConnectionOptions class]); OCMStub([mockScene session]).andReturn(session); [mockDelegate scene:mockScene willConnectToSession:session options:options]; - XCTAssertEqual(delegate.engines.count, 0.0); + XCTAssertEqual(delegate.flutterManagedEngines.count, 0.0); OCMVerify(times(0), [mockLifecycleDelegate scene:mockScene willConnectToSession:session options:options]); @@ -257,9 +357,9 @@ - (void)testEngineReceivedConnectNotificationForSceneAfterActualEvent { [mockDelegate engine:mockEngine receivedConnectNotificationFor:mockScene]; [mockDelegate engine:mockEngine2 receivedConnectNotificationFor:mockScene]; - OCMVerify(times(1), [mockDelegate addFlutterEngine:mockEngine]); - OCMVerify(times(1), [mockDelegate addFlutterEngine:mockEngine2]); - XCTAssertEqual(delegate.engines.count, 2.0); + OCMVerify(times(1), [mockDelegate addFlutterManagedEngine:mockEngine]); + OCMVerify(times(1), [mockDelegate addFlutterManagedEngine:mockEngine2]); + XCTAssertEqual(delegate.flutterManagedEngines.count, 2.0); OCMVerify(times(1), [mockDelegate scene:mockScene willConnectToSession:session options:options]); // This is called twice because once is @@ -289,8 +389,8 @@ - (void)testSceneWillConnectToSessionOptionsHandledByScenePlugin { id session = OCMClassMock([UISceneSession class]); id options = OCMClassMock([UISceneConnectionOptions class]); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene willConnectToSession:session options:options]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene @@ -322,8 +422,8 @@ - (void)testSceneWillConnectToSessionOptionsHandledByUniversalLinks { OCMStub([options userActivities]).andReturn(userActivities); OCMStub([flutterApp isFlutterDeepLinkingEnabled]).andReturn(YES); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene willConnectToSession:session options:options]; OCMVerify(times(1), [mockEngine sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); @@ -353,8 +453,8 @@ - (void)testSceneWillConnectToSessionOptionsHandledByDeepLinks { NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; OCMStub([options URLContexts]).andReturn(urlContexts); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene willConnectToSession:session options:options]; OCMVerify(times(1), [mockEngine sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); @@ -384,13 +484,71 @@ - (void)testSceneWillConnectToSessionOptionsHandledByNoPlugin { OCMStub([options userActivities]).andReturn(userActivities); OCMStub([flutterApp isFlutterDeepLinkingEnabled]).andReturn(NO); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene willConnectToSession:session options:options]; OCMVerify(times(0), [mockEngine sendDeepLinkToFramework:url completionHandler:[OCMArg any]]); } +- (void)testSceneWillConnectToSessionAddsEngineFromRootViewController { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + FlutterPluginSceneLifeCycleDelegate* mockDelegate = OCMPartialMock(delegate); + + id mockScene = OCMClassMock([UIWindowScene class]); + id mockWindow = OCMClassMock([UIWindow class]); + id mockViewController = OCMClassMock([FlutterViewController class]); + id mockEngine = OCMClassMock([FlutterEngine class]); + id mockSceneDelegate = OCMProtocolMock(@protocol(UIWindowSceneDelegate)); + + OCMStub([mockScene delegate]).andReturn(mockSceneDelegate); + OCMStub([mockSceneDelegate window]).andReturn(mockWindow); + OCMStub([mockWindow rootViewController]).andReturn(mockViewController); + OCMStub([mockViewController engine]).andReturn(mockEngine); + + id session = OCMClassMock([UISceneSession class]); + id options = OCMClassMock([UISceneConnectionOptions class]); + + [mockDelegate scene:mockScene willConnectToSession:session options:options]; + + OCMVerify(times(1), [mockDelegate addFlutterManagedEngine:mockEngine]); + OCMVerify(times(1), [mockDelegate scene:mockScene + willConnectToSession:session + flutterEngine:mockEngine + options:options]); +} + +- (void)testSceneWillConnectToSessionAddsEngineFromRootViewControllerAndNotNotification { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + FlutterPluginSceneLifeCycleDelegate* mockDelegate = OCMPartialMock(delegate); + + id mockScene = OCMClassMock([UIWindowScene class]); + id mockWindow = OCMClassMock([UIWindow class]); + id mockViewController = OCMClassMock([FlutterViewController class]); + id mockEngine = OCMClassMock([FlutterEngine class]); + id mockSceneDelegate = OCMProtocolMock(@protocol(UIWindowSceneDelegate)); + + OCMStub([mockScene delegate]).andReturn(mockSceneDelegate); + OCMStub([mockSceneDelegate window]).andReturn(mockWindow); + OCMStub([mockWindow rootViewController]).andReturn(mockViewController); + OCMStub([mockViewController engine]).andReturn(mockEngine); + + id session = OCMClassMock([UISceneSession class]); + id options = OCMClassMock([UISceneConnectionOptions class]); + + [mockDelegate scene:mockScene willConnectToSession:session options:options]; + [mockDelegate engine:mockEngine receivedConnectNotificationFor:mockScene]; + + OCMVerify(times(2), [mockDelegate addFlutterManagedEngine:mockEngine]); + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); + OCMVerify(times(1), [mockDelegate scene:mockScene + willConnectToSession:session + flutterEngine:mockEngine + options:options]); +} + - (void)testSceneDidDisconnect { FlutterPluginSceneLifeCycleDelegate* delegate = [[FlutterPluginSceneLifeCycleDelegate alloc] init]; @@ -400,8 +558,8 @@ - (void)testSceneDidDisconnect { id mockScene = mocks[@"mockScene"]; id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"]; - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate sceneDidDisconnect:mockScene]; OCMVerify(times(1), [mockLifecycleDelegate sceneDidDisconnect:mockScene]); @@ -417,8 +575,8 @@ - (void)testSceneWillEnterForeground { id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"]; id mockAppLifecycleDelegate = mocks[@"mockAppLifecycleDelegate"]; - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate sceneWillEnterForeground:mockScene]; OCMVerify(times(1), [mockLifecycleDelegate sceneWillEnterForeground:mockScene]); @@ -435,8 +593,8 @@ - (void)testSceneDidBecomeActive { id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"]; id mockAppLifecycleDelegate = mocks[@"mockAppLifecycleDelegate"]; - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate sceneDidBecomeActive:mockScene]; OCMVerify(times(1), [mockLifecycleDelegate sceneDidBecomeActive:mockScene]); @@ -453,8 +611,8 @@ - (void)testSceneWillResignActive { id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"]; id mockAppLifecycleDelegate = mocks[@"mockAppLifecycleDelegate"]; - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate sceneWillResignActive:mockScene]; OCMVerify(times(1), [mockLifecycleDelegate sceneWillResignActive:mockScene]); @@ -471,8 +629,8 @@ - (void)testSceneDidEnterBackground { id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"]; id mockAppLifecycleDelegate = mocks[@"mockAppLifecycleDelegate"]; - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate sceneDidEnterBackground:mockScene]; OCMVerify(times(1), [mockLifecycleDelegate sceneDidEnterBackground:mockScene]); @@ -495,8 +653,8 @@ - (void)testSceneOpenURLContextsHandledByScenePlugin { id urlContext = OCMClassMock([UIOpenURLContext class]); NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene openURLContexts:urlContexts]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene openURLContexts:urlContexts]); @@ -519,8 +677,8 @@ - (void)testSceneOpenURLContextsHandledByApplicationPlugin { id urlContext = OCMClassMock([UIOpenURLContext class]); NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene openURLContexts:urlContexts]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene openURLContexts:urlContexts]); @@ -547,8 +705,8 @@ - (void)testSceneOpenURLContextsHandledByDeeplink { OCMStub([urlContext URL]).andReturn(url); NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene openURLContexts:urlContexts]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene openURLContexts:urlContexts]); @@ -574,8 +732,8 @@ - (void)testSceneOpenURLContextsHandledByNoPlugin { id urlContext = OCMClassMock([UIOpenURLContext class]); NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene openURLContexts:urlContexts]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene openURLContexts:urlContexts]); @@ -610,8 +768,8 @@ - (void)testSceneOpenURLContextsWithMultipleEnginesSomeHandledByPlugin { OCMStub([urlContext URL]).andReturn(url); NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; - [delegate addFlutterEngine:mockEngine1]; - [delegate addFlutterEngine:mockEngine2]; + [delegate addFlutterManagedEngine:mockEngine1]; + [delegate addFlutterManagedEngine:mockEngine2]; [delegate scene:mockScene openURLContexts:urlContexts]; @@ -651,8 +809,8 @@ - (void)testSceneOpenURLContextsWithMultipleEnginesHandledByApplication { OCMStub([urlContext URL]).andReturn(url); NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; - [delegate addFlutterEngine:mockEngine1]; - [delegate addFlutterEngine:mockEngine2]; + [delegate addFlutterManagedEngine:mockEngine1]; + [delegate addFlutterManagedEngine:mockEngine2]; [delegate scene:mockScene openURLContexts:urlContexts]; @@ -692,8 +850,8 @@ - (void)testSceneOpenURLContextsWithMultipleEnginesHandledByDeeplink { OCMStub([urlContext URL]).andReturn(url); NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; - [delegate addFlutterEngine:mockEngine1]; - [delegate addFlutterEngine:mockEngine2]; + [delegate addFlutterManagedEngine:mockEngine1]; + [delegate addFlutterManagedEngine:mockEngine2]; [delegate scene:mockScene openURLContexts:urlContexts]; @@ -720,8 +878,8 @@ - (void)testSceneContinueUserActivityHandledByScenePlugin { id userActivity = OCMClassMock([NSUserActivity class]); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene continueUserActivity:userActivity]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene continueUserActivity:userActivity]); @@ -744,8 +902,8 @@ - (void)testSceneContinueUserActivityHandledByApplicationPlugin { id userActivity = OCMClassMock([NSUserActivity class]); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene continueUserActivity:userActivity]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene continueUserActivity:userActivity]); @@ -772,8 +930,8 @@ - (void)testSceneContinueUserActivityHandledByUniversalLinks { id userActivity = OCMClassMock([NSUserActivity class]); OCMStub([userActivity webpageURL]).andReturn(url); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene continueUserActivity:userActivity]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene continueUserActivity:userActivity]); @@ -806,8 +964,8 @@ - (void)testStateRestorationActivityForScene { OCMStub([mockSession configuration]).andReturn(mockConfiguration); OCMStub([mockConfiguration name]).andReturn(configName); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); NSUserActivity* state = [delegate stateRestorationActivityForScene:mockScene]; XCTAssertEqual(state.userInfo[restorationId], mockData); XCTAssertEqual(state.activityType, configName); @@ -850,8 +1008,8 @@ - (void)testSceneRestoreInteractionStateWithUserActivity { }; OCMStub([userActivity userInfo]).andReturn(mockUserInfo); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene restoreInteractionStateWithUserActivity:userActivity]; OCMVerify(times(1), [mockRestorationPlugin setRestorationData:mockData]); [mockBundle stopMocking]; @@ -894,8 +1052,8 @@ - (void)testSceneDoesNotRestoreInteractionStateWithUserActivity { }; OCMStub([userActivity userInfo]).andReturn(mockUserInfo); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene restoreInteractionStateWithUserActivity:userActivity]; OCMVerify(times(0), [mockRestorationPlugin setRestorationData:mockData]); [mockBundle stopMocking]; @@ -921,8 +1079,8 @@ - (void)testSceneContinueUserActivityHandledByNoPlugin { id userActivity = OCMClassMock([NSUserActivity class]); OCMStub([userActivity webpageURL]).andReturn(url); - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate scene:mockScene continueUserActivity:userActivity]; OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene continueUserActivity:userActivity]); @@ -952,8 +1110,8 @@ - (void)testWindowScenePerformActionForShortcutItemHandledByScenePlugin { id handler = ^(BOOL succeeded) { }; - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate windowScene:mockScene performActionForShortcutItem:shortcutItem @@ -988,8 +1146,8 @@ - (void)testWindowScenePerformActionForShortcutItemHandledByApplicationPlugin { id handler = ^(BOOL succeeded) { }; - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate windowScene:mockScene performActionForShortcutItem:shortcutItem @@ -1024,8 +1182,8 @@ - (void)testWindowScenePerformActionForShortcutItemHandledByNoPlugin { id handler = ^(BOOL succeeded) { }; - [delegate addFlutterEngine:mockEngine]; - XCTAssertEqual(delegate.engines.count, 1.0); + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); [delegate windowScene:mockScene performActionForShortcutItem:shortcutItem diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h index c7ac6bb35455c..749c73b183fa3 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h @@ -14,9 +14,21 @@ */ @interface FlutterPluginSceneLifeCycleDelegate () -- (void)addFlutterEngine:(FlutterEngine*)engine; +/** + * Associates the `FlutterEngine` with the `FlutterPluginSceneLifeCycleDelegate` so that it will + * forward scene events to the plugins within the engine. + * + * Returns NO if the engine is already associated with the delegate. + */ +- (BOOL)addFlutterManagedEngine:(FlutterEngine*)engine; -- (void)removeFlutterEngine:(FlutterEngine*)engine; +/** + * Removes the `FlutterEngine` from the `FlutterPluginSceneLifeCycleDelegate` so that it will no + * longer forward scene events to the plugins within the engine. + * + * Returns NO if the engine is not associated with the delegate. + */ +- (BOOL)removeFlutterManagedEngine:(FlutterEngine*)engine; - (void)engine:(FlutterEngine*)engine receivedConnectNotificationFor:(UIScene*)scene; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Test.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Test.h index 9657554cd8f26..265a34d3f2f60 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Test.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Test.h @@ -11,9 +11,15 @@ // Category to add test-only visibility. @interface FlutterPluginSceneLifeCycleDelegate (Test) @property(nonatomic, strong) UISceneConnectionOptions* connectionOptions; -@property(nonatomic, strong) NSPointerArray* engines; +@property(nonatomic, strong) NSPointerArray* flutterManagedEngines; +@property(nonatomic, strong) NSPointerArray* developerManagedEngines; -- (void)updateEnginesInScene:(UIScene*)scene; +- (void)updateFlutterManagedEnginesInScene:(UIScene*)scene; +- (void)scene:(UIScene*)scene + willConnectToSession:(UISceneSession*)session + flutterEngine:(FlutterEngine*)engine + options:(UISceneConnectionOptions*)connectionOptions; +- (NSArray*)allEngines; @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm index 8262b6733b2c8..76ee723f9248f 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterView.mm @@ -13,6 +13,7 @@ @interface FlutterView () @property(nonatomic, weak) id delegate; +@property(nonatomic, weak) UIWindowScene* previousScene; @end @implementation FlutterView { @@ -246,26 +247,31 @@ - (void)willMoveToWindow:(UIWindow*)newWindow { // When a FlutterView moves windows, it may also be moving scenes. Add/remove the FlutterEngine // from the FlutterSceneLifeCycleProvider.sceneLifeCycleDelegate if it changes scenes. UIWindowScene* newScene = newWindow.windowScene; - UIWindowScene* previousScene = self.window.windowScene; - if (newScene == previousScene) { + UIWindowScene* currentScene = self.window.windowScene; + + if (newScene == currentScene) { return; } - FlutterPluginSceneLifeCycleDelegate* newSceneLifeCycleDelegate = - [FlutterPluginSceneLifeCycleDelegate fromScene:newScene]; - if (newSceneLifeCycleDelegate != nil) { - return [newSceneLifeCycleDelegate addFlutterEngine:(FlutterEngine*)self.delegate]; - } - // The window, and therefore windowScene, property may be nil if the receiver does not currently - // reside in any window. This occurs when the receiver has just been removed from its superview - // or when the receiver has just been added to a superview that is not attached to a window. - // Remove the engine from the previous scene if set since it is no longer in that window and - // scene. + // Remove the engine from the previous scene if it's no longer in that window and scene. FlutterPluginSceneLifeCycleDelegate* previousSceneLifeCycleDelegate = - [FlutterPluginSceneLifeCycleDelegate fromScene:previousScene]; - if (previousSceneLifeCycleDelegate != nil) { - return [previousSceneLifeCycleDelegate removeFlutterEngine:(FlutterEngine*)self.delegate]; + [FlutterPluginSceneLifeCycleDelegate fromScene:self.previousScene]; + if (previousSceneLifeCycleDelegate) { + [previousSceneLifeCycleDelegate removeFlutterManagedEngine:(FlutterEngine*)self.delegate]; + self.previousScene = nil; } -} + if (newScene) { + // Add the engine to the new scene's lifecycle delegate. + FlutterPluginSceneLifeCycleDelegate* newSceneLifeCycleDelegate = + [FlutterPluginSceneLifeCycleDelegate fromScene:newScene]; + if (newSceneLifeCycleDelegate) { + [newSceneLifeCycleDelegate addFlutterManagedEngine:(FlutterEngine*)self.delegate]; + } + } else { + // If the view is being removed from a window, store the current scene to remove the engine + // from it later when the view is added to a new window. + self.previousScene = currentScene; + } +} @end diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm index 1fcc384de29d9..5e67acd73735d 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterViewTest.mm @@ -79,8 +79,8 @@ - (void)testViewWillMoveToWindow { id mockWindow = mocks[@"mockWindow"]; [view willMoveToWindow:mockWindow]; - OCMVerify(times(1), [mockLifecycleDelegate addFlutterEngine:mockEngine]); - XCTAssertEqual(lifecycleDelegate.engines.count, 1.0); + OCMVerify(times(1), [mockLifecycleDelegate addFlutterManagedEngine:mockEngine]); + XCTAssertEqual(lifecycleDelegate.flutterManagedEngines.count, 1.0); } - (void)testViewWillMoveToSameWindow { @@ -95,8 +95,8 @@ - (void)testViewWillMoveToSameWindow { [view willMoveToWindow:mockWindow]; [view willMoveToWindow:mockWindow]; - OCMVerify(times(2), [mockLifecycleDelegate addFlutterEngine:mockEngine]); - XCTAssertEqual(lifecycleDelegate.engines.count, 1.0); + OCMVerify(times(2), [mockLifecycleDelegate addFlutterManagedEngine:mockEngine]); + XCTAssertEqual(lifecycleDelegate.flutterManagedEngines.count, 1.0); } - (void)testMultipleViewsWillMoveToSameWindow { @@ -116,9 +116,9 @@ - (void)testMultipleViewsWillMoveToSameWindow { [view1 willMoveToWindow:mockWindow1]; [view2 willMoveToWindow:mockWindow1]; [view1 willMoveToWindow:mockWindow1]; - OCMVerify(times(2), [mockLifecycleDelegate addFlutterEngine:mockEngine1]); - OCMVerify(times(1), [mockLifecycleDelegate addFlutterEngine:mockEngine2]); - XCTAssertEqual(lifecycleDelegate.engines.count, 2.0); + OCMVerify(times(2), [mockLifecycleDelegate addFlutterManagedEngine:mockEngine1]); + OCMVerify(times(1), [mockLifecycleDelegate addFlutterManagedEngine:mockEngine2]); + XCTAssertEqual(lifecycleDelegate.flutterManagedEngines.count, 2.0); } - (void)testMultipleViewsWillMoveToDifferentWindow { @@ -141,21 +141,13 @@ - (void)testMultipleViewsWillMoveToDifferentWindow { [view1 willMoveToWindow:mockWindow1]; [view2 willMoveToWindow:mockWindow2]; [view1 willMoveToWindow:mockWindow1]; - OCMVerify(times(2), [mockLifecycleDelegate1 addFlutterEngine:mockEngine1]); - OCMVerify(times(1), [mockLifecycleDelegate2 addFlutterEngine:mockEngine2]); - XCTAssertEqual(lifecycleDelegate1.engines.count, 1.0); - XCTAssertEqual(lifecycleDelegate2.engines.count, 1.0); + OCMVerify(times(2), [mockLifecycleDelegate1 addFlutterManagedEngine:mockEngine1]); + OCMVerify(times(1), [mockLifecycleDelegate2 addFlutterManagedEngine:mockEngine2]); + XCTAssertEqual(lifecycleDelegate1.flutterManagedEngines.count, 1.0); + XCTAssertEqual(lifecycleDelegate2.flutterManagedEngines.count, 1.0); } -- (void)testNilWindowForViewWhenNoPrevious { - id mockEngine = OCMClassMock([FlutterEngine class]); - FlutterView* view = [[FlutterView alloc] initWithDelegate:mockEngine - opaque:NO - enableWideGamut:NO]; - [view willMoveToWindow:nil]; -} - -- (void)testNilWindowForViewWhenPrevious { +- (void)testViewRemovedFromWindowAndAddedToNewScene { NSDictionary* mocks = [self createWindowMocks]; FlutterView* view = (FlutterView*)mocks[@"view"]; id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"]; @@ -164,32 +156,28 @@ - (void)testNilWindowForViewWhenPrevious { id mockEngine = mocks[@"mockEngine"]; id mockWindow = mocks[@"mockWindow"]; - id mockView = OCMPartialMock(view); - OCMStub([mockView window]).andReturn(mockWindow); - - [mockView willMoveToWindow:nil]; - - OCMVerify(times(1), [mockLifecycleDelegate removeFlutterEngine:mockEngine]); - XCTAssertEqual(lifecycleDelegate.engines.count, 0.0); -} - -- (void)testViewWillMoveToWindowWhenPreviousEqualsNew { - NSDictionary* mocks = [self createWindowMocks]; - FlutterView* view = (FlutterView*)mocks[@"view"]; - id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"]; - FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate = - (FlutterPluginSceneLifeCycleDelegate*)mocks[@"lifecycleDelegate"]; - id mockEngine = mocks[@"mockEngine"]; - id mockWindow = mocks[@"mockWindow"]; + NSDictionary* mocks2 = [self createWindowMocks]; + id mockWindow2 = mocks2[@"mockWindow"]; + id mockLifecycleDelegate2 = mocks2[@"mockLifecycleDelegate"]; + FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate2 = + (FlutterPluginSceneLifeCycleDelegate*)mocks2[@"lifecycleDelegate"]; id mockView = OCMPartialMock(view); - OCMStub([mockView window]).andReturn(mockWindow); [mockView willMoveToWindow:mockWindow]; + OCMVerify(times(1), [mockLifecycleDelegate addFlutterManagedEngine:mockEngine]); + XCTAssertEqual(lifecycleDelegate.flutterManagedEngines.count, 1.0); - OCMVerify(times(0), [mockLifecycleDelegate addFlutterEngine:mockEngine]); - OCMVerify(times(0), [mockLifecycleDelegate removeFlutterEngine:[OCMArg any]]); - XCTAssertEqual(lifecycleDelegate.engines.count, 0.0); + OCMStub([mockView window]).andReturn(mockWindow); + [mockView willMoveToWindow:nil]; + XCTAssertEqual(lifecycleDelegate.flutterManagedEngines.count, 1.0); + + OCMStub([mockView window]).andReturn(nil); + [mockView willMoveToWindow:mockWindow2]; + OCMVerify(times(1), [mockLifecycleDelegate removeFlutterManagedEngine:mockEngine]); + XCTAssertEqual(lifecycleDelegate.flutterManagedEngines.count, 0.0); + OCMVerify(times(1), [mockLifecycleDelegate2 addFlutterManagedEngine:mockEngine]); + XCTAssertEqual(lifecycleDelegate2.flutterManagedEngines.count, 1.0); } - (NSDictionary*)createWindowMocks { From 38dc1c88e931d15121274bc93ddfe2160a178264 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Mon, 6 Oct 2025 23:37:53 -0400 Subject: [PATCH 092/204] [ Widget Preview ] Rework UI and theming (#176581) This change consists of several UI changes: - Added theming support for light and dark mode based on system preferences - Pulled in a subset of the DevTools theming constants - Reduced size of widget preview buttons and controls - Added initial support for setting theme based on IDE parameters passed as query parameters. This does not include responding to theme changes made after the widget previewer is loaded. - Fixed issue where the `WidgetPreviewErrorWidget` would still show controls, even though they aren't relevant **IDE Theming Example:** image --- .../preview_pubspec_builder.dart | 2 + .../templates/template_manifest.json | 8 + .../lib/src/controls.dart.tmpl | 124 +++--- .../src/theme/_ide_theme_desktop.dart.tmpl | 10 + .../lib/src/theme/_ide_theme_web.dart.tmpl | 43 +++ .../lib/src/theme/ide_theme.dart.tmpl | 46 +++ .../lib/src/theme/theme.dart.tmpl | 360 ++++++++++++++++++ .../lib/src/utils.dart.tmpl | 23 +- .../lib/src/utils/color_utils.dart.tmpl | 70 ++++ .../lib/src/utils/url/_url_stub.dart.tmpl | 24 ++ .../lib/src/utils/url/_url_web.dart.tmpl | 36 ++ .../lib/src/utils/url/url.dart.tmpl | 7 + .../src/widget_preview_rendering.dart.tmpl | 106 +++--- .../lib/src/controls.dart | 124 +++--- .../lib/src/theme/_ide_theme_desktop.dart | 10 + .../lib/src/theme/_ide_theme_web.dart | 43 +++ .../lib/src/theme/ide_theme.dart | 46 +++ .../lib/src/theme/theme.dart | 360 ++++++++++++++++++ .../lib/src/utils.dart | 23 +- .../lib/src/utils/color_utils.dart | 70 ++++ .../lib/src/utils/url/_url_stub.dart | 24 ++ .../lib/src/utils/url/_url_web.dart | 36 ++ .../lib/src/utils/url/url.dart | 7 + .../lib/src/widget_preview_rendering.dart | 106 +++--- .../test/ide_theme_test.dart | 57 +++ .../test/theme_test.dart | 70 ++++ 26 files changed, 1557 insertions(+), 278 deletions(-) create mode 100644 packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/_ide_theme_desktop.dart.tmpl create mode 100644 packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/_ide_theme_web.dart.tmpl create mode 100644 packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/ide_theme.dart.tmpl create mode 100644 packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/theme.dart.tmpl create mode 100644 packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/color_utils.dart.tmpl create mode 100644 packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/url/_url_stub.dart.tmpl create mode 100644 packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/url/_url_web.dart.tmpl create mode 100644 packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/url/url.dart.tmpl create mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/_ide_theme_desktop.dart create mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/_ide_theme_web.dart create mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/ide_theme.dart create mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/theme.dart create mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/color_utils.dart create mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/url/_url_stub.dart create mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/url/_url_web.dart create mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/url/url.dart create mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/ide_theme_test.dart create mode 100644 packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/theme_test.dart diff --git a/packages/flutter_tools/lib/src/widget_preview/preview_pubspec_builder.dart b/packages/flutter_tools/lib/src/widget_preview/preview_pubspec_builder.dart index bd4070f12bce3..082f875f3c5a0 100644 --- a/packages/flutter_tools/lib/src/widget_preview/preview_pubspec_builder.dart +++ b/packages/flutter_tools/lib/src/widget_preview/preview_pubspec_builder.dart @@ -45,6 +45,7 @@ class PreviewPubspecBuilder { /// - stack_trace, which is used to generate terse stack traces for displaying errors thrown /// by widgets being previewed. /// - url_launcher, which is used to open a browser to the preview documentation. + /// - web, which is used to access query parameters provided by the IDE. static const _kWidgetPreviewScaffoldDeps = [ 'dtd', 'flutter_lints', @@ -53,6 +54,7 @@ class PreviewPubspecBuilder { 'path', 'stack_trace', 'url_launcher', + 'web', ]; /// Maps asset URIs to relative paths for the widget preview project to diff --git a/packages/flutter_tools/templates/template_manifest.json b/packages/flutter_tools/templates/template_manifest.json index d27b35e4b6236..d975f14561d97 100644 --- a/packages/flutter_tools/templates/template_manifest.json +++ b/packages/flutter_tools/templates/template_manifest.json @@ -349,6 +349,14 @@ "templates/widget_preview_scaffold/lib/src/dtd/dtd_services.dart.tmpl", "templates/widget_preview_scaffold/lib/src/dtd/editor_service.dart.tmpl", "templates/widget_preview_scaffold/lib/src/generated_preview.dart.tmpl", + "templates/widget_preview_scaffold/lib/src/theme/_ide_theme_desktop.dart.tmpl", + "templates/widget_preview_scaffold/lib/src/theme/_ide_theme_web.dart.tmpl", + "templates/widget_preview_scaffold/lib/src/theme/ide_theme.dart.tmpl", + "templates/widget_preview_scaffold/lib/src/theme/theme.dart.tmpl", + "templates/widget_preview_scaffold/lib/src/utils/url/_url_stub.dart.tmpl", + "templates/widget_preview_scaffold/lib/src/utils/url/_url_web.dart.tmpl", + "templates/widget_preview_scaffold/lib/src/utils/url/url.dart.tmpl", + "templates/widget_preview_scaffold/lib/src/utils/color_utils.dart.tmpl", "templates/widget_preview_scaffold/lib/src/utils.dart.tmpl", "templates/widget_preview_scaffold/pubspec.yaml.tmpl", "templates/widget_preview_scaffold/README.md.tmpl", diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/controls.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/controls.dart.tmpl index b98b2777e463d..bc2129d100732 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/controls.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/controls.dart.tmpl @@ -3,36 +3,9 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'widget_preview_scaffold_controller.dart'; - -class _WidgetPreviewIconButton extends StatelessWidget { - const _WidgetPreviewIconButton({ - required this.tooltip, - required this.onPressed, - required this.icon, - }); - - final String tooltip; - final void Function()? onPressed; - final IconData icon; - @override - Widget build(BuildContext context) { - return Tooltip( - message: tooltip, - child: Ink( - decoration: ShapeDecoration( - shape: const CircleBorder(), - color: onPressed != null ? Colors.lightBlue : Colors.grey, - ), - child: IconButton( - onPressed: onPressed, - icon: Icon(color: Colors.white, icon), - ), - ), - ); - } -} +import 'theme/theme.dart'; +import 'widget_preview_scaffold_controller.dart'; /// Provides controls to change the zoom level of a [WidgetPreview]. class ZoomControls extends StatelessWidget { @@ -40,35 +13,37 @@ class ZoomControls extends StatelessWidget { const ZoomControls({ super.key, required TransformationController transformationController, - required this.enabled, }) : _transformationController = transformationController; final TransformationController _transformationController; - final bool enabled; @override Widget build(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - _WidgetPreviewIconButton( - tooltip: 'Zoom in', - onPressed: enabled ? _zoomIn : null, - icon: Icons.zoom_in, - ), - const SizedBox(width: 10), - _WidgetPreviewIconButton( - tooltip: 'Zoom out', - onPressed: enabled ? _zoomOut : null, - icon: Icons.zoom_out, - ), - const SizedBox(width: 10), - _WidgetPreviewIconButton( - tooltip: 'Reset zoom', - onPressed: enabled ? _reset : null, - icon: Icons.zoom_out_map, - ), - ], + const iconColor = Colors.black; + return _ControlDecorator( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + tooltip: 'Zoom in', + onPressed: _zoomIn, + icon: Icon(Icons.zoom_in_sharp), + color: iconColor, + ), + IconButton( + tooltip: 'Zoom out', + onPressed: _zoomOut, + icon: Icon(Icons.zoom_out), + color: iconColor, + ), + IconButton( + tooltip: 'Reset zoom', + onPressed: _reset, + icon: Icon(Icons.zoom_out_map), + color: iconColor, + ), + ], + ), ); } @@ -105,10 +80,10 @@ class _ControlDecorator extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: EdgeInsets.all(8.0), + padding: EdgeInsets.all(densePadding), decoration: BoxDecoration( color: Colors.grey[300], - borderRadius: BorderRadius.circular(8.0), + borderRadius: defaultBorderRadius, ), child: child, ); @@ -123,6 +98,7 @@ class LayoutTypeSelector extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); return _ControlDecorator( child: ValueListenableBuilder( valueListenable: controller.layoutTypeListenable, @@ -130,6 +106,8 @@ class LayoutTypeSelector extends StatelessWidget { return Row( children: [ IconButton( + style: theme.iconButtonTheme.style, + visualDensity: VisualDensity.compact, onPressed: () => controller.layoutType = LayoutType.gridView, icon: Icon(Icons.grid_on), color: selectedLayout == LayoutType.gridView @@ -138,6 +116,7 @@ class LayoutTypeSelector extends StatelessWidget { ), IconButton( onPressed: () => controller.layoutType = LayoutType.listView, + visualDensity: VisualDensity.compact, icon: Icon(Icons.view_list), color: selectedLayout == LayoutType.listView ? Colors.blue @@ -195,21 +174,19 @@ class FilterBySelectedFileToggle extends StatelessWidget { /// re-inserting it on the next frame. This has the effect of re-running local initializers in /// State objects, which normally requires a hot restart to accomplish in a normal application. class SoftRestartButton extends StatelessWidget { - const SoftRestartButton({ - super.key, - required this.enabled, - required this.softRestartListenable, - }); + const SoftRestartButton({super.key, required this.softRestartListenable}); final ValueNotifier softRestartListenable; - final bool enabled; @override Widget build(BuildContext context) { - return _WidgetPreviewIconButton( - tooltip: 'Hot restart', - onPressed: enabled ? _onRestart : null, - icon: Icons.refresh, + return _ControlDecorator( + child: IconButton( + tooltip: 'Hot restart', + onPressed: _onRestart, + icon: Icon(Icons.refresh), + color: Colors.black, + ), ); } @@ -232,6 +209,7 @@ class WidgetPreviewerRestartButton extends StatelessWidget { tooltip: 'Restart the Widget Previewer', onPressed: controller.dtdServices.hotRestartPreviewer, icon: Icon(Icons.restart_alt), + color: Colors.black, ), ); } @@ -244,14 +222,9 @@ extension on Brightness { /// A button that toggles the current theme brightness. class BrightnessToggleButton extends StatelessWidget { - const BrightnessToggleButton({ - super.key, - required this.enabled, - required this.brightnessListenable, - }); + const BrightnessToggleButton({super.key, required this.brightnessListenable}); final ValueNotifier brightnessListenable; - final bool enabled; @override Widget build(BuildContext context) { @@ -259,10 +232,13 @@ class BrightnessToggleButton extends StatelessWidget { valueListenable: brightnessListenable, builder: (context, brightness, _) { final brightness = brightnessListenable.value; - return _WidgetPreviewIconButton( - tooltip: 'Switch to ${brightness.isLight ? 'dark' : 'light'} mode', - onPressed: enabled ? _toggleBrightness : null, - icon: brightness.isLight ? Icons.dark_mode : Icons.light_mode, + return _ControlDecorator( + child: IconButton( + tooltip: 'Switch to ${brightness.isLight ? 'dark' : 'light'} mode', + onPressed: _toggleBrightness, + icon: Icon(brightness.isLight ? Icons.dark_mode : Icons.light_mode), + color: Colors.black, + ), ); }, ); diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/_ide_theme_desktop.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/_ide_theme_desktop.dart.tmpl new file mode 100644 index 0000000000000..6c8d16f6814b5 --- /dev/null +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/_ide_theme_desktop.dart.tmpl @@ -0,0 +1,10 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// NOTE: originally from package:devtools_app_shared + +import 'ide_theme.dart'; + +/// Load any IDE-supplied theming. +IdeTheme getIdeTheme() => IdeTheme(); diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/_ide_theme_web.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/_ide_theme_web.dart.tmpl new file mode 100644 index 0000000000000..d0461424ca070 --- /dev/null +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/_ide_theme_web.dart.tmpl @@ -0,0 +1,43 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// NOTE: originally from package:devtools_app_shared + +import 'dart:ui'; + +import 'package:web/web.dart'; + +import '../utils/url/url.dart'; +import 'ide_theme.dart'; + +/// Load any IDE-supplied theming. +IdeTheme getIdeTheme() { + final queryParams = IdeThemeQueryParams(loadQueryParams()); + + final overrides = IdeTheme( + backgroundColor: queryParams.backgroundColor, + foregroundColor: queryParams.foregroundColor, + isDarkMode: queryParams.darkMode, + ); + + // If the environment has provided a background color, set it immediately + // to avoid a white page until the first Flutter frame is rendered. + if (overrides.backgroundColor != null) { + document.body!.style.backgroundColor = toCssHexColor( + overrides.backgroundColor!, + ); + } + + return overrides; +} + +/// Converts a dart:ui Color into #RRGGBBAA format for use in CSS. +String toCssHexColor(Color color) { + // In CSS Hex, Alpha comes last, but in Flutter's `value` field, alpha is + // in the high bytes, so just using `value.toRadixString(16)` will put alpha + // in the wrong position. + String hex(double channelValue) => + (channelValue * 255).round().toRadixString(16).padLeft(2, '0'); + return '#${hex(color.r)}${hex(color.g)}${hex(color.b)}${hex(color.a)}'; +} diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/ide_theme.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/ide_theme.dart.tmpl new file mode 100644 index 0000000000000..6262838bf4587 --- /dev/null +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/ide_theme.dart.tmpl @@ -0,0 +1,46 @@ +// 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. + +// NOTE: originally from package:devtools_app_shared + +import 'package:flutter/widgets.dart'; + +import '../utils/color_utils.dart'; +import 'theme.dart'; + +export '_ide_theme_desktop.dart' + if (dart.library.js_interop) '_ide_theme_web.dart'; + +/// IDE-supplied theming. +final class IdeTheme { + const IdeTheme({this.backgroundColor, this.foregroundColor, bool? isDarkMode}) + : _isDarkMode = isDarkMode; + + final Color? backgroundColor; + final Color? foregroundColor; + final bool? _isDarkMode; + + bool get isDarkMode => _isDarkMode ?? useDarkThemeAsDefault; + + /// Whether the IDE specified the DevTools color theme. + /// + /// If this returns false, that means the + /// [IdeThemeQueryParams.devToolsThemeKey] query parameter was not passed to + /// DevTools from the IDE. + bool get ideSpecifiedTheme => _isDarkMode != null; +} + +extension type IdeThemeQueryParams(Map params) { + Color? get backgroundColor => tryParseColor(params[backgroundColorKey]); + + Color? get foregroundColor => tryParseColor(params[foregroundColorKey]); + + bool get darkMode => params[devToolsThemeKey] != lightThemeValue; + + static const backgroundColorKey = 'backgroundColor'; + static const foregroundColorKey = 'foregroundColor'; + static const devToolsThemeKey = 'theme'; + static const lightThemeValue = 'light'; + static const darkThemeValue = 'dark'; +} diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/theme.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/theme.dart.tmpl new file mode 100644 index 0000000000000..2741b782e2826 --- /dev/null +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/theme/theme.dart.tmpl @@ -0,0 +1,360 @@ +// 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. + +// NOTE: originally from package:devtools_app_shared + +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:widget_preview_scaffold/src/utils/color_utils.dart'; + +import 'ide_theme.dart'; + +// TODO(kenz): try to eliminate as many custom colors as possible, and pull +// colors only from the [lightColorScheme] and the [darkColorScheme]. + +/// Whether dark theme should be used as the default theme if none has been +/// explicitly set. +const useDarkThemeAsDefault = true; + +/// Constructs the light or dark theme for the app taking into account +/// IDE-supplied theming. +ThemeData themeFor({ + required bool isDarkTheme, + required IdeTheme ideTheme, + required ThemeData theme, +}) { + final colorTheme = isDarkTheme + ? _darkTheme(ideTheme: ideTheme, theme: theme) + : _lightTheme(ideTheme: ideTheme, theme: theme); + + return colorTheme.copyWith( + primaryTextTheme: theme.primaryTextTheme.merge(colorTheme.primaryTextTheme), + textTheme: theme.textTheme.merge(colorTheme.textTheme), + ); +} + +ThemeData _darkTheme({required IdeTheme ideTheme, required ThemeData theme}) { + final background = isValidDarkColor(ideTheme.backgroundColor) + ? ideTheme.backgroundColor! + : theme.colorScheme.surface; + return _baseTheme(theme: theme, backgroundColor: background); +} + +ThemeData _lightTheme({required IdeTheme ideTheme, required ThemeData theme}) { + final background = isValidLightColor(ideTheme.backgroundColor) + ? ideTheme.backgroundColor! + : theme.colorScheme.surface; + return _baseTheme(theme: theme, backgroundColor: background); +} + +ThemeData _baseTheme({ + required ThemeData theme, + required Color backgroundColor, +}) { + // TODO(kenz): do we need to pass in the foreground color from the [IdeTheme] + // as well as the background color? + const kCardRadius = Radius.circular(12); + return theme.copyWith( + tabBarTheme: theme.tabBarTheme.copyWith( + tabAlignment: TabAlignment.start, + labelStyle: theme.regularTextStyle, + labelPadding: const EdgeInsets.symmetric( + horizontal: defaultTabBarPadding, + ), + ), + canvasColor: backgroundColor, + scaffoldBackgroundColor: backgroundColor, + iconButtonTheme: IconButtonThemeData( + style: IconButton.styleFrom( + padding: const EdgeInsets.all(densePadding), + minimumSize: const Size(defaultButtonHeight, defaultButtonHeight), + fixedSize: const Size(defaultButtonHeight, defaultButtonHeight), + iconSize: defaultIconSize, + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + minimumSize: const Size(buttonMinWidth, defaultButtonHeight), + fixedSize: const Size.fromHeight(defaultButtonHeight), + foregroundColor: theme.colorScheme.onSurface, + padding: const EdgeInsets.symmetric(horizontal: denseSpacing), + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + padding: const EdgeInsets.all(densePadding), + minimumSize: const Size(buttonMinWidth, defaultButtonHeight), + fixedSize: const Size.fromHeight(defaultButtonHeight), + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + minimumSize: const Size(buttonMinWidth, defaultButtonHeight), + fixedSize: const Size.fromHeight(defaultButtonHeight), + backgroundColor: theme.colorScheme.primary, + foregroundColor: theme.colorScheme.onPrimary, + padding: const EdgeInsets.symmetric(horizontal: denseSpacing), + ), + ), + menuButtonTheme: MenuButtonThemeData( + style: ButtonStyle( + textStyle: WidgetStatePropertyAll(theme.regularTextStyle), + fixedSize: const WidgetStatePropertyAll(Size.fromHeight(24.0)), + ), + ), + expansionTileTheme: ExpansionTileThemeData( + backgroundColor: backgroundColor.brighten(), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(kCardRadius), + ), + collapsedShape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(kCardRadius), + ), + ), + listTileTheme: ListTileThemeData( + dense: true, + tileColor: backgroundColor.brighten(), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(kCardRadius), + ), + ), + dropdownMenuTheme: DropdownMenuThemeData(textStyle: theme.regularTextStyle), + primaryTextTheme: _devToolsTextTheme(theme, theme.primaryTextTheme), + textTheme: _devToolsTextTheme(theme, theme.textTheme), + colorScheme: theme.colorScheme.copyWith(surface: backgroundColor), + ); +} + +TextTheme _devToolsTextTheme(ThemeData theme, TextTheme textTheme) { + return textTheme.copyWith( + displayLarge: theme.boldTextStyle.copyWith(fontSize: 24), + displayMedium: theme.boldTextStyle.copyWith(fontSize: 22), + displaySmall: theme.boldTextStyle.copyWith(fontSize: 20), + headlineLarge: theme.regularTextStyle.copyWith( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + headlineMedium: theme.regularTextStyle.copyWith( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + headlineSmall: theme.regularTextStyle.copyWith( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + titleLarge: theme._largeText.copyWith(fontWeight: FontWeight.w500), + titleMedium: theme.regularTextStyle.copyWith(fontWeight: FontWeight.w500), + titleSmall: theme._smallText.copyWith(fontWeight: FontWeight.w500), + bodyLarge: theme._largeText, + bodyMedium: theme.regularTextStyle, + bodySmall: theme._smallText, + labelLarge: theme._largeText, + labelMedium: theme.regularTextStyle, + labelSmall: theme._smallText, + ); +} + +/// Light theme color scheme generated from DevTools Figma file. +/// +/// Do not manually change these values. +const lightColorScheme = ColorScheme( + brightness: Brightness.light, + primary: Color(0xFF195BB9), + onPrimary: Color(0xFFFFFFFF), + primaryContainer: Color(0xFFD8E2FF), + onPrimaryContainer: Color(0xFF001A41), + secondary: Color(0xFF575E71), + onSecondary: Color(0xFFFFFFFF), + secondaryContainer: Color(0xFFDBE2F9), + onSecondaryContainer: Color(0xFF141B2C), + tertiary: Color(0xFF815600), + onTertiary: Color(0xFFFFFFFF), + tertiaryContainer: Color(0xFFFFDDB1), + onTertiaryContainer: Color(0xFF291800), + error: Color(0xFFBA1A1A), + errorContainer: Color(0xFFFFDAD5), + onError: Color(0xFFFFFFFF), + onErrorContainer: Color(0xFF410002), + surface: Color(0xFFFFFFFF), + onSurface: Color(0xFF1B1B1F), + surfaceContainerHighest: Color(0xFFE1E2EC), + onSurfaceVariant: Color(0xFF44474F), + outline: Color(0xFF75777F), + onInverseSurface: Color(0xFFF2F0F4), + inverseSurface: Color(0xFF303033), + inversePrimary: Color(0xFFADC6FF), + shadow: Color(0xFF000000), + surfaceTint: Color(0xFF195BB9), + outlineVariant: Color(0xFFC4C6D0), + scrim: Color(0xFF000000), +); + +/// Dark theme color scheme generated from DevTools Figma file. +/// +/// Do not manually change these values. +const darkColorScheme = ColorScheme( + brightness: Brightness.dark, + primary: Color(0xFFADC6FF), + onPrimary: Color(0xFF002E69), + primaryContainer: Color(0xFF004494), + onPrimaryContainer: Color(0xFFD8E2FF), + secondary: Color(0xFFBFC6DC), + onSecondary: Color(0xFF293041), + secondaryContainer: Color(0xFF3F4759), + onSecondaryContainer: Color(0xFFDBE2F9), + tertiary: Color(0xFFFEBA4B), + onTertiary: Color(0xFF442B00), + tertiaryContainer: Color(0xFF624000), + onTertiaryContainer: Color(0xFFFFDDB1), + error: Color(0xFFFFB4AB), + errorContainer: Color(0xFF930009), + onError: Color(0xFF690004), + onErrorContainer: Color(0xFFFFDAD5), + surface: Color(0xFF1B1B1F), + onSurface: Color(0xFFC7C6CA), + surfaceContainerHighest: Color(0xFF44474F), + onSurfaceVariant: Color(0xFFC4C6D0), + outline: Color(0xFF8E9099), + onInverseSurface: Color(0xFF1B1B1F), + inverseSurface: Color(0xFFE3E2E6), + inversePrimary: Color(0xFF195BB9), + shadow: Color(0xFF000000), + surfaceTint: Color(0xFFADC6FF), + outlineVariant: Color(0xFF44474F), + scrim: Color(0xFF000000), +); + +/// Threshold used to determine whether a colour is light/dark enough for us to +/// override the default DevTools themes with. +/// +/// A value of 0.5 would result in all colours being considered light/dark, and +/// a value of 0.12 allowing around only the 12% darkest/lightest colours by +/// Flutter's luminance calculation. +/// 12% was chosen because VS Code's default light background color is #f3f3f3 +/// which is a little under 11%. +const _lightDarkLuminanceThreshold = 0.12; + +bool isValidDarkColor(Color? color) { + if (color == null) { + return false; + } + return color.computeLuminance() <= _lightDarkLuminanceThreshold; +} + +bool isValidLightColor(Color? color) { + if (color == null) { + return false; + } + return color.computeLuminance() >= 1 - _lightDarkLuminanceThreshold; +} + +// Size constants: +const defaultButtonHeight = 26.0; +const buttonMinWidth = 26.0; + +const defaultIconSize = 14.0; + +// Padding / spacing constants: +const extraLargeSpacing = 32.0; +const largeSpacing = 16.0; +const defaultSpacing = 12.0; +const intermediateSpacing = 10.0; +const denseSpacing = 8.0; + +const defaultTabBarPadding = 14.0; +const tabBarSpacing = 8.0; +const denseRowSpacing = 6.0; + +const densePadding = 4.0; + +// Other UI related constants: +final defaultBorderRadius = BorderRadius.circular(_defaultBorderRadiusValue); +const defaultRadius = Radius.circular(_defaultBorderRadiusValue); +const _defaultBorderRadiusValue = 16.0; + +const defaultElevation = 4.0; + +// Font size constants: +const largeFontSize = 14.0; +const defaultFontSize = 12.0; +const smallFontSize = 10.0; + +extension DevToolsSharedColorScheme on ColorScheme { + bool get isLight => brightness == Brightness.light; + + bool get isDark => brightness == Brightness.dark; + + Color get subtleTextColor => const Color(0xFF919094); + + Color get _devtoolsLink => + isLight ? const Color(0xFF1976D2) : Colors.lightBlueAccent; + + Color get tooltipTextColor => isLight ? Colors.white : Colors.black; +} + +/// Utility extension methods to the [ThemeData] class. +extension ThemeDataExtension on ThemeData { + /// Returns whether we are currently using a dark theme. + bool get isDarkTheme => brightness == Brightness.dark; + + TextStyle get regularTextStyle => fixBlurryText( + TextStyle(color: colorScheme.onSurface, fontSize: defaultFontSize), + ); + + TextStyle regularTextStyleWithColor(Color? color, {Color? backgroundColor}) => + regularTextStyle.copyWith(color: color, backgroundColor: backgroundColor); + + TextStyle get _smallText => + regularTextStyle.copyWith(fontSize: smallFontSize); + + TextStyle get _largeText => + regularTextStyle.copyWith(fontSize: largeFontSize); + + TextStyle get errorTextStyle => regularTextStyleWithColor(colorScheme.error); + + TextStyle get boldTextStyle => + regularTextStyle.copyWith(fontWeight: FontWeight.bold); + + TextStyle get subtleTextStyle => + regularTextStyle.copyWith(color: colorScheme.subtleTextColor); + + TextStyle get fixedFontStyle => fixBlurryText( + regularTextStyle.copyWith( + fontFamily: GoogleFonts.robotoMono().fontFamily, + // Slightly smaller for fixes font text since it will appear larger + // to begin with. + fontSize: defaultFontSize - 1, + ), + ); + + TextStyle get subtleFixedFontStyle => + fixedFontStyle.copyWith(color: colorScheme.subtleTextColor); + + TextStyle get selectedSubtleTextStyle => + subtleTextStyle.copyWith(color: colorScheme.onSurface); + + TextStyle get tooltipFixedFontStyle => + fixedFontStyle.copyWith(color: colorScheme.tooltipTextColor); + + TextStyle get fixedFontLinkStyle => fixedFontStyle.copyWith( + color: colorScheme._devtoolsLink, + decoration: TextDecoration.underline, + ); + + TextStyle get linkTextStyle => fixBlurryText( + TextStyle( + color: colorScheme._devtoolsLink, + decoration: TextDecoration.underline, + fontSize: defaultFontSize, + ), + ); +} + +/// Returns a [TextStyle] with [FontFeature.proportionalFigures] applied to +/// fix blurry text. +TextStyle fixBlurryText(TextStyle style) { + return style.copyWith( + fontFeatures: [const FontFeature.proportionalFigures()], + ); +} diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils.dart.tmpl index 05d0bef7315e0..7e0da163dc1f0 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils.dart.tmpl @@ -4,7 +4,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/widget_previews.dart'; -import 'package:widget_preview_scaffold/src/widget_preview.dart'; + +import 'widget_preview.dart'; Iterable buildMultiWidgetPreview({ required String packageName, @@ -75,26 +76,6 @@ WidgetPreview buildWidgetPreviewError({ ); } -/// Returns a [TextStyle] with [FontFeature.proportionalFigures] applied to -/// fix blurry text. -TextStyle fixBlurryText(TextStyle style) { - return style.copyWith( - fontFeatures: [const FontFeature.proportionalFigures()], - ); -} - -final TextStyle linkTextStyle = fixBlurryText( - underlineTextStyle.copyWith( - // TODO(bkonyi): this color scheme is from DevTools and should be responsive - // to changes in the previewer theme. - color: const Color(0xFF1976D2), - ), -); - -final TextStyle underlineTextStyle = fixBlurryText( - TextStyle(decoration: TextDecoration.underline), -); - /// A basic vertical spacer. class VerticalSpacer extends StatelessWidget { /// Creates a basic vertical spacer. diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/color_utils.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/color_utils.dart.tmpl new file mode 100644 index 0000000000000..03df6d7e2ddf8 --- /dev/null +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/color_utils.dart.tmpl @@ -0,0 +1,70 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +Color? tryParseColor(String? input) { + if (input == null) return null; + + try { + return parseCssHexColor(input); + } catch (e) { + return null; + } +} + +/// Parses a 3 or 6 digit CSS Hex Color into a dart:ui Color. +Color parseCssHexColor(String input) { + // Remove any leading # (and the escaped version to be lenient) + input = input.replaceAll('#', '').replaceAll('%23', ''); + + // Handle 3/4-digit hex codes (eg. #123 == #112233) + if (input.length == 3 || input.length == 4) { + input = input.split('').map((c) => '$c$c').join(); + } + + // Pad alpha with FF. + if (input.length == 6) { + input = '${input}ff'; + } + + // In CSS, alpha is in the lowest bits, but for Flutter's value, it's in the + // highest bits, so move the alpha from the end to the start before parsing. + if (input.length == 8) { + input = '${input.substring(6)}${input.substring(0, 6)}'; + } + final value = int.parse(input, radix: 16); + + return Color(value); +} + +/// Utility extension methods to the [Color] class. +extension ColorExtension on Color { + /// Return a slightly darker color than the current color. + Color darken([double percent = 0.05]) { + assert(0.0 <= percent && percent <= 1.0); + percent = 1.0 - percent; + + final c = this; + return Color.from( + alpha: c.a, + red: c.r * percent, + green: c.g * percent, + blue: c.b * percent, + ); + } + + /// Return a slightly brighter color than the current color. + Color brighten([double percent = 0.05]) { + assert(0.0 <= percent && percent <= 1.0); + + final c = this; + return Color.from( + alpha: c.a, + red: c.r + ((1.0 - c.r) * percent), + green: c.g + ((1.0 - c.g) * percent), + blue: c.b + ((1.0 - c.b) * percent), + ); + } +} diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/url/_url_stub.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/url/_url_stub.dart.tmpl new file mode 100644 index 0000000000000..51ebed0a1cb16 --- /dev/null +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/url/_url_stub.dart.tmpl @@ -0,0 +1,24 @@ +// 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. + +// NOTE: originally from package:devtools_app_shared + +Map loadQueryParams() => {}; + +/// Gets the URL from the browser. +/// +/// Returns null for non-web platforms. +String? getWebUrl() => null; + +/// Performs a web redirect using window.location.replace(). +/// +/// No-op for non-web platforms. +// Unused parameter lint doesn't make sense for stub files. +void webRedirect(String url) {} + +/// Updates the query parameter with [key] to the new [value], and optionally +/// reloads the page when [reload] is true. +/// +/// No-op for non-web platforms. +void updateQueryParameter(String key, String? value, {bool reload = false}) {} diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/url/_url_web.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/url/_url_web.dart.tmpl new file mode 100644 index 0000000000000..875a52b157128 --- /dev/null +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/url/_url_web.dart.tmpl @@ -0,0 +1,36 @@ +// 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. + +// NOTE: originally from package:devtools_app_shared + +import 'package:web/web.dart'; + +Map loadQueryParams({String Function(String)? urlModifier}) { + var url = getWebUrl()!; + url = urlModifier?.call(url) ?? url; + return Uri.parse(url).queryParameters; +} + +String? getWebUrl() => window.location.toString(); + +void webRedirect(String url) { + window.location.replace(url); +} + +void updateQueryParameter(String key, String? value, {bool reload = false}) { + final newQueryParams = Map.of(loadQueryParams()); + if (value == null) { + newQueryParams.remove(key); + } else { + newQueryParams[key] = value; + } + final newUri = Uri.parse( + window.location.toString(), + ).replace(queryParameters: newQueryParams); + window.history.replaceState(window.history.state, '', newUri.toString()); + + if (reload) { + window.location.reload(); + } +} diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/url/url.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/url/url.dart.tmpl new file mode 100644 index 0000000000000..7f15f82d57923 --- /dev/null +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/utils/url/url.dart.tmpl @@ -0,0 +1,7 @@ +// 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. + +// NOTE: originally from package:devtools_app_shared + +export '_url_stub.dart' if (dart.library.js_interop) '_url_web.dart'; diff --git a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl index 2d3c43ae74c57..2c560d2892db3 100644 --- a/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl +++ b/packages/flutter_tools/templates/widget_preview_scaffold/lib/src/widget_preview_rendering.dart.tmpl @@ -11,17 +11,18 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widget_previews.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:stack_trace/stack_trace.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; -import 'package:widget_preview_scaffold/src/widget_preview_inspector_service.dart'; +import 'dtd/editor_service.dart'; +import 'theme/ide_theme.dart'; +import 'theme/theme.dart'; import 'controls.dart'; import 'generated_preview.dart'; import 'utils.dart'; import 'widget_preview.dart'; +import 'widget_preview_inspector_service.dart'; import 'widget_preview_scaffold_controller.dart'; /// Displayed when an unhandled exception is thrown when initializing the widget @@ -51,39 +52,36 @@ class WidgetPreviewErrorWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final TextStyle boldStyle = fixBlurryText( - TextStyle(fontWeight: FontWeight.bold), - ); - final TextStyle monospaceStyle = GoogleFonts.robotoMono(); - + final theme = Theme.of(context); return SizedBox( height: size.height, child: SingleChildScrollView( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text.rich( TextSpan( children: [ TextSpan( text: 'Failed to initialize widget tree: ', - style: boldStyle, + style: theme.boldTextStyle, ), - TextSpan(text: error.toString(), style: monospaceStyle), + TextSpan(text: error.toString(), style: theme.fixedFontStyle), ], ), ), - Text('Stacktrace:', style: boldStyle), + Text('Stacktrace:', style: theme.boldTextStyle), ValueListenableBuilder( valueListenable: controller.editorServiceAvailable, builder: (context, editorServiceAvailable, child) { return SelectableText.rich( TextSpan( children: _formatFrames( + theme, trace.frames, editorServiceAvailable, ), - style: monospaceStyle, + style: theme.fixedFontStyle, ), ); }, @@ -95,6 +93,7 @@ class WidgetPreviewErrorWidget extends StatelessWidget { } List _formatFrames( + ThemeData theme, List frames, bool editorServiceAvailable, ) { @@ -112,11 +111,14 @@ class WidgetPreviewErrorWidget extends StatelessWidget { final isLinkable = (frame.uri.isScheme('file') || frame.uri.isScheme('package')) && editorServiceAvailable; + final style = isLinkable + ? theme.fixedFontLinkStyle + : theme.fixedFontStyle; return TextSpan( children: [ TextSpan( text: frame.location, - style: isLinkable ? linkTextStyle : underlineTextStyle, + style: style, recognizer: isLinkable ? (TapGestureRecognizer() ..onTap = () async { @@ -134,7 +136,7 @@ class WidgetPreviewErrorWidget extends StatelessWidget { ), TextSpan(text: ' ' * (longest - frame.location.length)), const TextSpan(text: ' '), - TextSpan(text: '${frame.member}\n'), + TextSpan(text: '${frame.member}\n', style: style), ], ); }).toList(); @@ -151,27 +153,22 @@ class NoPreviewsDetectedWidget extends StatelessWidget { @override Widget build(BuildContext context) { - // TODO(bkonyi): base this on the current color theme (dark vs light) - final style = fixBlurryText(TextStyle(color: Colors.black)); + final theme = Theme.of(context); return Center( child: Column( children: [ - Text( - 'No previews detected', - style: style.copyWith(fontWeight: FontWeight.bold), - ), + Text('No previews detected', style: theme.boldTextStyle), const VerticalSpacer(), Text('Read more about getting started with widget previews at:'), Text.rich( TextSpan( text: documentationUrl.toString(), - style: linkTextStyle, + style: theme.linkTextStyle, recognizer: TapGestureRecognizer() ..onTap = () { launchUrl(documentationUrl); }, ), - style: style, ), ], ), @@ -423,6 +420,10 @@ class WidgetPreviewWidgetState extends State { valueListenable: WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier, builder: (context, enableWidgetInspector, child) { + // Don't allow inspecting the error widget. + if (child is WidgetPreviewErrorWidget) { + return child; + } if (enableWidgetInspector) { return WidgetInspector( // TODO(bkonyi): wire up inspector controls for individual previews or @@ -506,12 +507,16 @@ class WidgetPreviewWidgetState extends State { child: preview, ), const VerticalSpacer(), - _WidgetPreviewControlRow( - transformationController: transformationController, - errorThrownDuringTreeConstruction: - errorThrownDuringTreeConstruction, - brightnessListenable: brightnessListenable, - softRestartListenable: softRestartListenable, + Builder( + builder: (context) { + return _WidgetPreviewControlRow( + transformationController: transformationController, + errorThrownDuringTreeConstruction: + errorThrownDuringTreeConstruction, + brightnessListenable: brightnessListenable, + softRestartListenable: softRestartListenable, + ); + }, ), ], ), @@ -546,26 +551,21 @@ class _WidgetPreviewControlRow extends StatelessWidget { @override Widget build(BuildContext context) { + // Don't show controls if an error occurred. + if (errorThrownDuringTreeConstruction) { + return Container(); + } return Row( mainAxisSize: MainAxisSize.min, // If an unhandled exception was caught and we're displaying an error // widget, these controls should be disabled. // TODO(bkonyi): improve layout of controls. children: [ - ZoomControls( - transformationController: transformationController, - enabled: !errorThrownDuringTreeConstruction, - ), + ZoomControls(transformationController: transformationController), const SizedBox(width: 30), - BrightnessToggleButton( - enabled: !errorThrownDuringTreeConstruction, - brightnessListenable: brightnessListenable, - ), + BrightnessToggleButton(brightnessListenable: brightnessListenable), const SizedBox(width: 10), - SoftRestartButton( - enabled: !errorThrownDuringTreeConstruction, - softRestartListenable: softRestartListenable, - ), + SoftRestartButton(softRestartListenable: softRestartListenable), ], ); } @@ -949,7 +949,10 @@ Future mainImpl() async { // Forces the set of previews to be recalculated after a hot reload. HotReloadListener( onHotReload: controller.onHotReload, - child: WidgetPreviewScaffold(controller: controller), + child: WidgetPreviewScaffold( + controller: controller, + ideTheme: getIdeTheme(), + ), ), ), ), @@ -957,16 +960,31 @@ Future mainImpl() async { } class WidgetPreviewScaffold extends StatelessWidget { - const WidgetPreviewScaffold({super.key, required this.controller}); + const WidgetPreviewScaffold({ + super.key, + required this.controller, + this.ideTheme = const IdeTheme(), + }); final WidgetPreviewScaffoldController controller; + final IdeTheme ideTheme; @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, + theme: themeFor( + isDarkTheme: false, + ideTheme: ideTheme, + theme: ThemeData(), + ), + darkTheme: themeFor( + isDarkTheme: true, + ideTheme: ideTheme, + theme: ThemeData.dark(), + ), + themeMode: ideTheme.isDarkMode ? ThemeMode.dark : ThemeMode.light, home: Material( - color: Colors.transparent, child: Column( children: [ // Display the previewer diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/controls.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/controls.dart index b98b2777e463d..bc2129d100732 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/controls.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/controls.dart @@ -3,36 +3,9 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'widget_preview_scaffold_controller.dart'; - -class _WidgetPreviewIconButton extends StatelessWidget { - const _WidgetPreviewIconButton({ - required this.tooltip, - required this.onPressed, - required this.icon, - }); - - final String tooltip; - final void Function()? onPressed; - final IconData icon; - @override - Widget build(BuildContext context) { - return Tooltip( - message: tooltip, - child: Ink( - decoration: ShapeDecoration( - shape: const CircleBorder(), - color: onPressed != null ? Colors.lightBlue : Colors.grey, - ), - child: IconButton( - onPressed: onPressed, - icon: Icon(color: Colors.white, icon), - ), - ), - ); - } -} +import 'theme/theme.dart'; +import 'widget_preview_scaffold_controller.dart'; /// Provides controls to change the zoom level of a [WidgetPreview]. class ZoomControls extends StatelessWidget { @@ -40,35 +13,37 @@ class ZoomControls extends StatelessWidget { const ZoomControls({ super.key, required TransformationController transformationController, - required this.enabled, }) : _transformationController = transformationController; final TransformationController _transformationController; - final bool enabled; @override Widget build(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - _WidgetPreviewIconButton( - tooltip: 'Zoom in', - onPressed: enabled ? _zoomIn : null, - icon: Icons.zoom_in, - ), - const SizedBox(width: 10), - _WidgetPreviewIconButton( - tooltip: 'Zoom out', - onPressed: enabled ? _zoomOut : null, - icon: Icons.zoom_out, - ), - const SizedBox(width: 10), - _WidgetPreviewIconButton( - tooltip: 'Reset zoom', - onPressed: enabled ? _reset : null, - icon: Icons.zoom_out_map, - ), - ], + const iconColor = Colors.black; + return _ControlDecorator( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + tooltip: 'Zoom in', + onPressed: _zoomIn, + icon: Icon(Icons.zoom_in_sharp), + color: iconColor, + ), + IconButton( + tooltip: 'Zoom out', + onPressed: _zoomOut, + icon: Icon(Icons.zoom_out), + color: iconColor, + ), + IconButton( + tooltip: 'Reset zoom', + onPressed: _reset, + icon: Icon(Icons.zoom_out_map), + color: iconColor, + ), + ], + ), ); } @@ -105,10 +80,10 @@ class _ControlDecorator extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - padding: EdgeInsets.all(8.0), + padding: EdgeInsets.all(densePadding), decoration: BoxDecoration( color: Colors.grey[300], - borderRadius: BorderRadius.circular(8.0), + borderRadius: defaultBorderRadius, ), child: child, ); @@ -123,6 +98,7 @@ class LayoutTypeSelector extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = Theme.of(context); return _ControlDecorator( child: ValueListenableBuilder( valueListenable: controller.layoutTypeListenable, @@ -130,6 +106,8 @@ class LayoutTypeSelector extends StatelessWidget { return Row( children: [ IconButton( + style: theme.iconButtonTheme.style, + visualDensity: VisualDensity.compact, onPressed: () => controller.layoutType = LayoutType.gridView, icon: Icon(Icons.grid_on), color: selectedLayout == LayoutType.gridView @@ -138,6 +116,7 @@ class LayoutTypeSelector extends StatelessWidget { ), IconButton( onPressed: () => controller.layoutType = LayoutType.listView, + visualDensity: VisualDensity.compact, icon: Icon(Icons.view_list), color: selectedLayout == LayoutType.listView ? Colors.blue @@ -195,21 +174,19 @@ class FilterBySelectedFileToggle extends StatelessWidget { /// re-inserting it on the next frame. This has the effect of re-running local initializers in /// State objects, which normally requires a hot restart to accomplish in a normal application. class SoftRestartButton extends StatelessWidget { - const SoftRestartButton({ - super.key, - required this.enabled, - required this.softRestartListenable, - }); + const SoftRestartButton({super.key, required this.softRestartListenable}); final ValueNotifier softRestartListenable; - final bool enabled; @override Widget build(BuildContext context) { - return _WidgetPreviewIconButton( - tooltip: 'Hot restart', - onPressed: enabled ? _onRestart : null, - icon: Icons.refresh, + return _ControlDecorator( + child: IconButton( + tooltip: 'Hot restart', + onPressed: _onRestart, + icon: Icon(Icons.refresh), + color: Colors.black, + ), ); } @@ -232,6 +209,7 @@ class WidgetPreviewerRestartButton extends StatelessWidget { tooltip: 'Restart the Widget Previewer', onPressed: controller.dtdServices.hotRestartPreviewer, icon: Icon(Icons.restart_alt), + color: Colors.black, ), ); } @@ -244,14 +222,9 @@ extension on Brightness { /// A button that toggles the current theme brightness. class BrightnessToggleButton extends StatelessWidget { - const BrightnessToggleButton({ - super.key, - required this.enabled, - required this.brightnessListenable, - }); + const BrightnessToggleButton({super.key, required this.brightnessListenable}); final ValueNotifier brightnessListenable; - final bool enabled; @override Widget build(BuildContext context) { @@ -259,10 +232,13 @@ class BrightnessToggleButton extends StatelessWidget { valueListenable: brightnessListenable, builder: (context, brightness, _) { final brightness = brightnessListenable.value; - return _WidgetPreviewIconButton( - tooltip: 'Switch to ${brightness.isLight ? 'dark' : 'light'} mode', - onPressed: enabled ? _toggleBrightness : null, - icon: brightness.isLight ? Icons.dark_mode : Icons.light_mode, + return _ControlDecorator( + child: IconButton( + tooltip: 'Switch to ${brightness.isLight ? 'dark' : 'light'} mode', + onPressed: _toggleBrightness, + icon: Icon(brightness.isLight ? Icons.dark_mode : Icons.light_mode), + color: Colors.black, + ), ); }, ); diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/_ide_theme_desktop.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/_ide_theme_desktop.dart new file mode 100644 index 0000000000000..6c8d16f6814b5 --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/_ide_theme_desktop.dart @@ -0,0 +1,10 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// NOTE: originally from package:devtools_app_shared + +import 'ide_theme.dart'; + +/// Load any IDE-supplied theming. +IdeTheme getIdeTheme() => IdeTheme(); diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/_ide_theme_web.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/_ide_theme_web.dart new file mode 100644 index 0000000000000..d0461424ca070 --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/_ide_theme_web.dart @@ -0,0 +1,43 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// NOTE: originally from package:devtools_app_shared + +import 'dart:ui'; + +import 'package:web/web.dart'; + +import '../utils/url/url.dart'; +import 'ide_theme.dart'; + +/// Load any IDE-supplied theming. +IdeTheme getIdeTheme() { + final queryParams = IdeThemeQueryParams(loadQueryParams()); + + final overrides = IdeTheme( + backgroundColor: queryParams.backgroundColor, + foregroundColor: queryParams.foregroundColor, + isDarkMode: queryParams.darkMode, + ); + + // If the environment has provided a background color, set it immediately + // to avoid a white page until the first Flutter frame is rendered. + if (overrides.backgroundColor != null) { + document.body!.style.backgroundColor = toCssHexColor( + overrides.backgroundColor!, + ); + } + + return overrides; +} + +/// Converts a dart:ui Color into #RRGGBBAA format for use in CSS. +String toCssHexColor(Color color) { + // In CSS Hex, Alpha comes last, but in Flutter's `value` field, alpha is + // in the high bytes, so just using `value.toRadixString(16)` will put alpha + // in the wrong position. + String hex(double channelValue) => + (channelValue * 255).round().toRadixString(16).padLeft(2, '0'); + return '#${hex(color.r)}${hex(color.g)}${hex(color.b)}${hex(color.a)}'; +} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/ide_theme.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/ide_theme.dart new file mode 100644 index 0000000000000..6262838bf4587 --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/ide_theme.dart @@ -0,0 +1,46 @@ +// 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. + +// NOTE: originally from package:devtools_app_shared + +import 'package:flutter/widgets.dart'; + +import '../utils/color_utils.dart'; +import 'theme.dart'; + +export '_ide_theme_desktop.dart' + if (dart.library.js_interop) '_ide_theme_web.dart'; + +/// IDE-supplied theming. +final class IdeTheme { + const IdeTheme({this.backgroundColor, this.foregroundColor, bool? isDarkMode}) + : _isDarkMode = isDarkMode; + + final Color? backgroundColor; + final Color? foregroundColor; + final bool? _isDarkMode; + + bool get isDarkMode => _isDarkMode ?? useDarkThemeAsDefault; + + /// Whether the IDE specified the DevTools color theme. + /// + /// If this returns false, that means the + /// [IdeThemeQueryParams.devToolsThemeKey] query parameter was not passed to + /// DevTools from the IDE. + bool get ideSpecifiedTheme => _isDarkMode != null; +} + +extension type IdeThemeQueryParams(Map params) { + Color? get backgroundColor => tryParseColor(params[backgroundColorKey]); + + Color? get foregroundColor => tryParseColor(params[foregroundColorKey]); + + bool get darkMode => params[devToolsThemeKey] != lightThemeValue; + + static const backgroundColorKey = 'backgroundColor'; + static const foregroundColorKey = 'foregroundColor'; + static const devToolsThemeKey = 'theme'; + static const lightThemeValue = 'light'; + static const darkThemeValue = 'dark'; +} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/theme.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/theme.dart new file mode 100644 index 0000000000000..2741b782e2826 --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/theme/theme.dart @@ -0,0 +1,360 @@ +// 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. + +// NOTE: originally from package:devtools_app_shared + +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:widget_preview_scaffold/src/utils/color_utils.dart'; + +import 'ide_theme.dart'; + +// TODO(kenz): try to eliminate as many custom colors as possible, and pull +// colors only from the [lightColorScheme] and the [darkColorScheme]. + +/// Whether dark theme should be used as the default theme if none has been +/// explicitly set. +const useDarkThemeAsDefault = true; + +/// Constructs the light or dark theme for the app taking into account +/// IDE-supplied theming. +ThemeData themeFor({ + required bool isDarkTheme, + required IdeTheme ideTheme, + required ThemeData theme, +}) { + final colorTheme = isDarkTheme + ? _darkTheme(ideTheme: ideTheme, theme: theme) + : _lightTheme(ideTheme: ideTheme, theme: theme); + + return colorTheme.copyWith( + primaryTextTheme: theme.primaryTextTheme.merge(colorTheme.primaryTextTheme), + textTheme: theme.textTheme.merge(colorTheme.textTheme), + ); +} + +ThemeData _darkTheme({required IdeTheme ideTheme, required ThemeData theme}) { + final background = isValidDarkColor(ideTheme.backgroundColor) + ? ideTheme.backgroundColor! + : theme.colorScheme.surface; + return _baseTheme(theme: theme, backgroundColor: background); +} + +ThemeData _lightTheme({required IdeTheme ideTheme, required ThemeData theme}) { + final background = isValidLightColor(ideTheme.backgroundColor) + ? ideTheme.backgroundColor! + : theme.colorScheme.surface; + return _baseTheme(theme: theme, backgroundColor: background); +} + +ThemeData _baseTheme({ + required ThemeData theme, + required Color backgroundColor, +}) { + // TODO(kenz): do we need to pass in the foreground color from the [IdeTheme] + // as well as the background color? + const kCardRadius = Radius.circular(12); + return theme.copyWith( + tabBarTheme: theme.tabBarTheme.copyWith( + tabAlignment: TabAlignment.start, + labelStyle: theme.regularTextStyle, + labelPadding: const EdgeInsets.symmetric( + horizontal: defaultTabBarPadding, + ), + ), + canvasColor: backgroundColor, + scaffoldBackgroundColor: backgroundColor, + iconButtonTheme: IconButtonThemeData( + style: IconButton.styleFrom( + padding: const EdgeInsets.all(densePadding), + minimumSize: const Size(defaultButtonHeight, defaultButtonHeight), + fixedSize: const Size(defaultButtonHeight, defaultButtonHeight), + iconSize: defaultIconSize, + ), + ), + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom( + minimumSize: const Size(buttonMinWidth, defaultButtonHeight), + fixedSize: const Size.fromHeight(defaultButtonHeight), + foregroundColor: theme.colorScheme.onSurface, + padding: const EdgeInsets.symmetric(horizontal: denseSpacing), + ), + ), + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom( + padding: const EdgeInsets.all(densePadding), + minimumSize: const Size(buttonMinWidth, defaultButtonHeight), + fixedSize: const Size.fromHeight(defaultButtonHeight), + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + minimumSize: const Size(buttonMinWidth, defaultButtonHeight), + fixedSize: const Size.fromHeight(defaultButtonHeight), + backgroundColor: theme.colorScheme.primary, + foregroundColor: theme.colorScheme.onPrimary, + padding: const EdgeInsets.symmetric(horizontal: denseSpacing), + ), + ), + menuButtonTheme: MenuButtonThemeData( + style: ButtonStyle( + textStyle: WidgetStatePropertyAll(theme.regularTextStyle), + fixedSize: const WidgetStatePropertyAll(Size.fromHeight(24.0)), + ), + ), + expansionTileTheme: ExpansionTileThemeData( + backgroundColor: backgroundColor.brighten(), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(kCardRadius), + ), + collapsedShape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(kCardRadius), + ), + ), + listTileTheme: ListTileThemeData( + dense: true, + tileColor: backgroundColor.brighten(), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(kCardRadius), + ), + ), + dropdownMenuTheme: DropdownMenuThemeData(textStyle: theme.regularTextStyle), + primaryTextTheme: _devToolsTextTheme(theme, theme.primaryTextTheme), + textTheme: _devToolsTextTheme(theme, theme.textTheme), + colorScheme: theme.colorScheme.copyWith(surface: backgroundColor), + ); +} + +TextTheme _devToolsTextTheme(ThemeData theme, TextTheme textTheme) { + return textTheme.copyWith( + displayLarge: theme.boldTextStyle.copyWith(fontSize: 24), + displayMedium: theme.boldTextStyle.copyWith(fontSize: 22), + displaySmall: theme.boldTextStyle.copyWith(fontSize: 20), + headlineLarge: theme.regularTextStyle.copyWith( + fontSize: 18, + fontWeight: FontWeight.w600, + ), + headlineMedium: theme.regularTextStyle.copyWith( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + headlineSmall: theme.regularTextStyle.copyWith( + fontSize: 14, + fontWeight: FontWeight.w600, + ), + titleLarge: theme._largeText.copyWith(fontWeight: FontWeight.w500), + titleMedium: theme.regularTextStyle.copyWith(fontWeight: FontWeight.w500), + titleSmall: theme._smallText.copyWith(fontWeight: FontWeight.w500), + bodyLarge: theme._largeText, + bodyMedium: theme.regularTextStyle, + bodySmall: theme._smallText, + labelLarge: theme._largeText, + labelMedium: theme.regularTextStyle, + labelSmall: theme._smallText, + ); +} + +/// Light theme color scheme generated from DevTools Figma file. +/// +/// Do not manually change these values. +const lightColorScheme = ColorScheme( + brightness: Brightness.light, + primary: Color(0xFF195BB9), + onPrimary: Color(0xFFFFFFFF), + primaryContainer: Color(0xFFD8E2FF), + onPrimaryContainer: Color(0xFF001A41), + secondary: Color(0xFF575E71), + onSecondary: Color(0xFFFFFFFF), + secondaryContainer: Color(0xFFDBE2F9), + onSecondaryContainer: Color(0xFF141B2C), + tertiary: Color(0xFF815600), + onTertiary: Color(0xFFFFFFFF), + tertiaryContainer: Color(0xFFFFDDB1), + onTertiaryContainer: Color(0xFF291800), + error: Color(0xFFBA1A1A), + errorContainer: Color(0xFFFFDAD5), + onError: Color(0xFFFFFFFF), + onErrorContainer: Color(0xFF410002), + surface: Color(0xFFFFFFFF), + onSurface: Color(0xFF1B1B1F), + surfaceContainerHighest: Color(0xFFE1E2EC), + onSurfaceVariant: Color(0xFF44474F), + outline: Color(0xFF75777F), + onInverseSurface: Color(0xFFF2F0F4), + inverseSurface: Color(0xFF303033), + inversePrimary: Color(0xFFADC6FF), + shadow: Color(0xFF000000), + surfaceTint: Color(0xFF195BB9), + outlineVariant: Color(0xFFC4C6D0), + scrim: Color(0xFF000000), +); + +/// Dark theme color scheme generated from DevTools Figma file. +/// +/// Do not manually change these values. +const darkColorScheme = ColorScheme( + brightness: Brightness.dark, + primary: Color(0xFFADC6FF), + onPrimary: Color(0xFF002E69), + primaryContainer: Color(0xFF004494), + onPrimaryContainer: Color(0xFFD8E2FF), + secondary: Color(0xFFBFC6DC), + onSecondary: Color(0xFF293041), + secondaryContainer: Color(0xFF3F4759), + onSecondaryContainer: Color(0xFFDBE2F9), + tertiary: Color(0xFFFEBA4B), + onTertiary: Color(0xFF442B00), + tertiaryContainer: Color(0xFF624000), + onTertiaryContainer: Color(0xFFFFDDB1), + error: Color(0xFFFFB4AB), + errorContainer: Color(0xFF930009), + onError: Color(0xFF690004), + onErrorContainer: Color(0xFFFFDAD5), + surface: Color(0xFF1B1B1F), + onSurface: Color(0xFFC7C6CA), + surfaceContainerHighest: Color(0xFF44474F), + onSurfaceVariant: Color(0xFFC4C6D0), + outline: Color(0xFF8E9099), + onInverseSurface: Color(0xFF1B1B1F), + inverseSurface: Color(0xFFE3E2E6), + inversePrimary: Color(0xFF195BB9), + shadow: Color(0xFF000000), + surfaceTint: Color(0xFFADC6FF), + outlineVariant: Color(0xFF44474F), + scrim: Color(0xFF000000), +); + +/// Threshold used to determine whether a colour is light/dark enough for us to +/// override the default DevTools themes with. +/// +/// A value of 0.5 would result in all colours being considered light/dark, and +/// a value of 0.12 allowing around only the 12% darkest/lightest colours by +/// Flutter's luminance calculation. +/// 12% was chosen because VS Code's default light background color is #f3f3f3 +/// which is a little under 11%. +const _lightDarkLuminanceThreshold = 0.12; + +bool isValidDarkColor(Color? color) { + if (color == null) { + return false; + } + return color.computeLuminance() <= _lightDarkLuminanceThreshold; +} + +bool isValidLightColor(Color? color) { + if (color == null) { + return false; + } + return color.computeLuminance() >= 1 - _lightDarkLuminanceThreshold; +} + +// Size constants: +const defaultButtonHeight = 26.0; +const buttonMinWidth = 26.0; + +const defaultIconSize = 14.0; + +// Padding / spacing constants: +const extraLargeSpacing = 32.0; +const largeSpacing = 16.0; +const defaultSpacing = 12.0; +const intermediateSpacing = 10.0; +const denseSpacing = 8.0; + +const defaultTabBarPadding = 14.0; +const tabBarSpacing = 8.0; +const denseRowSpacing = 6.0; + +const densePadding = 4.0; + +// Other UI related constants: +final defaultBorderRadius = BorderRadius.circular(_defaultBorderRadiusValue); +const defaultRadius = Radius.circular(_defaultBorderRadiusValue); +const _defaultBorderRadiusValue = 16.0; + +const defaultElevation = 4.0; + +// Font size constants: +const largeFontSize = 14.0; +const defaultFontSize = 12.0; +const smallFontSize = 10.0; + +extension DevToolsSharedColorScheme on ColorScheme { + bool get isLight => brightness == Brightness.light; + + bool get isDark => brightness == Brightness.dark; + + Color get subtleTextColor => const Color(0xFF919094); + + Color get _devtoolsLink => + isLight ? const Color(0xFF1976D2) : Colors.lightBlueAccent; + + Color get tooltipTextColor => isLight ? Colors.white : Colors.black; +} + +/// Utility extension methods to the [ThemeData] class. +extension ThemeDataExtension on ThemeData { + /// Returns whether we are currently using a dark theme. + bool get isDarkTheme => brightness == Brightness.dark; + + TextStyle get regularTextStyle => fixBlurryText( + TextStyle(color: colorScheme.onSurface, fontSize: defaultFontSize), + ); + + TextStyle regularTextStyleWithColor(Color? color, {Color? backgroundColor}) => + regularTextStyle.copyWith(color: color, backgroundColor: backgroundColor); + + TextStyle get _smallText => + regularTextStyle.copyWith(fontSize: smallFontSize); + + TextStyle get _largeText => + regularTextStyle.copyWith(fontSize: largeFontSize); + + TextStyle get errorTextStyle => regularTextStyleWithColor(colorScheme.error); + + TextStyle get boldTextStyle => + regularTextStyle.copyWith(fontWeight: FontWeight.bold); + + TextStyle get subtleTextStyle => + regularTextStyle.copyWith(color: colorScheme.subtleTextColor); + + TextStyle get fixedFontStyle => fixBlurryText( + regularTextStyle.copyWith( + fontFamily: GoogleFonts.robotoMono().fontFamily, + // Slightly smaller for fixes font text since it will appear larger + // to begin with. + fontSize: defaultFontSize - 1, + ), + ); + + TextStyle get subtleFixedFontStyle => + fixedFontStyle.copyWith(color: colorScheme.subtleTextColor); + + TextStyle get selectedSubtleTextStyle => + subtleTextStyle.copyWith(color: colorScheme.onSurface); + + TextStyle get tooltipFixedFontStyle => + fixedFontStyle.copyWith(color: colorScheme.tooltipTextColor); + + TextStyle get fixedFontLinkStyle => fixedFontStyle.copyWith( + color: colorScheme._devtoolsLink, + decoration: TextDecoration.underline, + ); + + TextStyle get linkTextStyle => fixBlurryText( + TextStyle( + color: colorScheme._devtoolsLink, + decoration: TextDecoration.underline, + fontSize: defaultFontSize, + ), + ); +} + +/// Returns a [TextStyle] with [FontFeature.proportionalFigures] applied to +/// fix blurry text. +TextStyle fixBlurryText(TextStyle style) { + return style.copyWith( + fontFeatures: [const FontFeature.proportionalFigures()], + ); +} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils.dart index 05d0bef7315e0..7e0da163dc1f0 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils.dart @@ -4,7 +4,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/widget_previews.dart'; -import 'package:widget_preview_scaffold/src/widget_preview.dart'; + +import 'widget_preview.dart'; Iterable buildMultiWidgetPreview({ required String packageName, @@ -75,26 +76,6 @@ WidgetPreview buildWidgetPreviewError({ ); } -/// Returns a [TextStyle] with [FontFeature.proportionalFigures] applied to -/// fix blurry text. -TextStyle fixBlurryText(TextStyle style) { - return style.copyWith( - fontFeatures: [const FontFeature.proportionalFigures()], - ); -} - -final TextStyle linkTextStyle = fixBlurryText( - underlineTextStyle.copyWith( - // TODO(bkonyi): this color scheme is from DevTools and should be responsive - // to changes in the previewer theme. - color: const Color(0xFF1976D2), - ), -); - -final TextStyle underlineTextStyle = fixBlurryText( - TextStyle(decoration: TextDecoration.underline), -); - /// A basic vertical spacer. class VerticalSpacer extends StatelessWidget { /// Creates a basic vertical spacer. diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/color_utils.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/color_utils.dart new file mode 100644 index 0000000000000..03df6d7e2ddf8 --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/color_utils.dart @@ -0,0 +1,70 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +Color? tryParseColor(String? input) { + if (input == null) return null; + + try { + return parseCssHexColor(input); + } catch (e) { + return null; + } +} + +/// Parses a 3 or 6 digit CSS Hex Color into a dart:ui Color. +Color parseCssHexColor(String input) { + // Remove any leading # (and the escaped version to be lenient) + input = input.replaceAll('#', '').replaceAll('%23', ''); + + // Handle 3/4-digit hex codes (eg. #123 == #112233) + if (input.length == 3 || input.length == 4) { + input = input.split('').map((c) => '$c$c').join(); + } + + // Pad alpha with FF. + if (input.length == 6) { + input = '${input}ff'; + } + + // In CSS, alpha is in the lowest bits, but for Flutter's value, it's in the + // highest bits, so move the alpha from the end to the start before parsing. + if (input.length == 8) { + input = '${input.substring(6)}${input.substring(0, 6)}'; + } + final value = int.parse(input, radix: 16); + + return Color(value); +} + +/// Utility extension methods to the [Color] class. +extension ColorExtension on Color { + /// Return a slightly darker color than the current color. + Color darken([double percent = 0.05]) { + assert(0.0 <= percent && percent <= 1.0); + percent = 1.0 - percent; + + final c = this; + return Color.from( + alpha: c.a, + red: c.r * percent, + green: c.g * percent, + blue: c.b * percent, + ); + } + + /// Return a slightly brighter color than the current color. + Color brighten([double percent = 0.05]) { + assert(0.0 <= percent && percent <= 1.0); + + final c = this; + return Color.from( + alpha: c.a, + red: c.r + ((1.0 - c.r) * percent), + green: c.g + ((1.0 - c.g) * percent), + blue: c.b + ((1.0 - c.b) * percent), + ); + } +} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/url/_url_stub.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/url/_url_stub.dart new file mode 100644 index 0000000000000..51ebed0a1cb16 --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/url/_url_stub.dart @@ -0,0 +1,24 @@ +// 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. + +// NOTE: originally from package:devtools_app_shared + +Map loadQueryParams() => {}; + +/// Gets the URL from the browser. +/// +/// Returns null for non-web platforms. +String? getWebUrl() => null; + +/// Performs a web redirect using window.location.replace(). +/// +/// No-op for non-web platforms. +// Unused parameter lint doesn't make sense for stub files. +void webRedirect(String url) {} + +/// Updates the query parameter with [key] to the new [value], and optionally +/// reloads the page when [reload] is true. +/// +/// No-op for non-web platforms. +void updateQueryParameter(String key, String? value, {bool reload = false}) {} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/url/_url_web.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/url/_url_web.dart new file mode 100644 index 0000000000000..875a52b157128 --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/url/_url_web.dart @@ -0,0 +1,36 @@ +// 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. + +// NOTE: originally from package:devtools_app_shared + +import 'package:web/web.dart'; + +Map loadQueryParams({String Function(String)? urlModifier}) { + var url = getWebUrl()!; + url = urlModifier?.call(url) ?? url; + return Uri.parse(url).queryParameters; +} + +String? getWebUrl() => window.location.toString(); + +void webRedirect(String url) { + window.location.replace(url); +} + +void updateQueryParameter(String key, String? value, {bool reload = false}) { + final newQueryParams = Map.of(loadQueryParams()); + if (value == null) { + newQueryParams.remove(key); + } else { + newQueryParams[key] = value; + } + final newUri = Uri.parse( + window.location.toString(), + ).replace(queryParameters: newQueryParams); + window.history.replaceState(window.history.state, '', newUri.toString()); + + if (reload) { + window.location.reload(); + } +} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/url/url.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/url/url.dart new file mode 100644 index 0000000000000..7f15f82d57923 --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/utils/url/url.dart @@ -0,0 +1,7 @@ +// 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. + +// NOTE: originally from package:devtools_app_shared + +export '_url_stub.dart' if (dart.library.js_interop) '_url_web.dart'; diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart index 2d3c43ae74c57..2c560d2892db3 100644 --- a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/lib/src/widget_preview_rendering.dart @@ -11,17 +11,18 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widget_previews.dart'; -import 'package:google_fonts/google_fonts.dart'; import 'package:stack_trace/stack_trace.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:widget_preview_scaffold/src/dtd/editor_service.dart'; -import 'package:widget_preview_scaffold/src/widget_preview_inspector_service.dart'; +import 'dtd/editor_service.dart'; +import 'theme/ide_theme.dart'; +import 'theme/theme.dart'; import 'controls.dart'; import 'generated_preview.dart'; import 'utils.dart'; import 'widget_preview.dart'; +import 'widget_preview_inspector_service.dart'; import 'widget_preview_scaffold_controller.dart'; /// Displayed when an unhandled exception is thrown when initializing the widget @@ -51,39 +52,36 @@ class WidgetPreviewErrorWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final TextStyle boldStyle = fixBlurryText( - TextStyle(fontWeight: FontWeight.bold), - ); - final TextStyle monospaceStyle = GoogleFonts.robotoMono(); - + final theme = Theme.of(context); return SizedBox( height: size.height, child: SingleChildScrollView( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text.rich( TextSpan( children: [ TextSpan( text: 'Failed to initialize widget tree: ', - style: boldStyle, + style: theme.boldTextStyle, ), - TextSpan(text: error.toString(), style: monospaceStyle), + TextSpan(text: error.toString(), style: theme.fixedFontStyle), ], ), ), - Text('Stacktrace:', style: boldStyle), + Text('Stacktrace:', style: theme.boldTextStyle), ValueListenableBuilder( valueListenable: controller.editorServiceAvailable, builder: (context, editorServiceAvailable, child) { return SelectableText.rich( TextSpan( children: _formatFrames( + theme, trace.frames, editorServiceAvailable, ), - style: monospaceStyle, + style: theme.fixedFontStyle, ), ); }, @@ -95,6 +93,7 @@ class WidgetPreviewErrorWidget extends StatelessWidget { } List _formatFrames( + ThemeData theme, List frames, bool editorServiceAvailable, ) { @@ -112,11 +111,14 @@ class WidgetPreviewErrorWidget extends StatelessWidget { final isLinkable = (frame.uri.isScheme('file') || frame.uri.isScheme('package')) && editorServiceAvailable; + final style = isLinkable + ? theme.fixedFontLinkStyle + : theme.fixedFontStyle; return TextSpan( children: [ TextSpan( text: frame.location, - style: isLinkable ? linkTextStyle : underlineTextStyle, + style: style, recognizer: isLinkable ? (TapGestureRecognizer() ..onTap = () async { @@ -134,7 +136,7 @@ class WidgetPreviewErrorWidget extends StatelessWidget { ), TextSpan(text: ' ' * (longest - frame.location.length)), const TextSpan(text: ' '), - TextSpan(text: '${frame.member}\n'), + TextSpan(text: '${frame.member}\n', style: style), ], ); }).toList(); @@ -151,27 +153,22 @@ class NoPreviewsDetectedWidget extends StatelessWidget { @override Widget build(BuildContext context) { - // TODO(bkonyi): base this on the current color theme (dark vs light) - final style = fixBlurryText(TextStyle(color: Colors.black)); + final theme = Theme.of(context); return Center( child: Column( children: [ - Text( - 'No previews detected', - style: style.copyWith(fontWeight: FontWeight.bold), - ), + Text('No previews detected', style: theme.boldTextStyle), const VerticalSpacer(), Text('Read more about getting started with widget previews at:'), Text.rich( TextSpan( text: documentationUrl.toString(), - style: linkTextStyle, + style: theme.linkTextStyle, recognizer: TapGestureRecognizer() ..onTap = () { launchUrl(documentationUrl); }, ), - style: style, ), ], ), @@ -423,6 +420,10 @@ class WidgetPreviewWidgetState extends State { valueListenable: WidgetsBinding.instance.debugShowWidgetInspectorOverrideNotifier, builder: (context, enableWidgetInspector, child) { + // Don't allow inspecting the error widget. + if (child is WidgetPreviewErrorWidget) { + return child; + } if (enableWidgetInspector) { return WidgetInspector( // TODO(bkonyi): wire up inspector controls for individual previews or @@ -506,12 +507,16 @@ class WidgetPreviewWidgetState extends State { child: preview, ), const VerticalSpacer(), - _WidgetPreviewControlRow( - transformationController: transformationController, - errorThrownDuringTreeConstruction: - errorThrownDuringTreeConstruction, - brightnessListenable: brightnessListenable, - softRestartListenable: softRestartListenable, + Builder( + builder: (context) { + return _WidgetPreviewControlRow( + transformationController: transformationController, + errorThrownDuringTreeConstruction: + errorThrownDuringTreeConstruction, + brightnessListenable: brightnessListenable, + softRestartListenable: softRestartListenable, + ); + }, ), ], ), @@ -546,26 +551,21 @@ class _WidgetPreviewControlRow extends StatelessWidget { @override Widget build(BuildContext context) { + // Don't show controls if an error occurred. + if (errorThrownDuringTreeConstruction) { + return Container(); + } return Row( mainAxisSize: MainAxisSize.min, // If an unhandled exception was caught and we're displaying an error // widget, these controls should be disabled. // TODO(bkonyi): improve layout of controls. children: [ - ZoomControls( - transformationController: transformationController, - enabled: !errorThrownDuringTreeConstruction, - ), + ZoomControls(transformationController: transformationController), const SizedBox(width: 30), - BrightnessToggleButton( - enabled: !errorThrownDuringTreeConstruction, - brightnessListenable: brightnessListenable, - ), + BrightnessToggleButton(brightnessListenable: brightnessListenable), const SizedBox(width: 10), - SoftRestartButton( - enabled: !errorThrownDuringTreeConstruction, - softRestartListenable: softRestartListenable, - ), + SoftRestartButton(softRestartListenable: softRestartListenable), ], ); } @@ -949,7 +949,10 @@ Future mainImpl() async { // Forces the set of previews to be recalculated after a hot reload. HotReloadListener( onHotReload: controller.onHotReload, - child: WidgetPreviewScaffold(controller: controller), + child: WidgetPreviewScaffold( + controller: controller, + ideTheme: getIdeTheme(), + ), ), ), ), @@ -957,16 +960,31 @@ Future mainImpl() async { } class WidgetPreviewScaffold extends StatelessWidget { - const WidgetPreviewScaffold({super.key, required this.controller}); + const WidgetPreviewScaffold({ + super.key, + required this.controller, + this.ideTheme = const IdeTheme(), + }); final WidgetPreviewScaffoldController controller; + final IdeTheme ideTheme; @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, + theme: themeFor( + isDarkTheme: false, + ideTheme: ideTheme, + theme: ThemeData(), + ), + darkTheme: themeFor( + isDarkTheme: true, + ideTheme: ideTheme, + theme: ThemeData.dark(), + ), + themeMode: ideTheme.isDarkMode ? ThemeMode.dark : ThemeMode.light, home: Material( - color: Colors.transparent, child: Column( children: [ // Display the previewer diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/ide_theme_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/ide_theme_test.dart new file mode 100644 index 0000000000000..53cab5ecc7563 --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/ide_theme_test.dart @@ -0,0 +1,57 @@ +// 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. + +// NOTE: these tests are originally from package:devtools_app_shared. + +import 'dart:ui'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:widget_preview_scaffold/src/theme/ide_theme.dart'; + +void main() { + group('$IdeThemeQueryParams', () { + test('successfully creates params', () { + final params = IdeThemeQueryParams({ + 'backgroundColor': '#112233', + 'foregroundColor': '#112244', + 'theme': 'dark', + }); + + expect(params.params, isNotEmpty); + expect(params.backgroundColor, const Color(0xFF112233)); + expect(params.foregroundColor, const Color(0xFF112244)); + expect(params.darkMode, true); + }); + + test('handles bad input', () { + final params = IdeThemeQueryParams({ + 'backgroundColor': 'badcolor', + 'foregroundColor': 'badcolor', + 'theme': 'dark', + }); + + expect(params.params, isNotEmpty); + expect(params.backgroundColor, isNull); + expect(params.foregroundColor, isNull); + expect(params.darkMode, true); + }); + + test('ignores unsupported query params', () { + final params = IdeThemeQueryParams({ + 'fontSize': '50', // Font size is not supported. + 'theme': 'dark', + }); + + expect(params.darkMode, true); + }); + + test('creates empty params', () { + final params = IdeThemeQueryParams({}); + expect(params.params, isEmpty); + expect(params.backgroundColor, isNull); + expect(params.foregroundColor, isNull); + expect(params.darkMode, true); + }); + }); +} diff --git a/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/theme_test.dart b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/theme_test.dart new file mode 100644 index 0000000000000..e72984c1019bb --- /dev/null +++ b/packages/flutter_tools/test/widget_preview_scaffold.shard/widget_preview_scaffold/test/theme_test.dart @@ -0,0 +1,70 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// NOTE: these tests are originally from package:devtools_app_shared. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:widget_preview_scaffold/src/theme/ide_theme.dart'; +import 'package:widget_preview_scaffold/src/theme/theme.dart'; + +void main() { + group('Theme', () { + ThemeData theme; + + test('can be used without override', () { + theme = themeFor( + isDarkTheme: true, + ideTheme: IdeTheme(), + theme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme), + ); + expect(theme.brightness, equals(Brightness.dark)); + expect(theme.scaffoldBackgroundColor, equals(darkColorScheme.surface)); + + theme = themeFor( + isDarkTheme: false, + ideTheme: IdeTheme(), + theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme), + ); + expect(theme.brightness, equals(Brightness.light)); + expect(theme.scaffoldBackgroundColor, equals(lightColorScheme.surface)); + }); + + test('can be inferred from override background color', () { + theme = themeFor( + isDarkTheme: false, // Will be overridden by white BG + ideTheme: IdeTheme(backgroundColor: Colors.white70), + theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme), + ); + expect(theme.brightness, equals(Brightness.light)); + expect(theme.scaffoldBackgroundColor, equals(Colors.white70)); + + theme = themeFor( + isDarkTheme: true, // Will be overridden by black BG + ideTheme: IdeTheme(backgroundColor: Colors.black), + theme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme), + ); + expect(theme.brightness, equals(Brightness.dark)); + expect(theme.scaffoldBackgroundColor, equals(Colors.black)); + }); + + test('will not be inferred for colors that are not dark/light enough', () { + theme = themeFor( + isDarkTheme: false, // Will not be overridden - not dark enough + ideTheme: IdeTheme(backgroundColor: Colors.orange), + theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme), + ); + expect(theme.brightness, equals(Brightness.light)); + expect(theme.scaffoldBackgroundColor, equals(lightColorScheme.surface)); + + theme = themeFor( + isDarkTheme: true, // Will not be overridden - not light enough + ideTheme: IdeTheme(backgroundColor: Colors.orange), + theme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme), + ); + expect(theme.brightness, equals(Brightness.dark)); + expect(theme.scaffoldBackgroundColor, equals(darkColorScheme.surface)); + }); + }); +} From e20c5f5594a36affb23de6c49b9dbc4454b69b33 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 7 Oct 2025 03:47:40 -0400 Subject: [PATCH 093/204] Roll Skia from d09786dfb854 to d10a0d877ff4 (11 revisions) (#176616) https://skia.googlesource.com/skia.git/+log/d09786dfb854..d10a0d877ff4 2025-10-07 recipe-mega-autoroller@chops-service-accounts.iam.gserviceaccount.com Roll recipe dependencies (trivial). 2025-10-07 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Dawn from d8f0feef7c17 to 2677a29136e6 (10 revisions) 2025-10-07 skia-autoroll@skia-public.iam.gserviceaccount.com Roll SwiftShader from 18d4f3db9407 to 794b0cfce1d8 (1 revision) 2025-10-07 skia-autoroll@skia-public.iam.gserviceaccount.com Roll Skia Infra from b57557664701 to 199e640f88d7 (10 revisions) 2025-10-06 skia-autoroll@skia-public.iam.gserviceaccount.com Roll vulkan-deps from ea4cd2b85ec4 to 5925e8cb6a0d (9 revisions) 2025-10-06 mike@reedtribe.org Use pathbuilder to keep paths' immutable (their geometry) 2025-10-06 thomsmit@google.com [graphite] Disable lighten/darken test IntelUHD630 2025-10-06 kjlubick@google.com Remove old define in hairline code 2025-10-06 michaelludwig@google.com [graphite] Relax circular corner detection for analytic clip 2025-10-06 kjlubick@google.com Remove SkOnce from function local initializations 2025-10-06 michaelludwig@google.com [graphite] Rename BufferManager::BufferInfo to BufferState + other renames If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/skia-flutter-autoroll Please CC egdaniel@google.com,jimgraham@google.com,kjlubick@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Skia: https://bugs.chromium.org/p/skia/issues/entry To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- DEPS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEPS b/DEPS index 0152ab17cfe17..337dcbb7202dc 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ vars = { 'flutter_git': 'https://flutter.googlesource.com', 'skia_git': 'https://skia.googlesource.com', 'llvm_git': 'https://llvm.googlesource.com', - 'skia_revision': 'd09786dfb85445bfca55df40ab3b9c1af9c25161', + 'skia_revision': 'd10a0d877ff48a4c2d0c512f314d7942d53c5f72', # WARNING: DO NOT EDIT canvaskit_cipd_instance MANUALLY # See `lib/web_ui/README.md` for how to roll CanvasKit to a new version. From 884512c06f7edceec21166f0f22f169389b13ae5 Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Tue, 7 Oct 2025 10:01:20 -0500 Subject: [PATCH 094/204] Add fallback for 'scene:willConnectToSession:options' (#176580) This adds an application lifecycle event fallback for when the app has migrated to UIScene but the plugin has not. So if the app has migrated to UIScene and receives the `scene:willConnectToSession:options` event, if no other plugins process the event, it'll attempt to use `application:didFinishLaunchingWithOptions` event if there are meaningful launch arguments. This is a best attempt and may not cover all use cases. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../FlutterPluginAppLifeCycleDelegate.mm | 62 ++++++++++++- .../FlutterPluginAppLifeCycleDelegateTest.mm | 64 +++++++++++++ ...utterPluginAppLifeCycleDelegate_internal.h | 7 ++ .../framework/Source/FlutterSceneLifeCycle.mm | 27 +++++- .../Source/FlutterSceneLifeCycleTest.mm | 89 +++++++++++++++++-- 5 files changed, 238 insertions(+), 11 deletions(-) diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm index 56c0574f0fe31..2f2d1b8f9329a 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate.mm @@ -148,11 +148,38 @@ - (BOOL)application:(UIApplication*)application if (_delegates.count > 0) { self.didForwardApplicationDidLaunch = YES; } - for (NSObject* delegate in [_delegates allObjects]) { - if (!delegate) { + return [self application:application + didFinishLaunchingWithOptions:launchOptions + isFallbackForScene:NO]; +} + +- (BOOL)sceneWillConnectFallback:(UISceneConnectionOptions*)connectionOptions { + UIApplication* application = FlutterSharedApplication.application; + if (!application) { + return NO; + } + NSDictionary* convertedLaunchOptions = + ConvertConnectionOptions(connectionOptions); + if (convertedLaunchOptions.count == 0) { + // Only use fallback if there are meaningful launch options. + return NO; + } + if (![self application:application + didFinishLaunchingWithOptions:convertedLaunchOptions + isFallbackForScene:YES]) { + return YES; + } + return NO; +} + +- (BOOL)application:(UIApplication*)application + didFinishLaunchingWithOptions:(NSDictionary*)launchOptions + isFallbackForScene:(BOOL)isFallback { + for (NSObject* delegate in _delegates) { + if (!delegate || (isFallback && [self pluginSupportsSceneLifecycle:delegate])) { continue; } - if ([delegate respondsToSelector:_cmd]) { + if ([delegate respondsToSelector:@selector(application:didFinishLaunchingWithOptions:)]) { if (![delegate application:application didFinishLaunchingWithOptions:launchOptions]) { return NO; } @@ -161,6 +188,35 @@ - (BOOL)application:(UIApplication*)application return YES; } +/* Makes a best attempt to convert UISceneConnectionOptions from the scene event + * (`scene:willConnectToSession:options:`) to a NSDictionary of options used to the application + * lifecycle event. + * + * For more information on UISceneConnectionOptions, see + * https://developer.apple.com/documentation/uikit/uiscene/connectionoptions. + * + * For information about the possible keys in the NSDictionary and how to handle them, see + * https://developer.apple.com/documentation/uikit/uiapplication/launchoptionskey + */ +static NSDictionary* ConvertConnectionOptions( + UISceneConnectionOptions* connectionOptions) { + NSMutableDictionary* convertedOptions = + [NSMutableDictionary dictionary]; + + if (connectionOptions.shortcutItem) { + convertedOptions[UIApplicationLaunchOptionsShortcutItemKey] = connectionOptions.shortcutItem; + } + if (connectionOptions.sourceApplication) { + convertedOptions[UIApplicationLaunchOptionsSourceApplicationKey] = + connectionOptions.sourceApplication; + } + if (connectionOptions.URLContexts.anyObject.URL) { + convertedOptions[UIApplicationLaunchOptionsURLKey] = + connectionOptions.URLContexts.anyObject.URL; + } + return convertedOptions; +} + - (BOOL)application:(UIApplication*)application willFinishLaunchingWithOptions:(NSDictionary*)launchOptions { if (_delegates.count > 0) { diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm index 000d61aa4cd40..edb903cc60b11 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegateTest.mm @@ -22,6 +22,11 @@ @interface FakeTestFlutterPluginWithSceneEvents : NSObject *)options { @@ -86,6 +91,65 @@ - (void)testCreate { XCTAssertNotNil(delegate); } +- (void)testSceneWillConnectFallback { + FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; + id plugin = [[FakePlugin alloc] init]; + id mockPlugin = OCMPartialMock(plugin); + [delegate addDelegate:mockPlugin]; + + id mockOptions = OCMClassMock([UISceneConnectionOptions class]); + id mockShortcutItem = OCMClassMock([UIApplicationShortcutItem class]); + OCMStub([mockOptions shortcutItem]).andReturn(mockShortcutItem); + OCMStub([mockOptions sourceApplication]).andReturn(@"bundle_id"); + id urlContext = OCMClassMock([UIOpenURLContext class]); + NSURL* url = [NSURL URLWithString:@"http://example.com"]; + OCMStub([urlContext URL]).andReturn(url); + NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; + OCMStub([mockOptions URLContexts]).andReturn(urlContexts); + + NSDictionary* expectedApplicationOptions = @{ + UIApplicationLaunchOptionsShortcutItemKey : mockShortcutItem, + UIApplicationLaunchOptionsSourceApplicationKey : @"bundle_id", + UIApplicationLaunchOptionsURLKey : url, + }; + + [delegate sceneWillConnectFallback:mockOptions]; + OCMVerify([mockPlugin application:[UIApplication sharedApplication] + didFinishLaunchingWithOptions:expectedApplicationOptions]); +} + +- (void)testSceneWillConnectFallbackSkippedSupportsScenes { + FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; + id plugin = [[FakeTestFlutterPluginWithSceneEvents alloc] init]; + id mockPlugin = OCMPartialMock(plugin); + [delegate addDelegate:mockPlugin]; + + id mockOptions = OCMClassMock([UISceneConnectionOptions class]); + id mockShortcutItem = OCMClassMock([UIApplicationShortcutItem class]); + OCMStub([mockOptions shortcutItem]).andReturn(mockShortcutItem); + OCMStub([mockOptions sourceApplication]).andReturn(@"bundle_id"); + id urlContext = OCMClassMock([UIOpenURLContext class]); + NSURL* url = [NSURL URLWithString:@"http://example.com"]; + OCMStub([urlContext URL]).andReturn(url); + NSSet* urlContexts = [NSSet setWithObjects:urlContext, nil]; + OCMStub([mockOptions URLContexts]).andReturn(urlContexts); + + [delegate sceneWillConnectFallback:mockOptions]; + OCMReject([mockPlugin application:[OCMArg any] didFinishLaunchingWithOptions:[OCMArg any]]); +} + +- (void)testSceneWillConnectFallbackSkippedNoOptions { + FlutterPluginAppLifeCycleDelegate* delegate = [[FlutterPluginAppLifeCycleDelegate alloc] init]; + id plugin = [[FakePlugin alloc] init]; + id mockPlugin = OCMPartialMock(plugin); + [delegate addDelegate:mockPlugin]; + + id mockOptions = OCMClassMock([UISceneConnectionOptions class]); + + [delegate sceneWillConnectFallback:mockOptions]; + OCMReject([mockPlugin application:[OCMArg any] didFinishLaunchingWithOptions:[OCMArg any]]); +} + - (void)testDidEnterBackground { XCTNSNotificationExpectation* expectation = [[XCTNSNotificationExpectation alloc] initWithName:UIApplicationDidEnterBackgroundNotification]; diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h index 529371a97c5cc..c68505a520b17 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterPluginAppLifeCycleDelegate_internal.h @@ -35,6 +35,13 @@ */ - (void)sceneFallbackWillFinishLaunchingApplication:(UIApplication*)application; +/** + * Forwards the application equivalent lifecycle event of + * `scene:willConnectToSession:options:` -> `application:didFinishLaunchingWithOptions:` to plugins + * that have not adopted the FlutterSceneLifeCycleDelegate protocol. + */ +- (BOOL)sceneWillConnectFallback:(UISceneConnectionOptions*)connectionOptions; + /** * Forwards the application equivalent lifecycle event of * `sceneWillEnterForeground:` -> `applicationWillEnterForeground:` to plugins that have not adopted diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm index 561582d3abb6a..019de11b5bbbd 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle.mm @@ -32,6 +32,9 @@ @interface FlutterPluginSceneLifeCycleDelegate () @property(nonatomic, strong) NSPointerArray* developerManagedEngines; @property(nonatomic, strong) UISceneConnectionOptions* connectionOptions; +@property(nonatomic, assign) BOOL sceneWillConnectEventHandledByPlugin; +@property(nonatomic, assign) BOOL sceneWillConnectFallbackCalled; + @end @implementation FlutterPluginSceneLifeCycleDelegate @@ -39,6 +42,8 @@ - (instancetype)init { if (self = [super init]) { _flutterManagedEngines = [NSPointerArray weakObjectsPointerArray]; _developerManagedEngines = [NSPointerArray weakObjectsPointerArray]; + _sceneWillConnectFallbackCalled = NO; + _sceneWillConnectEventHandledByPlugin = NO; } return self; } @@ -208,10 +213,28 @@ - (void)scene:(UIScene*)scene willConnectToSession:(UISceneSession*)session flutterEngine:(FlutterEngine*)engine options:(UISceneConnectionOptions*)connectionOptions { + // Don't send connection options if a plugin has already used them. + UISceneConnectionOptions* availableOptions = connectionOptions; + if (self.sceneWillConnectEventHandledByPlugin) { + availableOptions = nil; + } BOOL handledByPlugin = [engine.sceneLifeCycleDelegate scene:scene willConnectToSession:session - options:connectionOptions]; - if (!handledByPlugin) { + options:availableOptions]; + + // If no plugins handled this, give the application fallback a chance to handle it. + // Only call the fallback once since it's per application. + if (!handledByPlugin && !self.sceneWillConnectFallbackCalled) { + self.sceneWillConnectFallbackCalled = YES; + if ([[self applicationLifeCycleDelegate] sceneWillConnectFallback:connectionOptions]) { + handledByPlugin = YES; + } + } + if (handledByPlugin) { + self.sceneWillConnectEventHandledByPlugin = YES; + } + + if (!self.sceneWillConnectEventHandledByPlugin) { // Only process deeplinks if a plugin has not already done something to handle this event. [self handleDeeplinkingForEngine:engine options:connectionOptions]; } diff --git a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm index bb2488ba94deb..da41317bd5eee 100644 --- a/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm +++ b/engine/src/flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycleTest.mm @@ -313,7 +313,7 @@ - (void)testEngineReceivedConnectNotificationForSceneBeforeActualEvent { options:options]); OCMVerify(times(1), [mockLifecycleDelegate2 scene:mockScene willConnectToSession:session - options:options]); + options:nil]); XCTAssertEqual(delegate.flutterManagedEngines.count, 2.0); } @@ -360,16 +360,13 @@ - (void)testEngineReceivedConnectNotificationForSceneAfterActualEvent { OCMVerify(times(1), [mockDelegate addFlutterManagedEngine:mockEngine]); OCMVerify(times(1), [mockDelegate addFlutterManagedEngine:mockEngine2]); XCTAssertEqual(delegate.flutterManagedEngines.count, 2.0); - OCMVerify(times(1), [mockDelegate scene:mockScene - willConnectToSession:session - options:options]); // This is called twice because once is - // within the test itself. + OCMVerify(times(1), [mockDelegate scene:mockScene willConnectToSession:session options:options]); OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene willConnectToSession:session options:options]); OCMVerify(times(1), [mockLifecycleDelegate2 scene:mockScene willConnectToSession:session - options:options]); + options:nil]); } - (void)testSceneWillConnectToSessionOptionsHandledByScenePlugin { @@ -385,6 +382,39 @@ - (void)testSceneWillConnectToSessionOptionsHandledByScenePlugin { willConnectToSession:[OCMArg any] options:[OCMArg any]]) .andReturn(YES); + id mockAppLifecycleDelegate = mocks[@"mockAppLifecycleDelegate"]; + OCMStub([mockAppLifecycleDelegate sceneWillConnectFallback:[OCMArg any]]).andReturn(YES); + + id session = OCMClassMock([UISceneSession class]); + id options = OCMClassMock([UISceneConnectionOptions class]); + + [delegate addFlutterManagedEngine:mockEngine]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 1.0); + + [delegate scene:mockScene willConnectToSession:session options:options]; + OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene + willConnectToSession:session + options:options]); + OCMVerify(times(0), [mockAppLifecycleDelegate sceneWillConnectFallback:options]); + OCMVerify(times(0), [mockEngine sendDeepLinkToFramework:[OCMArg any] + completionHandler:[OCMArg any]]); +} + +- (void)testSceneWillConnectToSessionOptionsHandledByApplicationPlugin { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mocks = [self mocksForEvents]; + id mockEngine = mocks[@"mockEngine"]; + id mockScene = mocks[@"mockScene"]; + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate = + (FlutterEnginePluginSceneLifeCycleDelegate*)mocks[@"mockLifecycleDelegate"]; + OCMStub([mockLifecycleDelegate scene:[OCMArg any] + willConnectToSession:[OCMArg any] + options:[OCMArg any]]) + .andReturn(NO); + id mockAppLifecycleDelegate = mocks[@"mockAppLifecycleDelegate"]; + OCMStub([mockAppLifecycleDelegate sceneWillConnectFallback:[OCMArg any]]).andReturn(YES); id session = OCMClassMock([UISceneSession class]); id options = OCMClassMock([UISceneConnectionOptions class]); @@ -396,6 +426,53 @@ - (void)testSceneWillConnectToSessionOptionsHandledByScenePlugin { OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene willConnectToSession:session options:options]); + OCMVerify(times(1), [mockAppLifecycleDelegate sceneWillConnectFallback:options]); + OCMVerify(times(0), [mockEngine sendDeepLinkToFramework:[OCMArg any] + completionHandler:[OCMArg any]]); +} + +- (void)testSceneWillConnectToSessionOptionsHandledByApplicationPluginMultipleEngines { + FlutterPluginSceneLifeCycleDelegate* delegate = + [[FlutterPluginSceneLifeCycleDelegate alloc] init]; + + id mocks = [self mocksForEvents]; + id mockEngine = mocks[@"mockEngine"]; + id mockScene = mocks[@"mockScene"]; + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate = + (FlutterEnginePluginSceneLifeCycleDelegate*)mocks[@"mockLifecycleDelegate"]; + OCMStub([mockLifecycleDelegate scene:[OCMArg any] + willConnectToSession:[OCMArg any] + options:[OCMArg any]]) + .andReturn(NO); + + id mocks2 = [self mocksForEvents]; + id mockEngine2 = mocks2[@"mockEngine"]; + FlutterEnginePluginSceneLifeCycleDelegate* mockLifecycleDelegate2 = + (FlutterEnginePluginSceneLifeCycleDelegate*)mocks2[@"mockLifecycleDelegate"]; + OCMStub([mockLifecycleDelegate2 scene:[OCMArg any] + willConnectToSession:[OCMArg any] + options:[OCMArg any]]) + .andReturn(NO); + + id mockAppLifecycleDelegate = mocks2[@"mockAppLifecycleDelegate"]; + OCMStub([mockAppLifecycleDelegate sceneWillConnectFallback:[OCMArg any]]).andReturn(YES); + id session = OCMClassMock([UISceneSession class]); + id options = OCMClassMock([UISceneConnectionOptions class]); + + [delegate addFlutterManagedEngine:mockEngine]; + [delegate addFlutterManagedEngine:mockEngine2]; + XCTAssertEqual(delegate.flutterManagedEngines.count, 2.0); + + [delegate scene:mockScene willConnectToSession:session options:options]; + OCMVerify(times(1), [mockLifecycleDelegate scene:mockScene + willConnectToSession:session + options:options]); + OCMVerify(times(1), [mockLifecycleDelegate2 scene:mockScene + willConnectToSession:session + options:nil]); + OCMVerify(times(1), [mockAppLifecycleDelegate sceneWillConnectFallback:options]); + OCMVerify(times(0), [mockEngine sendDeepLinkToFramework:[OCMArg any] + completionHandler:[OCMArg any]]); } - (void)testSceneWillConnectToSessionOptionsHandledByUniversalLinks { From 9e97c5893ef09eaaa50e8b9b95082d8019353500 Mon Sep 17 00:00:00 2001 From: engine-flutter-autoroll Date: Tue, 7 Oct 2025 11:57:25 -0400 Subject: [PATCH 095/204] Roll Packages from d3ef88b5feb8 to 8ca6416d680d (2 revisions) (#176633) https://github.com/flutter/packages/compare/d3ef88b5feb8...8ca6416d680d 2025-10-06 jessiewong401@gmail.com Update CI to Test Against SDK 36v3 (flutter/packages#10169) 2025-10-06 1063596+reidbaker@users.noreply.github.com [espresso] updates build files to use JVM 17 (flutter/packages#10162) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC flutter-ecosystem@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md --- bin/internal/flutter_packages.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/internal/flutter_packages.version b/bin/internal/flutter_packages.version index 0005e047c24d9..adb87db91b076 100644 --- a/bin/internal/flutter_packages.version +++ b/bin/internal/flutter_packages.version @@ -1 +1 @@ -d3ef88b5feb85fe3092921e01edf72f845058063 +8ca6416d680d41b62c57e0ad155a269fb9b51ca7 From 08def364d21b8f8bf9042d78ceeb54b1c29522b8 Mon Sep 17 00:00:00 2001 From: Paul Berry Date: Tue, 7 Oct 2025 09:12:11 -0700 Subject: [PATCH 096/204] Bump the customer tests to pick up an update to Zulip's tests. (#176463) Picks up https://github.com/flutter/tests/commit/986c4326b4e4bb4e37bc963c2cc2aaa10b943859, which unblocks https://dart-review.googlesource.com/c/sdk/+/452528 by picking up an `// ignore` comment. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md From 202dc3b970e7a8b8be58a9b65e1775b7715f02bd Mon Sep 17 00:00:00 2001 From: Gray Mackall <34871572+gmackall@users.noreply.github.com> Date: Tue, 7 Oct 2025 13:12:27 -0700 Subject: [PATCH 097/204] Make it clear that you need to install clangd in VSCode intellisense c++ config (#176609) Make it clear that you need to install clangd in VSCode intellisense c++ config Co-authored-by: Gray Mackall --- .../Setting-up-the-Engine-development-environment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/engine/contributing/Setting-up-the-Engine-development-environment.md b/docs/engine/contributing/Setting-up-the-Engine-development-environment.md index 77716160a4e8d..1c4963a890a97 100644 --- a/docs/engine/contributing/Setting-up-the-Engine-development-environment.md +++ b/docs/engine/contributing/Setting-up-the-Engine-development-environment.md @@ -104,7 +104,7 @@ The easiest way to do this is create a [multi-root workspace](https://code.visua } ``` -Then, edit the `"settings"` key: +Then, install the [`clangd` extension](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd) and edit the `"settings"` key: ```json "settings": { From 3dbd33d3aa3fdfd7700685b7a59b9505fa139859 Mon Sep 17 00:00:00 2001 From: Valentin Vignal <32538273+ValentinVignal@users.noreply.github.com> Date: Wed, 8 Oct 2025 04:13:57 +0800 Subject: [PATCH 098/204] Migrate to `WidgetStateInputBorder` (#176386) Follow up https://github.com/flutter/flutter/pull/176330 Migrate the remaining files from `MaterialStateOutlineInputBorder` and `MaterialStateUnderlineInputBorder ` to `WidgetStateInputBorder `. This PR only focus on `WidgetStateInputBorder`. - This minimizes conflicts and reduces the size of the PR for easier reviews and follow up - I'll work on the other elements of `packages/flutter/lib/src/material/material_state.dart` into other PRs ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- packages/flutter/lib/src/material/input_decorator.dart | 6 +++--- packages/flutter/test/material/input_decorator_test.dart | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/material/input_decorator.dart b/packages/flutter/lib/src/material/input_decorator.dart index 1e984d4c1b308..283ee859764ae 100644 --- a/packages/flutter/lib/src/material/input_decorator.dart +++ b/packages/flutter/lib/src/material/input_decorator.dart @@ -5261,9 +5261,9 @@ class InputDecorationThemeData with Diagnosticable { /// The shape of the border to draw around the decoration's container. /// - /// If [border] is a [MaterialStateUnderlineInputBorder] - /// or [MaterialStateOutlineInputBorder], then the effective border can depend on - /// the [WidgetState.focused] state, i.e. if the [TextField] is focused or not. + /// If [border] is a [WidgetStateInputBorder], then the effective border can + /// depend on the [WidgetState.focused] state, i.e. if the [TextField] is + /// focused or not. /// /// The decoration's container is the area which is filled if [filled] is /// true and bordered per the [border]. It's the area adjacent to diff --git a/packages/flutter/test/material/input_decorator_test.dart b/packages/flutter/test/material/input_decorator_test.dart index 8ed040a535840..a7900f0ea3f88 100644 --- a/packages/flutter/test/material/input_decorator_test.dart +++ b/packages/flutter/test/material/input_decorator_test.dart @@ -8456,7 +8456,7 @@ void main() { ); // InputDecoration (baseDecoration) defines InputDecoration properties - final MaterialStateOutlineInputBorder border = MaterialStateOutlineInputBorder.resolveWith(( + final WidgetStateInputBorder border = WidgetStateInputBorder.resolveWith(( Set states, ) { return const OutlineInputBorder(); @@ -8522,7 +8522,7 @@ void main() { expect(decoration.counterStyle, decorationStyle); expect(decoration.filled, false); expect(decoration.fillColor, Colors.blue); - expect(decoration.border, isA()); + expect(decoration.border, isA()); expect(decoration.alignLabelWithHint, false); expect( decoration.constraints, From a50b9d56d43453dc6bf2beb075773c08dbf943f8 Mon Sep 17 00:00:00 2001 From: Elliott Brooks <21270878+elliette@users.noreply.github.com> Date: Tue, 7 Oct 2025 14:05:21 -0700 Subject: [PATCH 099/204] Selecting an implementation widget with the on-device inspector opens the code location for the nearest project widget (#176530) Fixes https://github.com/flutter/devtools/issues/9252 Previously, if a user selected a widget that was not created in their project files using the on-device inspector, we would open the code location for that widget. Now, we open the code location of the nearest ancestor widget in their project files. (e.g., a user selects `RichText` in the framework, we open the `Text` widget in their project) Users can still open the code location for a Flutter framework or third-party package widget by explicitly selecting it in the DevTools widget tree. See more details/rationale on issue comment https://github.com/flutter/devtools/issues/9252#issuecomment-3368491936 _Note: This resolves one of the top user complaints in the 2025 DevTools user survey._ ![project_file_fix](https://github.com/user-attachments/assets/e3839ada-52b6-4d4d-b1d7-7fea93c7380c) --- .../lib/src/widgets/widget_inspector.dart | 49 +- .../test/widgets/widget_inspector_test.dart | 482 +++++++++--------- 2 files changed, 284 insertions(+), 247 deletions(-) diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index 247b85f60c58f..23d9a81835ebb 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart @@ -1613,21 +1613,36 @@ mixin WidgetInspectorService { switch (object) { case Element() when object != selection.currentElement: selection.currentElement = object; - _sendInspectEvent(selection.currentElement); + _notifyToolsOfSelection(selection.currentElement); return true; case RenderObject() when object != selection.current: selection.current = object; - _sendInspectEvent(selection.current); + _notifyToolsOfSelection(selection.current); return true; } return false; } - /// Notify attached tools to navigate to an object's source location. - void _sendInspectEvent(Object? object) { + /// Notify connected tools (e.g. Flutter DevTools, IDE plugins) that a new + /// widget has been selected. + /// + /// This method triggers two actions: + /// 1. It calls [developer.inspect] on the provided [object], making it + /// available for inspection in Flutter DevTools. + /// 2. It posts a 'navigate' [ToolEvent] with the source code location of the + /// selected widget, allowing IDEs to navigate to the corresponding file + /// and line. + /// + /// If [restrictToProjectFiles] is true and the selected widget is not from + /// the local project (i.e., it's from the Flutter framework or a package), + /// the 'navigate' event will point to the nearest ancestor widget that is + /// part of the local project. + void _notifyToolsOfSelection(Object? object, {bool restrictToProjectFiles = false}) { inspect(object); - final _Location? location = _getSelectedWidgetLocation(); + final _Location? location = _getSelectedWidgetLocation( + restrictToSummaryTree: restrictToProjectFiles, + ); if (location != null) { postEvent('navigate', { 'fileUri': location.file, // URI file path of the location. @@ -2463,8 +2478,18 @@ mixin WidgetInspectorService { return _safeJsonEncode(_getSelectedSummaryWidget(null, groupName)); } - _Location? _getSelectedWidgetLocation() { - return _getCreationLocation(_getSelectedWidgetDiagnosticsNode(null)?.value); + /// Returns the creation location of the currently selected widget. + /// + /// If [restrictToSummaryTree] is true and the currently selected widget is + /// not in the summary tree (i.e. not created by the current project), this + /// method will instead return the location of its nearest ancestor widget + /// that is in the summary tree. + _Location? _getSelectedWidgetLocation({bool restrictToSummaryTree = false}) { + final DiagnosticsNode? selectedNode = restrictToSummaryTree + ? _getSelectedSummaryDiagnosticsNode(null) + : _getSelectedWidgetDiagnosticsNode(null); + + return _getCreationLocation(selectedNode?.value); } DiagnosticsNode? _getSelectedSummaryDiagnosticsNode(String? previousSelectionId) { @@ -3019,7 +3044,10 @@ class _WidgetInspectorState extends State with WidgetsBindingOb selection.clear(); } else { // Otherwise notify DevTools of the current selection. - WidgetInspectorService.instance._sendInspectEvent(selection.current); + WidgetInspectorService.instance._notifyToolsOfSelection( + selection.current, + restrictToProjectFiles: true, + ); } } @@ -3029,7 +3057,10 @@ class _WidgetInspectorState extends State with WidgetsBindingOb } if (_lastPointerLocation != null) { _inspectAt(_lastPointerLocation!); - WidgetInspectorService.instance._sendInspectEvent(selection.current); + WidgetInspectorService.instance._notifyToolsOfSelection( + selection.current, + restrictToProjectFiles: true, + ); } } diff --git a/packages/flutter/test/widgets/widget_inspector_test.dart b/packages/flutter/test/widgets/widget_inspector_test.dart index a0d4054cafa07..e7c72f30ae837 100644 --- a/packages/flutter/test/widgets/widget_inspector_test.dart +++ b/packages/flutter/test/widgets/widget_inspector_test.dart @@ -939,7 +939,7 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { }); testWidgets( - 'WidgetInspector Exit Selection Mode button', + 'On-device selection test', (WidgetTester tester) async { // Enable widget selection mode. WidgetInspectorService.instance.isSelectMode = true; @@ -947,23 +947,12 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { final GlobalKey inspectorKey = GlobalKey(); setupDefaultPubRootDirectory(service); - Widget exitWidgetSelectionButtonBuilder( - BuildContext context, { - required VoidCallback onPressed, - required String semanticsLabel, - required GlobalKey key, - }) { - return Material( - child: ElevatedButton(onPressed: onPressed, key: key, child: null), - ); - } - await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WidgetInspector( key: inspectorKey, - exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder, + exitWidgetSelectionButtonBuilder: null, tapBehaviorButtonBuilder: null, moveExitWidgetSelectionButtonBuilder: null, child: const Text('Child 1'), @@ -991,286 +980,303 @@ class _TestWidgetInspectorService extends TestWidgetInspectorService { final String file = event['fileUri']! as String; final int line = event['line']! as int; final int column = event['column']! as int; - expect(file, endsWith('text.dart')); + expect(file, endsWith('widget_inspector_test.dart')); // We don't hardcode the actual lines the widgets are created on as that // would make this test fragile. expect(line, isNotNull); // Column numbers are more stable than line numbers. - expect(column, equals(16)); + expect(column, equals(28)); }, // [intended] Test requires --track-widget-creation flag. skip: !WidgetInspectorService.instance.isWidgetCreationTracked(), ); - testWidgets( - '[LTR] WidgetInspector Move Exit Selection Mode button to the right then left', - (WidgetTester tester) async { - WidgetInspectorService.instance.isSelectMode = true; - final GlobalKey inspectorKey = GlobalKey(); - setupDefaultPubRootDirectory(service); - - Widget exitWidgetSelectionButtonBuilder( - BuildContext context, { - required VoidCallback onPressed, - required String semanticsLabel, - required GlobalKey key, - }) { - return Material( - child: ElevatedButton( - onPressed: onPressed, - key: key, - child: const Text('EXIT SELECT MODE'), - ), - ); - } - - Widget moveWidgetSelectionButtonBuilder( - BuildContext context, { - required VoidCallback onPressed, - required String semanticsLabel, - bool usesDefaultAlignment = true, - }) { - return Material( - child: ElevatedButton( - onPressed: onPressed, - child: Text(usesDefaultAlignment ? 'MOVE RIGHT' : 'MOVE LEFT'), - ), - ); - } - - Finder buttonFinder(String buttonText) { - return find.ancestor(of: find.text(buttonText), matching: find.byType(ElevatedButton)); - } + group('On-device inspector buttons', () { + Widget exitWidgetSelectionButtonBuilder( + BuildContext context, { + required VoidCallback onPressed, + required String semanticsLabel, + required GlobalKey key, + }) { + return Material( + child: ElevatedButton( + onPressed: onPressed, + key: key, + child: const Text('EXIT SELECT MODE'), + ), + ); + } - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WidgetInspector( - key: inspectorKey, - exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder, - moveExitWidgetSelectionButtonBuilder: moveWidgetSelectionButtonBuilder, - tapBehaviorButtonBuilder: null, - child: const Text('APP'), - ), + Widget moveWidgetSelectionButtonBuilder( + BuildContext context, { + required VoidCallback onPressed, + required String semanticsLabel, + bool usesDefaultAlignment = true, + }) { + return Material( + child: ElevatedButton( + onPressed: onPressed, + child: Text(usesDefaultAlignment ? 'MOVE RIGHT' : 'MOVE LEFT'), ), ); + } - final Finder exitButton = buttonFinder('EXIT SELECT MODE'); - expect(exitButton, findsOneWidget); - final Finder moveRightButton = buttonFinder('MOVE RIGHT'); - expect(moveRightButton, findsOneWidget); - final double initialExitButtonX = tester.getCenter(exitButton).dx; + Widget tapBehaviorButtonBuilder( + BuildContext context, { + required VoidCallback onPressed, + required String semanticsLabel, + required bool selectionOnTapEnabled, + }) { + return Material( + child: ElevatedButton( + onPressed: onPressed, + child: Text(selectionOnTapEnabled ? 'SELECTION ON TAP' : 'APP INTERACTION ON TAP'), + ), + ); + } - await tester.tap(moveRightButton); - await tester.pump(); + Finder buttonFinder(String buttonText) { + return find.ancestor(of: find.text(buttonText), matching: find.byType(ElevatedButton)); + } - final Finder moveLeftButton = buttonFinder('MOVE LEFT'); - expect(moveLeftButton, findsOneWidget); - final double movedExitButtonX = tester.getCenter(exitButton).dx; + int navigateEventsCount() => service.dispatchedEvents('navigate', stream: 'ToolEvent').length; - expect(initialExitButtonX, lessThan(movedExitButtonX), reason: 'LTR: should move right'); + testWidgets( + 'Exit select mode button', + (WidgetTester tester) async { + // Enable widget selection mode. + WidgetInspectorService.instance.isSelectMode = true; - await tester.tap(moveLeftButton); - await tester.pump(); + final GlobalKey inspectorKey = GlobalKey(); + setupDefaultPubRootDirectory(service); - final double finalExitButtonX = tester.getCenter(exitButton).dx; - expect(finalExitButtonX, equals(initialExitButtonX)); - }, - // [intended] Test requires --track-widget-creation flag. - skip: !WidgetInspectorService.instance.isWidgetCreationTracked(), - ); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WidgetInspector( + key: inspectorKey, + exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder, + tapBehaviorButtonBuilder: null, + moveExitWidgetSelectionButtonBuilder: null, + child: const Column(children: [Text('Child 1'), Text('Child 2')]), + ), + ), + ); - testWidgets( - '[RTL] WidgetInspector Move Exit Selection Mode button to the left then right', - (WidgetTester tester) async { - WidgetInspectorService.instance.isSelectMode = true; - final GlobalKey inspectorKey = GlobalKey(); - setupDefaultPubRootDirectory(service); + // tap on child 1 + final Finder child1 = find.text('Child 1'); + await tester.tap(child1, warnIfMissed: false); + await tester.pump(); - Widget exitWidgetSelectionButtonBuilder( - BuildContext context, { - required VoidCallback onPressed, - required String semanticsLabel, - required GlobalKey key, - }) { - return Material( - child: ElevatedButton( - onPressed: onPressed, - key: key, - child: const Text('EXIT SELECT MODE'), - ), + // ensure that developer.inspect was called on child 1 + expect( + service.inspectedObjects(), + equals([child1.evaluate().first.renderObject]), ); - } - Widget moveWidgetSelectionButtonBuilder( - BuildContext context, { - required VoidCallback onPressed, - required String semanticsLabel, - bool usesDefaultAlignment = true, - }) { - return Material( - child: ElevatedButton( - onPressed: onPressed, - child: Text(usesDefaultAlignment ? 'MOVE RIGHT' : 'MOVE LEFT'), - ), + // ensure that there was a single navigate event + expect(navigateEventsCount(), equals(1)); + + // tap the exit selection mode button + final Finder exitButton = buttonFinder('EXIT SELECT MODE'); + expect(exitButton, findsOneWidget); + await tester.tap(exitButton); + await tester.pump(); + + // tap on child 2 + final Finder child2 = find.text('Child 2'); + await tester.tap(child2, warnIfMissed: false); + await tester.pump(); + + // ensure that developer.inspect was still only called on child 1 + expect( + service.inspectedObjects(), + equals([child1.evaluate().first.renderObject]), ); - } - Finder buttonFinder(String buttonText) { - return find.ancestor(of: find.text(buttonText), matching: find.byType(ElevatedButton)); - } + // ensure that there is still only a single navigate event + expect(navigateEventsCount(), equals(1)); + }, + // [intended] Test requires --track-widget-creation flag. + skip: !WidgetInspectorService.instance.isWidgetCreationTracked(), + ); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.rtl, - child: WidgetInspector( - key: inspectorKey, - exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder, - moveExitWidgetSelectionButtonBuilder: moveWidgetSelectionButtonBuilder, - tapBehaviorButtonBuilder: null, - child: const Text('APP'), + testWidgets( + '[LTR] Move button group to the right then left', + (WidgetTester tester) async { + WidgetInspectorService.instance.isSelectMode = true; + final GlobalKey inspectorKey = GlobalKey(); + setupDefaultPubRootDirectory(service); + + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WidgetInspector( + key: inspectorKey, + exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder, + moveExitWidgetSelectionButtonBuilder: moveWidgetSelectionButtonBuilder, + tapBehaviorButtonBuilder: null, + child: const Text('APP'), + ), ), - ), - ); + ); - final Finder exitButton = buttonFinder('EXIT SELECT MODE'); - expect(exitButton, findsOneWidget); - final Finder moveRightButton = buttonFinder('MOVE RIGHT'); - expect(moveRightButton, findsOneWidget); - final double initialExitButtonX = tester.getCenter(exitButton).dx; + final Finder exitButton = buttonFinder('EXIT SELECT MODE'); + expect(exitButton, findsOneWidget); + final Finder moveRightButton = buttonFinder('MOVE RIGHT'); + expect(moveRightButton, findsOneWidget); + final double initialExitButtonX = tester.getCenter(exitButton).dx; - await tester.tap(moveRightButton); - await tester.pump(); + await tester.tap(moveRightButton); + await tester.pump(); - final Finder moveLeftButton = buttonFinder('MOVE LEFT'); - expect(moveLeftButton, findsOneWidget); - final double movedExitButtonX = tester.getCenter(exitButton).dx; + final Finder moveLeftButton = buttonFinder('MOVE LEFT'); + expect(moveLeftButton, findsOneWidget); + final double movedExitButtonX = tester.getCenter(exitButton).dx; - expect(initialExitButtonX, greaterThan(movedExitButtonX), reason: 'RTL: should move left'); + expect(initialExitButtonX, lessThan(movedExitButtonX), reason: 'LTR: should move right'); - await tester.tap(moveLeftButton); - await tester.pump(); + await tester.tap(moveLeftButton); + await tester.pump(); - final double finalExitButtonX = tester.getCenter(exitButton).dx; - expect(finalExitButtonX, equals(initialExitButtonX)); - }, - // [intended] Test requires --track-widget-creation flag. - skip: !WidgetInspectorService.instance.isWidgetCreationTracked(), - ); + final double finalExitButtonX = tester.getCenter(exitButton).dx; + expect(finalExitButtonX, equals(initialExitButtonX)); + }, + // [intended] Test requires --track-widget-creation flag. + skip: !WidgetInspectorService.instance.isWidgetCreationTracked(), + ); - testWidgets( - 'WidgetInspector Tap behavior button', - (WidgetTester tester) async { - Widget exitWidgetSelectionButtonBuilder( - BuildContext context, { - required VoidCallback onPressed, - required String semanticsLabel, - required GlobalKey key, - }) { - return Material( - child: ElevatedButton(onPressed: onPressed, key: key, child: null), - ); - } + testWidgets( + '[RTL] Move button group to the left then right', + (WidgetTester tester) async { + WidgetInspectorService.instance.isSelectMode = true; + final GlobalKey inspectorKey = GlobalKey(); + setupDefaultPubRootDirectory(service); - Widget tapBehaviorButtonBuilder( - BuildContext context, { - required VoidCallback onPressed, - required String semanticsLabel, - required bool selectionOnTapEnabled, - }) { - return Material( - child: ElevatedButton( - onPressed: onPressed, - child: Text(selectionOnTapEnabled ? 'SELECTION ON TAP' : 'APP INTERACTION ON TAP'), + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.rtl, + child: WidgetInspector( + key: inspectorKey, + exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder, + moveExitWidgetSelectionButtonBuilder: moveWidgetSelectionButtonBuilder, + tapBehaviorButtonBuilder: null, + child: const Text('APP'), + ), ), ); - } - Finder buttonFinder(String buttonText) { - return find.ancestor(of: find.text(buttonText), matching: find.byType(ElevatedButton)); - } + final Finder exitButton = buttonFinder('EXIT SELECT MODE'); + expect(exitButton, findsOneWidget); + final Finder moveRightButton = buttonFinder('MOVE RIGHT'); + expect(moveRightButton, findsOneWidget); + final double initialExitButtonX = tester.getCenter(exitButton).dx; - int navigateEventsCount() => - service.dispatchedEvents('navigate', stream: 'ToolEvent').length; + await tester.tap(moveRightButton); + await tester.pump(); - // Enable widget selection mode. - WidgetInspectorService.instance.isSelectMode = true; + final Finder moveLeftButton = buttonFinder('MOVE LEFT'); + expect(moveLeftButton, findsOneWidget); + final double movedExitButtonX = tester.getCenter(exitButton).dx; - // Pump the test widget. - final GlobalKey inspectorKey = GlobalKey(); - setupDefaultPubRootDirectory(service); - await tester.pumpWidget( - Directionality( - textDirection: TextDirection.ltr, - child: WidgetInspector( - key: inspectorKey, - exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder, - tapBehaviorButtonBuilder: tapBehaviorButtonBuilder, - moveExitWidgetSelectionButtonBuilder: null, - child: const Row(children: [Text('Child 1'), Text('Child 2')]), + expect( + initialExitButtonX, + greaterThan(movedExitButtonX), + reason: 'RTL: should move left', + ); + + await tester.tap(moveLeftButton); + await tester.pump(); + + final double finalExitButtonX = tester.getCenter(exitButton).dx; + expect(finalExitButtonX, equals(initialExitButtonX)); + }, + // [intended] Test requires --track-widget-creation flag. + skip: !WidgetInspectorService.instance.isWidgetCreationTracked(), + ); + + testWidgets( + 'Tap behavior button', + (WidgetTester tester) async { + // Enable widget selection mode. + WidgetInspectorService.instance.isSelectMode = true; + + // Pump the test widget. + final GlobalKey inspectorKey = GlobalKey(); + setupDefaultPubRootDirectory(service); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: WidgetInspector( + key: inspectorKey, + exitWidgetSelectionButtonBuilder: exitWidgetSelectionButtonBuilder, + tapBehaviorButtonBuilder: tapBehaviorButtonBuilder, + moveExitWidgetSelectionButtonBuilder: null, + child: const Row(children: [Text('Child 1'), Text('Child 2')]), + ), ), - ), - ); + ); - // Verify there are no navigate events yet. - expect(navigateEventsCount(), equals(0)); + // Verify there are no navigate events yet. + expect(navigateEventsCount(), equals(0)); - // Tap on the first child widget. - final Finder child1 = find.text('Child 1'); - await tester.tap(child1, warnIfMissed: false); - await tester.pump(); + // Tap on the first child widget. + final Finder child1 = find.text('Child 1'); + await tester.tap(child1, warnIfMissed: false); + await tester.pump(); - // Verify the selection matches the first child widget. - final Element child1Element = child1.evaluate().first; - expect(service.selection.current, equals(child1Element.renderObject)); + // Verify the selection matches the first child widget. + final Element child1Element = child1.evaluate().first; + expect(service.selection.current, equals(child1Element.renderObject)); - // Verify that a navigate event was sent. - expect(navigateEventsCount(), equals(1)); + // Verify that a navigate event was sent. + expect(navigateEventsCount(), equals(1)); - // Tap on the SELECTION ON TAP button. - final Finder tapBehaviorButtonBefore = buttonFinder('SELECTION ON TAP'); - expect(tapBehaviorButtonBefore, findsOneWidget); - await tester.tap(tapBehaviorButtonBefore); - await tester.pump(); + // Tap on the SELECTION ON TAP button. + final Finder tapBehaviorButtonBefore = buttonFinder('SELECTION ON TAP'); + expect(tapBehaviorButtonBefore, findsOneWidget); + await tester.tap(tapBehaviorButtonBefore); + await tester.pump(); - // Verify the tap behavior button's UI has been updated. - expect(tapBehaviorButtonBefore, findsNothing); - final Finder tapBehaviorButtonAfter = buttonFinder('APP INTERACTION ON TAP'); - expect(tapBehaviorButtonAfter, findsOneWidget); + // Verify the tap behavior button's UI has been updated. + expect(tapBehaviorButtonBefore, findsNothing); + final Finder tapBehaviorButtonAfter = buttonFinder('APP INTERACTION ON TAP'); + expect(tapBehaviorButtonAfter, findsOneWidget); - // Tap on the second child widget. - final Finder child2 = find.text('Child 2'); - await tester.tap(child2, warnIfMissed: false); - await tester.pump(); + // Tap on the second child widget. + final Finder child2 = find.text('Child 2'); + await tester.tap(child2, warnIfMissed: false); + await tester.pump(); - // Verify there is no selection. - expect(service.selection.current, isNull); + // Verify there is no selection. + expect(service.selection.current, isNull); - // Verify no navigate events were sent. - expect(navigateEventsCount(), equals(1)); + // Verify no navigate events were sent. + expect(navigateEventsCount(), equals(1)); - // Tap on the SELECTION ON TAP button again. - await tester.tap(tapBehaviorButtonAfter); - await tester.pump(); + // Tap on the SELECTION ON TAP button again. + await tester.tap(tapBehaviorButtonAfter); + await tester.pump(); - // Verify the tap behavior button's UI has been reset. - expect(tapBehaviorButtonAfter, findsNothing); - expect(tapBehaviorButtonBefore, findsOneWidget); + // Verify the tap behavior button's UI has been reset. + expect(tapBehaviorButtonAfter, findsNothing); + expect(tapBehaviorButtonBefore, findsOneWidget); - // Tap on the second child widget again. - await tester.tap(child2, warnIfMissed: false); - await tester.pump(); + // Tap on the second child widget again. + await tester.tap(child2, warnIfMissed: false); + await tester.pump(); - // Verify the selection now matches the second child widget. - final Element child2Element = child2.evaluate().first; - expect(service.selection.current, equals(child2Element.renderObject)); + // Verify the selection now matches the second child widget. + final Element child2Element = child2.evaluate().first; + expect(service.selection.current, equals(child2Element.renderObject)); - // Verify another navigate event was sent. - expect(navigateEventsCount(), equals(2)); - }, - // [intended] Test requires --track-widget-creation flag. - skip: !WidgetInspectorService.instance.isWidgetCreationTracked(), - ); + // Verify another navigate event was sent. + expect(navigateEventsCount(), equals(2)); + }, + // [intended] Test requires --track-widget-creation flag. + skip: !WidgetInspectorService.instance.isWidgetCreationTracked(), + ); + }); testWidgets('test transformDebugCreator will re-order if after stack trace', ( WidgetTester tester, From 00da9435e302f5fb92df6aacfb8f4065b4633b5f Mon Sep 17 00:00:00 2001 From: Victoria Ashworth <15619084+vashworth@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:56:01 -0500 Subject: [PATCH 100/204] Rename UIScene integration test projects and fix Xcode compatibility (#176635) This PR renames the UIScene integration test projects, makes them compatible with Xcode 16, and organizes the tests. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../bin/tasks/module_uiscene_test_ios.dart | 316 +++++----- .../project.pbxproj | 543 ++++++++++++++++ .../contents.xcworkspacedata | 0 .../contents.xcworkspacedata | 2 +- .../AccentColor.colorset/Contents.json | 0 .../AppIcon.appiconset/Contents.json | 0 .../Assets.xcassets/Contents.json | 0 .../ContentView.swift | 0 .../NativeSwiftUIExperimentApp.swift} | 2 +- .../NativeSwiftUIExperimentUITests.swift | 8 + .../Podfile | 6 +- .../project.pbxproj | 580 ++++++++++++++++++ .../contents.xcworkspacedata | 0 .../contents.xcworkspacedata | 2 +- .../AppDelegate.swift | 0 .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 35 ++ .../Assets.xcassets/Contents.json | 6 + .../Base.lproj/LaunchScreen.storyboard | 0 .../Base.lproj/Main.storyboard | 0 .../NativeUIKitSwiftExperiment}/Info.plist | 0 .../SceneDelegate.swift | 0 .../ViewController.swift | 0 .../NativeUIKitSwiftExperimentUITests.swift | 8 + .../Podfile | 6 +- ...ITests-ApplicationEvents-AppMigrated.swift | 2 +- ...sts-ApplicationEvents-AppNotMigrated.swift | 2 +- ...Events-FlutterImplicitEngineDelegate.swift | 2 +- ...-SceneEvents-ApplicationLaunchEvents.swift | 2 +- .../UITests-SceneEvents-MultiScenes.swift | 2 +- ...ests-SceneEvents-NoApplicationEvents.swift | 2 +- .../native/UITests-SceneEvents.swift | 2 +- ...eEventsNoConnect-NoApplicationEvents.swift | 2 +- .../native/UITests-StateRestoration.swift | 2 +- .../xcode_swiftui/xcode-swiftui-Info.plist | 5 - .../xcode_swiftui.xcodeproj/project.pbxproj | 553 ----------------- .../xcode_uikit_swift/Info.plist | 6 - .../project.pbxproj | 558 ----------------- 38 files changed, 1373 insertions(+), 1292 deletions(-) create mode 100644 dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment.xcodeproj/project.pbxproj rename dev/integration_tests/ios_add2app_uiscene/{xcode_swiftui/xcode_swiftui.xcodeproj => NativeSwiftUIExperiment/NativeSwiftUIExperiment.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (100%) rename dev/integration_tests/ios_add2app_uiscene/{xcode_uikit_swift/xcode_uikit_swift.xcworkspace => NativeSwiftUIExperiment/NativeSwiftUIExperiment.xcworkspace}/contents.xcworkspacedata (75%) rename dev/integration_tests/ios_add2app_uiscene/{xcode_swiftui/xcode_swiftui => NativeSwiftUIExperiment/NativeSwiftUIExperiment}/Assets.xcassets/AccentColor.colorset/Contents.json (100%) rename dev/integration_tests/ios_add2app_uiscene/{xcode_swiftui/xcode_swiftui => NativeSwiftUIExperiment/NativeSwiftUIExperiment}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename dev/integration_tests/ios_add2app_uiscene/{xcode_swiftui/xcode_swiftui => NativeSwiftUIExperiment/NativeSwiftUIExperiment}/Assets.xcassets/Contents.json (100%) rename dev/integration_tests/ios_add2app_uiscene/{xcode_swiftui/xcode_swiftui => NativeSwiftUIExperiment/NativeSwiftUIExperiment}/ContentView.swift (100%) rename dev/integration_tests/ios_add2app_uiscene/{xcode_swiftui/xcode_swiftui/xcode_swiftuiApp.swift => NativeSwiftUIExperiment/NativeSwiftUIExperiment/NativeSwiftUIExperimentApp.swift} (87%) create mode 100644 dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperimentUITests/NativeSwiftUIExperimentUITests.swift rename dev/integration_tests/ios_add2app_uiscene/{xcode_swiftui => NativeSwiftUIExperiment}/Podfile (80%) create mode 100644 dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment.xcodeproj/project.pbxproj rename dev/integration_tests/ios_add2app_uiscene/{xcode_uikit_swift/xcode_uikit_swift.xcodeproj => NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (100%) rename dev/integration_tests/ios_add2app_uiscene/{xcode_swiftui/xcode_swiftui.xcworkspace => NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment.xcworkspace}/contents.xcworkspacedata (74%) rename dev/integration_tests/ios_add2app_uiscene/{xcode_uikit_swift/xcode_uikit_swift => NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment}/AppDelegate.swift (100%) create mode 100644 dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Assets.xcassets/Contents.json rename dev/integration_tests/ios_add2app_uiscene/{xcode_uikit_swift/xcode_uikit_swift => NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment}/Base.lproj/LaunchScreen.storyboard (100%) rename dev/integration_tests/ios_add2app_uiscene/{xcode_uikit_swift/xcode_uikit_swift => NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment}/Base.lproj/Main.storyboard (100%) rename dev/integration_tests/ios_add2app_uiscene/{xcode_uikit_swift/xcode_uikit_swift => NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment}/Info.plist (100%) rename dev/integration_tests/ios_add2app_uiscene/{xcode_uikit_swift/xcode_uikit_swift => NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment}/SceneDelegate.swift (100%) rename dev/integration_tests/ios_add2app_uiscene/{xcode_uikit_swift/xcode_uikit_swift => NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment}/ViewController.swift (100%) create mode 100644 dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift rename dev/integration_tests/ios_add2app_uiscene/{xcode_uikit_swift => NativeUIKitSwiftExperiment}/Podfile (79%) delete mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode-swiftui-Info.plist delete mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.pbxproj delete mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/Info.plist delete mode 100644 dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift.xcodeproj/project.pbxproj diff --git a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart index 144d0a1e8e2dd..07c1a77d0eb6a 100644 --- a/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart +++ b/dev/devicelab/bin/tasks/module_uiscene_test_ios.dart @@ -78,7 +78,8 @@ Future main(List args) async { templatesDir: templatesDir, ); - bool testFailed = false; + int testCount = 0; + int testFailedCount = 0; await testWithNewIOSSimulator( 'TestAdd2AppSim', deviceTypeId: 'com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11-inch-3rd-generation', @@ -121,8 +122,9 @@ Future main(List args) async { xcodeProjectDir: xcodeProjectDir, xcodeProjectName: xcodeProjectName, ); + testCount++; if (result != 0) { - testFailed = true; + testFailedCount++; } // Reset files to original between scenarios unless we're targetting a specific test. @@ -136,9 +138,9 @@ Future main(List args) async { }, ); - if (testFailed) { + if (testFailedCount > 0) { return TaskResult.failure( - 'One or more native tests failed. Search the logs for "** TEST FAILED **"', + '$testFailedCount out of $testCount native tests failed. Search the logs for "** TEST FAILED **"', ); } return TaskResult.success(null); @@ -202,9 +204,9 @@ Future<(String, Directory)> _createNativeApp({ switch (xcodeProjectType) { case XcodeProjectType.UIKitSwift: - xcodeProjectName = 'xcode_uikit_swift'; + xcodeProjectName = 'NativeUIKitSwiftExperiment'; case XcodeProjectType.SwiftUI: - xcodeProjectName = 'xcode_swiftui'; + xcodeProjectName = 'NativeSwiftUIExperiment'; } // Copy Xcode project final Directory xcodeProjectDir = Directory(path.join(destinationDir.path, xcodeProjectName)); @@ -373,15 +375,53 @@ class Scenarios { /// in a native iOS app. The file replacements are used to set up the /// specific configuration for each scenario. late Map> uiKitSwiftScenarios = >{ + ...basicLifecycleScenarios, + ...stateRestorationScenarios, + ...implicitEngineDelegateScenarios, + ...multiSceneScenarios, + }; + + late Map> swiftUIScenarios = >{ + 'SwiftUI-FlutterSceneDelegate': { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/Info-migrated-no-config.plist': + r'$XCODE_PROJ_DIR/NativeSwiftUIExperiment/NativeSwiftUIExperiment-Info.plist', + r'$TEMPLATE_DIR/native/SwiftUIApp-FlutterSceneDelegate.swift': + r'$XCODE_PROJ_DIR/NativeSwiftUIExperiment/NativeSwiftUIExperimentApp.swift', + r'$TEMPLATE_DIR/native/SwiftUIApp-ContentView.swift': + r'$XCODE_PROJ_DIR/NativeSwiftUIExperiment/ContentView.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift': + r'$XCODE_PROJ_DIR/NativeSwiftUIExperimentUITests/NativeSwiftUIExperimentUITests.swift', + }, + 'SwiftUI-FlutterSceneLifeCycleProvider': { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/Info-migrated-no-config.plist': + r'$XCODE_PROJ_DIR/NativeSwiftUIExperiment/NativeSwiftUIExperiment-Info.plist', + r'$TEMPLATE_DIR/native/SwiftUIApp-FlutterSceneLifeCycleProvider.swift': + r'$XCODE_PROJ_DIR/NativeSwiftUIExperiment/NativeSwiftUIExperimentApp.swift', + r'$TEMPLATE_DIR/native/SwiftUIApp-ContentView.swift': + r'$XCODE_PROJ_DIR/NativeSwiftUIExperiment/ContentView.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift': + r'$XCODE_PROJ_DIR/NativeSwiftUIExperimentUITests/NativeSwiftUIExperimentUITests.swift', + }, + }; + + late Map> basicLifecycleScenarios = >{ // When both the app and the plugin have migrated to scenes, we expect scene events. 'AppMigrated-FlutterSceneDelegate-PluginMigrated': { ...sharedLifecycleFiles, r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift', r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', r'$TEMPLATE_DIR/native/UITests-SceneEvents.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', }, // When the app has migrated but the plugin hasn't, we expect application events to be used as @@ -389,22 +429,22 @@ class Scenarios { 'AppMigrated-FlutterSceneDelegate-PluginNotMigrated': { ...sharedLifecycleFiles, r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift', r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-unmigrated.swift': r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-AppMigrated.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', }, // When both the app and the plugin have migrated to scenes, we expect scene events. 'AppMigrated-FlutterSceneLifeCycleProvider-PluginMigrated': { ...sharedLifecycleFiles, r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneLifeCycleProvider.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift', r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', r'$TEMPLATE_DIR/native/UITests-SceneEvents.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', }, // When the app has migrated but the plugin hasn't, we expect application events to be used as @@ -412,51 +452,122 @@ class Scenarios { 'AppMigrated-FlutterSceneLifeCycleProvider-PluginNotMigrated': { ...sharedLifecycleFiles, r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneLifeCycleProvider.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift', r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-unmigrated.swift': r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-AppMigrated.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', }, // When the app has not migrated, but the plugin supports both, we expect application events. 'AppNotMigrated-FlutterSceneDelegate-PluginMigrated': { ...sharedLifecycleFiles, r'$TEMPLATE_DIR/native/Info-unmigrated.plist': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/Info.plist', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist', r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-AppNotMigrated.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', }, // When the app and plugin have not migrated, we expect application events. 'AppNotMigrated-FlutterSceneDelegate-PluginNotMigrated': { ...sharedLifecycleFiles, r'$TEMPLATE_DIR/native/Info-unmigrated.plist': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/Info.plist', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist', r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-unmigrated.swift': r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-AppNotMigrated.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', + }, + }; + + late Map> multiSceneScenarios = >{ + // When multi scene is enabled and the rootViewController is a FlutterViewController, we + // expect all scene events without manual registration. + 'MultiSceneEnabled-FlutterSceneDelegate-RootViewController': { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/Info-MultiSceneEnabled-Storyboard.plist': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist', + r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift', + r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Base.lproj/Main.storyboard', + r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/UITests-SceneEvents.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', }, - ...multiSceneScenarios, + // When multi scene is enabled and the ViewController is created programatically with a + // manually registered FlutterEngine, we expect all scene events. + 'MultiSceneEnabled-FlutterSceneDelegate-ManualRegistration-NoStoryboard': { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/Info-MultiSceneEnabled-NoStoryboard.plist': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist', + r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift', + r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate-MultiScene-NoStoryboard.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift', + r'$TEMPLATE_DIR/native/ViewController-FlutterEngineFromSceneDelegate-NoStoryboard.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/ViewController.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', + }, + + // When multi scene is enabled and the ViewController is created via Storyboard with a + // manually registered FlutterEngine, we expect all scene events. + 'MultiSceneEnabled-FlutterSceneDelegate-ManualRegistration-Storyboard': { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/Info-MultiSceneEnabled-Storyboard.plist': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist', + r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift', + r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate-MultiScene-Storyboard.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift', + r'$TEMPLATE_DIR/native/ViewController-FlutterEngineFromSceneDelegate-Storyboard.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/ViewController.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', + }, + }; + + late Map> stateRestorationScenarios = >{ + // State restoration work both when migrated and when not. + 'AppMigrated-StateRestoration': {...sharedStateRestorationFiles}, + 'AppNotMigrated-StateRestoration': { + ...sharedStateRestorationFiles, + r'$TEMPLATE_DIR/native/Info-unmigrated.plist': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist', + }, + }; + + late Map> + implicitEngineDelegateScenarios = >{ // When using an implicit FlutterEngine created by the storyboard, we expect plugins to // receive application launch events and scene events. 'FlutterImplicitEngineDelegate-AppMigrated-StoryboardFlutterViewController': { ...sharedAppLifecycleFiles, ...sharedPluginLifecycleFiles, r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift', r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/Base.lproj/Main.storyboard', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Base.lproj/Main.storyboard', r'$TEMPLATE_DIR/native/AppDelegate-FlutterImplicitEngineDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift', r'$TEMPLATE_DIR/native/UITests-SceneEvents-ApplicationLaunchEvents.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', }, // When registering plugins with the AppDelegate's self (and therefore the FlutterLaunchEngine) @@ -467,37 +578,36 @@ class Scenarios { ...sharedAppLifecycleFiles, ...sharedPluginLifecycleFiles, r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift', r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/Base.lproj/Main.storyboard', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Base.lproj/Main.storyboard', r'$TEMPLATE_DIR/native/AppDelegate-FlutterImplicitEngineDelegateWithLaunchEngine.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift', r'$TEMPLATE_DIR/native/UITests-SceneEvents.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', }, // When the app has not migrated to scenes, storyboard is instantiated earlier in the lifecycle. // So when using an implicit FlutterEngine created by the storyboard, we expect plugins to // receive all application events. - 'FlutterImplicitEngineDelegate-AppNotMigrated-StoryboardFlutterViewController': - { - ...sharedAppLifecycleFiles, - ...sharedPluginLifecycleFiles, - r'$TEMPLATE_DIR/native/Info-unmigrated.plist': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/Info.plist', - r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', - r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': - r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', - r'$TEMPLATE_DIR/native/AppDelegate-FlutterImplicitEngineDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', - r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/Base.lproj/Main.storyboard', - r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-FlutterImplicitEngineDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', - }, + 'FlutterImplicitEngineDelegate-AppNotMigrated-StoryboardFlutterViewController': { + ...sharedAppLifecycleFiles, + ...sharedPluginLifecycleFiles, + r'$TEMPLATE_DIR/native/Info-unmigrated.plist': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Info.plist', + r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift', + r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': + r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', + r'$TEMPLATE_DIR/native/AppDelegate-FlutterImplicitEngineDelegate.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift', + r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Base.lproj/Main.storyboard', + r'$TEMPLATE_DIR/native/UITests-ApplicationEvents-FlutterImplicitEngineDelegate.swift': + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', + }, // When using an implicit FlutterEngine, created by the FlutterViewController in another // ViewController, we expect plugins to be registered after the FlutterViewController is @@ -508,113 +618,15 @@ class Scenarios { ...sharedAppLifecycleFiles, ...sharedPluginLifecycleFiles, r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift', r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', r'$TEMPLATE_DIR/native/AppDelegate-FlutterImplicitEngineDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift', r'$TEMPLATE_DIR/native/ViewController-ImplicitFlutterEngine.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/ViewController.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/ViewController.swift', r'$TEMPLATE_DIR/native/UITests-SceneEventsNoConnect-NoApplicationEvents.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', - }, - - // State restoration work both when migrated and when not. - 'AppMigrated-StateRestoration': {...sharedStateRestorationFiles}, - 'AppNotMigrated-StateRestoration': { - ...sharedStateRestorationFiles, - r'$TEMPLATE_DIR/native/Info-unmigrated.plist': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/Info.plist', - }, - }; - - late Map> swiftUIScenarios = >{ - 'SwiftUI-FlutterSceneDelegate': { - ...sharedAppLifecycleFiles, - ...sharedPluginLifecycleFiles, - r'$TEMPLATE_DIR/native/Info-migrated-no-config.plist': - r'$XCODE_PROJ_DIR/xcode_swiftui/xcode_swiftui-Info.plist', - r'$TEMPLATE_DIR/native/SwiftUIApp-FlutterSceneDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_swiftui/xcode_swiftuiApp.swift', - r'$TEMPLATE_DIR/native/SwiftUIApp-ContentView.swift': - r'$XCODE_PROJ_DIR/xcode_swiftui/ContentView.swift', - r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': - r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', - r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift': - r'$XCODE_PROJ_DIR/xcode_swiftuiUITests/xcode_swiftuiUITests.swift', - }, - 'SwiftUI-FlutterSceneLifeCycleProvider': { - ...sharedAppLifecycleFiles, - ...sharedPluginLifecycleFiles, - r'$TEMPLATE_DIR/native/Info-migrated-no-config.plist': - r'$XCODE_PROJ_DIR/xcode_swiftui/xcode_swiftui-Info.plist', - r'$TEMPLATE_DIR/native/SwiftUIApp-FlutterSceneLifeCycleProvider.swift': - r'$XCODE_PROJ_DIR/xcode_swiftui/xcode_swiftuiApp.swift', - r'$TEMPLATE_DIR/native/SwiftUIApp-ContentView.swift': - r'$XCODE_PROJ_DIR/xcode_swiftui/ContentView.swift', - r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': - r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', - r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift': - r'$XCODE_PROJ_DIR/xcode_swiftuiUITests/xcode_swiftuiUITests.swift', - }, - }; - - late Map> multiSceneScenarios = >{ - // When multi scene is enabled and the rootViewController is a FlutterViewController, we - // expect all scene events without manual registration. - 'MultiSceneEnabled-FlutterSceneDelegate-RootViewController': { - ...sharedAppLifecycleFiles, - ...sharedPluginLifecycleFiles, - r'$TEMPLATE_DIR/native/Info-MultiSceneEnabled-Storyboard.plist': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/Info.plist', - r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', - r'$TEMPLATE_DIR/native/Main-FlutterViewController.storyboard': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/Base.lproj/Main.storyboard', - r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', - r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': - r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', - r'$TEMPLATE_DIR/native/UITests-SceneEvents.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', - }, - - // When multi scene is enabled and the ViewController is created programatically with a - // manually registered FlutterEngine, we expect all scene events. - 'MultiSceneEnabled-FlutterSceneDelegate-ManualRegistration-NoStoryboard': { - ...sharedAppLifecycleFiles, - ...sharedPluginLifecycleFiles, - r'$TEMPLATE_DIR/native/Info-MultiSceneEnabled-NoStoryboard.plist': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/Info.plist', - r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', - r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate-MultiScene-NoStoryboard.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', - r'$TEMPLATE_DIR/native/ViewController-FlutterEngineFromSceneDelegate-NoStoryboard.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/ViewController.swift', - r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': - r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', - r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', - }, - - // When multi scene is enabled and the ViewController is created via Storyboard with a - // manually registered FlutterEngine, we expect all scene events. - 'MultiSceneEnabled-FlutterSceneDelegate-ManualRegistration-Storyboard': { - ...sharedAppLifecycleFiles, - ...sharedPluginLifecycleFiles, - r'$TEMPLATE_DIR/native/Info-MultiSceneEnabled-Storyboard.plist': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/Info.plist', - r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', - r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate-MultiScene-Storyboard.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', - r'$TEMPLATE_DIR/native/ViewController-FlutterEngineFromSceneDelegate-Storyboard.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/ViewController.swift', - r'$TEMPLATE_DIR/flutterplugin/ios/LifecyclePlugin-migrated.swift': - r'$PLUGIN_DIR/ios/Classes/MyPlugin.swift', - r'$TEMPLATE_DIR/native/UITests-SceneEvents-NoApplicationEvents.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', }, }; @@ -622,9 +634,9 @@ class Scenarios { ...sharedAppLifecycleFiles, ...sharedPluginLifecycleFiles, r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate-FlutterEngine.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift', r'$TEMPLATE_DIR/native/ViewController-FlutterEngineFromAppDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/ViewController.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/ViewController.swift', }; late Map sharedAppLifecycleFiles = { @@ -643,12 +655,12 @@ class Scenarios { late Map sharedStateRestorationFiles = { r'$TEMPLATE_DIR/flutterapp/lib/main-StateRestorationTest': r'$APP_DIR/lib/main.dart', r'$TEMPLATE_DIR/native/AppDelegate-FlutterAppDelegate-FlutterEngine.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/AppDelegate.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/AppDelegate.swift', r'$TEMPLATE_DIR/native/SceneDelegate-FlutterSceneDelegate.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/SceneDelegate.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/SceneDelegate.swift', r'$TEMPLATE_DIR/native/Main-FlutterViewController-RestorationId.storyboard': - r'$XCODE_PROJ_DIR/xcode_uikit_swift/Base.lproj/Main.storyboard', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperiment/Base.lproj/Main.storyboard', r'$TEMPLATE_DIR/native/UITests-StateRestoration.swift': - r'$XCODE_PROJ_DIR/xcode_uikit_swiftUITests/xcode_uikit_swiftUITests.swift', + r'$XCODE_PROJ_DIR/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift', }; } diff --git a/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment.xcodeproj/project.pbxproj b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment.xcodeproj/project.pbxproj new file mode 100644 index 0000000000000..20f28ec1a718d --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment.xcodeproj/project.pbxproj @@ -0,0 +1,543 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1E54F750DBB9DDB3CD0BFF34 /* Pods_NativeSwiftUIExperiment_NativeSwiftUIExperimentUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 67398A34BD2DEF2BBE88904A /* Pods_NativeSwiftUIExperiment_NativeSwiftUIExperimentUITests.framework */; }; + 78D658792E95923600CAC372 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 78D658752E95923600CAC372 /* Assets.xcassets */; }; + 78D6587A2E95923600CAC372 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D658762E95923600CAC372 /* ContentView.swift */; }; + 78D6587B2E95923600CAC372 /* NativeSwiftUIExperimentApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D658772E95923600CAC372 /* NativeSwiftUIExperimentApp.swift */; }; + 78D658A22E95999E00CAC372 /* NativeSwiftUIExperimentUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D658A12E95999E00CAC372 /* NativeSwiftUIExperimentUITests.swift */; }; + 8CC4D8ED4325AD66F4105A16 /* Pods_NativeSwiftUIExperiment.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 79FA578B09AFD4EEB7A29CB7 /* Pods_NativeSwiftUIExperiment.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 7831F3402E8E1F8A00BBDDC3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7831F3202E8E1F8800BBDDC3 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7831F3272E8E1F8800BBDDC3; + remoteInfo = NativeSwiftUIExperiment; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 004E2C25B02E5C8091BDDA34 /* Pods-NativeSwiftUIExperiment.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeSwiftUIExperiment.release.xcconfig"; path = "Target Support Files/Pods-NativeSwiftUIExperiment/Pods-NativeSwiftUIExperiment.release.xcconfig"; sourceTree = ""; }; + 03FE462B080E3A5DA0F8E380 /* Pods-NativeSwiftUIExperiment-NativeSwiftUIExperimentUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeSwiftUIExperiment-NativeSwiftUIExperimentUITests.release.xcconfig"; path = "Target Support Files/Pods-NativeSwiftUIExperiment-NativeSwiftUIExperimentUITests/Pods-NativeSwiftUIExperiment-NativeSwiftUIExperimentUITests.release.xcconfig"; sourceTree = ""; }; + 3A6B97C8420FF2942440AAD6 /* Pods-NativeSwiftUIExperimentTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeSwiftUIExperimentTests.release.xcconfig"; path = "Target Support Files/Pods-NativeSwiftUIExperimentTests/Pods-NativeSwiftUIExperimentTests.release.xcconfig"; sourceTree = ""; }; + 67398A34BD2DEF2BBE88904A /* Pods_NativeSwiftUIExperiment_NativeSwiftUIExperimentUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NativeSwiftUIExperiment_NativeSwiftUIExperimentUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7831F3282E8E1F8800BBDDC3 /* NativeSwiftUIExperiment.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NativeSwiftUIExperiment.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7831F33F2E8E1F8A00BBDDC3 /* NativeSwiftUIExperimentUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NativeSwiftUIExperimentUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 78D658752E95923600CAC372 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 78D658762E95923600CAC372 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 78D658772E95923600CAC372 /* NativeSwiftUIExperimentApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeSwiftUIExperimentApp.swift; sourceTree = ""; }; + 78D658A12E95999E00CAC372 /* NativeSwiftUIExperimentUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeSwiftUIExperimentUITests.swift; sourceTree = ""; }; + 79FA578B09AFD4EEB7A29CB7 /* Pods_NativeSwiftUIExperiment.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NativeSwiftUIExperiment.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7BCA9D8BB2B4333006E2F322 /* Pods-NativeSwiftUIExperiment-NativeSwiftUIExperimentUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeSwiftUIExperiment-NativeSwiftUIExperimentUITests.debug.xcconfig"; path = "Target Support Files/Pods-NativeSwiftUIExperiment-NativeSwiftUIExperimentUITests/Pods-NativeSwiftUIExperiment-NativeSwiftUIExperimentUITests.debug.xcconfig"; sourceTree = ""; }; + 82A2DD74D26CD1CD4CF1CBC4 /* Pods-NativeSwiftUIExperimentTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeSwiftUIExperimentTests.debug.xcconfig"; path = "Target Support Files/Pods-NativeSwiftUIExperimentTests/Pods-NativeSwiftUIExperimentTests.debug.xcconfig"; sourceTree = ""; }; + EC6932CFFBDC3F339936C54B /* Pods-NativeSwiftUIExperiment.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeSwiftUIExperiment.debug.xcconfig"; path = "Target Support Files/Pods-NativeSwiftUIExperiment/Pods-NativeSwiftUIExperiment.debug.xcconfig"; sourceTree = ""; }; + FE9DE93C7456A24671A3DE9F /* Pods_NativeSwiftUIExperimentTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NativeSwiftUIExperimentTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 7831F3252E8E1F8800BBDDC3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8CC4D8ED4325AD66F4105A16 /* Pods_NativeSwiftUIExperiment.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7831F33C2E8E1F8A00BBDDC3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 1E54F750DBB9DDB3CD0BFF34 /* Pods_NativeSwiftUIExperiment_NativeSwiftUIExperimentUITests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 7831F31F2E8E1F8800BBDDC3 = { + isa = PBXGroup; + children = ( + 78D658782E95923600CAC372 /* NativeSwiftUIExperiment */, + 78D6587C2E95923A00CAC372 /* NativeSwiftUIExperimentUITests */, + 7831F3292E8E1F8800BBDDC3 /* Products */, + D6395A4C1FB53F511B21901C /* Pods */, + AD0504386FD58B1C55D3EB41 /* Frameworks */, + ); + sourceTree = ""; + }; + 7831F3292E8E1F8800BBDDC3 /* Products */ = { + isa = PBXGroup; + children = ( + 7831F3282E8E1F8800BBDDC3 /* NativeSwiftUIExperiment.app */, + 7831F33F2E8E1F8A00BBDDC3 /* NativeSwiftUIExperimentUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 78D658782E95923600CAC372 /* NativeSwiftUIExperiment */ = { + isa = PBXGroup; + children = ( + 78D658752E95923600CAC372 /* Assets.xcassets */, + 78D658762E95923600CAC372 /* ContentView.swift */, + 78D658772E95923600CAC372 /* NativeSwiftUIExperimentApp.swift */, + ); + path = NativeSwiftUIExperiment; + sourceTree = ""; + }; + 78D6587C2E95923A00CAC372 /* NativeSwiftUIExperimentUITests */ = { + isa = PBXGroup; + children = ( + 78D658A12E95999E00CAC372 /* NativeSwiftUIExperimentUITests.swift */, + ); + path = NativeSwiftUIExperimentUITests; + sourceTree = ""; + }; + AD0504386FD58B1C55D3EB41 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 79FA578B09AFD4EEB7A29CB7 /* Pods_NativeSwiftUIExperiment.framework */, + 67398A34BD2DEF2BBE88904A /* Pods_NativeSwiftUIExperiment_NativeSwiftUIExperimentUITests.framework */, + FE9DE93C7456A24671A3DE9F /* Pods_NativeSwiftUIExperimentTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D6395A4C1FB53F511B21901C /* Pods */ = { + isa = PBXGroup; + children = ( + EC6932CFFBDC3F339936C54B /* Pods-NativeSwiftUIExperiment.debug.xcconfig */, + 004E2C25B02E5C8091BDDA34 /* Pods-NativeSwiftUIExperiment.release.xcconfig */, + 7BCA9D8BB2B4333006E2F322 /* Pods-NativeSwiftUIExperiment-NativeSwiftUIExperimentUITests.debug.xcconfig */, + 03FE462B080E3A5DA0F8E380 /* Pods-NativeSwiftUIExperiment-NativeSwiftUIExperimentUITests.release.xcconfig */, + 82A2DD74D26CD1CD4CF1CBC4 /* Pods-NativeSwiftUIExperimentTests.debug.xcconfig */, + 3A6B97C8420FF2942440AAD6 /* Pods-NativeSwiftUIExperimentTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7831F3272E8E1F8800BBDDC3 /* NativeSwiftUIExperiment */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7831F3492E8E1F8A00BBDDC3 /* Build configuration list for PBXNativeTarget "NativeSwiftUIExperiment" */; + buildPhases = ( + 34BFACEA473D8BE603BB2C92 /* [CP] Check Pods Manifest.lock */, + 7831F3242E8E1F8800BBDDC3 /* Sources */, + 7831F3252E8E1F8800BBDDC3 /* Frameworks */, + 7831F3262E8E1F8800BBDDC3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NativeSwiftUIExperiment; + productName = NativeSwiftUIExperiment; + productReference = 7831F3282E8E1F8800BBDDC3 /* NativeSwiftUIExperiment.app */; + productType = "com.apple.product-type.application"; + }; + 7831F33E2E8E1F8A00BBDDC3 /* NativeSwiftUIExperimentUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7831F34F2E8E1F8A00BBDDC3 /* Build configuration list for PBXNativeTarget "NativeSwiftUIExperimentUITests" */; + buildPhases = ( + 8F4390440840AC76F3DF8923 /* [CP] Check Pods Manifest.lock */, + 7831F33B2E8E1F8A00BBDDC3 /* Sources */, + 7831F33C2E8E1F8A00BBDDC3 /* Frameworks */, + 7831F33D2E8E1F8A00BBDDC3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 7831F3412E8E1F8A00BBDDC3 /* PBXTargetDependency */, + ); + name = NativeSwiftUIExperimentUITests; + productName = NativeSwiftUIExperimentUITests; + productReference = 7831F33F2E8E1F8A00BBDDC3 /* NativeSwiftUIExperimentUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7831F3202E8E1F8800BBDDC3 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1630; + LastUpgradeCheck = 1630; + TargetAttributes = { + 7831F3272E8E1F8800BBDDC3 = { + CreatedOnToolsVersion = 16.3; + }; + 7831F33E2E8E1F8A00BBDDC3 = { + CreatedOnToolsVersion = 16.3; + TestTargetID = 7831F3272E8E1F8800BBDDC3; + }; + }; + }; + buildConfigurationList = 7831F3232E8E1F8800BBDDC3 /* Build configuration list for PBXProject "NativeSwiftUIExperiment" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7831F31F2E8E1F8800BBDDC3; + productRefGroup = 7831F3292E8E1F8800BBDDC3 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7831F3272E8E1F8800BBDDC3 /* NativeSwiftUIExperiment */, + 7831F33E2E8E1F8A00BBDDC3 /* NativeSwiftUIExperimentUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 7831F3262E8E1F8800BBDDC3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 78D658792E95923600CAC372 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7831F33D2E8E1F8A00BBDDC3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 34BFACEA473D8BE603BB2C92 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-NativeSwiftUIExperiment-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 8F4390440840AC76F3DF8923 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-NativeSwiftUIExperiment-NativeSwiftUIExperimentUITests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7831F3242E8E1F8800BBDDC3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 78D6587A2E95923600CAC372 /* ContentView.swift in Sources */, + 78D6587B2E95923600CAC372 /* NativeSwiftUIExperimentApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7831F33B2E8E1F8A00BBDDC3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 78D658A22E95999E00CAC372 /* NativeSwiftUIExperimentUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 7831F3412E8E1F8A00BBDDC3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7831F3272E8E1F8800BBDDC3 /* NativeSwiftUIExperiment */; + targetProxy = 7831F3402E8E1F8A00BBDDC3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 7831F3472E8E1F8A00BBDDC3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_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_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 7831F3482E8E1F8A00BBDDC3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_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_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 7831F34A2E8E1F8A00BBDDC3 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = EC6932CFFBDC3F339936C54B /* Pods-NativeSwiftUIExperiment.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = NO; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.devicelab.NativeSwiftUIExperiment; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7831F34B2E8E1F8A00BBDDC3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 004E2C25B02E5C8091BDDA34 /* Pods-NativeSwiftUIExperiment.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = NO; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.devicelab.NativeSwiftUIExperiment; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 7831F3502E8E1F8A00BBDDC3 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7BCA9D8BB2B4333006E2F322 /* Pods-NativeSwiftUIExperiment-NativeSwiftUIExperimentUITests.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.devicelab.NativeSwiftUIExperimentUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = NativeSwiftUIExperiment; + }; + name = Debug; + }; + 7831F3512E8E1F8A00BBDDC3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 03FE462B080E3A5DA0F8E380 /* Pods-NativeSwiftUIExperiment-NativeSwiftUIExperimentUITests.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.devicelab.NativeSwiftUIExperimentUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = NativeSwiftUIExperiment; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7831F3232E8E1F8800BBDDC3 /* Build configuration list for PBXProject "NativeSwiftUIExperiment" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7831F3472E8E1F8A00BBDDC3 /* Debug */, + 7831F3482E8E1F8A00BBDDC3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7831F3492E8E1F8A00BBDDC3 /* Build configuration list for PBXNativeTarget "NativeSwiftUIExperiment" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7831F34A2E8E1F8A00BBDDC3 /* Debug */, + 7831F34B2E8E1F8A00BBDDC3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7831F34F2E8E1F8A00BBDDC3 /* Build configuration list for PBXNativeTarget "NativeSwiftUIExperimentUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7831F3502E8E1F8A00BBDDC3 /* Debug */, + 7831F3512E8E1F8A00BBDDC3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 7831F3202E8E1F8800BBDDC3 /* Project object */; +} diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift.xcworkspace/contents.xcworkspacedata b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment.xcworkspace/contents.xcworkspacedata similarity index 75% rename from dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift.xcworkspace/contents.xcworkspacedata rename to dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment.xcworkspace/contents.xcworkspacedata index aaef693588234..e450bff98dfd7 100644 --- a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift.xcworkspace/contents.xcworkspacedata +++ b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment.xcworkspace/contents.xcworkspacedata @@ -2,7 +2,7 @@ + location = "group:NativeSwiftUIExperiment.xcodeproj"> diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/AccentColor.colorset/Contents.json b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/AccentColor.colorset/Contents.json rename to dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/AppIcon.appiconset/Contents.json b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/AppIcon.appiconset/Contents.json rename to dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/Contents.json b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment/Assets.xcassets/Contents.json similarity index 100% rename from dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/Assets.xcassets/Contents.json rename to dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment/Assets.xcassets/Contents.json diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/ContentView.swift b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment/ContentView.swift similarity index 100% rename from dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/ContentView.swift rename to dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment/ContentView.swift diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/xcode_swiftuiApp.swift b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment/NativeSwiftUIExperimentApp.swift similarity index 87% rename from dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/xcode_swiftuiApp.swift rename to dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment/NativeSwiftUIExperimentApp.swift index ea03864988d41..b649262506417 100644 --- a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui/xcode_swiftuiApp.swift +++ b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperiment/NativeSwiftUIExperimentApp.swift @@ -5,7 +5,7 @@ import SwiftUI @main -struct xcode_swiftuiApp: App { +struct NativeSwiftUIExperimentApp: App { var body: some Scene { WindowGroup { ContentView() diff --git a/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperimentUITests/NativeSwiftUIExperimentUITests.swift b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperimentUITests/NativeSwiftUIExperimentUITests.swift new file mode 100644 index 0000000000000..ef33e3c00bcf5 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/NativeSwiftUIExperimentUITests/NativeSwiftUIExperimentUITests.swift @@ -0,0 +1,8 @@ +// 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 XCTest + +final class NativeSwiftUIExperimentUITests: XCTestCase { +} diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/Podfile b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/Podfile similarity index 80% rename from dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/Podfile rename to dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/Podfile index d04cf9b1f849e..270dca46be2ee 100644 --- a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/Podfile +++ b/dev/integration_tests/ios_add2app_uiscene/NativeSwiftUIExperiment/Podfile @@ -4,14 +4,14 @@ flutter_application_path = '../my_module' load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') -target 'xcode_swiftui' do +target 'NativeSwiftUIExperiment' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! - # Pods for xcode_swiftui + # Pods for NativeSwiftUIExperiment install_all_flutter_pods(flutter_application_path) - target 'xcode_swiftuiUITests' do + target 'NativeSwiftUIExperimentUITests' do # Pods for testing end diff --git a/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment.xcodeproj/project.pbxproj b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment.xcodeproj/project.pbxproj new file mode 100644 index 0000000000000..cf9bedc5002f1 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment.xcodeproj/project.pbxproj @@ -0,0 +1,580 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 4BB66C84F8951359042349F4 /* Pods_NativeUIKitSwiftExperiment.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A4D480656A4DC96AD5C5EFDD /* Pods_NativeUIKitSwiftExperiment.framework */; }; + 610ADDF570221F721827E1AA /* Pods_NativeUIKitSwiftExperiment_NativeUIKitSwiftExperimentUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C914C66E8DD265B4CFF52CB /* Pods_NativeUIKitSwiftExperiment_NativeUIKitSwiftExperimentUITests.framework */; }; + 78D658982E95925200CAC372 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 78D6588F2E95925200CAC372 /* Assets.xcassets */; }; + 78D6589A2E95925200CAC372 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 78D658922E95925200CAC372 /* LaunchScreen.storyboard */; }; + 78D6589B2E95925200CAC372 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 78D658942E95925200CAC372 /* Main.storyboard */; }; + 78D6589C2E95925200CAC372 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D6588E2E95925200CAC372 /* AppDelegate.swift */; }; + 78D6589D2E95925200CAC372 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D658952E95925200CAC372 /* SceneDelegate.swift */; }; + 78D6589E2E95925200CAC372 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D658962E95925200CAC372 /* ViewController.swift */; }; + 78D658A02E95997A00CAC372 /* NativeUIKitSwiftExperimentUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D6589F2E95997A00CAC372 /* NativeUIKitSwiftExperimentUITests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 7831F2DA2E8E1DA600BBDDC3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 7831F2B12E8E1DA500BBDDC3 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 7831F2B82E8E1DA500BBDDC3; + remoteInfo = NativeUIKitSwiftExperiment; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 1B0B95C75E59AE52E80662F2 /* Pods-NativeUIKitSwiftExperimentTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeUIKitSwiftExperimentTests.release.xcconfig"; path = "Target Support Files/Pods-NativeUIKitSwiftExperimentTests/Pods-NativeUIKitSwiftExperimentTests.release.xcconfig"; sourceTree = ""; }; + 2E8A95FA1206B4F0A8B8260F /* Pods-NativeUIKitSwiftExperimentTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeUIKitSwiftExperimentTests.debug.xcconfig"; path = "Target Support Files/Pods-NativeUIKitSwiftExperimentTests/Pods-NativeUIKitSwiftExperimentTests.debug.xcconfig"; sourceTree = ""; }; + 4AED7B23B41F0BEEF3636A24 /* Pods_NativeUIKitSwiftExperimentTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NativeUIKitSwiftExperimentTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 7831F2B92E8E1DA500BBDDC3 /* NativeUIKitSwiftExperiment.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NativeUIKitSwiftExperiment.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 7831F2D92E8E1DA600BBDDC3 /* NativeUIKitSwiftExperimentUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NativeUIKitSwiftExperimentUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 78D6588E2E95925200CAC372 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 78D6588F2E95925200CAC372 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 78D658902E95925200CAC372 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 78D658912E95925200CAC372 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 78D658932E95925200CAC372 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 78D658952E95925200CAC372 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 78D658962E95925200CAC372 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 78D6589F2E95997A00CAC372 /* NativeUIKitSwiftExperimentUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeUIKitSwiftExperimentUITests.swift; sourceTree = ""; }; + 8C914C66E8DD265B4CFF52CB /* Pods_NativeUIKitSwiftExperiment_NativeUIKitSwiftExperimentUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NativeUIKitSwiftExperiment_NativeUIKitSwiftExperimentUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9612A8B853DE7FDD653EE7ED /* Pods-NativeUIKitSwiftExperiment.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeUIKitSwiftExperiment.release.xcconfig"; path = "Target Support Files/Pods-NativeUIKitSwiftExperiment/Pods-NativeUIKitSwiftExperiment.release.xcconfig"; sourceTree = ""; }; + A29DBD83AAC1A3A5C96040BA /* Pods-NativeUIKitSwiftExperiment-NativeUIKitSwiftExperimentUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeUIKitSwiftExperiment-NativeUIKitSwiftExperimentUITests.debug.xcconfig"; path = "Target Support Files/Pods-NativeUIKitSwiftExperiment-NativeUIKitSwiftExperimentUITests/Pods-NativeUIKitSwiftExperiment-NativeUIKitSwiftExperimentUITests.debug.xcconfig"; sourceTree = ""; }; + A4D480656A4DC96AD5C5EFDD /* Pods_NativeUIKitSwiftExperiment.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_NativeUIKitSwiftExperiment.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B9CCF65EACEBC0BDD2FD6E8D /* Pods-NativeUIKitSwiftExperiment-NativeUIKitSwiftExperimentUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeUIKitSwiftExperiment-NativeUIKitSwiftExperimentUITests.release.xcconfig"; path = "Target Support Files/Pods-NativeUIKitSwiftExperiment-NativeUIKitSwiftExperimentUITests/Pods-NativeUIKitSwiftExperiment-NativeUIKitSwiftExperimentUITests.release.xcconfig"; sourceTree = ""; }; + F03D00B986BD9C868CE71A46 /* Pods-NativeUIKitSwiftExperiment.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NativeUIKitSwiftExperiment.debug.xcconfig"; path = "Target Support Files/Pods-NativeUIKitSwiftExperiment/Pods-NativeUIKitSwiftExperiment.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 7831F2B62E8E1DA500BBDDC3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4BB66C84F8951359042349F4 /* Pods_NativeUIKitSwiftExperiment.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7831F2D62E8E1DA600BBDDC3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 610ADDF570221F721827E1AA /* Pods_NativeUIKitSwiftExperiment_NativeUIKitSwiftExperimentUITests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4A789515CC3F244B1D703789 /* Pods */ = { + isa = PBXGroup; + children = ( + F03D00B986BD9C868CE71A46 /* Pods-NativeUIKitSwiftExperiment.debug.xcconfig */, + 9612A8B853DE7FDD653EE7ED /* Pods-NativeUIKitSwiftExperiment.release.xcconfig */, + A29DBD83AAC1A3A5C96040BA /* Pods-NativeUIKitSwiftExperiment-NativeUIKitSwiftExperimentUITests.debug.xcconfig */, + B9CCF65EACEBC0BDD2FD6E8D /* Pods-NativeUIKitSwiftExperiment-NativeUIKitSwiftExperimentUITests.release.xcconfig */, + 2E8A95FA1206B4F0A8B8260F /* Pods-NativeUIKitSwiftExperimentTests.debug.xcconfig */, + 1B0B95C75E59AE52E80662F2 /* Pods-NativeUIKitSwiftExperimentTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 7831F2B02E8E1DA500BBDDC3 = { + isa = PBXGroup; + children = ( + 78D658972E95925200CAC372 /* NativeUIKitSwiftExperiment */, + 78D6588D2E95925000CAC372 /* NativeUIKitSwiftExperimentUITests */, + 7831F2BA2E8E1DA500BBDDC3 /* Products */, + 4A789515CC3F244B1D703789 /* Pods */, + AEEDE682CAE498D5F586A528 /* Frameworks */, + ); + sourceTree = ""; + }; + 7831F2BA2E8E1DA500BBDDC3 /* Products */ = { + isa = PBXGroup; + children = ( + 7831F2B92E8E1DA500BBDDC3 /* NativeUIKitSwiftExperiment.app */, + 7831F2D92E8E1DA600BBDDC3 /* NativeUIKitSwiftExperimentUITests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 78D6588D2E95925000CAC372 /* NativeUIKitSwiftExperimentUITests */ = { + isa = PBXGroup; + children = ( + 78D6589F2E95997A00CAC372 /* NativeUIKitSwiftExperimentUITests.swift */, + ); + path = NativeUIKitSwiftExperimentUITests; + sourceTree = ""; + }; + 78D658972E95925200CAC372 /* NativeUIKitSwiftExperiment */ = { + isa = PBXGroup; + children = ( + 78D6588E2E95925200CAC372 /* AppDelegate.swift */, + 78D6588F2E95925200CAC372 /* Assets.xcassets */, + 78D658902E95925200CAC372 /* Info.plist */, + 78D658922E95925200CAC372 /* LaunchScreen.storyboard */, + 78D658942E95925200CAC372 /* Main.storyboard */, + 78D658952E95925200CAC372 /* SceneDelegate.swift */, + 78D658962E95925200CAC372 /* ViewController.swift */, + ); + path = NativeUIKitSwiftExperiment; + sourceTree = ""; + }; + AEEDE682CAE498D5F586A528 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A4D480656A4DC96AD5C5EFDD /* Pods_NativeUIKitSwiftExperiment.framework */, + 8C914C66E8DD265B4CFF52CB /* Pods_NativeUIKitSwiftExperiment_NativeUIKitSwiftExperimentUITests.framework */, + 4AED7B23B41F0BEEF3636A24 /* Pods_NativeUIKitSwiftExperimentTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 7831F2B82E8E1DA500BBDDC3 /* NativeUIKitSwiftExperiment */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7831F2E22E8E1DA600BBDDC3 /* Build configuration list for PBXNativeTarget "NativeUIKitSwiftExperiment" */; + buildPhases = ( + 53700F70A365B30B76FAAE60 /* [CP] Check Pods Manifest.lock */, + 7831F2B52E8E1DA500BBDDC3 /* Sources */, + 7831F2B62E8E1DA500BBDDC3 /* Frameworks */, + 7831F2B72E8E1DA500BBDDC3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NativeUIKitSwiftExperiment; + productName = NativeUIKitSwiftExperiment; + productReference = 7831F2B92E8E1DA500BBDDC3 /* NativeUIKitSwiftExperiment.app */; + productType = "com.apple.product-type.application"; + }; + 7831F2D82E8E1DA600BBDDC3 /* NativeUIKitSwiftExperimentUITests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7831F2EA2E8E1DA600BBDDC3 /* Build configuration list for PBXNativeTarget "NativeUIKitSwiftExperimentUITests" */; + buildPhases = ( + 6095060F247D95902FF8D52F /* [CP] Check Pods Manifest.lock */, + 7831F2D52E8E1DA600BBDDC3 /* Sources */, + 7831F2D62E8E1DA600BBDDC3 /* Frameworks */, + 7831F2D72E8E1DA600BBDDC3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 7831F2DB2E8E1DA600BBDDC3 /* PBXTargetDependency */, + ); + name = NativeUIKitSwiftExperimentUITests; + productName = NativeUIKitSwiftExperimentUITests; + productReference = 7831F2D92E8E1DA600BBDDC3 /* NativeUIKitSwiftExperimentUITests.xctest */; + productType = "com.apple.product-type.bundle.ui-testing"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 7831F2B12E8E1DA500BBDDC3 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1630; + LastUpgradeCheck = 1630; + TargetAttributes = { + 7831F2B82E8E1DA500BBDDC3 = { + CreatedOnToolsVersion = 16.3; + }; + 7831F2D82E8E1DA600BBDDC3 = { + CreatedOnToolsVersion = 16.3; + TestTargetID = 7831F2B82E8E1DA500BBDDC3; + }; + }; + }; + buildConfigurationList = 7831F2B42E8E1DA500BBDDC3 /* Build configuration list for PBXProject "NativeUIKitSwiftExperiment" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 7831F2B02E8E1DA500BBDDC3; + productRefGroup = 7831F2BA2E8E1DA500BBDDC3 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 7831F2B82E8E1DA500BBDDC3 /* NativeUIKitSwiftExperiment */, + 7831F2D82E8E1DA600BBDDC3 /* NativeUIKitSwiftExperimentUITests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 7831F2B72E8E1DA500BBDDC3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 78D658982E95925200CAC372 /* Assets.xcassets in Resources */, + 78D6589A2E95925200CAC372 /* LaunchScreen.storyboard in Resources */, + 78D6589B2E95925200CAC372 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7831F2D72E8E1DA600BBDDC3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 53700F70A365B30B76FAAE60 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-NativeUIKitSwiftExperiment-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 6095060F247D95902FF8D52F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-NativeUIKitSwiftExperiment-NativeUIKitSwiftExperimentUITests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 7831F2B52E8E1DA500BBDDC3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 78D6589C2E95925200CAC372 /* AppDelegate.swift in Sources */, + 78D6589D2E95925200CAC372 /* SceneDelegate.swift in Sources */, + 78D6589E2E95925200CAC372 /* ViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 7831F2D52E8E1DA600BBDDC3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 78D658A02E95997A00CAC372 /* NativeUIKitSwiftExperimentUITests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 7831F2DB2E8E1DA600BBDDC3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 7831F2B82E8E1DA500BBDDC3 /* NativeUIKitSwiftExperiment */; + targetProxy = 7831F2DA2E8E1DA600BBDDC3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 78D658922E95925200CAC372 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 78D658912E95925200CAC372 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; + 78D658942E95925200CAC372 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 78D658932E95925200CAC372 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 7831F2E32E8E1DA600BBDDC3 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = F03D00B986BD9C868CE71A46 /* Pods-NativeUIKitSwiftExperiment.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NativeUIKitSwiftExperiment/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.devicelab.NativeUIKitSwiftExperiment; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7831F2E42E8E1DA600BBDDC3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9612A8B853DE7FDD653EE7ED /* Pods-NativeUIKitSwiftExperiment.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = NativeUIKitSwiftExperiment/Info.plist; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UIMainStoryboardFile = Main; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.devicelab.NativeUIKitSwiftExperiment; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + 7831F2E52E8E1DA600BBDDC3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_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_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 7831F2E62E8E1DA600BBDDC3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_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_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 7831F2EB2E8E1DA600BBDDC3 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = A29DBD83AAC1A3A5C96040BA /* Pods-NativeUIKitSwiftExperiment-NativeUIKitSwiftExperimentUITests.debug.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.devicelab.NativeUIKitSwiftExperimentUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = NativeUIKitSwiftExperiment; + }; + name = Debug; + }; + 7831F2EC2E8E1DA600BBDDC3 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = B9CCF65EACEBC0BDD2FD6E8D /* Pods-NativeUIKitSwiftExperiment-NativeUIKitSwiftExperimentUITests.release.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.devicelab.NativeUIKitSwiftExperimentUITests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + TEST_TARGET_NAME = NativeUIKitSwiftExperiment; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 7831F2B42E8E1DA500BBDDC3 /* Build configuration list for PBXProject "NativeUIKitSwiftExperiment" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7831F2E52E8E1DA600BBDDC3 /* Debug */, + 7831F2E62E8E1DA600BBDDC3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7831F2E22E8E1DA600BBDDC3 /* Build configuration list for PBXNativeTarget "NativeUIKitSwiftExperiment" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7831F2E32E8E1DA600BBDDC3 /* Debug */, + 7831F2E42E8E1DA600BBDDC3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 7831F2EA2E8E1DA600BBDDC3 /* Build configuration list for PBXNativeTarget "NativeUIKitSwiftExperimentUITests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7831F2EB2E8E1DA600BBDDC3 /* Debug */, + 7831F2EC2E8E1DA600BBDDC3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 7831F2B12E8E1DA500BBDDC3 /* Project object */; +} diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcworkspace/contents.xcworkspacedata b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment.xcworkspace/contents.xcworkspacedata similarity index 74% rename from dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcworkspace/contents.xcworkspacedata rename to dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment.xcworkspace/contents.xcworkspacedata index 457d4d1cd781a..5ae37d1af5dfd 100644 --- a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcworkspace/contents.xcworkspacedata +++ b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment.xcworkspace/contents.xcworkspacedata @@ -2,7 +2,7 @@ + location = "group:NativeUIKitSwiftExperiment.xcodeproj"> diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift/AppDelegate.swift b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/AppDelegate.swift similarity index 100% rename from dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift/AppDelegate.swift rename to dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/AppDelegate.swift diff --git a/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Assets.xcassets/AccentColor.colorset/Contents.json b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000000000..eb87897008164 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Assets.xcassets/AppIcon.appiconset/Contents.json b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000000..2305880107db5 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,35 @@ +{ + "images" : [ + { + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "tinted" + } + ], + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Assets.xcassets/Contents.json b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Assets.xcassets/Contents.json new file mode 100644 index 0000000000000..73c00596a7fca --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift/Base.lproj/LaunchScreen.storyboard b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift/Base.lproj/LaunchScreen.storyboard rename to dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Base.lproj/LaunchScreen.storyboard diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift/Base.lproj/Main.storyboard b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Base.lproj/Main.storyboard similarity index 100% rename from dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift/Base.lproj/Main.storyboard rename to dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Base.lproj/Main.storyboard diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift/Info.plist b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Info.plist similarity index 100% rename from dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift/Info.plist rename to dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/Info.plist diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift/SceneDelegate.swift b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/SceneDelegate.swift similarity index 100% rename from dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift/SceneDelegate.swift rename to dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/SceneDelegate.swift diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift/ViewController.swift b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/ViewController.swift similarity index 100% rename from dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift/ViewController.swift rename to dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperiment/ViewController.swift diff --git a/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift new file mode 100644 index 0000000000000..bc694d851f5f3 --- /dev/null +++ b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/NativeUIKitSwiftExperimentUITests/NativeUIKitSwiftExperimentUITests.swift @@ -0,0 +1,8 @@ +// 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 XCTest + +final class NativeUIKitSwiftExperimentUITests: XCTestCase { +} diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/Podfile b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/Podfile similarity index 79% rename from dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/Podfile rename to dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/Podfile index 494dfe24d37d1..9063807bfb2b1 100644 --- a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/Podfile +++ b/dev/integration_tests/ios_add2app_uiscene/NativeUIKitSwiftExperiment/Podfile @@ -4,14 +4,14 @@ flutter_application_path = '../my_module' load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') -target 'xcode_uikit_swift' do +target 'NativeUIKitSwiftExperiment' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! - # Pods for xcode_uikit_swift + # Pods for NativeUIKitSwiftExperiment install_all_flutter_pods(flutter_application_path) - target 'xcode_uikit_swiftUITests' do + target 'NativeUIKitSwiftExperimentUITests' do # Pods for testing end diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-AppMigrated.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-AppMigrated.swift index 0252721d52266..95295dc4a4c3e 100644 --- a/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-AppMigrated.swift +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-AppMigrated.swift @@ -4,7 +4,7 @@ import XCTest -final class xcode_uikit_swiftUITests: XCTestCase { +final class NativeUIKitSwiftExperimentUITests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-AppNotMigrated.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-AppNotMigrated.swift index 55b2996ad82f8..0f111d4beae46 100644 --- a/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-AppNotMigrated.swift +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-AppNotMigrated.swift @@ -4,7 +4,7 @@ import XCTest -final class xcode_uikit_swiftUITests: XCTestCase { +final class NativeUIKitSwiftExperimentUITests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-FlutterImplicitEngineDelegate.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-FlutterImplicitEngineDelegate.swift index 092b2cb599326..6518fbf441af8 100644 --- a/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-FlutterImplicitEngineDelegate.swift +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-ApplicationEvents-FlutterImplicitEngineDelegate.swift @@ -4,7 +4,7 @@ import XCTest -final class xcode_uikit_swiftUITests: XCTestCase { +final class NativeUIKitSwiftExperimentUITests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-ApplicationLaunchEvents.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-ApplicationLaunchEvents.swift index d3a2dfd0978ab..98c1b58460806 100644 --- a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-ApplicationLaunchEvents.swift +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-ApplicationLaunchEvents.swift @@ -4,7 +4,7 @@ import XCTest -final class xcode_uikit_swiftUITests: XCTestCase { +final class NativeUIKitSwiftExperimentUITests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-MultiScenes.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-MultiScenes.swift index 40a355ea4087c..75634f7d27f56 100644 --- a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-MultiScenes.swift +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-MultiScenes.swift @@ -4,7 +4,7 @@ import XCTest -final class xcode_uikit_swiftUITests: XCTestCase { +final class NativeUIKitSwiftExperimentUITests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-NoApplicationEvents.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-NoApplicationEvents.swift index 1bd19653eaf15..a2ece0049abca 100644 --- a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-NoApplicationEvents.swift +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents-NoApplicationEvents.swift @@ -4,7 +4,7 @@ import XCTest -final class xcode_uikit_swiftUITests: XCTestCase { +final class NativeUIKitSwiftExperimentUITests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents.swift index 3f1aa08ad8beb..85f519c957ad2 100644 --- a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents.swift +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEvents.swift @@ -4,7 +4,7 @@ import XCTest -final class xcode_uikit_swiftUITests: XCTestCase { +final class NativeUIKitSwiftExperimentUITests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEventsNoConnect-NoApplicationEvents.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEventsNoConnect-NoApplicationEvents.swift index 60c0f53277403..76f8f8725c048 100644 --- a/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEventsNoConnect-NoApplicationEvents.swift +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-SceneEventsNoConnect-NoApplicationEvents.swift @@ -4,7 +4,7 @@ import XCTest -final class xcode_uikit_swiftUITests: XCTestCase { +final class NativeUIKitSwiftExperimentUITests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false diff --git a/dev/integration_tests/ios_add2app_uiscene/native/UITests-StateRestoration.swift b/dev/integration_tests/ios_add2app_uiscene/native/UITests-StateRestoration.swift index 1a6335c8db015..56ba11f9776ce 100644 --- a/dev/integration_tests/ios_add2app_uiscene/native/UITests-StateRestoration.swift +++ b/dev/integration_tests/ios_add2app_uiscene/native/UITests-StateRestoration.swift @@ -4,7 +4,7 @@ import XCTest -final class xcode_uikit_swiftUITests: XCTestCase { +final class NativeUIKitSwiftExperimentUITests: XCTestCase { override func setUpWithError() throws { continueAfterFailure = false diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode-swiftui-Info.plist b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode-swiftui-Info.plist deleted file mode 100644 index 0c67376ebacb4..0000000000000 --- a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode-swiftui-Info.plist +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.pbxproj b/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.pbxproj deleted file mode 100644 index 42fa8661b3648..0000000000000 --- a/dev/integration_tests/ios_add2app_uiscene/xcode_swiftui/xcode_swiftui.xcodeproj/project.pbxproj +++ /dev/null @@ -1,553 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXBuildFile section */ - 09AE28A9A100161970ABA956 /* Pods_xcode_swiftui.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 149DE053CEEA02EA47E518CD /* Pods_xcode_swiftui.framework */; }; - 1991B26C9F33F99FB17C6D71 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8A71D00ADA4BD1423A4888D5 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 787D51772E8A1A61002EB011 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 787D51572E8A1A60002EB011 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 787D515E2E8A1A60002EB011; - remoteInfo = xcode_swiftui; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 0625163ECFD280D6722E02EB /* Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig"; path = "Target Support Files/Pods-xcode_swiftui-xcode_swiftuiUITests/Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig"; sourceTree = ""; }; - 149DE053CEEA02EA47E518CD /* Pods_xcode_swiftui.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xcode_swiftui.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 36F75FFFDAB0368B5D9AEF50 /* Pods-xcode_swiftuiTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftuiTests.release.xcconfig"; path = "Target Support Files/Pods-xcode_swiftuiTests/Pods-xcode_swiftuiTests.release.xcconfig"; sourceTree = ""; }; - 5B754AE90ACC1361E738A440 /* Pods-xcode_swiftui.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftui.debug.xcconfig"; path = "Target Support Files/Pods-xcode_swiftui/Pods-xcode_swiftui.debug.xcconfig"; sourceTree = ""; }; - 714588AEC6F0B96B28798AB0 /* Pods-xcode_swiftui.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftui.release.xcconfig"; path = "Target Support Files/Pods-xcode_swiftui/Pods-xcode_swiftui.release.xcconfig"; sourceTree = ""; }; - 787D515F2E8A1A60002EB011 /* xcode_swiftui.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = xcode_swiftui.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 787D51762E8A1A61002EB011 /* xcode_swiftuiUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = xcode_swiftuiUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 8A71D00ADA4BD1423A4888D5 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xcode_swiftui_xcode_swiftuiUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 8B2B617A60DAC180929CD642 /* Pods-xcode_swiftuiTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftuiTests.debug.xcconfig"; path = "Target Support Files/Pods-xcode_swiftuiTests/Pods-xcode_swiftuiTests.debug.xcconfig"; sourceTree = ""; }; - BB561EFA00EC91BA8A168BE3 /* Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig"; path = "Target Support Files/Pods-xcode_swiftui-xcode_swiftuiUITests/Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig"; sourceTree = ""; }; - EE34DDFB6BB2E47ABD6F8EE5 /* Pods_xcode_swiftuiTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xcode_swiftuiTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFileSystemSynchronizedRootGroup section */ - 787D51612E8A1A60002EB011 /* xcode_swiftui */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = xcode_swiftui; - sourceTree = ""; - }; - 787D51792E8A1A61002EB011 /* xcode_swiftuiUITests */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = xcode_swiftuiUITests; - sourceTree = ""; - }; -/* End PBXFileSystemSynchronizedRootGroup section */ - -/* Begin PBXFrameworksBuildPhase section */ - 787D515C2E8A1A60002EB011 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 09AE28A9A100161970ABA956 /* Pods_xcode_swiftui.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 787D51732E8A1A61002EB011 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 1991B26C9F33F99FB17C6D71 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 3FAB8759C7E0FBF58FE109A5 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 149DE053CEEA02EA47E518CD /* Pods_xcode_swiftui.framework */, - 8A71D00ADA4BD1423A4888D5 /* Pods_xcode_swiftui_xcode_swiftuiUITests.framework */, - EE34DDFB6BB2E47ABD6F8EE5 /* Pods_xcode_swiftuiTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 787D51562E8A1A60002EB011 = { - isa = PBXGroup; - children = ( - 787D51612E8A1A60002EB011 /* xcode_swiftui */, - 787D51792E8A1A61002EB011 /* xcode_swiftuiUITests */, - 787D51602E8A1A60002EB011 /* Products */, - E283866923164D07FF92228F /* Pods */, - 3FAB8759C7E0FBF58FE109A5 /* Frameworks */, - ); - sourceTree = ""; - }; - 787D51602E8A1A60002EB011 /* Products */ = { - isa = PBXGroup; - children = ( - 787D515F2E8A1A60002EB011 /* xcode_swiftui.app */, - 787D51762E8A1A61002EB011 /* xcode_swiftuiUITests.xctest */, - ); - name = Products; - sourceTree = ""; - }; - E283866923164D07FF92228F /* Pods */ = { - isa = PBXGroup; - children = ( - 5B754AE90ACC1361E738A440 /* Pods-xcode_swiftui.debug.xcconfig */, - 714588AEC6F0B96B28798AB0 /* Pods-xcode_swiftui.release.xcconfig */, - 0625163ECFD280D6722E02EB /* Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig */, - BB561EFA00EC91BA8A168BE3 /* Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig */, - 8B2B617A60DAC180929CD642 /* Pods-xcode_swiftuiTests.debug.xcconfig */, - 36F75FFFDAB0368B5D9AEF50 /* Pods-xcode_swiftuiTests.release.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 787D515E2E8A1A60002EB011 /* xcode_swiftui */ = { - isa = PBXNativeTarget; - buildConfigurationList = 787D51802E8A1A61002EB011 /* Build configuration list for PBXNativeTarget "xcode_swiftui" */; - buildPhases = ( - 6E75E8B810F47E67163AA6AA /* [CP] Check Pods Manifest.lock */, - 787D515B2E8A1A60002EB011 /* Sources */, - 787D515C2E8A1A60002EB011 /* Frameworks */, - 787D515D2E8A1A60002EB011 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - fileSystemSynchronizedGroups = ( - 787D51612E8A1A60002EB011 /* xcode_swiftui */, - ); - name = xcode_swiftui; - productName = xcode_swiftui; - productReference = 787D515F2E8A1A60002EB011 /* xcode_swiftui.app */; - productType = "com.apple.product-type.application"; - }; - 787D51752E8A1A61002EB011 /* xcode_swiftuiUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 787D51862E8A1A61002EB011 /* Build configuration list for PBXNativeTarget "xcode_swiftuiUITests" */; - buildPhases = ( - B6AB117EAEF0BDF8D52A82C6 /* [CP] Check Pods Manifest.lock */, - 787D51722E8A1A61002EB011 /* Sources */, - 787D51732E8A1A61002EB011 /* Frameworks */, - 787D51742E8A1A61002EB011 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 787D51782E8A1A61002EB011 /* PBXTargetDependency */, - ); - fileSystemSynchronizedGroups = ( - 787D51792E8A1A61002EB011 /* xcode_swiftuiUITests */, - ); - name = xcode_swiftuiUITests; - productName = xcode_swiftuiUITests; - productReference = 787D51762E8A1A61002EB011 /* xcode_swiftuiUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 787D51572E8A1A60002EB011 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 2600; - LastUpgradeCheck = 2600; - TargetAttributes = { - 787D515E2E8A1A60002EB011 = { - CreatedOnToolsVersion = 26.0; - }; - 787D51752E8A1A61002EB011 = { - CreatedOnToolsVersion = 26.0; - TestTargetID = 787D515E2E8A1A60002EB011; - }; - }; - }; - buildConfigurationList = 787D515A2E8A1A60002EB011 /* Build configuration list for PBXProject "xcode_swiftui" */; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 787D51562E8A1A60002EB011; - minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 77; - productRefGroup = 787D51602E8A1A60002EB011 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 787D515E2E8A1A60002EB011 /* xcode_swiftui */, - 787D51752E8A1A61002EB011 /* xcode_swiftuiUITests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 787D515D2E8A1A60002EB011 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 787D51742E8A1A61002EB011 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 6E75E8B810F47E67163AA6AA /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-xcode_swiftui-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - B6AB117EAEF0BDF8D52A82C6 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-xcode_swiftui-xcode_swiftuiUITests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 787D515B2E8A1A60002EB011 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 787D51722E8A1A61002EB011 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 787D51782E8A1A61002EB011 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 787D515E2E8A1A60002EB011 /* xcode_swiftui */; - targetProxy = 787D51772E8A1A61002EB011 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 787D517E2E8A1A61002EB011 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_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_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - DEVELOPMENT_TEAM = S8QB4VV633; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 787D517F2E8A1A61002EB011 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_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_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEVELOPMENT_TEAM = S8QB4VV633; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 787D51812E8A1A61002EB011 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 5B754AE90ACC1361E738A440 /* Pods-xcode_swiftui.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = S8QB4VV633; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "xcode-swiftui-Info.plist"; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = NO; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-swiftui"; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 787D51822E8A1A61002EB011 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 714588AEC6F0B96B28798AB0 /* Pods-xcode_swiftui.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = S8QB4VV633; - ENABLE_PREVIEWS = YES; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = "xcode-swiftui-Info.plist"; - INFOPLIST_KEY_UIApplicationSceneManifest_Generation = NO; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchScreen_Generation = YES; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-swiftui"; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 787D51872E8A1A61002EB011 /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 0625163ECFD280D6722E02EB /* Pods-xcode_swiftui-xcode_swiftuiUITests.debug.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = S8QB4VV633; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-swiftuiUITests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRING_CATALOG_GENERATE_SYMBOLS = NO; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = xcode_swiftui; - }; - name = Debug; - }; - 787D51882E8A1A61002EB011 /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = BB561EFA00EC91BA8A168BE3 /* Pods-xcode_swiftui-xcode_swiftuiUITests.release.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = S8QB4VV633; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-swiftuiUITests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRING_CATALOG_GENERATE_SYMBOLS = NO; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = xcode_swiftui; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 787D515A2E8A1A60002EB011 /* Build configuration list for PBXProject "xcode_swiftui" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 787D517E2E8A1A61002EB011 /* Debug */, - 787D517F2E8A1A61002EB011 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 787D51802E8A1A61002EB011 /* Build configuration list for PBXNativeTarget "xcode_swiftui" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 787D51812E8A1A61002EB011 /* Debug */, - 787D51822E8A1A61002EB011 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 787D51862E8A1A61002EB011 /* Build configuration list for PBXNativeTarget "xcode_swiftuiUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 787D51872E8A1A61002EB011 /* Debug */, - 787D51882E8A1A61002EB011 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 787D51572E8A1A60002EB011 /* Project object */; -} diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/Info.plist b/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/Info.plist deleted file mode 100644 index 6631ffa6f2426..0000000000000 --- a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/Info.plist +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift.xcodeproj/project.pbxproj b/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift.xcodeproj/project.pbxproj deleted file mode 100644 index bbbe7eb7ebb00..0000000000000 --- a/dev/integration_tests/ios_add2app_uiscene/xcode_uikit_swift/xcode_uikit_swift.xcodeproj/project.pbxproj +++ /dev/null @@ -1,558 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 77; - objects = { - -/* Begin PBXBuildFile section */ - 7ECB200B0AD58CB670B3C2F5 /* Pods_xcode_uikit_swift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EB983C002B12D858A66ED6E5 /* Pods_xcode_uikit_swift.framework */; }; - ADF8AAB67AB135B2A1561A1A /* Pods_xcode_uikit_swift_xcode_uikit_swiftUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 913B8FA3C251CDF1F658AF4A /* Pods_xcode_uikit_swift_xcode_uikit_swiftUITests.framework */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 78C59F342E7CF7E60047C08C /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 78C59F0B2E7CF7E50047C08C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 78C59F122E7CF7E50047C08C; - remoteInfo = xcode_uikit_swift; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXFileReference section */ - 16C0FC186D03BDF4B43AC51F /* Pods-xcode_uikit_swift-xcode_uikit_swiftUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_uikit_swift-xcode_uikit_swiftUITests.debug.xcconfig"; path = "Target Support Files/Pods-xcode_uikit_swift-xcode_uikit_swiftUITests/Pods-xcode_uikit_swift-xcode_uikit_swiftUITests.debug.xcconfig"; sourceTree = ""; }; - 3806160BF4CE46B7F0835E86 /* Pods_xcode_uikit_swiftTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xcode_uikit_swiftTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 78C59F132E7CF7E50047C08C /* xcode_uikit_swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = xcode_uikit_swift.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 78C59F332E7CF7E60047C08C /* xcode_uikit_swiftUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = xcode_uikit_swiftUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 8FB3091209B846F6D7EB7052 /* Pods-xcode_uikit_swiftTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_uikit_swiftTests.debug.xcconfig"; path = "Target Support Files/Pods-xcode_uikit_swiftTests/Pods-xcode_uikit_swiftTests.debug.xcconfig"; sourceTree = ""; }; - 913B8FA3C251CDF1F658AF4A /* Pods_xcode_uikit_swift_xcode_uikit_swiftUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xcode_uikit_swift_xcode_uikit_swiftUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - AA8638B6FDB23B33B736128C /* Pods-xcode_uikit_swift-xcode_uikit_swiftUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_uikit_swift-xcode_uikit_swiftUITests.release.xcconfig"; path = "Target Support Files/Pods-xcode_uikit_swift-xcode_uikit_swiftUITests/Pods-xcode_uikit_swift-xcode_uikit_swiftUITests.release.xcconfig"; sourceTree = ""; }; - B9D05C8534261452EAB3A7BC /* Pods-xcode_uikit_swift.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_uikit_swift.release.xcconfig"; path = "Target Support Files/Pods-xcode_uikit_swift/Pods-xcode_uikit_swift.release.xcconfig"; sourceTree = ""; }; - BDA321AB5BF5DA4FE38A4622 /* Pods-xcode_uikit_swiftTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_uikit_swiftTests.release.xcconfig"; path = "Target Support Files/Pods-xcode_uikit_swiftTests/Pods-xcode_uikit_swiftTests.release.xcconfig"; sourceTree = ""; }; - E656BC01F89D48706A5D6D5A /* Pods-xcode_uikit_swift.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-xcode_uikit_swift.debug.xcconfig"; path = "Target Support Files/Pods-xcode_uikit_swift/Pods-xcode_uikit_swift.debug.xcconfig"; sourceTree = ""; }; - EB983C002B12D858A66ED6E5 /* Pods_xcode_uikit_swift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_xcode_uikit_swift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; -/* End PBXFileReference section */ - -/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ - 78C59F3B2E7CF7E60047C08C /* Exceptions for "xcode_uikit_swift" folder in "xcode_uikit_swift" target */ = { - isa = PBXFileSystemSynchronizedBuildFileExceptionSet; - membershipExceptions = ( - Info.plist, - ); - target = 78C59F122E7CF7E50047C08C /* xcode_uikit_swift */; - }; -/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ - -/* Begin PBXFileSystemSynchronizedRootGroup section */ - 78C59F152E7CF7E50047C08C /* xcode_uikit_swift */ = { - isa = PBXFileSystemSynchronizedRootGroup; - exceptions = ( - 78C59F3B2E7CF7E60047C08C /* Exceptions for "xcode_uikit_swift" folder in "xcode_uikit_swift" target */, - ); - path = xcode_uikit_swift; - sourceTree = ""; - }; - 78C59F362E7CF7E60047C08C /* xcode_uikit_swiftUITests */ = { - isa = PBXFileSystemSynchronizedRootGroup; - path = xcode_uikit_swiftUITests; - sourceTree = ""; - }; -/* End PBXFileSystemSynchronizedRootGroup section */ - -/* Begin PBXFrameworksBuildPhase section */ - 78C59F102E7CF7E50047C08C /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 7ECB200B0AD58CB670B3C2F5 /* Pods_xcode_uikit_swift.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 78C59F302E7CF7E60047C08C /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ADF8AAB67AB135B2A1561A1A /* Pods_xcode_uikit_swift_xcode_uikit_swiftUITests.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 23B2B2DBB7BD8B28923087DA /* Pods */ = { - isa = PBXGroup; - children = ( - E656BC01F89D48706A5D6D5A /* Pods-xcode_uikit_swift.debug.xcconfig */, - B9D05C8534261452EAB3A7BC /* Pods-xcode_uikit_swift.release.xcconfig */, - 16C0FC186D03BDF4B43AC51F /* Pods-xcode_uikit_swift-xcode_uikit_swiftUITests.debug.xcconfig */, - AA8638B6FDB23B33B736128C /* Pods-xcode_uikit_swift-xcode_uikit_swiftUITests.release.xcconfig */, - 8FB3091209B846F6D7EB7052 /* Pods-xcode_uikit_swiftTests.debug.xcconfig */, - BDA321AB5BF5DA4FE38A4622 /* Pods-xcode_uikit_swiftTests.release.xcconfig */, - ); - path = Pods; - sourceTree = ""; - }; - 373087D926E10D683610FD42 /* Frameworks */ = { - isa = PBXGroup; - children = ( - EB983C002B12D858A66ED6E5 /* Pods_xcode_uikit_swift.framework */, - 913B8FA3C251CDF1F658AF4A /* Pods_xcode_uikit_swift_xcode_uikit_swiftUITests.framework */, - 3806160BF4CE46B7F0835E86 /* Pods_xcode_uikit_swiftTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - 78C59F0A2E7CF7E50047C08C = { - isa = PBXGroup; - children = ( - 78C59F152E7CF7E50047C08C /* xcode_uikit_swift */, - 78C59F362E7CF7E60047C08C /* xcode_uikit_swiftUITests */, - 78C59F142E7CF7E50047C08C /* Products */, - 23B2B2DBB7BD8B28923087DA /* Pods */, - 373087D926E10D683610FD42 /* Frameworks */, - ); - sourceTree = ""; - }; - 78C59F142E7CF7E50047C08C /* Products */ = { - isa = PBXGroup; - children = ( - 78C59F132E7CF7E50047C08C /* xcode_uikit_swift.app */, - 78C59F332E7CF7E60047C08C /* xcode_uikit_swiftUITests.xctest */, - ); - name = Products; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 78C59F122E7CF7E50047C08C /* xcode_uikit_swift */ = { - isa = PBXNativeTarget; - buildConfigurationList = 78C59F3C2E7CF7E60047C08C /* Build configuration list for PBXNativeTarget "xcode_uikit_swift" */; - buildPhases = ( - CE7DEDFA2784B3198AB14F2A /* [CP] Check Pods Manifest.lock */, - 78C59F0F2E7CF7E50047C08C /* Sources */, - 78C59F102E7CF7E50047C08C /* Frameworks */, - 78C59F112E7CF7E50047C08C /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - fileSystemSynchronizedGroups = ( - 78C59F152E7CF7E50047C08C /* xcode_uikit_swift */, - ); - name = xcode_uikit_swift; - productName = xcode_uikit_swift; - productReference = 78C59F132E7CF7E50047C08C /* xcode_uikit_swift.app */; - productType = "com.apple.product-type.application"; - }; - 78C59F322E7CF7E60047C08C /* xcode_uikit_swiftUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 78C59F442E7CF7E60047C08C /* Build configuration list for PBXNativeTarget "xcode_uikit_swiftUITests" */; - buildPhases = ( - D19583AC007C16E523F2DFFB /* [CP] Check Pods Manifest.lock */, - 78C59F2F2E7CF7E60047C08C /* Sources */, - 78C59F302E7CF7E60047C08C /* Frameworks */, - 78C59F312E7CF7E60047C08C /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 78C59F352E7CF7E60047C08C /* PBXTargetDependency */, - ); - fileSystemSynchronizedGroups = ( - 78C59F362E7CF7E60047C08C /* xcode_uikit_swiftUITests */, - ); - name = xcode_uikit_swiftUITests; - productName = xcode_uikit_swiftUITests; - productReference = 78C59F332E7CF7E60047C08C /* xcode_uikit_swiftUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 78C59F0B2E7CF7E50047C08C /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 2600; - LastUpgradeCheck = 2600; - TargetAttributes = { - 78C59F122E7CF7E50047C08C = { - CreatedOnToolsVersion = 26.0; - }; - 78C59F322E7CF7E60047C08C = { - CreatedOnToolsVersion = 26.0; - TestTargetID = 78C59F122E7CF7E50047C08C; - }; - }; - }; - buildConfigurationList = 78C59F0E2E7CF7E50047C08C /* Build configuration list for PBXProject "xcode_uikit_swift" */; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 78C59F0A2E7CF7E50047C08C; - minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 77; - productRefGroup = 78C59F142E7CF7E50047C08C /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 78C59F122E7CF7E50047C08C /* xcode_uikit_swift */, - 78C59F322E7CF7E60047C08C /* xcode_uikit_swiftUITests */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 78C59F112E7CF7E50047C08C /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 78C59F312E7CF7E60047C08C /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - CE7DEDFA2784B3198AB14F2A /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-xcode_uikit_swift-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - D19583AC007C16E523F2DFFB /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-xcode_uikit_swift-xcode_uikit_swiftUITests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 78C59F0F2E7CF7E50047C08C /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 78C59F2F2E7CF7E60047C08C /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 78C59F352E7CF7E60047C08C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 78C59F122E7CF7E50047C08C /* xcode_uikit_swift */; - targetProxy = 78C59F342E7CF7E60047C08C /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 78C59F3D2E7CF7E60047C08C /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = E656BC01F89D48706A5D6D5A /* Pods-xcode_uikit_swift.debug.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = xcode_uikit_swift/Info.plist; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-uikit-swift"; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Debug; - }; - 78C59F3E2E7CF7E60047C08C /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = B9D05C8534261452EAB3A7BC /* Pods-xcode_uikit_swift.release.xcconfig */; - buildSettings = { - ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - INFOPLIST_FILE = xcode_uikit_swift/Info.plist; - INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; - INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - ); - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-uikit-swift"; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRING_CATALOG_GENERATE_SYMBOLS = YES; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor; - SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - }; - name = Release; - }; - 78C59F3F2E7CF7E60047C08C /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_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_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - }; - name = Debug; - }; - 78C59F402E7CF7E60047C08C /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_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_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_USER_SCRIPT_SANDBOXING = NO; - GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 26.0; - LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - SWIFT_COMPILATION_MODE = wholemodule; - VALIDATE_PRODUCT = YES; - }; - name = Release; - }; - 78C59F452E7CF7E60047C08C /* Debug */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = 16C0FC186D03BDF4B43AC51F /* Pods-xcode_uikit_swift-xcode_uikit_swiftUITests.debug.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-uikit-swiftUITests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRING_CATALOG_GENERATE_SYMBOLS = NO; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = xcode_uikit_swift; - }; - name = Debug; - }; - 78C59F462E7CF7E60047C08C /* Release */ = { - isa = XCBuildConfiguration; - baseConfigurationReference = AA8638B6FDB23B33B736128C /* Pods-xcode_uikit_swift-xcode_uikit_swiftUITests.release.xcconfig */; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - GENERATE_INFOPLIST_FILE = YES; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.devicelab.xcode-uikit-swiftUITests"; - PRODUCT_NAME = "$(TARGET_NAME)"; - STRING_CATALOG_GENERATE_SYMBOLS = NO; - SWIFT_APPROACHABLE_CONCURRENCY = YES; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_TARGET_NAME = xcode_uikit_swift; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 78C59F0E2E7CF7E50047C08C /* Build configuration list for PBXProject "xcode_uikit_swift" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 78C59F3F2E7CF7E60047C08C /* Debug */, - 78C59F402E7CF7E60047C08C /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 78C59F3C2E7CF7E60047C08C /* Build configuration list for PBXNativeTarget "xcode_uikit_swift" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 78C59F3D2E7CF7E60047C08C /* Debug */, - 78C59F3E2E7CF7E60047C08C /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 78C59F442E7CF7E60047C08C /* Build configuration list for PBXNativeTarget "xcode_uikit_swiftUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 78C59F452E7CF7E60047C08C /* Debug */, - 78C59F462E7CF7E60047C08C /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 78C59F0B2E7CF7E50047C08C /* Project object */; -} From c8c09f6d052b60fbd2803f6acd0bc127c7786877 Mon Sep 17 00:00:00 2001 From: Ben Konyi Date: Tue, 7 Oct 2025 18:55:27 -0400 Subject: [PATCH 101/204] [ Tool ] Output `app.dtd` and `app.devTools` in machine mode (#176655) Fixes https://github.com/flutter/flutter/issues/176310 --- packages/flutter_tools/doc/daemon.md | 52 +++++++++++--- .../lib/src/commands/daemon.dart | 13 ++-- .../lib/src/isolated/resident_web_runner.dart | 70 +++++++++++-------- .../lib/src/resident_runner.dart | 5 +- packages/flutter_tools/lib/src/run_cold.dart | 12 +++- packages/flutter_tools/lib/src/run_hot.dart | 9 ++- .../test/general.shard/cold_test.dart | 34 +++++++++ .../integration.shard/flutter_run_test.dart | 12 ++++ .../test/integration.shard/test_driver.dart | 31 ++++++++ 9 files changed, 189 insertions(+), 49 deletions(-) diff --git a/packages/flutter_tools/doc/daemon.md b/packages/flutter_tools/doc/daemon.md index 5fc9d22273e8b..1d4a4a381ab15 100644 --- a/packages/flutter_tools/doc/daemon.md +++ b/packages/flutter_tools/doc/daemon.md @@ -17,13 +17,13 @@ A set of `flutter daemon` commands/events are also exposed via `flutter run --ma The daemon speaks [JSON-RPC](http://json-rpc.org/) to clients. It uses stdin and stdout as the transport protocol. To send a command to the server, create your command as a JSON-RPC message, encode it to JSON, surround the encoded text with square brackets, and write it as one line of text to the stdin of the process: ```json -[{"method":"daemon.version","id":0}] +[{ "method": "daemon.version", "id": 0 }] ``` The response will come back as a single line from stdout: ```json -[{"id":0,"result":"0.1.0"}] +[{ "id": 0, "result": "0.1.0" }] ``` All requests and responses should be wrapped in square brackets. This ensures that the communications are resilient to stray output in the stdout/stdin stream. @@ -35,17 +35,42 @@ Each command should have a `method` field. This is in the form '`domain.command` Any params for that command should be passed in through a `params` field. Here's an example request/response for the `device.getDevices` method: ```json -[{"method":"device.getDevices","id":2}] +[{ "method": "device.getDevices", "id": 2 }] ``` ```json -[{"id":2,"result":[{"id":"702ABC1F-5EA5-4F83-84AB-6380CA91D39A","name":"iPhone 6","platform":"ios_x64","available":true}]}] +[ + { + "id": 2, + "result": [ + { + "id": "702ABC1F-5EA5-4F83-84AB-6380CA91D39A", + "name": "iPhone 6", + "platform": "ios_x64", + "available": true + } + ] + } +] ``` Events that come from the server will have an `event` field containing the type of event, along with a `params` field. ```json -[{"event":"device.added","params":{"id":"1DD6786B-37D4-4355-AA15-B818A87A18B4","name":"iPhone XS Max","platform":"ios","emulator":true,"ephemeral":false,"platformType":"ios","category":"mobile"}}] +[ + { + "event": "device.added", + "params": { + "id": "1DD6786B-37D4-4355-AA15-B818A87A18B4", + "name": "iPhone XS Max", + "platform": "ios", + "emulator": true, + "ephemeral": false, + "platformType": "ios", + "category": "mobile" + } + } +] ``` ## Domains and Commands @@ -75,7 +100,7 @@ The schema for each element in `reasons` is: - reasonText (String) - a description of why the platform is not supported - fixText (String) - human readable instructions of how to fix this reason - fixCode (String) - stringified version of the `_ReasonCode` enum. To be used -by daemon clients who intend to auto-fix. + by daemon clients who intend to auto-fix. The possible platform types are the `PlatformType` enumeration in the lib/src/device.dart library. @@ -170,6 +195,17 @@ This is sent when an app is stopped or detached from. The `params` field will be This is sent once a web application is being served and available for the user to access. The `params` field will be a map with a string `url` field and a boolean `launched` indicating whether the application has already been launched in a browser (this will generally be true for a browser device unless `--no-web-browser-launch` was used, and false for the headless `web-server` device). +#### app.devTools + +This is sent after the [`app.debugPort`](#appdebugPort) event if DevTools is being served for this application instance. The +`params` field will be a map with the string `uri` field containing the DevTools URI with query parameters already set to connect +to the running application. + +#### app.dtd + +This is sent after the [`app.debugPort`](#appdebugPort) event if the Dart Tooling Daemon (DTD) is being served for this application +instance. The `params` field will be a map with the string `uri` field containing the DTD URI. + ### Daemon-to-Editor Requests These requests come _from_ the Flutter daemon and should be responded to by the client/editor. @@ -224,11 +260,11 @@ Removed a forwarded port. It takes `deviceId`, `devicePort`, and `hostPort` as r #### device.added -This is sent when a device is connected (and polling has been enabled via `enable()`). The `params` field will be a map with the fields `id`, `name`, `platform`, `category`, `platformType`, `ephemeral`, and `emulator`. For more information on `platform`, `category`, `platformType`, and `ephemeral` see `device.getDevices`. +This is sent when a device is connected (and polling has been enabled via `enable()`). The `params` field will be a map with the fields `id`, `name`, `platform`, `category`, `platformType`, `ephemeral`, and `emulator`. For more information on `platform`, `category`, `platformType`, and `ephemeral` see `device.getDevices`. #### device.removed -This is sent when a device is disconnected (and polling has been enabled via `enable()`). The `params` field will be a map with the fields `id`, `name`, `platform`, `category`, `platformType`, `ephemeral`, and `emulator`. For more information on `platform`, `category`, `platformType`, and `ephemeral` see `device.getDevices`. +This is sent when a device is disconnected (and polling has been enabled via `enable()`). The `params` field will be a map with the fields `id`, `name`, `platform`, `category`, `platformType`, `ephemeral`, and `emulator`. For more information on `platform`, `category`, `platformType`, and `ephemeral` see `device.getDevices`. ### emulator domain diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index 678f7a793ec44..ae3e5a2d9a6d2 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -796,15 +796,18 @@ class AppDomain extends Domain { // As it just writes to stdout. unawaited( connectionInfoCompleter.future.then((DebugConnectionInfo info) { - final params = { + _sendAppEvent(app, 'debugPort', { // The web vmservice proxy does not have an http address. 'port': info.httpUri?.port ?? info.wsUri!.port, 'wsUri': info.wsUri.toString(), - }; - if (info.baseUri != null) { - params['baseUri'] = info.baseUri; + 'baseUri': ?info.baseUri, + }); + if (info.devToolsUri != null) { + _sendAppEvent(app, 'devTools', {'uri': info.devToolsUri!.toString()}); + } + if (info.dtdUri != null) { + _sendAppEvent(app, 'dtd', {'uri': info.dtdUri!.toString()}); } - _sendAppEvent(app, 'debugPort', params); }), ); } diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index 12bf47fbb17de..2284eba0cb8fa 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -138,7 +138,7 @@ class ResidentWebRunner extends ResidentRunner { @override FileSystem get fileSystem => _fileSystem; - FlutterDevice? get device => flutterDevices.first; + FlutterDevice? get flutterDevice => flutterDevices.first; final FlutterProject flutterProject; // Mapping from service name to service method. @@ -156,11 +156,11 @@ class ResidentWebRunner extends ResidentRunner { /// Device is debuggable if not a WebServer device, or if running with /// --start-paused or using DWDS WebSocket connection (WebServer device). late final bool _deviceIsDebuggable = - device!.device is! WebServerDevice || + flutterDevice!.device is! WebServerDevice || debuggingOptions.startPaused || useDwdsWebSocketConnection; - late final useDwdsWebSocketConnection = device!.device is! ChromiumDevice; + late final useDwdsWebSocketConnection = flutterDevice!.device is! ChromiumDevice; @override // Web uses a different plugin registry. @@ -220,7 +220,7 @@ class ResidentWebRunner extends ResidentRunner { await _extensionEventSub?.cancel(); if (stopAppDuringCleanup) { - await device!.device!.stopApp(null); + await flutterDevice!.device!.stopApp(null); } _registeredMethodsForService.clear(); @@ -243,7 +243,7 @@ class ResidentWebRunner extends ResidentRunner { @override Future stopEchoingDeviceLog() async { // Do nothing for ResidentWebRunner - await device!.stopEchoingDeviceLog(); + await flutterDevice!.stopEchoingDeviceLog(); } @override @@ -264,10 +264,10 @@ class ResidentWebRunner extends ResidentRunner { final String modeName = debuggingOptions.buildInfo.mode.friendlyName; _logger.printStatus( 'Launching ${getDisplayPath(target, _fileSystem)} ' - 'on ${device!.device!.displayName} in $modeName mode...', + 'on ${flutterDevice!.device!.displayName} in $modeName mode...', ); - if (device!.device is ChromiumDevice) { - _chromiumLauncher = (device!.device! as ChromiumDevice).chromeLauncher; + if (flutterDevice!.device is ChromiumDevice) { + _chromiumLauncher = (flutterDevice!.device! as ChromiumDevice).chromeLauncher; } try { @@ -280,10 +280,10 @@ class ResidentWebRunner extends ResidentRunner { final WebDevServerConfig updatedConfig = originalConfig.copyWith(port: resolvedPort); final ExpressionCompiler? expressionCompiler = debuggingOptions.webEnableExpressionEvaluation - ? WebExpressionCompiler(device!.generator!, fileSystem: _fileSystem) + ? WebExpressionCompiler(flutterDevice!.generator!, fileSystem: _fileSystem) : null; - device!.devFS = WebDevFS( + flutterDevice!.devFS = WebDevFS( webDevServerConfig: updatedConfig, packagesFilePath: packagesFilePath, urlTunneller: _urlTunneller, @@ -313,7 +313,7 @@ class ResidentWebRunner extends ResidentRunner { logger: logger, platform: _platform, ); - Uri url = await device!.devFS!.create(); + Uri url = await flutterDevice!.devFS!.create(); if (updatedConfig.https?.certKeyPath != null && updatedConfig.https?.certPath != null) { url = url.replace(scheme: 'https'); } @@ -325,7 +325,7 @@ class ResidentWebRunner extends ResidentRunner { appFailedToStart(); return 1; } - device!.generator!.accept(); + flutterDevice!.generator!.accept(); cacheInitialDillCompilation(); } else { final webBuilder = WebBuilder( @@ -344,15 +344,15 @@ class ResidentWebRunner extends ResidentRunner { compilerConfigs: [_compilerConfig], ); } - final webDevFS = device!.devFS! as WebDevFS; + final webDevFS = flutterDevice!.devFS! as WebDevFS; final bool useDebugExtension = - device!.device is WebServerDevice && debuggingOptions.startPaused; + flutterDevice!.device is WebServerDevice && debuggingOptions.startPaused; // Listen for connected apps early and then await this `Future` later // when we attach. final Future? connectDebug = supportsServiceProtocol ? webDevFS.connect(useDebugExtension) : null; - await device!.device!.startApp( + await flutterDevice!.device!.startApp( package, mainPath: target, debuggingOptions: debuggingOptions, @@ -427,7 +427,7 @@ class ResidentWebRunner extends ResidentRunner { } final String targetPlatform = getNameForTargetPlatform(TargetPlatform.web_javascript); - final String sdkName = await device!.device!.sdkNameAndVersion; + final String sdkName = await flutterDevice!.device!.sdkNameAndVersion; // Will be null if there is no report. final UpdateFSReport? report; @@ -437,10 +437,10 @@ class ResidentWebRunner extends ResidentRunner { // wasteful. report = await _updateDevFS(fullRestart: fullRestart, resetCompiler: false); if (report.success) { - device!.generator!.accept(); + flutterDevice!.generator!.accept(); } else { status.stop(); - await device!.generator!.reject(); + await flutterDevice!.generator!.reject(); if (report.hotReloadRejected) { // We cannot capture the reason why the reload was rejected as it may // contain user information. @@ -725,16 +725,17 @@ class ResidentWebRunner extends ResidentRunner { } } final InvalidationResult invalidationResult = await projectFileInvalidator.findInvalidated( - lastCompiled: device!.devFS!.lastCompiled, - urisToMonitor: device!.devFS!.sources, + lastCompiled: flutterDevice!.devFS!.lastCompiled, + urisToMonitor: flutterDevice!.devFS!.sources, packagesPath: packagesFilePath, - packageConfig: device!.devFS!.lastPackageConfig ?? debuggingOptions.buildInfo.packageConfig, + packageConfig: + flutterDevice!.devFS!.lastPackageConfig ?? debuggingOptions.buildInfo.packageConfig, ); final Status devFSStatus = _logger.startProgress( 'Waiting for connection from debug service on ' - '${device!.device!.displayName}...', + '${flutterDevice!.device!.displayName}...', ); - final UpdateFSReport report = await device!.devFS!.update( + final UpdateFSReport report = await flutterDevice!.devFS!.update( mainUri: await _generateEntrypoint( _fileSystem.file(mainPath).absolute.uri, invalidationResult.packageConfig, @@ -742,7 +743,7 @@ class ResidentWebRunner extends ResidentRunner { target: target, bundle: assetBundle, bundleFirstUpload: isFirstUpload, - generator: device!.generator!, + generator: flutterDevice!.generator!, fullRestart: fullRestart, resetCompiler: resetCompiler, dillOutputPath: dillOutputPath, @@ -750,7 +751,7 @@ class ResidentWebRunner extends ResidentRunner { invalidatedFiles: invalidationResult.uris!, packageConfig: invalidationResult.packageConfig!, trackWidgetCreation: debuggingOptions.buildInfo.trackWidgetCreation, - shaderCompiler: device!.developmentShaderCompiler, + shaderCompiler: flutterDevice!.developmentShaderCompiler, ); devFSStatus.stop(); _logger.printTrace('Synced ${getSizeAsPlatformMB(report.syncedBytes)}.'); @@ -841,20 +842,23 @@ class ResidentWebRunner extends ResidentRunner { // It is safe to ignore this error because we expect an error to be // thrown if we're not already subscribed. } + final Device device = flutterDevice!.device!; await setUpVmService( reloadSources: (String isolateId, {bool? force, bool? pause}) async { await restart(pause: pause); }, - device: device!.device, + device: device, flutterProject: flutterProject, printStructuredErrorLogMethod: printStructuredErrorLog, vmService: _vmService.service, ); final Uri websocketUri = Uri.parse(debugConnection.uri); - device!.vmService = _vmService; + flutterDevice!.vmService = _vmService; if (debugConnection.devToolsUri != null) { - (device!.device! as WebDevice).devToolsUri = Uri.parse(debugConnection.devToolsUri!); + (flutterDevice!.device! as WebDevice).devToolsUri = Uri.parse( + debugConnection.devToolsUri!, + ); } // Run main immediately if the app is not started paused or if there @@ -881,7 +885,13 @@ class ResidentWebRunner extends ResidentRunner { // service message instead. _logger.printStatus('Debug service listening on $websocketUri'); printDebuggerList(); - connectionInfoCompleter?.complete(DebugConnectionInfo(wsUri: websocketUri)); + connectionInfoCompleter?.complete( + DebugConnectionInfo( + wsUri: websocketUri, + devToolsUri: Uri.tryParse(debugConnection.devToolsUri ?? ''), + // TODO(bkonyi): surface DTD URI once it's visible from DWDS + ), + ); }), ); } else { @@ -902,7 +912,7 @@ class ResidentWebRunner extends ResidentRunner { @override Future exitApp() async { if (stopAppDuringCleanup) { - await device!.exitApps(); + await flutterDevice!.exitApps(); } appFinished(); } diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 534cfc22a89c8..d4837c2d5aa31 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -1867,11 +1867,14 @@ class TerminalHandler { } class DebugConnectionInfo { - DebugConnectionInfo({this.httpUri, this.wsUri, this.baseUri}); + DebugConnectionInfo({this.httpUri, this.wsUri, this.baseUri, this.dtdUri, this.devToolsUri}); final Uri? httpUri; final Uri? wsUri; final String? baseUri; + + final Uri? dtdUri; + final Uri? devToolsUri; } /// Returns the next platform value for the switcher. diff --git a/packages/flutter_tools/lib/src/run_cold.dart b/packages/flutter_tools/lib/src/run_cold.dart index 1d37ae0976eb2..dba7bd1b96241 100644 --- a/packages/flutter_tools/lib/src/run_cold.dart +++ b/packages/flutter_tools/lib/src/run_cold.dart @@ -4,6 +4,7 @@ import 'dart:async'; +import 'base/dds.dart'; import 'base/file_system.dart'; import 'base/logger.dart'; import 'build_info.dart'; @@ -75,12 +76,17 @@ class ColdRunner extends ResidentRunner { } } - if (flutterDevices.first.vmServiceUris != null) { + final FlutterDevice flutterDevice = flutterDevices.first; + if (flutterDevice.vmServiceUris != null) { + final FlutterVmService? vmService = flutterDevice.vmService; + final DartDevelopmentService dds = flutterDevice.device!.dds; // For now, only support one debugger connection. connectionInfoCompleter?.complete( DebugConnectionInfo( - httpUri: flutterDevices.first.vmService!.httpAddress, - wsUri: flutterDevices.first.vmService!.wsAddress, + httpUri: vmService!.httpAddress, + wsUri: vmService.wsAddress, + devToolsUri: dds.devToolsUri, + dtdUri: dds.dtdUri, ), ); } diff --git a/packages/flutter_tools/lib/src/run_hot.dart b/packages/flutter_tools/lib/src/run_hot.dart index a6aeaaa6a37ef..b22c97e42d0aa 100644 --- a/packages/flutter_tools/lib/src/run_hot.dart +++ b/packages/flutter_tools/lib/src/run_hot.dart @@ -11,6 +11,7 @@ import 'package:unified_analytics/unified_analytics.dart'; import 'package:vm_service/vm_service.dart' as vm_service; import 'base/context.dart'; +import 'base/dds.dart'; import 'base/file_system.dart'; import 'base/logger.dart'; import 'base/platform.dart'; @@ -278,12 +279,16 @@ class HotRunner extends ResidentRunner { try { final List baseUris = await _initDevFS(); if (connectionInfoCompleter != null) { + final FlutterVmService vmService = flutterDevices.first.vmService!; + final DartDevelopmentService dds = flutterDevices.first.device!.dds; // Only handle one debugger connection. connectionInfoCompleter.complete( DebugConnectionInfo( - httpUri: flutterDevices.first.vmService!.httpAddress, - wsUri: flutterDevices.first.vmService!.wsAddress, + httpUri: vmService.httpAddress, + wsUri: vmService.wsAddress, baseUri: baseUris.first.toString(), + devToolsUri: dds.devToolsUri, + dtdUri: dds.dtdUri, ), ); } diff --git a/packages/flutter_tools/test/general.shard/cold_test.dart b/packages/flutter_tools/test/general.shard/cold_test.dart index b383fac2ecbef..e2b89c41d83eb 100644 --- a/packages/flutter_tools/test/general.shard/cold_test.dart +++ b/packages/flutter_tools/test/general.shard/cold_test.dart @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:file/memory.dart'; +import 'package:flutter_tools/src/base/dds.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/platform.dart'; @@ -210,6 +211,9 @@ class FakeDevice extends Fake implements Device { @override Future get targetPlatform async => TargetPlatform.tester; + @override + DartDevelopmentService get dds => FakeDartDevelopmentService(); + var wasDisposed = false; @override @@ -218,6 +222,36 @@ class FakeDevice extends Fake implements Device { } } +class FakeDartDevelopmentService extends Fake implements DartDevelopmentService { + @override + late Future done; + + @override + Uri? uri; + + @override + Uri? devToolsUri; + + @override + Uri? dtdUri; + + @override + Future startDartDevelopmentService( + Uri vmServiceUri, { + int? ddsPort, + FlutterDevice? device, + bool? ipv6, + bool? disableServiceAuthCodes, + bool enableDevTools = false, + bool cacheStartupProfile = false, + String? google3WorkspaceRoot, + Uri? devToolsServerAddress, + }) async {} + + @override + Future shutdown() async {} +} + class TestFlutterDevice extends FlutterDevice { TestFlutterDevice({ required Device device, diff --git a/packages/flutter_tools/test/integration.shard/flutter_run_test.dart b/packages/flutter_tools/test/integration.shard/flutter_run_test.dart index d40653389d279..f592c93875ca4 100644 --- a/packages/flutter_tools/test/integration.shard/flutter_run_test.dart +++ b/packages/flutter_tools/test/integration.shard/flutter_run_test.dart @@ -52,6 +52,18 @@ void main() { } }); + testWithoutContext('flutter run outputs DTD and DevTools events', () async { + await flutter.run(startPaused: true, withDebugger: true); + expect(flutter.devToolsUri, isNotNull); + expect(flutter.dtdUri, isNotNull); + }); + + testWithoutContext('flutter run does not output DTD and DevTools events', () async { + await flutter.run(startPaused: true, withDebugger: true, noDevtools: true); + expect(flutter.devToolsUri, isNull); + expect(flutter.dtdUri, isNull); + }); + testWithoutContext('sets activeDevToolsServerAddress extension', () async { await flutter.run( startPaused: true, diff --git a/packages/flutter_tools/test/integration.shard/test_driver.dart b/packages/flutter_tools/test/integration.shard/test_driver.dart index 58eed5e0e709d..6b33bcdbc0d71 100644 --- a/packages/flutter_tools/test/integration.shard/test_driver.dart +++ b/packages/flutter_tools/test/integration.shard/test_driver.dart @@ -52,6 +52,8 @@ abstract final class FlutterTestDriver { final _errorBuffer = StringBuffer(); String? _lastResponse; Uri? _vmServiceWsUri; + Uri? _devToolsUri; + Uri? _dtdUri; int? _attachPort; var _hasExited = false; @@ -62,6 +64,8 @@ abstract final class FlutterTestDriver { int? get vmServicePort => _vmServiceWsUri?.port; bool get hasExited => _hasExited; Uri? get vmServiceWsUri => _vmServiceWsUri; + Uri? get devToolsUri => _devToolsUri; + Uri? get dtdUri => _dtdUri; /// Completes with the full method name for the 'reloadSources' service once /// it's registered (e.g., `s0.reloadSources`). @@ -602,8 +606,14 @@ final class FlutterRunTestDriver extends FlutterTestDriver { ...?additionalCommandArgs, ], withDebugger: withDebugger, + withDevtools: !noDevtools, startPaused: startPaused, waitForDebugPort: device != WebServerDevice.kWebServerDeviceId && !wasm, + waitForDtdAndDevTools: + device != WebServerDevice.kWebServerDeviceId && + device != GoogleChromeDevice.kChromeDeviceId && + !noDevtools && + spawnDdsInstance, pauseOnExceptions: pauseOnExceptions, script: script, verbose: verbose, @@ -642,9 +652,11 @@ final class FlutterRunTestDriver extends FlutterTestDriver { List args, { String? script, bool withDebugger = false, + bool withDevtools = false, bool startPaused = false, bool pauseOnExceptions = false, bool waitForDebugPort = false, + bool waitForDtdAndDevTools = true, bool verbose = false, int? attachPort, }) async { @@ -685,11 +697,30 @@ final class FlutterRunTestDriver extends FlutterTestDriver { event: 'app.started', timeout: appStartTimeout, ); + final Future devTools = + _waitFor( + event: 'app.devTools', + timeout: appStartTimeout, + ignoreAppStopEvent: true, + ).then((event) async { + _devToolsUri = Uri.parse( + (event['params']! as Map)['uri']! as String, + ); + }); + final Future dtd = + _waitFor(event: 'app.dtd', timeout: appStartTimeout, ignoreAppStopEvent: true).then(( + event, + ) { + _dtdUri = Uri.parse((event['params']! as Map)['uri']! as String); + }); late final Map debugPort; if (waitForDebugPort || withDebugger || attachPort != null) { debugPort = await _waitFor(event: 'app.debugPort', timeout: appStartTimeout); } + if (withDebugger && waitForDtdAndDevTools) { + await Future.wait([devTools, dtd]); + } if (withDebugger || attachPort != null) { final wsUriString = (debugPort['params']! as Map)['wsUri']! as String; _vmServiceWsUri = Uri.parse(wsUriString); From 4b7ee522d8364d9d02f36bba64517f6a620d2616 Mon Sep 17 00:00:00 2001 From: Ivan Inozemtsev Date: Wed, 8 Oct 2025 00:57:22 +0200 Subject: [PATCH 102/204] Roll Dart SDK to 3.10.0-290.1.beta (#176629) Rolling Dart SDK 3.10 beta3 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. --- DEPS | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/DEPS b/DEPS index 337dcbb7202dc..9e8169ae258a9 100644 --- a/DEPS +++ b/DEPS @@ -56,16 +56,16 @@ vars = { # Dart is: https://github.com/dart-lang/sdk/blob/main/DEPS # You can use //tools/dart/create_updated_flutter_deps.py to produce # updated revision list of existing dependencies. - 'dart_revision': '6b0193498f09e59e25d75233d0558143caa61174', + 'dart_revision': '94670bffc27d8233f94a77199e2803a5c4c8ff78', # WARNING: DO NOT EDIT MANUALLY # The lines between blank lines above and below are generated by a script. See create_updated_flutter_deps.py - 'dart_ai_rev': 'ec5d6aa38c8a6a9e687cc5223e2592ac91f2d5fe', + 'dart_ai_rev': '59db320ee39a1ae5f6e2830be851e52bb7263ce1', 'dart_binaryen_rev': '1d2e23d5e55788091a51420ba3a9889d4efe7509', 'dart_boringssl_rev': '706742e482d89214f13a642ccfcdad596a24a32f', 'dart_core_rev': '5c3e2c38df268be2347f3aad30ced0147dd012bb', - 'dart_devtools_rev': '5e1792245005088a0a0dfe28f207bd22045ba783', - 'dart_ecosystem_rev': '36e514d52c5d056227e4cea98b784ade50b5b4f1', + 'dart_devtools_rev': '0327830448901920f739259364c3f2f624df5a03', + 'dart_ecosystem_rev': '96ee86147a5f4c70aed64262e1521b745936cdb1', 'dart_http_rev': '2c53fa3c558ec5d1dd9fce4360d435113dba11e5', 'dart_i18n_rev': '34d1832b7e65d9aef1f7f6a82c22f6e53476191c', 'dart_libprotobuf_rev': '24487dd1045c7f3d64a21f38a3f0c06cc4cf2edb', @@ -74,10 +74,10 @@ vars = { 'dart_protobuf_rev': '14bbd0bd7ff9b7e322ff4e85bd243f6905170b92', 'dart_pub_rev': 'f7f1891e2de3d795532f45ec214f88ac912ffcd6', 'dart_sync_http_rev': '6666fff944221891182e1f80bf56569338164d72', - 'dart_tools_rev': 'ce9d2ad5d246329af04cf03e1203ee665886741b', + 'dart_tools_rev': '6866f9b19553625cc8af099d67aecbfb02067dcb', 'dart_vector_math_rev': 'a7b7e9ccb931348dbfa669e0f8fea1bf97705b16', 'dart_web_rev': '816abcc1bf186f61c7e66e7f4c56d1554a61ab27', - 'dart_webdev_rev': '0b2a408f6f64a29cd0d18ac7d2d407a4e1db8e0f', + 'dart_webdev_rev': '29ba1b12c83b5d0e34bc61181801120c36b95c54', 'dart_webdriver_rev': '09104f459ed834d48b132f6b7734923b1fbcf2e9', 'dart_webkit_inspection_protocol_rev': '0f7685804d77ec02c6564d7ac1a6c8a2341c5bdf', @@ -302,7 +302,7 @@ deps = { Var('chromium_git') + '/external/github.com/WebAssembly/binaryen.git@1d2e23d5e55788091a51420ba3a9889d4efe7509', 'engine/src/flutter/third_party/dart/third_party/devtools': - {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:5e1792245005088a0a0dfe28f207bd22045ba783'}]}, + {'dep_type': 'cipd', 'packages': [{'package': 'dart/third_party/flutter/devtools', 'version': 'git_revision:0327830448901920f739259364c3f2f624df5a03'}]}, 'engine/src/flutter/third_party/dart/third_party/pkg/ai': Var('dart_git') + '/ai.git' + '@' + Var('dart_ai_rev'), @@ -314,7 +314,7 @@ deps = { Var('dart_git') + '/dart_style.git@ca019b0498692ad78f5f0f0f6208a1258e17fc90', 'engine/src/flutter/third_party/dart/third_party/pkg/dartdoc': - Var('dart_git') + '/dartdoc.git@669b15f7da5da04a38e95e8ac0ff6471697a549f', + Var('dart_git') + '/dartdoc.git@ec2a4feee51961e9fbdd2bd94060cc8fc994c47e', 'engine/src/flutter/third_party/dart/third_party/pkg/ecosystem': Var('dart_git') + '/ecosystem.git' + '@' + Var('dart_ecosystem_rev'), @@ -347,7 +347,7 @@ deps = { Var('dart_git') + '/external/github.com/simolus3/tar.git@13479f7c2a18f499e840ad470cfcca8c579f6909', 'engine/src/flutter/third_party/dart/third_party/pkg/test': - Var('dart_git') + '/test.git@a16f14975c5625ef99abc71f7e91bca5d8e55054', + Var('dart_git') + '/test.git@8083c8f24ffbca58cc0385add03c296b70636e7a', 'engine/src/flutter/third_party/dart/third_party/pkg/tools': Var('dart_git') + '/tools.git' + '@' + Var('dart_tools_rev'), From 1cee136823542a2c0c09b6672bdac22724c4279c Mon Sep 17 00:00:00 2001 From: Kostia Sokolovskyi Date: Wed, 8 Oct 2025 02:27:22 +0200 Subject: [PATCH 103/204] Fix PopupMenu does not update when PopupMenuTheme in Theme changes. (#175513) Fixes https://github.com/flutter/flutter/issues/43824 ### Description - Fixes `PopupMenu` does not update when `PopupMenuTheme` in `Theme` changes

Code sample ```dart import 'package:flutter/material.dart'; void main() => runApp(const App()); class App extends StatelessWidget { const App({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', debugShowCheckedModeBanner: false, theme: ThemeData( colorScheme: const ColorScheme.light(), popupMenuTheme: PopupMenuThemeData( elevation: 10, color: Colors.red, shadowColor: Colors.black, surfaceTintColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), menuPadding: EdgeInsets.all(10), ), ), darkTheme: ThemeData( colorScheme: const ColorScheme.dark(), popupMenuTheme: PopupMenuThemeData( elevation: 20, color: Colors.blue, shadowColor: Colors.white, surfaceTintColor: Colors.black, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), menuPadding: EdgeInsets.all(20), ), ), home: const HomePage(), ); } } class HomePage extends StatelessWidget { const HomePage({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Hello Flutter'), actions: [ PopupMenuButton( itemBuilder: (context) => >[ const PopupMenuItem(value: 1, child: Text('Item 1')), const PopupMenuItem(value: 2, child: Text('Item 2')), const PopupMenuItem(value: 3, child: Text('Item 3')), ], ) ], ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: const [Text('Nothing here')], ), ), ); } } ```
| BEFORE | AFTER | | - | - | |