-
Notifications
You must be signed in to change notification settings - Fork 167
[dynamic_color] Add tone-based colors to color scheme #599
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
02ca6c7
9cdfd8c
96c1f1d
afe5763
95b450a
293eabf
78667e1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import 'package:flutter/material.dart'; | ||
|
||
class ColorSchemes { | ||
final ColorScheme light; | ||
final ColorScheme dark; | ||
|
||
const ColorSchemes({required this.light, required this.dark}); | ||
|
||
factory ColorSchemes.fromList(List<int> colors) { | ||
return ColorSchemes( | ||
light: _colorSchemeFromList(colors, 0, Brightness.light), | ||
dark: _colorSchemeFromList(colors, 1, Brightness.dark), | ||
); | ||
} | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is somewhat fragile an difficult to validate correctness with DynamicColorPlugin.kt There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, I'll see if I can improve that. Perhaps a map with readable names instead of a list? |
||
ColorScheme _colorSchemeFromList( | ||
List<int> colors, int n, Brightness brightness) { | ||
int offset = (colors.length ~/ 2) * n; | ||
return ColorScheme( | ||
brightness: brightness, | ||
primary: Color(colors[offset + 0]), | ||
onPrimary: Color(colors[offset + 1]), | ||
primaryContainer: Color(colors[offset + 2]), | ||
onPrimaryContainer: Color(colors[offset + 3]), | ||
primaryFixed: Color(colors[offset + 4]), | ||
primaryFixedDim: Color(colors[offset + 5]), | ||
onPrimaryFixed: Color(colors[offset + 6]), | ||
onPrimaryFixedVariant: Color(colors[offset + 7]), | ||
secondary: Color(colors[offset + 8]), | ||
onSecondary: Color(colors[offset + 9]), | ||
secondaryContainer: Color(colors[offset + 10]), | ||
onSecondaryContainer: Color(colors[offset + 11]), | ||
secondaryFixed: Color(colors[offset + 12]), | ||
secondaryFixedDim: Color(colors[offset + 13]), | ||
onSecondaryFixed: Color(colors[offset + 14]), | ||
onSecondaryFixedVariant: Color(colors[offset + 15]), | ||
tertiary: Color(colors[offset + 16]), | ||
onTertiary: Color(colors[offset + 17]), | ||
tertiaryContainer: Color(colors[offset + 18]), | ||
onTertiaryContainer: Color(colors[offset + 19]), | ||
tertiaryFixed: Color(colors[offset + 20]), | ||
tertiaryFixedDim: Color(colors[offset + 21]), | ||
onTertiaryFixed: Color(colors[offset + 22]), | ||
onTertiaryFixedVariant: Color(colors[offset + 23]), | ||
error: Color(colors[offset + 24]), | ||
onError: Color(colors[offset + 25]), | ||
errorContainer: Color(colors[offset + 26]), | ||
onErrorContainer: Color(colors[offset + 27]), | ||
surface: Color(colors[offset + 28]), | ||
onSurface: Color(colors[offset + 29]), | ||
surfaceDim: Color(colors[offset + 30]), | ||
surfaceBright: Color(colors[offset + 31]), | ||
onSurfaceVariant: Color(colors[offset + 32]), | ||
surfaceContainerLowest: Color(colors[offset + 33]), | ||
surfaceContainerLow: Color(colors[offset + 34]), | ||
surfaceContainer: Color(colors[offset + 35]), | ||
surfaceContainerHigh: Color(colors[offset + 36]), | ||
surfaceContainerHighest: Color(colors[offset + 37]), | ||
inverseSurface: Color(colors[offset + 38]), | ||
onInverseSurface: Color(colors[offset + 39]), | ||
inversePrimary: Color(colors[offset + 40]), | ||
outline: Color(colors[offset + 41]), | ||
outlineVariant: Color(colors[offset + 42]), | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,3 @@ | ||
// TODO(guidezpl): remove ignore after migration to new color roles | ||
// ignore_for_file: deprecated_member_use | ||
|
||
import 'package:flutter/material.dart'; | ||
import 'package:material_color_utilities/material_color_utilities.dart'; | ||
|
||
|
@@ -9,47 +6,137 @@ extension CorePaletteToColorScheme on CorePalette { | |
ColorScheme toColorScheme({ | ||
Brightness brightness = Brightness.light, | ||
}) { | ||
final Scheme scheme; | ||
|
||
switch (brightness) { | ||
case Brightness.light: | ||
scheme = Scheme.lightFromCorePalette(this); | ||
break; | ||
return _toLightColorScheme(this); | ||
case Brightness.dark: | ||
scheme = Scheme.darkFromCorePalette(this); | ||
break; | ||
return _toDarkColorScheme(this); | ||
} | ||
} | ||
} | ||
|
||
ColorScheme _toLightColorScheme(CorePalette corePalette) { | ||
return ColorScheme( | ||
brightness: Brightness.light, | ||
primary: Color(corePalette.primary.get(40)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, these values only work for one type of scheme, there are many types. See https://github.com/material-foundation/material-color-utilities/blob/main/dev_guide/creating_color_scheme.md There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've read through that page and I confess I'm not entirely sure what you mean. The point of this code is to generate a specific colour scheme based on the system colour palette. The values used here are taken from the official Android material library - https://github.com/material-components/material-components-android/blob/master/docs/theming/Color.md. The dynamic colour implementation in Jetpack Compose is also implemented in a similar way. |
||
onPrimary: Color(corePalette.primary.get(100)), | ||
primaryContainer: Color(corePalette.primary.get(90)), | ||
onPrimaryContainer: Color(corePalette.primary.get(10)), | ||
primaryFixed: Color(corePalette.primary.get(90)), | ||
primaryFixedDim: Color(corePalette.primary.get(80)), | ||
onPrimaryFixed: Color(corePalette.primary.get(10)), | ||
onPrimaryFixedVariant: Color(corePalette.primary.get(30)), | ||
secondary: Color(corePalette.secondary.get(40)), | ||
onSecondary: Color(corePalette.secondary.get(100)), | ||
secondaryContainer: Color(corePalette.secondary.get(90)), | ||
onSecondaryContainer: Color(corePalette.secondary.get(10)), | ||
secondaryFixed: Color(corePalette.secondary.get(90)), | ||
secondaryFixedDim: Color(corePalette.secondary.get(80)), | ||
onSecondaryFixed: Color(corePalette.secondary.get(10)), | ||
onSecondaryFixedVariant: Color(corePalette.secondary.get(30)), | ||
tertiary: Color(corePalette.tertiary.get(40)), | ||
onTertiary: Color(corePalette.tertiary.get(100)), | ||
tertiaryContainer: Color(corePalette.tertiary.get(90)), | ||
onTertiaryContainer: Color(corePalette.tertiary.get(10)), | ||
tertiaryFixed: Color(corePalette.tertiary.get(90)), | ||
tertiaryFixedDim: Color(corePalette.tertiary.get(80)), | ||
onTertiaryFixed: Color(corePalette.tertiary.get(10)), | ||
onTertiaryFixedVariant: Color(corePalette.tertiary.get(30)), | ||
error: Color(corePalette.error.get(40)), | ||
onError: Color(corePalette.error.get(100)), | ||
errorContainer: Color(corePalette.error.get(90)), | ||
onErrorContainer: Color(corePalette.error.get(10)), | ||
surface: _guessTone(corePalette.neutral, 98), | ||
onSurface: Color(corePalette.neutral.get(10)), | ||
surfaceDim: _guessTone(corePalette.neutral, 87), | ||
surfaceBright: _guessTone(corePalette.neutral, 98), | ||
onSurfaceVariant: Color(corePalette.neutralVariant.get(30)), | ||
surfaceContainerLowest: Color(corePalette.neutral.get(100)), | ||
surfaceContainerLow: _guessTone(corePalette.neutral, 96), | ||
surfaceContainer: _guessTone(corePalette.neutral, 94), | ||
surfaceContainerHigh: _guessTone(corePalette.neutral, 92), | ||
surfaceContainerHighest: Color(corePalette.neutral.get(90)), | ||
inverseSurface: Color(corePalette.neutral.get(20)), | ||
onInverseSurface: Color(corePalette.neutral.get(95)), | ||
inversePrimary: Color(corePalette.primary.get(80)), | ||
outline: Color(corePalette.neutralVariant.get(50)), | ||
outlineVariant: Color(corePalette.neutralVariant.get(80)), | ||
); | ||
} | ||
|
||
ColorScheme _toDarkColorScheme(CorePalette corePalette) { | ||
return ColorScheme( | ||
brightness: Brightness.dark, | ||
primary: Color(corePalette.primary.get(80)), | ||
onPrimary: Color(corePalette.primary.get(20)), | ||
primaryContainer: Color(corePalette.primary.get(30)), | ||
onPrimaryContainer: Color(corePalette.primary.get(90)), | ||
primaryFixed: Color(corePalette.primary.get(90)), | ||
primaryFixedDim: Color(corePalette.primary.get(80)), | ||
onPrimaryFixed: Color(corePalette.primary.get(10)), | ||
onPrimaryFixedVariant: Color(corePalette.primary.get(30)), | ||
secondary: Color(corePalette.secondary.get(80)), | ||
onSecondary: Color(corePalette.secondary.get(20)), | ||
secondaryContainer: Color(corePalette.secondary.get(30)), | ||
onSecondaryContainer: Color(corePalette.secondary.get(90)), | ||
secondaryFixed: Color(corePalette.secondary.get(90)), | ||
secondaryFixedDim: Color(corePalette.secondary.get(80)), | ||
onSecondaryFixed: Color(corePalette.secondary.get(10)), | ||
onSecondaryFixedVariant: Color(corePalette.secondary.get(30)), | ||
tertiary: Color(corePalette.tertiary.get(80)), | ||
onTertiary: Color(corePalette.tertiary.get(20)), | ||
tertiaryContainer: Color(corePalette.tertiary.get(30)), | ||
onTertiaryContainer: Color(corePalette.tertiary.get(90)), | ||
tertiaryFixed: Color(corePalette.tertiary.get(90)), | ||
tertiaryFixedDim: Color(corePalette.tertiary.get(80)), | ||
onTertiaryFixed: Color(corePalette.tertiary.get(10)), | ||
onTertiaryFixedVariant: Color(corePalette.tertiary.get(30)), | ||
error: Color(corePalette.error.get(80)), | ||
onError: Color(corePalette.error.get(20)), | ||
errorContainer: Color(corePalette.error.get(30)), | ||
onErrorContainer: Color(corePalette.error.get(90)), | ||
surface: _guessTone(corePalette.neutral, 6), | ||
onSurface: Color(corePalette.neutral.get(90)), | ||
surfaceDim: _guessTone(corePalette.neutral, 6), | ||
surfaceBright: _guessTone(corePalette.neutral, 24), | ||
onSurfaceVariant: Color(corePalette.neutralVariant.get(80)), | ||
surfaceContainerLowest: _guessTone(corePalette.neutral, 4), | ||
surfaceContainerLow: Color(corePalette.neutral.get(10)), | ||
surfaceContainer: _guessTone(corePalette.neutral, 12), | ||
surfaceContainerHigh: _guessTone(corePalette.neutral, 17), | ||
surfaceContainerHighest: _guessTone(corePalette.neutral, 22), | ||
inverseSurface: Color(corePalette.neutral.get(90)), | ||
onInverseSurface: Color(corePalette.neutral.get(20)), | ||
inversePrimary: Color(corePalette.primary.get(40)), | ||
outline: Color(corePalette.neutralVariant.get(60)), | ||
outlineVariant: Color(corePalette.neutralVariant.get(30)), | ||
); | ||
} | ||
|
||
// This logic is taken from material_color_utilities 0.12 - https://github.com/material-foundation/material-color-utilities/blob/be615fc90286787bbe0c04ef58a6987e0e8fdc29/dart/lib/palettes/tonal_palette.dart#L93C5-L111. | ||
// Once flutter updates to the latest version, this workaround can be removed. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Best to wait for flutter/flutter#170000 rather than introduce workarounds IMO |
||
Color _guessTone(TonalPalette palette, double tone) { | ||
// Approximately deduces the original hue and chroma that generated this | ||
// list of colors. | ||
// Uses the hue and chroma of the provided color with the highest chroma. | ||
|
||
var bestHue = 0.0, bestChroma = 0.0; | ||
for (final argb in palette.asList) { | ||
final hct = Hct.fromInt(argb); | ||
if (hct.tone == tone) { | ||
return Color(hct.toInt()); | ||
} | ||
|
||
// If the color is too close to white, its chroma may have been | ||
// affected by a known issue, so we ignore it. | ||
// https://github.com/material-foundation/material-color-utilities/issues/140 | ||
|
||
if (hct.tone > 98.0) continue; | ||
|
||
if (hct.chroma > bestChroma) { | ||
bestHue = hct.hue; | ||
bestChroma = hct.chroma; | ||
} | ||
return ColorScheme( | ||
primary: Color(scheme.primary), | ||
onPrimary: Color(scheme.onPrimary), | ||
primaryContainer: Color(scheme.primaryContainer), | ||
onPrimaryContainer: Color(scheme.onPrimaryContainer), | ||
secondary: Color(scheme.secondary), | ||
onSecondary: Color(scheme.onSecondary), | ||
secondaryContainer: Color(scheme.secondaryContainer), | ||
onSecondaryContainer: Color(scheme.onSecondaryContainer), | ||
tertiary: Color(scheme.tertiary), | ||
onTertiary: Color(scheme.onTertiary), | ||
tertiaryContainer: Color(scheme.tertiaryContainer), | ||
onTertiaryContainer: Color(scheme.onTertiaryContainer), | ||
error: Color(scheme.error), | ||
onError: Color(scheme.onError), | ||
errorContainer: Color(scheme.errorContainer), | ||
onErrorContainer: Color(scheme.onErrorContainer), | ||
outline: Color(scheme.outline), | ||
outlineVariant: Color(scheme.outlineVariant), | ||
background: Color(scheme.background), | ||
onBackground: Color(scheme.onBackground), | ||
surface: Color(scheme.surface), | ||
onSurface: Color(scheme.onSurface), | ||
surfaceVariant: Color(scheme.surfaceVariant), | ||
onSurfaceVariant: Color(scheme.onSurfaceVariant), | ||
inverseSurface: Color(scheme.inverseSurface), | ||
onInverseSurface: Color(scheme.inverseOnSurface), | ||
inversePrimary: Color(scheme.inversePrimary), | ||
shadow: Color(scheme.shadow), | ||
scrim: Color(scheme.scrim), | ||
brightness: brightness, | ||
); | ||
} | ||
return Color(Hct.from(bestHue, bestChroma, tone).toInt()); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not obtain
system_palette_key_color_primary_light
and other key colors and generate the scheme directly?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that guaranteed to return the same colours as this code that is querying from the system? Again, this code is based on the official Android implementation here, and I think it would be good to keep the implementation as similar as possible to ensure consistency with non-Flutter apps.