Unverified Commit 8630315a authored by Darren Austin's avatar Darren Austin Committed by GitHub

API to generate a ColorScheme from a single seed color. (#93463)

parent 818f5caf
......@@ -200,13 +200,30 @@ enum MaterialTapTargetSize {
class ThemeData with Diagnosticable {
/// Create a [ThemeData] that's used to configure a [Theme].
///
/// Typically, only the [brightness], [primaryColor], or [primarySwatch] are
/// specified. That pair of values are used to construct the [colorScheme].
///
/// The [colorScheme] and [textTheme] are used by the Material components to
/// compute default values for visual properties. The API documentation for
/// each component widget explains exactly how the defaults are computed.
///
/// When providing a [ColorScheme], apps can either provide one directly
/// with the [colorScheme] parameter, or have one generated for them by
/// using the [colorSchemeSeed] and [brightness] parameters. A generated
/// color scheme will be based on the tones of [colorSchemeSeed] and all of
/// its contrasting color will meet accessibility guidelines for readability.
/// (See [ColorScheme.fromSeed] for more details.)
///
/// If the app wants to customize a generated color scheme, it can use
/// [ColorScheme.fromSeed] directly and then [ColorScheme.copyWith] on the
/// result to override any colors that need to be replaced. The result of
/// this can be used as the [colorScheme] directly.
///
/// For historical reasons, instead of using a [colorSchemeSeed] or
/// [colorScheme], you can provide either a [primaryColor] or [primarySwatch]
/// to construct the [colorScheme], but the results will not be as complete
/// as when using generation from a seed color.
///
/// If [colorSchemeSeed] is non-null then [colorScheme], [primaryColor] and
/// [primarySwatch] must all be null.
///
/// The [textTheme] [TextStyle] colors are black if the color scheme's
/// brightness is [Brightness.light], and white for [Brightness.dark].
///
......@@ -219,6 +236,7 @@ class ThemeData with Diagnosticable {
/// * [ThemeData.from], which creates a ThemeData from a [ColorScheme].
/// * [ThemeData.light], which creates a light blue theme.
/// * [ThemeData.dark], which creates dark theme with a teal secondary [ColorScheme] color.
/// * [ColorScheme.fromSeed], which is used to create a [ColorScheme] from a seed color.
factory ThemeData({
// GENERAL CONFIGURATION
AndroidOverscrollIndicator? androidOverscrollIndicator,
......@@ -234,9 +252,10 @@ class ThemeData with Diagnosticable {
bool? useMaterial3,
// COLOR
// [colorScheme] is the preferred way to configure colors. The other color
// properties (as well as brightness, primaryColorBrightness, and primarySwatch)
// properties (as well as primaryColorBrightness, and primarySwatch)
// will gradually be phased out, see https://github.com/flutter/flutter/issues/91772.
ColorScheme? colorScheme,
Color? colorSchemeSeed,
Brightness? brightness,
MaterialColor? primarySwatch,
Color? primaryColor,
......@@ -368,7 +387,6 @@ class ThemeData with Diagnosticable {
Brightness? primaryColorBrightness,
}) {
// GENERAL CONFIGURATION
applyElevationOverlayColor ??= false;
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
inputDecorationTheme ??= const InputDecorationTheme();
platform ??= defaultTargetPlatform;
......@@ -392,8 +410,35 @@ class ThemeData with Diagnosticable {
// COLOR
assert(colorScheme?.brightness == null || brightness == null || colorScheme!.brightness == brightness);
assert(colorSchemeSeed == null || colorScheme == null);
assert(colorSchemeSeed == null || primarySwatch == null);
assert(colorSchemeSeed == null || primaryColor == null);
final Brightness _brightness = brightness ?? colorScheme?.brightness ?? Brightness.light;
final bool isDark = _brightness == Brightness.dark;
if (colorSchemeSeed != null) {
colorScheme = ColorScheme.fromSeed(seedColor: colorSchemeSeed, brightness: _brightness);
// For surfaces that use primary color in light themes and surface color in dark
final Color primarySurfaceColor = isDark ? colorScheme.surface : colorScheme.primary;
final Color onPrimarySurfaceColor = isDark ? colorScheme.onSurface : colorScheme.onPrimary;
// Default some of the color settings to values from the color scheme
primaryColor = primarySurfaceColor;
primaryColorBrightness = ThemeData.estimateBrightnessForColor(primarySurfaceColor);
canvasColor ??= colorScheme.background;
accentColor ??= colorScheme.secondary;
accentColorBrightness ??= ThemeData.estimateBrightnessForColor(colorScheme.secondary);
scaffoldBackgroundColor ??= colorScheme.background;
bottomAppBarColor ??= colorScheme.surface;
cardColor ??= colorScheme.surface;
dividerColor ??= colorScheme.outline;
backgroundColor ??= colorScheme.background;
dialogBackgroundColor ??= colorScheme.background;
indicatorColor ??= onPrimarySurfaceColor;
errorColor ??= colorScheme.error;
applyElevationOverlayColor ??= isDark;
}
applyElevationOverlayColor ??= false;
primarySwatch ??= Colors.blue;
primaryColor ??= isDark ? Colors.grey[900]! : primarySwatch;
final Brightness _primaryColorBrightness = estimateBrightnessForColor(primaryColor);
......
......@@ -161,4 +161,139 @@ void main() {
expect(scheme.primaryVariant, const Color(0xffbe9eff));
expect(scheme.secondaryVariant, const Color(0xff66fff9));
});
test('can generate a light scheme from a seed color', () {
final ColorScheme scheme = ColorScheme.fromSeed(seedColor: Colors.blue);
expect(scheme.primary, const Color(0xff0061a6));
expect(scheme.onPrimary, const Color(0xffffffff));
expect(scheme.primaryContainer, const Color(0xffd0e4ff));
expect(scheme.onPrimaryContainer, const Color(0xff001d36));
expect(scheme.secondary, const Color(0xff535f70));
expect(scheme.onSecondary, const Color(0xffffffff));
expect(scheme.secondaryContainer, const Color(0xffd6e3f7));
expect(scheme.onSecondaryContainer, const Color(0xff101c2b));
expect(scheme.tertiary, const Color(0xff6b5778));
expect(scheme.onTertiary, const Color(0xffffffff));
expect(scheme.tertiaryContainer, const Color(0xfff3daff));
expect(scheme.onTertiaryContainer, const Color(0xff251432));
expect(scheme.error, const Color(0xffba1b1b));
expect(scheme.onError, const Color(0xffffffff));
expect(scheme.errorContainer, const Color(0xffffdad4));
expect(scheme.onErrorContainer, const Color(0xff410001));
expect(scheme.outline, const Color(0xff73777f));
expect(scheme.background, const Color(0xffe2e2e6));
expect(scheme.onBackground, const Color(0xff1b1b1b));
expect(scheme.surface, const Color(0xfffdfcff));
expect(scheme.onSurface, const Color(0xff000000));
expect(scheme.surfaceVariant, const Color(0xffdfe2eb));
expect(scheme.onSurfaceVariant, const Color(0xff42474e));
expect(scheme.inverseSurface, const Color(0xff2f3033));
expect(scheme.onInverseSurface, const Color(0xfff1f0f4));
expect(scheme.inversePrimary, const Color(0xff9ccaff));
expect(scheme.shadow, const Color(0xff000000));
expect(scheme.brightness, Brightness.light);
});
test('can generate a dark scheme from a seed color', () {
final ColorScheme scheme = ColorScheme.fromSeed(seedColor: Colors.blue, brightness: Brightness.dark);
expect(scheme.primary, const Color(0xff9ccaff));
expect(scheme.onPrimary, const Color(0xff00325a));
expect(scheme.primaryContainer, const Color(0xff60b0ff));
expect(scheme.onPrimaryContainer, const Color(0xff001d36));
expect(scheme.secondary, const Color(0xffbbc8db));
expect(scheme.onSecondary, const Color(0xff253140));
expect(scheme.secondaryContainer, const Color(0xff9facbf));
expect(scheme.onSecondaryContainer, const Color(0xff101c2b));
expect(scheme.tertiary, const Color(0xffd6bee4));
expect(scheme.onTertiary, const Color(0xff3b2948));
expect(scheme.tertiaryContainer, const Color(0xffbba3c9));
expect(scheme.onTertiaryContainer, const Color(0xff251432));
expect(scheme.error, const Color(0xffffb4a9));
expect(scheme.onError, const Color(0xff680003));
expect(scheme.errorContainer, const Color(0xffff897a));
expect(scheme.onErrorContainer, const Color(0xff410001));
expect(scheme.outline, const Color(0xff8d9199));
expect(scheme.background, const Color(0xff1b1b1b));
expect(scheme.onBackground, const Color(0xffe2e2e6));
expect(scheme.surface, const Color(0xff1b1b1b));
expect(scheme.onSurface, const Color(0xffffffff));
expect(scheme.surfaceVariant, const Color(0xff42474e));
expect(scheme.onSurfaceVariant, const Color(0xffc3c7d0));
expect(scheme.inverseSurface, const Color(0xffe2e2e6));
expect(scheme.onInverseSurface, const Color(0xff2f3033));
expect(scheme.inversePrimary, const Color(0xff0061a6));
expect(scheme.shadow, const Color(0xff000000));
expect(scheme.brightness, Brightness.dark);
});
test('can override specific colors in a generated scheme', () {
final ColorScheme baseScheme = ColorScheme.fromSeed(seedColor: Colors.blue);
const Color primaryOverride = Color(0xffabcdef);
final ColorScheme scheme = ColorScheme.fromSeed(
seedColor: Colors.blue,
primary: primaryOverride,
);
expect(scheme.primary, primaryOverride);
// The rest should be the same.
expect(scheme.onPrimary, baseScheme.onPrimary);
expect(scheme.primaryContainer, baseScheme.primaryContainer);
expect(scheme.onPrimaryContainer, baseScheme.onPrimaryContainer);
expect(scheme.secondary, baseScheme.secondary);
expect(scheme.onSecondary, baseScheme.onSecondary);
expect(scheme.secondaryContainer, baseScheme.secondaryContainer);
expect(scheme.onSecondaryContainer, baseScheme.onSecondaryContainer);
expect(scheme.tertiary, baseScheme.tertiary);
expect(scheme.onTertiary, baseScheme.onTertiary);
expect(scheme.tertiaryContainer, baseScheme.tertiaryContainer);
expect(scheme.onTertiaryContainer, baseScheme.onTertiaryContainer);
expect(scheme.error, baseScheme.error);
expect(scheme.onError, baseScheme.onError);
expect(scheme.errorContainer, baseScheme.errorContainer);
expect(scheme.onErrorContainer, baseScheme.onErrorContainer);
expect(scheme.outline, baseScheme.outline);
expect(scheme.background, baseScheme.background);
expect(scheme.onBackground, baseScheme.onBackground);
expect(scheme.surface, baseScheme.surface);
expect(scheme.onSurface, baseScheme.onSurface);
expect(scheme.surfaceVariant, baseScheme.surfaceVariant);
expect(scheme.onSurfaceVariant, baseScheme.onSurfaceVariant);
expect(scheme.inverseSurface, baseScheme.inverseSurface);
expect(scheme.onInverseSurface, baseScheme.onInverseSurface);
expect(scheme.inversePrimary, baseScheme.inversePrimary);
expect(scheme.shadow, baseScheme.shadow);
expect(scheme.brightness, baseScheme.brightness);
});
testWidgets('generated scheme "on" colors meet a11y contrast guidelines', (WidgetTester tester) async {
final ColorScheme colors = ColorScheme.fromSeed(seedColor: Colors.teal);
Widget label(String text, Color textColor, Color background) {
return Container(
color: background,
padding: const EdgeInsets.all(8),
child: Text(text, style: TextStyle(color: textColor)),
);
}
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: colors),
home: Scaffold(
body: Column(
children: <Widget>[
label('primary', colors.onPrimary, colors.primary),
label('secondary', colors.onSecondary, colors.secondary),
label('tertiary', colors.onTertiary, colors.tertiary),
label('error', colors.onError, colors.error),
label('background', colors.onBackground, colors.background),
label('surface', colors.onSurface, colors.surface),
],
),
),
),
);
await expectLater(tester, meetsGuideline(textContrastGuideline));
},
skip: isBrowser, // https://github.com/flutter/flutter/issues/44115
);
}
......@@ -130,6 +130,111 @@ void main() {
expect(const TextSelectionThemeData(cursorColor: Colors.red).cursorColor, Colors.red);
});
test('If colorSchemeSeed is used colorScheme, primaryColor and primarySwatch should not be.', () {
expect(() => ThemeData(colorSchemeSeed: Colors.blue, colorScheme: const ColorScheme.light()), throwsAssertionError);
expect(() => ThemeData(colorSchemeSeed: Colors.blue, primaryColor: Colors.green), throwsAssertionError);
expect(() => ThemeData(colorSchemeSeed: Colors.blue, primarySwatch: Colors.green), throwsAssertionError);
});
test('ThemeData can generate a light colorScheme from colorSchemeSeed', () {
final ThemeData theme = ThemeData(colorSchemeSeed: Colors.blue);
expect(theme.colorScheme.primary, const Color(0xff0061a6));
expect(theme.colorScheme.onPrimary, const Color(0xffffffff));
expect(theme.colorScheme.primaryContainer, const Color(0xffd0e4ff));
expect(theme.colorScheme.onPrimaryContainer, const Color(0xff001d36));
expect(theme.colorScheme.secondary, const Color(0xff535f70));
expect(theme.colorScheme.onSecondary, const Color(0xffffffff));
expect(theme.colorScheme.secondaryContainer, const Color(0xffd6e3f7));
expect(theme.colorScheme.onSecondaryContainer, const Color(0xff101c2b));
expect(theme.colorScheme.tertiary, const Color(0xff6b5778));
expect(theme.colorScheme.onTertiary, const Color(0xffffffff));
expect(theme.colorScheme.tertiaryContainer, const Color(0xfff3daff));
expect(theme.colorScheme.onTertiaryContainer, const Color(0xff251432));
expect(theme.colorScheme.error, const Color(0xffba1b1b));
expect(theme.colorScheme.onError, const Color(0xffffffff));
expect(theme.colorScheme.errorContainer, const Color(0xffffdad4));
expect(theme.colorScheme.onErrorContainer, const Color(0xff410001));
expect(theme.colorScheme.outline, const Color(0xff73777f));
expect(theme.colorScheme.background, const Color(0xffe2e2e6));
expect(theme.colorScheme.onBackground, const Color(0xff1b1b1b));
expect(theme.colorScheme.surface, const Color(0xfffdfcff));
expect(theme.colorScheme.onSurface, const Color(0xff000000));
expect(theme.colorScheme.surfaceVariant, const Color(0xffdfe2eb));
expect(theme.colorScheme.onSurfaceVariant, const Color(0xff42474e));
expect(theme.colorScheme.inverseSurface, const Color(0xff2f3033));
expect(theme.colorScheme.onInverseSurface, const Color(0xfff1f0f4));
expect(theme.colorScheme.inversePrimary, const Color(0xff9ccaff));
expect(theme.colorScheme.shadow, const Color(0xff000000));
expect(theme.colorScheme.brightness, Brightness.light);
expect(theme.primaryColor, theme.colorScheme.primary);
expect(theme.primaryColorBrightness, Brightness.dark);
expect(theme.canvasColor, theme.colorScheme.background);
expect(theme.accentColor, theme.colorScheme.secondary);
expect(theme.accentColorBrightness, Brightness.dark);
expect(theme.scaffoldBackgroundColor, theme.colorScheme.background);
expect(theme.bottomAppBarColor, theme.colorScheme.surface);
expect(theme.cardColor, theme.colorScheme.surface);
expect(theme.dividerColor, theme.colorScheme.outline);
expect(theme.backgroundColor, theme.colorScheme.background);
expect(theme.dialogBackgroundColor, theme.colorScheme.background);
expect(theme.indicatorColor, theme.colorScheme.onPrimary);
expect(theme.errorColor, theme.colorScheme.error);
expect(theme.applyElevationOverlayColor, false);
});
test('ThemeData can generate a dark colorScheme from colorSchemeSeed', () {
final ThemeData theme = ThemeData(
colorSchemeSeed: Colors.blue,
brightness: Brightness.dark,
);
expect(theme.colorScheme.primary, const Color(0xff9ccaff));
expect(theme.colorScheme.onPrimary, const Color(0xff00325a));
expect(theme.colorScheme.primaryContainer, const Color(0xff60b0ff));
expect(theme.colorScheme.onPrimaryContainer, const Color(0xff001d36));
expect(theme.colorScheme.secondary, const Color(0xffbbc8db));
expect(theme.colorScheme.onSecondary, const Color(0xff253140));
expect(theme.colorScheme.secondaryContainer, const Color(0xff9facbf));
expect(theme.colorScheme.onSecondaryContainer, const Color(0xff101c2b));
expect(theme.colorScheme.tertiary, const Color(0xffd6bee4));
expect(theme.colorScheme.onTertiary, const Color(0xff3b2948));
expect(theme.colorScheme.tertiaryContainer, const Color(0xffbba3c9));
expect(theme.colorScheme.onTertiaryContainer, const Color(0xff251432));
expect(theme.colorScheme.error, const Color(0xffffb4a9));
expect(theme.colorScheme.onError, const Color(0xff680003));
expect(theme.colorScheme.errorContainer, const Color(0xffff897a));
expect(theme.colorScheme.onErrorContainer, const Color(0xff410001));
expect(theme.colorScheme.outline, const Color(0xff8d9199));
expect(theme.colorScheme.background, const Color(0xff1b1b1b));
expect(theme.colorScheme.onBackground, const Color(0xffe2e2e6));
expect(theme.colorScheme.surface, const Color(0xff1b1b1b));
expect(theme.colorScheme.onSurface, const Color(0xffffffff));
expect(theme.colorScheme.surfaceVariant, const Color(0xff42474e));
expect(theme.colorScheme.onSurfaceVariant, const Color(0xffc3c7d0));
expect(theme.colorScheme.inverseSurface, const Color(0xffe2e2e6));
expect(theme.colorScheme.onInverseSurface, const Color(0xff2f3033));
expect(theme.colorScheme.inversePrimary, const Color(0xff0061a6));
expect(theme.colorScheme.shadow, const Color(0xff000000));
expect(theme.colorScheme.brightness, Brightness.dark);
expect(theme.primaryColor, theme.colorScheme.surface);
expect(theme.primaryColorBrightness, Brightness.dark);
expect(theme.canvasColor, theme.colorScheme.background);
expect(theme.accentColor, theme.colorScheme.secondary);
expect(theme.accentColorBrightness, Brightness.light);
expect(theme.scaffoldBackgroundColor, theme.colorScheme.background);
expect(theme.bottomAppBarColor, theme.colorScheme.surface);
expect(theme.cardColor, theme.colorScheme.surface);
expect(theme.dividerColor, theme.colorScheme.outline);
expect(theme.backgroundColor, theme.colorScheme.background);
expect(theme.dialogBackgroundColor, theme.colorScheme.background);
expect(theme.indicatorColor, theme.colorScheme.onSurface);
expect(theme.errorColor, theme.colorScheme.error);
expect(theme.applyElevationOverlayColor, true);
});
testWidgets('ThemeData.from a light color scheme sets appropriate values', (WidgetTester tester) async {
const ColorScheme lightColors = ColorScheme.light();
final ThemeData theme = ThemeData.from(colorScheme: lightColors);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment