diff --git a/examples/api/lib/material/color_scheme/color_scheme.0.dart b/examples/api/lib/material/color_scheme/color_scheme.0.dart index 51ce183f9e2..78a6af44399 100644 --- a/examples/api/lib/material/color_scheme/color_scheme.0.dart +++ b/examples/api/lib/material/color_scheme/color_scheme.0.dart @@ -19,95 +19,93 @@ class ColorSchemeExample extends StatefulWidget { class _ColorSchemeExampleState extends State { Color selectedColor = ColorSeed.baseColor.color; + Brightness selectedBrightness = Brightness.light; + static const List schemeVariants = DynamicSchemeVariant.values; @override Widget build(BuildContext context) { - final Color? colorSeed = selectedColor == ColorSeed.baseColor.color ? null : selectedColor; - final ThemeData lightTheme = ThemeData( - colorSchemeSeed: colorSeed, - brightness: Brightness.light, - ); - final ThemeData darkTheme = ThemeData( - colorSchemeSeed: colorSeed, - brightness: Brightness.dark, - ); - - Widget schemeLabel(String brightness) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 15), - child: Text( - brightness, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - ); - } - - Widget schemeView(ThemeData theme) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 15), - child: ColorSchemeView(colorScheme: theme.colorScheme), - ); - } - return MaterialApp( - theme: ThemeData(colorSchemeSeed: selectedColor), + debugShowCheckedModeBanner: false, + theme: ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: selectedColor, + brightness: selectedBrightness, + ) + ), home: Builder( builder: (BuildContext context) => Scaffold( appBar: AppBar( title: const Text('ColorScheme'), - leading: MenuAnchor( - builder: (BuildContext context, MenuController controller, Widget? widget) { - return IconButton( - icon: Icon(Icons.circle, color: selectedColor), - onPressed: () { - setState(() { - if (!controller.isOpen) { - controller.open(); - } - }); - }, - ); - }, - menuChildren: List.generate(ColorSeed.values.length, (int index) { - final Color itemColor = ColorSeed.values[index].color; - return MenuItemButton( - leadingIcon: selectedColor == ColorSeed.values[index].color - ? Icon(Icons.circle, color: itemColor) - : Icon(Icons.circle_outlined, color: itemColor), - onPressed: () { - setState(() { - selectedColor = itemColor; - }); - }, - child: Text(ColorSeed.values[index].label), - ); - }), - ), + actions: [ + Row( + children: [ + const Text('Color Seed'), + MenuAnchor( + builder: (BuildContext context, MenuController controller, Widget? widget) { + return IconButton( + icon: Icon(Icons.circle, color: selectedColor), + onPressed: () { + setState(() { + if (!controller.isOpen) { + controller.open(); + } + }); + }, + ); + }, + menuChildren: List.generate(ColorSeed.values.length, (int index) { + final Color itemColor = ColorSeed.values[index].color; + return MenuItemButton( + leadingIcon: selectedColor == ColorSeed.values[index].color + ? Icon(Icons.circle, color: itemColor) + : Icon(Icons.circle_outlined, color: itemColor), + onPressed: () { + setState(() { + selectedColor = itemColor; + }); + }, + child: Text(ColorSeed.values[index].label), + ); + }), + ), + ], + ), + ], ), body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.only(top: 5), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - Expanded( - child: Column( - children: [ - schemeLabel('Light ColorScheme'), - schemeView(lightTheme), - ], + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + child: Row( + children: [ + const Text('Brightness'), + const SizedBox(width: 10), + Switch( + value: selectedBrightness == Brightness.light, + onChanged: (bool value) { + setState(() { + selectedBrightness = value ? Brightness.light : Brightness.dark; + }); + }, ), - ), - Expanded( - child: Column( - children: [ - schemeLabel('Dark ColorScheme'), - schemeView(darkTheme), - ], - ), - ), - ], + ], + ), + ), + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: List.generate(schemeVariants.length, (int index) { + return ColorSchemeVariantColumn( + selectedColor: selectedColor, + brightness: selectedBrightness, + schemeVariant: schemeVariants[index], + ); + }).toList(), + ), ), ], ), @@ -119,6 +117,47 @@ class _ColorSchemeExampleState extends State { } } +class ColorSchemeVariantColumn extends StatelessWidget { + const ColorSchemeVariantColumn({ + super.key, + this.schemeVariant = DynamicSchemeVariant.tonalSpot, + this.brightness = Brightness.light, + required this.selectedColor, + }); + + final DynamicSchemeVariant schemeVariant; + final Brightness brightness; + final Color selectedColor; + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints.tightFor(width: 250), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 15), + child: Text( + schemeVariant.name == 'tonalSpot' ? '${schemeVariant.name} (Default)' : schemeVariant.name, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15), + child: ColorSchemeView( + colorScheme: ColorScheme.fromSeed( + seedColor: selectedColor, + brightness: brightness, + dynamicSchemeVariant: schemeVariant, + ), + ), + ), + ], + ) + ); + } +} + class ColorSchemeView extends StatelessWidget { const ColorSchemeView({super.key, required this.colorScheme}); @@ -301,7 +340,10 @@ enum ColorSeed { yellow('Yellow', Colors.yellow), orange('Orange', Colors.orange), deepOrange('Deep Orange', Colors.deepOrange), - pink('Pink', Colors.pink); + pink('Pink', Colors.pink), + brightBlue('Bright Blue', Color(0xFF0000FF)), + brightGreen('Bright Green', Color(0xFF00FF00)), + brightRed('Bright Red', Color(0xFFFF0000)); const ColorSeed(this.label, this.color); final String label; diff --git a/examples/api/test/material/color_scheme/color_scheme.0_test.dart b/examples/api/test/material/color_scheme/color_scheme.0_test.dart index 492a71884b9..58e0e142ff0 100644 --- a/examples/api/test/material/color_scheme/color_scheme.0_test.dart +++ b/examples/api/test/material/color_scheme/color_scheme.0_test.dart @@ -11,10 +11,9 @@ void main() { await tester.pumpWidget( const example.ColorSchemeExample(), ); - expect(find.text('Light ColorScheme'), findsOneWidget); - expect(find.text('Dark ColorScheme'), findsOneWidget); + expect(find.text('tonalSpot (Default)'), findsOneWidget); - expect(find.byType(example.ColorChip), findsNWidgets(86)); + expect(find.byType(example.ColorChip), findsNWidgets(43 * 9)); }); testWidgets('Change color seed', (WidgetTester tester) async { @@ -30,7 +29,7 @@ void main() { ) ); } - expect(coloredBox().color, const Color(0xFF6750A4)); + expect(coloredBox().color, const Color(0xff65558f)); await tester.tap(find.byType(MenuAnchor)); await tester.pumpAndSettle(); await tester.tap(find.widgetWithText(MenuItemButton, 'Yellow')); diff --git a/packages/flutter/lib/src/material/color_scheme.dart b/packages/flutter/lib/src/material/color_scheme.dart index eaa01ace6ba..30914823f7f 100644 --- a/packages/flutter/lib/src/material/color_scheme.dart +++ b/packages/flutter/lib/src/material/color_scheme.dart @@ -12,6 +12,52 @@ import 'package:material_color_utilities/material_color_utilities.dart'; import 'colors.dart'; import 'theme_data.dart'; +/// The algorithm used to construct a [ColorScheme] in [ColorScheme.fromSeed]. +/// +/// The `tonalSpot` variant builds default Material scheme colors. These colors are +/// mapped to light or dark tones to achieve visually accessible color +/// pairings with sufficient contrast between foreground and background elements. +/// +/// In some cases, the tones can prevent colors from appearing as intended, +/// such as when a color is too light to offer enough contrast for accessibility. +/// Color fidelity (`DynamicSchemeVariant.fidelity`) is a feature that adjusts +/// tones in these cases to produce the intended visual results without harming +/// visual contrast. +enum DynamicSchemeVariant { + /// Default for Material theme colors. Builds pastel palettes with a low chroma. + tonalSpot, + + /// The resulting color palettes match seed color, even if the seed color + /// is very bright (high chroma). + fidelity, + + /// All colors are grayscale, no chroma. + monochrome, + + /// Close to grayscale, a hint of chroma. + neutral, + + /// Pastel colors, high chroma palettes. The primary palette's chroma is at + /// maximum. Use `fidelity` instead if tokens should alter their tone to match + /// the palette vibrancy. + vibrant, + + /// Pastel colors, medium chroma palettes. The primary palette's hue is + /// different from the seed color, for variety. + expressive, + + /// Almost identical to `fidelity`. Tokens and palettes match the seed color. + /// [ColorScheme.primaryContainer] is the seed color, adjusted to ensure + /// contrast with surfaces. The tertiary palette is analogue of the seed color. + content, + + /// A playful theme - the seed color's hue does not appear in the theme. + rainbow, + + /// A playful theme - the seed color's hue does not appear in the theme. + fruitSalad, +} + /// {@template flutter.material.color_scheme.ColorScheme} /// A set of 45 colors based on the /// [Material spec](https://m3.material.io/styles/color/the-color-system/color-roles) @@ -215,20 +261,35 @@ class ColorScheme with Diagnosticable { /// Generate a [ColorScheme] derived from the given `seedColor`. /// - /// Using the seedColor as a starting point, a set of tonal palettes are - /// constructed. These tonal palettes are based on the Material 3 Color - /// system and provide all the needed colors for a [ColorScheme]. These - /// colors are designed to work well together and meet contrast - /// requirements for accessibility. + /// Using the `seedColor` as a starting point, a set of tonal palettes are + /// constructed. By default, the tonal palettes are based on the Material 3 + /// Color system and provide all of the [ColorScheme] colors. These colors are + /// designed to work well together and meet contrast requirements for + /// accessibility. /// /// If any of the optional color parameters are non-null they will be /// used in place of the generated colors for that field in the resulting /// color scheme. This allows apps to override specific colors for their /// needs. /// - /// Given the nature of the algorithm, the seedColor may not wind up as + /// Given the nature of the algorithm, the `seedColor` may not wind up as /// one of the ColorScheme colors. /// + /// The `dynamicSchemeVariant` parameter creates different types of + /// [DynamicScheme]s, which are used to generate different styles of [ColorScheme]s. + /// By default, `dynamicSchemeVariant` is set to `tonalSpot`. A [ColorScheme] + /// constructed by `dynamicSchemeVariant.tonalSpot` has pastel palettes and + /// won't be too "colorful" even if the `seedColor` has a high chroma value. + /// If the resulting color scheme is too dark, consider setting `dynamicSchemeVariant` + /// to [DynamicSchemeVariant.fidelity], whose palettes match the seed color. + /// + /// {@tool dartpad} + /// This sample shows how to use [ColorScheme.fromSeed] to create dynamic + /// color schemes with different [DynamicSchemeVariant]s. + /// + /// ** See code in examples/api/lib/material/color_scheme/color_scheme.0.dart ** + /// {@end-tool} + /// /// See also: /// /// * , the @@ -238,6 +299,7 @@ class ColorScheme with Diagnosticable { factory ColorScheme.fromSeed({ required Color seedColor, Brightness brightness = Brightness.light, + DynamicSchemeVariant dynamicSchemeVariant = DynamicSchemeVariant.tonalSpot, Color? primary, Color? onPrimary, Color? primaryContainer, @@ -300,13 +362,7 @@ class ColorScheme with Diagnosticable { ) Color? surfaceVariant, }) { - final SchemeTonalSpot scheme; - switch (brightness) { - case Brightness.light: - scheme = SchemeTonalSpot(sourceColorHct: Hct.fromInt(seedColor.value), isDark: false, contrastLevel: 0.0); - case Brightness.dark: - scheme = SchemeTonalSpot(sourceColorHct: Hct.fromInt(seedColor.value), isDark: true, contrastLevel: 0.0); - } + final DynamicScheme scheme = _buildDynamicScheme(brightness, seedColor, dynamicSchemeVariant); return ColorScheme( primary: primary ?? Color(MaterialDynamicColors.primary.getArgb(scheme)), @@ -1615,6 +1671,7 @@ class ColorScheme with Diagnosticable { static Future fromImageProvider({ required ImageProvider provider, Brightness brightness = Brightness.light, + DynamicSchemeVariant dynamicSchemeVariant = DynamicSchemeVariant.tonalSpot, Color? primary, Color? onPrimary, Color? primaryContainer, @@ -1688,13 +1745,7 @@ class ColorScheme with Diagnosticable { final List scoredResults = Score.score(colorToCount, desired: 1); final ui.Color baseColor = Color(scoredResults.first); - final SchemeTonalSpot scheme; - switch (brightness) { - case Brightness.light: - scheme = SchemeTonalSpot(sourceColorHct: Hct.fromInt(baseColor.value), isDark: false, contrastLevel: 0.0); - case Brightness.dark: - scheme = SchemeTonalSpot(sourceColorHct: Hct.fromInt(baseColor.value), isDark: true, contrastLevel: 0.0); - } + final DynamicScheme scheme = _buildDynamicScheme(brightness, baseColor, dynamicSchemeVariant); return ColorScheme( primary: primary ?? Color(MaterialDynamicColors.primary.getArgb(scheme)), @@ -1830,4 +1881,20 @@ class ColorScheme with Diagnosticable { final int b = abgr & onlyBMask; return (abgr & exceptRMask & exceptBMask) | (b << 16) | r; } + + static DynamicScheme _buildDynamicScheme(Brightness brightness, Color seedColor, DynamicSchemeVariant schemeVariant) { + final bool isDark = brightness == Brightness.dark; + final Hct sourceColor = Hct.fromInt(seedColor.value); + return switch (schemeVariant) { + DynamicSchemeVariant.tonalSpot => SchemeTonalSpot(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + DynamicSchemeVariant.fidelity => SchemeFidelity(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + DynamicSchemeVariant.content => SchemeContent(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + DynamicSchemeVariant.monochrome => SchemeMonochrome(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + DynamicSchemeVariant.neutral => SchemeNeutral(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + DynamicSchemeVariant.vibrant => SchemeVibrant(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + DynamicSchemeVariant.expressive => SchemeExpressive(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + DynamicSchemeVariant.rainbow => SchemeRainbow(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + DynamicSchemeVariant.fruitSalad => SchemeFruitSalad(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + }; + } } diff --git a/packages/flutter/test/material/color_scheme_test.dart b/packages/flutter/test/material/color_scheme_test.dart index 17eba66e0e7..b65ca9b6d7c 100644 --- a/packages/flutter/test/material/color_scheme_test.dart +++ b/packages/flutter/test/material/color_scheme_test.dart @@ -2,10 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:typed_data'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:material_color_utilities/material_color_utilities.dart'; import '../image_data.dart'; @@ -684,4 +685,143 @@ void main() { }, skip: isBrowser, // https://github.com/flutter/flutter/issues/44115 ); + + testWidgets('Color values in ColorScheme.fromSeed with different variants matches values in DynamicScheme', (WidgetTester tester) async { + const Color seedColor = Colors.orange; + final Hct sourceColor = Hct.fromInt(seedColor.value); + for (final DynamicSchemeVariant schemeVariant in DynamicSchemeVariant.values) { + final DynamicScheme dynamicScheme = switch (schemeVariant) { + DynamicSchemeVariant.tonalSpot => SchemeTonalSpot(sourceColorHct: sourceColor, isDark: false, contrastLevel: 0.0), + DynamicSchemeVariant.fidelity => SchemeFidelity(sourceColorHct: sourceColor, isDark: false, contrastLevel: 0.0), + DynamicSchemeVariant.content => SchemeContent(sourceColorHct: sourceColor, isDark: false, contrastLevel: 0.0), + DynamicSchemeVariant.monochrome => SchemeMonochrome(sourceColorHct: sourceColor, isDark: false, contrastLevel: 0.0), + DynamicSchemeVariant.neutral => SchemeNeutral(sourceColorHct: sourceColor, isDark: false, contrastLevel: 0.0), + DynamicSchemeVariant.vibrant => SchemeVibrant(sourceColorHct: sourceColor, isDark: false, contrastLevel: 0.0), + DynamicSchemeVariant.expressive => SchemeExpressive(sourceColorHct: sourceColor, isDark: false, contrastLevel: 0.0), + DynamicSchemeVariant.rainbow => SchemeRainbow(sourceColorHct: sourceColor, isDark: false, contrastLevel: 0.0), + DynamicSchemeVariant.fruitSalad => SchemeFruitSalad(sourceColorHct: sourceColor, isDark: false, contrastLevel: 0.0), + }; + final ColorScheme colorScheme = ColorScheme.fromSeed( + seedColor: seedColor, + dynamicSchemeVariant: schemeVariant, + ); + + expect(colorScheme.primary.value, MaterialDynamicColors.primary.getArgb(dynamicScheme)); + expect(colorScheme.onPrimary.value, MaterialDynamicColors.onPrimary.getArgb(dynamicScheme)); + expect(colorScheme.primaryContainer.value, MaterialDynamicColors.primaryContainer.getArgb(dynamicScheme)); + expect(colorScheme.onPrimaryContainer.value, MaterialDynamicColors.onPrimaryContainer.getArgb(dynamicScheme)); + expect(colorScheme.primaryFixed.value, MaterialDynamicColors.primaryFixed.getArgb(dynamicScheme)); + expect(colorScheme.primaryFixedDim.value, MaterialDynamicColors.primaryFixedDim.getArgb(dynamicScheme)); + expect(colorScheme.onPrimaryFixed.value, MaterialDynamicColors.onPrimaryFixed.getArgb(dynamicScheme)); + expect(colorScheme.onPrimaryFixedVariant.value, MaterialDynamicColors.onPrimaryFixedVariant.getArgb(dynamicScheme)); + expect(colorScheme.secondary.value, MaterialDynamicColors.secondary.getArgb(dynamicScheme)); + expect(colorScheme.onSecondary.value, MaterialDynamicColors.onSecondary.getArgb(dynamicScheme)); + expect(colorScheme.secondaryContainer.value, MaterialDynamicColors.secondaryContainer.getArgb(dynamicScheme)); + expect(colorScheme.onSecondaryContainer.value, MaterialDynamicColors.onSecondaryContainer.getArgb(dynamicScheme)); + expect(colorScheme.secondaryFixed.value, MaterialDynamicColors.secondaryFixed.getArgb(dynamicScheme)); + expect(colorScheme.secondaryFixedDim.value, MaterialDynamicColors.secondaryFixedDim.getArgb(dynamicScheme)); + expect(colorScheme.onSecondaryFixed.value, MaterialDynamicColors.onSecondaryFixed.getArgb(dynamicScheme)); + expect(colorScheme.onSecondaryFixedVariant.value, MaterialDynamicColors.onSecondaryFixedVariant.getArgb(dynamicScheme)); + expect(colorScheme.tertiary.value, MaterialDynamicColors.tertiary.getArgb(dynamicScheme)); + expect(colorScheme.onTertiary.value, MaterialDynamicColors.onTertiary.getArgb(dynamicScheme)); + expect(colorScheme.tertiaryContainer.value, MaterialDynamicColors.tertiaryContainer.getArgb(dynamicScheme)); + expect(colorScheme.onTertiaryContainer.value, MaterialDynamicColors.onTertiaryContainer.getArgb(dynamicScheme)); + expect(colorScheme.tertiaryFixed.value, MaterialDynamicColors.tertiaryFixed.getArgb(dynamicScheme)); + expect(colorScheme.tertiaryFixedDim.value, MaterialDynamicColors.tertiaryFixedDim.getArgb(dynamicScheme)); + expect(colorScheme.onTertiaryFixed.value, MaterialDynamicColors.onTertiaryFixed.getArgb(dynamicScheme)); + expect(colorScheme.onTertiaryFixedVariant.value, MaterialDynamicColors.onTertiaryFixedVariant.getArgb(dynamicScheme)); + expect(colorScheme.error.value, MaterialDynamicColors.error.getArgb(dynamicScheme)); + expect(colorScheme.onError.value, MaterialDynamicColors.onError.getArgb(dynamicScheme)); + expect(colorScheme.errorContainer.value, MaterialDynamicColors.errorContainer.getArgb(dynamicScheme)); + expect(colorScheme.onErrorContainer.value, MaterialDynamicColors.onErrorContainer.getArgb(dynamicScheme)); + expect(colorScheme.background.value, MaterialDynamicColors.background.getArgb(dynamicScheme)); + expect(colorScheme.onBackground.value, MaterialDynamicColors.onBackground.getArgb(dynamicScheme)); + expect(colorScheme.surface.value, MaterialDynamicColors.surface.getArgb(dynamicScheme)); + expect(colorScheme.surfaceDim.value, MaterialDynamicColors.surfaceDim.getArgb(dynamicScheme)); + expect(colorScheme.surfaceBright.value, MaterialDynamicColors.surfaceBright.getArgb(dynamicScheme)); + expect(colorScheme.surfaceContainerLowest.value, MaterialDynamicColors.surfaceContainerLowest.getArgb(dynamicScheme)); + expect(colorScheme.surfaceContainerLow.value, MaterialDynamicColors.surfaceContainerLow.getArgb(dynamicScheme)); + expect(colorScheme.surfaceContainer.value, MaterialDynamicColors.surfaceContainer.getArgb(dynamicScheme)); + expect(colorScheme.surfaceContainerHigh.value, MaterialDynamicColors.surfaceContainerHigh.getArgb(dynamicScheme)); + expect(colorScheme.surfaceContainerHighest.value, MaterialDynamicColors.surfaceContainerHighest.getArgb(dynamicScheme)); + expect(colorScheme.onSurface.value, MaterialDynamicColors.onSurface.getArgb(dynamicScheme)); + expect(colorScheme.surfaceVariant.value, MaterialDynamicColors.surfaceVariant.getArgb(dynamicScheme)); + expect(colorScheme.onSurfaceVariant.value, MaterialDynamicColors.onSurfaceVariant.getArgb(dynamicScheme)); + expect(colorScheme.outline.value, MaterialDynamicColors.outline.getArgb(dynamicScheme)); + expect(colorScheme.outlineVariant.value, MaterialDynamicColors.outlineVariant.getArgb(dynamicScheme)); + expect(colorScheme.shadow.value, MaterialDynamicColors.shadow.getArgb(dynamicScheme)); + expect(colorScheme.scrim.value, MaterialDynamicColors.scrim.getArgb(dynamicScheme)); + expect(colorScheme.inverseSurface.value, MaterialDynamicColors.inverseSurface.getArgb(dynamicScheme)); + expect(colorScheme.onInverseSurface.value, MaterialDynamicColors.inverseOnSurface.getArgb(dynamicScheme)); + expect(colorScheme.inversePrimary.value, MaterialDynamicColors.inversePrimary.getArgb(dynamicScheme)); + } + }); + + testWidgets('ColorScheme.fromSeed with different variants spot checks', (WidgetTester tester) async { + // Default (Variant.tonalSpot). + await _testFilledButtonColor(tester, ColorScheme.fromSeed(seedColor: const Color(0xFF000000)), const Color(0xFF8C4A60)); + await _testFilledButtonColor(tester, ColorScheme.fromSeed(seedColor: const Color(0xFF00FF00)), const Color(0xFF406836)); + await _testFilledButtonColor(tester, ColorScheme.fromSeed(seedColor: const Color(0xFF6559F5)), const Color(0xFF5B5891)); + await _testFilledButtonColor(tester, ColorScheme.fromSeed(seedColor: const Color(0xFFFFFFFF)), const Color(0xFF006874)); + + // Variant.fidelity. + await _testFilledButtonColor( + tester, + ColorScheme.fromSeed( + seedColor: const Color(0xFF000000), + dynamicSchemeVariant: DynamicSchemeVariant.fidelity + ), + const Color(0xFF000000) + ); + await _testFilledButtonColor( + tester, + ColorScheme.fromSeed( + seedColor: const Color(0xFF00FF00), + dynamicSchemeVariant: DynamicSchemeVariant.fidelity + ), + const Color(0xFF026E00) + ); + await _testFilledButtonColor( + tester, + ColorScheme.fromSeed( + seedColor: const Color(0xFF6559F5), + dynamicSchemeVariant: DynamicSchemeVariant.fidelity + ), + const Color(0xFF4C3CDB) + ); + await _testFilledButtonColor( + tester, + ColorScheme.fromSeed( + seedColor: const Color(0xFFFFFFFF), + dynamicSchemeVariant: DynamicSchemeVariant.fidelity + ), + const Color(0xFF5D5F5F) + ); + }); +} + +Future _testFilledButtonColor(WidgetTester tester, ColorScheme scheme, Color expectation) async { + final GlobalKey key = GlobalKey(); + await tester.pumpWidget(Container()); // reset + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + colorScheme: scheme, + ), + home: FilledButton( + key: key, + onPressed: () {}, + child: const SizedBox.square(dimension: 200), + ), + ), + ); + + + final Finder buttonMaterial = find.descendant( + of: find.byType(FilledButton), + matching: find.byType(Material), + ); + final Material material = tester.widget(buttonMaterial); + + expect(material.color, expectation); }