Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/dynamic_color/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Update other dependencies
- Update `compileSdkVersion` to `34`
- Fix lints
- Add new tone-based colors from Flutter 3.22

## 1.7.0 - 2024-03-01
### Changed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ class DynamicColorPlugin : FlutterPlugin, MethodCallHandler {
}
}

"getColorSchemes" -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
val resources: Resources = binding.applicationContext.resources
result.success(getColorScheme(resources))
} else {
result.success(null)
}
}

else -> result.notImplemented()
}
}
Expand Down Expand Up @@ -120,4 +129,98 @@ class DynamicColorPlugin : FlutterPlugin, MethodCallHandler {
resources.getColor(android.R.color.system_neutral2_0, null),
);
}

@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private fun getColorScheme(resources: Resources): IntArray {
return intArrayOf(
// light
Copy link
Collaborator

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?

Copy link
Author

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.

resources.getColor(android.R.color.system_primary_light, null),
resources.getColor(android.R.color.system_on_primary_light, null),
resources.getColor(android.R.color.system_primary_container_light, null),
resources.getColor(android.R.color.system_on_primary_container_light, null),
resources.getColor(android.R.color.system_primary_fixed, null),
resources.getColor(android.R.color.system_primary_fixed_dim, null),
resources.getColor(android.R.color.system_on_primary_fixed, null),
resources.getColor(android.R.color.system_on_primary_fixed_variant, null),
resources.getColor(android.R.color.system_secondary_light, null),
resources.getColor(android.R.color.system_on_secondary_light, null),
resources.getColor(android.R.color.system_secondary_container_light, null),
resources.getColor(android.R.color.system_on_secondary_container_light, null),
resources.getColor(android.R.color.system_secondary_fixed, null),
resources.getColor(android.R.color.system_secondary_fixed_dim, null),
resources.getColor(android.R.color.system_on_secondary_fixed, null),
resources.getColor(android.R.color.system_on_secondary_fixed_variant, null),
resources.getColor(android.R.color.system_tertiary_light, null),
resources.getColor(android.R.color.system_on_tertiary_light, null),
resources.getColor(android.R.color.system_tertiary_container_light, null),
resources.getColor(android.R.color.system_on_tertiary_container_light, null),
resources.getColor(android.R.color.system_tertiary_fixed, null),
resources.getColor(android.R.color.system_tertiary_fixed_dim, null),
resources.getColor(android.R.color.system_on_tertiary_fixed, null),
resources.getColor(android.R.color.system_on_tertiary_fixed_variant, null),
resources.getColor(android.R.color.system_error_light, null),
resources.getColor(android.R.color.system_on_error_light, null),
resources.getColor(android.R.color.system_error_container_light, null),
resources.getColor(android.R.color.system_on_error_container_light, null),
resources.getColor(android.R.color.system_surface_light, null),
resources.getColor(android.R.color.system_on_surface_light, null),
resources.getColor(android.R.color.system_surface_dim_light, null),
resources.getColor(android.R.color.system_surface_bright_light, null),
resources.getColor(android.R.color.system_on_surface_variant_light, null),
resources.getColor(android.R.color.system_surface_container_lowest_light, null),
resources.getColor(android.R.color.system_surface_container_low_light, null),
resources.getColor(android.R.color.system_surface_container_light, null),
resources.getColor(android.R.color.system_surface_container_high_light, null),
resources.getColor(android.R.color.system_surface_container_highest_light, null),
resources.getColor(android.R.color.system_surface_dark, null),
resources.getColor(android.R.color.system_on_surface_dark, null),
resources.getColor(android.R.color.system_primary_dark, null),
resources.getColor(android.R.color.system_outline_light, null),
resources.getColor(android.R.color.system_outline_variant_light, null),
// dark
resources.getColor(android.R.color.system_primary_dark, null),
resources.getColor(android.R.color.system_on_primary_dark, null),
resources.getColor(android.R.color.system_primary_container_dark, null),
resources.getColor(android.R.color.system_on_primary_container_dark, null),
resources.getColor(android.R.color.system_primary_fixed, null),
resources.getColor(android.R.color.system_primary_fixed_dim, null),
resources.getColor(android.R.color.system_on_primary_fixed, null),
resources.getColor(android.R.color.system_on_primary_fixed_variant, null),
resources.getColor(android.R.color.system_secondary_dark, null),
resources.getColor(android.R.color.system_on_secondary_dark, null),
resources.getColor(android.R.color.system_secondary_container_dark, null),
resources.getColor(android.R.color.system_on_secondary_container_dark, null),
resources.getColor(android.R.color.system_secondary_fixed, null),
resources.getColor(android.R.color.system_secondary_fixed_dim, null),
resources.getColor(android.R.color.system_on_secondary_fixed, null),
resources.getColor(android.R.color.system_on_secondary_fixed_variant, null),
resources.getColor(android.R.color.system_tertiary_dark, null),
resources.getColor(android.R.color.system_on_tertiary_dark, null),
resources.getColor(android.R.color.system_tertiary_container_dark, null),
resources.getColor(android.R.color.system_on_tertiary_container_dark, null),
resources.getColor(android.R.color.system_tertiary_fixed, null),
resources.getColor(android.R.color.system_tertiary_fixed_dim, null),
resources.getColor(android.R.color.system_on_tertiary_fixed, null),
resources.getColor(android.R.color.system_on_tertiary_fixed_variant, null),
resources.getColor(android.R.color.system_error_dark, null),
resources.getColor(android.R.color.system_on_error_dark, null),
resources.getColor(android.R.color.system_error_container_dark, null),
resources.getColor(android.R.color.system_on_error_container_dark, null),
resources.getColor(android.R.color.system_surface_dark, null),
resources.getColor(android.R.color.system_on_surface_dark, null),
resources.getColor(android.R.color.system_surface_dim_dark, null),
resources.getColor(android.R.color.system_surface_bright_dark, null),
resources.getColor(android.R.color.system_on_surface_variant_dark, null),
resources.getColor(android.R.color.system_surface_container_lowest_dark, null),
resources.getColor(android.R.color.system_surface_container_low_dark, null),
resources.getColor(android.R.color.system_surface_container_dark, null),
resources.getColor(android.R.color.system_surface_container_high_dark, null),
resources.getColor(android.R.color.system_surface_container_highest_dark, null),
resources.getColor(android.R.color.system_surface_light, null),
resources.getColor(android.R.color.system_on_surface_light, null),
resources.getColor(android.R.color.system_primary_light, null),
resources.getColor(android.R.color.system_outline_dark, null),
resources.getColor(android.R.color.system_outline_variant_dark, null),
)
}
}
66 changes: 66 additions & 0 deletions packages/dynamic_color/lib/src/color_schemes.dart
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),
);
}
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is somewhat fragile an difficult to validate correctness with DynamicColorPlugin.kt

Copy link
Author

Choose a reason for hiding this comment

The 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]),
);
}
169 changes: 128 additions & 41 deletions packages/dynamic_color/lib/src/corepalette_to_colorscheme.dart
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';

Expand All @@ -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)),
Copy link
Collaborator

Choose a reason for hiding this comment

The 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

Copy link
Author

Choose a reason for hiding this comment

The 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The 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());
}
Loading