diff --git a/.ci.yaml b/.ci.yaml index eb01816c7e2f9..e1511b473a0a3 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -1499,7 +1499,6 @@ targets: scheduler: luci - name: Linux_android cubic_bezier_perf_sksl_warmup__e2e_summary - bringup: true # Flaky https://github.com/flutter/flutter/issues/103504 recipe: devicelab/devicelab_drone presubmit: false timeout: 60 @@ -1941,6 +1940,28 @@ targets: task_name: new_gallery__transition_perf scheduler: luci + - name: Linux_android new_gallery_impeller__transition_perf + recipe: devicelab/devicelab_drone + bringup: true + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux"] + task_name: new_gallery_impeller__transition_perf + scheduler: luci + + - name: Linux_samsung_s10 new_gallery_impeller__transition_perf + recipe: devicelab/devicelab_drone + bringup: true + presubmit: false + timeout: 60 + properties: + tags: > + ["devicelab", "android", "linux", "samsung", "s10"] + task_name: new_gallery_impeller__transition_perf + scheduler: luci + - name: Linux_android picture_cache_perf__e2e_summary recipe: devicelab/devicelab_drone presubmit: false @@ -2963,7 +2984,6 @@ targets: scheduler: luci - name: Mac_arm64_android run_release_test - bringup: true # Flaky https://github.com/flutter/flutter/issues/103059 recipe: devicelab/devicelab_drone presubmit: false runIf: diff --git a/TESTOWNERS b/TESTOWNERS index d36f11ccbf94a..b836a5931dcb3 100644 --- a/TESTOWNERS +++ b/TESTOWNERS @@ -124,6 +124,7 @@ /dev/devicelab/bin/tasks/integration_ui_textfield.dart @zanderso @flutter/tool /dev/devicelab/bin/tasks/microbenchmarks.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/new_gallery__transition_perf.dart @zanderso @flutter/engine +/dev/devicelab/bin/tasks/new_gallery_impeller__transition_perf.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/picture_cache_perf__timeline_summary.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/platform_channel_sample_test.dart @zanderso @flutter/engine /dev/devicelab/bin/tasks/platform_interaction_test.dart @stuartmorgan @flutter/plugin diff --git a/bin/internal/engine.version b/bin/internal/engine.version index ec8be1e1c7c34..4201fd0761762 100644 --- a/bin/internal/engine.version +++ b/bin/internal/engine.version @@ -1 +1 @@ -96b2ba2914e1b7b24a2a336c5b1b887ff6fd7411 +9db8df273012ad935357236a1bb2cc7bbac7cc85 diff --git a/bin/internal/flutter_plugins.version b/bin/internal/flutter_plugins.version index f632de3b33028..e8977fc12f79c 100644 --- a/bin/internal/flutter_plugins.version +++ b/bin/internal/flutter_plugins.version @@ -1 +1 @@ -a692f840ce727e4a1f9b6936c9d0219330af5742 +65620edc0718454e63ab9a78aaa8df1f9fde1e44 diff --git a/bin/internal/fuchsia-linux.version b/bin/internal/fuchsia-linux.version index f3d02903503ea..746b8ec49feee 100644 --- a/bin/internal/fuchsia-linux.version +++ b/bin/internal/fuchsia-linux.version @@ -1 +1 @@ -8RxhsfRJzzbTq9c5O0eUEUCQSZmMtmAFcNLCzp133UEC +itIdMnhgq-5ulecEDYO7eJFrAXE5IiUP6RWeQg-hMskC diff --git a/bin/internal/fuchsia-mac.version b/bin/internal/fuchsia-mac.version index 846835e31f36f..95b13efa0618f 100644 --- a/bin/internal/fuchsia-mac.version +++ b/bin/internal/fuchsia-mac.version @@ -1 +1 @@ --aNEAMFAx0_sTpar15sK4aL3nNDQ7NPNXInSiqKxRsIC +ako3u9MBgSSt-YtF6ckwXzkXPzInMmOVSw74WgYd5y8C diff --git a/dev/benchmarks/microbenchmarks/lib/foundation/clamp.dart b/dev/benchmarks/microbenchmarks/lib/foundation/clamp.dart new file mode 100644 index 0000000000000..1a933193ab19b --- /dev/null +++ b/dev/benchmarks/microbenchmarks/lib/foundation/clamp.dart @@ -0,0 +1,73 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart' show clampDouble; + +import '../common.dart'; + +const int _kBatchSize = 100000; +const int _kNumIterations = 1000; + +void main() { + assert(false, + "Don't run benchmarks in debug mode! Use 'flutter run --release'."); + final BenchmarkResultPrinter printer = BenchmarkResultPrinter(); + + final Stopwatch watch = Stopwatch(); + { + final List clampDoubleValues = []; + for (int j = 0; j < _kNumIterations; ++j) { + double tally = 0; + watch.reset(); + watch.start(); + for (int i = 0; i < _kBatchSize; i += 1) { + tally += clampDouble(-1.0, 0.0, 1.0); + tally += clampDouble(2.0, 0.0, 1.0); + tally += clampDouble(0.0, 0.0, 1.0); + tally += clampDouble(double.nan, 0.0, 1.0); + } + watch.stop(); + clampDoubleValues.add(watch.elapsedMicroseconds.toDouble() / _kBatchSize); + if (tally < 0.0) { + print("This shouldn't happen."); + } + } + + printer.addResultStatistics( + description: 'clamp - clampDouble', + values: clampDoubleValues, + unit: 'us per iteration', + name: 'clamp_clampDouble', + ); + } + + { + final List doubleClampValues = []; + + for (int j = 0; j < _kNumIterations; ++j) { + double tally = 0; + watch.reset(); + watch.start(); + for (int i = 0; i < _kBatchSize; i += 1) { + tally += -1.0.clamp(0.0, 1.0); + tally += 2.0.clamp(0.0, 1.0); + tally += 0.0.clamp(0.0, 1.0); + tally += double.nan.clamp(0.0, 1.0); + } + watch.stop(); + doubleClampValues.add(watch.elapsedMicroseconds.toDouble() / _kBatchSize); + if (tally < 0.0) { + print("This shouldn't happen."); + } + } + + printer.addResultStatistics( + description: 'clamp - Double.clamp', + values: doubleClampValues, + unit: 'us per iteration', + name: 'clamp_Double_clamp', + ); + } + printer.printToStdout(); +} diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart index 974d6bd91cbf1..0bef90aeda4db 100644 --- a/dev/bots/analyze.dart +++ b/dev/bots/analyze.dart @@ -88,6 +88,9 @@ Future run(List arguments) async { exitWithError(['The analyze.dart script must be run with --enable-asserts.']); } + print('$clock No Double.clamp'); + await verifyNoDoubleClamp(flutterRoot); + print('$clock All tool test files end in _test.dart...'); await verifyToolTestsEndInTestDart(flutterRoot); @@ -203,6 +206,80 @@ Future run(List arguments) async { // TESTS +FeatureSet _parsingFeatureSet() => FeatureSet.fromEnableFlags2( + sdkLanguageVersion: Version.parse('2.17.0-0'), + flags: ['super-parameters']); + +_Line _getLine(ParseStringResult parseResult, int offset) { + final int lineNumber = + parseResult.lineInfo.getLocation(offset).lineNumber; + final String content = parseResult.content.substring( + parseResult.lineInfo.getOffsetOfLine(lineNumber - 1), + parseResult.lineInfo.getOffsetOfLine(lineNumber) - 1); + return _Line(lineNumber, content); +} + +class _DoubleClampVisitor extends RecursiveAstVisitor { + _DoubleClampVisitor(this.parseResult); + + final List<_Line> clamps = <_Line>[]; + final ParseStringResult parseResult; + + @override + CompilationUnit? visitMethodInvocation(MethodInvocation node) { + if (node.methodName.name == 'clamp') { + final _Line line = _getLine(parseResult, node.function.offset); + if (!line.content.contains('// ignore_clamp_double_lint')) { + clamps.add(line); + } + } + + node.visitChildren(this); + return null; + } +} + +/// Verify that we use clampDouble instead of Double.clamp for performance reasons. +/// +/// We currently can't distinguish valid uses of clamp from problematic ones so +/// if the clamp is operating on a type other than a `double` the +/// `// ignore_clamp_double_lint` comment must be added to the line where clamp is +/// invoked. +/// +/// See also: +/// * https://github.com/flutter/flutter/pull/103559 +/// * https://github.com/flutter/flutter/issues/103917 +Future verifyNoDoubleClamp(String workingDirectory) async { + final String flutterLibPath = '$workingDirectory/packages/flutter/lib'; + final Stream testFiles = + _allFiles(flutterLibPath, 'dart', minimumMatches: 100); + final List errors = []; + await for (final File file in testFiles) { + try { + final ParseStringResult parseResult = parseFile( + featureSet: _parsingFeatureSet(), + path: file.absolute.path, + ); + final _DoubleClampVisitor visitor = _DoubleClampVisitor(parseResult); + visitor.visitCompilationUnit(parseResult.unit); + for (final _Line clamp in visitor.clamps) { + errors.add('${file.path}:${clamp.line}: `clamp` method used without ignore_clamp_double_lint comment.'); + } + } catch (ex) { + // TODO(gaaclarke): There is a bug with super parameter parsing on mac so + // we skip certain files until that is fixed. + // https://github.com/dart-lang/sdk/issues/49032 + print('skipping ${file.path}: $ex'); + } + } + if (errors.isNotEmpty) { + exitWithError([ + ...errors, + '\n${bold}See: https://github.com/flutter/flutter/pull/103559', + ]); + } +} + /// Verify tool test files end in `_test.dart`. /// /// The test runner will only recognize files ending in `_test.dart` as tests to @@ -518,16 +595,16 @@ Future _verifyNoMissingLicenseForExtension( return 0; } -class _TestSkip { - _TestSkip(this.line, this.content); +class _Line { + _Line(this.line, this.content); final int line; final String content; } -Iterable<_TestSkip> _getTestSkips(File file) { +Iterable<_Line> _getTestSkips(File file) { final ParseStringResult parseResult = parseFile( - featureSet: FeatureSet.fromEnableFlags2(sdkLanguageVersion: Version.parse('2.17.0-0'), flags: ['super-parameters']), + featureSet: _parsingFeatureSet(), path: file.absolute.path, ); final _TestSkipLinesVisitor visitor = _TestSkipLinesVisitor(parseResult); @@ -536,10 +613,10 @@ Iterable<_TestSkip> _getTestSkips(File file) { } class _TestSkipLinesVisitor extends RecursiveAstVisitor { - _TestSkipLinesVisitor(this.parseResult) : skips = <_TestSkip>{}; + _TestSkipLinesVisitor(this.parseResult) : skips = <_Line>{}; final ParseStringResult parseResult; - final Set<_TestSkip> skips; + final Set<_Line> skips; static bool isTestMethod(String name) { return name.startsWith('test') || name == 'group' || name == 'expect'; @@ -550,10 +627,7 @@ class _TestSkipLinesVisitor extends RecursiveAstVisitor { if (isTestMethod(node.methodName.toString())) { for (final Expression argument in node.argumentList.arguments) { if (argument is NamedExpression && argument.name.label.name == 'skip') { - final int lineNumber = parseResult.lineInfo.getLocation(argument.beginToken.charOffset).lineNumber; - final String content = parseResult.content.substring(parseResult.lineInfo.getOffsetOfLine(lineNumber - 1), - parseResult.lineInfo.getOffsetOfLine(lineNumber) - 1); - skips.add(_TestSkip(lineNumber, content)); + skips.add(_getLine(parseResult, argument.beginToken.charOffset)); } } } @@ -571,7 +645,7 @@ Future verifySkipTestComments(String workingDirectory) async { .where((File f) => f.path.endsWith('_test.dart')); await for (final File file in testFiles) { - for (final _TestSkip skip in _getTestSkips(file)) { + for (final _Line skip in _getTestSkips(file)) { final Match? match = _skipTestCommentPattern.firstMatch(skip.content); final String? skipComment = match?.group(1); if (skipComment == null || diff --git a/dev/bots/docs.sh b/dev/bots/docs.sh index edafc308b3948..7836ff186aa64 100755 --- a/dev/bots/docs.sh +++ b/dev/bots/docs.sh @@ -20,7 +20,7 @@ function generate_docs() { # Install and activate dartdoc. # NOTE: When updating to a new dartdoc version, please also update # `dartdoc_options.yaml` to include newly introduced error and warning types. - "$DART" pub global activate dartdoc 5.0.1 + "$DART" pub global activate dartdoc 5.1.2 # Install and activate the snippets tool, which resides in the # assets-for-api-docs repo: diff --git a/dev/devicelab/bin/tasks/native_ui_tests_ios.dart b/dev/devicelab/bin/tasks/native_ui_tests_ios.dart deleted file mode 100644 index 68bc76f3eff24..0000000000000 --- a/dev/devicelab/bin/tasks/native_ui_tests_ios.dart +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter_devicelab/framework/devices.dart'; -import 'package:flutter_devicelab/framework/framework.dart'; -import 'package:flutter_devicelab/framework/ios.dart'; -import 'package:flutter_devicelab/framework/task_result.dart'; -import 'package:flutter_devicelab/framework/utils.dart'; -import 'package:path/path.dart' as path; - -Future main() async { - deviceOperatingSystem = DeviceOperatingSystem.ios; - - await task(() async { - final String projectDirectory = '${flutterDirectory.path}/dev/integration_tests/flutter_gallery'; - - await inDirectory(projectDirectory, () async { - section('Build clean'); - - await flutter('clean'); - - section('Build gallery app'); - - await flutter( - 'build', - options: [ - 'ios', - '-v', - '--release', - '--config-only', - ], - ); - }); - - section('Run platform unit tests'); - - final Device device = await devices.workingDevice; - if (!await runXcodeTests( - platformDirectory: path.join(projectDirectory, 'ios'), - destination: 'id=${device.deviceId}', - testName: 'native_ui_tests_ios', - )) { - return TaskResult.failure('Platform unit tests failed'); - } - - return TaskResult.success(null); - }); -} diff --git a/dev/devicelab/bin/tasks/new_gallery_impeller__transition_perf.dart b/dev/devicelab/bin/tasks/new_gallery_impeller__transition_perf.dart new file mode 100644 index 0000000000000..3ddb1555a3356 --- /dev/null +++ b/dev/devicelab/bin/tasks/new_gallery_impeller__transition_perf.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. + +import 'dart:io'; + +import 'package:flutter_devicelab/framework/devices.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/framework/utils.dart'; +import 'package:flutter_devicelab/tasks/new_gallery.dart'; +import 'package:path/path.dart' as path; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + + final Directory galleryParentDir = Directory.systemTemp.createTempSync('flutter_new_gallery_test.'); + final Directory galleryDir = Directory(path.join(galleryParentDir.path, 'gallery')); + + try { + await task(NewGalleryPerfTest(galleryDir, enableImpeller: true).run); + } finally { + rmTree(galleryParentDir); + } +} diff --git a/dev/devicelab/lib/tasks/microbenchmarks.dart b/dev/devicelab/lib/tasks/microbenchmarks.dart index 8077bbae18cf5..af83d94f7e3d8 100644 --- a/dev/devicelab/lib/tasks/microbenchmarks.dart +++ b/dev/devicelab/lib/tasks/microbenchmarks.dart @@ -60,6 +60,7 @@ TaskFunction createMicrobenchmarkTask() { ...await runMicrobench('lib/language/sync_star_bench.dart'), ...await runMicrobench('lib/language/sync_star_semantics_bench.dart'), ...await runMicrobench('lib/foundation/all_elements_bench.dart'), + ...await runMicrobench('lib/foundation/clamp.dart'), ...await runMicrobench('lib/foundation/change_notifier_bench.dart'), ...await runMicrobench('lib/foundation/standard_method_codec_bench.dart'), ...await runMicrobench('lib/foundation/standard_message_codec_bench.dart'), diff --git a/dev/integration_tests/flutter_gallery/ios/GalleryUITests/GalleryUITests.m b/dev/integration_tests/flutter_gallery/ios/GalleryUITests/GalleryUITests.m deleted file mode 100644 index 92dde33f295d4..0000000000000 --- a/dev/integration_tests/flutter_gallery/ios/GalleryUITests/GalleryUITests.m +++ /dev/null @@ -1,26 +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 - -@interface GalleryUITests : XCTestCase -@property (strong) XCUIApplication *app; -@end - -@implementation GalleryUITests - -- (void)setUp { - self.continueAfterFailure = NO; - - XCUIApplication *app = [[XCUIApplication alloc] init]; - [app launch]; - self.app = app; -} - -- (void)testLaunch { - // Basic smoke test that the app launched and any element was loaded. - XCTAssertTrue([self.app.otherElements.firstMatch waitForExistenceWithTimeout:60.0]); -} - -@end diff --git a/dev/integration_tests/flutter_gallery/ios/GalleryUITests/Info.plist b/dev/integration_tests/flutter_gallery/ios/GalleryUITests/Info.plist deleted file mode 100644 index 64d65ca495770..0000000000000 --- a/dev/integration_tests/flutter_gallery/ios/GalleryUITests/Info.plist +++ /dev/null @@ -1,22 +0,0 @@ - - - - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/project.pbxproj b/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/project.pbxproj index 5facc21e7b3f4..0437c23e8e8c4 100644 --- a/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/project.pbxproj +++ b/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 46; objects = { /* Begin PBXBuildFile section */ @@ -15,19 +15,8 @@ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; AA1F617779EAA28FB12EC66E /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C77CA0BBC4B57129484236F4 /* libPods-Runner.a */; }; - F7C2268825DCA70C00389C82 /* GalleryUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7C2268725DCA70C00389C82 /* GalleryUITests.m */; }; /* End PBXBuildFile section */ -/* Begin PBXContainerItemProxy section */ - F7C2268A25DCA70C00389C82 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 97C146E61CF9000F007C117D /* Project object */; - proxyType = 1; - remoteGlobalIDString = 97C146ED1CF9000F007C117D; - remoteInfo = Runner; - }; -/* End PBXContainerItemProxy section */ - /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -60,9 +49,6 @@ C77CA0BBC4B57129484236F4 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; DC01738FBE39EADD5AC4BF42 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; ECF490DDAB8ABCEEFB1780BE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - F7C2268525DCA70C00389C82 /* GalleryUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GalleryUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - F7C2268725DCA70C00389C82 /* GalleryUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GalleryUITests.m; sourceTree = ""; }; - F7C2268925DCA70C00389C82 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -74,13 +60,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F7C2268225DCA70C00389C82 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -100,7 +79,6 @@ children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, - F7C2268625DCA70C00389C82 /* GalleryUITests */, 97C146EF1CF9000F007C117D /* Products */, CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, E54E8B7296D73DD9B2385312 /* Pods */, @@ -111,7 +89,6 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Flutter Gallery.app */, - F7C2268525DCA70C00389C82 /* GalleryUITests.xctest */, ); name = Products; sourceTree = ""; @@ -158,15 +135,6 @@ name = Pods; sourceTree = ""; }; - F7C2268625DCA70C00389C82 /* GalleryUITests */ = { - isa = PBXGroup; - children = ( - F7C2268725DCA70C00389C82 /* GalleryUITests.m */, - F7C2268925DCA70C00389C82 /* Info.plist */, - ); - path = GalleryUITests; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -191,24 +159,6 @@ productReference = 97C146EE1CF9000F007C117D /* Flutter Gallery.app */; productType = "com.apple.product-type.application"; }; - F7C2268425DCA70C00389C82 /* GalleryUITests */ = { - isa = PBXNativeTarget; - buildConfigurationList = F7C2268F25DCA70C00389C82 /* Build configuration list for PBXNativeTarget "GalleryUITests" */; - buildPhases = ( - F7C2268125DCA70C00389C82 /* Sources */, - F7C2268225DCA70C00389C82 /* Frameworks */, - F7C2268325DCA70C00389C82 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - F7C2268B25DCA70C00389C82 /* PBXTargetDependency */, - ); - name = GalleryUITests; - productName = GalleryUITests; - productReference = F7C2268525DCA70C00389C82 /* GalleryUITests.xctest */; - productType = "com.apple.product-type.bundle.ui-testing"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -221,11 +171,6 @@ 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; - F7C2268425DCA70C00389C82 = { - CreatedOnToolsVersion = 12.4; - ProvisioningStyle = Automatic; - TestTargetID = 97C146ED1CF9000F007C117D; - }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; @@ -242,7 +187,6 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, - F7C2268425DCA70C00389C82 /* GalleryUITests */, ); }; /* End PBXProject section */ @@ -259,13 +203,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F7C2268325DCA70C00389C82 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ @@ -313,7 +250,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -328,24 +265,8 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - F7C2268125DCA70C00389C82 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - F7C2268825DCA70C00389C82 /* GalleryUITests.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ -/* Begin PBXTargetDependency section */ - F7C2268B25DCA70C00389C82 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 97C146ED1CF9000F007C117D /* Runner */; - targetProxy = F7C2268A25DCA70C00389C82 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -558,45 +479,6 @@ }; name = Release; }; - F7C2268C25DCA70C00389C82 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - INFOPLIST_FILE = GalleryUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.flutterio.GalleryUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_TARGET_NAME = Runner; - }; - name = Debug; - }; - F7C2268D25DCA70C00389C82 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - INFOPLIST_FILE = GalleryUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.flutterio.GalleryUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_TARGET_NAME = Runner; - }; - name = Release; - }; - F7C2268E25DCA70C00389C82 /* Profile */ = { - isa = XCBuildConfiguration; - buildSettings = { - INFOPLIST_FILE = GalleryUITests/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MTL_FAST_MATH = YES; - PRODUCT_BUNDLE_IDENTIFIER = com.flutterio.GalleryUITests; - PRODUCT_NAME = "$(TARGET_NAME)"; - TEST_TARGET_NAME = Runner; - }; - name = Profile; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -620,16 +502,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F7C2268F25DCA70C00389C82 /* Build configuration list for PBXNativeTarget "GalleryUITests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - F7C2268C25DCA70C00389C82 /* Debug */, - F7C2268D25DCA70C00389C82 /* Release */, - F7C2268E25DCA70C00389C82 /* Profile */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; diff --git a/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c27e9dc99aea2..e7b2d8764c65f 100644 --- a/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/dev/integration_tests/flutter_gallery/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -37,16 +37,6 @@ - - - - runApp(const MyApp()); +void main() => runApp(const AppBarApp()); -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - static const String _title = 'Flutter Code Sample'; +class AppBarApp extends StatelessWidget { + const AppBarApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( - title: _title, - home: MyStatelessWidget(), + home: AppBarExample(), ); } } -class MyStatelessWidget extends StatelessWidget { - const MyStatelessWidget({super.key}); +class AppBarExample extends StatelessWidget { + const AppBarExample({super.key}); @override Widget build(BuildContext context) { diff --git a/examples/api/lib/material/app_bar/app_bar.1.dart b/examples/api/lib/material/app_bar/app_bar.1.dart index f94b07875b05f..925df09cfb7f2 100644 --- a/examples/api/lib/material/app_bar/app_bar.1.dart +++ b/examples/api/lib/material/app_bar/app_bar.1.dart @@ -6,43 +6,120 @@ import 'package:flutter/material.dart'; -void main() => runApp(const MyApp()); +final List _items = List.generate(51, (int index) => index); -class MyApp extends StatelessWidget { - const MyApp({super.key}); +void main() => runApp(const AppBarApp()); - static const String _title = 'Flutter Code Sample'; +class AppBarApp extends StatelessWidget { + const AppBarApp({super.key}); @override Widget build(BuildContext context) { - return const MaterialApp( - title: _title, - home: MyStatelessWidget(), + return MaterialApp( + theme: ThemeData( + colorSchemeSeed: const Color(0xff6750a4), + useMaterial3: true, + ), + home: const AppBarExample(), ); } } -class MyStatelessWidget extends StatelessWidget { - const MyStatelessWidget({super.key}); +class AppBarExample extends StatefulWidget { + const AppBarExample({super.key}); + + @override + State createState() => _AppBarExampleState(); +} + +class _AppBarExampleState extends State { + bool shadowColor = false; + double? scrolledUnderElevation; @override Widget build(BuildContext context) { - final ButtonStyle style = - TextButton.styleFrom(primary: Theme.of(context).colorScheme.onPrimary); + final ColorScheme colorScheme = Theme.of(context).colorScheme; + final Color oddItemColor = colorScheme.primary.withOpacity(0.05); + final Color evenItemColor = colorScheme.primary.withOpacity(0.15); + return Scaffold( appBar: AppBar( - actions: [ - TextButton( - style: style, - onPressed: () {}, - child: const Text('Action 1'), - ), - TextButton( - style: style, - onPressed: () {}, - child: const Text('Action 2'), + title: const Text('AppBar Demo'), + scrolledUnderElevation: scrolledUnderElevation, + shadowColor: shadowColor ? Theme.of(context).colorScheme.shadow : null, + ), + body: GridView.builder( + shrinkWrap: true, + itemCount: _items.length, + padding: const EdgeInsets.all(8.0), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + childAspectRatio: 2.0, + mainAxisSpacing: 10.0, + crossAxisSpacing: 10.0, + ), + itemBuilder: (BuildContext context, int index) { + if (index == 0) { + return Center( + child: Text( + 'Scroll to see the Appbar in effect.', + style: Theme.of(context).textTheme.labelLarge, + textAlign: TextAlign.center, + ), + ); + } + return Container( + alignment: Alignment.center, + // tileColor: _items[index].isOdd ? oddItemColor : evenItemColor, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.0), + color: _items[index].isOdd ? oddItemColor : evenItemColor, + ), + child: Text('Item $index'), + ); + }, + ), + bottomNavigationBar: BottomAppBar( + child: Padding( + padding: const EdgeInsets.all(8), + child: OverflowBar( + overflowAlignment: OverflowBarAlignment.center, + alignment: MainAxisAlignment.center, + overflowSpacing: 5.0, + children: [ + ElevatedButton.icon( + onPressed: () { + setState(() { + shadowColor = !shadowColor; + }); + }, + icon: Icon( + shadowColor ? Icons.visibility_off : Icons.visibility, + ), + label: const Text('shadow color'), + ), + const SizedBox(width: 5), + ElevatedButton.icon( + onPressed: () { + if (scrolledUnderElevation == null) { + setState(() { + // Default elevation is 3.0, increment by 1.0. + scrolledUnderElevation = 4.0; + }); + } else { + setState(() { + scrolledUnderElevation = scrolledUnderElevation! + 1.0; + }); + } + }, + icon: const Icon(Icons.add), + label: Text( + 'scrolledUnderElevation: ${scrolledUnderElevation ?? 'default'}', + ), + ), + ], ), - ], + ), ), ); } diff --git a/examples/api/lib/material/app_bar/app_bar.2.dart b/examples/api/lib/material/app_bar/app_bar.2.dart new file mode 100644 index 0000000000000..58a4f236cf60f --- /dev/null +++ b/examples/api/lib/material/app_bar/app_bar.2.dart @@ -0,0 +1,47 @@ +// 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. + +// Flutter code sample for AppBar + +import 'package:flutter/material.dart'; + +void main() => runApp(const AppBarApp()); + +class AppBarApp extends StatelessWidget { + const AppBarApp({super.key}); + + @override + Widget build(BuildContext context) { + return const MaterialApp( + home: AppBarExample(), + ); + } +} + +class AppBarExample extends StatelessWidget { + const AppBarExample({super.key}); + + @override + Widget build(BuildContext context) { + final ButtonStyle style = TextButton.styleFrom( + primary: Theme.of(context).colorScheme.onPrimary, + ); + return Scaffold( + appBar: AppBar( + actions: [ + TextButton( + style: style, + onPressed: () {}, + child: const Text('Action 1'), + ), + TextButton( + style: style, + onPressed: () {}, + child: const Text('Action 2'), + ), + ], + ), + ); + } +} diff --git a/examples/api/lib/material/app_bar/sliver_app_bar.1.dart b/examples/api/lib/material/app_bar/sliver_app_bar.1.dart index 72895d256a215..8859c4512e068 100644 --- a/examples/api/lib/material/app_bar/sliver_app_bar.1.dart +++ b/examples/api/lib/material/app_bar/sliver_app_bar.1.dart @@ -6,30 +6,27 @@ import 'package:flutter/material.dart'; -void main() => runApp(const MyApp()); +void main() => runApp(const AppBarApp()); -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - static const String _title = 'Flutter Code Sample'; +class AppBarApp extends StatelessWidget { + const AppBarApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( - title: _title, - home: MyStatefulWidget(), + home: SliverAppBarExample(), ); } } -class MyStatefulWidget extends StatefulWidget { - const MyStatefulWidget({super.key}); +class SliverAppBarExample extends StatefulWidget { + const SliverAppBarExample({super.key}); @override - State createState() => _MyStatefulWidgetState(); + State createState() => _SliverAppBarExampleState(); } -class _MyStatefulWidgetState extends State { +class _SliverAppBarExampleState extends State { bool _pinned = true; bool _snap = false; bool _floating = false; diff --git a/examples/api/test/material/appbar/app_bar.0_test.dart b/examples/api/test/material/appbar/app_bar.0_test.dart new file mode 100644 index 0000000000000..b131b297f7cbf --- /dev/null +++ b/examples/api/test/material/appbar/app_bar.0_test.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. + +import 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/app_bar/app_bar.0.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Appbar updates on navigation', (WidgetTester tester) async { + await tester.pumpWidget( + const example.AppBarApp(), + ); + + expect(find.widgetWithText(AppBar, 'AppBar Demo'), findsOneWidget); + expect(find.text('This is the home page'), findsOneWidget); + + await tester.tap(find.byIcon(Icons.navigate_next)); + await tester.pumpAndSettle(); + + expect(find.widgetWithText(AppBar, 'Next page'), findsOneWidget); + expect(find.text('This is the next page'), findsOneWidget); + }); +} diff --git a/examples/api/test/material/appbar/app_bar.1_test.dart b/examples/api/test/material/appbar/app_bar.1_test.dart new file mode 100644 index 0000000000000..2713cf41bffb7 --- /dev/null +++ b/examples/api/test/material/appbar/app_bar.1_test.dart @@ -0,0 +1,55 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/app_bar/app_bar.1.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +const Offset _kOffset = Offset(0.0, -100.0); + +void main() { + testWidgets('Appbar Material 3 test', (WidgetTester tester) async { + await tester.pumpWidget( + const example.AppBarApp() + ); + + expect(find.widgetWithText(AppBar, 'AppBar Demo'), findsOneWidget); + Material appbarMaterial = _getAppBarMaterial(tester); + expect(appbarMaterial.shadowColor, null); + expect(appbarMaterial.elevation, 0); + + await tester.drag(find.text('Item 4'), _kOffset, touchSlopY: 0, warnIfMissed: false); + + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + + await tester.tap(find.text('shadow color')); + await tester.pumpAndSettle(); + appbarMaterial = _getAppBarMaterial(tester); + expect(appbarMaterial.shadowColor, Colors.black); + expect(appbarMaterial.elevation, 3.0); + + await tester.tap(find.text('scrolledUnderElevation: default')); + await tester.pumpAndSettle(); + + appbarMaterial = _getAppBarMaterial(tester); + expect(appbarMaterial.shadowColor, Colors.black); + expect(appbarMaterial.elevation, 4.0); + + await tester.tap(find.text('scrolledUnderElevation: 4.0')); + await tester.pumpAndSettle(); + appbarMaterial = _getAppBarMaterial(tester); + expect(appbarMaterial.shadowColor, Colors.black); + expect(appbarMaterial.elevation, 5.0); + }); +} + +Material _getAppBarMaterial(WidgetTester tester) { + return tester.widget( + find.descendant( + of: find.byType(AppBar), + matching: find.byType(Material), + ), + ); +} diff --git a/examples/api/test/material/appbar/app_bar.2_test.dart b/examples/api/test/material/appbar/app_bar.2_test.dart new file mode 100644 index 0000000000000..b064e95435c44 --- /dev/null +++ b/examples/api/test/material/appbar/app_bar.2_test.dart @@ -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 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/app_bar/app_bar.2.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Appbar and actions', (WidgetTester tester) async { + await tester.pumpWidget( + const example.AppBarApp(), + ); + + expect(find.byType(AppBar), findsOneWidget); + expect(find.widgetWithText(TextButton, 'Action 1'), findsOneWidget); + expect(find.widgetWithText(TextButton, 'Action 2'), findsOneWidget); + }); +} diff --git a/examples/api/test/material/appbar/sliver_app_bar.1_test.dart b/examples/api/test/material/appbar/sliver_app_bar.1_test.dart new file mode 100644 index 0000000000000..5a7cb784a8b7e --- /dev/null +++ b/examples/api/test/material/appbar/sliver_app_bar.1_test.dart @@ -0,0 +1,25 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/app_bar/sliver_app_bar.1.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +const Offset _kOffset = Offset(0.0, -200.0); + +void main() { + testWidgets('SliverAppbar can be pinned', (WidgetTester tester) async { + await tester.pumpWidget( + const example.AppBarApp(), + ); + + expect(find.widgetWithText(SliverAppBar, 'SliverAppBar'), findsOneWidget); + expect(tester.getBottomLeft(find.text('SliverAppBar')).dy, 144.0); + + await tester.drag(find.text('0'), _kOffset, touchSlopY: 0, warnIfMissed: false); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 500)); + expect(tester.getBottomLeft(find.text('SliverAppBar')).dy, 40.0); + }); +} diff --git a/packages/flutter/lib/foundation.dart b/packages/flutter/lib/foundation.dart index c01ccaaf38992..56e6064f8fe8c 100644 --- a/packages/flutter/lib/foundation.dart +++ b/packages/flutter/lib/foundation.dart @@ -33,6 +33,7 @@ export 'src/foundation/diagnostics.dart'; export 'src/foundation/isolates.dart'; export 'src/foundation/key.dart'; export 'src/foundation/licenses.dart'; +export 'src/foundation/math.dart'; export 'src/foundation/node.dart'; export 'src/foundation/object.dart'; export 'src/foundation/observer_list.dart'; diff --git a/packages/flutter/lib/src/animation/animation_controller.dart b/packages/flutter/lib/src/animation/animation_controller.dart index 0bd969444c1e7..d4161b3f9d863 100644 --- a/packages/flutter/lib/src/animation/animation_controller.dart +++ b/packages/flutter/lib/src/animation/animation_controller.dart @@ -395,7 +395,7 @@ class AnimationController extends Animation } void _internalSetValue(double newValue) { - _value = newValue.clamp(lowerBound, upperBound); + _value = clampDouble(newValue, lowerBound, upperBound); if (_value == lowerBound) { _status = AnimationStatus.dismissed; } else if (_value == upperBound) { @@ -598,7 +598,7 @@ class AnimationController extends Animation stop(); if (simulationDuration == Duration.zero) { if (value != target) { - _value = target.clamp(lowerBound, upperBound); + _value = clampDouble(target, lowerBound, upperBound); notifyListeners(); } _status = (_direction == _AnimationDirection.forward) ? @@ -741,7 +741,7 @@ class AnimationController extends Animation assert(!isAnimating); _simulation = simulation; _lastElapsedDuration = Duration.zero; - _value = simulation.x(0.0).clamp(lowerBound, upperBound); + _value = clampDouble(simulation.x(0.0), lowerBound, upperBound); final TickerFuture result = _ticker!.start(); _status = (_direction == _AnimationDirection.forward) ? AnimationStatus.forward : @@ -820,7 +820,7 @@ class AnimationController extends Animation _lastElapsedDuration = elapsed; final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond; assert(elapsedInSeconds >= 0.0); - _value = _simulation!.x(elapsedInSeconds).clamp(lowerBound, upperBound); + _value = clampDouble(_simulation!.x(elapsedInSeconds), lowerBound, upperBound); if (_simulation!.isDone(elapsedInSeconds)) { _status = (_direction == _AnimationDirection.forward) ? AnimationStatus.completed : @@ -855,7 +855,7 @@ class _InterpolationSimulation extends Simulation { @override double x(double timeInSeconds) { - final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0); + final double t = clampDouble(timeInSeconds / _durationInSeconds, 0.0, 1.0); if (t == 0.0) return _begin; else if (t == 1.0) diff --git a/packages/flutter/lib/src/animation/curves.dart b/packages/flutter/lib/src/animation/curves.dart index c9eb57ce50f88..46ad736a918d3 100644 --- a/packages/flutter/lib/src/animation/curves.dart +++ b/packages/flutter/lib/src/animation/curves.dart @@ -182,7 +182,7 @@ class Interval extends Curve { assert(end >= 0.0); assert(end <= 1.0); assert(end >= begin); - t = ((t - begin) / (end - begin)).clamp(0.0, 1.0); + t = clampDouble((t - begin) / (end - begin), 0.0, 1.0); if (t == 0.0 || t == 1.0) return t; return curve.transform(t); diff --git a/packages/flutter/lib/src/cupertino/context_menu.dart b/packages/flutter/lib/src/cupertino/context_menu.dart index d9e640b229816..f74ab0b46429a 100644 --- a/packages/flutter/lib/src/cupertino/context_menu.dart +++ b/packages/flutter/lib/src/cupertino/context_menu.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; import 'dart:ui' as ui; + import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart' show kMinFlingVelocity, kLongPressTimeout; import 'package:flutter/scheduler.dart'; @@ -958,7 +959,7 @@ class _ContextMenuRouteStaticState extends State<_ContextMenuRouteStatic> with T _moveAnimation = Tween( begin: Offset.zero, end: Offset( - endX.clamp(-_kPadding, _kPadding), + clampDouble(endX, -_kPadding, _kPadding), endY, ), ).animate( diff --git a/packages/flutter/lib/src/cupertino/desktop_text_selection.dart b/packages/flutter/lib/src/cupertino/desktop_text_selection.dart index c7e5dcf0ec7a5..037c0b059cb47 100644 --- a/packages/flutter/lib/src/cupertino/desktop_text_selection.dart +++ b/packages/flutter/lib/src/cupertino/desktop_text_selection.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -157,7 +158,7 @@ class _CupertinoDesktopTextSelectionControlsToolbarState extends State<_Cupertin final MediaQueryData mediaQuery = MediaQuery.of(context); final Offset midpointAnchor = Offset( - (widget.selectionMidpoint.dx - widget.globalEditableRegion.left).clamp( + clampDouble(widget.selectionMidpoint.dx - widget.globalEditableRegion.left, mediaQuery.padding.left, mediaQuery.size.width - mediaQuery.padding.right, ), diff --git a/packages/flutter/lib/src/cupertino/refresh.dart b/packages/flutter/lib/src/cupertino/refresh.dart index b99debd8faf65..d106165c4cca2 100644 --- a/packages/flutter/lib/src/cupertino/refresh.dart +++ b/packages/flutter/lib/src/cupertino/refresh.dart @@ -4,6 +4,7 @@ import 'dart:math'; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; @@ -375,7 +376,7 @@ class CupertinoSliverRefreshControl extends StatefulWidget { double refreshTriggerPullDistance, double refreshIndicatorExtent, ) { - final double percentageComplete = (pulledExtent / refreshTriggerPullDistance).clamp(0.0, 1.0); + final double percentageComplete = clampDouble(pulledExtent / refreshTriggerPullDistance, 0.0, 1.0); // Place the indicator at the top of the sliver that opens up. Note that we're using // a Stack/Positioned widget because the CupertinoActivityIndicator does some internal diff --git a/packages/flutter/lib/src/cupertino/slider.dart b/packages/flutter/lib/src/cupertino/slider.dart index 420cca40febb1..b86c282a3e6b3 100644 --- a/packages/flutter/lib/src/cupertino/slider.dart +++ b/packages/flutter/lib/src/cupertino/slider.dart @@ -5,6 +5,7 @@ import 'dart:math' as math; import 'dart:ui' show lerpDouble; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -434,7 +435,7 @@ class _RenderCupertinoSlider extends RenderConstrainedBox { double _currentDragValue = 0.0; double get _discretizedCurrentDragValue { - double dragValue = _currentDragValue.clamp(0.0, 1.0); + double dragValue = clampDouble(_currentDragValue, 0.0, 1.0); if (divisions != null) dragValue = (dragValue * divisions!).round() / divisions!; return dragValue; @@ -554,8 +555,8 @@ class _RenderCupertinoSlider extends RenderConstrainedBox { config.onIncrease = _increaseAction; config.onDecrease = _decreaseAction; config.value = '${(value * 100).round()}%'; - config.increasedValue = '${((value + _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%'; - config.decreasedValue = '${((value - _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%'; + config.increasedValue = '${(clampDouble(value + _semanticActionUnit, 0.0, 1.0) * 100).round()}%'; + config.decreasedValue = '${(clampDouble(value - _semanticActionUnit, 0.0, 1.0) * 100).round()}%'; } } @@ -563,11 +564,11 @@ class _RenderCupertinoSlider extends RenderConstrainedBox { void _increaseAction() { if (isInteractive) - onChanged!((value + _semanticActionUnit).clamp(0.0, 1.0)); + onChanged!(clampDouble(value + _semanticActionUnit, 0.0, 1.0)); } void _decreaseAction() { if (isInteractive) - onChanged!((value - _semanticActionUnit).clamp(0.0, 1.0)); + onChanged!(clampDouble(value - _semanticActionUnit, 0.0, 1.0)); } } diff --git a/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart b/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart index fd59719f73121..d242e474cdb5d 100644 --- a/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart +++ b/packages/flutter/lib/src/cupertino/sliding_segmented_control.dart @@ -494,7 +494,7 @@ class _SegmentedControlState extends State= 2); - int index = (dx ~/ (renderBox.size.width / numOfChildren)).clamp(0, numOfChildren - 1); + int index = (dx ~/ (renderBox.size.width / numOfChildren)).clamp(0, numOfChildren - 1); // ignore_clamp_double_lint switch (Directionality.of(context)) { case TextDirection.ltr: diff --git a/packages/flutter/lib/src/cupertino/text_selection.dart b/packages/flutter/lib/src/cupertino/text_selection.dart index 3ea2e010069ee..662496d6064ca 100644 --- a/packages/flutter/lib/src/cupertino/text_selection.dart +++ b/packages/flutter/lib/src/cupertino/text_selection.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -91,7 +92,7 @@ class _CupertinoTextSelectionControlsToolbarState extends State<_CupertinoTextSe // The toolbar should appear below the TextField when there is not enough // space above the TextField to show it, assuming there's always enough // space at the bottom in this case. - final double anchorX = (widget.selectionMidpoint.dx + widget.globalEditableRegion.left).clamp( + final double anchorX = clampDouble(widget.selectionMidpoint.dx + widget.globalEditableRegion.left, _kArrowScreenPadding + mediaQuery.padding.left, mediaQuery.size.width - mediaQuery.padding.right - _kArrowScreenPadding, ); diff --git a/packages/flutter/lib/src/foundation/diagnostics.dart b/packages/flutter/lib/src/foundation/diagnostics.dart index dc77398c84b60..308e2b341976a 100644 --- a/packages/flutter/lib/src/foundation/diagnostics.dart +++ b/packages/flutter/lib/src/foundation/diagnostics.dart @@ -9,6 +9,7 @@ import 'package:meta/meta.dart'; import 'assertions.dart'; import 'constants.dart'; import 'debug.dart'; +import 'math.dart' show clampDouble; import 'object.dart'; // Examples can assume: @@ -2044,7 +2045,7 @@ class PercentProperty extends DoubleProperty { final double? v = value; if (v == null) return value.toString(); - return '${(v.clamp(0.0, 1.0) * 100.0).toStringAsFixed(1)}%'; + return '${(clampDouble(v, 0.0, 1.0) * 100.0).toStringAsFixed(1)}%'; } } diff --git a/packages/flutter/lib/src/foundation/math.dart b/packages/flutter/lib/src/foundation/math.dart new file mode 100644 index 0000000000000..053192ad506eb --- /dev/null +++ b/packages/flutter/lib/src/foundation/math.dart @@ -0,0 +1,23 @@ +// 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. + +/// Same as [num.clamp] but optimized for non-null [double]. +/// +/// This is faster because it avoids polymorphism, boxing, and special cases for +/// floating point numbers. +// +// See also: //dev/benchmarks/microbenchmarks/lib/foundation/clamp.dart +double clampDouble(double x, double min, double max) { + assert(min <= max && !max.isNaN && !min.isNaN); + if (x < min) { + return min; + } + if (x > max) { + return max; + } + if (x.isNaN) { + return max; + } + return x; +} diff --git a/packages/flutter/lib/src/gestures/force_press.dart b/packages/flutter/lib/src/gestures/force_press.dart index e0c83b099766a..a3bea2729b674 100644 --- a/packages/flutter/lib/src/gestures/force_press.dart +++ b/packages/flutter/lib/src/gestures/force_press.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart' show clampDouble; import 'arena.dart'; import 'events.dart'; import 'recognizer.dart'; @@ -331,7 +332,7 @@ class ForcePressGestureRecognizer extends OneSequenceGestureRecognizer { // If the device incorrectly reports a pressure outside of pressureMin // and pressureMax, we still want this recognizer to respond normally. if (!value.isNaN) - value = value.clamp(0.0, 1.0); + value = clampDouble(value, 0.0, 1.0); return value; } diff --git a/packages/flutter/lib/src/material/animated_icons.dart b/packages/flutter/lib/src/material/animated_icons.dart index b38da78160f4d..2f3c0ae5e13e5 100644 --- a/packages/flutter/lib/src/material/animated_icons.dart +++ b/packages/flutter/lib/src/material/animated_icons.dart @@ -9,6 +9,7 @@ import 'dart:math' as math show pi; import 'dart:ui' as ui show Paint, Path, Canvas; import 'dart:ui' show lerpDouble; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; // This package is split into multiple parts to enable a private API that is diff --git a/packages/flutter/lib/src/material/animated_icons/animated_icons.dart b/packages/flutter/lib/src/material/animated_icons/animated_icons.dart index a0afbe8078bde..272cc75c8d0b0 100644 --- a/packages/flutter/lib/src/material/animated_icons/animated_icons.dart +++ b/packages/flutter/lib/src/material/animated_icons/animated_icons.dart @@ -161,7 +161,7 @@ class _AnimatedIconPainter extends CustomPainter { } canvas.scale(scale, scale); - final double clampedProgress = progress.value.clamp(0.0, 1.0); + final double clampedProgress = clampDouble(progress.value, 0.0, 1.0); for (final _PathFrames path in paths) path.paint(canvas, color, uiPathFactory, clampedProgress); } diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index 4ae16d682748c..06f97af3255dc 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -107,6 +107,15 @@ class _PreferredAppBarSize extends Size { /// ** See code in examples/api/lib/material/app_bar/app_bar.0.dart ** /// {@end-tool} /// +/// Material Design 3 introduced new types of app bar. +/// {@tool dartpad} +/// This sample shows the creation of an [AppBar] widget with the [shadowColor] and +/// [scrolledUnderElevation] properties set, as described in: +/// https://m3.material.io/components/top-app-bar/overview +/// +/// ** See code in examples/api/lib/material/app_bar/app_bar.1.dart ** +/// {@end-tool} +/// /// ## Troubleshooting /// /// ### Why don't my TextButton actions appear? @@ -125,9 +134,10 @@ class _PreferredAppBarSize extends Size { /// [TextButton.style]: /// /// {@tool dartpad} +/// This sample shows an [AppBar] with two action buttons with their primary +/// color set to [ColorScheme.onPrimary]. /// -/// -/// ** See code in examples/api/lib/material/app_bar/app_bar.1.dart ** +/// ** See code in examples/api/lib/material/app_bar/app_bar.2.dart ** /// {@end-tool} /// /// See also: @@ -162,6 +172,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget { this.bottom, this.elevation, this.scrolledUnderElevation, + this.notificationPredicate = defaultScrollNotificationPredicate, this.shadowColor, this.surfaceTintColor, this.shape, @@ -197,6 +208,7 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget { this.systemOverlayStyle, }) : assert(automaticallyImplyLeading != null), assert(elevation == null || elevation >= 0.0), + assert(notificationPredicate != null), assert(primary != null), assert(toolbarOpacity != null), assert(bottomOpacity != null), @@ -421,6 +433,13 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget { /// shadow. final double? scrolledUnderElevation; + /// A check that specifies which child's [ScrollNotification]s should be + /// listened to. + /// + /// By default, checks whether `notification.depth == 0`. Set it to something + /// else for more complicated layouts. + final ScrollNotificationPredicate notificationPredicate; + /// {@template flutter.material.appbar.shadowColor} /// The color of the shadow below the app bar. /// @@ -809,7 +828,7 @@ class _AppBarState extends State { } void _handleScrollNotification(ScrollNotification notification) { - if (notification is ScrollUpdateNotification && notification.depth == 0) { + if (notification is ScrollUpdateNotification && widget.notificationPredicate(notification)) { final bool oldScrolledUnder = _scrolledUnder; final ScrollMetrics metrics = notification.metrics; switch (metrics.axisDirection) { @@ -1254,7 +1273,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { final bool isScrolledUnder = overlapsContent || (pinned && shrinkOffset > maxExtent - minExtent); final bool isPinnedWithOpacityFade = pinned && floating && bottom != null && extraToolbarHeight == 0.0; final double toolbarOpacity = !pinned || isPinnedWithOpacityFade - ? (visibleToolbarHeight / (toolbarHeight ?? kToolbarHeight)).clamp(0.0, 1.0) + ? clampDouble(visibleToolbarHeight / (toolbarHeight ?? kToolbarHeight), 0.0, 1.0) : 1.0; final Widget appBar = FlexibleSpaceBar.createSettings( @@ -1291,7 +1310,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { titleSpacing: titleSpacing, shape: shape, toolbarOpacity: toolbarOpacity, - bottomOpacity: pinned ? 1.0 : ((visibleMainHeight / _bottomHeight).clamp(0.0, 1.0)), + bottomOpacity: pinned ? 1.0 : clampDouble(visibleMainHeight / _bottomHeight, 0.0, 1.0), toolbarHeight: toolbarHeight, leadingWidth: leadingWidth, backwardsCompatibility: backwardsCompatibility, diff --git a/packages/flutter/lib/src/material/button.dart b/packages/flutter/lib/src/material/button.dart index 8c992282b24a7..dcc6ee04be034 100644 --- a/packages/flutter/lib/src/material/button.dart +++ b/packages/flutter/lib/src/material/button.dart @@ -365,7 +365,7 @@ class _RawMaterialButtonState extends State with MaterialStat right: densityAdjustment.dx, bottom: densityAdjustment.dy, ), - ).clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); + ).clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); // ignore_clamp_double_lint final Widget result = ConstrainedBox( diff --git a/packages/flutter/lib/src/material/button_style_button.dart b/packages/flutter/lib/src/material/button_style_button.dart index ab48cf499eb44..ddb6e3f4ae218 100644 --- a/packages/flutter/lib/src/material/button_style_button.dart +++ b/packages/flutter/lib/src/material/button_style_button.dart @@ -302,7 +302,7 @@ class _ButtonStyleState extends State with MaterialStateMixin final double dx = math.max(0, densityAdjustment.dx); final EdgeInsetsGeometry padding = resolvedPadding! .add(EdgeInsets.fromLTRB(dx, dy, dx, dy)) - .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); + .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); // ignore_clamp_double_lint // If an opaque button's background is becoming translucent while its // elevation is changing, change the elevation first. Material implicitly diff --git a/packages/flutter/lib/src/material/chip.dart b/packages/flutter/lib/src/material/chip.dart index cbc65fa4e2d59..65922875af3ae 100644 --- a/packages/flutter/lib/src/material/chip.dart +++ b/packages/flutter/lib/src/material/chip.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; @@ -1111,7 +1112,7 @@ class _RawChipState extends State with MaterialStateMixin, TickerProvid final EdgeInsetsGeometry defaultLabelPadding = EdgeInsets.lerp( const EdgeInsets.symmetric(horizontal: 8.0), const EdgeInsets.symmetric(horizontal: 4.0), - (MediaQuery.of(context).textScaleFactor - 1.0).clamp(0.0, 1.0), + clampDouble(MediaQuery.of(context).textScaleFactor - 1.0, 0.0, 1.0), )!; final ThemeData theme = Theme.of(context); diff --git a/packages/flutter/lib/src/material/desktop_text_selection.dart b/packages/flutter/lib/src/material/desktop_text_selection.dart index ee3884de376fc..0192f0e08f013 100644 --- a/packages/flutter/lib/src/material/desktop_text_selection.dart +++ b/packages/flutter/lib/src/material/desktop_text_selection.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -153,7 +154,7 @@ class _DesktopTextSelectionControlsToolbarState extends State<_DesktopTextSelect final MediaQueryData mediaQuery = MediaQuery.of(context); final Offset midpointAnchor = Offset( - (widget.selectionMidpoint.dx - widget.globalEditableRegion.left).clamp( + clampDouble(widget.selectionMidpoint.dx - widget.globalEditableRegion.left, mediaQuery.padding.left, mediaQuery.size.width - mediaQuery.padding.right, ), diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index 3bbe176800d6b..f6a0bb547a720 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart @@ -4,6 +4,7 @@ import 'dart:ui'; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; import 'color_scheme.dart'; @@ -1176,7 +1177,7 @@ class DialogRoute extends RawDialogRoute { } double _paddingScaleFactor(double textScaleFactor) { - final double clampedTextScaleFactor = textScaleFactor.clamp(1.0, 2.0); + final double clampedTextScaleFactor = clampDouble(textScaleFactor, 1.0, 2.0); // The final padding scale factor is clamped between 1/3 and 1. For example, // a non-scaled padding of 24 will produce a padding between 24 and 8. return lerpDouble(1.0, 1.0 / 3.0, clampedTextScaleFactor - 1.0)!; diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index 70da898cfff36..404472fef3190 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -67,12 +67,12 @@ class _DropdownMenuPainter extends CustomPainter { void paint(Canvas canvas, Size size) { final double selectedItemOffset = getSelectedItemOffset(); final Tween top = Tween( - begin: selectedItemOffset.clamp(0.0, math.max(size.height - _kMenuItemHeight, 0.0)), + begin: clampDouble(selectedItemOffset, 0.0, math.max(size.height - _kMenuItemHeight, 0.0)), end: 0.0, ); final Tween bottom = Tween( - begin: (top.begin! + _kMenuItemHeight).clamp(math.min(_kMenuItemHeight, size.height), size.height), + begin: clampDouble(top.begin! + _kMenuItemHeight, math.min(_kMenuItemHeight, size.height), size.height), end: size.height, ); @@ -166,8 +166,8 @@ class _DropdownMenuItemButtonState extends State<_DropdownMenuItemButton> if (widget.itemIndex == widget.route.selectedIndex) { opacity = CurvedAnimation(parent: widget.route.animation!, curve: const Threshold(0.0)); } else { - final double start = (0.5 + (widget.itemIndex + 1) * unit).clamp(0.0, 1.0); - final double end = (start + 1.5 * unit).clamp(0.0, 1.0); + final double start = clampDouble(0.5 + (widget.itemIndex + 1) * unit, 0.0, 1.0); + final double end = clampDouble(start + 1.5 * unit, 0.0, 1.0); opacity = CurvedAnimation(parent: widget.route.animation!, curve: Interval(start, end)); } Widget child = Container( @@ -371,10 +371,10 @@ class _DropdownMenuRouteLayout extends SingleChildLayoutDelegate { final double left; switch (textDirection!) { case TextDirection.rtl: - left = buttonRect.right.clamp(0.0, size.width) - childSize.width; + left = clampDouble(buttonRect.right, 0.0, size.width) - childSize.width; break; case TextDirection.ltr: - left = buttonRect.left.clamp(0.0, size.width - childSize.width); + left = clampDouble(buttonRect.left, 0.0, size.width - childSize.width); break; } diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart index 58ec0cb05a3ac..b4187283f2c58 100644 --- a/packages/flutter/lib/src/material/flexible_space_bar.dart +++ b/packages/flutter/lib/src/material/flexible_space_bar.dart @@ -5,6 +5,7 @@ import 'dart:math' as math; import 'dart:ui' as ui; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; import 'colors.dart'; @@ -231,7 +232,7 @@ class _FlexibleSpaceBarState extends State { // 0.0 -> Expanded // 1.0 -> Collapsed to toolbar - final double t = (1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent).clamp(0.0, 1.0); + final double t = clampDouble(1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent, 0.0, 1.0); // background if (widget.background != null) { @@ -307,7 +308,10 @@ class _FlexibleSpaceBarState extends State { if (widget.stretchModes.contains(StretchMode.fadeTitle) && constraints.maxHeight > settings.maxExtent) { final double stretchOpacity = 1 - - (((constraints.maxHeight - settings.maxExtent) / 100).clamp(0.0, 1.0)); + clampDouble( + (constraints.maxHeight - settings.maxExtent) / 100, + 0.0, + 1.0); title = Opacity( opacity: stretchOpacity, child: title, diff --git a/packages/flutter/lib/src/material/input_border.dart b/packages/flutter/lib/src/material/input_border.dart index 3e550fa497d7f..185d4ee4e5618 100644 --- a/packages/flutter/lib/src/material/input_border.dart +++ b/packages/flutter/lib/src/material/input_border.dart @@ -5,6 +5,7 @@ import 'dart:math' as math; import 'dart:ui' show lerpDouble; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; /// Defines the appearance of an [InputDecorator]'s border. @@ -418,7 +419,7 @@ class OutlineInputBorder extends InputBorder { // Currently, BorderRadius only supports circular radii. const double cornerArcSweep = math.pi / 2.0; final double tlCornerArcSweep = math.acos( - (1 - start / scaledRRect.tlRadiusX).clamp(0.0, 1.0), + clampDouble(1 - start / scaledRRect.tlRadiusX, 0.0, 1.0), ); final Path path = Path() @@ -436,7 +437,7 @@ class OutlineInputBorder extends InputBorder { } else if (start + extent < scaledRRect.width) { final double dx = scaledRRect.width - (start + extent); final double sweep = math.asin( - (1 - dx / scaledRRect.trRadiusX).clamp(0.0, 1.0), + clampDouble(1 - dx / scaledRRect.trRadiusX, 0.0, 1.0), ); path.addArc(trCorner, trCornerArcStart + sweep, trCornerArcSweep - sweep); } diff --git a/packages/flutter/lib/src/material/navigation_bar.dart b/packages/flutter/lib/src/material/navigation_bar.dart index 30e968c3e5dd9..f62552440457d 100644 --- a/packages/flutter/lib/src/material/navigation_bar.dart +++ b/packages/flutter/lib/src/material/navigation_bar.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; import 'color_scheme.dart'; @@ -918,7 +919,7 @@ class _ClampTextScaleFactor extends StatelessWidget { Widget build(BuildContext context) { return MediaQuery( data: MediaQuery.of(context).copyWith( - textScaleFactor: MediaQuery.of(context).textScaleFactor.clamp( + textScaleFactor: clampDouble(MediaQuery.of(context).textScaleFactor, 0.0, upperLimit, ), diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index aaefd3da6ad7e..30111ba4e1ba9 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -543,7 +543,7 @@ class _PopupMenu extends StatelessWidget { for (int i = 0; i < route.items.length; i += 1) { final double start = (i + 1) * unit; - final double end = (start + 1.5 * unit).clamp(0.0, 1.0); + final double end = clampDouble(start + 1.5 * unit, 0.0, 1.0); final CurvedAnimation opacity = CurvedAnimation( parent: route.animation!, curve: Interval(start, end), diff --git a/packages/flutter/lib/src/material/progress_indicator.dart b/packages/flutter/lib/src/material/progress_indicator.dart index afc387d49c21f..43932ddd45c8a 100644 --- a/packages/flutter/lib/src/material/progress_indicator.dart +++ b/packages/flutter/lib/src/material/progress_indicator.dart @@ -202,7 +202,7 @@ class _LinearProgressIndicatorPainter extends CustomPainter { } if (value != null) { - drawBar(0.0, value!.clamp(0.0, 1.0) * size.width); + drawBar(0.0, clampDouble(value!, 0.0, 1.0) * size.width); } else { final double x1 = size.width * line1Tail.transform(animationValue); final double width1 = size.width * line1Head.transform(animationValue) - x1; @@ -385,7 +385,7 @@ class _CircularProgressIndicatorPainter extends CustomPainter { ? _startAngle : _startAngle + tailValue * 3 / 2 * math.pi + rotationValue * math.pi * 2.0 + offsetValue * 0.5 * math.pi, arcSweep = value != null - ? value.clamp(0.0, 1.0) * _sweep + ? clampDouble(value, 0.0, 1.0) * _sweep : math.max(headValue * 3 / 2 * math.pi - tailValue * 3 / 2 * math.pi, _epsilon); final Color? backgroundColor; diff --git a/packages/flutter/lib/src/material/range_slider.dart b/packages/flutter/lib/src/material/range_slider.dart index fdd10e1e79e5b..17039d23ddfea 100644 --- a/packages/flutter/lib/src/material/range_slider.dart +++ b/packages/flutter/lib/src/material/range_slider.dart @@ -1079,7 +1079,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix } double _discretize(double value) { - double result = value.clamp(0.0, 1.0); + double result = clampDouble(value, 0.0, 1.0); if (isDiscrete) { result = (result * divisions!).round() / divisions!; } @@ -1092,7 +1092,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix void _startInteraction(Offset globalPosition) { _state.showValueIndicator(); - final double tapValue = _getValueFromGlobalPosition(globalPosition).clamp(0.0, 1.0); + final double tapValue = clampDouble(_getValueFromGlobalPosition(globalPosition), 0.0, 1.0); _lastThumbSelection = sliderTheme.thumbSelector!(textDirection, values, tapValue, _thumbSize, size, 0); if (_lastThumbSelection != null) { @@ -1613,11 +1613,11 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix } double get _decreasedStartValue { - return (values.start - _semanticActionUnit).clamp(0.0, 1.0); + return clampDouble(values.start - _semanticActionUnit, 0.0, 1.0); } double get _increasedEndValue { - return (values.end + _semanticActionUnit).clamp(0.0, 1.0); + return clampDouble(values.end + _semanticActionUnit, 0.0, 1.0); } double get _decreasedEndValue { diff --git a/packages/flutter/lib/src/material/refresh_indicator.dart b/packages/flutter/lib/src/material/refresh_indicator.dart index e1e88a7ba62a1..01fff3623bae5 100644 --- a/packages/flutter/lib/src/material/refresh_indicator.dart +++ b/packages/flutter/lib/src/material/refresh_indicator.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:math' as math; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; import 'debug.dart'; @@ -409,7 +410,7 @@ class RefreshIndicatorState extends State with TickerProviderS double newValue = _dragOffset! / (containerExtent * _kDragContainerExtentPercentage); if (_mode == _RefreshIndicatorMode.armed) newValue = math.max(newValue, 1.0 / _kDragSizeFactorLimit); - _positionController.value = newValue.clamp(0.0, 1.0); // this triggers various rebuilds + _positionController.value = clampDouble(newValue, 0.0, 1.0); // this triggers various rebuilds if (_mode == _RefreshIndicatorMode.drag && _valueColor.value!.alpha == 0xFF) _mode = _RefreshIndicatorMode.armed; } diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 00807ce9f16ed..b9f6538639c82 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -993,7 +993,7 @@ class _ScaffoldLayout extends MultiChildLayoutDelegate { if (extendBody) { bodyMaxHeight += bottomWidgetsHeight; - bodyMaxHeight = bodyMaxHeight.clamp(0.0, looseConstraints.maxHeight - contentTop); + bodyMaxHeight = clampDouble(bodyMaxHeight, 0.0, looseConstraints.maxHeight - contentTop); assert(bodyMaxHeight <= math.max(0.0, looseConstraints.maxHeight - contentTop)); } @@ -2366,7 +2366,7 @@ class ScaffoldState extends State with TickerProviderStateMixin, Resto /// [Scaffold.floatingActionButton]. This value must not be null. set _floatingActionButtonVisibilityValue(double newValue) { assert(newValue != null); - _floatingActionButtonVisibilityController.value = newValue.clamp( + _floatingActionButtonVisibilityController.value = clampDouble(newValue, _floatingActionButtonVisibilityController.lowerBound, _floatingActionButtonVisibilityController.upperBound, ); diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index f0d16818e3a0a..78ac129bac750 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -1273,7 +1273,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { } double _discretize(double value) { - double result = value.clamp(0.0, 1.0); + double result = clampDouble(value, 0.0, 1.0); if (isDiscrete) { result = (result * divisions!).round() / divisions!; } @@ -1540,12 +1540,12 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { if (semanticFormatterCallback != null) { config.value = semanticFormatterCallback!(_state._lerp(value)); - config.increasedValue = semanticFormatterCallback!(_state._lerp((value + _semanticActionUnit).clamp(0.0, 1.0))); - config.decreasedValue = semanticFormatterCallback!(_state._lerp((value - _semanticActionUnit).clamp(0.0, 1.0))); + config.increasedValue = semanticFormatterCallback!(_state._lerp(clampDouble(value + _semanticActionUnit, 0.0, 1.0))); + config.decreasedValue = semanticFormatterCallback!(_state._lerp(clampDouble(value - _semanticActionUnit, 0.0, 1.0))); } else { config.value = '${(value * 100).round()}%'; - config.increasedValue = '${((value + _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%'; - config.decreasedValue = '${((value - _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%'; + config.increasedValue = '${(clampDouble(value + _semanticActionUnit, 0.0, 1.0) * 100).round()}%'; + config.decreasedValue = '${(clampDouble(value - _semanticActionUnit, 0.0, 1.0) * 100).round()}%'; } } @@ -1553,13 +1553,13 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { void increaseAction() { if (isInteractive) { - onChanged!((value + _semanticActionUnit).clamp(0.0, 1.0)); + onChanged!(clampDouble(value + _semanticActionUnit, 0.0, 1.0)); } } void decreaseAction() { if (isInteractive) { - onChanged!((value - _semanticActionUnit).clamp(0.0, 1.0)); + onChanged!(clampDouble(value - _semanticActionUnit, 0.0, 1.0)); } } } diff --git a/packages/flutter/lib/src/material/slider_theme.dart b/packages/flutter/lib/src/material/slider_theme.dart index 1bfb0979fdcf2..0b919c36b269e 100644 --- a/packages/flutter/lib/src/material/slider_theme.dart +++ b/packages/flutter/lib/src/material/slider_theme.dart @@ -3180,7 +3180,7 @@ class _PaddleSliderValueIndicatorPathPainter { // factor of the value indicator. final double neckStretchBaseline = math.max(0.0, rightBottomNeckCenterY - math.max(leftTopNeckCenter.dy, neckRightCenter.dy)); final double t = math.pow(inverseTextScale, 3.0) as double; - final double stretch = (neckStretchBaseline * t).clamp(0.0, 10.0 * neckStretchBaseline); + final double stretch = clampDouble(neckStretchBaseline * t, 0.0, 10.0 * neckStretchBaseline); final Offset neckStretch = Offset(0.0, neckStretchBaseline - stretch); assert(!_debuggingLabelLocation || () { diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 58f78e8ddf63c..49ddfea34948d 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -311,7 +311,7 @@ double _indexChangeProgress(TabController controller) { // The controller's offset is changing because the user is dragging the // TabBarView's PageView to the left or right. if (!controller.indexIsChanging) - return (currentIndex - controllerValue).abs().clamp(0.0, 1.0); + return clampDouble((currentIndex - controllerValue).abs(), 0.0, 1.0); // The TabController animation's value is changing from previousIndex to currentIndex. return (controllerValue - currentIndex).abs() / (currentIndex - previousIndex).abs(); @@ -417,8 +417,8 @@ class _IndicatorPainter extends CustomPainter { final double index = controller.index.toDouble(); final double value = controller.animation!.value; final bool ltr = index > value; - final int from = (ltr ? value.floor() : value.ceil()).clamp(0, maxTabIndex); - final int to = (ltr ? from + 1 : from - 1).clamp(0, maxTabIndex); + final int from = (ltr ? value.floor() : value.ceil()).clamp(0, maxTabIndex); // ignore_clamp_double_lint + final int to = (ltr ? from + 1 : from - 1).clamp(0, maxTabIndex); // ignore_clamp_double_lint final Rect fromRect = indicatorRect(size, from); final Rect toRect = indicatorRect(size, to); _currentRect = Rect.lerp(fromRect, toRect, (value - from).abs()); @@ -491,8 +491,8 @@ class _DragAnimation extends Animation with AnimationWithParentMixin { case TextDirection.ltr: break; } - return (tabCenter - viewportWidth / 2.0).clamp(minExtent, maxExtent); + return clampDouble(tabCenter - viewportWidth / 2.0, minExtent, maxExtent); } double _tabCenteredScrollOffset(int index) { @@ -1510,12 +1510,12 @@ class _TabBarViewState extends State { _controller!.index = _pageController.page!.round(); _currentIndex =_controller!.index; } - _controller!.offset = (_pageController.page! - _controller!.index).clamp(-1.0, 1.0); + _controller!.offset = clampDouble(_pageController.page! - _controller!.index, -1.0, 1.0); } else if (notification is ScrollEndNotification) { _controller!.index = _pageController.page!.round(); _currentIndex = _controller!.index; if (!_controller!.indexIsChanging) - _controller!.offset = (_pageController.page! - _controller!.index).clamp(-1.0, 1.0); + _controller!.offset = clampDouble(_pageController.page! - _controller!.index, -1.0, 1.0); } _warpUnderwayCount -= 1; diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 0bf3addeb3d46..679109f821df7 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -898,7 +898,7 @@ class _TextFieldState extends State with RestorationMixin implements if (widget.maxLength! > 0) { // Show the maxLength in the counter counterText += '/${widget.maxLength}'; - final int remaining = (widget.maxLength! - currentLength).clamp(0, widget.maxLength!); + final int remaining = (widget.maxLength! - currentLength).clamp(0, widget.maxLength!); // ignore_clamp_double_lint semanticCounterText = localizations.remainingTextFieldCharacterCount(remaining); } diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index 7ef7b2b89a59d..26ac146d74073 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -2686,8 +2686,8 @@ class VisualDensity with Diagnosticable { BoxConstraints effectiveConstraints(BoxConstraints constraints) { assert(constraints != null && constraints.debugAssertIsValid()); return constraints.copyWith( - minWidth: (constraints.minWidth + baseSizeAdjustment.dx).clamp(0.0, constraints.maxWidth), - minHeight: (constraints.minHeight + baseSizeAdjustment.dy).clamp(0.0, constraints.maxHeight), + minWidth: clampDouble(constraints.minWidth + baseSizeAdjustment.dx, 0.0, constraints.maxWidth), + minHeight: clampDouble(constraints.minHeight + baseSizeAdjustment.dy, 0.0, constraints.maxHeight), ); } diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart index 5ceda834b37c0..98d9658dede5e 100644 --- a/packages/flutter/lib/src/material/tooltip.dart +++ b/packages/flutter/lib/src/material/tooltip.dart @@ -134,7 +134,8 @@ class Tooltip extends StatefulWidget { /// The amount of space by which to inset the tooltip's [child]. /// - /// Defaults to 16.0 logical pixels in each direction. + /// On mobile, defaults to 16.0 logical pixels horizontally and 4.0 vertically. + /// On desktop, defaults to 8.0 logical pixels horizontally and 4.0 vertically. final EdgeInsetsGeometry? padding; /// The empty space that surrounds the tooltip. @@ -403,11 +404,11 @@ class TooltipState extends State with SingleTickerProviderStateMixin { case TargetPlatform.macOS: case TargetPlatform.linux: case TargetPlatform.windows: - return const EdgeInsets.symmetric(horizontal: 8.0); + return const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0); case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: - return const EdgeInsets.symmetric(horizontal: 16.0); + return const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0); } } diff --git a/packages/flutter/lib/src/painting/colors.dart b/packages/flutter/lib/src/painting/colors.dart index 4acde694c947e..60c81abfd3c12 100644 --- a/packages/flutter/lib/src/painting/colors.dart +++ b/packages/flutter/lib/src/painting/colors.dart @@ -206,10 +206,10 @@ class HSVColor { if (b == null) return a._scaleAlpha(1.0 - t); return HSVColor.fromAHSV( - lerpDouble(a.alpha, b.alpha, t)!.clamp(0.0, 1.0), + clampDouble(lerpDouble(a.alpha, b.alpha, t)!, 0.0, 1.0), lerpDouble(a.hue, b.hue, t)! % 360.0, - lerpDouble(a.saturation, b.saturation, t)!.clamp(0.0, 1.0), - lerpDouble(a.value, b.value, t)!.clamp(0.0, 1.0), + clampDouble(lerpDouble(a.saturation, b.saturation, t)!, 0.0, 1.0), + clampDouble(lerpDouble(a.value, b.value, t)!, 0.0, 1.0), ); } @@ -290,7 +290,7 @@ class HSLColor { // Saturation can exceed 1.0 with rounding errors, so clamp it. final double saturation = lightness == 1.0 ? 0.0 - : ((delta / (1.0 - (2.0 * lightness - 1.0).abs())).clamp(0.0, 1.0)); + : clampDouble(delta / (1.0 - (2.0 * lightness - 1.0).abs()), 0.0, 1.0); return HSLColor.fromAHSL(alpha, hue, saturation, lightness); } @@ -390,10 +390,10 @@ class HSLColor { if (b == null) return a._scaleAlpha(1.0 - t); return HSLColor.fromAHSL( - lerpDouble(a.alpha, b.alpha, t)!.clamp(0.0, 1.0), + clampDouble(lerpDouble(a.alpha, b.alpha, t)!, 0.0, 1.0), lerpDouble(a.hue, b.hue, t)! % 360.0, - lerpDouble(a.saturation, b.saturation, t)!.clamp(0.0, 1.0), - lerpDouble(a.lightness, b.lightness, t)!.clamp(0.0, 1.0), + clampDouble(lerpDouble(a.saturation, b.saturation, t)!, 0.0, 1.0), + clampDouble(lerpDouble(a.lightness, b.lightness, t)!, 0.0, 1.0), ); } diff --git a/packages/flutter/lib/src/painting/decoration_image.dart b/packages/flutter/lib/src/painting/decoration_image.dart index 1bd756292fbd0..b3eeb236b80bd 100644 --- a/packages/flutter/lib/src/painting/decoration_image.dart +++ b/packages/flutter/lib/src/painting/decoration_image.dart @@ -154,9 +154,8 @@ class DecorationImage { /// Used to set the filterQuality of the image. /// - /// Use the "low" quality setting to scale the image, which corresponds to - /// bilinear interpolation, rather than the default "none" which corresponds - /// to nearest-neighbor. + /// Defaults to [FilterQuality.low] to scale the image, which corresponds to + /// bilinear interpolation. final FilterQuality filterQuality; /// Whether the colors of the image are inverted when drawn. diff --git a/packages/flutter/lib/src/painting/edge_insets.dart b/packages/flutter/lib/src/painting/edge_insets.dart index 4c11af16eaed9..6ba1eb6f8ca8b 100644 --- a/packages/flutter/lib/src/painting/edge_insets.dart +++ b/packages/flutter/lib/src/painting/edge_insets.dart @@ -161,12 +161,12 @@ abstract class EdgeInsetsGeometry { /// or equal to `min`, and less than or equal to `max`. EdgeInsetsGeometry clamp(EdgeInsetsGeometry min, EdgeInsetsGeometry max) { return _MixedEdgeInsets.fromLRSETB( - _left.clamp(min._left, max._left), - _right.clamp(min._right, max._right), - _start.clamp(min._start, max._start), - _end.clamp(min._end, max._end), - _top.clamp(min._top, max._top), - _bottom.clamp(min._bottom, max._bottom), + clampDouble(_left, min._left, max._left), + clampDouble(_right, min._right, max._right), + clampDouble(_start, min._start, max._start), + clampDouble(_end, min._end, max._end), + clampDouble(_top, min._top, max._top), + clampDouble(_bottom, min._bottom, max._bottom), ); } @@ -505,10 +505,10 @@ class EdgeInsets extends EdgeInsetsGeometry { @override EdgeInsetsGeometry clamp(EdgeInsetsGeometry min, EdgeInsetsGeometry max) { return EdgeInsets.fromLTRB( - _left.clamp(min._left, max._left), - _top.clamp(min._top, max._top), - _right.clamp(min._right, max._right), - _bottom.clamp(min._bottom, max._bottom), + clampDouble(_left, min._left, max._left), + clampDouble(_top, min._top, max._top), + clampDouble(_right, min._right, max._right), + clampDouble(_bottom, min._bottom, max._bottom), ); } diff --git a/packages/flutter/lib/src/painting/flutter_logo.dart b/packages/flutter/lib/src/painting/flutter_logo.dart index 66895ce7dd90b..a4fe8a1932e3f 100644 --- a/packages/flutter/lib/src/painting/flutter_logo.dart +++ b/packages/flutter/lib/src/painting/flutter_logo.dart @@ -119,7 +119,7 @@ class FlutterLogoDecoration extends Decoration { b.style, b.margin * t, b._position, - b._opacity * t.clamp(0.0, 1.0), + b._opacity * clampDouble(t, 0.0, 1.0), ); } if (b == null) { @@ -128,7 +128,7 @@ class FlutterLogoDecoration extends Decoration { a.style, a.margin * t, a._position, - a._opacity * (1.0 - t).clamp(0.0, 1.0), + a._opacity * clampDouble(1.0 - t, 0.0, 1.0), ); } if (t == 0.0) @@ -140,7 +140,7 @@ class FlutterLogoDecoration extends Decoration { t < 0.5 ? a.style : b.style, EdgeInsets.lerp(a.margin, b.margin, t)!, a._position + (b._position - a._position) * t, - (a._opacity + (b._opacity - a._opacity) * t).clamp(0.0, 1.0), + clampDouble(a._opacity + (b._opacity - a._opacity) * t, 0.0, 1.0), ); } diff --git a/packages/flutter/lib/src/painting/geometry.dart b/packages/flutter/lib/src/painting/geometry.dart index 94aa59b8173df..3e61c68bf50ef 100644 --- a/packages/flutter/lib/src/painting/geometry.dart +++ b/packages/flutter/lib/src/painting/geometry.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart' show clampDouble; import 'basic_types.dart'; /// Position a child box within a container box, either above or below a target @@ -64,7 +65,7 @@ Offset positionDependentBox({ if (size.width - margin * 2.0 < childSize.width) { x = (size.width - childSize.width) / 2.0; } else { - final double normalizedTargetX = target.dx.clamp(margin, size.width - margin); + final double normalizedTargetX = clampDouble(target.dx, margin, size.width - margin); final double edge = margin + childSize.width / 2.0; if (normalizedTargetX < edge) { x = margin; diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 690a96c19c623..2b37f23b7b753 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -621,7 +621,7 @@ class TextPainter { newWidth = maxIntrinsicWidth; break; } - newWidth = newWidth.clamp(minWidth, maxWidth); + newWidth = clampDouble(newWidth, minWidth, maxWidth); if (newWidth != _applyFloatingPointHack(_paragraph!.width)) { _paragraph!.layout(ui.ParagraphConstraints(width: newWidth)); } @@ -781,7 +781,8 @@ class TextPainter { final double caretEnd = box.end; final double dx = box.direction == TextDirection.rtl ? caretEnd - caretPrototype.width : caretEnd; - return Rect.fromLTRB(dx.clamp(0, _paragraph!.width), box.top, dx.clamp(0, _paragraph!.width), box.bottom); + return Rect.fromLTRB(clampDouble(dx, 0, _paragraph!.width), box.top, + clampDouble(dx, 0, _paragraph!.width), box.bottom); } return null; } @@ -823,7 +824,7 @@ class TextPainter { final TextBox box = boxes.last; final double caretStart = box.start; final double dx = box.direction == TextDirection.rtl ? caretStart - caretPrototype.width : caretStart; - return Rect.fromLTRB(dx.clamp(0, _paragraph!.width), box.top, dx.clamp(0, _paragraph!.width), box.bottom); + return Rect.fromLTRB(clampDouble(dx, 0, _paragraph!.width), box.top, clampDouble(dx, 0, _paragraph!.width), box.bottom); } return null; } diff --git a/packages/flutter/lib/src/painting/text_style.dart b/packages/flutter/lib/src/painting/text_style.dart index 2bfbee3f36690..0d948cd7dae6b 100644 --- a/packages/flutter/lib/src/painting/text_style.dart +++ b/packages/flutter/lib/src/painting/text_style.dart @@ -967,7 +967,7 @@ class TextStyle with Diagnosticable { fontFamily: fontFamily ?? _fontFamily, fontFamilyFallback: fontFamilyFallback ?? this.fontFamilyFallback, fontSize: fontSize == null ? null : fontSize! * fontSizeFactor + fontSizeDelta, - fontWeight: fontWeight == null ? null : FontWeight.values[(fontWeight!.index + fontWeightDelta).clamp(0, FontWeight.values.length - 1)], + fontWeight: fontWeight == null ? null : FontWeight.values[(fontWeight!.index + fontWeightDelta).clamp(0, FontWeight.values.length - 1)], // ignore_clamp_double_lint fontStyle: fontStyle ?? this.fontStyle, letterSpacing: letterSpacing == null ? null : letterSpacing! * letterSpacingFactor + letterSpacingDelta, wordSpacing: wordSpacing == null ? null : wordSpacing! * wordSpacingFactor + wordSpacingDelta, diff --git a/packages/flutter/lib/src/physics/clamped_simulation.dart b/packages/flutter/lib/src/physics/clamped_simulation.dart index 6c5b7bb05571a..0454e914318b9 100644 --- a/packages/flutter/lib/src/physics/clamped_simulation.dart +++ b/packages/flutter/lib/src/physics/clamped_simulation.dart @@ -55,10 +55,10 @@ class ClampedSimulation extends Simulation { final double dxMax; @override - double x(double time) => simulation.x(time).clamp(xMin, xMax); + double x(double time) => clampDouble(simulation.x(time), xMin, xMax); @override - double dx(double time) => simulation.dx(time).clamp(dxMin, dxMax); + double dx(double time) => clampDouble(simulation.dx(time), dxMin, dxMax); @override bool isDone(double time) => simulation.isDone(time); diff --git a/packages/flutter/lib/src/physics/friction_simulation.dart b/packages/flutter/lib/src/physics/friction_simulation.dart index 868243bd9c9cc..9c24c07a60563 100644 --- a/packages/flutter/lib/src/physics/friction_simulation.dart +++ b/packages/flutter/lib/src/physics/friction_simulation.dart @@ -115,14 +115,14 @@ class BoundedFrictionSimulation extends FrictionSimulation { super.velocity, this._minX, this._maxX, - ) : assert(position.clamp(_minX, _maxX) == position); + ) : assert(clampDouble(position, _minX, _maxX) == position); final double _minX; final double _maxX; @override double x(double time) { - return super.x(time).clamp(_minX, _maxX); + return clampDouble(super.x(time), _minX, _maxX); } @override diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart index 0d508c736f8dc..563414b5c9418 100644 --- a/packages/flutter/lib/src/rendering/box.dart +++ b/packages/flutter/lib/src/rendering/box.dart @@ -212,10 +212,10 @@ class BoxConstraints extends Constraints { /// as close as possible to the original constraints. BoxConstraints enforce(BoxConstraints constraints) { return BoxConstraints( - minWidth: minWidth.clamp(constraints.minWidth, constraints.maxWidth), - maxWidth: maxWidth.clamp(constraints.minWidth, constraints.maxWidth), - minHeight: minHeight.clamp(constraints.minHeight, constraints.maxHeight), - maxHeight: maxHeight.clamp(constraints.minHeight, constraints.maxHeight), + minWidth: clampDouble(minWidth, constraints.minWidth, constraints.maxWidth), + maxWidth: clampDouble(maxWidth, constraints.minWidth, constraints.maxWidth), + minHeight: clampDouble(minHeight, constraints.minHeight, constraints.maxHeight), + maxHeight: clampDouble(maxHeight, constraints.minHeight, constraints.maxHeight), ); } @@ -224,10 +224,10 @@ class BoxConstraints extends Constraints { /// box constraints. BoxConstraints tighten({ double? width, double? height }) { return BoxConstraints( - minWidth: width == null ? minWidth : width.clamp(minWidth, maxWidth), - maxWidth: width == null ? maxWidth : width.clamp(minWidth, maxWidth), - minHeight: height == null ? minHeight : height.clamp(minHeight, maxHeight), - maxHeight: height == null ? maxHeight : height.clamp(minHeight, maxHeight), + minWidth: width == null ? minWidth : clampDouble(width, minWidth, maxWidth), + maxWidth: width == null ? maxWidth : clampDouble(width, minWidth, maxWidth), + minHeight: height == null ? minHeight : clampDouble(height, minHeight, maxHeight), + maxHeight: height == null ? maxHeight : clampDouble(height, minHeight, maxHeight), ); } @@ -253,14 +253,14 @@ class BoxConstraints extends Constraints { /// possible to the given width. double constrainWidth([ double width = double.infinity ]) { assert(debugAssertIsValid()); - return width.clamp(minWidth, maxWidth); + return clampDouble(width, minWidth, maxWidth); } /// Returns the height that both satisfies the constraints and is as close as /// possible to the given height. double constrainHeight([ double height = double.infinity ]) { assert(debugAssertIsValid()); - return height.clamp(minHeight, maxHeight); + return clampDouble(height, minHeight, maxHeight); } Size _debugPropagateDebugSize(Size size, Size result) { diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index f896509332fe7..f59ed40060f3c 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -1651,8 +1651,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, final Offset start = Offset(0.0, preferredLineHeight) + caretOffset + paintOffset; return [TextSelectionPoint(start, null)]; } else { - final Offset start = Offset(boxes.first.start.clamp(0, _textPainter.size.width), boxes.first.bottom) + paintOffset; - final Offset end = Offset(boxes.last.end.clamp(0, _textPainter.size.width), boxes.last.bottom) + paintOffset; + final Offset start = Offset(clampDouble(boxes.first.start, 0, _textPainter.size.width), boxes.first.bottom) + paintOffset; + final Offset end = Offset(clampDouble(boxes.last.end, 0, _textPainter.size.width), boxes.last.bottom) + paintOffset; return [ TextSelectionPoint(start, boxes.first.direction), TextSelectionPoint(end, boxes.last.direction), @@ -1958,6 +1958,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, extentOffset: extentOffset, affinity: fromPosition.affinity, ); + _setSelection(newSelection, cause); } @@ -2480,8 +2481,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, void _paintHandleLayers(PaintingContext context, List endpoints) { Offset startPoint = endpoints[0].point; startPoint = Offset( - startPoint.dx.clamp(0.0, size.width), - startPoint.dy.clamp(0.0, size.height), + clampDouble(startPoint.dx, 0.0, size.width), + clampDouble(startPoint.dy, 0.0, size.height), ); context.pushLayer( LeaderLayer(link: startHandleLayerLink, offset: startPoint), @@ -2491,8 +2492,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, if (endpoints.length == 2) { Offset endPoint = endpoints[1].point; endPoint = Offset( - endPoint.dx.clamp(0.0, size.width), - endPoint.dy.clamp(0.0, size.height), + clampDouble(endPoint.dx, 0.0, size.width), + clampDouble(endPoint.dy, 0.0, size.height), ); context.pushLayer( LeaderLayer(link: endHandleLayerLink, offset: endPoint), diff --git a/packages/flutter/lib/src/rendering/sliver.dart b/packages/flutter/lib/src/rendering/sliver.dart index e759d4fe8243e..47c2c0444b8cb 100644 --- a/packages/flutter/lib/src/rendering/sliver.dart +++ b/packages/flutter/lib/src/rendering/sliver.dart @@ -1337,7 +1337,7 @@ abstract class RenderSliver extends RenderObject { final double a = constraints.scrollOffset; final double b = constraints.scrollOffset + constraints.remainingPaintExtent; // the clamp on the next line is to avoid floating point rounding errors - return (to.clamp(a, b) - from.clamp(a, b)).clamp(0.0, constraints.remainingPaintExtent); + return clampDouble(clampDouble(to, a, b) - clampDouble(from, a, b), 0.0, constraints.remainingPaintExtent); } /// Computes the portion of the region from `from` to `to` that is within @@ -1353,7 +1353,7 @@ abstract class RenderSliver extends RenderObject { final double a = constraints.scrollOffset + constraints.cacheOrigin; final double b = constraints.scrollOffset + constraints.remainingCacheExtent; // the clamp on the next line is to avoid floating point rounding errors - return (to.clamp(a, b) - from.clamp(a, b)).clamp(0.0, constraints.remainingCacheExtent); + return clampDouble(clampDouble(to, a, b) - clampDouble(from, a, b), 0.0, constraints.remainingCacheExtent); } /// Returns the distance from the leading _visible_ edge of the sliver to the diff --git a/packages/flutter/lib/src/rendering/sliver_grid.dart b/packages/flutter/lib/src/rendering/sliver_grid.dart index 17cafcea2d958..fc7650a86c7a2 100644 --- a/packages/flutter/lib/src/rendering/sliver_grid.dart +++ b/packages/flutter/lib/src/rendering/sliver_grid.dart @@ -570,10 +570,10 @@ class RenderSliverGrid extends RenderSliverMultiBoxAdaptor { if (firstChild != null) { final int oldFirstIndex = indexOf(firstChild!); final int oldLastIndex = indexOf(lastChild!); - final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount); + final int leadingGarbage = (firstIndex - oldFirstIndex).clamp(0, childCount); // ignore_clamp_double_lint final int trailingGarbage = targetLastIndex == null ? 0 - : (oldLastIndex - targetLastIndex).clamp(0, childCount); + : (oldLastIndex - targetLastIndex).clamp(0, childCount); // ignore_clamp_double_lint collectGarbage(leadingGarbage, trailingGarbage); } else { collectGarbage(0, 0); diff --git a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart index 7904484a5c9cf..c2706a9c1e871 100644 --- a/packages/flutter/lib/src/rendering/sliver_persistent_header.dart +++ b/packages/flutter/lib/src/rendering/sliver_persistent_header.dart @@ -364,7 +364,7 @@ abstract class RenderSliverScrollingPersistentHeader extends RenderSliverPersist geometry = SliverGeometry( scrollExtent: maxExtent, paintOrigin: math.min(constraints.overlap, 0.0), - paintExtent: paintExtent.clamp(0.0, constraints.remainingPaintExtent), + paintExtent: clampDouble(paintExtent, 0.0, constraints.remainingPaintExtent), maxPaintExtent: maxExtent + stretchOffset, hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. ); @@ -381,7 +381,7 @@ abstract class RenderSliverScrollingPersistentHeader extends RenderSliverPersist geometry = SliverGeometry( scrollExtent: maxExtent, paintOrigin: math.min(constraints.overlap, 0.0), - paintExtent: paintExtent.clamp(0.0, constraints.remainingPaintExtent), + paintExtent: clampDouble(paintExtent, 0.0, constraints.remainingPaintExtent), maxPaintExtent: maxExtent, hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. ); @@ -423,7 +423,7 @@ abstract class RenderSliverPinnedPersistentHeader extends RenderSliverPersistent final bool overlapsContent = constraints.overlap > 0.0; layoutChild(constraints.scrollOffset, maxExtent, overlapsContent: overlapsContent); final double effectiveRemainingPaintExtent = math.max(0, constraints.remainingPaintExtent - constraints.overlap); - final double layoutExtent = (maxExtent - constraints.scrollOffset).clamp(0.0, effectiveRemainingPaintExtent); + final double layoutExtent = clampDouble(maxExtent - constraints.scrollOffset, 0.0, effectiveRemainingPaintExtent); final double stretchOffset = stretchConfiguration != null ? constraints.overlap.abs() : 0.0; @@ -595,8 +595,8 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste geometry = SliverGeometry( scrollExtent: maxExtent, paintOrigin: math.min(constraints.overlap, 0.0), - paintExtent: paintExtent.clamp(0.0, constraints.remainingPaintExtent), - layoutExtent: layoutExtent.clamp(0.0, constraints.remainingPaintExtent), + paintExtent: clampDouble(paintExtent, 0.0, constraints.remainingPaintExtent), + layoutExtent: clampDouble(layoutExtent, 0.0, constraints.remainingPaintExtent), maxPaintExtent: maxExtent + stretchOffset, hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. ); @@ -677,7 +677,7 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste if (delta > 0.0) // If we are trying to expand when allowFloatingExpansion is false, delta = 0.0; // disallow the expansion. (But allow shrinking, i.e. delta < 0.0 is fine.) } - _effectiveScrollOffset = (_effectiveScrollOffset! - delta).clamp(0.0, constraints.scrollOffset); + _effectiveScrollOffset = clampDouble(_effectiveScrollOffset! - delta, 0.0, constraints.scrollOffset); } else { _effectiveScrollOffset = constraints.scrollOffset; } @@ -738,13 +738,16 @@ abstract class RenderSliverFloatingPersistentHeader extends RenderSliverPersiste // A stretch header can have a bigger childExtent than maxExtent. final double effectiveMaxExtent = math.max(childExtent, maxExtent); - targetExtent = targetExtent.clamp( - showOnScreen.minShowOnScreenExtent, - showOnScreen.maxShowOnScreenExtent, - ) - // Clamp the value back to the valid range after applying additional - // constraints. Contracting is not allowed. - .clamp(childExtent, effectiveMaxExtent); + targetExtent = clampDouble( + clampDouble( + targetExtent, + showOnScreen.minShowOnScreenExtent, + showOnScreen.maxShowOnScreenExtent, + ), + // Clamp the value back to the valid range after applying additional + // constraints. Contracting is not allowed. + childExtent, + effectiveMaxExtent); // Expands the header if needed, with animation. if (targetExtent > childExtent) { @@ -806,7 +809,7 @@ abstract class RenderSliverFloatingPinnedPersistentHeader extends RenderSliverFl constraints.remainingPaintExtent; final double maxExtent = this.maxExtent; final double paintExtent = maxExtent - _effectiveScrollOffset!; - final double clampedPaintExtent = paintExtent.clamp( + final double clampedPaintExtent = clampDouble(paintExtent, minAllowedExtent, constraints.remainingPaintExtent, ); @@ -818,7 +821,7 @@ abstract class RenderSliverFloatingPinnedPersistentHeader extends RenderSliverFl scrollExtent: maxExtent, paintOrigin: math.min(constraints.overlap, 0.0), paintExtent: clampedPaintExtent, - layoutExtent: layoutExtent.clamp(0.0, clampedPaintExtent), + layoutExtent: clampDouble(layoutExtent, 0.0, clampedPaintExtent), maxPaintExtent: maxExtent + stretchOffset, maxScrollObstructionExtent: minExtent, hasVisualOverflow: true, // Conservatively say we do have overflow to avoid complexity. diff --git a/packages/flutter/lib/src/rendering/viewport.dart b/packages/flutter/lib/src/rendering/viewport.dart index fbc98bc9df737..01a69aedf2194 100644 --- a/packages/flutter/lib/src/rendering/viewport.dart +++ b/packages/flutter/lib/src/rendering/viewport.dart @@ -1385,9 +1385,9 @@ class RenderViewport extends RenderViewportBase(hasDragged - ? _currentSize.value.clamp(minSize, maxSize) + ? clampDouble(_currentSize.value, minSize, maxSize) : initialSize), hasDragged: hasDragged, ); diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 5eb767eb53885..759a002c5894c 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -2264,7 +2264,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien ? editableSize.width / 2 - rect.center.dx // Valid additional offsets range from (rect.right - size.width) // to (rect.left). Pick the closest one if out of range. - : 0.0.clamp(rect.right - editableSize.width, rect.left); + : clampDouble(0.0, rect.right - editableSize.width, rect.left); unitOffset = const Offset(1, 0); } else { // The caret is vertically centered within the line. Expand the caret's @@ -2278,17 +2278,17 @@ class EditableTextState extends State with AutomaticKeepAliveClien additionalOffset = expandedRect.height >= editableSize.height ? editableSize.height / 2 - expandedRect.center.dy - : 0.0.clamp(expandedRect.bottom - editableSize.height, expandedRect.top); + : clampDouble(0.0, expandedRect.bottom - editableSize.height, expandedRect.top); unitOffset = const Offset(0, 1); } // No overscrolling when encountering tall fonts/scripts that extend past // the ascent. - final double targetOffset = (additionalOffset + _scrollController.offset) - .clamp( - _scrollController.position.minScrollExtent, - _scrollController.position.maxScrollExtent, - ); + final double targetOffset = clampDouble( + additionalOffset + _scrollController.offset, + _scrollController.position.minScrollExtent, + _scrollController.position.maxScrollExtent, + ); final double offsetDelta = _scrollController.offset - targetOffset; return RevealedOffset(rect: rect.shift(unitOffset * offsetDelta), offset: targetOffset); @@ -2441,6 +2441,23 @@ class EditableTextState extends State with AutomaticKeepAliveClien _selectionOverlay?.updateForScroll(); } + void _createSelectionOverlay() { + _selectionOverlay = TextSelectionOverlay( + clipboardStatus: _clipboardStatus, + context: context, + value: _value, + debugRequiredFor: widget, + toolbarLayerLink: _toolbarLayerLink, + startHandleLayerLink: _startHandleLayerLink, + endHandleLayerLink: _endHandleLayerLink, + renderObject: renderEditable, + selectionControls: widget.selectionControls, + selectionDelegate: this, + dragStartBehavior: widget.dragStartBehavior, + onSelectionHandleTapped: widget.onSelectionHandleTapped, + ); + } + @pragma('vm:notify-debugger-on-exception') void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) { // We return early if the selection is not valid. This can happen when the @@ -2478,20 +2495,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien _selectionOverlay = null; } else { if (_selectionOverlay == null) { - _selectionOverlay = TextSelectionOverlay( - clipboardStatus: _clipboardStatus, - context: context, - value: _value, - debugRequiredFor: widget, - toolbarLayerLink: _toolbarLayerLink, - startHandleLayerLink: _startHandleLayerLink, - endHandleLayerLink: _endHandleLayerLink, - renderObject: renderEditable, - selectionControls: widget.selectionControls, - selectionDelegate: this, - dragStartBehavior: widget.dragStartBehavior, - onSelectionHandleTapped: widget.onSelectionHandleTapped, - ); + _createSelectionOverlay(); } else { _selectionOverlay!.update(_value); } @@ -2943,6 +2947,18 @@ class EditableTextState extends State with AutomaticKeepAliveClien if (shouldShowCaret) { _scheduleShowCaretOnScreen(withAnimation: true); } + + // Even if the value doesn't change, it may be necessary to focus and build + // the selection overlay. For example, this happens when right clicking an + // unfocused field that previously had a selection in the same spot. + if (value == textEditingValue) { + if (!widget.focusNode.hasFocus) { + widget.focusNode.requestFocus(); + _createSelectionOverlay(); + } + return; + } + _formatAndSetValue(value, cause, userInteraction: true); } diff --git a/packages/flutter/lib/src/widgets/icon_theme_data.dart b/packages/flutter/lib/src/widgets/icon_theme_data.dart index c1af339a4e1b6..0381efb16ea3a 100644 --- a/packages/flutter/lib/src/widgets/icon_theme_data.dart +++ b/packages/flutter/lib/src/widgets/icon_theme_data.dart @@ -84,7 +84,7 @@ class IconThemeData with Diagnosticable { final Color? color; /// An opacity to apply to both explicit and default icon colors. - double? get opacity => _opacity?.clamp(0.0, 1.0); + double? get opacity => _opacity == null ? null : clampDouble(_opacity!, 0.0, 1.0); final double? _opacity; /// The default size for icons. diff --git a/packages/flutter/lib/src/widgets/implicit_animations.dart b/packages/flutter/lib/src/widgets/implicit_animations.dart index 56665362cadac..78cc6274235d6 100644 --- a/packages/flutter/lib/src/widgets/implicit_animations.dart +++ b/packages/flutter/lib/src/widgets/implicit_animations.dart @@ -848,7 +848,7 @@ class _AnimatedPaddingState extends AnimatedWidgetBaseState { return Padding( padding: _padding! .evaluate(animation) - .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity), + .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity), // ignore_clamp_double_lint child: widget.child, ); } diff --git a/packages/flutter/lib/src/widgets/interactive_viewer.dart b/packages/flutter/lib/src/widgets/interactive_viewer.dart index 3362e067c4f8c..e9cc1f0ca587c 100644 --- a/packages/flutter/lib/src/widgets/interactive_viewer.dart +++ b/packages/flutter/lib/src/widgets/interactive_viewer.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/gestures.dart'; import 'package:flutter/physics.dart'; import 'package:vector_math/vector_math_64.dart' show Quad, Vector3, Matrix4; @@ -386,7 +387,7 @@ class InteractiveViewer extends StatefulWidget { // the point. final Vector3 l1P = point - l1; final Vector3 l1L2 = l2 - l1; - final double fraction = (l1P.dot(l1L2) / lengthSquared).clamp(0.0, 1.0); + final double fraction = clampDouble(l1P.dot(l1L2) / lengthSquared, 0.0, 1.0); return l1 + l1L2 * fraction; } @@ -659,7 +660,7 @@ class _InteractiveViewerState extends State with TickerProvid _viewport.height / _boundaryRect.height, ), ); - final double clampedTotalScale = totalScale.clamp( + final double clampedTotalScale = clampDouble(totalScale, widget.minScale, widget.maxScale, ); diff --git a/packages/flutter/lib/src/widgets/nested_scroll_view.dart b/packages/flutter/lib/src/widgets/nested_scroll_view.dart index 2781643371086..ce689c53a3445 100644 --- a/packages/flutter/lib/src/widgets/nested_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/nested_scroll_view.dart @@ -732,7 +732,7 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont double pixels, minRange, maxRange, correctionOffset; double extra = 0.0; if (innerPosition.pixels == innerPosition.minScrollExtent) { - pixels = _outerPosition!.pixels.clamp( + pixels = clampDouble(_outerPosition!.pixels, _outerPosition!.minScrollExtent, _outerPosition!.maxScrollExtent, ); // TODO(ianh): gracefully handle out-of-range outer positions @@ -799,7 +799,7 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont double unnestOffset(double value, _NestedScrollPosition source) { if (source == _outerPosition) - return value.clamp( + return clampDouble(value, _outerPosition!.minScrollExtent, _outerPosition!.maxScrollExtent, ); @@ -810,7 +810,7 @@ class _NestedScrollCoordinator implements ScrollActivityDelegate, ScrollHoldCont double nestOffset(double value, _NestedScrollPosition target) { if (target == _outerPosition) - return value.clamp( + return clampDouble(value, _outerPosition!.minScrollExtent, _outerPosition!.maxScrollExtent, ); @@ -1212,7 +1212,7 @@ class _NestedScrollPosition extends ScrollPosition implements ScrollActivityDele // 0.0, representing the end of the overscrolled portion. : pixels < 0.0 ? 0.0 : math.max(maxScrollExtent, pixels); final double oldPixels = pixels; - final double newPixels = (pixels - delta).clamp(min, max); + final double newPixels = clampDouble(pixels - delta, min, max); final double clampedDelta = newPixels - pixels; if (clampedDelta == 0.0) return delta; @@ -1268,7 +1268,7 @@ class _NestedScrollPosition extends ScrollPosition implements ScrollActivityDele final double max = delta < 0.0 ? double.infinity : math.max(maxScrollExtent, pixels); - final double newPixels = (pixels + delta).clamp(min, max); + final double newPixels = clampDouble(pixels + delta, min, max); final double clampedDelta = newPixels - pixels; if (clampedDelta == 0.0) return delta; @@ -1494,7 +1494,7 @@ class _NestedOuterBallisticScrollActivity extends BallisticScrollActivity { done = true; } } else { - value = value.clamp(metrics.minRange, metrics.maxRange); + value = clampDouble(value, metrics.minRange, metrics.maxRange); done = true; } final bool result = super.applyMoveTo(value + metrics.correctionOffset); diff --git a/packages/flutter/lib/src/widgets/overscroll_indicator.dart b/packages/flutter/lib/src/widgets/overscroll_indicator.dart index 329f21235479a..143930479d39c 100644 --- a/packages/flutter/lib/src/widgets/overscroll_indicator.dart +++ b/packages/flutter/lib/src/widgets/overscroll_indicator.dart @@ -255,10 +255,10 @@ class _GlowingOverscrollIndicatorState extends State final Offset position = renderer.globalToLocal(notification.dragDetails!.globalPosition); switch (notification.metrics.axis) { case Axis.horizontal: - controller!.pull(notification.overscroll.abs(), size.width, position.dy.clamp(0.0, size.height), size.height); + controller!.pull(notification.overscroll.abs(), size.width, clampDouble(position.dy, 0.0, size.height), size.height); break; case Axis.vertical: - controller!.pull(notification.overscroll.abs(), size.height, position.dx.clamp(0.0, size.width), size.width); + controller!.pull(notification.overscroll.abs(), size.height, clampDouble(position.dx, 0.0, size.width), size.width); break; } } @@ -405,9 +405,9 @@ class _GlowController extends ChangeNotifier { assert(velocity >= 0.0); _pullRecedeTimer?.cancel(); _pullRecedeTimer = null; - velocity = velocity.clamp(_minVelocity, _maxVelocity); + velocity = clampDouble(velocity, _minVelocity, _maxVelocity); _glowOpacityTween.begin = _state == _GlowState.idle ? 0.3 : _glowOpacity.value; - _glowOpacityTween.end = (velocity * _velocityGlowFactor).clamp(_glowOpacityTween.begin!, _maxOpacity); + _glowOpacityTween.end = clampDouble(velocity * _velocityGlowFactor, _glowOpacityTween.begin!, _maxOpacity); _glowSizeTween.begin = _glowSize.value; _glowSizeTween.end = math.min(0.025 + 7.5e-7 * velocity * velocity, 1.0); _glowController.duration = Duration(milliseconds: (0.15 + velocity * 0.02).round()); @@ -716,7 +716,7 @@ class _StretchingOverscrollIndicatorState extends State= 0.0); - velocity = velocity.clamp(1, 10000); + velocity = clampDouble(velocity, 1, 10000); _stretchSizeTween.begin = _stretchSize.value; _stretchSizeTween.end = math.min(_stretchIntensity + (_flingFriction / velocity), 1.0); _stretchController.duration = Duration(milliseconds: (velocity * 0.02).round()); diff --git a/packages/flutter/lib/src/widgets/page_view.dart b/packages/flutter/lib/src/widgets/page_view.dart index ee27b4038a457..04beaf5a94096 100644 --- a/packages/flutter/lib/src/widgets/page_view.dart +++ b/packages/flutter/lib/src/widgets/page_view.dart @@ -4,7 +4,7 @@ import 'dart:math' as math; -import 'package:flutter/foundation.dart' show precisionErrorTolerance; +import 'package:flutter/foundation.dart' show precisionErrorTolerance, clampDouble; import 'package:flutter/gestures.dart' show DragStartBehavior; import 'package:flutter/rendering.dart'; @@ -296,7 +296,7 @@ class PageMetrics extends FixedScrollMetrics { /// The current page displayed in the [PageView]. double? get page { - return math.max(0.0, pixels.clamp(minScrollExtent, maxScrollExtent)) / + return math.max(0.0, clampDouble(pixels, minScrollExtent, maxScrollExtent)) / math.max(1.0, viewportDimension * viewportFraction); } @@ -396,7 +396,7 @@ class _PagePosition extends ScrollPositionWithSingleContext implements PageMetri ); return !hasPixels || !hasContentDimensions ? null - : _cachedPage ?? getPageFromPixels(pixels.clamp(minScrollExtent, maxScrollExtent), viewportDimension); + : _cachedPage ?? getPageFromPixels(clampDouble(pixels, minScrollExtent, maxScrollExtent), viewportDimension); } @override diff --git a/packages/flutter/lib/src/widgets/scroll_metrics.dart b/packages/flutter/lib/src/widgets/scroll_metrics.dart index 3231ac6c3230f..10c20da60c46b 100644 --- a/packages/flutter/lib/src/widgets/scroll_metrics.dart +++ b/packages/flutter/lib/src/widgets/scroll_metrics.dart @@ -116,9 +116,9 @@ mixin ScrollMetrics { assert(minScrollExtent <= maxScrollExtent); return viewportDimension // "above" overscroll value - - (minScrollExtent - pixels).clamp(0, viewportDimension) + - clampDouble(minScrollExtent - pixels, 0, viewportDimension) // "below" overscroll value - - (pixels - maxScrollExtent).clamp(0, viewportDimension); + - clampDouble(pixels - maxScrollExtent, 0, viewportDimension); } /// The quantity of content conceptually "below" the viewport in the scrollable. diff --git a/packages/flutter/lib/src/widgets/scroll_physics.dart b/packages/flutter/lib/src/widgets/scroll_physics.dart index 6ce2fe6503bb5..a98dfab4a9c9e 100644 --- a/packages/flutter/lib/src/widgets/scroll_physics.dart +++ b/packages/flutter/lib/src/widgets/scroll_physics.dart @@ -554,7 +554,7 @@ class RangeMaintainingScrollPhysics extends ScrollPhysics { double result = super.adjustPositionForNewDimensions(oldPosition: oldPosition, newPosition: newPosition, isScrolling: isScrolling, velocity: velocity); if (enforceBoundary) { // ...but if they put us out of range then reinforce the boundary. - result = result.clamp(newPosition.minScrollExtent, newPosition.maxScrollExtent); + result = clampDouble(result, newPosition.minScrollExtent, newPosition.maxScrollExtent); } return result; } diff --git a/packages/flutter/lib/src/widgets/scroll_position.dart b/packages/flutter/lib/src/widgets/scroll_position.dart index 92b0c429d8e02..84a56662ef1f1 100644 --- a/packages/flutter/lib/src/widgets/scroll_position.dart +++ b/packages/flutter/lib/src/widgets/scroll_position.dart @@ -710,16 +710,16 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { double target; switch (alignmentPolicy) { case ScrollPositionAlignmentPolicy.explicit: - target = viewport.getOffsetToReveal(object, alignment, rect: targetRect).offset.clamp(minScrollExtent, maxScrollExtent); + target = clampDouble(viewport.getOffsetToReveal(object, alignment, rect: targetRect).offset, minScrollExtent, maxScrollExtent); break; case ScrollPositionAlignmentPolicy.keepVisibleAtEnd: - target = viewport.getOffsetToReveal(object, 1.0, rect: targetRect).offset.clamp(minScrollExtent, maxScrollExtent); + target = clampDouble(viewport.getOffsetToReveal(object, 1.0, rect: targetRect).offset, minScrollExtent, maxScrollExtent); if (target < pixels) { target = pixels; } break; case ScrollPositionAlignmentPolicy.keepVisibleAtStart: - target = viewport.getOffsetToReveal(object, 0.0, rect: targetRect).offset.clamp(minScrollExtent, maxScrollExtent); + target = clampDouble(viewport.getOffsetToReveal(object, 0.0, rect: targetRect).offset, minScrollExtent, maxScrollExtent); if (target > pixels) { target = pixels; } @@ -824,7 +824,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { assert(clamp != null); if (clamp!) - to = to.clamp(minScrollExtent, maxScrollExtent); + to = clampDouble(to, minScrollExtent, maxScrollExtent); return super.moveTo(to, duration: duration, curve: curve); } diff --git a/packages/flutter/lib/src/widgets/scroll_simulation.dart b/packages/flutter/lib/src/widgets/scroll_simulation.dart index 03732ad8ebf91..eb12592b79e78 100644 --- a/packages/flutter/lib/src/widgets/scroll_simulation.dart +++ b/packages/flutter/lib/src/widgets/scroll_simulation.dart @@ -212,13 +212,13 @@ class ClampingScrollSimulation extends Simulation { @override double x(double time) { - final double t = (time / _duration).clamp(0.0, 1.0); + final double t = clampDouble(time / _duration, 0.0, 1.0); return position + _distance * _flingDistancePenetration(t) * velocity.sign; } @override double dx(double time) { - final double t = (time / _duration).clamp(0.0, 1.0); + final double t = clampDouble(time / _duration, 0.0, 1.0); return _distance * _flingVelocityPenetration(t) * velocity.sign / _duration; } diff --git a/packages/flutter/lib/src/widgets/scrollbar.dart b/packages/flutter/lib/src/widgets/scrollbar.dart index ebd9e5fe27139..413a4a1f3cb13 100644 --- a/packages/flutter/lib/src/widgets/scrollbar.dart +++ b/packages/flutter/lib/src/widgets/scrollbar.dart @@ -539,8 +539,11 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { // Thumb extent reflects fraction of content visible, as long as this // isn't less than the absolute minimum size. // _totalContentExtent >= viewportDimension, so (_totalContentExtent - _mainAxisPadding) > 0 - final double fractionVisible = ((_lastMetrics!.extentInside - _mainAxisPadding) / (_totalContentExtent - _mainAxisPadding)) - .clamp(0.0, 1.0); + final double fractionVisible = clampDouble( + (_lastMetrics!.extentInside - _mainAxisPadding) / + (_totalContentExtent - _mainAxisPadding), + 0.0, + 1.0); final double thumbExtent = math.max( math.min(_trackExtent, minOverscrollLength), @@ -563,11 +566,11 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { // [0.8, 1.0] to [0.0, 1.0], so 0% to 20% of overscroll will produce // values for the thumb that range between minLength and the smallest // possible value, minOverscrollLength. - : safeMinLength * (1.0 - fractionOverscrolled.clamp(0.0, 0.2) / 0.2); + : safeMinLength * (1.0 - clampDouble(fractionOverscrolled, 0.0, 0.2) / 0.2); // The `thumbExtent` should be no greater than `trackSize`, otherwise // the scrollbar may scroll towards the wrong direction. - return thumbExtent.clamp(newMinLength, _trackExtent); + return clampDouble(thumbExtent, newMinLength, _trackExtent); } @override @@ -611,7 +614,7 @@ class ScrollbarPainter extends ChangeNotifier implements CustomPainter { final double scrollableExtent = metrics.maxScrollExtent - metrics.minScrollExtent; final double fractionPast = (scrollableExtent > 0) - ? ((metrics.pixels - metrics.minScrollExtent) / scrollableExtent).clamp(0.0, 1.0) + ? clampDouble((metrics.pixels - metrics.minScrollExtent) / scrollableExtent, 0.0, 1.0) : 0; return (_isReversed ? 1 - fractionPast : fractionPast) * (_trackExtent - thumbExtent); @@ -1444,6 +1447,9 @@ class RawScrollbarState extends State with TickerProv } bool _debugCheckHasValidScrollPosition() { + if (!mounted) { + return true; + } final ScrollController? scrollController = widget.controller ?? PrimaryScrollController.of(context); final bool tryPrimary = widget.controller == null; final String controllerForError = tryPrimary @@ -1608,7 +1614,7 @@ class RawScrollbarState extends State with TickerProv case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: - newPosition = newPosition.clamp(position.minScrollExtent, position.maxScrollExtent); + newPosition = clampDouble(newPosition, position.minScrollExtent, position.maxScrollExtent); break; case TargetPlatform.iOS: case TargetPlatform.android: diff --git a/packages/flutter/lib/src/widgets/sliver_fill.dart b/packages/flutter/lib/src/widgets/sliver_fill.dart index 51942d151224a..d199f5fc3c81f 100644 --- a/packages/flutter/lib/src/widgets/sliver_fill.dart +++ b/packages/flutter/lib/src/widgets/sliver_fill.dart @@ -60,7 +60,7 @@ class SliverFillViewport extends StatelessWidget { @override Widget build(BuildContext context) { return _SliverFractionalPadding( - viewportFraction: padEnds ? (1 - viewportFraction).clamp(0, 1) / 2 : 0, + viewportFraction: padEnds ? clampDouble(1 - viewportFraction, 0, 1) / 2 : 0, sliver: _SliverFillViewportRenderObjectWidget( viewportFraction: viewportFraction, delegate: delegate, diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index a21fede1fcc3d..e5567bba0560e 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -1208,12 +1208,28 @@ class _SelectionHandleOverlayState extends State<_SelectionHandleOverlay> with S alignment: Alignment.topLeft, width: interactiveRect.width, height: interactiveRect.height, - child: GestureDetector( + child: RawGestureDetector( behavior: HitTestBehavior.translucent, - dragStartBehavior: widget.dragStartBehavior, - onPanStart: widget.onSelectionHandleDragStart, - onPanUpdate: widget.onSelectionHandleDragUpdate, - onPanEnd: widget.onSelectionHandleDragEnd, + gestures: { + PanGestureRecognizer: GestureRecognizerFactoryWithHandlers( + () => PanGestureRecognizer( + debugOwner: this, + // Mouse events select the text and do not drag the cursor. + supportedDevices: { + PointerDeviceKind.touch, + PointerDeviceKind.stylus, + PointerDeviceKind.unknown, + }, + ), + (PanGestureRecognizer instance) { + instance + ..dragStartBehavior = widget.dragStartBehavior + ..onStart = widget.onSelectionHandleDragStart + ..onUpdate = widget.onSelectionHandleDragUpdate + ..onEnd = widget.onSelectionHandleDragEnd; + }, + ), + }, child: Padding( padding: EdgeInsets.only( left: padding.left, diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index a2f09f67b019f..009853db63116 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -2897,7 +2897,7 @@ void main() { // The selection doesn't move beyond the left handle. There's always at // least 1 char selected. expect(controller.selection.extentOffset, 5); - }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); + }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); testWidgets('Can select text by dragging with a mouse', (WidgetTester tester) async { final TextEditingController controller = TextEditingController(); @@ -3571,7 +3571,7 @@ void main() { expect(left.opacity.value, equals(1.0)); expect(right.opacity.value, equals(1.0)); - }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); + }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); testWidgets('when CupertinoTextField would be blocked by keyboard, it is shown with enough space for the selection handle', (WidgetTester tester) async { final ScrollController scrollController = ScrollController(); @@ -5663,4 +5663,56 @@ void main() { variant: TargetPlatformVariant.all(), skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu. ); + + testWidgets('Can right click to focus multiple times', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/pull/103228 + final FocusNode focusNode1 = FocusNode(); + final FocusNode focusNode2 = FocusNode(); + final UniqueKey key1 = UniqueKey(); + final UniqueKey key2 = UniqueKey(); + await tester.pumpWidget( + CupertinoApp( + home: Column( + children: [ + CupertinoTextField( + key: key1, + focusNode: focusNode1, + ), + CupertinoTextField( + key: key2, + focusNode: focusNode2, + ), + ], + ), + ), + ); + + // Interact with the field to establish the input connection. + await tester.tapAt( + tester.getCenter(find.byKey(key1)), + buttons: kSecondaryMouseButton, + ); + await tester.pump(); + + expect(focusNode1.hasFocus, isTrue); + expect(focusNode2.hasFocus, isFalse); + + await tester.tapAt( + tester.getCenter(find.byKey(key2)), + buttons: kSecondaryMouseButton, + ); + await tester.pump(); + + expect(focusNode1.hasFocus, isFalse); + expect(focusNode2.hasFocus, isTrue); + + await tester.tapAt( + tester.getCenter(find.byKey(key1)), + buttons: kSecondaryMouseButton, + ); + await tester.pump(); + + expect(focusNode1.hasFocus, isTrue); + expect(focusNode2.hasFocus, isFalse); + }); } diff --git a/packages/flutter/test/foundation/math_test.dart b/packages/flutter/test/foundation/math_test.dart new file mode 100644 index 0000000000000..2c50bf766ba5d --- /dev/null +++ b/packages/flutter/test/foundation/math_test.dart @@ -0,0 +1,16 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('clampDouble', () { + expect(clampDouble(-1.0, 0.0, 1.0), equals(0.0)); + expect(clampDouble(2.0, 0.0, 1.0), equals(1.0)); + expect(clampDouble(double.infinity, 0.0, 1.0), equals(1.0)); + expect(clampDouble(-double.infinity, 0.0, 1.0), equals(0.0)); + expect(clampDouble(double.nan, 0.0, double.infinity), equals(double.infinity)); + }); +} diff --git a/packages/flutter/test/material/app_bar_test.dart b/packages/flutter/test/material/app_bar_test.dart index 635e17c694f33..1aec5d4539344 100644 --- a/packages/flutter/test/material/app_bar_test.dart +++ b/packages/flutter/test/material/app_bar_test.dart @@ -1011,6 +1011,53 @@ void main() { expect(getMaterial().elevation, 10); }); + testWidgets('scrolledUnderElevation with nested scroll view', (WidgetTester tester) async { + Widget buildAppBar({double? scrolledUnderElevation}) { + return MaterialApp( + theme: ThemeData(useMaterial3: true), + home: Scaffold( + appBar: AppBar( + title: const Text('Title'), + scrolledUnderElevation: scrolledUnderElevation, + notificationPredicate: (ScrollNotification notification) { + return notification.depth == 1; + }, + ), + body: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: 4, + itemBuilder: (BuildContext context, int index) { + return SizedBox( + height: 600.0, + width: 800.0, + child: ListView.builder( + itemCount: 100, + itemBuilder: (BuildContext context, int index) => + ListTile(title: Text('Item $index')), + ), + ); + }, + ), + ), + ); + } + + Material getMaterial() => tester.widget(find.descendant( + of: find.byType(AppBar), + matching: find.byType(Material), + )); + + await tester.pumpWidget(buildAppBar(scrolledUnderElevation: 10)); + // Starts with the base elevation. + expect(getMaterial().elevation, 0.0); + + await tester.fling(find.text('Item 2'), const Offset(0.0, -600.0), 2000.0); + await tester.pumpAndSettle(); + + // After scrolling it should be the scrolledUnderElevation. + expect(getMaterial().elevation, 10); + }); + group('SliverAppBar elevation', () { Widget buildSliverAppBar(bool forceElevated, {double? elevation, double? themeElevation}) { return MaterialApp( diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 8112632263e07..7b1f4c4261664 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -9400,7 +9400,7 @@ void main() { expect(left.opacity.value, equals(1.0)); expect(right.opacity.value, equals(1.0)); - }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); + }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); testWidgets('iPad Scribble selection change shows selection handles', (WidgetTester tester) async { const String testText = 'lorem ipsum'; @@ -11399,4 +11399,58 @@ void main() { variant: TargetPlatformVariant.all(), skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu. ); + + testWidgets('Can right click to focus multiple times', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/pull/103228 + final FocusNode focusNode1 = FocusNode(); + final FocusNode focusNode2 = FocusNode(); + final UniqueKey key1 = UniqueKey(); + final UniqueKey key2 = UniqueKey(); + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Column( + children: [ + TextField( + key: key1, + focusNode: focusNode1, + ), + TextField( + key: key2, + focusNode: focusNode2, + ), + ], + ), + ), + ), + ); + + // Interact with the field to establish the input connection. + await tester.tapAt( + tester.getCenter(find.byKey(key1)), + buttons: kSecondaryButton, + ); + await tester.pump(); + + expect(focusNode1.hasFocus, isTrue); + expect(focusNode2.hasFocus, isFalse); + + await tester.tapAt( + tester.getCenter(find.byKey(key2)), + buttons: kSecondaryButton, + ); + await tester.pump(); + + expect(focusNode1.hasFocus, isFalse); + expect(focusNode2.hasFocus, isTrue); + + await tester.tapAt( + tester.getCenter(find.byKey(key1)), + buttons: kSecondaryButton, + ); + await tester.pump(); + + expect(focusNode1.hasFocus, isTrue); + expect(focusNode2.hasFocus, isFalse); + }); } diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart index b8b8e8ae5b63b..dddebfdd48250 100644 --- a/packages/flutter/test/material/tooltip_test.dart +++ b/packages/flutter/test/material/tooltip_test.dart @@ -801,15 +801,16 @@ void main() { tooltipKey.currentState?.ensureTooltipVisible(); await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0) - final RenderBox tip = tester.renderObject( - _findTooltipContainer(tooltipText), - ); + final RenderBox tip = tester.renderObject(_findTooltipContainer(tooltipText)); expect(tip.size.height, equals(32.0)); expect(tip.size.width, equals(74.0)); expect(tip, paints..rrect( rrect: RRect.fromRectAndRadius(tip.paintBounds, const Radius.circular(4.0)), color: const Color(0xe6616161), )); + + final Container tooltipContainer = tester.firstWidget(_findTooltipContainer(tooltipText)); + expect(tooltipContainer.padding, const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0)); }); testWidgets('Tooltip default size, shape, and color test for Desktop', (WidgetTester tester) async { @@ -830,14 +831,15 @@ void main() { final RenderParagraph tooltipRenderParagraph = tester.renderObject(find.text(tooltipText)); expect(tooltipRenderParagraph.textSize.height, equals(12.0)); - final RenderBox tooltipContainer = tester.renderObject( - _findTooltipContainer(tooltipText), - ); - expect(tooltipContainer.size.height, equals(24.0)); - expect(tooltipContainer, paints..rrect( - rrect: RRect.fromRectAndRadius(tooltipContainer.paintBounds, const Radius.circular(4.0)), + final RenderBox tooltipRenderBox = tester.renderObject(_findTooltipContainer(tooltipText)); + expect(tooltipRenderBox.size.height, equals(24.0)); + expect(tooltipRenderBox, paints..rrect( + rrect: RRect.fromRectAndRadius(tooltipRenderBox.paintBounds, const Radius.circular(4.0)), color: const Color(0xe6616161), )); + + final Container tooltipContainer = tester.firstWidget(_findTooltipContainer(tooltipText)); + expect(tooltipContainer.padding, const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0)); }, variant: const TargetPlatformVariant({TargetPlatform.macOS, TargetPlatform.linux, TargetPlatform.windows})); testWidgets('Can tooltip decoration be customized', (WidgetTester tester) async { @@ -1437,7 +1439,7 @@ void main() { tip = tester.renderObject( _findTooltipContainer(tooltipText), ); - expect(tip.size.height, equals(56.0)); + expect(tip.size.height, equals(64.0)); }); testWidgets('Tooltip text displays with richMessage', (WidgetTester tester) async { diff --git a/packages/flutter/test/rendering/viewport_test.dart b/packages/flutter/test/rendering/viewport_test.dart index 545598d4da6e6..67b98975247e4 100644 --- a/packages/flutter/test/rendering/viewport_test.dart +++ b/packages/flutter/test/rendering/viewport_test.dart @@ -1708,9 +1708,8 @@ void main() { ' If this widget is always nested in a scrollable widget there is\n' ' no need to use a viewport because there will always be enough\n' ' horizontal space for the children. In this case, consider using a\n' - ' Row instead. Otherwise, consider using the "shrinkWrap" property\n' - ' (or a ShrinkWrappingViewport) to size the width of the viewport\n' - ' to the sum of the widths of its children.\n', + ' Row or Wrap instead. Otherwise, consider using a CustomScrollView\n' + ' to concatenate arbitrary slivers into a single scrollable.\n', ); }); @@ -1743,9 +1742,9 @@ void main() { ' If this widget is always nested in a scrollable widget there is\n' ' no need to use a viewport because there will always be enough\n' ' vertical space for the children. In this case, consider using a\n' - ' Column instead. Otherwise, consider using the "shrinkWrap"\n' - ' property (or a ShrinkWrappingViewport) to size the height of the\n' - ' viewport to the sum of the heights of its children.\n', + ' Column or Wrap instead. Otherwise, consider using a\n' + ' CustomScrollView to concatenate arbitrary slivers into a single\n' + ' scrollable.\n', ); }); }); diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index af841f5ca0b27..0f32d3ed0c74c 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -7825,7 +7825,7 @@ void main() { // On web, we don't show the Flutter toolbar and instead rely on the browser // toolbar. Until we change that, this test should remain skipped. skip: kIsWeb, // [intended] - variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }) + variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }) ); testWidgets("scrolling doesn't bounce", (WidgetTester tester) async { @@ -11982,6 +11982,221 @@ void main() { expect(tester.takeException(), null); // On web, the text selection toolbar cut button is handled by the browser. }, skip: kIsWeb); // [intended] + + group('Mac document shortcuts', () { + testWidgets('ctrl-A/E', (WidgetTester tester) async { + final String targetPlatformString = defaultTargetPlatform.toString(); + final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); + final TextEditingController controller = TextEditingController(text: testText); + controller.selection = const TextSelection( + baseOffset: 0, + extentOffset: 0, + affinity: TextAffinity.upstream, + ); + await tester.pumpWidget(MaterialApp( + home: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 400, + child: EditableText( + maxLines: 10, + controller: controller, + showSelectionHandles: true, + autofocus: true, + focusNode: FocusNode(), + style: Typography.material2018().black.subtitle1!, + cursorColor: Colors.blue, + backgroundCursorColor: Colors.grey, + selectionControls: materialTextSelectionControls, + keyboardType: TextInputType.text, + textAlign: TextAlign.right, + ), + ), + ), + )); + + await tester.pump(); // Wait for autofocus to take effect. + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 0); + + await tester.sendKeyDownEvent( + LogicalKeyboardKey.controlLeft, + platform: platform, + ); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.keyE, platform: platform); + await tester.pump(); + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft, platform: platform); + await tester.pump(); + + expect( + controller.selection, + equals( + const TextSelection.collapsed( + offset: 19, + affinity: TextAffinity.upstream, + ), + ), + reason: 'on $platform', + ); + + await tester.sendKeyDownEvent( + LogicalKeyboardKey.controlLeft, + platform: platform, + ); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.keyA, platform: platform); + await tester.pump(); + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft, platform: platform); + await tester.pump(); + + expect( + controller.selection, + equals( + const TextSelection.collapsed( + offset: 0, + ), + ), + reason: 'on $platform', + ); + }, + skip: kIsWeb, // [intended] on web these keys are handled by the browser. + variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }), + ); + + testWidgets('ctrl-F/B', (WidgetTester tester) async { + final String targetPlatformString = defaultTargetPlatform.toString(); + final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); + final TextEditingController controller = TextEditingController(text: testText); + controller.selection = const TextSelection( + baseOffset: 0, + extentOffset: 0, + affinity: TextAffinity.upstream, + ); + await tester.pumpWidget(MaterialApp( + home: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 400, + child: EditableText( + maxLines: 10, + controller: controller, + showSelectionHandles: true, + autofocus: true, + focusNode: FocusNode(), + style: Typography.material2018().black.subtitle1!, + cursorColor: Colors.blue, + backgroundCursorColor: Colors.grey, + selectionControls: materialTextSelectionControls, + keyboardType: TextInputType.text, + textAlign: TextAlign.right, + ), + ), + ), + )); + + await tester.pump(); // Wait for autofocus to take effect. + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 0); + + await tester.sendKeyDownEvent( + LogicalKeyboardKey.controlLeft, + platform: platform, + ); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.keyF, platform: platform); + await tester.pump(); + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft, platform: platform); + await tester.pump(); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 1); + + await tester.sendKeyDownEvent( + LogicalKeyboardKey.controlLeft, + platform: platform, + ); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.keyB, platform: platform); + await tester.pump(); + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft, platform: platform); + await tester.pump(); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 0); + }, + skip: kIsWeb, // [intended] on web these keys are handled by the browser. + variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }), + ); + + testWidgets('ctrl-N/P', (WidgetTester tester) async { + final String targetPlatformString = defaultTargetPlatform.toString(); + final String platform = targetPlatformString.substring(targetPlatformString.indexOf('.') + 1).toLowerCase(); + final TextEditingController controller = TextEditingController(text: testText); + controller.selection = const TextSelection( + baseOffset: 0, + extentOffset: 0, + affinity: TextAffinity.upstream, + ); + await tester.pumpWidget(MaterialApp( + home: Align( + alignment: Alignment.topLeft, + child: SizedBox( + width: 400, + child: EditableText( + maxLines: 10, + controller: controller, + showSelectionHandles: true, + autofocus: true, + focusNode: FocusNode(), + style: Typography.material2018().black.subtitle1!, + cursorColor: Colors.blue, + backgroundCursorColor: Colors.grey, + selectionControls: materialTextSelectionControls, + keyboardType: TextInputType.text, + textAlign: TextAlign.right, + ), + ), + ), + )); + + await tester.pump(); // Wait for autofocus to take effect. + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 0); + + await tester.sendKeyDownEvent( + LogicalKeyboardKey.controlLeft, + platform: platform, + ); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.keyN, platform: platform); + await tester.pump(); + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft, platform: platform); + await tester.pump(); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 20); + + await tester.sendKeyDownEvent( + LogicalKeyboardKey.controlLeft, + platform: platform, + ); + await tester.pump(); + await tester.sendKeyEvent(LogicalKeyboardKey.keyP, platform: platform); + await tester.pump(); + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft, platform: platform); + await tester.pump(); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 0); + }, + skip: kIsWeb, // [intended] on web these keys are handled by the browser. + variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS }), + ); + }); } class UnsettableController extends TextEditingController { diff --git a/packages/flutter/test/widgets/scrollbar_test.dart b/packages/flutter/test/widgets/scrollbar_test.dart index d0ce262492799..3b551a9d1b150 100644 --- a/packages/flutter/test/widgets/scrollbar_test.dart +++ b/packages/flutter/test/widgets/scrollbar_test.dart @@ -2444,4 +2444,42 @@ void main() { } expect(() => tester.pumpWidget(buildApp()), throwsAssertionError); }); + + testWidgets('Skip the ScrollPosition check if the bar was unmounted', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/103939 + final ScrollController scrollController = ScrollController(); + Widget buildApp(bool buildBar) { + return Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: MediaQueryData( + invertColors: buildBar, // Trigger a post frame check before unmount. + ), + child: PrimaryScrollController( + controller: scrollController, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + Widget content = const SingleChildScrollView( + child: SizedBox(width: 4000.0, height: 4000.0), + ); + if (buildBar) { + content = RawScrollbar( + thumbVisibility: true, + child: content, + ); + } + return content; + }, + ), + ), + ), + ); + } + + await tester.pumpWidget(buildApp(true)); + + await tester.pumpWidget(buildApp(false)); + + // Go without throw. + }); } diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index 3e28b67093928..e29917efcf6c6 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -4279,7 +4279,7 @@ void main() { expect(left.opacity.value, equals(1.0)); expect(right.opacity.value, equals(1.0)); - }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); + }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); testWidgets('Long press shows handles and toolbar', (WidgetTester tester) async { await tester.pumpWidget( diff --git a/packages/flutter/test/widgets/text_selection_test.dart b/packages/flutter/test/widgets/text_selection_test.dart index 80c36f6d09253..6de7ba5a3a516 100644 --- a/packages/flutter/test/widgets/text_selection_test.dart +++ b/packages/flutter/test/widgets/text_selection_test.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'clipboard_utils.dart'; +import 'editable_text_utils.dart'; void main() { late int tapCount; @@ -628,7 +629,7 @@ void main() { }); testWidgets('Mouse drag does not show handles nor toolbar', (WidgetTester tester) async { - // Regressing test for https://github.com/flutter/flutter/issues/69001 + // Regression test for https://github.com/flutter/flutter/issues/69001 await tester.pumpWidget( const MaterialApp( home: Scaffold( @@ -652,6 +653,231 @@ void main() { expect(editableText.selectionOverlay!.toolbarIsVisible, isFalse); }); + testWidgets('Mouse drag selects and cannot drag cursor', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/102928 + final TextEditingController controller = TextEditingController( + text: 'I love flutter!', + ); + final GlobalKey editableTextKey = GlobalKey(); + final FakeTextSelectionGestureDetectorBuilderDelegate delegate = FakeTextSelectionGestureDetectorBuilderDelegate( + editableTextKey: editableTextKey, + forcePressEnabled: false, + selectionEnabled: true, + ); + final TextSelectionGestureDetectorBuilder provider = + TextSelectionGestureDetectorBuilder(delegate: delegate); + + await tester.pumpWidget( + MaterialApp( + home: provider.buildGestureDetector( + behavior: HitTestBehavior.translucent, + child: EditableText( + key: editableTextKey, + controller: controller, + focusNode: FocusNode(), + backgroundCursorColor: Colors.white, + cursorColor: Colors.white, + style: const TextStyle(), + selectionControls: materialTextSelectionControls, + ), + ), + ), + ); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, -1); + + final Offset position = textOffsetToPosition(tester, 4); + + await tester.tapAt(position); + await tester.pump(); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 4); + + final TestGesture gesture = await tester.startGesture(position, kind: PointerDeviceKind.mouse); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.moveTo(textOffsetToPosition(tester, 7)); + await tester.pump(); + await gesture.moveTo(textOffsetToPosition(tester, 10)); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(controller.selection.isCollapsed, isFalse); + expect(controller.selection.baseOffset, 4); + expect(controller.selection.extentOffset, 10); + }); + + testWidgets('Touch drag moves the cursor', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/102928 + final TextEditingController controller = TextEditingController( + text: 'I love flutter!', + ); + final GlobalKey editableTextKey = GlobalKey(); + final FakeTextSelectionGestureDetectorBuilderDelegate delegate = FakeTextSelectionGestureDetectorBuilderDelegate( + editableTextKey: editableTextKey, + forcePressEnabled: false, + selectionEnabled: true, + ); + final TextSelectionGestureDetectorBuilder provider = + TextSelectionGestureDetectorBuilder(delegate: delegate); + + await tester.pumpWidget( + MaterialApp( + home: provider.buildGestureDetector( + behavior: HitTestBehavior.translucent, + child: EditableText( + key: editableTextKey, + controller: controller, + focusNode: FocusNode(), + backgroundCursorColor: Colors.white, + cursorColor: Colors.white, + style: const TextStyle(), + selectionControls: materialTextSelectionControls, + ), + ), + ), + ); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, -1); + + final Offset position = textOffsetToPosition(tester, 4); + + await tester.tapAt(position); + await tester.pump(); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 4); + + final TestGesture gesture = await tester.startGesture(position); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.moveTo(textOffsetToPosition(tester, 7)); + await tester.pump(); + await gesture.moveTo(textOffsetToPosition(tester, 10)); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 10); + }); + + testWidgets('Stylus drag moves the cursor', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/102928 + final TextEditingController controller = TextEditingController( + text: 'I love flutter!', + ); + final GlobalKey editableTextKey = GlobalKey(); + final FakeTextSelectionGestureDetectorBuilderDelegate delegate = FakeTextSelectionGestureDetectorBuilderDelegate( + editableTextKey: editableTextKey, + forcePressEnabled: false, + selectionEnabled: true, + ); + final TextSelectionGestureDetectorBuilder provider = + TextSelectionGestureDetectorBuilder(delegate: delegate); + + await tester.pumpWidget( + MaterialApp( + home: provider.buildGestureDetector( + behavior: HitTestBehavior.translucent, + child: EditableText( + key: editableTextKey, + controller: controller, + focusNode: FocusNode(), + backgroundCursorColor: Colors.white, + cursorColor: Colors.white, + style: const TextStyle(), + selectionControls: materialTextSelectionControls, + ), + ), + ), + ); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, -1); + + final Offset position = textOffsetToPosition(tester, 4); + + await tester.tapAt(position); + await tester.pump(); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 4); + + final TestGesture gesture = await tester.startGesture(position, kind: PointerDeviceKind.stylus); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.moveTo(textOffsetToPosition(tester, 7)); + await tester.pump(); + await gesture.moveTo(textOffsetToPosition(tester, 10)); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 10); + }); + + testWidgets('Drag of unknown type moves the cursor', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/102928 + final TextEditingController controller = TextEditingController( + text: 'I love flutter!', + ); + final GlobalKey editableTextKey = GlobalKey(); + final FakeTextSelectionGestureDetectorBuilderDelegate delegate = FakeTextSelectionGestureDetectorBuilderDelegate( + editableTextKey: editableTextKey, + forcePressEnabled: false, + selectionEnabled: true, + ); + final TextSelectionGestureDetectorBuilder provider = + TextSelectionGestureDetectorBuilder(delegate: delegate); + + await tester.pumpWidget( + MaterialApp( + home: provider.buildGestureDetector( + behavior: HitTestBehavior.translucent, + child: EditableText( + key: editableTextKey, + controller: controller, + focusNode: FocusNode(), + backgroundCursorColor: Colors.white, + cursorColor: Colors.white, + style: const TextStyle(), + selectionControls: materialTextSelectionControls, + ), + ), + ), + ); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, -1); + + final Offset position = textOffsetToPosition(tester, 4); + + await tester.tapAt(position); + await tester.pump(); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 4); + + final TestGesture gesture = await tester.startGesture(position, kind: PointerDeviceKind.unknown); + addTearDown(gesture.removePointer); + await tester.pump(); + await gesture.moveTo(textOffsetToPosition(tester, 7)); + await tester.pump(); + await gesture.moveTo(textOffsetToPosition(tester, 10)); + await tester.pump(); + await gesture.up(); + await tester.pumpAndSettle(); + + expect(controller.selection.isCollapsed, isTrue); + expect(controller.selection.baseOffset, 10); + }); + testWidgets('test TextSelectionGestureDetectorBuilder drag with RenderEditable viewport offset change', (WidgetTester tester) async { await pumpTextSelectionGestureDetectorBuilder(tester); final FakeRenderEditable renderEditable = tester.renderObject(find.byType(FakeEditable)); @@ -767,7 +993,7 @@ void main() { of: find.byType(CompositedTransformFollower), matching: find.descendant( of: find.byType(FadeTransition), - matching: find.byType(GestureDetector), + matching: find.byType(RawGestureDetector), ), ); diff --git a/packages/flutter/test_private/test/animated_icons_private_test.dart.tmpl b/packages/flutter/test_private/test/animated_icons_private_test.dart.tmpl index 482f76d355870..96d23c4d4bbd2 100644 --- a/packages/flutter/test_private/test/animated_icons_private_test.dart.tmpl +++ b/packages/flutter/test_private/test/animated_icons_private_test.dart.tmpl @@ -15,6 +15,7 @@ import 'dart:math' as math show pi; import 'dart:ui' show lerpDouble, Offset; import 'dart:ui' as ui show Paint, Path, Canvas; +import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/packages/flutter_localizations/lib/src/l10n/README.md b/packages/flutter_localizations/lib/src/l10n/README.md index 0146fdfe3c70e..180d9fb3b86b2 100644 --- a/packages/flutter_localizations/lib/src/l10n/README.md +++ b/packages/flutter_localizations/lib/src/l10n/README.md @@ -56,10 +56,7 @@ translation of "CANCEL" which is defined for the `cancelButtonLabel` resource ID. Each of the language-specific .arb files contains an entry for -`cancelButtonLabel`. They're all represented by the `Map` in the -generated `localizations.dart` file. The Map is used by the -MaterialLocalizations class. - +`cancelButtonLabel`. ### material_en.arb and cupertino_en.arb Define all of the resource IDs @@ -143,7 +140,7 @@ The value of `timeOfDayFormat` defines how a time picker displayed by [showTimePicker()](https://api.flutter.dev/flutter/material/showTimePicker.html) formats and lays out its time controls. The value of `timeOfDayFormat` must be a string that matches one of the formats defined by -. +. It is converted to an enum value because the `material_en.arb` file has this value labeled as `"x-flutter-type": "icuShortTimePattern"`. @@ -155,36 +152,32 @@ section in the Material spec. The Material theme uses the [Typography.geometryThemeFor](https://api.flutter.dev/flutter/material/Typography/geometryThemeFor.html). -### 'generated_*_localizations.dart': all of the localizations as a Map +### 'generated_*_localizations.dart': all of the localizations -If you look at the comment at the top of the `generated_material_localizations.dart` -and `generated_cupertino_localizations.dart` files, you'll -see that it was manually generated using a `dev/tools/localizations` -app called `gen_localizations`. +All of the localizations are combined in a single file per library +using the gen_localizations script. -You can see what that script would generate by running this command: +You can see what that script would generate by running: +```dart +dart dev/tools/localization/bin/gen_localizations.dart +``` +Actually update the generated files with: ```dart -dart dev/tools/localizations/bin/gen_localizations.dart packages/flutter_localizations/lib/src/l10n material +dart dev/tools/localization/bin/gen_localizations.dart --overwrite ``` -The gen_localizations app just combines the contents of all of the -.arb files into a single `Map` per library that has entries for each .arb -file's locale. The `MaterialLocalizations` and `CupertinoLocalizations` -class implementations use these Maps to implement the methods that lookup localized resource values. +The gen_localizations script just combines the contents of all of the +.arb files, each into a class which extends `Global*Localizations`. +The `MaterialLocalizations` and `CupertinoLocalizations` +class implementations use these to lookup localized resource values. -The gen_localizations app must be run by hand after .arb files have -been updated. The app's first parameter is the path to this directory, -the second is the file name prefix (the file name less the locale +The gen_localizations script must be run by hand after .arb files have +been updated. The script optionally takes parameters +1. The path to this directory, +2. The file name prefix (the file name less the locale suffix) for the .arb files in this directory. -To in-place update the generated localizations file using the default -values, you can just run: - -```dart -dart dev/tools/localizations/bin/gen_localizations.dart --overwrite -``` - ### Special handling for the Kannada (kn) translations 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 c97a4ca350571..a2e5057f4aa29 100644 --- a/packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart +++ b/packages/flutter_localizations/lib/src/l10n/generated_material_localizations.dart @@ -17733,7 +17733,7 @@ class MaterialLocalizationId extends GlobalMaterialLocalizations { String get tabLabelRaw => r'Tab $tabIndex dari $tabCount'; @override - TimeOfDayFormat get timeOfDayFormatRaw => TimeOfDayFormat.HH_colon_mm; + TimeOfDayFormat get timeOfDayFormatRaw => TimeOfDayFormat.HH_dot_mm; @override String get timePickerDialHelpText => 'PILIH WAKTU'; diff --git a/packages/flutter_localizations/lib/src/l10n/material_id.arb b/packages/flutter_localizations/lib/src/l10n/material_id.arb index 640a2c661220c..edf3dc105421d 100644 --- a/packages/flutter_localizations/lib/src/l10n/material_id.arb +++ b/packages/flutter_localizations/lib/src/l10n/material_id.arb @@ -1,6 +1,6 @@ { "scriptCategory": "English-like", - "timeOfDayFormat": "HH:mm", + "timeOfDayFormat": "HH.mm", "openAppDrawerTooltip": "Buka menu navigasi", "backButtonTooltip": "Kembali", "closeButtonTooltip": "Tutup", diff --git a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl index cb2d214906c35..4e44e629c0e07 100644 --- a/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl +++ b/packages/flutter_tools/templates/plugin/android-kotlin.tmpl/build.gradle.tmpl @@ -44,7 +44,3 @@ android { minSdkVersion {{minSdkVersion}} } } - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -}