Last active
September 15, 2020 13:48
-
-
Save rydmike/b59cea071eefdbe7c1332b2d014bc156 to your computer and use it in GitHub Desktop.
Flutter: ColorScheme Theme Quality Issues
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// MIT License | |
// Copyright 2020 Mike Rydstrom | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included | |
// in all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | |
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
// IN THE SOFTWARE. | |
import 'package:flutter/material.dart'; | |
const double kEdgePadding = 16.0; | |
const double kMaxContentWidth = 1200.0; | |
const ColorScheme lightScheme = ColorScheme.light(); | |
const ColorScheme darkScheme = ColorScheme.dark(); | |
final ThemeData lightTheme = ThemeData.from(colorScheme: lightScheme); | |
final ThemeData darkTheme = ThemeData.from(colorScheme: darkScheme); | |
void main() { | |
runApp(MyApp()); | |
} | |
class MyApp extends StatefulWidget { | |
@override | |
_MyAppState createState() => _MyAppState(); | |
} | |
class _MyAppState extends State<MyApp> { | |
bool isDarkMode; | |
bool useSelectionTheme; | |
@override | |
void initState() { | |
isDarkMode = false; | |
useSelectionTheme = true; | |
super.initState(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
return MaterialApp( | |
title: 'Issue Discovery', | |
debugShowCheckedModeBanner: false, | |
theme: lightTheme.copyWith(useTextSelectionTheme: useSelectionTheme), | |
darkTheme: darkTheme.copyWith(useTextSelectionTheme: useSelectionTheme), | |
themeMode: isDarkMode ? ThemeMode.dark : ThemeMode.light, | |
home: DemoPage( | |
title: 'Issue Discovery', | |
setDarkMode: (bool value) { | |
setState(() { | |
isDarkMode = value; | |
}); | |
}, | |
setSelectionTheme: (bool value) { | |
setState(() { | |
useSelectionTheme = value; | |
}); | |
}, | |
), | |
); | |
} | |
} | |
class DemoPage extends StatefulWidget { | |
const DemoPage({ | |
Key key, | |
this.title, | |
this.setDarkMode, | |
this.setSelectionTheme, | |
}) : super(key: key); | |
final String title; | |
final ValueChanged<bool> setDarkMode; | |
final ValueChanged<bool> setSelectionTheme; | |
@override | |
_DemoPageState createState() => _DemoPageState(); | |
} | |
class _DemoPageState extends State<DemoPage> { | |
bool isDarkMode; | |
bool useSelectionTheme; | |
@override | |
void initState() { | |
isDarkMode = false; | |
useSelectionTheme = true; | |
super.initState(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
final MediaQueryData media = MediaQuery.of(context); | |
final ThemeData theme = Theme.of(context); | |
final bool isDark = theme.brightness == Brightness.dark; | |
final double topPadding = media.padding.top; | |
final double bottomPadding = media.padding.bottom; | |
final TextTheme textTheme = theme.textTheme; | |
final TextStyle headline5 = textTheme.headline5; | |
final TextStyle headline4 = textTheme.headline4; | |
return Scaffold( | |
extendBodyBehindAppBar: true, | |
extendBody: true, | |
appBar: AppBar( | |
title: Text(widget.title), | |
elevation: 0, | |
), | |
body: Scrollbar( | |
child: Center( | |
child: ConstrainedBox( | |
constraints: const BoxConstraints(maxWidth: kMaxContentWidth), | |
child: ListView( | |
padding: EdgeInsets.fromLTRB( | |
kEdgePadding, | |
topPadding + kToolbarHeight + kEdgePadding, | |
kEdgePadding, | |
kEdgePadding + bottomPadding, | |
), | |
children: <Widget>[ | |
Text( | |
'ColorScheme Theme Quality Issues', | |
style: textTheme.headline4, | |
), | |
// | |
// Change theme | |
// | |
const Divider(), | |
Text('Completeness and Quality Issues', style: headline5), | |
const Text( | |
'ColorScheme themes create ThemeData that is inconsistent ' | |
'with the provided ColorScheme. Some sub themes are also incomplete ' | |
'when using ColorScheme based themes because ColorScheme does ' | |
'not set all Theme() color properties used by sub themes.\n\n' | |
'' | |
'For broader adoption of ColorScheme based theming, it would be ' | |
'useful if it behaved as might be expected and was more ' | |
'backwards compatible.\n\n' | |
'' | |
'This issue demo uses "ColorScheme.light()" and ' | |
'"ColorScheme.dark()" and Theme.from(colorscheme) for each ' | |
'of them to demonstrate incompleteness and inconsistencies in ' | |
'the resulting light and dark themes.', | |
), | |
const Divider(), | |
const Text( | |
'To observe inconsistencies, you need to switch between ' | |
'dark and light theme, as some are ok in light theme and others ' | |
'in dark theme and wise versa.', | |
), | |
SwitchListTile.adaptive( | |
title: const Text('Use dark theme'), | |
subtitle: | |
const Text('OFF for light theme, ON for dark theme.'), | |
value: isDarkMode, | |
onChanged: (bool value) { | |
setState(() { | |
isDarkMode = value; | |
widget.setDarkMode(isDarkMode); | |
}); | |
}, | |
), | |
const Divider(), | |
Text('toggleableActiveColor', style: headline5), | |
const Text( | |
'ColorScheme based themes will get the default dark theme based ' | |
'toggleable color and not the secondary color from the dark ' | |
'ColorScheme. The resulting color may or may not fit with ' | |
'the used colors in ColorScheme based theme. The default dark theme ' | |
'color is close to ColorScheme.dark() so it works fairly well for ' | |
'the default values, but not for general usage.\n', | |
), | |
Wrap( | |
children: <Widget>[ | |
const Text('Result:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.toggleableActiveColor, | |
child: Text( | |
'toggleableActiveColor\n\n' | |
'${theme.toggleableActiveColor}', | |
style: TextStyle( | |
color: theme.colorScheme.onSecondary)), | |
), | |
), | |
if (isDark) const Text('FAIL Expected:'), | |
if (!isDark) const Text('OK Expected:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.colorScheme.secondary, | |
child: Text( | |
'colorScheme.secondary\n\n' | |
'${theme.colorScheme.secondary}', | |
style: TextStyle( | |
color: theme.colorScheme.onSecondary)), | |
), | |
), | |
], | |
), | |
const Text( | |
'\nProposed fix:\n' | |
'ThemeData.from() should also set \n' | |
'toggleableActiveColor: colorScheme.secondary \n' | |
'for better backwards compatibility and so that the dark mode ' | |
'toggleableActiveColor also matches the ColorScheme based theme ' | |
'in dark mode and not only light mode.\n', | |
), | |
const Divider(), | |
Text('primaryColorDark', style: headline5), | |
const Text( | |
'ColorScheme based themes do not set any color at all ' | |
"for the Theme() color primaryColorDark. It only gets default value from " | |
'default Theme factory for both light and dark mode. The resulting color ' | |
'may or may not fit with the used colors in ColorScheme based theme.\n', | |
), | |
Wrap( | |
children: <Widget>[ | |
const Text('Result:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.primaryColorDark, | |
child: Text( | |
'primaryColorDark\n\n' | |
'${theme.primaryColorDark}', | |
style: TextStyle( | |
color: isDark | |
? theme.colorScheme.onSurface | |
: theme.colorScheme.onPrimary)), | |
), | |
), | |
if (!isDark) const Text('FAIL Expected\ndarker hue of:'), | |
if (isDark) const Text('OK Expected\ndarker hue of:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.primaryColor, | |
child: Text( | |
'primaryColor\n\n' | |
'${theme.primaryColor}', | |
style: TextStyle( | |
color: isDark | |
? theme.colorScheme.onSurface | |
: theme.colorScheme.onPrimary)), | |
), | |
), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.colorScheme.primary, | |
child: Text( | |
'colorScheme.primary\n\n' | |
'${theme.colorScheme.primary}', | |
style: | |
TextStyle(color: theme.colorScheme.onPrimary)), | |
), | |
), | |
if (isDark) | |
const Text( | |
'It is a matter of design/opinion if it should ' | |
'be darker than primaryColor or colorScheme.primary'), | |
const Text( | |
'\nProposed fix:\n' | |
'For a better fit with legacy themes and to match ' | |
'the colors in the ColorScheme the ThemeData.from() ' | |
'should set primaryColorDark to a slightly darker hue based on ' | |
'colorScheme.primary for light themes.\n', | |
), | |
], | |
), | |
const Divider(), | |
Text('primaryColorLight', style: headline5), | |
const Text( | |
'ColorScheme based themes do not set any color at all ' | |
"for the Theme() color primaryColorDark. It only gets default value from " | |
'default Theme factory for both light and dark mode. The resulting color ' | |
'may or may not fit with the used colors in ColorScheme based theme.\n', | |
), | |
Wrap( | |
children: <Widget>[ | |
const Text('Result:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.primaryColorLight, | |
child: Text( | |
'primaryColorLight\n\n' | |
'${theme.primaryColorLight}', | |
style: | |
TextStyle(color: theme.colorScheme.onSurface)), | |
), | |
), | |
if (!isDark) const Text('FAIL Expected\nlighter hue of:'), | |
if (isDark) const Text('OK Expected\nlighter hue of:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.primaryColor, | |
child: Text( | |
'primaryColor\n\n' | |
'${theme.primaryColor}', | |
style: TextStyle( | |
color: isDark | |
? theme.colorScheme.onSurface | |
: theme.colorScheme.onPrimary)), | |
), | |
), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.colorScheme.primary, | |
child: Text( | |
'colorScheme.primary\n\n' | |
'${theme.colorScheme.primary}', | |
style: | |
TextStyle(color: theme.colorScheme.onPrimary)), | |
), | |
), | |
if (isDark) | |
const Text( | |
'It is a matter of design/opinion if it should ' | |
'be lighter than primaryColor or colorScheme.primary', | |
), | |
const Text( | |
'\nProposed fix:\n' | |
'For a better fit with legacy themes and to match ' | |
'the colors in the ColorScheme the ThemeData.from() ' | |
'should set primaryColorLight to a lighter hue based on ' | |
'colorScheme.primary for light themes.\n', | |
), | |
], | |
), | |
const Divider(), | |
Text('secondaryHeaderColor', style: headline5), | |
const Text( | |
'ColorScheme based themes do not set any color at all ' | |
"for the Theme() color secondaryHeaderColor. It only gets default value from " | |
'default Theme factory for both light and dark mode. The resulting color ' | |
'may or may not fit with the used colors in ColorScheme based theme.\n', | |
), | |
Wrap( | |
children: <Widget>[ | |
const Text('Result:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.secondaryHeaderColor, | |
child: Text( | |
'secondaryHeaderColor\n\n' | |
'${theme.secondaryHeaderColor}', | |
style: | |
TextStyle(color: theme.colorScheme.onSurface)), | |
), | |
), | |
if (!isDark) | |
const Text('FAIL Expected\nmuch lighter hue of:'), | |
if (isDark) const Text('OK Expected\nlighter hue of:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.primaryColor, | |
child: Text( | |
'primaryColor\n\n' | |
'${theme.primaryColor}', | |
style: TextStyle( | |
color: isDark | |
? theme.colorScheme.onSurface | |
: theme.colorScheme.onPrimary)), | |
), | |
), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.colorScheme.primary, | |
child: Text( | |
'colorScheme.primary\n\n' | |
'${theme.colorScheme.primary}', | |
style: | |
TextStyle(color: theme.colorScheme.onPrimary)), | |
), | |
), | |
if (isDark) | |
const Text( | |
'It is a matter of design/opinion if it should ' | |
'be lighter than primaryColor or colorScheme.primary', | |
), | |
const Text( | |
'\nProposed fix:\n' | |
'For a better fit with legacy themes and to match ' | |
'the colors in the ColorScheme the ThemeData.from() ' | |
'should set secondaryHeaderColor to a much lighter hue based on ' | |
'colorScheme.primary for light themes.\n', | |
), | |
], | |
), | |
const Divider(), | |
const Text( | |
"Just a repeat of dark/light toggle so I don't have to scroll " | |
'all the way back up to toggle it.', | |
), | |
SwitchListTile.adaptive( | |
title: const Text('Use dark theme'), | |
subtitle: | |
const Text('OFF for light theme, ON for dark theme.'), | |
value: isDarkMode, | |
onChanged: (bool value) { | |
setState(() { | |
isDarkMode = value; | |
widget.setDarkMode(isDarkMode); | |
}); | |
}, | |
), | |
const Divider(), | |
Text('Text selection and ColorScheme', style: headline4), | |
const Text( | |
'The new "TextSelectionThemeData" and "useTextSelectionTheme" ' | |
'partially address the issues below, but only when the colors are used in ' | |
'"TextField() and "SelectableText()". Still the color properties in the ' | |
'resulting ThemeData are left at their default values, which may be confusing and ' | |
'they also do not match the applied ColorScheme based theme.', | |
), | |
SwitchListTile.adaptive( | |
title: const Text('Use text selection theme'), | |
subtitle: const Text( | |
'OFF for no selection theme, ON for text selection theme.'), | |
value: useSelectionTheme, | |
onChanged: (bool value) { | |
setState(() { | |
useSelectionTheme = value; | |
widget.setSelectionTheme(useSelectionTheme); | |
}); | |
}, | |
), | |
const Divider(), | |
const Text( | |
'Use the text field below to enter some text and select it ' | |
'to see the difference between using it and not using the ' | |
'text selection theme. Wether it is used or not, does not have ' | |
'any impact on the ThemeData proprties below as defined by the ColorScheme.', | |
), | |
const TextField(), | |
const Text( | |
'\nThe "useTextSelectionTheme" cannot be set with ColorScheme, ' | |
'an extra copyWith is required and even then the ThemeData result ' | |
'values are left at their Theme() defaults, this may be confusing.', | |
), | |
const Divider(), | |
Text('textSelectionColor', style: headline5), | |
const Text( | |
'ColorScheme based themes do not set any color at all ' | |
"for the Theme() color textSelectionColor. It only gets default value from " | |
'default Theme factory for both light and dark mode. The resulting color ' | |
'may or may not fit with the used colors in ColorScheme based theme.\n', | |
), | |
Wrap( | |
children: <Widget>[ | |
const Text('Result:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.textSelectionColor, | |
child: Text( | |
'textSelectionColor\n\n' | |
'${theme.cursorColor}', | |
style: | |
TextStyle(color: theme.colorScheme.onSurface)), | |
), | |
), | |
if (!isDark) | |
const Text( | |
'FAIL Expected\nlighter hue of\ncolorScheme.primary:'), | |
if (isDark) | |
const Text('FAIL Expected\ncolorScheme.secondary:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.colorScheme.primary, | |
child: Text( | |
'colorScheme.primary\n\n' | |
'${theme.colorScheme.primary}', | |
style: | |
TextStyle(color: theme.colorScheme.onPrimary)), | |
), | |
), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.colorScheme.secondary, | |
child: Text( | |
'colorScheme.secondary\n\n' | |
'${theme.colorScheme.secondary}', | |
style: TextStyle( | |
color: theme.colorScheme.onSecondary)), | |
), | |
), | |
if (!isDark) | |
const Text( | |
'If following textSelectionTheme, then expected colorScheme.primary with opacity(0.12)'), | |
if (isDark) | |
const Text( | |
'If following textSelectionTheme, then expected colorScheme.primary with opacity(0.40)'), | |
const Text( | |
'\nProposed fix:\n' | |
'For a better fit with legacy themes and to match ' | |
'the colors in the ColorScheme the ThemeData.from() ' | |
'should set textSelectionColor to a lighter hue based on ' | |
'colorScheme.primary for light themes and to colorScheme.secondary ' | |
'for dark themes.\n' | |
'Alternatively:\n' | |
'ColorScheme based themes could ' | |
'set the Theme() property to the colorScheme.primary based ' | |
'colors introduced with TextSelectionThemeData, this would not ' | |
'break older Theme() based themes and would make the property ' | |
'correctly ColorScheme based.\n', | |
), | |
], | |
), | |
const Divider(), | |
Text('textSelectionHandleColor', style: headline5), | |
const Text( | |
'ColorScheme based themes do not set any color at all ' | |
"for the Theme() color textSelectionHandleColor. It only gets default value from " | |
'default Theme factory for both light and dark mode. The resulting color ' | |
'may or may not fit with the used colors in ColorScheme based theme.\n', | |
), | |
Wrap( | |
children: <Widget>[ | |
const Text('Result:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.textSelectionHandleColor, | |
child: Text( | |
'textSelectionHandleColor\n\n' | |
'${theme.textSelectionHandleColor}', | |
style: | |
TextStyle(color: theme.colorScheme.onSurface)), | |
), | |
), | |
if (!isDark) | |
const Text( | |
'FAIL Expected\nlighter hue of\ncolorScheme.primary:'), | |
if (isDark) | |
const Text('FAIL Expected\ncolorScheme.secondary:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.colorScheme.primary, | |
child: Text( | |
'colorScheme.primary\n\n' | |
'${theme.colorScheme.primary}', | |
style: | |
TextStyle(color: theme.colorScheme.onPrimary)), | |
), | |
), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.colorScheme.secondary, | |
child: Text( | |
'colorScheme.secondary\n\n' | |
'${theme.colorScheme.secondary}', | |
style: TextStyle( | |
color: theme.colorScheme.onSecondary)), | |
), | |
), | |
], | |
), | |
const Text( | |
'If following textSelectionTheme, then expected colorScheme.primary'), | |
const Text( | |
'\nProposed fix:\n' | |
'For a better fit with legacy themes and to match ' | |
'the colors in the ColorScheme the ThemeData.from() ' | |
'should set textSelectionHandleColor to a lighter hue based on ' | |
'colorScheme.primary for light themes and to a darker hue of ' | |
'colorScheme.secondary for dark themes.\n' | |
'Alternatively:\n' | |
'ColorScheme based themes could ' | |
'set the Theme() property to the colorScheme.primary based ' | |
'colors introduced with TextSelectionThemeData, this would not ' | |
'break older Theme() based themes and would make the property ' | |
'correctly ColorScheme based.\n', | |
), | |
const Divider(), | |
Text('cursorColor', style: headline5), | |
const Text( | |
'ColorScheme based themes do not set any color at all ' | |
"for the Theme() color cursorColor. It only gets default value from " | |
'default Theme factory for both light and dark mode. The resulting color ' | |
'may or may not fit with the used colors in ColorScheme based theme.\n', | |
), | |
Wrap( | |
children: <Widget>[ | |
const Text('Result:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.cursorColor, | |
child: Text( | |
'cursorColor\n\n' | |
'${theme.cursorColor}', | |
style: | |
TextStyle(color: theme.colorScheme.onSurface)), | |
), | |
), | |
const Text( | |
'FAIL Expected\ncolorScheme.primary\nor a hue of it:'), | |
Padding( | |
padding: const EdgeInsets.all(8.0), | |
child: Container( | |
padding: const EdgeInsets.all(8.0), | |
color: theme.colorScheme.primary, | |
child: Text( | |
'colorScheme.primary\n\n' | |
'${theme.colorScheme.primary}', | |
style: | |
TextStyle(color: theme.colorScheme.onPrimary)), | |
), | |
), | |
], | |
), | |
const Text( | |
'If following textSelectionTheme, then expected colorScheme.primary'), | |
const Text( | |
'\nProposed fix:\n' | |
'For a better fit with legacy themes and to match ' | |
'the colors in the ColorScheme the ThemeData.from() ' | |
'should set cursorColor to colorScheme.primary or some hue of it.\n' | |
'Alternatively:\n' | |
'ColorScheme based themes could ' | |
'set the Theme() property to the colorScheme.primary based ' | |
'colors introduced with TextSelectionThemeData, this would not ' | |
'break older Theme() based themes and would make the property ' | |
'correctly ColorScheme based.\n\n' | |
'It is worth noticing that default Theme() uses the same fixed blue ' | |
'color for light and dark mode and that this blue color ' | |
'is not any variant of the default blue colors used in the default ' | |
'blue primary swatch, which is a bit odd. It is very close to the ' | |
'default blue hues in default Theme() and thus works well with the ' | |
'the default Theme(), but it does not work well with ColorScheme ' | |
'themes or any other none default and none blue based theme.\n', | |
), | |
const Divider(), | |
Text('Source code for this demo', style: headline5), | |
const SelectableText( | |
'https://gist.github.com/rydmike/b59cea071eefdbe7c1332b2d014bc156'), | |
const Divider(), | |
], | |
), | |
), | |
), | |
), | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Demo code for Flutter issue:
ColorScheme Theme Quality Issues - Gaps in completeness and inconsistencies #65782
flutter/flutter#65782