From c729380704f05a9cb07f26e25388e43d62fe732f Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Tue, 21 May 2024 09:43:33 +0300 Subject: [PATCH 01/37] Change flutter version constraint --- CHANGELOG.md | 8 ++++++++ example/pubspec.yaml | 4 ++-- pubspec.yaml | 4 ++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20663f2..5b57a89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. +## 2.1.0-dev.1 + +This is a pre-release of FFS 2.1.0, that will be used to add and test some new preconfigured `FlexTones` with no primary chromacity in neutral palettes. + +**FIX** +* [FIX #13](https://github.com/rydmike/flex_seed_scheme/issues/13). The planned new tones are not yet included in this dev release, but it sets Flutter version constraint to flutter: '>=3.22.0-0.3.pre', so that the package can also be used on **beta** and **stable** channels, that are still on 3.22.0-a.b.pre versions, which is considered smaller than **3.22.0**, used in the stable release of the package. You can use this version of the package if you need to use **beta** or **master** channel. It is apart from the version constraint difference identical to the `2.0.0` release. + + ## 2.0.0 **May 14, 2024** diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 96c1189..4c6dbee 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,10 +1,10 @@ name: flex_seed_scheme_example description: Example that demonstrate how to use the FlexSeedScheme package. -version: 2.0.0 +version: 2.1.0-dev.1 publish_to: 'none' environment: sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.22.0' + flutter: '3.22.0-0.3.pre' dependencies: # Add Cupertino Icons font to your application. Used by the diff --git a/pubspec.yaml b/pubspec.yaml index ef2ba60..ab47bbe 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flex_seed_scheme description: A more flexible and powerful version of Flutter's ColorScheme.fromSeed. Use multiple seed colors, custom chroma and tone mapping. -version: 2.0.0 +version: 2.1.0-dev.1 homepage: https://github.com/rydmike/flex_seed_scheme repository: https://github.com/rydmike/flex_seed_scheme issue_tracker: https://github.com/rydmike/flex_seed_scheme/issues @@ -29,7 +29,7 @@ topics: environment: sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.22.0' + flutter: '>=3.22.0-0.3.pre' dependencies: # Used by mcu: tonal_palette.dart From 4ba37785d1690a2afc1dc1e4b1a844948aa97151 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 16 Jun 2024 17:20:44 +0300 Subject: [PATCH 02/37] Updated `MaterialDynamicColors` to use the expressive on-colors spec --- .fvmrc | 2 +- CHANGELOG.md | 8 +++++++ example/pubspec.lock | 14 +++++------ example/pubspec.yaml | 8 +++---- .../dynamiccolor/material_dynamic_colors.dart | 24 ++++++++++++------- pubspec.yaml | 4 ++-- test/mcu/dynamic_color_test.dart | 2 +- test/mcu/scheme_correctness_test.dart | 8 +++---- 8 files changed, 43 insertions(+), 27 deletions(-) diff --git a/.fvmrc b/.fvmrc index c300356..1c299f8 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "stable" + "flutter": "master" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b57a89..bc2b660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. +## 2.1.0-dev.2 + +This is a pre-release of FFS 2.1.0, that will be used to add and test some new preconfigured `FlexTones` with no primary chromacity in neutral palettes. + +* **CHANGE** +- Updated `MaterialDynamicColors` to use the expressive on-colors spec. This brings the internal Material Color Utilities up to version 0.12.0 on pub. +- Sets Flutter constraint back to `>=3.22.0` since beta and master are no higher than `3.22.0` it can be done. + ## 2.1.0-dev.1 This is a pre-release of FFS 2.1.0, that will be used to add and test some new preconfigured `FlexTones` with no primary chromacity in neutral palettes. diff --git a/example/pubspec.lock b/example/pubspec.lock index 52d5039..3894240 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -61,17 +61,17 @@ packages: dependency: "direct main" description: name: flex_color_picker - sha256: "5c846437069fb7afdd7ade6bf37e628a71d2ab0787095ddcb1253bf9345d5f3a" + sha256: "31b27677d8d8400e4cff5edb3f189f606dd964d608779b6ae1b7ddad37ea48c6" url: "https://pub.dev" source: hosted - version: "3.4.1" + version: "3.5.0" flex_seed_scheme: dependency: "direct main" description: path: ".." relative: true source: path - version: "2.0.0" + version: "2.1.0-dev.2" flutter: dependency: "direct main" description: flutter @@ -208,18 +208,18 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.2.6" + version: "6.3.0" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775" + sha256: "17cd5e205ea615e2c6ea7a77323a11712dffa0720a8a90540db57a01347f9ad9" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_ios: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 4c6dbee..daa154e 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,10 +1,10 @@ name: flex_seed_scheme_example description: Example that demonstrate how to use the FlexSeedScheme package. -version: 2.1.0-dev.1 +version: 2.1.0-dev.2 publish_to: 'none' environment: sdk: '>=3.0.0 <4.0.0' - flutter: '3.22.0-0.3.pre' + flutter: '3.22.0' dependencies: # Add Cupertino Icons font to your application. Used by the @@ -15,7 +15,7 @@ dependencies: # Color picker package, by Mike Rydstrom, rydmike.com (@rydmike). # Used for color selection in the Playground to make customized color schemes. # https://pub.dev/packages/flex_color_picker - flex_color_picker: ^3.4.1 + flex_color_picker: ^3.5.0 # FlexSeedScheme package, by Mike Rydstrom, rydmike.com (@rydmike). # https://pub.dev/packages/flex_seed_scheme @@ -28,7 +28,7 @@ dependencies: # Used for launching a WEB URL, by Google flutter.dev. # https://pub.dev/packages/url_launcher - url_launcher: ^6.2.6 + url_launcher: ^6.3.0 dev_dependencies: flutter_test: diff --git a/lib/src/mcu/dynamiccolor/material_dynamic_colors.dart b/lib/src/mcu/dynamiccolor/material_dynamic_colors.dart index da04d7b..ff94cc6 100644 --- a/lib/src/mcu/dynamiccolor/material_dynamic_colors.dart +++ b/lib/src/mcu/dynamiccolor/material_dynamic_colors.dart @@ -318,10 +318,10 @@ class MaterialDynamicColors { if (_isMonochrome(s)) { return s.isDark ? 0 : 100; } - return s.isDark ? 90 : 10; + return s.isDark ? 90 : 30; }, background: (DynamicScheme s) => MaterialDynamicColors.primaryContainer, - contrastCurve: ContrastCurve(4.5, 7, 11, 21), + contrastCurve: ContrastCurve(3, 4.5, 7, 11), ); /// Get DynamicColor for inversePrimary. @@ -400,14 +400,17 @@ class MaterialDynamicColors { name: 'on_secondary_container', palette: (DynamicScheme s) => s.secondaryPalette, tone: (DynamicScheme s) { - if (!_isFidelity(s)) { + if (_isMonochrome(s)) { return s.isDark ? 90 : 10; } + if (!_isFidelity(s)) { + return s.isDark ? 90 : 30; + } return DynamicColor.foregroundTone( MaterialDynamicColors.secondaryContainer.tone(s), 4.5); }, background: (DynamicScheme s) => MaterialDynamicColors.secondaryContainer, - contrastCurve: ContrastCurve(4.5, 7, 11, 21), + contrastCurve: ContrastCurve(3, 4.5, 7, 11), ); /// Get DynamicColor for tertiary. @@ -479,13 +482,13 @@ class MaterialDynamicColors { return s.isDark ? 0 : 100; } if (!_isFidelity(s)) { - return s.isDark ? 90 : 10; + return s.isDark ? 90 : 30; } return DynamicColor.foregroundTone( MaterialDynamicColors.tertiaryContainer.tone(s), 4.5); }, background: (DynamicScheme s) => MaterialDynamicColors.tertiaryContainer, - contrastCurve: ContrastCurve(4.5, 7, 11, 21), + contrastCurve: ContrastCurve(3, 4.5, 7, 11), ); /// Get DynamicColor for error. @@ -533,9 +536,14 @@ class MaterialDynamicColors { static DynamicColor onErrorContainer = DynamicColor.fromPalette( name: 'on_error_container', palette: (DynamicScheme s) => s.errorPalette, - tone: (DynamicScheme s) => s.isDark ? 90 : 10, + tone: (DynamicScheme s) { + if (_isMonochrome(s)) { + return s.isDark ? 90 : 10; + } + return s.isDark ? 90 : 30; + }, background: (DynamicScheme s) => MaterialDynamicColors.errorContainer, - contrastCurve: ContrastCurve(4.5, 7, 11, 21), + contrastCurve: ContrastCurve(3, 4.5, 7, 11), ); /// Get DynamicColor for primaryFixed. diff --git a/pubspec.yaml b/pubspec.yaml index ab47bbe..a1a61d6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flex_seed_scheme description: A more flexible and powerful version of Flutter's ColorScheme.fromSeed. Use multiple seed colors, custom chroma and tone mapping. -version: 2.1.0-dev.1 +version: 2.1.0-dev.2 homepage: https://github.com/rydmike/flex_seed_scheme repository: https://github.com/rydmike/flex_seed_scheme issue_tracker: https://github.com/rydmike/flex_seed_scheme/issues @@ -29,7 +29,7 @@ topics: environment: sdk: '>=3.0.0 <4.0.0' - flutter: '>=3.22.0-0.3.pre' + flutter: '>=3.22.0' dependencies: # Used by mcu: tonal_palette.dart diff --git a/test/mcu/dynamic_color_test.dart b/test/mcu/dynamic_color_test.dart index a76feaa..38e2160 100644 --- a/test/mcu/dynamic_color_test.dart +++ b/test/mcu/dynamic_color_test.dart @@ -110,7 +110,7 @@ void main() { isDark: true, contrastLevel: -0.5, )), - equals(0xffbac040), + equals(0xff959b1a), ); expect( MaterialDynamicColors.inverseSurface.getArgb(SchemeContent( diff --git a/test/mcu/scheme_correctness_test.dart b/test/mcu/scheme_correctness_test.dart index b53838d..c94852c 100644 --- a/test/mcu/scheme_correctness_test.dart +++ b/test/mcu/scheme_correctness_test.dart @@ -283,16 +283,16 @@ final List<_Constraint> constraints = <_Constraint>[ contrastCurve: ContrastCurve(4.5, 7, 11, 21)), _ContrastConstraint(MaterialDynamicColors.onPrimaryContainer, MaterialDynamicColors.primaryContainer, - contrastCurve: ContrastCurve(4.5, 7, 11, 21)), + contrastCurve: ContrastCurve(3, 4.5, 7, 11)), _ContrastConstraint(MaterialDynamicColors.onSecondaryContainer, MaterialDynamicColors.secondaryContainer, - contrastCurve: ContrastCurve(4.5, 7, 11, 21)), + contrastCurve: ContrastCurve(3, 4.5, 7, 11)), _ContrastConstraint(MaterialDynamicColors.onTertiaryContainer, MaterialDynamicColors.tertiaryContainer, - contrastCurve: ContrastCurve(4.5, 7, 11, 21)), + contrastCurve: ContrastCurve(3, 4.5, 7, 11)), _ContrastConstraint(MaterialDynamicColors.onErrorContainer, MaterialDynamicColors.errorContainer, - contrastCurve: ContrastCurve(4.5, 7, 11, 21)), + contrastCurve: ContrastCurve(3, 4.5, 7, 11)), _ContrastConstraint( MaterialDynamicColors.onPrimaryFixed, MaterialDynamicColors.primaryFixed, contrastCurve: ContrastCurve(4.5, 7, 11, 21)), From 3024d6344fe224f51fa8065b47d5d875e3d19691 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 16 Jun 2024 18:30:08 +0300 Subject: [PATCH 03/37] Update tests for the expressive on-colors spec. --- CHANGELOG.md | 12 ++- .../views/widgets/show_tonal_palette.dart | 3 +- lib/src/flex/flex_seed_scheme.dart | 77 ++++++++++++++----- test/flex_seed_scheme_test.dart | 1 + test/mcu/dynamic_scheme_test.dart | 40 ++++++---- test/mcu/scheme_content_test.dart | 16 ++-- test/mcu/scheme_expressive_test.dart | 8 +- test/mcu/scheme_fidelity_test.dart | 16 ++-- test/mcu/scheme_fruit_salad_test.dart | 12 +-- test/mcu/scheme_monochrome_test.dart | 6 +- test/mcu/scheme_neutral_test.dart | 12 +-- test/mcu/scheme_rainbow_test.dart | 12 +-- test/mcu/scheme_tonal_spot_test.dart | 24 +++--- test/mcu/scheme_vibrant_test.dart | 12 +-- 14 files changed, 154 insertions(+), 97 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc2b660..996d1fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,15 +4,19 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. ## 2.1.0-dev.2 -This is a pre-release of FFS 2.1.0, that will be used to add and test some new preconfigured `FlexTones` with no primary chromacity in neutral palettes. +This is pre-release 2 of FFS 2.1.0. + +* **NEW** + * Added support for `contrastLevel` to `SeedColorScheme.fromSeeds`. This allows you to set the contrast level of the generated color scheme, when using `SeedColorScheme.fromSeeds` with the `variant` property. Using `contrastLevel` has no effect when using `tones`, but with `tones` you can create custom tone for even more flexibility in seed generation with higher or less contrast. + * **CHANGE** -- Updated `MaterialDynamicColors` to use the expressive on-colors spec. This brings the internal Material Color Utilities up to version 0.12.0 on pub. -- Sets Flutter constraint back to `>=3.22.0` since beta and master are no higher than `3.22.0` it can be done. + * Updated `MaterialDynamicColors` to use the expressive on-colors spec. This brings the internal Material Color Utilities up to version 0.12.0 on pub. This changes the contrast curve for light/dark color and dark mode color tone for onPrimaryContainer, onSecondaryContainer, onTertiaryContainer and onErrorContainer. The dark mode tone is changed from 10 to 30, which is the correct spec. Prior to MCU version 0.12.0 the `MaterialDynamicColors` used the wrong spec. Flutter 3.22.x and Flutter master still uses MCU earlier than 0.12.0 and have the wrong onColor tones. This will be corrected when Flutter updates to MCU 0.12.0. + * Sets Flutter constraint back to `>=3.22.0` since beta and master are now higher than `3.22.0` so it can now be done, and master and beta can use this constraint as well. ## 2.1.0-dev.1 -This is a pre-release of FFS 2.1.0, that will be used to add and test some new preconfigured `FlexTones` with no primary chromacity in neutral palettes. +This is a pre-release 1 of FFS 2.1.0. **FIX** * [FIX #13](https://github.com/rydmike/flex_seed_scheme/issues/13). The planned new tones are not yet included in this dev release, but it sets Flutter version constraint to flutter: '>=3.22.0-0.3.pre', so that the package can also be used on **beta** and **stable** channels, that are still on 3.22.0-a.b.pre versions, which is considered smaller than **3.22.0**, used in the stable release of the package. You can use this version of the package if you need to use **beta** or **master** channel. It is apart from the version constraint difference identical to the `2.0.0` release. diff --git a/example/lib/home/views/widgets/show_tonal_palette.dart b/example/lib/home/views/widgets/show_tonal_palette.dart index cc23be5..4c858f2 100644 --- a/example/lib/home/views/widgets/show_tonal_palette.dart +++ b/example/lib/home/views/widgets/show_tonal_palette.dart @@ -30,11 +30,12 @@ class ShowTonalPalette extends StatelessWidget { List neutralTonals = []; List neutralVariantTonals = []; + // TODO(rydmike): Add usage of contrast level to demo app. // Are we using a Flutter SDK scheme? Otherwise use FlexTone. if (controller.usedVariant.isFlutterScheme) { // Get DynamicScheme tones if using Flutter SDK scheme. final DynamicScheme dynamicScheme = SeedColorScheme.buildDynamicScheme( - brightness, controller.primarySeedColor, controller.usedVariant); + brightness, controller.primarySeedColor, controller.usedVariant, 0.0); // Assign the tonals for the schemes to the int lists using tone indexes // from FlexTonalPalette based on used type. diff --git a/lib/src/flex/flex_seed_scheme.dart b/lib/src/flex/flex_seed_scheme.dart index 1803a01..c5faa18 100644 --- a/lib/src/flex/flex_seed_scheme.dart +++ b/lib/src/flex/flex_seed_scheme.dart @@ -427,6 +427,10 @@ extension SeedColorScheme on ColorScheme { /// /// Any seed produced [ColorScheme] color can be overridden by providing it a /// given [Color] value. + /// + /// The propertis [tones] and [variant] are mutually exclusive, only one of + /// them can be used. If both are null, the default from [tones] is used. + static ColorScheme fromSeeds({ /// The overall brightness of this color scheme. Brightness brightness = Brightness.light, @@ -534,8 +538,8 @@ extension SeedColorScheme on ColorScheme { /// way to select a predefined seed generation configuration, instead of /// providing a [FlexTones] configuration. The [variant] API is also used /// to provide access to the DynamicSchemeVariant that are available - /// in Flutter 3.23.0 and later. With FSS you can use them in Flutter 3.22 - /// already. + /// in Flutter 3.22.2 and later. With FSS you can use them in + /// Flutter 3.22.0 already. FlexTones? tones, /// An optional way to select the used algorithm for seeded [ColorScheme] @@ -545,12 +549,11 @@ extension SeedColorScheme on ColorScheme { /// can be used. If both are null, the default from [tones] is used. /// /// The [variant] selections includes all the Flutter SDK defined options - /// that will be available in the future in Flutter Stable after 3.22.x, - /// that are available in master channel now but did not land in Flutter - /// 3.22. Variant options that are identical to the Flutter SDK options + /// are available in the in Flutter Stable 3.22.2 and later. Variant options + /// that are identical to the Flutter SDK options /// have [FlexSchemeVariant.value], [isFlutterScheme] set to true. These - /// enum options will not respect and use any other seed generation keys - /// than the [primaryKey], as they only support using one seed color. + /// enum options will not respect and use any other seed generation key + /// color than the [primaryKey], as they only support using one seed color. /// /// The [FlexSchemeVariant] also includes quick selections for all the /// predefined [FlexTones] configurations. However, with [variant] you can @@ -561,6 +564,21 @@ extension SeedColorScheme on ColorScheme { /// configurations passed in to [tones]. FlexSchemeVariant? variant, + /// The [contrastLevel] parameter indicates the contrast level between color + /// pairs, such as [primary] and [onPrimary]. 0.0 is the default (normal); + /// -1.0 is the lowest; 1.0 is the highest. From Material Design guideline, + /// the medium and high contrast correspond to 0.5 and 1.0 respectively. + /// + /// The contrast level property is only used when generating the + /// [ColorScheme] based on [variant]. When using [tones] the [contrastLevel] + /// is ignored. With [tones] contrast level can be set as desired using + /// custom [FlexTones] configurations. There are two + /// predefined higher contrast level tone mappings available as + /// [FlexTones.highContrast] and [FlexTones.ultraContrast], you can use them + /// as they are or as examples on how to create your own custom high + /// contrast tone mappings. + double contrastLevel = 0.0, + /// Override color for the seed generated [primary] color. /// /// You may want to assign the [primaryKey] to this color in light @@ -740,7 +758,7 @@ extension SeedColorScheme on ColorScheme { if (variant != null && variant.isFlutterScheme) { final DynamicScheme scheme = - buildDynamicScheme(brightness, primaryKey, variant); + buildDynamicScheme(brightness, primaryKey, variant, contrastLevel); return ColorScheme( primary: primary ?? Color(MaterialDynamicColors.primary.getArgb(scheme)), @@ -936,7 +954,12 @@ extension SeedColorScheme on ColorScheme { /// If used with a FlexTones based [FlexSchemeVariant] variant it returns /// tonalSpot, the default Material-3 SDK style. static DynamicScheme buildDynamicScheme( - Brightness brightness, Color seedColor, FlexSchemeVariant variant) { + Brightness brightness, Color seedColor, FlexSchemeVariant variant, + [double contrastLevel = 0.0]) { + assert( + contrastLevel >= -1.0 && contrastLevel <= 1.0, + 'contrastLevel must be between -1.0 and 1.0 inclusive.', + ); final bool isDark = brightness == Brightness.dark; final Hct sourceColor = Hct.fromInt(seedColor.value); return switch (variant) { @@ -954,23 +977,41 @@ extension SeedColorScheme on ColorScheme { FlexSchemeVariant.chroma || FlexSchemeVariant.tonalSpot => SchemeTonalSpot( - sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + sourceColorHct: sourceColor, + isDark: isDark, + contrastLevel: contrastLevel), FlexSchemeVariant.fidelity => SchemeFidelity( - sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + sourceColorHct: sourceColor, + isDark: isDark, + contrastLevel: contrastLevel), FlexSchemeVariant.content => SchemeContent( - sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + sourceColorHct: sourceColor, + isDark: isDark, + contrastLevel: contrastLevel), FlexSchemeVariant.monochrome => SchemeMonochrome( - sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + sourceColorHct: sourceColor, + isDark: isDark, + contrastLevel: contrastLevel), FlexSchemeVariant.neutral => SchemeNeutral( - sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + sourceColorHct: sourceColor, + isDark: isDark, + contrastLevel: contrastLevel), FlexSchemeVariant.vibrant => SchemeVibrant( - sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + sourceColorHct: sourceColor, + isDark: isDark, + contrastLevel: contrastLevel), FlexSchemeVariant.expressive => SchemeExpressive( - sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + sourceColorHct: sourceColor, + isDark: isDark, + contrastLevel: contrastLevel), FlexSchemeVariant.rainbow => SchemeRainbow( - sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + sourceColorHct: sourceColor, + isDark: isDark, + contrastLevel: contrastLevel), FlexSchemeVariant.fruitSalad => SchemeFruitSalad( - sourceColorHct: sourceColor, isDark: isDark, contrastLevel: 0.0), + sourceColorHct: sourceColor, + isDark: isDark, + contrastLevel: contrastLevel), }; } } diff --git a/test/flex_seed_scheme_test.dart b/test/flex_seed_scheme_test.dart index e00bacc..ca02ec3 100644 --- a/test/flex_seed_scheme_test.dart +++ b/test/flex_seed_scheme_test.dart @@ -741,6 +741,7 @@ void main() { expect(scheme, equals(scheme2)); }); + // TODO(rydmike): Add tests for contrast levels? // ColorScheme test with DynamicScheme test( diff --git a/test/mcu/dynamic_scheme_test.dart b/test/mcu/dynamic_scheme_test.dart index 3597a0b..ca531ee 100644 --- a/test/mcu/dynamic_scheme_test.dart +++ b/test/mcu/dynamic_scheme_test.dart @@ -101,8 +101,10 @@ void main() { MaterialDynamicColors.onPrimary.getArgb(dynamicScheme)); expect(colorScheme.primaryContainer.value, MaterialDynamicColors.primaryContainer.getArgb(dynamicScheme)); - expect(colorScheme.onPrimaryContainer.value, - MaterialDynamicColors.onPrimaryContainer.getArgb(dynamicScheme)); + + // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 + // expect(colorScheme.onPrimaryContainer.value, + // MaterialDynamicColors.onPrimaryContainer.getArgb(dynamicScheme)); expect(colorScheme.primaryFixed.value, MaterialDynamicColors.primaryFixed.getArgb(dynamicScheme)); expect(colorScheme.primaryFixedDim.value, @@ -117,8 +119,9 @@ void main() { MaterialDynamicColors.onSecondary.getArgb(dynamicScheme)); expect(colorScheme.secondaryContainer.value, MaterialDynamicColors.secondaryContainer.getArgb(dynamicScheme)); - expect(colorScheme.onSecondaryContainer.value, - MaterialDynamicColors.onSecondaryContainer.getArgb(dynamicScheme)); + // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 + // expect(colorScheme.onSecondaryContainer.value, + // MaterialDynamicColors.onSecondaryContainer.getArgb(dynamicScheme)); expect(colorScheme.secondaryFixed.value, MaterialDynamicColors.secondaryFixed.getArgb(dynamicScheme)); expect(colorScheme.secondaryFixedDim.value, @@ -133,8 +136,9 @@ void main() { MaterialDynamicColors.onTertiary.getArgb(dynamicScheme)); expect(colorScheme.tertiaryContainer.value, MaterialDynamicColors.tertiaryContainer.getArgb(dynamicScheme)); - expect(colorScheme.onTertiaryContainer.value, - MaterialDynamicColors.onTertiaryContainer.getArgb(dynamicScheme)); + // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 + // expect(colorScheme.onTertiaryContainer.value, + // MaterialDynamicColors.onTertiaryContainer.getArgb(dynamicScheme)); expect(colorScheme.tertiaryFixed.value, MaterialDynamicColors.tertiaryFixed.getArgb(dynamicScheme)); expect(colorScheme.tertiaryFixedDim.value, @@ -149,8 +153,9 @@ void main() { MaterialDynamicColors.onError.getArgb(dynamicScheme)); expect(colorScheme.errorContainer.value, MaterialDynamicColors.errorContainer.getArgb(dynamicScheme)); - expect(colorScheme.onErrorContainer.value, - MaterialDynamicColors.onErrorContainer.getArgb(dynamicScheme)); + // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 + // expect(colorScheme.onErrorContainer.value, + // MaterialDynamicColors.onErrorContainer.getArgb(dynamicScheme)); expect(colorScheme.background.value, MaterialDynamicColors.background.getArgb(dynamicScheme)); expect(colorScheme.onBackground.value, @@ -196,8 +201,9 @@ void main() { expect(colorScheme.primary.value, dynamicScheme.primary); expect(colorScheme.onPrimary.value, dynamicScheme.onPrimary); expect(colorScheme.primaryContainer.value, dynamicScheme.primaryContainer); - expect( - colorScheme.onPrimaryContainer.value, dynamicScheme.onPrimaryContainer); + // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 + // expect( + // colorScheme.onPrimaryContainer.value, dynamicScheme.onPrimaryContainer); expect(colorScheme.primaryFixed.value, dynamicScheme.primaryFixed); expect(colorScheme.primaryFixedDim.value, dynamicScheme.primaryFixedDim); expect(colorScheme.onPrimaryFixed.value, dynamicScheme.onPrimaryFixed); @@ -207,8 +213,9 @@ void main() { expect(colorScheme.onSecondary.value, dynamicScheme.onSecondary); expect( colorScheme.secondaryContainer.value, dynamicScheme.secondaryContainer); - expect(colorScheme.onSecondaryContainer.value, - dynamicScheme.onSecondaryContainer); + // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 + // expect(colorScheme.onSecondaryContainer.value, + // dynamicScheme.onSecondaryContainer); expect(colorScheme.secondaryFixed.value, dynamicScheme.secondaryFixed); expect( colorScheme.secondaryFixedDim.value, dynamicScheme.secondaryFixedDim); @@ -219,8 +226,9 @@ void main() { expect(colorScheme.onTertiary.value, dynamicScheme.onTertiary); expect( colorScheme.tertiaryContainer.value, dynamicScheme.tertiaryContainer); - expect(colorScheme.onTertiaryContainer.value, - dynamicScheme.onTertiaryContainer); + // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 + // expect(colorScheme.onTertiaryContainer.value, + // dynamicScheme.onTertiaryContainer); expect(colorScheme.tertiaryFixed.value, dynamicScheme.tertiaryFixed); expect(colorScheme.tertiaryFixedDim.value, dynamicScheme.tertiaryFixedDim); expect(colorScheme.onTertiaryFixed.value, dynamicScheme.onTertiaryFixed); @@ -229,7 +237,9 @@ void main() { expect(colorScheme.error.value, dynamicScheme.error); expect(colorScheme.onError.value, dynamicScheme.onError); expect(colorScheme.errorContainer.value, dynamicScheme.errorContainer); - expect(colorScheme.onErrorContainer.value, dynamicScheme.onErrorContainer); + // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 + // expect(colorScheme.onErrorContainer.value, + // dynamicScheme.onErrorContainer); expect(colorScheme.background.value, dynamicScheme.background); expect(colorScheme.onBackground.value, dynamicScheme.onBackground); expect(colorScheme.surface.value, dynamicScheme.surface); diff --git a/test/mcu/scheme_content_test.dart b/test/mcu/scheme_content_test.dart index a86c2d9..c551652 100644 --- a/test/mcu/scheme_content_test.dart +++ b/test/mcu/scheme_content_test.dart @@ -146,7 +146,7 @@ void main() { isDark: false, contrastLevel: -1); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff333dff)); + isColor(0xff5E68FF)); }); test('lightTheme_standardContrast_onPrimaryContainer', () { @@ -155,7 +155,7 @@ void main() { isDark: false, contrastLevel: 0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xffE8E7FF)); + isColor(0xffB3B7FF)); }); test('lightTheme_maxContrast_onPrimaryContainer', () { @@ -248,7 +248,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff949bff)); + isColor(0xff6B75FF)); }); test('darkTheme_standardContrast_onPrimaryContainer', () { @@ -257,7 +257,7 @@ void main() { isDark: true, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xffE8E7FF)); + isColor(0xffB3B7FF)); }); test('darkTheme_maxContrast_onPrimaryContainer', () { @@ -266,7 +266,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff00003D)); }); test('darkTheme_minContrast_onTertiaryContainer', () { @@ -275,7 +275,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xffe577ff)); + isColor(0xffC254DE)); }); test('darkTheme_standardContrast_onTertiaryContainer', () { @@ -284,7 +284,7 @@ void main() { isDark: true, contrastLevel: 0.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xffFFDFFF)); + isColor(0xffF09FFF)); }); test('darkTheme_maxContrast_onTertiaryContainer', () { @@ -293,7 +293,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff1A0022)); }); test('darkTheme_minContrast_surface', () { diff --git a/test/mcu/scheme_expressive_test.dart b/test/mcu/scheme_expressive_test.dart index d38bd23..0db3397 100644 --- a/test/mcu/scheme_expressive_test.dart +++ b/test/mcu/scheme_expressive_test.dart @@ -92,7 +92,7 @@ void main() { isDark: false, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff146c48)); + isColor(0xff388862)); }); test('lightTheme_standardContrast_onPrimaryContainer', () { @@ -101,7 +101,7 @@ void main() { isDark: false, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff002112)); + isColor(0xff005234)); }); test('lightTheme_maxContrast_onPrimaryContainer', () { @@ -195,7 +195,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff64b48a)); + isColor(0xff43936C)); }); test('darkTheme_standardContrast_onPrimaryContainer', () { @@ -213,7 +213,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff000E06)); }); test('darkTheme_minContrast_surface', () { diff --git a/test/mcu/scheme_fidelity_test.dart b/test/mcu/scheme_fidelity_test.dart index 8bd18a4..d3d8df0 100644 --- a/test/mcu/scheme_fidelity_test.dart +++ b/test/mcu/scheme_fidelity_test.dart @@ -147,7 +147,7 @@ void main() { isDark: false, contrastLevel: -1); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff333dff)); + isColor(0xff5E68FF)); }); test('lightTheme_standardContrast_onPrimaryContainer', () { @@ -156,7 +156,7 @@ void main() { isDark: false, contrastLevel: 0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xffE8E7FF)); + isColor(0xffB3B7FF)); }); test('lightTheme_maxContrast_onPrimaryContainer', () { @@ -249,7 +249,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff949bff)); + isColor(0xff6B75FF)); }); test('darkTheme_standardContrast_onPrimaryContainer', () { @@ -258,7 +258,7 @@ void main() { isDark: true, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xffE8E7FF)); + isColor(0xffB3B7FF)); }); test('darkTheme_maxContrast_onPrimaryContainer', () { @@ -267,7 +267,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff00003D)); }); test('darkTheme_minContrast_onTertiaryContainer', () { @@ -276,7 +276,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xffff7d6b)); + isColor(0xffEF4635)); }); test('darkTheme_standardContrast_onTertiaryContainer', () { @@ -285,7 +285,7 @@ void main() { isDark: true, contrastLevel: 0.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xffFFE2DE)); + isColor(0xffFFA598)); }); test('darkTheme_maxContrast_onTertiaryContainer', () { @@ -294,7 +294,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff220000)); }); test('darkTheme_minContrast_surface', () { diff --git a/test/mcu/scheme_fruit_salad_test.dart b/test/mcu/scheme_fruit_salad_test.dart index 983df69..506c317 100644 --- a/test/mcu/scheme_fruit_salad_test.dart +++ b/test/mcu/scheme_fruit_salad_test.dart @@ -119,7 +119,7 @@ void main() { isDark: false, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff006688)); + isColor(0xff0083AE)); }); test('lightTheme_standardContrast_onPrimaryContainer', () { @@ -128,7 +128,7 @@ void main() { isDark: false, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff001e2b)); + isColor(0xff004D67)); }); test('lightTheme_maxContrast_onPrimaryContainer', () { @@ -239,7 +239,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff3fafe0)); + isColor(0xff008EBC)); }); test('darkTheme_standardContrast_onPrimaryContainer', () { @@ -257,7 +257,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff000D15)); }); test('darkTheme_minContrast_onTertiaryContainer', () { @@ -266,7 +266,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xff9b9fdd)); + isColor(0xff7B7FBB)); }); test('darkTheme_standardContrast_onTertiaryContainer', () { @@ -284,7 +284,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff00003C)); }); test('darkTheme_minContrast_surface', () { diff --git a/test/mcu/scheme_monochrome_test.dart b/test/mcu/scheme_monochrome_test.dart index 7b546c1..0164544 100644 --- a/test/mcu/scheme_monochrome_test.dart +++ b/test/mcu/scheme_monochrome_test.dart @@ -92,7 +92,7 @@ void main() { isDark: false, contrastLevel: -1); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff5e5e5e)); + isColor(0xff7A7A7A)); }); test('lightTheme_standardContrast_onPrimaryContainer', () { @@ -194,7 +194,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xffa4a4a4)); + isColor(0xff848484)); }); test('darkTheme_standardContrast_onPrimaryContainer', () { @@ -221,7 +221,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xffa4a4a4)); + isColor(0xff848484)); }); test('darkTheme_standardContrast_onTertiaryContainer', () { diff --git a/test/mcu/scheme_neutral_test.dart b/test/mcu/scheme_neutral_test.dart index 803bebb..fba9d2d 100644 --- a/test/mcu/scheme_neutral_test.dart +++ b/test/mcu/scheme_neutral_test.dart @@ -92,7 +92,7 @@ void main() { isDark: false, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff5d5d6c)); + isColor(0xff797888)); }); test('lightTheme_standardContrast_onPrimaryContainer', () { @@ -101,7 +101,7 @@ void main() { isDark: false, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff1a1b27)); + isColor(0xff454654)); }); test('lightTheme_maxContrast_onPrimaryContainer', () { @@ -194,7 +194,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xffa3a2b3)); + isColor(0xff838393)); }); test('darkTheme_standardContrast_onPrimaryContainer', () { @@ -212,7 +212,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff090A16)); }); test('darkTheme_minContrast_onTertiaryContainer', () { @@ -221,7 +221,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xffa2a2b9)); + isColor(0xff828299)); }); test('darkTheme_standardContrast_onTertiaryContainer', () { @@ -239,7 +239,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff080A1B)); }); test('darkTheme_minContrast_surface', () { diff --git a/test/mcu/scheme_rainbow_test.dart b/test/mcu/scheme_rainbow_test.dart index ab374b4..00f3f32 100644 --- a/test/mcu/scheme_rainbow_test.dart +++ b/test/mcu/scheme_rainbow_test.dart @@ -119,7 +119,7 @@ void main() { isDark: false, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff5056a9)); + isColor(0xff6C72C7)); }); test('lightTheme_standardContrast_onPrimaryContainer', () { @@ -128,7 +128,7 @@ void main() { isDark: false, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff050865)); + isColor(0xff383E8F)); }); test('lightTheme_maxContrast_onPrimaryContainer', () { @@ -239,7 +239,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff969cf5)); + isColor(0xff767CD2)); }); test('darkTheme_standardContrast_onPrimaryContainer', () { @@ -257,7 +257,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff00003D)); }); test('darkTheme_minContrast_onTertiaryContainer', () { @@ -266,7 +266,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xffc397b2)); + isColor(0xffA17891)); }); test('darkTheme_standardContrast_onTertiaryContainer', () { @@ -284,7 +284,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff1B0315)); }); test('darkTheme_minContrast_surface', () { diff --git a/test/mcu/scheme_tonal_spot_test.dart b/test/mcu/scheme_tonal_spot_test.dart index 965db20..eea2a34 100644 --- a/test/mcu/scheme_tonal_spot_test.dart +++ b/test/mcu/scheme_tonal_spot_test.dart @@ -93,7 +93,7 @@ void main() { isDark: false, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff5d5d6c)); + isColor(0xff797888)); }); test('lightTheme_standardContrast_onPrimaryContainer', () { @@ -102,7 +102,7 @@ void main() { isDark: false, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff1a1b27)); + isColor(0xff454654)); }); test('lightTheme_maxContrast_onPrimaryContainer', () { @@ -195,7 +195,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xffa3a2b3)); + isColor(0xff838393)); }); test('darkTheme_standardContrast_onPrimaryContainer', () { @@ -213,7 +213,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff090A16)); }); test('darkTheme_minContrast_onTertiaryContainer', () { @@ -222,7 +222,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xffa2a2b9)); + isColor(0xff828299)); }); test('darkTheme_standardContrast_onTertiaryContainer', () { @@ -240,7 +240,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff080A1B)); }); test('darkTheme_minContrast_surface', () { @@ -325,7 +325,7 @@ void main() { isDark: false, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff555992)); + isColor(0xff7175B0)); }); test('lightTheme_standardContrast_onPrimaryContainer', () { @@ -334,7 +334,7 @@ void main() { isDark: false, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff11144B)); + isColor(0xff3E4278)); }); test('lightTheme_maxContrast_onPrimaryContainer', () { @@ -532,7 +532,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff9B9FDD)); + isColor(0xff7B7FBB)); }); test('darkTheme_standardContrast_onPrimaryContainer', () { @@ -550,7 +550,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff00003C)); }); test('darkTheme_minContrast_onSecondary', () { @@ -586,7 +586,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xffC397B2)); + isColor(0xffA17891)); }); test('darkTheme_standardContrast_onTertiaryContainer', () { @@ -604,7 +604,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff1B0315)); }); test('darkTheme_minContrast_onTertiary', () { diff --git a/test/mcu/scheme_vibrant_test.dart b/test/mcu/scheme_vibrant_test.dart index c55c69e..ee3222f 100644 --- a/test/mcu/scheme_vibrant_test.dart +++ b/test/mcu/scheme_vibrant_test.dart @@ -92,7 +92,7 @@ void main() { isDark: false, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff333dff)); + isColor(0xff5E68FF)); }); test('lightTheme_standardContrast_onPrimaryContainer', () { @@ -101,7 +101,7 @@ void main() { isDark: false, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff00006e)); + isColor(0xff0000EF)); }); test('lightTheme_maxContrast_onPrimaryContainer', () { @@ -194,7 +194,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff949bff)); + isColor(0xff6B75FF)); }); test('darkTheme_standardContrast_onPrimaryContainer', () { @@ -212,7 +212,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff00003D)); }); test('darkTheme_minContrast_onTertiaryContainer', () { @@ -221,7 +221,7 @@ void main() { isDark: true, contrastLevel: -1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xffb798cd)); + isColor(0xff9679AB)); }); test('darkTheme_standardContrast_onTertiaryContainer', () { @@ -239,7 +239,7 @@ void main() { isDark: true, contrastLevel: 1.0); expect(MaterialDynamicColors.onTertiaryContainer.getArgb(scheme), - isColor(0xff000000)); + isColor(0xff16002A)); }); test('darkTheme_minContrast_surface', () { From eee8fefac4dbfc1aeea11dcaa2d343815dbc7620 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 16 Jun 2024 18:31:05 +0300 Subject: [PATCH 04/37] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 996d1fc..ff4889c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ This is pre-release 2 of FFS 2.1.0. This is a pre-release 1 of FFS 2.1.0. **FIX** -* [FIX #13](https://github.com/rydmike/flex_seed_scheme/issues/13). The planned new tones are not yet included in this dev release, but it sets Flutter version constraint to flutter: '>=3.22.0-0.3.pre', so that the package can also be used on **beta** and **stable** channels, that are still on 3.22.0-a.b.pre versions, which is considered smaller than **3.22.0**, used in the stable release of the package. You can use this version of the package if you need to use **beta** or **master** channel. It is apart from the version constraint difference identical to the `2.0.0` release. +* [FIX #13](https://github.com/rydmike/flex_seed_scheme/issues/13). Sets Flutter version constraint to flutter: '>=3.22.0-0.3.pre', so that the package can also be used on **beta** and **stable** channels, that are still on 3.22.0-a.b.pre versions, which is considered smaller than **3.22.0**, used in the stable release of the package. You can use this version of the package if you need to use **beta** or **master** channel. It is apart from the version constraint difference identical to the `2.0.0` release. ## 2.0.0 From e10f6d3fc53f28087f5b2b70094450ccc73bad6f Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 16 Jun 2024 19:07:03 +0300 Subject: [PATCH 05/37] Add: monochromeSurfaces for FlexTones --- CHANGELOG.md | 5 ++-- example/lib/home/views/pages/home_page.dart | 10 ++++++++ .../views/widgets/show_tonal_palette.dart | 5 ++-- .../theme/controllers/theme_controller.dart | 9 ++++++++ example/lib/theme/model/app_theme.dart | 2 ++ lib/src/flex/flex_tones.dart | 23 +++++++++++++++++++ 6 files changed, 50 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff4889c..8b76dd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ This is pre-release 2 of FFS 2.1.0. * **NEW** * Added support for `contrastLevel` to `SeedColorScheme.fromSeeds`. This allows you to set the contrast level of the generated color scheme, when using `SeedColorScheme.fromSeeds` with the `variant` property. Using `contrastLevel` has no effect when using `tones`, but with `tones` you can create custom tone for even more flexibility in seed generation with higher or less contrast. - + * The `FlexTones` got a new built-in modifier `monochromeSurfaces()`. It can be applied to any predefined or custom `FlexTones` to make the surface colors monochrome and use pure greyscale for its tonal palettes, with no color tint from their key color or primary key seed color. + * **CHANGE** * Updated `MaterialDynamicColors` to use the expressive on-colors spec. This brings the internal Material Color Utilities up to version 0.12.0 on pub. This changes the contrast curve for light/dark color and dark mode color tone for onPrimaryContainer, onSecondaryContainer, onTertiaryContainer and onErrorContainer. The dark mode tone is changed from 10 to 30, which is the correct spec. Prior to MCU version 0.12.0 the `MaterialDynamicColors` used the wrong spec. Flutter 3.22.x and Flutter master still uses MCU earlier than 0.12.0 and have the wrong onColor tones. This will be corrected when Flutter updates to MCU 0.12.0. @@ -16,7 +17,7 @@ This is pre-release 2 of FFS 2.1.0. ## 2.1.0-dev.1 -This is a pre-release 1 of FFS 2.1.0. +This is pre-release 1 of FFS 2.1.0. **FIX** * [FIX #13](https://github.com/rydmike/flex_seed_scheme/issues/13). Sets Flutter version constraint to flutter: '>=3.22.0-0.3.pre', so that the package can also be used on **beta** and **stable** channels, that are still on 3.22.0-a.b.pre versions, which is considered smaller than **3.22.0**, used in the stable release of the package. You can use this version of the package if you need to use **beta** or **master** channel. It is apart from the version constraint difference identical to the `2.0.0` release. diff --git a/example/lib/home/views/pages/home_page.dart b/example/lib/home/views/pages/home_page.dart index 22485cf..2bc0782 100644 --- a/example/lib/home/views/pages/home_page.dart +++ b/example/lib/home/views/pages/home_page.dart @@ -117,6 +117,16 @@ class HomePage extends StatelessWidget { : controller.setUseErrorKey, ), const Divider(), + SwitchListTile( + dense: true, + title: const Text( + 'Use monochrome surface colors, pure grey scale, no tint'), + value: controller.useMonoSurfaces && + !controller.usedVariant.isFlutterScheme, + onChanged: controller.usedVariant.isFlutterScheme + ? null + : controller.setUseMonoSurfaces, + ), SwitchListTile( dense: true, title: const Text( diff --git a/example/lib/home/views/widgets/show_tonal_palette.dart b/example/lib/home/views/widgets/show_tonal_palette.dart index 4c858f2..d045c7e 100644 --- a/example/lib/home/views/widgets/show_tonal_palette.dart +++ b/example/lib/home/views/widgets/show_tonal_palette.dart @@ -81,8 +81,9 @@ class ShowTonalPalette extends StatelessWidget { tertiaryChroma: tones.tertiaryChroma, tertiaryMinChroma: tones.tertiaryMinChroma, tertiaryHueRotation: tones.tertiaryHueRotation, - neutralChroma: tones.neutralChroma, - neutralVariantChroma: tones.neutralVariantChroma, + neutralChroma: controller.useMonoSurfaces ? 0 : tones.neutralChroma, + neutralVariantChroma: + controller.useMonoSurfaces ? 0 : tones.neutralVariantChroma, paletteType: paletteType, ); // Assign the tonals for the schemes to the int lists. diff --git a/example/lib/theme/controllers/theme_controller.dart b/example/lib/theme/controllers/theme_controller.dart index a34fd2d..eb45b20 100644 --- a/example/lib/theme/controllers/theme_controller.dart +++ b/example/lib/theme/controllers/theme_controller.dart @@ -62,6 +62,15 @@ class ThemeController with ChangeNotifier { if (notify) notifyListeners(); } + bool _monoSurfaces = false; + bool get useMonoSurfaces => _monoSurfaces; + void setUseMonoSurfaces(bool? value, [bool notify = true]) { + if (value == null) return; + if (value == _monoSurfaces) return; + _monoSurfaces = value; + if (notify) notifyListeners(); + } + bool _keepMainOnColorsBW = false; bool get keepMainOnColorsBW => _keepMainOnColorsBW; void setKeepMainOnColorsBW(bool? value, [bool notify = true]) { diff --git a/example/lib/theme/model/app_theme.dart b/example/lib/theme/model/app_theme.dart index 6e53597..3c7b78e 100644 --- a/example/lib/theme/model/app_theme.dart +++ b/example/lib/theme/model/app_theme.dart @@ -39,6 +39,7 @@ class AppTheme { ? null : controller.usedVariant .tones(Brightness.light) + .monochromeSurfaces(controller.useMonoSurfaces) .onMainsUseBW(controller.keepMainOnColorsBW) .onSurfacesUseBW(controller.keepSurfaceOnColorsBW) .surfacesUseBW(controller.keepLightSurfaceColorsWhite), @@ -76,6 +77,7 @@ class AppTheme { ? null : controller.usedVariant .tones(Brightness.dark) + .monochromeSurfaces(controller.useMonoSurfaces) .onMainsUseBW(controller.keepMainOnColorsBW) .onSurfacesUseBW(controller.keepSurfaceOnColorsBW) .surfacesUseBW(controller.keepDarkSurfaceColorsBlack), diff --git a/lib/src/flex/flex_tones.dart b/lib/src/flex/flex_tones.dart index 3b49d47..50a4269 100644 --- a/lib/src/flex/flex_tones.dart +++ b/lib/src/flex/flex_tones.dart @@ -981,6 +981,29 @@ class FlexTones with Diagnosticable { ); } + // TODO(rydmike): Add tests for monochromeSurfaces. + + /// Returns a new [FlexTones] instance where the neutral and neutral variant + /// chrome is set to 0. This will result in that regardless of the seed color + /// the neutral and neutral variant tonal colors will be a pure grey scale + /// without any chromacity in them. Resulting in surface colors with no color + /// tint in them. + /// + /// The [useMonochrome] flag is true by default, making the function + /// effective. If set to false, the function is a no op and just returns the + /// [FlexTones] object unmodified. This is typically used to control applying + /// the tint removal via a controller. + FlexTones monochromeSurfaces([bool useMonochrome = true]) { + // ignore: avoid_returning_this + if (!useMonochrome) return this; + return copyWith( + neutralChroma: 0, + neutralMinChroma: 0, + neutralVariantChroma: 0, + neutralVariantMinChroma: 0, + ); + } + /// Tone used for [ColorScheme.primary] from primary [FlexTonalPalette]. final int primaryTone; From 474a2413c8b8a885c58f0d06165dbf35d506acfe Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 16 Jun 2024 19:13:48 +0300 Subject: [PATCH 06/37] FIX: Error key color was not used in the example in dark mode --- example/lib/theme/model/app_theme.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/example/lib/theme/model/app_theme.dart b/example/lib/theme/model/app_theme.dart index 3c7b78e..9289ab3 100644 --- a/example/lib/theme/model/app_theme.dart +++ b/example/lib/theme/model/app_theme.dart @@ -70,6 +70,7 @@ class AppTheme { controller.useSecondaryKey ? controller.secondarySeedColor : null, tertiaryKey: controller.useTertiaryKey ? controller.tertiarySeedColor : null, + errorKey: controller.useErrorKey ? controller.errorSeedColor : null, variant: controller.usedVariant.isFlutterScheme ? controller.usedVariant : null, From df5c29a9e163ac017f185b2ff5b23704d021be23 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 16 Jun 2024 19:15:07 +0300 Subject: [PATCH 07/37] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b76dd5..73517d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,11 @@ This is pre-release 2 of FFS 2.1.0. * Updated `MaterialDynamicColors` to use the expressive on-colors spec. This brings the internal Material Color Utilities up to version 0.12.0 on pub. This changes the contrast curve for light/dark color and dark mode color tone for onPrimaryContainer, onSecondaryContainer, onTertiaryContainer and onErrorContainer. The dark mode tone is changed from 10 to 30, which is the correct spec. Prior to MCU version 0.12.0 the `MaterialDynamicColors` used the wrong spec. Flutter 3.22.x and Flutter master still uses MCU earlier than 0.12.0 and have the wrong onColor tones. This will be corrected when Flutter updates to MCU 0.12.0. * Sets Flutter constraint back to `>=3.22.0` since beta and master are now higher than `3.22.0` so it can now be done, and master and beta can use this constraint as well. + +* **FIX** + * EXAMPLE: The key color to seed error palette was not used in the example in dark mode. + + ## 2.1.0-dev.1 This is pre-release 1 of FFS 2.1.0. From b764caabb207a44be68caf712e3bd0a4ab8245c4 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 16 Jun 2024 21:58:30 +0300 Subject: [PATCH 08/37] Example: Add contrast level control. Reorg UI. --- example/lib/core/constants/app_data.dart | 4 +- .../views/universal/list_tile_slider.dart | 133 ++++++++++++++++++ example/lib/home/views/pages/home_page.dart | 42 ++++-- .../theme/controllers/theme_controller.dart | 9 ++ example/lib/theme/model/app_theme.dart | 2 + 5 files changed, 174 insertions(+), 16 deletions(-) create mode 100644 example/lib/core/views/universal/list_tile_slider.dart diff --git a/example/lib/core/constants/app_data.dart b/example/lib/core/constants/app_data.dart index e965e7d..388e05b 100644 --- a/example/lib/core/constants/app_data.dart +++ b/example/lib/core/constants/app_data.dart @@ -26,14 +26,14 @@ class AppData { // Version of the WEB build, usually same as package, but it also has a // build numbers. static const String versionMajor = '2'; - static const String versionMinor = '0'; + static const String versionMinor = '1'; static const String versionPatch = '0'; static const String versionBuild = '01'; static const String version = '$versionMajor.$versionMinor.$versionPatch ' 'Build-$versionBuild'; static const String packageVersion = '$versionMajor.$versionMinor.$versionPatch'; - static const String flutterVersion = '3.22.0 (canvaskit)'; + static const String flutterVersion = '3.22.2 (canvaskit)'; static const String copyright = '© 2022-2024'; static const String author = 'Mike Rydstrom'; static const String license = 'BSD 3-Clause License'; diff --git a/example/lib/core/views/universal/list_tile_slider.dart b/example/lib/core/views/universal/list_tile_slider.dart new file mode 100644 index 0000000..0bedf3f --- /dev/null +++ b/example/lib/core/views/universal/list_tile_slider.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; + +/// A [ListTile] with a [Slider] either in [title] or in [subtitle] +/// position that can never return null. +/// +/// The Slider value is displayed as a trailing widget. +/// +/// This is a Flutter "Universal" Widget that only depends on the SDK and +/// can be dropped into any application. +class ListTileSlider extends StatelessWidget { + const ListTileSlider({ + super.key, + this.title, + this.subtitle, + this.sliderLabel, + this.dense = false, + this.min = 0.0, + this.max = 100.0, + this.divisions, + this.valueDecimals = 0, + this.enabled = true, + // + required this.value, + this.onChanged, + }); + + /// The primary content of the Slider list tile. + /// + /// Typically a [Text] widget. + /// + /// This should not wrap. To enforce the single line limit, use + /// [Text.maxLines]. + /// + /// If both [title] and [subtitle] are null, the [Slider] + /// will be placed in the [ListTile]'s title position. + final Widget? title; + + /// Additional content displayed below the title. + /// + /// Typically a [Text] widget. + /// + /// If both [title] is not null and [subtitle] are is null, the [Slider] + /// will be placed in the [ListTile]'s subtitle position. + /// + /// If both [title] and [subtitle] are not null, the [Slider] will + /// be placed in the [ListTile]'s subtitle position, in a [Column] below + /// the [subtitle]. + final Widget? subtitle; + + /// Use dense style for the ListTile. + final bool dense; + + /// A label placed above the slider value shown as trailing widget. + final String? sliderLabel; + + /// The minimum value the slider can have. + final double min; + + /// The maximum value the slider can have. + final double max; + + /// The number of discrete divisions. + /// + /// If null, set to ([max]- [min]).toInt(). + final int? divisions; + + /// The number of decimals to show for the value. + /// + /// Defaults to 0. + final int valueDecimals; + + /// Whether this list tile and slider operation is interactive. + final bool enabled; + + /// The current value of the slider. + /// + /// It is shown as the value of the slider in the ListTile trailing property. + final double value; + + /// Called when the user starts selecting a new value for the slider. + final ValueChanged? onChanged; + + @override + Widget build(BuildContext context) { + // For now using hard coded style selection for sliderLabel. + final TextStyle style = Theme.of(context).textTheme.bodySmall!; + + final Slider slider = Slider( + min: min, + max: max, + divisions: divisions ?? (max - min).toInt(), + label: value.toStringAsFixed(valueDecimals), + value: value, + onChanged: enabled + ? (double value) { + onChanged?.call(value); + } + // Sett to null to also disable the Slider in the ListTile. + : null, + ); + + return ListTile( + enabled: enabled, + dense: dense, + title: (title == null && subtitle == null) ? slider : title, + subtitle: (title != null && subtitle == null) + ? slider + : (subtitle != null) + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + subtitle!, + slider, + ], + ) + : null, + trailing: Padding( + // For now using hard coded padding for sliderLabel. + padding: const EdgeInsets.only(right: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (sliderLabel != null) Text(sliderLabel!, style: style), + Text( + value.toStringAsFixed(valueDecimals), + style: style.copyWith(fontWeight: FontWeight.bold), + ), + ], + ), + ), + ); + } +} diff --git a/example/lib/home/views/pages/home_page.dart b/example/lib/home/views/pages/home_page.dart index 2bc0782..4b05065 100644 --- a/example/lib/home/views/pages/home_page.dart +++ b/example/lib/home/views/pages/home_page.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../../../about/views/about.dart'; import '../../../core/constants/app_data.dart'; import '../../../core/views/universal/color_scheme_view.dart'; +import '../../../core/views/universal/list_tile_slider.dart'; import '../../../core/views/universal/showcase_material.dart'; import '../../../theme/controllers/theme_controller.dart'; import '../widgets/flex_tones_popup_menu.dart'; @@ -60,6 +61,28 @@ class HomePage extends StatelessWidget { contentPadding: const EdgeInsetsDirectional.only(start: 16, end: 24), ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: ColorSchemeView(), + ), + ListTileSlider( + dense: true, + title: const Text('Contrast level. Only for MCU dynamic schemes'), + enabled: controller.usedVariant.isFlutterScheme, + min: -1, + max: 1, + divisions: 8, + valueDecimals: 2, + value: controller.usedVariant.isFlutterScheme + ? controller.contrastLevel + : 0, + onChanged: controller.setContrastLevel, + sliderLabel: 'Contrast', + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ShowTonalPalette(controller: controller), + ), ListTile( title: Text('${controller.usedVariant.variantName}' ' scheme variant configuration info:'), @@ -73,6 +96,7 @@ class HomePage extends StatelessWidget { dense: true, title: Text('Primary key color is always used to seed the ' 'ColorScheme. Tap to change colors.')), + const Divider(), if (controller.usedVariant.isFlutterScheme) const ListTile( dense: true, @@ -86,7 +110,6 @@ class HomePage extends StatelessWidget { title: Text('Additional seed generation options are available ' 'when using FlexTones based scheme variants.'), ), - const Divider(), SwitchListTile( dense: true, title: @@ -150,8 +173,8 @@ class HomePage extends StatelessWidget { if (isLight) SwitchListTile( dense: true, - title: const Text('Keep surface and background white in seeded ' - 'light ColorScheme'), + title: const Text('Keep surface and deprecated background color, ' + 'white in seeded light ColorScheme'), value: controller.keepLightSurfaceColorsWhite && !controller.usedVariant.isFlutterScheme, onChanged: controller.usedVariant.isFlutterScheme @@ -161,23 +184,14 @@ class HomePage extends StatelessWidget { else SwitchListTile( dense: true, - title: const Text('Keep surface and background black in seeded ' - 'dark ColorScheme'), + title: const Text('Keep surface and deprecated background color, ' + 'black in seeded dark ColorScheme'), value: controller.keepDarkSurfaceColorsBlack && !controller.usedVariant.isFlutterScheme, onChanged: controller.usedVariant.isFlutterScheme ? null : controller.setKeepDarkSurfaceColorsBlack, ), - const Divider(), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: ShowTonalPalette(controller: controller), - ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: ColorSchemeView(), - ), const SizedBox(height: 16), const Divider(), const ListTile( diff --git a/example/lib/theme/controllers/theme_controller.dart b/example/lib/theme/controllers/theme_controller.dart index eb45b20..b388423 100644 --- a/example/lib/theme/controllers/theme_controller.dart +++ b/example/lib/theme/controllers/theme_controller.dart @@ -116,6 +116,15 @@ class ThemeController with ChangeNotifier { if (notify) notifyListeners(); } + double _contrastLevel = 0.0; + double get contrastLevel => _contrastLevel; + void setContrastLevel(double? value, [bool notify = true]) { + if (value == null) return; + if (value == _contrastLevel) return; + _contrastLevel = value; + if (notify) notifyListeners(); + } + Color _primarySeedColor = AppColor.primary; Color get primarySeedColor => _primarySeedColor; void setPrimarySeedColor(Color? value, [bool notify = true]) { diff --git a/example/lib/theme/model/app_theme.dart b/example/lib/theme/model/app_theme.dart index 9289ab3..0e922cb 100644 --- a/example/lib/theme/model/app_theme.dart +++ b/example/lib/theme/model/app_theme.dart @@ -29,6 +29,7 @@ class AppTheme { variant: controller.usedVariant.isFlutterScheme ? controller.usedVariant : null, + contrastLevel: controller.contrastLevel, // Tone chroma config and tone mapping is optional. If you do not add it // you get a config matching Flutter's Material 3 ColorScheme.fromSeed. // @@ -74,6 +75,7 @@ class AppTheme { variant: controller.usedVariant.isFlutterScheme ? controller.usedVariant : null, + contrastLevel: controller.contrastLevel, tones: controller.usedVariant.isFlutterScheme ? null : controller.usedVariant From 1a7f85fe67d6ef9357c53cefd381776094308666 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 16 Jun 2024 21:58:46 +0300 Subject: [PATCH 09/37] Update README.md --- README.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 66 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a4176ac..93d33b9 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Use this package like `ColorScheme.fromSeed` with the following additional capab * Use separate seed key colors to generate seed-based tonal palettes for primary and optionally secondary, tertiary as well as error, neutral and neutral variant colors in `ColorScheme`. * Change the chroma limits and values used in the Material-3 default strategy for tonal palette generation in the new Google HCT (Hue-Chroma-Tone) color space. -* Get access to new Material-3 design `ColorScheme` variants like `fidelity` and `monochrome` that do not yet exist in Flutter SDK v3.22, but will arrive in later versions. +* Use new Material-3 design `ColorScheme` variants like `fidelity` and `monochrome` that arrived in Flutter SDK v3.22.2. With FSS you can use them already with Flutter 3.22.0. * Change which tones in the generated core tonal palettes are used by which `ColorScheme` color. * Use `FlexPaletteType.extended`, that gives access to all the new tones that are used in revised Material-3 design for surfaces and fixed main color. The new tones are used and supported by the standard `ColorScheme` in Flutter 3.22 and later. It is recommended to use `FlexPaletteType.extended` if creating new custom `FlexTones`. In the package version 2.0.0 and later, `FlexPaletteType.extended` is the default palette type. The older palette type `FlexPaletteType.common` is available for backwards compatibility, but should be avoided in Flutter 3.22 and later. @@ -78,6 +78,9 @@ to experiment. tones: FlexTones.vivid(Brightness.dark), ); ``` + +### Separate Key Colors to Seed Each Main Color Group + You can use separate key colors as seeds to generate the primary, secondary and tertiary tonal palettes. If no key color for `secondaryKey` and/or `tertiaryKey` are provided, they use `primaryKey` color as their seed color value. @@ -123,13 +126,15 @@ It is this mapping that `FlexTones` gives you control over. ### Variants -If you want to access the `ColorScheme` variants that Flutter SDK will provide in a future version, most likely in the next stable version after Flutter 3.22, then use `SeedColorScheme.fromSeeds` with the `variant` property of enum type `FlexSchemeVariant`. +If you want to use the `ColorScheme` variants that Flutter SDK provides via `ColorScheme.fromSeed` then use `SeedColorScheme.fromSeeds` with the `variant` property of enum type `FlexSchemeVariant`. + +If the variant `FlexSchemeVariant` style is one that is also provided by Flutter SDK, it has `FlexSchemeVariant` property `isFlutterScheme` set to true. In that case, all other key colors except `primaryKey` are ignored. This is because the Flutter SDK `ColorScheme` variant system only supports one seed color. -If the variant `FlexSchemeVariant` style is one that is also provided by Flutter SDK, it has `FlexSchemeVariant` property `isFlutterScheme` set to true. In that case, all other key colors except `primaryKey` are ignored. This is because the Flutter SDK `ColorScheme` variant system only supports one seed color. The `FlexSchemeVariant` also include the predefined `FlexTone` based variants. You can use the `variant` option as a way to select `ColorScheme` seed generation variant that is based on both new (coming) Flutter SDK options and the FlexSeedScheme predefined FlexTones `tones` based seed generation options. +The `FlexSchemeVariant` also include the predefined `FlexTone` based variants. You can use the `variant` option as a way to select `ColorScheme` seed generation variant that is based on both the in Flutter 3.22.2 new Flutter SDK options and the FlexSeedScheme predefined FlexTones `tones` based seed generation options. ```dart // Make a light ColorScheme from a seeds using variant style fidelity. - // This is a variant that is not yet available in Flutter SDK, but will be after 3.22.x stable. + // This is a variant that is available in Flutter SDK stable 3.22.2 or later. final ColorScheme schemeLight = SeedColorScheme.fromSeeds( brightness: Brightness.light, primaryKey: primarySeedColor, @@ -339,6 +344,31 @@ For other platforms, you need to use a user setting and toggle themes based on i ), ``` +### Contrast Level + +When using a `variant` that is based on Flutter SDK DynamicScheme, it has `FlexSchemeVariant` property `isFlutterScheme` set to true, you can also provide a `contrastLevel` for the seed generation. + +The `contrastLevel` parameter indicates the contrast level between color pairs, such as `primary` and `onPrimary`. The value 0.0 is the default normal contrast; -1.0 is the lowest; 1.0 is the highest. From **Material Design guideline**, the medium and high contrast, correspond to 0.5 and 1.0 respectively. The `contrastLevel` is used to adjust the contrast between the primary and onPrimary colors. The `contraslLevel` must be from -1.0 to 1.0. + +```dart + // Make a light high contrast ColorScheme from a seeds using variant style fidelity. + final ColorScheme schemeLight = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + variant: FlexSchemeVariant.fidelity, + contrastLevel: 1.0, + ); + // Make a dark high contrast ColorScheme from a seeds using variant style fidelity. + final ColorScheme schemeDark = SeedColorScheme.fromSeeds( + brightness: Brightness.dark, + primaryKey: primarySeedColor, + variant: FlexSchemeVariant.fidelity, + contrastLevel: 1.0, + ); +``` + +The contrast level provides a quick way to vary the contrast, but it can only be used with the `DynamicSchemeVariant` based variants, not with the `FlexTones` based variants. With `FlexTones` based variants, you can use multiple seed colors, but with the `DynamicSchemeVariant` only a single primary seed color. However, with `tones` based `FlexTones` you can create custom tones with even more flexibility in seed generation to make schemes with higher or less contrast. Two pre-configured high contrast tones exist earlier via `FlexTones.highContrast` and `FlexTones.ultraContrast`. You can use them as they are, or as examples of how to make your custom versions. + ### Black and White Contrast Another way to modify `FlexTones` configurations for contrast and accessibility, is by forcing all @@ -350,6 +380,8 @@ color accessibility and contrast on any `FlexTones` configuration. > If you use the `variant` property to the seed generated `ColorScheme` you cannot > use the below presented `tones` modifiers, as they are modifiers used only on the input `FlexTones` configurations provided in `tones`. In the package example app you can find a demonstration of how to instead use `tones` as input for `FlexTones` based variants and `variant` being effective only when using variants that are Flutter SDK based. +#### FlexTones Modifiers `onMainsUseBW()` and `onSurfacesUseBW()` + We can do this with the `FlexTones` modifying methods `onMainsUseBW()`, for main on colors and with `onSurfacesUseBW()` for the surface on colors. These modifiers automatically make their corresponding contrasting colors black or white, depending on if the source color is light or dark. @@ -371,7 +403,7 @@ The on colors made black or white by `onMainsUseBW()` are: The surface on colors made black or white by `onSurfacesUseBW()` are: -* `onBackground` (deprecated) +* `onBackground` (deprecated in Flutter 3.22) * `onSurface` * `onSurfaceVariant` * `onInverseSurface` @@ -408,6 +440,7 @@ final ColorScheme schemeDarkOnBW = SeedColorScheme.fromSeeds( .onSurfacesUseBW(), ); ``` +#### FlexTones Modifier `surfacesUseBW()` Another `FlexTones` modifier is `surfacesUseBW()`. This modifier will make the `surface` and `background` colors plain white in light mode and true black in dark mode. @@ -437,6 +470,34 @@ final ColorScheme schemeLightOnBW = SeedColorScheme.fromSeeds( ); ``` +#### FlexTones Modifier `monochromeSurfaces()` + +A new `FlexTones` modifier in FSS version 2.1.0 is `monochromeSurfaces()`. It can be applied to any predefined or custom `FlexTones` to make all the surface colors monochrome and use pure greyscale for the neutral and neutral variant tonal palettes. Surface colors will then have no color tint from their own key color or from the primary key seed color. For those tired of tinted surface colors in Material-3, this is a useful helper. + +This modifier will make the `surface` and +`background` colors plain white in light mode and true black in dark mode. + +This modifier can be used for great effect in light mode, as you can remove the colored +background surfaces from any of the `FlexTones` seeding strategies. Some designs may prefer +plain white, for backgrounds in light mode, for a more platform-agnostic design. + +In dark mode `surfacesUseBW()` can be used create seeded color schemes with true black +background and surface colors, but you may prefer to keep the primary seed color based +slightly primary color tinted backgrounds in dark mode. + + +```dart +// Make a vivid Material 3 seeded light ColorScheme, where all surface colors +// are monochrome with no tint in them, they are based on pure greyscale values. +final ColorScheme schemeLight = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + tones: FlexTones.vivid(Brightness.light).monochromeSurfaces(), +); +``` + ## [Example Application](https://rydmike.com/flexseedscheme/demo-v2) The included example application uses above color seeding and custom From 9812951432dd730ecffafffc966d2520d0d3087a Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 16 Jun 2024 21:59:01 +0300 Subject: [PATCH 10/37] Update CHANGELOG.md --- CHANGELOG.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73517d1..87393f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,17 +7,21 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. This is pre-release 2 of FFS 2.1.0. * **NEW** - * Added support for `contrastLevel` to `SeedColorScheme.fromSeeds`. This allows you to set the contrast level of the generated color scheme, when using `SeedColorScheme.fromSeeds` with the `variant` property. Using `contrastLevel` has no effect when using `tones`, but with `tones` you can create custom tone for even more flexibility in seed generation with higher or less contrast. - * The `FlexTones` got a new built-in modifier `monochromeSurfaces()`. It can be applied to any predefined or custom `FlexTones` to make the surface colors monochrome and use pure greyscale for its tonal palettes, with no color tint from their key color or primary key seed color. + * Added support for `contrastLevel` to `SeedColorScheme.fromSeeds`. This allows you to set the contrast level of the generated color scheme, when using `SeedColorScheme.fromSeeds` with the `variant` property. The `contrastLevel` parameter indicates the contrast level between color pairs, such as `primary` and `onPrimary`.The value 0.0 is the default (normal); -1.0 is the lowest; 1.0 is the highest. From Material Design guideline, the medium and high contrast, correspond to 0.5 and 1.0 respectively. + * The `contrastLevel` in Flutter SDK is not yet available in `ColorScheme.fromSeed` on Flutter stable 3.22.x, but is available on the master channel. With FSS you can use it already in Flutter 3.22.x. + * **NOTE:** Using `contrastLevel` has no effect when using `tones`. However, with `tones` you can create custom tones with even more flexibility in seed generation to make schemes with higher or less contrast. Two pre-configured high contrast tones exist earlier via `FlexTones.highContrast` and `FlexTones.ultraContrast`. + + * The `tones` configuration class `FlexTones` got a new built-in modifier, `monochromeSurfaces()`. It can be applied to any predefined or custom `FlexTones` to make the surface colors monochrome and use pure greyscale for the neutral and neutral variant tonal palettes, with no color tint from their key color or primary key seed color. * **CHANGE** - * Updated `MaterialDynamicColors` to use the expressive on-colors spec. This brings the internal Material Color Utilities up to version 0.12.0 on pub. This changes the contrast curve for light/dark color and dark mode color tone for onPrimaryContainer, onSecondaryContainer, onTertiaryContainer and onErrorContainer. The dark mode tone is changed from 10 to 30, which is the correct spec. Prior to MCU version 0.12.0 the `MaterialDynamicColors` used the wrong spec. Flutter 3.22.x and Flutter master still uses MCU earlier than 0.12.0 and have the wrong onColor tones. This will be corrected when Flutter updates to MCU 0.12.0. - * Sets Flutter constraint back to `>=3.22.0` since beta and master are now higher than `3.22.0` so it can now be done, and master and beta can use this constraint as well. + * Updated `MaterialDynamicColors` to use the correct expressive on-colors spec. This brings the internal Material Color Utilities up to version 0.12.0 as published on pub. This changes the contrast curve for light/dark colors and dark mode color tone, for onPrimaryContainer, onSecondaryContainer, onTertiaryContainer and onErrorContainer. The dark mode tone is changed from 10 to 30, which is the correct spec. Prior to MCU version 0.12.0 the `MaterialDynamicColors` used the wrong spec. Flutter stable 3.22.x and Flutter master still use MCU earlier than 0.12.0 and have the wrong onColor tones. This will be corrected in Flutter SDK when Flutter is updated to MCU 0.12.0 or later. With FSS 2.1.0 you get the correct spec already now. + + * Set Flutter constraint back to `>=3.22.0`. Since beta and master are now on `3.23.0` or higher, this constraint can now be used by master and beta without any issue. * **FIX** - * EXAMPLE: The key color to seed error palette was not used in the example in dark mode. + * SAMPLE: The key color to seed the error palette was not used in the main example in dark mode. ## 2.1.0-dev.1 From d18dc2c051e9e7db19e18e15105eb61d1f410f00 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 16 Jun 2024 21:59:21 +0300 Subject: [PATCH 11/37] Doc comment updates --- lib/src/flex/flex_seed_scheme.dart | 58 ++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/lib/src/flex/flex_seed_scheme.dart b/lib/src/flex/flex_seed_scheme.dart index c5faa18..a7e19da 100644 --- a/lib/src/flex/flex_seed_scheme.dart +++ b/lib/src/flex/flex_seed_scheme.dart @@ -428,9 +428,61 @@ extension SeedColorScheme on ColorScheme { /// Any seed produced [ColorScheme] color can be overridden by providing it a /// given [Color] value. /// - /// The propertis [tones] and [variant] are mutually exclusive, only one of + /// The properties [tones] and [variant] are mutually exclusive, only one of /// them can be used. If both are null, the default from [tones] is used. - + /// The [variant] can be used to select a predefined [FlexSchemeVariant] that + /// include all the [DynamicSchemeVariant]s in Flutter SDK, but also all the + /// predefined [FlexTones] in this package. /// + /// + /// A [ColorScheme] is a set of 46 colors based on the + /// [Material spec](https://m3.material.io/styles/color/the-color-system/color-roles) + /// that can be used to configure the color properties of most components. + /// + /// ### Colors in Material 3 + /// + /// The main accent color groups in the scheme are [primary], [secondary], + /// and [tertiary]. + /// + /// * Primary colors are used for key components across the UI, such as the + /// FAB, prominent buttons, and active states. + /// + /// * Secondary colors are used for less prominent components in the UI, such + /// as filter chips, while expanding the opportunity for color expression. + /// + /// * Tertiary colors are used for contrasting accents that can be used to + /// balance primary and secondary colors or bring heightened attention to + /// an element, such as an input field. The tertiary colors are left + /// for makers to use at their discretion and are intended to support + /// broader color expression in products. + /// + /// Each accent color group (primary, secondary and tertiary) includes + /// '-Fixed' and '-Dim' color roles, such as [primaryFixed] and + /// [primaryFixedDim]. Fixed roles are appropriate to use in places where + /// Container roles are normally used, but they stay the same color between + /// light and dark themes. The '-Dim' roles provide a stronger, more + /// emphasized color with the same fixed behavior. + /// + /// The remaining colors of the scheme are composed of neutral colors used for + /// backgrounds and surfaces, as well as specific colors for errors, dividers + /// and shadows. Surface colors are used for backgrounds and large, + /// low-emphasis areas of the screen. + /// + /// Material 3 also introduces tone-based surfaces and surface containers. + /// They replace the old opacity-based model which applied a tinted overlay on + /// top of surfaces based on their elevation. These colors include: + /// [surfaceBright], [surfaceDim], [surfaceContainerLowest], + /// [surfaceContainerLow], [surfaceContainer], [surfaceContainerHigh], and + /// [surfaceContainerHighest]. + /// + /// Many of the colors have matching 'on' colors, which are used for drawing + /// content on top of the matching color. For example, if something is using + /// [primary] for a background color, [onPrimary] would be used to paint text + /// and icons on top of it. For this reason, the 'on' colors should have a + /// contrast ratio with their matching colors of at least 4.5:1 in order to + /// be readable. On '-FixedVariant' roles, such as [onPrimaryFixedVariant], + /// also have the same color between light and dark themes, but compared + /// with on '-Fixed' roles, such as [onPrimaryFixed], they provide a + /// lower-emphasis option for text and icons. static ColorScheme fromSeeds({ /// The overall brightness of this color scheme. Brightness brightness = Brightness.light, @@ -569,7 +621,7 @@ extension SeedColorScheme on ColorScheme { /// -1.0 is the lowest; 1.0 is the highest. From Material Design guideline, /// the medium and high contrast correspond to 0.5 and 1.0 respectively. /// - /// The contrast level property is only used when generating the + /// The contrast level property is only used when seed generating the /// [ColorScheme] based on [variant]. When using [tones] the [contrastLevel] /// is ignored. With [tones] contrast level can be set as desired using /// custom [FlexTones] configurations. There are two From 34cfb3b71d567cde298fc1eb8530508aae6b86cf Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 16 Jun 2024 21:59:35 +0300 Subject: [PATCH 12/37] Update .fvmrc --- .fvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.fvmrc b/.fvmrc index 1c299f8..c300356 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "master" + "flutter": "stable" } \ No newline at end of file From e4cf1fdbfa324de3826ebdbf206d9321542c9d6c Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Mon, 17 Jun 2024 16:33:14 +0300 Subject: [PATCH 13/37] Update docs readme and chnage log --- CHANGELOG.md | 18 +++++++++++------- README.md | 39 ++++++++++++--------------------------- 2 files changed, 23 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87393f4..ebb8160 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,9 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. -## 2.1.0-dev.2 +## 2.1.0 -This is pre-release 2 of FFS 2.1.0. +**June 17, 2024** * **NEW** * Added support for `contrastLevel` to `SeedColorScheme.fromSeeds`. This allows you to set the contrast level of the generated color scheme, when using `SeedColorScheme.fromSeeds` with the `variant` property. The `contrastLevel` parameter indicates the contrast level between color pairs, such as `primary` and `onPrimary`.The value 0.0 is the default (normal); -1.0 is the lowest; 1.0 is the highest. From Material Design guideline, the medium and high contrast, correspond to 0.5 and 1.0 respectively. @@ -14,10 +14,12 @@ This is pre-release 2 of FFS 2.1.0. * The `tones` configuration class `FlexTones` got a new built-in modifier, `monochromeSurfaces()`. It can be applied to any predefined or custom `FlexTones` to make the surface colors monochrome and use pure greyscale for the neutral and neutral variant tonal palettes, with no color tint from their key color or primary key seed color. +* **COLOR VALUE BREAKING** + * Updated `MaterialDynamicColors` to use the correct expressive on-colors spec. This brings the internal Material Color Utilities (MCU) up to version 0.12.0 as published on pub. This changes the contrast curve for light/dark colors and dark mode color tone for the colors `onPrimaryContainer`, `onSecondaryContainer`, `onTertiaryContainer` and `onErrorContainer`. The dark mode tone is changed from 10 to 30, which is the correct spec. The mentioned colors also change slightly for light mode in some dynamic schemes due to the changed contrast curve, which was changed from `ContrastCurve(4.5, 7, 11, 21)` to `ContrastCurve(3, 4.5, 7, 11)` for the listed on container colors. + * Prior to MCU version 0.12.0 the `MaterialDynamicColors` used the wrong spec. Flutter stable 3.22.x and Flutter master still use an MCU version lower than 0.12.0 and have the wrong on color tones in dark mode. This will be corrected in Flutter SDK when Flutter is updated to use MCU 0.12.0 or later. With FSS 2.1.0 you get the correct spec already now. However, the mentioned `MaterialDynamicColors` on container colors will now differ slightly in light mode and more in dark mode, from the wrong ones that are currently produced by MCU versions before 0.12.0 that Flutter SDK still uses. When Flutter SDK bumps its version to MCU 0.12.0 r later, the mentioned FSS produced colors will again be identical to the SDK produced values. + * **CHANGE** - * Updated `MaterialDynamicColors` to use the correct expressive on-colors spec. This brings the internal Material Color Utilities up to version 0.12.0 as published on pub. This changes the contrast curve for light/dark colors and dark mode color tone, for onPrimaryContainer, onSecondaryContainer, onTertiaryContainer and onErrorContainer. The dark mode tone is changed from 10 to 30, which is the correct spec. Prior to MCU version 0.12.0 the `MaterialDynamicColors` used the wrong spec. Flutter stable 3.22.x and Flutter master still use MCU earlier than 0.12.0 and have the wrong onColor tones. This will be corrected in Flutter SDK when Flutter is updated to MCU 0.12.0 or later. With FSS 2.1.0 you get the correct spec already now. - - * Set Flutter constraint back to `>=3.22.0`. Since beta and master are now on `3.23.0` or higher, this constraint can now be used by master and beta without any issue. + * Revert Flutter constraint back to `>=3.22.0`. Since beta and master are now on `3.23.0` or higher, this constraint can now be used by master and beta channels without any issue. * **FIX** @@ -26,10 +28,12 @@ This is pre-release 2 of FFS 2.1.0. ## 2.1.0-dev.1 -This is pre-release 1 of FFS 2.1.0. +**May 21, 2024** + +This is a temp pre-release of FFS 2.1.0. **FIX** -* [FIX #13](https://github.com/rydmike/flex_seed_scheme/issues/13). Sets Flutter version constraint to flutter: '>=3.22.0-0.3.pre', so that the package can also be used on **beta** and **stable** channels, that are still on 3.22.0-a.b.pre versions, which is considered smaller than **3.22.0**, used in the stable release of the package. You can use this version of the package if you need to use **beta** or **master** channel. It is apart from the version constraint difference identical to the `2.0.0` release. +* [FIX #13](https://github.com/rydmike/flex_seed_scheme/issues/13). Sets Flutter version constraint to flutter: '>=3.22.0-0.3.pre', so that the package can also be used on **beta** and **stable** channels, while they are still on 3.22.0-a.b.pre versions, which is considered smaller than **3.22.0**, used in the stable release of the package. You can use this version of the package if you need to use **beta** or **master** channel before they have been bumped to 3.23.x. This release is apart from the version constraint difference identical to the `2.0.0` release. ## 2.0.0 diff --git a/README.md b/README.md index 93d33b9..2629b6a 100644 --- a/README.md +++ b/README.md @@ -282,18 +282,18 @@ By using `paletteType` with value `FlexPaletteType.extended`, you can create see The `ColorScheme` colors that use these new tones are now finally also available in Flutter 3.22 or later. For more information and the latest updates, see [Material-3 color-roles](https://m3.material.io/styles/color/the-color-system/color-roles) specification. -The updated Material-3 color system adds tones `[4, 6, 12, 17, 22, 24]`, they are used for new dark mode surfaces in revised Material-3 dark surface colors. Likewise, the added tones `[87, 92, 94, 96, 98]` are for light mode surfaces in the updated Material-3 color system. By default `paletteType` of `FlexTones.extended` is now used to enable support for the tones in the updated specification and also adding three more custom tone `[2, 5, 97]`. The `paletteType` with value `FlexPaletteType.extended` is now default, it produces 27 tones `[0, 2, 4, 5, 6, 10, 12, 17, 20, 22, 24, 30, 40, 50, 60, 70, 80, 87, 90, 92, 94, 95, 96, 97, 98, 99, 100]`. +The updated Material-3 color system adds tones `[4, 6, 12, 17, 22, 24]`, they are used for new dark mode surfaces in revised Material-3 dark surface colors. Likewise, the added tones `[87, 92, 94, 96, 98]` are for light mode surfaces in the updated Material-3 color system. By default `paletteType` of `FlexTones.extended` is now used to enable support for the tones in the updated specification and also adding three more custom tones `[2, 5, 97]`. The `paletteType` with value `FlexPaletteType.extended` is now default, it produces 27 tones `[0, 2, 4, 5, 6, 10, 12, 17, 20, 22, 24, 30, 40, 50, 60, 70, 80, 87, 90, 92, 94, 95, 96, 97, 98, 99, 100]`. To use the older classic setup you can still use `FlexTones.common`. It produces the legacy M3 tones with its own two additions `[5]` and `[98]` resulting in 15 tones `[0, 5, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 98, 99, 100]`. Flutter versions before 3.22 do not yet use these new tones in its standard `ColorScheme`. -For backwards compatibility, when using type `FlexPaletteType.common` the chroma of high tones, meaning higher or equal to 90, are limited to maximum chroma of 40. This keeps the chromacity of tones 90 to 100 lower than or equal to 40. If the source seed color has higher chromacity than 40, there may be a sudden jump in chroma reduction at tone 90. This is the standard behavior for the original Material-3 tonal palette computation. The `FlexPaletteType.common` type is intended to be used when there is a need to follow the M3's original palette design. +For backwards compatibility, when using type `FlexPaletteType.common` the chroma of high tones, meaning higher or equal to 90, are limited to maximum chroma of 40. This keeps the chromacity of tones 90 to 100 lower than or equal to 40. If the source seed color has higher chromacity than 40, there may be a sudden jump in chroma reduction at tone 90. This is the standard behavior for the original Material-3 tonal palette computation. The `FlexPaletteType.common` type is intended to be used when there is a need to follow the M3's original, now legacy, palette design. When using the `FlexPaletteType.extended` type tones, there are not only new tones, but the chroma limit of tones >= 90 is also removed. This increases fidelity of higher tone when high chromacity is used. ## Accessibility You can use `FlexTones` to create a seed generated `ColorScheme`, that is based on same colors -as your standard theme's light and dark scheme colors, but uses a chroma configuration and tone +as the standard theme's light and dark scheme colors, but uses a chroma configuration and tone mapping setup that increases contrast further from standard light and dark theme setup. There are two high contrast `FlexTones` configuration pre-made for this. They are called @@ -322,9 +322,9 @@ final ColorScheme schemeDarkHc = SeedColorScheme.fromSeeds( ); ``` -If you then define equivalent `ThemeData` based on those schemes as your standard `MaterialApp`, +If you define equivalent `ThemeData` based on those schemes as your standard `MaterialApp`, `theme` and `darkTheme` definitions, but assign them to `highContrastTheme` and -`highContrastDarkTheme`, you get more accessible themed colors that are based on same colors, +`highContrastDarkTheme`, you get more accessible themed colors that are based on the same colors but with higher contrast, that are activated when users select high-contrast theme in device accessibility system settings. Changing to accessibility theme based on device system setting automatically, by using theme data defined on `MaterialApp` properties `highContrastTheme` and @@ -333,7 +333,6 @@ For other platforms, you need to use a user setting and toggle themes based on i ```dart // Define accessibility high contrast versions using same color base. - // highContrastTheme: ThemeData( colorScheme: schemeLightHc, useMaterial3: true, @@ -346,9 +345,9 @@ For other platforms, you need to use a user setting and toggle themes based on i ### Contrast Level -When using a `variant` that is based on Flutter SDK DynamicScheme, it has `FlexSchemeVariant` property `isFlutterScheme` set to true, you can also provide a `contrastLevel` for the seed generation. +When using a `variant` that is based on the equivalent Flutter SDK `DynamicScheme`, indicated by that it has its `FlexSchemeVariant` property `isFlutterScheme` set to true, you can also provide a `contrastLevel` for the seed generation. -The `contrastLevel` parameter indicates the contrast level between color pairs, such as `primary` and `onPrimary`. The value 0.0 is the default normal contrast; -1.0 is the lowest; 1.0 is the highest. From **Material Design guideline**, the medium and high contrast, correspond to 0.5 and 1.0 respectively. The `contrastLevel` is used to adjust the contrast between the primary and onPrimary colors. The `contraslLevel` must be from -1.0 to 1.0. +The `contrastLevel` parameter is used to indicate the contrast level between color pairs, such as `primary` and `onPrimary`. The value 0.0 is the default normal contrast; -1.0 is the lowest; 1.0 is the highest. From **Material Design guideline**, the medium and high contrast, correspond to 0.5 and 1.0 respectively. The `contrastLevel` is used to adjust the contrast between the main color and its on color pair. The `contraslLevel` must be from -1.0 to 1.0. ```dart // Make a light high contrast ColorScheme from a seeds using variant style fidelity. @@ -377,14 +376,11 @@ If we remove the Material-3 guide used color system's colored contrasting colors color accessibility and contrast on any `FlexTones` configuration. > [!NOTE] -> If you use the `variant` property to the seed generated `ColorScheme` you cannot -> use the below presented `tones` modifiers, as they are modifiers used only on the input `FlexTones` configurations provided in `tones`. In the package example app you can find a demonstration of how to instead use `tones` as input for `FlexTones` based variants and `variant` being effective only when using variants that are Flutter SDK based. +> If you use the `variant` property to make your seed generated `ColorScheme`, you cannot use the below presented `tones` modifiers, as they are modifiers used on the input `FlexTones` configurations provided in `tones`. In the package main example app you can find a demonstration of how to use `tones` as input for `FlexTones` based variants, and `variant` being effective only when using variants that are Flutter SDK and MCU based. #### FlexTones Modifiers `onMainsUseBW()` and `onSurfacesUseBW()` -We can do this with the `FlexTones` modifying methods `onMainsUseBW()`, for main on colors and -with `onSurfacesUseBW()` for the surface on colors. These modifiers automatically make their -corresponding contrasting colors black or white, depending on if the source color is light or dark. +We can use black and white contrasting colors with the `FlexTones` modifying methods `onMainsUseBW()`, for main on colors and with `onSurfacesUseBW()` for the surface on colors. These modifiers automatically make their corresponding contrasting colors black or white, depending on if the corresponding main color is light or dark. The main colors are: * `primary`, `primaryContainer` @@ -451,8 +447,9 @@ plain white, for backgrounds in light mode, for a more platform-agnostic design. In dark mode `surfacesUseBW()` can be used create seeded color schemes with true black background and surface colors, but you may prefer to keep the primary seed color based -slightly primary color tinted backgrounds in dark mode. +slightly primary color tinted backgrounds in dark mode. +This modifier will make the `surface` color and still also the deprecated `background` color plain white in light mode and true black in dark mode. ```dart // Make a Material 3 seeded light ColorScheme, but with always @@ -472,19 +469,7 @@ final ColorScheme schemeLightOnBW = SeedColorScheme.fromSeeds( #### FlexTones Modifier `monochromeSurfaces()` -A new `FlexTones` modifier in FSS version 2.1.0 is `monochromeSurfaces()`. It can be applied to any predefined or custom `FlexTones` to make all the surface colors monochrome and use pure greyscale for the neutral and neutral variant tonal palettes. Surface colors will then have no color tint from their own key color or from the primary key seed color. For those tired of tinted surface colors in Material-3, this is a useful helper. - -This modifier will make the `surface` and -`background` colors plain white in light mode and true black in dark mode. - -This modifier can be used for great effect in light mode, as you can remove the colored -background surfaces from any of the `FlexTones` seeding strategies. Some designs may prefer -plain white, for backgrounds in light mode, for a more platform-agnostic design. - -In dark mode `surfacesUseBW()` can be used create seeded color schemes with true black -background and surface colors, but you may prefer to keep the primary seed color based -slightly primary color tinted backgrounds in dark mode. - +A new `FlexTones` modifier in FSS version 2.1.0 is `monochromeSurfaces()`. It can be applied to any predefined or custom `FlexTones` to make all the surface colors monochrome and use pure greyscale for the neutral and neutral variant tonal palettes. Surface colors will then have no color tint from their own key color or from the primary seed key color. For those tired of tinted surface colors in Material-3, this is a useful helper. ```dart // Make a vivid Material 3 seeded light ColorScheme, where all surface colors From 48b9beabff46dd58dd000be5cf8d8f9d47d10bfc Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:27:23 +0300 Subject: [PATCH 14/37] Add: Tests for new feature and 100% coverage --- test/flex_seed_scheme_test.dart | 31 ++++++++++++++++++ test/mcu/dynamic_scheme_test.dart | 54 +++++++++++++++++++++++++------ 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/test/flex_seed_scheme_test.dart b/test/flex_seed_scheme_test.dart index ca02ec3..5d48af3 100644 --- a/test/flex_seed_scheme_test.dart +++ b/test/flex_seed_scheme_test.dart @@ -873,5 +873,36 @@ void main() { } } }); + test( + 'FCS7.014-l: GIVEN a SeedColorScheme.fromSeeds using five seeds ' + 'and tones map FlexTones.material for a light scheme with ' + 'error neutral and variant chroma set but with neutral chroma ' + 'and variant neutral chroma set to 0 ' + 'EXPECT scheme equal to using monochromeSurfaces()', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + tones: FlexTones.material(Brightness.light).copyWith( + neutralMinChroma: 0, + neutralChroma: 0, + neutralVariantMinChroma: 0, + neutralVariantChroma: 0, + ), + ); + final ColorScheme scheme2 = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + tones: FlexTones.material(Brightness.light).monochromeSurfaces(), + ); + expect(scheme, scheme2); + }); }); } diff --git a/test/mcu/dynamic_scheme_test.dart b/test/mcu/dynamic_scheme_test.dart index ca531ee..c38697f 100644 --- a/test/mcu/dynamic_scheme_test.dart +++ b/test/mcu/dynamic_scheme_test.dart @@ -76,18 +76,18 @@ void main() { expect(dynamicScheme.getHct(surfaceHighest).toInt(), surfaceHighest.getArgb(dynamicScheme)); - // TODO(rydmike): Add DynamicSchemeVariant when they land in Flutter. + // TODO(rydmike): Add DynamicSchemeVariant test when Flutter uses MCU 0.12.0 // 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), + // DynamicSchemeVariant.tonalSpot => SchemeTonalSpot(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: contrastLevel), + // DynamicSchemeVariant.fidelity => SchemeFidelity(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: contrastLevel), + // DynamicSchemeVariant.content => SchemeContent(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: contrastLevel), + // DynamicSchemeVariant.monochrome => SchemeMonochrome(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: contrastLevel), + // DynamicSchemeVariant.neutral => SchemeNeutral(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: contrastLevel), + // DynamicSchemeVariant.vibrant => SchemeVibrant(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: contrastLevel), + // DynamicSchemeVariant.expressive => SchemeExpressive(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: contrastLevel), + // DynamicSchemeVariant.rainbow => SchemeRainbow(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: contrastLevel), + // DynamicSchemeVariant.fruitSalad => SchemeFruitSalad(sourceColorHct: sourceColor, isDark: isDark, contrastLevel: contrastLevel), // }; final ColorScheme colorScheme = ColorScheme.fromSeed( @@ -105,6 +105,9 @@ void main() { // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 // expect(colorScheme.onPrimaryContainer.value, // MaterialDynamicColors.onPrimaryContainer.getArgb(dynamicScheme)); + // TODO(rydmike): Using hard coded value test until Flutter updates. + expect(colorScheme.onPrimaryContainer.value, 4281079296); + // expect(colorScheme.primaryFixed.value, MaterialDynamicColors.primaryFixed.getArgb(dynamicScheme)); expect(colorScheme.primaryFixedDim.value, @@ -122,6 +125,11 @@ void main() { // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 // expect(colorScheme.onSecondaryContainer.value, // MaterialDynamicColors.onSecondaryContainer.getArgb(dynamicScheme)); + // TODO(rydmike): Using hard coded value test until Flutter updates. + expect(colorScheme.onSecondaryContainer.value, 4280883206); + expect(4284039724, + MaterialDynamicColors.onSecondaryContainer.getArgb(dynamicScheme)); + // expect(colorScheme.secondaryFixed.value, MaterialDynamicColors.secondaryFixed.getArgb(dynamicScheme)); expect(colorScheme.secondaryFixedDim.value, @@ -139,6 +147,11 @@ void main() { // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 // expect(colorScheme.onTertiaryContainer.value, // MaterialDynamicColors.onTertiaryContainer.getArgb(dynamicScheme)); + // TODO(rydmike): Using hard coded value test until Flutter updates. + expect(colorScheme.onTertiaryContainer.value, 4279639553); + expect(4282469156, + MaterialDynamicColors.onTertiaryContainer.getArgb(dynamicScheme)); + // expect(colorScheme.tertiaryFixed.value, MaterialDynamicColors.tertiaryFixed.getArgb(dynamicScheme)); expect(colorScheme.tertiaryFixedDim.value, @@ -156,6 +169,11 @@ void main() { // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 // expect(colorScheme.onErrorContainer.value, // MaterialDynamicColors.onErrorContainer.getArgb(dynamicScheme)); + // TODO(rydmike): Using hard coded value test until Flutter updates. + expect(colorScheme.onErrorContainer.value, 4282449922); + expect(4287823882, + MaterialDynamicColors.onErrorContainer.getArgb(dynamicScheme)); + // expect(colorScheme.background.value, MaterialDynamicColors.background.getArgb(dynamicScheme)); expect(colorScheme.onBackground.value, @@ -204,6 +222,10 @@ void main() { // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 // expect( // colorScheme.onPrimaryContainer.value, dynamicScheme.onPrimaryContainer); + // TODO(rydmike): Using hard coded value test until Flutter updates. + expect(colorScheme.onPrimaryContainer.value, 4281079296); + expect(4285086720, dynamicScheme.onPrimaryContainer); + // expect(colorScheme.primaryFixed.value, dynamicScheme.primaryFixed); expect(colorScheme.primaryFixedDim.value, dynamicScheme.primaryFixedDim); expect(colorScheme.onPrimaryFixed.value, dynamicScheme.onPrimaryFixed); @@ -216,6 +238,10 @@ void main() { // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 // expect(colorScheme.onSecondaryContainer.value, // dynamicScheme.onSecondaryContainer); + // TODO(rydmike): Using hard coded value test until Flutter updates. + expect(colorScheme.onSecondaryContainer.value, 4280883206); + expect(4284039724, dynamicScheme.onSecondaryContainer); + // expect(colorScheme.secondaryFixed.value, dynamicScheme.secondaryFixed); expect( colorScheme.secondaryFixedDim.value, dynamicScheme.secondaryFixedDim); @@ -229,6 +255,10 @@ void main() { // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 // expect(colorScheme.onTertiaryContainer.value, // dynamicScheme.onTertiaryContainer); + // TODO(rydmike): Using hard coded value test until Flutter updates. + expect(colorScheme.onTertiaryContainer.value, 4279639553); + expect(4282469156, dynamicScheme.onTertiaryContainer); + // expect(colorScheme.tertiaryFixed.value, dynamicScheme.tertiaryFixed); expect(colorScheme.tertiaryFixedDim.value, dynamicScheme.tertiaryFixedDim); expect(colorScheme.onTertiaryFixed.value, dynamicScheme.onTertiaryFixed); @@ -240,6 +270,10 @@ void main() { // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 // expect(colorScheme.onErrorContainer.value, // dynamicScheme.onErrorContainer); + // TODO(rydmike): Using hard coded value test until Flutter updates. + expect(colorScheme.onErrorContainer.value, 4282449922); + expect(4287823882, dynamicScheme.onErrorContainer); + // expect(colorScheme.background.value, dynamicScheme.background); expect(colorScheme.onBackground.value, dynamicScheme.onBackground); expect(colorScheme.surface.value, dynamicScheme.surface); From 34b99da70a2045e74f14da093f8d4fe6831bef65 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Mon, 17 Jun 2024 17:59:34 +0300 Subject: [PATCH 15/37] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 2629b6a..165e42d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ Use this package like `ColorScheme.fromSeed` with the following additional capab * Change which tones in the generated core tonal palettes are used by which `ColorScheme` color. * Use `FlexPaletteType.extended`, that gives access to all the new tones that are used in revised Material-3 design for surfaces and fixed main color. The new tones are used and supported by the standard `ColorScheme` in Flutter 3.22 and later. It is recommended to use `FlexPaletteType.extended` if creating new custom `FlexTones`. In the package version 2.0.0 and later, `FlexPaletteType.extended` is the default palette type. The older palette type `FlexPaletteType.common` is available for backwards compatibility, but should be avoided in Flutter 3.22 and later. +> [!NOTE] +> FlexSeedScheme still produces colors for the in Flutter 3.22 deprecated `ColorScheme` colors `background`, `onBackground` and `surfaceVariant`. It does so because despite the colors being deprecated, they are still used in `ThemeData` creation and in a few widgets by the Flutter SDK in both stable 3.22 and also in master 3.23. The colors are not used in the official Material-3 design, but are still used in the Flutter SDK. Since FSS references these deprecated colors, that it has to do to produce a correct and functional `ColorScheme`, it loses 10 score points in **pub.dev** pana analyzer. However, without doing this the `ColorScheme` produced by FSS would not be fully functional and correct for the Flutter SDK. So it is better to lose 10 points than to not work correctly. Whenever Flutter fully stops using these deprecated colors, FSS will be updated to reflect this and no longer reference them. + ## Getting Started Add the `flex_seed_scheme` package to `pubspec.yaml`: From ea898363b26a98f55ea3bfb65ee9ca1a9ed01ac0 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Mon, 17 Jun 2024 18:40:38 +0300 Subject: [PATCH 16/37] Example: Add feature to show the color values --- README.md | 2 +- .../views/universal/color_scheme_view.dart | 88 +++++++++++++++---- example/lib/home/views/pages/home_page.dart | 33 ++++--- .../home/views/widgets/show_input_colors.dart | 2 +- .../theme/controllers/theme_controller.dart | 9 ++ 5 files changed, 103 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 165e42d..942334c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Use this package like `ColorScheme.fromSeed` with the following additional capab * Use `FlexPaletteType.extended`, that gives access to all the new tones that are used in revised Material-3 design for surfaces and fixed main color. The new tones are used and supported by the standard `ColorScheme` in Flutter 3.22 and later. It is recommended to use `FlexPaletteType.extended` if creating new custom `FlexTones`. In the package version 2.0.0 and later, `FlexPaletteType.extended` is the default palette type. The older palette type `FlexPaletteType.common` is available for backwards compatibility, but should be avoided in Flutter 3.22 and later. > [!NOTE] -> FlexSeedScheme still produces colors for the in Flutter 3.22 deprecated `ColorScheme` colors `background`, `onBackground` and `surfaceVariant`. It does so because despite the colors being deprecated, they are still used in `ThemeData` creation and in a few widgets by the Flutter SDK in both stable 3.22 and also in master 3.23. The colors are not used in the official Material-3 design, but are still used in the Flutter SDK. Since FSS references these deprecated colors, that it has to do to produce a correct and functional `ColorScheme`, it loses 10 score points in **pub.dev** pana analyzer. However, without doing this the `ColorScheme` produced by FSS would not be fully functional and correct for the Flutter SDK. So it is better to lose 10 points than to not work correctly. Whenever Flutter fully stops using these deprecated colors, FSS will be updated to reflect this and no longer reference them. +> FlexSeedScheme still produces color values for the in Flutter 3.22 deprecated `ColorScheme` colors `background`, `onBackground` and `surfaceVariant`. It does so because despite the colors being deprecated, they are still used in `ThemeData` creation and in a few widgets by the Flutter SDK in both stable 3.22 and also in master 3.23. The colors are not used in the official Material-3 design, but are still used in the Flutter SDK. Since FSS references these deprecated colors, that it has to do to produce a correct and functional `ColorScheme`, it loses 10 score points in **pub.dev** pana analyzer. However, without doing this the `ColorScheme` produced by FSS would not be fully functional and correct for the Flutter SDK. So it is better to lose 10 points than to not work correctly. Whenever Flutter fully stops using these deprecated colors, FSS will be updated to reflect this and no longer reference them. ## Getting Started diff --git a/example/lib/core/views/universal/color_scheme_view.dart b/example/lib/core/views/universal/color_scheme_view.dart index 782694f..c1a6b81 100644 --- a/example/lib/core/views/universal/color_scheme_view.dart +++ b/example/lib/core/views/universal/color_scheme_view.dart @@ -15,9 +15,10 @@ const Size _equalThreeChipSize = Size(160, 57.333); /// It also allows copying the color values to the clipboard by tapping the /// scheme color. class ColorSchemeView extends StatelessWidget { - const ColorSchemeView({super.key, this.scheme}); + const ColorSchemeView({super.key, this.scheme, this.showColorValue = false}); final ColorScheme? scheme; + final bool showColorValue; @override Widget build(BuildContext context) { @@ -70,21 +71,26 @@ class ColorSchemeView extends StatelessWidget { color: colorScheme.primary, onColor: colorScheme.onPrimary, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( - label: 'onPrimary', - color: colorScheme.onPrimary, - onColor: colorScheme.primary), + label: 'onPrimary', + color: colorScheme.onPrimary, + onColor: colorScheme.primary, + showValue: showColorValue, + ), ColorChip( label: 'primaryContainer', color: colorScheme.primaryContainer, onColor: colorScheme.onPrimaryContainer, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( label: 'onPrimaryContainer', color: colorScheme.onPrimaryContainer, onColor: colorScheme.primaryContainer, + showValue: showColorValue, ), ]), ColorGroup(children: [ @@ -93,21 +99,26 @@ class ColorSchemeView extends StatelessWidget { color: colorScheme.primaryFixed, onColor: colorScheme.onPrimaryFixed, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( - label: 'onPrimaryFixed', - color: colorScheme.onPrimaryFixed, - onColor: colorScheme.primaryFixed), + label: 'onPrimaryFixed', + color: colorScheme.onPrimaryFixed, + onColor: colorScheme.primaryFixed, + showValue: showColorValue, + ), ColorChip( label: 'primaryFixedDim', color: colorScheme.primaryFixedDim, onColor: colorScheme.onPrimaryFixedVariant, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( label: 'onPrimaryFixedVariant', color: colorScheme.onPrimaryFixedVariant, onColor: colorScheme.primaryFixedDim, + showValue: showColorValue, ), ]), ColorGroup(children: [ @@ -116,22 +127,26 @@ class ColorSchemeView extends StatelessWidget { color: colorScheme.secondary, onColor: colorScheme.onSecondary, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( label: 'onSecondary', color: colorScheme.onSecondary, onColor: colorScheme.secondary, + showValue: showColorValue, ), ColorChip( label: 'secondaryContainer', color: colorScheme.secondaryContainer, onColor: colorScheme.onSecondaryContainer, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( label: 'onSecondaryContainer', color: colorScheme.onSecondaryContainer, onColor: colorScheme.secondaryContainer, + showValue: showColorValue, ), ]), ColorGroup(children: [ @@ -140,21 +155,26 @@ class ColorSchemeView extends StatelessWidget { color: colorScheme.secondaryFixed, onColor: colorScheme.onSecondaryFixed, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( - label: 'onSecondaryFixed', - color: colorScheme.onSecondaryFixed, - onColor: colorScheme.secondaryFixed), + label: 'onSecondaryFixed', + color: colorScheme.onSecondaryFixed, + onColor: colorScheme.secondaryFixed, + showValue: showColorValue, + ), ColorChip( label: 'secondaryFixedDim', color: colorScheme.secondaryFixedDim, onColor: colorScheme.onSecondaryFixedVariant, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( label: 'onSecondaryFixedVariant', color: colorScheme.onSecondaryFixedVariant, onColor: colorScheme.secondaryFixedDim, + showValue: showColorValue, ), ]), ColorGroup( @@ -164,22 +184,26 @@ class ColorSchemeView extends StatelessWidget { color: colorScheme.tertiary, onColor: colorScheme.onTertiary, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( label: 'onTertiary', color: colorScheme.onTertiary, onColor: colorScheme.tertiary, + showValue: showColorValue, ), ColorChip( label: 'tertiaryContainer', color: colorScheme.tertiaryContainer, onColor: colorScheme.onTertiaryContainer, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( label: 'onTertiaryContainer', color: colorScheme.onTertiaryContainer, onColor: colorScheme.tertiaryContainer, + showValue: showColorValue, ), ], ), @@ -189,21 +213,26 @@ class ColorSchemeView extends StatelessWidget { color: colorScheme.tertiaryFixed, onColor: colorScheme.onTertiaryFixed, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( - label: 'onTertiaryFixed', - color: colorScheme.onTertiaryFixed, - onColor: colorScheme.tertiaryFixed), + label: 'onTertiaryFixed', + color: colorScheme.onTertiaryFixed, + onColor: colorScheme.tertiaryFixed, + showValue: showColorValue, + ), ColorChip( label: 'tertiaryFixedDim', color: colorScheme.tertiaryFixedDim, onColor: colorScheme.onTertiaryFixedVariant, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( label: 'onTertiaryFixedVariant', color: colorScheme.onTertiaryFixedVariant, onColor: colorScheme.tertiaryFixedDim, + showValue: showColorValue, ), ]), ColorGroup( @@ -213,22 +242,26 @@ class ColorSchemeView extends StatelessWidget { color: colorScheme.error, onColor: colorScheme.onError, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( label: 'onError', color: colorScheme.onError, onColor: colorScheme.error, + showValue: showColorValue, ), ColorChip( label: 'errorContainer', color: colorScheme.errorContainer, onColor: colorScheme.onErrorContainer, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( label: 'onErrorContainer', color: colorScheme.onErrorContainer, onColor: colorScheme.errorContainer, + showValue: showColorValue, ), ], ), @@ -239,22 +272,26 @@ class ColorSchemeView extends StatelessWidget { color: colorScheme.surface, onColor: colorScheme.onSurface, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( label: 'onSurface', color: colorScheme.onSurface, onColor: colorScheme.surface, + showValue: showColorValue, ), ColorChip( label: 'surfaceContainer', color: colorScheme.surfaceContainer, onColor: colorScheme.onSurfaceVariant, size: _colorChipSize, + showValue: showColorValue, ), ColorChip( label: 'onSurfaceVariant', color: colorScheme.onSurfaceVariant, onColor: colorScheme.surfaceContainer, + showValue: showColorValue, ), ], ), @@ -265,24 +302,28 @@ class ColorSchemeView extends StatelessWidget { color: colorScheme.surfaceContainerLowest, onColor: colorScheme.onSurface, size: _equalFourChipSize, + showValue: showColorValue, ), ColorChip( label: 'surfaceContainerLow', color: colorScheme.surfaceContainerLow, onColor: colorScheme.onSurface, size: _equalFourChipSize, + showValue: showColorValue, ), ColorChip( label: 'surfaceContainerHigh', color: colorScheme.surfaceContainerHigh, onColor: colorScheme.onSurface, size: _equalFourChipSize, + showValue: showColorValue, ), ColorChip( label: 'surfaceContainerHighest', color: colorScheme.surfaceContainerHighest, onColor: colorScheme.onSurface, size: _equalFourChipSize, + showValue: showColorValue, ), ], ), @@ -293,24 +334,28 @@ class ColorSchemeView extends StatelessWidget { color: colorScheme.surfaceDim, onColor: colorScheme.onSurface, size: _equalFourChipSize, + showValue: showColorValue, ), ColorChip( label: 'surfaceBright', color: colorScheme.surfaceBright, onColor: colorScheme.onSurface, size: _equalFourChipSize, + showValue: showColorValue, ), ColorChip( label: 'inverseSurface', color: colorScheme.inverseSurface, onColor: colorScheme.onInverseSurface, size: _equalFourChipSize, + showValue: showColorValue, ), ColorChip( label: 'onInverseSurface', color: colorScheme.onInverseSurface, onColor: colorScheme.inverseSurface, size: _equalFourChipSize, + showValue: showColorValue, ), ], ), @@ -321,16 +366,19 @@ class ColorSchemeView extends StatelessWidget { color: colorScheme.outline, onColor: colorScheme.surface, size: _equalThreeChipSize, + showValue: showColorValue, ), ColorChip( label: 'outlineVariant', color: colorScheme.outlineVariant, size: _equalThreeChipSize, + showValue: showColorValue, ), ColorChip( label: 'scrim', color: colorScheme.scrim, size: _equalThreeChipSize, + showValue: showColorValue, ), ], ), @@ -340,17 +388,20 @@ class ColorSchemeView extends StatelessWidget { label: 'surfaceTint', color: colorScheme.surfaceTint, size: _equalThreeChipSize, + showValue: showColorValue, ), ColorChip( label: 'inversePrimary', color: colorScheme.inversePrimary, - onColor: colorScheme.primary, + onColor: colorScheme.inverseSurface, size: _equalThreeChipSize, + showValue: showColorValue, ), ColorChip( label: 'shadow', color: colorScheme.shadow, size: _equalThreeChipSize, + showValue: showColorValue, ), ], ), @@ -363,18 +414,21 @@ class ColorSchemeView extends StatelessWidget { color: colorScheme.background, onColor: colorScheme.onBackground, size: _equalThreeChipSize, + showValue: showColorValue, ), ColorChip( label: 'onBackground\n(deprecated)', color: colorScheme.onBackground, onColor: colorScheme.background, size: _equalThreeChipSize, + showValue: showColorValue, ), ColorChip( label: 'surfaceVariant\n(deprecated)', color: colorScheme.surfaceVariant, onColor: colorScheme.onSurfaceVariant, size: _equalThreeChipSize, + showValue: showColorValue, ), ], ), @@ -413,6 +467,7 @@ class ColorChip extends StatelessWidget { this.onColor, this.size, this.copyEnabled = true, + this.showValue = false, }); final Color color; @@ -420,6 +475,7 @@ class ColorChip extends StatelessWidget { final String label; final Size? size; final bool copyEnabled; + final bool showValue; static Color _contrastColor(Color color) { final Brightness brightness = ThemeData.estimateBrightnessForColor(color); @@ -454,12 +510,12 @@ class ColorChip extends StatelessWidget { } : null, child: Padding( - padding: const EdgeInsets.all(10), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), child: Row( children: [ Expanded( child: Text( - label, + showValue ? '$label\n#${color.hexCode}' : label, style: TextStyle(color: labelColor, fontSize: 11), ), ), diff --git a/example/lib/home/views/pages/home_page.dart b/example/lib/home/views/pages/home_page.dart index 4b05065..d97ff34 100644 --- a/example/lib/home/views/pages/home_page.dart +++ b/example/lib/home/views/pages/home_page.dart @@ -54,16 +54,25 @@ class HomePage extends StatelessWidget { ), body: ListView( children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ShowInputColors(controller: controller), + ), + const ListTile( + dense: true, + title: Text('At least a primary key color is always needed to ' + 'seed the ColorScheme. Tap to change colors.'), + ), FlexTonesPopupMenu( - title: 'Selected scheme variant:', + title: 'Used ColorScheme generation strategy:', variant: controller.usedVariant, onChanged: controller.setUsedTone, contentPadding: const EdgeInsetsDirectional.only(start: 16, end: 24), ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 16), - child: ColorSchemeView(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ColorSchemeView(showColorValue: controller.showColorValue), ), ListTileSlider( dense: true, @@ -88,14 +97,6 @@ class HomePage extends StatelessWidget { ' scheme variant configuration info:'), subtitle: Text('${controller.usedVariant.configDetails}\n'), ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: ShowInputColors(controller: controller), - ), - const ListTile( - dense: true, - title: Text('Primary key color is always used to seed the ' - 'ColorScheme. Tap to change colors.')), const Divider(), if (controller.usedVariant.isFlutterScheme) const ListTile( @@ -192,7 +193,13 @@ class HomePage extends StatelessWidget { ? null : controller.setKeepDarkSurfaceColorsBlack, ), - const SizedBox(height: 16), + const Divider(), + SwitchListTile( + dense: true, + title: const Text('Show color value in the ColorScheme view'), + value: controller.showColorValue, + onChanged: controller.setShowColorValue, + ), const Divider(), const ListTile( title: Text('Widget showcase, using Material default styles')), diff --git a/example/lib/home/views/widgets/show_input_colors.dart b/example/lib/home/views/widgets/show_input_colors.dart index a9b2a31..8c89ce7 100644 --- a/example/lib/home/views/widgets/show_input_colors.dart +++ b/example/lib/home/views/widgets/show_input_colors.dart @@ -85,7 +85,7 @@ class ShowInputColors extends StatelessWidget { Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Text( - 'Seed key colors', + 'Key colors used to seed the ColorScheme', style: theme.textTheme.titleMedium, ), ), diff --git a/example/lib/theme/controllers/theme_controller.dart b/example/lib/theme/controllers/theme_controller.dart index b388423..4957845 100644 --- a/example/lib/theme/controllers/theme_controller.dart +++ b/example/lib/theme/controllers/theme_controller.dart @@ -17,6 +17,15 @@ class ThemeController with ChangeNotifier { if (notify) notifyListeners(); } + bool _showColorValue = true; + bool get showColorValue => _showColorValue; + void setShowColorValue(bool? value, [bool notify = true]) { + if (value == null) return; + if (value == _showColorValue) return; + _showColorValue = value; + if (notify) notifyListeners(); + } + ThemeMode _themeMode = ThemeMode.light; ThemeMode get themeMode => _themeMode; void setThemeMode(ThemeMode? value, [bool notify = true]) { From 1e4f312dfd921b757a5da6d1850a629ce07f6fc1 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Thu, 20 Jun 2024 20:58:48 +0300 Subject: [PATCH 17/37] Updated the descriptions of `FlexSchemeVariant`. --- lib/src/flex/flex_scheme_variant.dart | 87 ++++++++++++-------------- lib/src/mcu/scheme/scheme_rainbow.dart | 4 ++ 2 files changed, 44 insertions(+), 47 deletions(-) diff --git a/lib/src/flex/flex_scheme_variant.dart b/lib/src/flex/flex_scheme_variant.dart index 807becb..77bcfe5 100644 --- a/lib/src/flex/flex_scheme_variant.dart +++ b/lib/src/flex/flex_scheme_variant.dart @@ -62,8 +62,7 @@ enum FlexSchemeVariant { /// 48. It uses chroma 6 on neutral palette, where the previous one used 4. tonalSpot( variantName: 'Tonal spot', - description: 'Default for Material-3 theme colors. Results in pastel ' - 'palettes with low chroma.', + description: 'Default Material-3 pastel colors with low chroma', configDetails: 'Primary - Chroma from key color, but min 36\n' 'Secondary - Chroma set to 16\n' 'Tertiary - Chroma set to 24\n' @@ -86,8 +85,8 @@ enum FlexSchemeVariant { /// TemperatureCache. It also maintains constant appearance. fidelity( variantName: 'Fidelity', - description: 'The resulting color palettes match seed color, also when ' - 'the seed color is very bright and using high chroma.', + description: 'Color palettes match the seed color, also when ' + 'it is bright and uses high chroma', configDetails: 'Primary - Chroma from key color\n' 'Secondary - Max of: chroma from key -32 or *0.5\n' 'Tertiary - TemperatureCache complement\n' @@ -103,7 +102,7 @@ enum FlexSchemeVariant { /// All colors are grayscale, no chroma. monochrome( variantName: 'Monochrome', - description: 'All colors are grayscale, with no chroma.', + description: 'All colors are grayscale, no chroma', configDetails: 'Primary - Chroma 0\n' 'Secondary - Chroma 0\n' 'Tertiary - Chroma 0\n' @@ -119,7 +118,7 @@ enum FlexSchemeVariant { /// A scheme that is near grayscale. neutral( variantName: 'Neutral', - description: 'Close to grayscale, but with a hint of chroma.', + description: 'Close to grayscale, only a hint of chroma', configDetails: 'Primary - Chroma 12\n' 'Secondary - Chroma 8\n' 'Tertiary - Chroma 16\n' @@ -139,8 +138,7 @@ enum FlexSchemeVariant { /// tokens should alter their tone to match the palette vibrancy. vibrant( variantName: 'Vibrant', - description: 'Maxed out colorfulness at each position in the primary ' - 'palette', + description: 'Maxed out colorfulness for all primaries', configDetails: 'Primary - Chroma 200\n' 'Secondary - Chroma 24\n' 'Tertiary - Chroma 32\n' @@ -157,8 +155,8 @@ enum FlexSchemeVariant { /// The primary palette's hue is different from the seed color, for variety. expressive( variantName: 'Expressive', - description: 'Primary palette hue is intentionally different from the ' - 'seed color.', + description: 'Primary hue is intentionally different from the ' + 'seed color', configDetails: 'Primary - Hue+240, Chroma 40\n' 'Secondary - Hue rotations, Chroma 24\n' 'Tertiary - Hue rotations, Chroma 32\n' @@ -186,8 +184,8 @@ enum FlexSchemeVariant { /// increasing hue. It also maintains constant appearance. content( variantName: 'Content', - description: 'Tokens and palettes match the seed color. Good for image ' - 'color extracted seed color.', + description: 'Color palettes match seed color, use with image ' + 'extracted seed color', configDetails: 'Primary - Chroma from key color\n' 'Secondary - Max of: chroma from key -32 or *0.5\n' 'Tertiary - TemperatureCache analogous last\n' @@ -200,12 +198,10 @@ enum FlexSchemeVariant { isFlutterScheme: true, ), - /// A playful theme - the seed color's hue does not appear in the theme. + /// A playful theme - the seed color's chroma is fixed. rainbow( variantName: 'Rainbow', - // TODO(rydmike): But it does, but this is what MCU and SDK says, so... - description: "A playful theme. The seed color's hue does not appear in " - 'the theme.', + description: "A playful theme, the seed color's chroma is fixed", configDetails: 'Primary - Chroma 48\n' 'Secondary - Chroma 16\n' 'Tertiary - Hue +60, Chroma 24\n' @@ -221,8 +217,7 @@ enum FlexSchemeVariant { /// A playful theme - the seed color's hue does not appear in the theme. fruitSalad( variantName: 'Fruit salad', - description: "A playful theme. The seed color's hue does not appear in " - 'the theme.', + description: "A playful theme, the seed color's hue is not in the theme", configDetails: 'Primary - Hue -50, Chroma 12\n' 'Secondary - Hue -50, Chroma 36\n' 'Tertiary - Chroma 36\n' @@ -254,7 +249,7 @@ enum FlexSchemeVariant { /// Flutter SDK DynamicSchemeVariant and MCU do not provide that feature-set. material( variantName: 'Material-3', - description: 'Default Material 3 design tone map and chroma setup', + description: 'Material-3 design tones and chroma setup', configDetails: 'Primary - Chroma from key color, but min 36\n' 'Secondary - Chroma set to 16\n' 'Tertiary - Chroma set to 24\n' @@ -281,8 +276,8 @@ enum FlexSchemeVariant { /// need and want to use the older Material-3 seed generation setup used in /// Flutter 3.19 and earlier versions. material3Legacy( - variantName: 'Material-3 Legacy', - description: 'Legacy Material 3 design tone map and chroma setup, used ' + variantName: 'Material-3 legacy', + description: 'Legacy Material-3 tones and chroma, used ' 'before Flutter 3.22', configDetails: 'Primary - Chroma from key color, but min 48\n' 'Secondary - Chroma set to 16\n' @@ -303,7 +298,7 @@ enum FlexSchemeVariant { /// in Material 3 default setup. soft( variantName: 'Soft', - description: 'Softer and more earth like tones than Material 3 defaults', + description: 'Softer and more earth like tones than Material-3 defaults', configDetails: 'Primary - Chroma set to 30\n' 'Secondary - Chroma set to 14\n' 'Tertiary - Chroma set to 20\n' @@ -326,7 +321,7 @@ enum FlexSchemeVariant { /// to their used key colors. vivid( variantName: 'Vivid', - description: 'More Vivid colors than Material 3 defaults', + description: 'More vivid colors than Material-3 defaults', configDetails: 'Primary - Chroma from key color, but min 50\n' 'Secondary - Chroma from key color\n' 'Tertiary - Chroma from key color\n' @@ -358,9 +353,7 @@ enum FlexSchemeVariant { /// backgrounds as starting points. vividSurfaces( variantName: 'Vivid surfaces', - description: 'Like Vivid, but with more colorful containers, onColors and ' - 'surface tones. Creates alpha blend like effect without ' - 'any blend level', + description: 'Like Vivid, but more colorful surfaces and containers', configDetails: 'Primary - Chroma from key color, but min 50\n' 'Secondary - Chroma from key color\n' 'Tertiary - Chroma from key color\n' @@ -391,7 +384,7 @@ enum FlexSchemeVariant { /// themes as options for the high contrast accessibility themes. highContrast( variantName: 'High contrast', - description: 'High contrast version, may be useful for accessibility', + description: 'High contrast theme, useful for accessibility', configDetails: 'Primary - Chroma from key color, but min 65\n' 'Secondary - Chroma from key color, but min 55\n' 'Tertiary - Chroma from key color, but min 55\n' @@ -408,8 +401,7 @@ enum FlexSchemeVariant { /// contrast version of selected ColorsSchemes. ultraContrast( variantName: 'Ultra contrast', - description: 'Ultra high contrast version, useful for accessibility, ' - 'less colorful than high contrast, especially dark mode', + description: 'Ultra high contrast theme, useful for accessibility', configDetails: 'Primary - Chroma from key color, but min 60\n' 'Secondary - Chroma from key color, but min 70\n' 'Tertiary - Chroma from key color, but min 65\n' @@ -426,7 +418,7 @@ enum FlexSchemeVariant { /// colorful ColorsSchemes. jolly( variantName: 'Jolly', - description: 'Jolly color tones with more chroma and pop in them', + description: 'Jolly colors with more chroma and pop', configDetails: 'Primary - Chroma from key color, but min 55\n' 'Secondary - Chroma from key color, but min 40\n' 'Tertiary - Chroma set to 40\n' @@ -442,10 +434,13 @@ enum FlexSchemeVariant { /// A tonal palette extraction setup that results in M3 like /// ColorsSchemes with chroma like [FlexTones.vividSurfaces], but /// tone mapping surface and background are swapped. + /// + /// This variant and its used [FlexTones.vividBackground] will be + /// deprecated when Flutter SDK stops using the deprecated background color. vividBackground( variantName: 'Vivid background', - description: 'Like Vivid surfaces, but with tone mapping for surface ' - 'and background swapped', + description: 'Like Vivid surfaces, but tone mapping for surface ' + 'and deprecated background color swapped', configDetails: 'Primary - Chroma from key color, but min 50\n' 'Secondary - Chroma from key color\n' 'Tertiary - Chroma from key color\n' @@ -462,15 +457,16 @@ enum FlexSchemeVariant { /// from primary if no ARGB key color is provided for tertiary palette. /// /// This setup will if only one seed color is used, produce a slightly more - /// chromatic color set than [FlexTones.material], since it does not rotate - /// hue from primary to get hue for tertiary, it will create a color - /// scheme using tonal palettes that are based on the same hue, but with - /// different chroma. In simple terms, all colors are shades of the provided - /// key color to seed the tonal palettes. We can get a nice one hue - /// toned theme with this configuration. + /// chromatic color set than [FlexTones.material]. + /// + /// Since it does not rotate hue from primary to get hue for tertiary, + /// it will create a color scheme using tonal palettes that are all based on + /// the same hue, but with different chroma. In simple terms, all colors are + /// shades of the provided key color to seed the tonal palettes. With this + /// setup we can get a nice one color tone based theme. oneHue( variantName: 'One hue', - description: 'If only primary key color given, scheme uses only one hue', + description: 'With only a primary seed the theme has only one hue', configDetails: 'Primary - Chroma from key color, but min 55\n' 'Secondary - Chroma set to 26\n' 'Tertiary - Chroma set to 36, no Hue rotation\n' @@ -491,10 +487,9 @@ enum FlexSchemeVariant { /// surface and background tone 6. candyPop( variantName: 'Candy pop', - description: 'A high contrast color scheme, useful for accessible themes, ' - 'with colors that pop like candy. Keeps the background and surface ' - 'white in light mode, and only a slight tint in dark mode. Neutrals ' - 'have very low chroma', + description: 'High contrast candy like colors. Background and surface are ' + 'white in light mode an only only a slight primary tint in dark mode. ' + 'Neutrals have low chroma', configDetails: 'Primary - Chroma from key color, but min 60\n' 'Secondary - Chroma from key color, but min 44\n' 'Tertiary - Chroma from key color, but min 50\n' @@ -517,10 +512,8 @@ enum FlexSchemeVariant { /// (3 and 6). Dark mode uses dark surface and background tone 6. chroma( variantName: 'Chroma', - description: - 'A color scheme that follows chroma of each used seed color. Useful ' - 'for manual control of pop or low chromacity. It uses low ' - 'surface tint and neutrals with medium chroma', + description: 'Colors follow chroma of each seed color. Useful ' + 'for manual control of chromacity. Neutrals have low chroma', configDetails: 'Primary - Chroma from key color, min 0\n' 'Secondary - Chroma from key color, min 0\n' 'Tertiary - Chroma from key color, min 0\n' diff --git a/lib/src/mcu/scheme/scheme_rainbow.dart b/lib/src/mcu/scheme/scheme_rainbow.dart index e3f1e95..fd0d8b3 100644 --- a/lib/src/mcu/scheme/scheme_rainbow.dart +++ b/lib/src/mcu/scheme/scheme_rainbow.dart @@ -18,6 +18,10 @@ import '../hct/hct.dart'; import '../palettes/tonal_palette.dart'; import '../utils/math_utils.dart'; +// TODO(rydmike): The doc below is what the MCU source says and does, BUT +// the hue actually matches the source hue, but chroma is locked to 45. +// Not sure what is up with the orig source code, but this is how it is now. + /// A playful theme - the source color's hue does not appear in the theme. class SchemeRainbow extends DynamicScheme { /// Default SchemeRainbow constructor. From 78d3bb77d57257f2d40b078f14252d1627452eb3 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Thu, 20 Jun 2024 20:58:57 +0300 Subject: [PATCH 18/37] Update CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebb8160..e5532de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. ## 2.1.0 -**June 17, 2024** +**June 20, 2024** * **NEW** * Added support for `contrastLevel` to `SeedColorScheme.fromSeeds`. This allows you to set the contrast level of the generated color scheme, when using `SeedColorScheme.fromSeeds` with the `variant` property. The `contrastLevel` parameter indicates the contrast level between color pairs, such as `primary` and `onPrimary`.The value 0.0 is the default (normal); -1.0 is the lowest; 1.0 is the highest. From Material Design guideline, the medium and high contrast, correspond to 0.5 and 1.0 respectively. @@ -20,6 +20,7 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. * **CHANGE** * Revert Flutter constraint back to `>=3.22.0`. Since beta and master are now on `3.23.0` or higher, this constraint can now be used by master and beta channels without any issue. + * Updated the descriptions of `FlexSchemeVariant`. * **FIX** From 10f1a9ef3f504fd7b8184d15e1cf270e1d815109 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Thu, 20 Jun 2024 21:01:10 +0300 Subject: [PATCH 19/37] Revise and improve the default example --- CHANGELOG.md | 1 + .../views/universal/color_scheme_view.dart | 789 +++++++++--------- .../views/universal/list_tile_reveal.dart | 171 ++++ example/lib/home/views/pages/home_page.dart | 111 +-- .../views/widgets/flex_tones_popup_menu.dart | 20 +- .../widgets/show_color_scheme_colors.dart | 215 ----- .../home/views/widgets/show_input_colors.dart | 394 ++++++--- .../theme/controllers/theme_controller.dart | 36 + example/lib/theme/model/app_theme.dart | 31 + 9 files changed, 974 insertions(+), 794 deletions(-) create mode 100644 example/lib/core/views/universal/list_tile_reveal.dart delete mode 100644 example/lib/home/views/widgets/show_color_scheme_colors.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index e5532de..1f4e1d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. * **CHANGE** * Revert Flutter constraint back to `>=3.22.0`. Since beta and master are now on `3.23.0` or higher, this constraint can now be used by master and beta channels without any issue. * Updated the descriptions of `FlexSchemeVariant`. + * The default example was significantly revised to include support for the new features and to show old features not demonstrated before. * **FIX** diff --git a/example/lib/core/views/universal/color_scheme_view.dart b/example/lib/core/views/universal/color_scheme_view.dart index c1a6b81..4e3854e 100644 --- a/example/lib/core/views/universal/color_scheme_view.dart +++ b/example/lib/core/views/universal/color_scheme_view.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flex_seed_scheme/flex_seed_scheme.dart'; import 'package:flutter/material.dart'; import '../../utils/flex_color_extension.dart'; @@ -15,9 +16,15 @@ const Size _equalThreeChipSize = Size(160, 57.333); /// It also allows copying the color values to the clipboard by tapping the /// scheme color. class ColorSchemeView extends StatelessWidget { - const ColorSchemeView({super.key, this.scheme, this.showColorValue = false}); + const ColorSchemeView({ + super.key, + this.scheme, + required this.tones, + this.showColorValue = false, + }); final ColorScheme? scheme; + final FlexTones tones; final bool showColorValue; @override @@ -43,7 +50,6 @@ class ColorSchemeView extends StatelessWidget { side: BorderSide(color: colorScheme.outlineVariant), ); } - return Theme( data: Theme.of(context).copyWith( cardTheme: CardTheme.of(context).copyWith( @@ -51,386 +57,423 @@ class ColorSchemeView extends StatelessWidget { shape: border, ), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Wrap( + spacing: spacing, + runSpacing: spacing, children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Text( - 'ColorScheme Colors', - style: theme.textTheme.titleMedium, + ColorGroup(children: [ + ColorChip( + label: 'primary', + color: colorScheme.primary, + onColor: colorScheme.onPrimary, + size: _colorChipSize, + showValue: showColorValue, + tone: 'P:${tones.primaryTone}', + ), + ColorChip( + label: 'onPrimary', + color: colorScheme.onPrimary, + onColor: colorScheme.primary, + showValue: showColorValue, + tone: 'P:${tones.onPrimaryTone}', + ), + ColorChip( + label: 'primaryContainer', + color: colorScheme.primaryContainer, + onColor: colorScheme.onPrimaryContainer, + size: _colorChipSize, + showValue: showColorValue, + tone: 'P:${tones.primaryContainerTone}', + ), + ColorChip( + label: 'onPrimaryContainer', + color: colorScheme.onPrimaryContainer, + onColor: colorScheme.primaryContainer, + showValue: showColorValue, + tone: 'P:${tones.onPrimaryContainerTone}', + ), + ]), + ColorGroup(children: [ + ColorChip( + label: 'primaryFixed', + color: colorScheme.primaryFixed, + onColor: colorScheme.onPrimaryFixed, + size: _colorChipSize, + showValue: showColorValue, + tone: 'P:${tones.primaryFixedTone}', + ), + ColorChip( + label: 'onPrimaryFixed', + color: colorScheme.onPrimaryFixed, + onColor: colorScheme.primaryFixed, + showValue: showColorValue, + tone: 'P:${tones.onPrimaryFixedTone}', + ), + ColorChip( + label: 'primaryFixedDim', + color: colorScheme.primaryFixedDim, + onColor: colorScheme.onPrimaryFixedVariant, + size: _colorChipSize, + showValue: showColorValue, + tone: 'P:${tones.primaryFixedDimTone}', + ), + ColorChip( + label: 'onPrimaryFixedVariant', + color: colorScheme.onPrimaryFixedVariant, + onColor: colorScheme.primaryFixedDim, + showValue: showColorValue, + tone: 'P:${tones.onPrimaryFixedVariantTone}', + ), + ]), + ColorGroup(children: [ + ColorChip( + label: 'secondary', + color: colorScheme.secondary, + onColor: colorScheme.onSecondary, + size: _colorChipSize, + showValue: showColorValue, + tone: 'S:${tones.secondaryTone}', + ), + ColorChip( + label: 'onSecondary', + color: colorScheme.onSecondary, + onColor: colorScheme.secondary, + showValue: showColorValue, + tone: 'S:${tones.onSecondaryTone}', + ), + ColorChip( + label: 'secondaryContainer', + color: colorScheme.secondaryContainer, + onColor: colorScheme.onSecondaryContainer, + size: _colorChipSize, + showValue: showColorValue, + tone: 'S:${tones.secondaryContainerTone}', + ), + ColorChip( + label: 'onSecondaryContainer', + color: colorScheme.onSecondaryContainer, + onColor: colorScheme.secondaryContainer, + showValue: showColorValue, + tone: 'S:${tones.onSecondaryContainerTone}', ), + ]), + ColorGroup(children: [ + ColorChip( + label: 'secondaryFixed', + color: colorScheme.secondaryFixed, + onColor: colorScheme.onSecondaryFixed, + size: _colorChipSize, + showValue: showColorValue, + tone: 'S:${tones.secondaryFixedTone}', + ), + ColorChip( + label: 'onSecondaryFixed', + color: colorScheme.onSecondaryFixed, + onColor: colorScheme.secondaryFixed, + showValue: showColorValue, + tone: 'S:${tones.onSecondaryFixedTone}', + ), + ColorChip( + label: 'secondaryFixedDim', + color: colorScheme.secondaryFixedDim, + onColor: colorScheme.onSecondaryFixedVariant, + size: _colorChipSize, + showValue: showColorValue, + tone: 'S:${tones.secondaryFixedDimTone}', + ), + ColorChip( + label: 'onSecondaryFixedVariant', + color: colorScheme.onSecondaryFixedVariant, + onColor: colorScheme.secondaryFixedDim, + showValue: showColorValue, + tone: 'S:${tones.onSecondaryFixedVariantTone}', + ), + ]), + ColorGroup( + children: [ + ColorChip( + label: 'tertiary', + color: colorScheme.tertiary, + onColor: colorScheme.onTertiary, + size: _colorChipSize, + showValue: showColorValue, + tone: 'T:${tones.tertiaryTone}', + ), + ColorChip( + label: 'onTertiary', + color: colorScheme.onTertiary, + onColor: colorScheme.tertiary, + showValue: showColorValue, + tone: 'T:${tones.onTertiaryTone}', + ), + ColorChip( + label: 'tertiaryContainer', + color: colorScheme.tertiaryContainer, + onColor: colorScheme.onTertiaryContainer, + size: _colorChipSize, + showValue: showColorValue, + tone: 'T:${tones.tertiaryContainerTone}', + ), + ColorChip( + label: 'onTertiaryContainer', + color: colorScheme.onTertiaryContainer, + onColor: colorScheme.tertiaryContainer, + showValue: showColorValue, + tone: 'T:${tones.onTertiaryContainerTone}', + ), + ], ), - Wrap( - spacing: spacing, - runSpacing: spacing, + ColorGroup(children: [ + ColorChip( + label: 'tertiaryFixed', + color: colorScheme.tertiaryFixed, + onColor: colorScheme.onTertiaryFixed, + size: _colorChipSize, + showValue: showColorValue, + tone: 'T:${tones.tertiaryFixedTone}', + ), + ColorChip( + label: 'onTertiaryFixed', + color: colorScheme.onTertiaryFixed, + onColor: colorScheme.tertiaryFixed, + showValue: showColorValue, + tone: 'T:${tones.onTertiaryFixedTone}', + ), + ColorChip( + label: 'tertiaryFixedDim', + color: colorScheme.tertiaryFixedDim, + onColor: colorScheme.onTertiaryFixedVariant, + size: _colorChipSize, + showValue: showColorValue, + tone: 'T:${tones.tertiaryFixedDimTone}', + ), + ColorChip( + label: 'onTertiaryFixedVariant', + color: colorScheme.onTertiaryFixedVariant, + onColor: colorScheme.tertiaryFixedDim, + showValue: showColorValue, + tone: 'T:${tones.onTertiaryFixedVariantTone}', + ), + ]), + ColorGroup( children: [ - ColorGroup(children: [ - ColorChip( - label: 'primary', - color: colorScheme.primary, - onColor: colorScheme.onPrimary, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onPrimary', - color: colorScheme.onPrimary, - onColor: colorScheme.primary, - showValue: showColorValue, - ), - ColorChip( - label: 'primaryContainer', - color: colorScheme.primaryContainer, - onColor: colorScheme.onPrimaryContainer, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onPrimaryContainer', - color: colorScheme.onPrimaryContainer, - onColor: colorScheme.primaryContainer, - showValue: showColorValue, - ), - ]), - ColorGroup(children: [ - ColorChip( - label: 'primaryFixed', - color: colorScheme.primaryFixed, - onColor: colorScheme.onPrimaryFixed, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onPrimaryFixed', - color: colorScheme.onPrimaryFixed, - onColor: colorScheme.primaryFixed, - showValue: showColorValue, - ), - ColorChip( - label: 'primaryFixedDim', - color: colorScheme.primaryFixedDim, - onColor: colorScheme.onPrimaryFixedVariant, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onPrimaryFixedVariant', - color: colorScheme.onPrimaryFixedVariant, - onColor: colorScheme.primaryFixedDim, - showValue: showColorValue, - ), - ]), - ColorGroup(children: [ - ColorChip( - label: 'secondary', - color: colorScheme.secondary, - onColor: colorScheme.onSecondary, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onSecondary', - color: colorScheme.onSecondary, - onColor: colorScheme.secondary, - showValue: showColorValue, - ), - ColorChip( - label: 'secondaryContainer', - color: colorScheme.secondaryContainer, - onColor: colorScheme.onSecondaryContainer, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onSecondaryContainer', - color: colorScheme.onSecondaryContainer, - onColor: colorScheme.secondaryContainer, - showValue: showColorValue, - ), - ]), - ColorGroup(children: [ - ColorChip( - label: 'secondaryFixed', - color: colorScheme.secondaryFixed, - onColor: colorScheme.onSecondaryFixed, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onSecondaryFixed', - color: colorScheme.onSecondaryFixed, - onColor: colorScheme.secondaryFixed, - showValue: showColorValue, - ), - ColorChip( - label: 'secondaryFixedDim', - color: colorScheme.secondaryFixedDim, - onColor: colorScheme.onSecondaryFixedVariant, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onSecondaryFixedVariant', - color: colorScheme.onSecondaryFixedVariant, - onColor: colorScheme.secondaryFixedDim, - showValue: showColorValue, - ), - ]), - ColorGroup( - children: [ - ColorChip( - label: 'tertiary', - color: colorScheme.tertiary, - onColor: colorScheme.onTertiary, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onTertiary', - color: colorScheme.onTertiary, - onColor: colorScheme.tertiary, - showValue: showColorValue, - ), - ColorChip( - label: 'tertiaryContainer', - color: colorScheme.tertiaryContainer, - onColor: colorScheme.onTertiaryContainer, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onTertiaryContainer', - color: colorScheme.onTertiaryContainer, - onColor: colorScheme.tertiaryContainer, - showValue: showColorValue, - ), - ], + ColorChip( + label: 'error', + color: colorScheme.error, + onColor: colorScheme.onError, + size: _colorChipSize, + showValue: showColorValue, + tone: 'E:${tones.errorTone}', ), - ColorGroup(children: [ - ColorChip( - label: 'tertiaryFixed', - color: colorScheme.tertiaryFixed, - onColor: colorScheme.onTertiaryFixed, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onTertiaryFixed', - color: colorScheme.onTertiaryFixed, - onColor: colorScheme.tertiaryFixed, - showValue: showColorValue, - ), - ColorChip( - label: 'tertiaryFixedDim', - color: colorScheme.tertiaryFixedDim, - onColor: colorScheme.onTertiaryFixedVariant, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onTertiaryFixedVariant', - color: colorScheme.onTertiaryFixedVariant, - onColor: colorScheme.tertiaryFixedDim, - showValue: showColorValue, - ), - ]), - ColorGroup( - children: [ - ColorChip( - label: 'error', - color: colorScheme.error, - onColor: colorScheme.onError, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onError', - color: colorScheme.onError, - onColor: colorScheme.error, - showValue: showColorValue, - ), - ColorChip( - label: 'errorContainer', - color: colorScheme.errorContainer, - onColor: colorScheme.onErrorContainer, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onErrorContainer', - color: colorScheme.onErrorContainer, - onColor: colorScheme.errorContainer, - showValue: showColorValue, - ), - ], + ColorChip( + label: 'onError', + color: colorScheme.onError, + onColor: colorScheme.error, + showValue: showColorValue, + tone: 'E:${tones.onErrorTone}', ), - ColorGroup( - children: [ - ColorChip( - label: 'surface', - color: colorScheme.surface, - onColor: colorScheme.onSurface, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onSurface', - color: colorScheme.onSurface, - onColor: colorScheme.surface, - showValue: showColorValue, - ), - ColorChip( - label: 'surfaceContainer', - color: colorScheme.surfaceContainer, - onColor: colorScheme.onSurfaceVariant, - size: _colorChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onSurfaceVariant', - color: colorScheme.onSurfaceVariant, - onColor: colorScheme.surfaceContainer, - showValue: showColorValue, - ), - ], + ColorChip( + label: 'errorContainer', + color: colorScheme.errorContainer, + onColor: colorScheme.onErrorContainer, + size: _colorChipSize, + showValue: showColorValue, + tone: 'E:${tones.errorContainerTone}', ), - ColorGroup( - children: [ - ColorChip( - label: 'surfaceContainerLowest', - color: colorScheme.surfaceContainerLowest, - onColor: colorScheme.onSurface, - size: _equalFourChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'surfaceContainerLow', - color: colorScheme.surfaceContainerLow, - onColor: colorScheme.onSurface, - size: _equalFourChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'surfaceContainerHigh', - color: colorScheme.surfaceContainerHigh, - onColor: colorScheme.onSurface, - size: _equalFourChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'surfaceContainerHighest', - color: colorScheme.surfaceContainerHighest, - onColor: colorScheme.onSurface, - size: _equalFourChipSize, - showValue: showColorValue, - ), - ], + ColorChip( + label: 'onErrorContainer', + color: colorScheme.onErrorContainer, + onColor: colorScheme.errorContainer, + showValue: showColorValue, + tone: 'E:${tones.onErrorContainerTone}', ), - ColorGroup( - children: [ - ColorChip( - label: 'surfaceDim', - color: colorScheme.surfaceDim, - onColor: colorScheme.onSurface, - size: _equalFourChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'surfaceBright', - color: colorScheme.surfaceBright, - onColor: colorScheme.onSurface, - size: _equalFourChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'inverseSurface', - color: colorScheme.inverseSurface, - onColor: colorScheme.onInverseSurface, - size: _equalFourChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onInverseSurface', - color: colorScheme.onInverseSurface, - onColor: colorScheme.inverseSurface, - size: _equalFourChipSize, - showValue: showColorValue, - ), - ], + ], + ), + ColorGroup( + children: [ + ColorChip( + label: 'surface', + color: colorScheme.surface, + onColor: colorScheme.onSurface, + size: _colorChipSize, + showValue: showColorValue, + tone: 'N:${tones.surfaceTone}', ), - ColorGroup( - children: [ - ColorChip( - label: 'outline', - color: colorScheme.outline, - onColor: colorScheme.surface, - size: _equalThreeChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'outlineVariant', - color: colorScheme.outlineVariant, - size: _equalThreeChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'scrim', - color: colorScheme.scrim, - size: _equalThreeChipSize, - showValue: showColorValue, - ), - ], + ColorChip( + label: 'onSurface', + color: colorScheme.onSurface, + onColor: colorScheme.surface, + showValue: showColorValue, + tone: 'N:${tones.onSurfaceTone}', ), - ColorGroup( - children: [ - ColorChip( - label: 'surfaceTint', - color: colorScheme.surfaceTint, - size: _equalThreeChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'inversePrimary', - color: colorScheme.inversePrimary, - onColor: colorScheme.inverseSurface, - size: _equalThreeChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'shadow', - color: colorScheme.shadow, - size: _equalThreeChipSize, - showValue: showColorValue, - ), - ], + ColorChip( + label: 'surfaceContainer', + color: colorScheme.surfaceContainer, + onColor: colorScheme.onSurfaceVariant, + size: _colorChipSize, + showValue: showColorValue, + tone: 'N:${tones.surfaceContainerTone}', ), - // TODO(rydmike): Remove deprecated colors in Flutter 3.25. - // Show the deprecated colors. - ColorGroup( - children: [ - ColorChip( - label: 'background\n(deprecated)', - color: colorScheme.background, - onColor: colorScheme.onBackground, - size: _equalThreeChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'onBackground\n(deprecated)', - color: colorScheme.onBackground, - onColor: colorScheme.background, - size: _equalThreeChipSize, - showValue: showColorValue, - ), - ColorChip( - label: 'surfaceVariant\n(deprecated)', - color: colorScheme.surfaceVariant, - onColor: colorScheme.onSurfaceVariant, - size: _equalThreeChipSize, - showValue: showColorValue, - ), - ], + ColorChip( + label: 'onSurfaceVariant', + color: colorScheme.onSurfaceVariant, + onColor: colorScheme.surfaceContainer, + showValue: showColorValue, + tone: 'NV:${tones.onSurfaceVariantTone}', + ), + ], + ), + ColorGroup( + children: [ + ColorChip( + label: 'surfaceContainerLowest', + color: colorScheme.surfaceContainerLowest, + onColor: colorScheme.onSurface, + size: _equalFourChipSize, + showValue: showColorValue, + tone: 'N:${tones.surfaceContainerLowestTone}', + ), + ColorChip( + label: 'surfaceContainerLow', + color: colorScheme.surfaceContainerLow, + onColor: colorScheme.onSurface, + size: _equalFourChipSize, + showValue: showColorValue, + tone: 'N:${tones.surfaceContainerLowTone}', + ), + ColorChip( + label: 'surfaceContainerHigh', + color: colorScheme.surfaceContainerHigh, + onColor: colorScheme.onSurface, + size: _equalFourChipSize, + showValue: showColorValue, + tone: 'N:${tones.surfaceContainerHighTone}', + ), + ColorChip( + label: 'surfaceContainerHighest', + color: colorScheme.surfaceContainerHighest, + onColor: colorScheme.onSurface, + size: _equalFourChipSize, + showValue: showColorValue, + tone: 'N:${tones.surfaceContainerHighestTone}', + ), + ], + ), + ColorGroup( + children: [ + ColorChip( + label: 'surfaceDim', + color: colorScheme.surfaceDim, + onColor: colorScheme.onSurface, + size: _equalFourChipSize, + showValue: showColorValue, + tone: 'N:${tones.surfaceDimTone}', + ), + ColorChip( + label: 'surfaceBright', + color: colorScheme.surfaceBright, + onColor: colorScheme.onSurface, + size: _equalFourChipSize, + showValue: showColorValue, + tone: 'N:${tones.surfaceBrightTone}', + ), + ColorChip( + label: 'inverseSurface', + color: colorScheme.inverseSurface, + onColor: colorScheme.onInverseSurface, + size: _equalFourChipSize, + showValue: showColorValue, + tone: 'N:${tones.inverseSurfaceTone}', + ), + ColorChip( + label: 'onInverseSurface', + color: colorScheme.onInverseSurface, + onColor: colorScheme.inverseSurface, + size: _equalFourChipSize, + showValue: showColorValue, + tone: 'N:${tones.onInverseSurfaceTone}', + ), + ], + ), + ColorGroup( + children: [ + ColorChip( + label: 'outline', + color: colorScheme.outline, + onColor: colorScheme.surface, + size: _equalThreeChipSize, + showValue: showColorValue, + tone: 'N:${tones.outlineTone}', + ), + ColorChip( + label: 'outlineVariant', + color: colorScheme.outlineVariant, + size: _equalThreeChipSize, + showValue: showColorValue, + tone: 'NV:${tones.outlineVariantTone}', + ), + ColorChip( + label: 'scrim', + color: colorScheme.scrim, + size: _equalThreeChipSize, + showValue: showColorValue, + tone: 'N:${tones.scrimTone}', + ), + ], + ), + ColorGroup( + children: [ + ColorChip( + label: 'surfaceTint', + color: colorScheme.surfaceTint, + size: _equalThreeChipSize, + showValue: showColorValue, + tone: 'P:${tones.surfaceTintTone}', + ), + ColorChip( + label: 'inversePrimary', + color: colorScheme.inversePrimary, + onColor: colorScheme.inverseSurface, + size: _equalThreeChipSize, + showValue: showColorValue, + tone: 'P:${tones.inversePrimaryTone}', + ), + ColorChip( + label: 'shadow', + color: colorScheme.shadow, + size: _equalThreeChipSize, + showValue: showColorValue, + tone: 'N:${tones.shadowTone}', + ), + ], + ), + // TODO(rydmike): Remove deprecated colors in Flutter 3.25. + // Show the deprecated colors. + ColorGroup( + children: [ + ColorChip( + label: 'background\n(deprecated)', + color: colorScheme.background, + onColor: colorScheme.onBackground, + size: _equalThreeChipSize, + showValue: showColorValue, + tone: 'N:${tones.backgroundTone}', + ), + ColorChip( + label: 'onBackground\n(deprecated)', + color: colorScheme.onBackground, + onColor: colorScheme.background, + size: _equalThreeChipSize, + showValue: showColorValue, + tone: 'N:${tones.onBackgroundTone}', + ), + ColorChip( + label: 'surfaceVariant\n(deprecated)', + color: colorScheme.surfaceVariant, + onColor: colorScheme.onSurfaceVariant, + size: _equalThreeChipSize, + showValue: showColorValue, + tone: 'NV:${tones.surfaceVariantTone}', ), ], ), @@ -468,6 +511,7 @@ class ColorChip extends StatelessWidget { this.size, this.copyEnabled = true, this.showValue = false, + this.tone = '', }); final Color color; @@ -476,6 +520,7 @@ class ColorChip extends StatelessWidget { final Size? size; final bool copyEnabled; final bool showValue; + final String tone; static Color _contrastColor(Color color) { final Brightness brightness = ThemeData.estimateBrightnessForColor(color); @@ -515,7 +560,7 @@ class ColorChip extends StatelessWidget { children: [ Expanded( child: Text( - showValue ? '$label\n#${color.hexCode}' : label, + showValue ? '$label\n#${color.hexCode} $tone' : label, style: TextStyle(color: labelColor, fontSize: 11), ), ), diff --git a/example/lib/core/views/universal/list_tile_reveal.dart b/example/lib/core/views/universal/list_tile_reveal.dart new file mode 100644 index 0000000..e9fbb28 --- /dev/null +++ b/example/lib/core/views/universal/list_tile_reveal.dart @@ -0,0 +1,171 @@ +import 'package:flutter/material.dart'; + +/// A custom [ListTile] that has a built-in animated custom leading action +/// after the [leading] widget built in as a part of [title] that +/// reveals the [subtitle] when clicked. +/// +/// This is useful when a more compact look is desired where more information +/// is provided as an optional user based reveal action. The purpose is to make +/// UI less talkative but provide easy access to additional usage explanation. +/// +/// This is a Flutter "Universal" Widget that only depends on the SDK and +/// can be dropped into any application. +class ListTileReveal extends StatefulWidget { + const ListTileReveal({ + super.key, + this.title, + this.leading, + this.subtitle, + this.trailing, + this.contentPadding, + this.onTap, + this.dense, + this.subtitleDense, + this.enabled = true, + this.isOpen, + this.duration = const Duration(milliseconds: 200), + }); + + /// A widget to display before the title. + /// + /// Typically an [Icon] or a [CircleAvatar] widget. + final Widget? leading; + + /// The primary content of the list tile. + /// + /// Typically a [Text] widget. + /// + /// This should not wrap. To enforce the single line limit, use + /// [Text.maxLines]. + final Widget? title; + + /// Additional content displayed below the title. + /// + /// Typically a [Text] widget. + final Widget? subtitle; + + /// A widget to display after the title. + /// + /// Typically an [Icon] widget. + /// + /// To show right-aligned metadata (assuming left-to-right reading order; + /// left-aligned for right-to-left reading order), consider using a [Row] with + /// [CrossAxisAlignment.baseline] alignment whose first item is [Expanded] and + /// whose second child is the metadata text, instead of using the [trailing] + /// property. + final Widget? trailing; + + /// The [ListTileReveal]'s internal padding. + /// + /// Insets a [ListTileReveal]'s contents: its [leading], [title], [subtitle], + /// and [trailing] widgets. + /// + /// If null, `EdgeInsets.symmetric(horizontal: 16.0)` is used. + final EdgeInsetsGeometry? contentPadding; + + /// Called when the user taps this list tile. + /// + /// Inoperative if [enabled] is false. + final GestureTapCallback? onTap; + + /// Whether this list tile and card operation is interactive. + final bool enabled; + + /// Whether this list tile is part of a vertically dense list. + /// + /// If this property is null then its value is based on [ListTileTheme.dense]. + /// + /// Dense list tiles default to a smaller height. + final bool? dense; + + /// Whether this list tile subtitle is dense. + /// + /// Dense list tiles default to a smaller height. The subtitle is also dense + /// if dense is true. + /// + /// If not defined defaults to false. + final bool? subtitleDense; + + /// Set to true to open the info section of the ListTile, to false to close + /// it. + /// + /// If not defined, defaults to false. + final bool? isOpen; + + /// The duration of the show and hide animation of child. + final Duration duration; + + @override + State createState() => _ListTileRevealState(); +} + +class _ListTileRevealState extends State { + late bool _isOpen; + + @override + void initState() { + super.initState(); + _isOpen = widget.isOpen ?? false; + } + + @override + void didUpdateWidget(covariant ListTileReveal oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.isOpen != oldWidget.isOpen) _isOpen = widget.isOpen ?? false; + } + + void _handleTap() { + setState(() { + _isOpen = !_isOpen; + }); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + ListTile( + enabled: widget.enabled, + contentPadding: widget.contentPadding, + leading: widget.leading, + dense: widget.dense, + title: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + if (widget.title != null) widget.title!, + if (widget.subtitle != null && widget.enabled) + IconButton( + iconSize: 20, + // ignore: avoid_bool_literals_in_conditional_expressions + isSelected: widget.enabled ? _isOpen : false, + icon: const Icon(Icons.info_outlined), + selectedIcon: const Icon(Icons.info), + onPressed: widget.enabled ? _handleTap : null, + ), + ], + ), + trailing: widget.trailing, + onTap: widget.enabled ? widget.onTap : null, + ), + AnimatedSwitcher( + duration: widget.duration, + transitionBuilder: (Widget child, Animation animation) { + return SizeTransition( + sizeFactor: animation, + axisAlignment: _isOpen ? 1 : -1, + child: child, + ); + }, + child: (_isOpen && widget.subtitle != null && widget.enabled) + ? ListTile( + dense: (widget.dense ?? false) || + (widget.subtitleDense ?? false), + subtitle: widget.subtitle, + onTap: widget.enabled ? _handleTap : null, + ) + : const SizedBox.shrink(), + ), + ], + ); + } +} diff --git a/example/lib/home/views/pages/home_page.dart b/example/lib/home/views/pages/home_page.dart index d97ff34..d11bfa7 100644 --- a/example/lib/home/views/pages/home_page.dart +++ b/example/lib/home/views/pages/home_page.dart @@ -1,3 +1,4 @@ +import 'package:flex_seed_scheme/flex_seed_scheme.dart'; import 'package:flutter/material.dart'; import '../../../about/views/about.dart'; @@ -20,7 +21,18 @@ class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { - final bool isLight = Theme.of(context).brightness == Brightness.light; + final Brightness brightness = Theme.of(context).brightness; + final bool isLight = brightness == Brightness.light; + + final FlexTones tones = controller.usedVariant.isFlutterScheme + ? FlexTones.material(brightness) + : controller.usedVariant + .tones(brightness) + .monochromeSurfaces(controller.useMonoSurfaces) + .onMainsUseBW(controller.keepMainOnColorsBW) + .onSurfacesUseBW(controller.keepSurfaceOnColorsBW) + .surfacesUseBW(controller.keepLightSurfaceColorsWhite); + return Scaffold( appBar: AppBar( title: Builder(builder: (BuildContext context) { @@ -54,17 +66,8 @@ class HomePage extends StatelessWidget { ), body: ListView( children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: ShowInputColors(controller: controller), - ), - const ListTile( - dense: true, - title: Text('At least a primary key color is always needed to ' - 'seed the ColorScheme. Tap to change colors.'), - ), FlexTonesPopupMenu( - title: 'Used ColorScheme generation strategy:', + title: 'ColorScheme seed strategy:', variant: controller.usedVariant, onChanged: controller.setUsedTone, contentPadding: @@ -72,11 +75,13 @@ class HomePage extends StatelessWidget { ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: ColorSchemeView(showColorValue: controller.showColorValue), + child: ShowInputColors(controller: controller), ), ListTileSlider( dense: true, - title: const Text('Contrast level. Only for MCU dynamic schemes'), + title: const Text('Contrast level'), + subtitle: const Text('Only available for MCU dynamic schemes.\n' + 'Levels in M3 guide 0: Normal, 0.5: Medium, 1: High'), enabled: controller.usedVariant.isFlutterScheme, min: -1, max: 1, @@ -88,63 +93,40 @@ class HomePage extends StatelessWidget { onChanged: controller.setContrastLevel, sliderLabel: 'Contrast', ), + SwitchListTile( + title: const Text('ColorScheme'), + subtitle: const Text('Show color values'), + value: controller.showColorValue, + onChanged: controller.setShowColorValue, + ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: ShowTonalPalette(controller: controller), + child: ColorSchemeView( + showColorValue: controller.showColorValue, + tones: tones, + ), ), - ListTile( - title: Text('${controller.usedVariant.variantName}' - ' scheme variant configuration info:'), - subtitle: Text('${controller.usedVariant.configDetails}\n'), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ShowTonalPalette(controller: controller), ), - const Divider(), if (controller.usedVariant.isFlutterScheme) const ListTile( dense: true, title: Text('Additional seed generation options are not ' - 'available when using Flutter SDK scheme variant styles. ' - 'Use a variant based on FlexTones for more options.'), + 'available when using Flutter MCU dynamic scheme variants. ' + 'Use a variant based on FSS FlexTones for more options.'), ) else const ListTile( dense: true, title: Text('Additional seed generation options are available ' - 'when using FlexTones based scheme variants.'), + 'when using FSS FlexTones based scheme variants.'), ), SwitchListTile( dense: true, - title: - const Text('Use secondary key color to seed the ColorScheme'), - value: controller.useSecondaryKey && - !controller.usedVariant.isFlutterScheme, - onChanged: controller.usedVariant.isFlutterScheme - ? null - : controller.setUseSecondaryKey, - ), - SwitchListTile( - dense: true, - title: const Text('Use tertiary key color to seed the ColorScheme'), - value: controller.useTertiaryKey && - !controller.usedVariant.isFlutterScheme, - onChanged: controller.usedVariant.isFlutterScheme - ? null - : controller.setUseTertiaryKey, - ), - SwitchListTile( - dense: true, - title: const Text( - 'Use custom error key color to seed the ColorScheme'), - value: controller.useErrorKey && - !controller.usedVariant.isFlutterScheme, - onChanged: controller.usedVariant.isFlutterScheme - ? null - : controller.setUseErrorKey, - ), - const Divider(), - SwitchListTile( - dense: true, - title: const Text( - 'Use monochrome surface colors, pure grey scale, no tint'), + title: const Text('Monochrome surfaces, pure grey scale, no tint '), + subtitle: const Text('tones.monochromeSurfaces()'), value: controller.useMonoSurfaces && !controller.usedVariant.isFlutterScheme, onChanged: controller.usedVariant.isFlutterScheme @@ -153,8 +135,8 @@ class HomePage extends StatelessWidget { ), SwitchListTile( dense: true, - title: const Text( - 'Keep main onColors in seeded ColorScheme black and white'), + title: const Text('Keep main on-colors black and white'), + subtitle: const Text('tones.onMainsUseBW()'), value: controller.keepMainOnColorsBW && !controller.usedVariant.isFlutterScheme, onChanged: controller.usedVariant.isFlutterScheme @@ -163,8 +145,8 @@ class HomePage extends StatelessWidget { ), SwitchListTile( dense: true, - title: const Text( - 'Keep surface onColors in seeded ColorScheme black and white'), + title: const Text('Keep surface on-colors black and white'), + subtitle: const Text('tones.onSurfacesUseBW()'), value: controller.keepSurfaceOnColorsBW && !controller.usedVariant.isFlutterScheme, onChanged: controller.usedVariant.isFlutterScheme @@ -175,7 +157,8 @@ class HomePage extends StatelessWidget { SwitchListTile( dense: true, title: const Text('Keep surface and deprecated background color, ' - 'white in seeded light ColorScheme'), + 'white in light scheme'), + subtitle: const Text('tones.surfacesUseBW()'), value: controller.keepLightSurfaceColorsWhite && !controller.usedVariant.isFlutterScheme, onChanged: controller.usedVariant.isFlutterScheme @@ -186,7 +169,8 @@ class HomePage extends StatelessWidget { SwitchListTile( dense: true, title: const Text('Keep surface and deprecated background color, ' - 'black in seeded dark ColorScheme'), + 'black in dark scheme'), + subtitle: const Text('tones.surfacesUseBW()'), value: controller.keepDarkSurfaceColorsBlack && !controller.usedVariant.isFlutterScheme, onChanged: controller.usedVariant.isFlutterScheme @@ -194,13 +178,6 @@ class HomePage extends StatelessWidget { : controller.setKeepDarkSurfaceColorsBlack, ), const Divider(), - SwitchListTile( - dense: true, - title: const Text('Show color value in the ColorScheme view'), - value: controller.showColorValue, - onChanged: controller.setShowColorValue, - ), - const Divider(), const ListTile( title: Text('Widget showcase, using Material default styles')), const Padding( diff --git a/example/lib/home/views/widgets/flex_tones_popup_menu.dart b/example/lib/home/views/widgets/flex_tones_popup_menu.dart index bc36057..e693a36 100644 --- a/example/lib/home/views/widgets/flex_tones_popup_menu.dart +++ b/example/lib/home/views/widgets/flex_tones_popup_menu.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import '../../../core/utils/flex_color_extension.dart'; import '../../../core/views/universal/color_scheme_box.dart'; +import '../../../core/views/universal/list_tile_reveal.dart'; /// Widget used to select used [FlexTones] with a popup menu. /// @@ -51,11 +52,24 @@ class FlexTonesPopupMenu extends StatelessWidget { ), ) ], - child: ListTile( + child: ListTileReveal( contentPadding: contentPadding ?? const EdgeInsets.symmetric(horizontal: 16), - title: Text('$title ${variant.variantName}'), - subtitle: Text(variant.description), + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('$title ${variant.variantName}'), + Text( + variant.description, + style: txtStyle, + ), + ], + ), + subtitle: ListTile( + title: Text('${variant.variantName}' + ' scheme variant configuration info:'), + subtitle: Text('${variant.configDetails}\n'), + ), trailing: Badge( label: variant.isFlutterScheme ? const Text('MCU', style: TextStyle(fontSize: 8)) diff --git a/example/lib/home/views/widgets/show_color_scheme_colors.dart b/example/lib/home/views/widgets/show_color_scheme_colors.dart deleted file mode 100644 index 5c15f88..0000000 --- a/example/lib/home/views/widgets/show_color_scheme_colors.dart +++ /dev/null @@ -1,215 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../core/views/app/color_card.dart'; - -/// Draw a number of boxes showing the colors of key theme color properties -/// in the ColorScheme of the inherited ThemeData and its color properties. -class ShowColorSchemeColors extends StatelessWidget { - const ShowColorSchemeColors({super.key, this.onBackgroundColor}); - - /// The color of the background the color widget are being drawn on. - /// - /// Some of the theme colors may have semi transparent fill color. To compute - /// a legible text color for the sum when it shown on a background color, we - /// need to alpha merge it with background and we need the exact background - /// color it is drawn on for that. If not passed in from parent, it is - /// assumed to be drawn on card color, which usually is close enough. - final Color? onBackgroundColor; - - // Return true if the color is light, meaning it needs dark text for contrast. - static bool _isLight(final Color color) => - ThemeData.estimateBrightnessForColor(color) == Brightness.light; - - // On color used when a theme color property does not have a theme onColor. - static Color _onColor(final Color color, final Color bg) => - _isLight(Color.alphaBlend(color, bg)) ? Colors.black : Colors.white; - - @override - Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); - final ColorScheme colorScheme = theme.colorScheme; - final bool useMaterial3 = theme.useMaterial3; - const double spacing = 6; - - // Grab the card border from the theme card shape - ShapeBorder? border = theme.cardTheme.shape; - // If we had one, copy in a border side to it. - if (border is RoundedRectangleBorder) { - border = border.copyWith( - side: BorderSide(color: theme.dividerColor), - ); - // If - } else { - // If border was null, make one matching Card default, but with border - // side, if it was not null, we leave it as it was. - border ??= RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(useMaterial3 ? 12 : 4)), - side: BorderSide(color: theme.dividerColor), - ); - } - - // Get effective background color. - final Color background = - onBackgroundColor ?? theme.cardTheme.color ?? theme.cardColor; - - // Wrap this widget branch in a custom theme where card has a border outline - // if it did not have one, but retains in ambient themed border radius. - return Theme( - data: Theme.of(context).copyWith( - cardTheme: CardTheme.of(context).copyWith( - elevation: 0, - shape: border, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Text( - 'ColorScheme Colors', - style: theme.textTheme.titleMedium, - ), - ), - Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - spacing: spacing, - runSpacing: spacing, - children: [ - ColorCard( - label: 'Primary', - color: colorScheme.primary, - textColor: colorScheme.onPrimary, - ), - ColorCard( - label: 'on\nPrimary', - color: colorScheme.onPrimary, - textColor: colorScheme.primary, - ), - ColorCard( - label: 'Primary\nContainer', - color: colorScheme.primaryContainer, - textColor: colorScheme.onPrimaryContainer, - ), - ColorCard( - label: 'onPrimary\nContainer', - color: colorScheme.onPrimaryContainer, - textColor: colorScheme.primaryContainer, - ), - ColorCard( - label: 'Secondary', - color: colorScheme.secondary, - textColor: colorScheme.onSecondary, - ), - ColorCard( - label: 'on\nSecondary', - color: colorScheme.onSecondary, - textColor: colorScheme.secondary, - ), - ColorCard( - label: 'Secondary\nContainer', - color: colorScheme.secondaryContainer, - textColor: colorScheme.onSecondaryContainer, - ), - ColorCard( - label: 'on\nSecondary\nContainer', - color: colorScheme.onSecondaryContainer, - textColor: colorScheme.secondaryContainer, - ), - ColorCard( - label: 'Tertiary', - color: colorScheme.tertiary, - textColor: colorScheme.onTertiary, - ), - ColorCard( - label: 'on\nTertiary', - color: colorScheme.onTertiary, - textColor: colorScheme.tertiary, - ), - ColorCard( - label: 'Tertiary\nContainer', - color: colorScheme.tertiaryContainer, - textColor: colorScheme.onTertiaryContainer, - ), - ColorCard( - label: 'on\nTertiary\nContainer', - color: colorScheme.onTertiaryContainer, - textColor: colorScheme.tertiaryContainer, - ), - ColorCard( - label: 'Error', - color: colorScheme.error, - textColor: colorScheme.onError, - ), - ColorCard( - label: 'on\nError', - color: colorScheme.onError, - textColor: colorScheme.error, - ), - ColorCard( - label: 'Error\nContainer', - color: colorScheme.errorContainer, - textColor: colorScheme.onErrorContainer, - ), - ColorCard( - label: 'onError\nContainer', - color: colorScheme.onErrorContainer, - textColor: colorScheme.errorContainer, - ), - ColorCard( - label: 'Surface', - color: colorScheme.surface, - textColor: colorScheme.onSurface, - ), - ColorCard( - label: 'on\nSurface', - color: colorScheme.onSurface, - textColor: colorScheme.surface, - ), - ColorCard( - label: 'Outline', - color: colorScheme.outline, - textColor: colorScheme.surface, - ), - ColorCard( - label: 'Outline\nVariant', - color: colorScheme.outlineVariant, - textColor: _onColor(colorScheme.outlineVariant, background), - ), - ColorCard( - label: 'Shadow', - color: colorScheme.shadow, - textColor: _onColor(colorScheme.shadow, background), - ), - ColorCard( - label: 'Scrim', - color: colorScheme.scrim, - textColor: _onColor(colorScheme.scrim, background), - ), - ColorCard( - label: 'Inverse\nSurface', - color: colorScheme.inverseSurface, - textColor: colorScheme.onInverseSurface, - ), - ColorCard( - label: 'onInverse\nSurface', - color: colorScheme.onInverseSurface, - textColor: colorScheme.inverseSurface, - ), - ColorCard( - label: 'Inverse\nPrimary', - color: colorScheme.inversePrimary, - textColor: colorScheme.inverseSurface, - ), - ColorCard( - label: 'Surface\nTint', - color: colorScheme.surfaceTint, - textColor: colorScheme.onPrimary, - ), - ], - ), - ], - ), - ); - } -} diff --git a/example/lib/home/views/widgets/show_input_colors.dart b/example/lib/home/views/widgets/show_input_colors.dart index 8c89ce7..a1379e9 100644 --- a/example/lib/home/views/widgets/show_input_colors.dart +++ b/example/lib/home/views/widgets/show_input_colors.dart @@ -96,179 +96,299 @@ class ShowInputColors extends StatelessWidget { runSpacing: 6, children: [ // Primary color - RepaintBoundary( - key: const ValueKey('input_primary'), - child: SizedBox( - width: boxWidth, - height: boxHeight, - child: Card( - margin: EdgeInsets.zero, - elevation: 0, - clipBehavior: Clip.hardEdge, - child: Material( - color: primary, - child: ColorPickerInkWellDialog( - color: primary, - onChanged: controller.setPrimarySeedColor, - recentColors: controller.recentColors, - onRecentColorsChanged: controller.setRecentColors, - wasCancelled: (bool cancelled) { - if (cancelled) { - controller.setPrimarySeedColor(primary); - } - }, - enabled: true, - child: ColorNameValue( - key: ValueKey('ipc primary $primary'), + Column( + children: [ + RepaintBoundary( + key: const ValueKey('input_primary'), + child: SizedBox( + width: boxWidth, + height: boxHeight, + child: Card( + margin: EdgeInsets.zero, + elevation: 0, + clipBehavior: Clip.hardEdge, + child: Material( color: primary, - textColor: onPrimary, - label: 'primary', - showInputColor: false, - showMaterialName: true, + child: ColorPickerInkWellDialog( + color: primary, + onChanged: controller.setPrimarySeedColor, + recentColors: controller.recentColors, + onRecentColorsChanged: controller.setRecentColors, + wasCancelled: (bool cancelled) { + if (cancelled) { + controller.setPrimarySeedColor(primary); + } + }, + enabled: true, + child: ColorNameValue( + key: ValueKey('ipc primary $primary'), + color: primary, + textColor: onPrimary, + label: 'primary', + showInputColor: false, + showMaterialName: true, + ), + ), ), ), ), ), - ), + const SizedBox( + width: boxWidth, + child: ListTile( + contentPadding: EdgeInsets.symmetric(horizontal: 4), + dense: true, + title: Text('Use seed color'), + ), + ), + SizedBox( + width: boxWidth, + child: SwitchListTile( + activeColor: colorScheme.primary, + contentPadding: const EdgeInsets.symmetric(horizontal: 4), + dense: true, + title: const Text('Pinned'), + value: controller.pinPrimary, + onChanged: controller.setPinPrimary, + ), + ), + ], ), // Secondary color - RepaintBoundary( - key: const ValueKey('input_secondary'), - child: SizedBox( - width: boxWidth, - height: boxHeight, - child: Card( - margin: EdgeInsets.zero, - elevation: 0, - clipBehavior: Clip.hardEdge, - child: Material( - color: controller.useSecondaryKey && - !controller.usedVariant.isFlutterScheme - ? secondary - : surface, - child: ColorPickerInkWellDialog( - color: secondary, - onChanged: controller.setSecondarySeedColor, - recentColors: controller.recentColors, - onRecentColorsChanged: controller.setRecentColors, - wasCancelled: (bool cancelled) { - if (cancelled) { - controller.setSecondarySeedColor(secondary); - } - }, - enabled: controller.useSecondaryKey && - !controller.usedVariant.isFlutterScheme, - child: ColorNameValue( - key: ValueKey('ipc secondary $secondary'), + Column( + children: [ + RepaintBoundary( + key: const ValueKey('input_secondary'), + child: SizedBox( + width: boxWidth, + height: boxHeight, + child: Card( + margin: EdgeInsets.zero, + elevation: 0, + clipBehavior: Clip.hardEdge, + child: Material( color: controller.useSecondaryKey && !controller.usedVariant.isFlutterScheme ? secondary : surface, - textColor: controller.useSecondaryKey && - !controller.usedVariant.isFlutterScheme - ? onSecondary - : surface, - showInputColor: false, - label: 'secondary', - showMaterialName: true, + child: ColorPickerInkWellDialog( + color: secondary, + onChanged: controller.setSecondarySeedColor, + recentColors: controller.recentColors, + onRecentColorsChanged: controller.setRecentColors, + wasCancelled: (bool cancelled) { + if (cancelled) { + controller.setSecondarySeedColor(secondary); + } + }, + enabled: controller.useSecondaryKey && + !controller.usedVariant.isFlutterScheme, + child: ColorNameValue( + key: ValueKey('ipc secondary $secondary'), + color: controller.useSecondaryKey && + !controller.usedVariant.isFlutterScheme + ? secondary + : surface, + textColor: controller.useSecondaryKey && + !controller.usedVariant.isFlutterScheme + ? onSecondary + : surface, + showInputColor: false, + label: 'secondary', + showMaterialName: true, + ), + ), ), ), ), ), - ), + SizedBox( + width: boxWidth, + child: SwitchListTile( + activeColor: colorScheme.secondary, + contentPadding: const EdgeInsets.symmetric(horizontal: 4), + dense: true, + title: const Text('Secondary'), + value: controller.useSecondaryKey && + !controller.usedVariant.isFlutterScheme, + onChanged: controller.usedVariant.isFlutterScheme + ? null + : controller.setUseSecondaryKey, + ), + ), + SizedBox( + width: boxWidth, + child: SwitchListTile( + activeColor: colorScheme.secondary, + contentPadding: const EdgeInsets.symmetric(horizontal: 4), + dense: true, + title: const Text('Pinned'), + value: controller.pinSecondary && + !controller.usedVariant.isFlutterScheme, + onChanged: controller.useSecondaryKey + ? controller.setPinSecondary + : null, + ), + ), + ], ), // Tertiary color - RepaintBoundary( - key: const ValueKey('input_tertiary'), - child: SizedBox( - width: boxWidth, - height: boxHeight, - child: Card( - margin: EdgeInsets.zero, - elevation: 0, - clipBehavior: Clip.hardEdge, - child: Material( - color: controller.useTertiaryKey && - !controller.usedVariant.isFlutterScheme - ? tertiary - : surface, - child: ColorPickerInkWellDialog( - color: controller.useTertiaryKey ? tertiary : surface, - onChanged: controller.setTertiarySeedColor, - recentColors: controller.recentColors, - onRecentColorsChanged: controller.setRecentColors, - wasCancelled: (bool cancelled) { - if (cancelled) { - controller.setTertiarySeedColor(tertiary); - } - }, - enabled: controller.useTertiaryKey && - !controller.usedVariant.isFlutterScheme, - child: ColorNameValue( - key: ValueKey('ipc tertiary $tertiary'), + Column( + children: [ + RepaintBoundary( + key: const ValueKey('input_tertiary'), + child: SizedBox( + width: boxWidth, + height: boxHeight, + child: Card( + margin: EdgeInsets.zero, + elevation: 0, + clipBehavior: Clip.hardEdge, + child: Material( color: controller.useTertiaryKey && !controller.usedVariant.isFlutterScheme ? tertiary : surface, - textColor: controller.useTertiaryKey && - !controller.usedVariant.isFlutterScheme - ? onTertiary - : surface, - label: 'tertiary', - showInputColor: false, - showMaterialName: true, + child: ColorPickerInkWellDialog( + color: + controller.useTertiaryKey ? tertiary : surface, + onChanged: controller.setTertiarySeedColor, + recentColors: controller.recentColors, + onRecentColorsChanged: controller.setRecentColors, + wasCancelled: (bool cancelled) { + if (cancelled) { + controller.setTertiarySeedColor(tertiary); + } + }, + enabled: controller.useTertiaryKey && + !controller.usedVariant.isFlutterScheme, + child: ColorNameValue( + key: ValueKey('ipc tertiary $tertiary'), + color: controller.useTertiaryKey && + !controller.usedVariant.isFlutterScheme + ? tertiary + : surface, + textColor: controller.useTertiaryKey && + !controller.usedVariant.isFlutterScheme + ? onTertiary + : surface, + label: 'tertiary', + showInputColor: false, + showMaterialName: true, + ), + ), ), ), ), ), - ), + SizedBox( + width: boxWidth, + child: SwitchListTile( + activeColor: colorScheme.tertiary, + contentPadding: const EdgeInsets.symmetric(horizontal: 4), + dense: true, + title: const Text('Tertiary'), + value: controller.useTertiaryKey && + !controller.usedVariant.isFlutterScheme, + onChanged: controller.usedVariant.isFlutterScheme + ? null + : controller.setUseTertiaryKey, + ), + ), + SizedBox( + width: boxWidth, + child: SwitchListTile( + activeColor: colorScheme.tertiary, + contentPadding: const EdgeInsets.symmetric(horizontal: 4), + dense: true, + title: const Text('Pinned'), + value: controller.pinTertiary && + !controller.usedVariant.isFlutterScheme, + onChanged: controller.useTertiaryKey + ? controller.setPinTertiary + : null, + ), + ), + ], ), // Error color - RepaintBoundary( - key: const ValueKey('input_error'), - child: SizedBox( - width: boxWidth, - height: boxHeight, - child: Card( - margin: EdgeInsets.zero, - elevation: 0, - clipBehavior: Clip.hardEdge, - child: Material( - color: controller.useErrorKey && - !controller.usedVariant.isFlutterScheme - ? error - : surface, - child: ColorPickerInkWellDialog( - color: controller.useErrorKey ? error : surface, - onChanged: controller.setErrorSeedColor, - recentColors: controller.recentColors, - onRecentColorsChanged: controller.setRecentColors, - wasCancelled: (bool cancelled) { - if (cancelled) { - controller.setErrorSeedColor(error); - } - }, - enabled: controller.useErrorKey && - !controller.usedVariant.isFlutterScheme, - child: ColorNameValue( - key: ValueKey('ipc error $error'), + Column( + children: [ + RepaintBoundary( + key: const ValueKey('input_error'), + child: SizedBox( + width: boxWidth, + height: boxHeight, + child: Card( + margin: EdgeInsets.zero, + elevation: 0, + clipBehavior: Clip.hardEdge, + child: Material( color: controller.useErrorKey && !controller.usedVariant.isFlutterScheme ? error : surface, - textColor: controller.useErrorKey && - !controller.usedVariant.isFlutterScheme - ? onError - : surface, - label: 'error', - showInputColor: false, - showMaterialName: true, + child: ColorPickerInkWellDialog( + color: controller.useErrorKey ? error : surface, + onChanged: controller.setErrorSeedColor, + recentColors: controller.recentColors, + onRecentColorsChanged: controller.setRecentColors, + wasCancelled: (bool cancelled) { + if (cancelled) { + controller.setErrorSeedColor(error); + } + }, + enabled: controller.useErrorKey && + !controller.usedVariant.isFlutterScheme, + child: ColorNameValue( + key: ValueKey('ipc error $error'), + color: controller.useErrorKey && + !controller.usedVariant.isFlutterScheme + ? error + : surface, + textColor: controller.useErrorKey && + !controller.usedVariant.isFlutterScheme + ? onError + : surface, + label: 'error', + showInputColor: false, + showMaterialName: true, + ), + ), ), ), ), ), - ), + SizedBox( + width: boxWidth, + child: SwitchListTile( + activeColor: colorScheme.error, + contentPadding: const EdgeInsets.symmetric(horizontal: 4), + dense: true, + title: const Text('Error'), + value: controller.useErrorKey && + !controller.usedVariant.isFlutterScheme, + onChanged: controller.usedVariant.isFlutterScheme + ? null + : controller.setUseErrorKey, + ), + ), + SizedBox( + width: boxWidth, + child: SwitchListTile( + activeColor: colorScheme.error, + contentPadding: const EdgeInsets.symmetric(horizontal: 4), + dense: true, + title: const Text('Pinned'), + value: controller.pinError && + !controller.usedVariant.isFlutterScheme, + onChanged: controller.useErrorKey + ? controller.setPinError + : null, + ), + ), + ], ), ], ), diff --git a/example/lib/theme/controllers/theme_controller.dart b/example/lib/theme/controllers/theme_controller.dart index 4957845..5e57923 100644 --- a/example/lib/theme/controllers/theme_controller.dart +++ b/example/lib/theme/controllers/theme_controller.dart @@ -71,6 +71,42 @@ class ThemeController with ChangeNotifier { if (notify) notifyListeners(); } + bool _pinPrimary = false; + bool get pinPrimary => _pinPrimary; + void setPinPrimary(bool? value, [bool notify = true]) { + if (value == null) return; + if (value == _pinPrimary) return; + _pinPrimary = value; + if (notify) notifyListeners(); + } + + bool _pinSecondary = false; + bool get pinSecondary => _pinSecondary; + void setPinSecondary(bool? value, [bool notify = true]) { + if (value == null) return; + if (value == _pinSecondary) return; + _pinSecondary = value; + if (notify) notifyListeners(); + } + + bool _pinTertiary = false; + bool get pinTertiary => _pinTertiary; + void setPinTertiary(bool? value, [bool notify = true]) { + if (value == null) return; + if (value == _pinTertiary) return; + _pinTertiary = value; + if (notify) notifyListeners(); + } + + bool _pinError = false; + bool get pinError => _pinError; + void setPinError(bool? value, [bool notify = true]) { + if (value == null) return; + if (value == _pinError) return; + _pinError = value; + if (notify) notifyListeners(); + } + bool _monoSurfaces = false; bool get useMonoSurfaces => _monoSurfaces; void setUseMonoSurfaces(bool? value, [bool notify = true]) { diff --git a/example/lib/theme/model/app_theme.dart b/example/lib/theme/model/app_theme.dart index 0e922cb..373456c 100644 --- a/example/lib/theme/model/app_theme.dart +++ b/example/lib/theme/model/app_theme.dart @@ -29,6 +29,8 @@ class AppTheme { variant: controller.usedVariant.isFlutterScheme ? controller.usedVariant : null, + // The contrast level only has any effect when using above variant based + // scheming strategy and the variant is one where isFlutterScheme is true. contrastLevel: controller.contrastLevel, // Tone chroma config and tone mapping is optional. If you do not add it // you get a config matching Flutter's Material 3 ColorScheme.fromSeed. @@ -44,6 +46,18 @@ class AppTheme { .onMainsUseBW(controller.keepMainOnColorsBW) .onSurfacesUseBW(controller.keepSurfaceOnColorsBW) .surfacesUseBW(controller.keepLightSurfaceColorsWhite), + // Pin input seed colors in light mode to corresponding main colors + // when set to be pinned in the UI. + primary: controller.pinPrimary ? controller.primarySeedColor : null, + secondary: controller.pinSecondary && controller.useSecondaryKey + ? controller.secondarySeedColor + : null, + tertiary: controller.pinTertiary && controller.useTertiaryKey + ? controller.tertiarySeedColor + : null, + error: controller.pinError && controller.useErrorKey + ? controller.errorSeedColor + : null, ); // Light mode theme @@ -84,6 +98,23 @@ class AppTheme { .onMainsUseBW(controller.keepMainOnColorsBW) .onSurfacesUseBW(controller.keepSurfaceOnColorsBW) .surfacesUseBW(controller.keepDarkSurfaceColorsBlack), + // Pin input seed colors in dark mode to corresponding container colors + // when set to be pinned in the UI. + // Typically input seed "brand" colors have high chroma and ar dark, hence + // they usually will not fit on the main colors in dark mode, but often + // they work well on the containers. This can be used to bring a touch + // of the source "brand" colors to the dark mode. + primaryContainer: + controller.pinPrimary ? controller.primarySeedColor : null, + secondaryContainer: controller.pinSecondary && controller.useSecondaryKey + ? controller.secondarySeedColor + : null, + tertiaryContainer: controller.pinTertiary && controller.useTertiaryKey + ? controller.tertiarySeedColor + : null, + errorContainer: controller.pinError && controller.useErrorKey + ? controller.errorSeedColor + : null, ); // Dark mode theme From 281785624d0cb008b9fd19508db6e2905b39c7e7 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Thu, 20 Jun 2024 23:59:44 +0300 Subject: [PATCH 20/37] Feature: Support multiple seed colors on MCU DynamicScheme:s --- CHANGELOG.md | 10 +- example/lib/core/constants/app_data.dart | 4 +- .../home/views/widgets/show_input_colors.dart | 78 ++++------- .../views/widgets/show_tonal_palette.dart | 13 +- example/pubspec.yaml | 2 +- lib/src/flex/flex_scheme_variant.dart | 85 ++++++------ lib/src/flex/flex_seed_scheme.dart | 122 ++++++++++++++---- lib/src/mcu/dynamiccolor/dynamic_scheme.dart | 3 +- lib/src/mcu/scheme/scheme_content.dart | 53 +++++--- lib/src/mcu/scheme/scheme_expressive.dart | 25 +++- lib/src/mcu/scheme/scheme_fidelity.dart | 49 +++++-- lib/src/mcu/scheme/scheme_fruit_salad.dart | 18 ++- lib/src/mcu/scheme/scheme_monochrome.dart | 5 + lib/src/mcu/scheme/scheme_neutral.dart | 21 ++- lib/src/mcu/scheme/scheme_rainbow.dart | 39 ++++-- lib/src/mcu/scheme/scheme_tonal_spot.dart | 21 ++- lib/src/mcu/scheme/scheme_vibrant.dart | 23 +++- pubspec.yaml | 2 +- test/flex_seed_scheme_test.dart | 4 +- 19 files changed, 391 insertions(+), 186 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f4e1d5..7043ec5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,24 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. -## 2.1.0 +## 3.0.0 **June 20, 2024** +* **BREAKING** + * The API for `SeedColorScheme.buildDynamicScheme` was changed to enable support for multiple seed colors on the MCU based `DynamicScheme` APIs and its extended schemes. + + * **NEW** * Added support for `contrastLevel` to `SeedColorScheme.fromSeeds`. This allows you to set the contrast level of the generated color scheme, when using `SeedColorScheme.fromSeeds` with the `variant` property. The `contrastLevel` parameter indicates the contrast level between color pairs, such as `primary` and `onPrimary`.The value 0.0 is the default (normal); -1.0 is the lowest; 1.0 is the highest. From Material Design guideline, the medium and high contrast, correspond to 0.5 and 1.0 respectively. * The `contrastLevel` in Flutter SDK is not yet available in `ColorScheme.fromSeed` on Flutter stable 3.22.x, but is available on the master channel. With FSS you can use it already in Flutter 3.22.x. * **NOTE:** Using `contrastLevel` has no effect when using `tones`. However, with `tones` you can create custom tones with even more flexibility in seed generation to make schemes with higher or less contrast. Two pre-configured high contrast tones exist earlier via `FlexTones.highContrast` and `FlexTones.ultraContrast`. * The `tones` configuration class `FlexTones` got a new built-in modifier, `monochromeSurfaces()`. It can be applied to any predefined or custom `FlexTones` to make the surface colors monochrome and use pure greyscale for the neutral and neutral variant tonal palettes, with no color tint from their key color or primary key seed color. + + * The forked internal MCU version received new features. First `DynamicScheme` can accept an optional `customErrorPalette` and then `SchemeTonalSpot`, `SchemeContent`, `SchemeFidelity`, `SchemeExpressive`, `SchemeFruitSalad`, `SchemeMonochrome`, `SchemeNeutral`, `SchemeRainbow` and `SchemeVibrant` that extend `DynamicScheme` all received properties to support individual seed colors for all tonal palettes. + + * The above addition enables `SeedColorScheme.fromSeeds` to support using all its key seed colors also when using MCU based `DynamicScheme` variants and not just for `FlexTones` based `tones` and `variants`. * **COLOR VALUE BREAKING** diff --git a/example/lib/core/constants/app_data.dart b/example/lib/core/constants/app_data.dart index 388e05b..024d20f 100644 --- a/example/lib/core/constants/app_data.dart +++ b/example/lib/core/constants/app_data.dart @@ -25,8 +25,8 @@ class AppData { static const String packageName = 'FlexSeed\u{00AD}Scheme'; // Version of the WEB build, usually same as package, but it also has a // build numbers. - static const String versionMajor = '2'; - static const String versionMinor = '1'; + static const String versionMajor = '3'; + static const String versionMinor = '0'; static const String versionPatch = '0'; static const String versionBuild = '01'; static const String version = '$versionMajor.$versionMinor.$versionPatch ' diff --git a/example/lib/home/views/widgets/show_input_colors.dart b/example/lib/home/views/widgets/show_input_colors.dart index a1379e9..4a5ce13 100644 --- a/example/lib/home/views/widgets/show_input_colors.dart +++ b/example/lib/home/views/widgets/show_input_colors.dart @@ -167,10 +167,8 @@ class ShowInputColors extends StatelessWidget { elevation: 0, clipBehavior: Clip.hardEdge, child: Material( - color: controller.useSecondaryKey && - !controller.usedVariant.isFlutterScheme - ? secondary - : surface, + color: + controller.useSecondaryKey ? secondary : surface, child: ColorPickerInkWellDialog( color: secondary, onChanged: controller.setSecondarySeedColor, @@ -181,16 +179,13 @@ class ShowInputColors extends StatelessWidget { controller.setSecondarySeedColor(secondary); } }, - enabled: controller.useSecondaryKey && - !controller.usedVariant.isFlutterScheme, + enabled: controller.useSecondaryKey, child: ColorNameValue( key: ValueKey('ipc secondary $secondary'), - color: controller.useSecondaryKey && - !controller.usedVariant.isFlutterScheme + color: controller.useSecondaryKey ? secondary : surface, - textColor: controller.useSecondaryKey && - !controller.usedVariant.isFlutterScheme + textColor: controller.useSecondaryKey ? onSecondary : surface, showInputColor: false, @@ -209,11 +204,8 @@ class ShowInputColors extends StatelessWidget { contentPadding: const EdgeInsets.symmetric(horizontal: 4), dense: true, title: const Text('Secondary'), - value: controller.useSecondaryKey && - !controller.usedVariant.isFlutterScheme, - onChanged: controller.usedVariant.isFlutterScheme - ? null - : controller.setUseSecondaryKey, + value: controller.useSecondaryKey, + onChanged: controller.setUseSecondaryKey, ), ), SizedBox( @@ -223,8 +215,7 @@ class ShowInputColors extends StatelessWidget { contentPadding: const EdgeInsets.symmetric(horizontal: 4), dense: true, title: const Text('Pinned'), - value: controller.pinSecondary && - !controller.usedVariant.isFlutterScheme, + value: controller.pinSecondary, onChanged: controller.useSecondaryKey ? controller.setPinSecondary : null, @@ -246,10 +237,7 @@ class ShowInputColors extends StatelessWidget { elevation: 0, clipBehavior: Clip.hardEdge, child: Material( - color: controller.useTertiaryKey && - !controller.usedVariant.isFlutterScheme - ? tertiary - : surface, + color: controller.useTertiaryKey ? tertiary : surface, child: ColorPickerInkWellDialog( color: controller.useTertiaryKey ? tertiary : surface, @@ -261,16 +249,13 @@ class ShowInputColors extends StatelessWidget { controller.setTertiarySeedColor(tertiary); } }, - enabled: controller.useTertiaryKey && - !controller.usedVariant.isFlutterScheme, + enabled: controller.useTertiaryKey, child: ColorNameValue( key: ValueKey('ipc tertiary $tertiary'), - color: controller.useTertiaryKey && - !controller.usedVariant.isFlutterScheme + color: controller.useTertiaryKey ? tertiary : surface, - textColor: controller.useTertiaryKey && - !controller.usedVariant.isFlutterScheme + textColor: controller.useTertiaryKey ? onTertiary : surface, label: 'tertiary', @@ -289,11 +274,8 @@ class ShowInputColors extends StatelessWidget { contentPadding: const EdgeInsets.symmetric(horizontal: 4), dense: true, title: const Text('Tertiary'), - value: controller.useTertiaryKey && - !controller.usedVariant.isFlutterScheme, - onChanged: controller.usedVariant.isFlutterScheme - ? null - : controller.setUseTertiaryKey, + value: controller.useTertiaryKey, + onChanged: controller.setUseTertiaryKey, ), ), SizedBox( @@ -303,8 +285,7 @@ class ShowInputColors extends StatelessWidget { contentPadding: const EdgeInsets.symmetric(horizontal: 4), dense: true, title: const Text('Pinned'), - value: controller.pinTertiary && - !controller.usedVariant.isFlutterScheme, + value: controller.pinTertiary, onChanged: controller.useTertiaryKey ? controller.setPinTertiary : null, @@ -325,10 +306,7 @@ class ShowInputColors extends StatelessWidget { elevation: 0, clipBehavior: Clip.hardEdge, child: Material( - color: controller.useErrorKey && - !controller.usedVariant.isFlutterScheme - ? error - : surface, + color: controller.useErrorKey ? error : surface, child: ColorPickerInkWellDialog( color: controller.useErrorKey ? error : surface, onChanged: controller.setErrorSeedColor, @@ -339,18 +317,12 @@ class ShowInputColors extends StatelessWidget { controller.setErrorSeedColor(error); } }, - enabled: controller.useErrorKey && - !controller.usedVariant.isFlutterScheme, + enabled: controller.useErrorKey, child: ColorNameValue( key: ValueKey('ipc error $error'), - color: controller.useErrorKey && - !controller.usedVariant.isFlutterScheme - ? error - : surface, - textColor: controller.useErrorKey && - !controller.usedVariant.isFlutterScheme - ? onError - : surface, + color: controller.useErrorKey ? error : surface, + textColor: + controller.useErrorKey ? onError : surface, label: 'error', showInputColor: false, showMaterialName: true, @@ -367,11 +339,8 @@ class ShowInputColors extends StatelessWidget { contentPadding: const EdgeInsets.symmetric(horizontal: 4), dense: true, title: const Text('Error'), - value: controller.useErrorKey && - !controller.usedVariant.isFlutterScheme, - onChanged: controller.usedVariant.isFlutterScheme - ? null - : controller.setUseErrorKey, + value: controller.useErrorKey, + onChanged: controller.setUseErrorKey, ), ), SizedBox( @@ -381,8 +350,7 @@ class ShowInputColors extends StatelessWidget { contentPadding: const EdgeInsets.symmetric(horizontal: 4), dense: true, title: const Text('Pinned'), - value: controller.pinError && - !controller.usedVariant.isFlutterScheme, + value: controller.pinError, onChanged: controller.useErrorKey ? controller.setPinError : null, diff --git a/example/lib/home/views/widgets/show_tonal_palette.dart b/example/lib/home/views/widgets/show_tonal_palette.dart index d045c7e..f925669 100644 --- a/example/lib/home/views/widgets/show_tonal_palette.dart +++ b/example/lib/home/views/widgets/show_tonal_palette.dart @@ -30,12 +30,21 @@ class ShowTonalPalette extends StatelessWidget { List neutralTonals = []; List neutralVariantTonals = []; - // TODO(rydmike): Add usage of contrast level to demo app. // Are we using a Flutter SDK scheme? Otherwise use FlexTone. if (controller.usedVariant.isFlutterScheme) { // Get DynamicScheme tones if using Flutter SDK scheme. final DynamicScheme dynamicScheme = SeedColorScheme.buildDynamicScheme( - brightness, controller.primarySeedColor, controller.usedVariant, 0.0); + brightness: brightness, + primarySeedColor: controller.primarySeedColor, + secondarySeedColor: + controller.useSecondaryKey ? controller.secondarySeedColor : null, + tertiarySeedColor: + controller.useTertiaryKey ? controller.tertiarySeedColor : null, + errorSeedColor: + controller.useErrorKey ? controller.errorSeedColor : null, + variant: controller.usedVariant, + contrastLevel: controller.contrastLevel, + ); // Assign the tonals for the schemes to the int lists using tone indexes // from FlexTonalPalette based on used type. diff --git a/example/pubspec.yaml b/example/pubspec.yaml index daa154e..d895734 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: flex_seed_scheme_example description: Example that demonstrate how to use the FlexSeedScheme package. -version: 2.1.0-dev.2 +version: 3.0.0 publish_to: 'none' environment: sdk: '>=3.0.0 <4.0.0' diff --git a/lib/src/flex/flex_scheme_variant.dart b/lib/src/flex/flex_scheme_variant.dart index 77bcfe5..9453bc2 100644 --- a/lib/src/flex/flex_scheme_variant.dart +++ b/lib/src/flex/flex_scheme_variant.dart @@ -64,11 +64,11 @@ enum FlexSchemeVariant { variantName: 'Tonal spot', description: 'Default Material-3 pastel colors with low chroma', configDetails: 'Primary - Chroma from key color, but min 36\n' - 'Secondary - Chroma set to 16\n' - 'Tertiary - Chroma set to 24\n' + 'Secondary - Chroma 16\n' + 'Tertiary - Hue rotated 60 degrees or key hue, Chroma 24\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' - 'Neutral - Chroma set to 6\n' - 'Neutral variant - Chroma set to 8\n' + 'Neutral - Chroma 6\n' + 'Neutral variant - Chroma 8\n' 'Variant style: MaterialColorUtilities (MCU)', icon: Icons.looks_3_outlined, shade: -6, @@ -89,10 +89,10 @@ enum FlexSchemeVariant { 'it is bright and uses high chroma', configDetails: 'Primary - Chroma from key color\n' 'Secondary - Max of: chroma from key -32 or *0.5\n' - 'Tertiary - TemperatureCache complement\n' + 'Tertiary - TemperatureCache complement hue or key hue and chroma\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' - 'Neutral - Chroma from key /8\n' - 'Neutral variant - Chroma from key /8 +4\n' + 'Neutral - Chroma from key/8\n' + 'Neutral variant - Chroma from key/8 +4\n' 'Variant style: Material Color Utilities (MCU)', icon: Icons.grain_outlined, shade: 0, @@ -138,10 +138,11 @@ enum FlexSchemeVariant { /// tokens should alter their tone to match the palette vibrancy. vibrant( variantName: 'Vibrant', - description: 'Maxed out colorfulness for all primaries', + description: 'Maxed out colorfulness for primaries. Secondary and ' + 'tertiary hues intentionally differ from their seed colors', configDetails: 'Primary - Chroma 200\n' - 'Secondary - Chroma 24\n' - 'Tertiary - Chroma 32\n' + 'Secondary - Chroma 24, Hue rotated 10-18 degrees\n' + 'Tertiary - Chroma 32, Hue rotated 20-35 degrees\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' 'Neutral - Chroma 10\n' 'Neutral variant - Chroma 12\n' @@ -155,14 +156,14 @@ enum FlexSchemeVariant { /// The primary palette's hue is different from the seed color, for variety. expressive( variantName: 'Expressive', - description: 'Primary hue is intentionally different from the ' - 'seed color', - configDetails: 'Primary - Hue+240, Chroma 40\n' - 'Secondary - Hue rotations, Chroma 24\n' - 'Tertiary - Hue rotations, Chroma 32\n' + description: 'Hue are intentionally different from the ' + 'seed colors', + configDetails: 'Primary - Hue rotated 240 degrees, Chroma 40\n' + 'Secondary - Chroma 24, Hue rotated 20-95 degrees\n' + 'Tertiary - Chroma 32, Hue rotated 20-120 degrees\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' - 'Neutral - Hue +15, Chroma 8\n' - 'Neutral variant - Hue +15, Chroma 12\n' + 'Neutral - Hue rotated 15 degrees, Chroma 8\n' + 'Neutral variant - Hue rotated 15 degrees, Chroma 12\n' 'Variant style: Material Color Utilities (MCU)', icon: Icons.shuffle_on_outlined, shade: 0, @@ -190,8 +191,8 @@ enum FlexSchemeVariant { 'Secondary - Max of: chroma from key -32 or *0.5\n' 'Tertiary - TemperatureCache analogous last\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' - 'Neutral - Chroma from key /8\n' - 'Neutral variant - Chroma from key /8 +4\n' + 'Neutral - Chroma from key div 8\n' + 'Neutral variant - Chroma from key div 8 plus 4\n' 'Variant style: Material Color Utilities (MCU)', icon: Icons.image_outlined, shade: 0, @@ -204,7 +205,7 @@ enum FlexSchemeVariant { description: "A playful theme, the seed color's chroma is fixed", configDetails: 'Primary - Chroma 48\n' 'Secondary - Chroma 16\n' - 'Tertiary - Hue +60, Chroma 24\n' + 'Tertiary - Hue+60 or seed hue, Chroma 24\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' 'Neutral - Chroma 0\n' 'Neutral variant - Chroma 0\n' @@ -217,9 +218,10 @@ enum FlexSchemeVariant { /// A playful theme - the seed color's hue does not appear in the theme. fruitSalad( variantName: 'Fruit salad', - description: "A playful theme, the seed color's hue is not in the theme", - configDetails: 'Primary - Hue -50, Chroma 12\n' - 'Secondary - Hue -50, Chroma 36\n' + description: 'A playful theme, the primary and secondary seed color hues ' + 'are not in the theme', + configDetails: 'Primary - Hue rotated -50 degrees, Chroma 48\n' + 'Secondary - Hue rotated -50 degrees, Chroma 36\n' 'Tertiary - Chroma 36\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' 'Neutral - Chroma 10\n' @@ -251,11 +253,11 @@ enum FlexSchemeVariant { variantName: 'Material-3', description: 'Material-3 design tones and chroma setup', configDetails: 'Primary - Chroma from key color, but min 36\n' - 'Secondary - Chroma set to 16\n' - 'Tertiary - Chroma set to 24\n' + 'Secondary - Chroma 16\n' + 'Tertiary - Hue rotated 60 degrees or key hue, Chroma 24\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' - 'Neutral - Chroma set to 6\n' - 'Neutral variant - Chroma set to 8\n' + 'Neutral - Chroma 6\n' + 'Neutral variant - Chroma 8\n' 'Variant style: Flex Seed Scheme (FSS)', icon: Icons.looks_3_outlined, shade: -6, @@ -280,8 +282,8 @@ enum FlexSchemeVariant { description: 'Legacy Material-3 tones and chroma, used ' 'before Flutter 3.22', configDetails: 'Primary - Chroma from key color, but min 48\n' - 'Secondary - Chroma set to 16\n' - 'Tertiary - Chroma set to 24\n' + 'Secondary - Chroma 16\n' + 'Tertiary - Hue rotated 60 degrees or key hue, Chroma 24\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' 'Neutral - Chroma set to 4\n' 'Neutral variant - Chroma set to 8\n' @@ -301,7 +303,7 @@ enum FlexSchemeVariant { description: 'Softer and more earth like tones than Material-3 defaults', configDetails: 'Primary - Chroma set to 30\n' 'Secondary - Chroma set to 14\n' - 'Tertiary - Chroma set to 20\n' + 'Tertiary - Hue rotated 60 degrees or key hue, Chroma set to 20\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' 'Neutral - Chroma set to 4\n' 'Neutral variant - Chroma set to 8\n' @@ -324,7 +326,7 @@ enum FlexSchemeVariant { description: 'More vivid colors than Material-3 defaults', configDetails: 'Primary - Chroma from key color, but min 50\n' 'Secondary - Chroma from key color\n' - 'Tertiary - Chroma from key color\n' + 'Tertiary - Hue rotated 60 degrees or key hue, Chroma from key color\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' 'Neutral - Chroma set to 4\n' 'Neutral variant - Chroma set to 8\n' @@ -356,7 +358,7 @@ enum FlexSchemeVariant { description: 'Like Vivid, but more colorful surfaces and containers', configDetails: 'Primary - Chroma from key color, but min 50\n' 'Secondary - Chroma from key color\n' - 'Tertiary - Chroma from key color\n' + 'Tertiary - Hue rotated 60 degrees or key hue, Chroma from key color\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' 'Neutral - Chroma set to 5\n' 'Neutral variant - Chroma set to 10\n' @@ -387,7 +389,8 @@ enum FlexSchemeVariant { description: 'High contrast theme, useful for accessibility', configDetails: 'Primary - Chroma from key color, but min 65\n' 'Secondary - Chroma from key color, but min 55\n' - 'Tertiary - Chroma from key color, but min 55\n' + 'Tertiary - Hue rotated 60 degrees or key hue, ' + 'Chroma from key color, but min 55\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' 'Neutral - Chroma set to 4\n' 'Neutral variant - Chroma set to 8\n' @@ -404,7 +407,8 @@ enum FlexSchemeVariant { description: 'Ultra high contrast theme, useful for accessibility', configDetails: 'Primary - Chroma from key color, but min 60\n' 'Secondary - Chroma from key color, but min 70\n' - 'Tertiary - Chroma from key color, but min 65\n' + 'Tertiary - Hue rotated 60 degrees or key hue, ' + 'Chroma from key color, but min 65\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' 'Neutral - Chroma set to 3\n' 'Neutral variant - Chroma set to 6\n' @@ -421,7 +425,7 @@ enum FlexSchemeVariant { description: 'Jolly colors with more chroma and pop', configDetails: 'Primary - Chroma from key color, but min 55\n' 'Secondary - Chroma from key color, but min 40\n' - 'Tertiary - Chroma set to 40\n' + 'Tertiary - Hue rotated 60 degrees or key hue, Chroma 40\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' 'Neutral - Chroma set to 6\n' 'Neutral variant - Chroma set to 10\n' @@ -443,7 +447,7 @@ enum FlexSchemeVariant { 'and deprecated background color swapped', configDetails: 'Primary - Chroma from key color, but min 50\n' 'Secondary - Chroma from key color\n' - 'Tertiary - Chroma from key color\n' + 'Tertiary - Hue rotated 60 degrees or key hue, Chroma from key color\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' 'Neutral - Chroma set to 5\n' 'Neutral variant - Chroma set to 10\n' @@ -487,12 +491,12 @@ enum FlexSchemeVariant { /// surface and background tone 6. candyPop( variantName: 'Candy pop', - description: 'High contrast candy like colors. Background and surface are ' - 'white in light mode an only only a slight primary tint in dark mode. ' + description: 'High contrast candy like colors. ' 'Neutrals have low chroma', configDetails: 'Primary - Chroma from key color, but min 60\n' 'Secondary - Chroma from key color, but min 44\n' - 'Tertiary - Chroma from key color, but min 50\n' + 'Tertiary - Hue rotated 60 degrees or key hue, ' + 'Chroma from key color, but min 50\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' 'Neutral - Chroma set to 2\n' 'Neutral variant - Chroma set to 4\n' @@ -516,7 +520,8 @@ enum FlexSchemeVariant { 'for manual control of chromacity. Neutrals have low chroma', configDetails: 'Primary - Chroma from key color, min 0\n' 'Secondary - Chroma from key color, min 0\n' - 'Tertiary - Chroma from key color, min 0\n' + 'Tertiary - Hue rotated 60 degrees or key hue, ' + 'Chroma from key color, min 0\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' 'Neutral - Chroma set to 2 (L), 3 (D)\n' 'Neutral variant - Chroma set to 4 (L), 6 (D)\n' diff --git a/lib/src/flex/flex_seed_scheme.dart b/lib/src/flex/flex_seed_scheme.dart index a7e19da..19c18a5 100644 --- a/lib/src/flex/flex_seed_scheme.dart +++ b/lib/src/flex/flex_seed_scheme.dart @@ -581,6 +581,7 @@ extension SeedColorScheme on ColorScheme { /// /// If not provided, a setup matching the Material 3 Color System /// specification is used by defaulting to [FlexTones.material]. + /// /// To create seed generated [ColorScheme] with /// different chroma limits and tonal mappings provide a custom [FlexTones], /// or use a predefined one like [FlexTones.jolly], [FlexTones.vivid] or @@ -588,7 +589,7 @@ extension SeedColorScheme on ColorScheme { /// /// Starting with version 2.0.0 you can also use [variant] as an optional /// way to select a predefined seed generation configuration, instead of - /// providing a [FlexTones] configuration. The [variant] API is also used + /// providing a [FlexTones] configuration. The [variant] API is used /// to provide access to the DynamicSchemeVariant that are available /// in Flutter 3.22.2 and later. With FSS you can use them in /// Flutter 3.22.0 already. @@ -603,17 +604,21 @@ extension SeedColorScheme on ColorScheme { /// The [variant] selections includes all the Flutter SDK defined options /// are available in the in Flutter Stable 3.22.2 and later. Variant options /// that are identical to the Flutter SDK options - /// have [FlexSchemeVariant.value], [isFlutterScheme] set to true. These - /// enum options will not respect and use any other seed generation key - /// color than the [primaryKey], as they only support using one seed color. + /// have [FlexSchemeVariant.value], [isFlutterScheme] set to true. Starting + /// with FSS version 3.0.0 these enum options can also use all the seed + /// generation key colors, not just the [primaryKey]. The standard MCU lib + /// only support using one seed color. FSS includes a forked MCU library + /// that now enables using up to six seed colors, providing more degrees of + /// freedom also with MCU based scheme variants not just with FlexTones + /// based ones. /// /// The [FlexSchemeVariant] also includes quick selections for all the /// predefined [FlexTones] configurations. However, with [variant] you can - /// only select one of the predefined configurations, and not make custom + /// only use the predefined configurations, and not make custom /// configurations like you can with [FlexTones]. Additionally you cannot - /// use the [FlexTones] modifiers [onMainsUseBW], [onSurfacesUseBW] and - /// [surfacesUseBW], since the only operate on the [FlexTones] - /// configurations passed in to [tones]. + /// use the [FlexTones] modifiers [monochromeSurfaces], [onMainsUseBW], + /// [onSurfacesUseBW] and [surfacesUseBW], since they operate on the + /// [FlexTones] configurations passed in to [tones]. FlexSchemeVariant? variant, /// The [contrastLevel] parameter indicates the contrast level between color @@ -809,8 +814,17 @@ extension SeedColorScheme on ColorScheme { 'Only one of tones or variant can be provided, not both.'); if (variant != null && variant.isFlutterScheme) { - final DynamicScheme scheme = - buildDynamicScheme(brightness, primaryKey, variant, contrastLevel); + final DynamicScheme scheme = buildDynamicScheme( + brightness: brightness, + primarySeedColor: primaryKey, + secondarySeedColor: secondaryKey, + tertiarySeedColor: tertiaryKey, + errorSeedColor: errorKey, + neutralSeedColor: neutralKey, + neutralVariantSeedColor: neutralVariantKey, + variant: variant, + contrastLevel: contrastLevel, + ); return ColorScheme( primary: primary ?? Color(MaterialDynamicColors.primary.getArgb(scheme)), @@ -1005,15 +1019,36 @@ extension SeedColorScheme on ColorScheme { /// /// If used with a FlexTones based [FlexSchemeVariant] variant it returns /// tonalSpot, the default Material-3 SDK style. - static DynamicScheme buildDynamicScheme( - Brightness brightness, Color seedColor, FlexSchemeVariant variant, - [double contrastLevel = 0.0]) { + static DynamicScheme buildDynamicScheme({ + required Brightness brightness, + required FlexSchemeVariant variant, + required Color primarySeedColor, + Color? secondarySeedColor, + Color? tertiarySeedColor, + Color? errorSeedColor, + Color? neutralSeedColor, + Color? neutralVariantSeedColor, + double contrastLevel = 0.0, + }) { assert( contrastLevel >= -1.0 && contrastLevel <= 1.0, 'contrastLevel must be between -1.0 and 1.0 inclusive.', ); final bool isDark = brightness == Brightness.dark; - final Hct sourceColor = Hct.fromInt(seedColor.value); + final Hct primarySourceColor = Hct.fromInt(primarySeedColor.value); + final Hct? secondarySourceColor = secondarySeedColor != null + ? Hct.fromInt(secondarySeedColor.value) + : null; + final Hct? tertiarySourceColor = + tertiarySeedColor != null ? Hct.fromInt(tertiarySeedColor.value) : null; + final Hct? neutralSourceColor = + neutralSeedColor != null ? Hct.fromInt(neutralSeedColor.value) : null; + final Hct? neutralVariantSourceColor = neutralVariantSeedColor != null + ? Hct.fromInt(neutralVariantSeedColor.value) + : null; + final Hct? errorSourceColor = + errorSeedColor != null ? Hct.fromInt(errorSeedColor.value) : null; + return switch (variant) { FlexSchemeVariant.material || FlexSchemeVariant.material3Legacy || @@ -1029,39 +1064,80 @@ extension SeedColorScheme on ColorScheme { FlexSchemeVariant.chroma || FlexSchemeVariant.tonalSpot => SchemeTonalSpot( - sourceColorHct: sourceColor, + sourceColorHct: primarySourceColor, + secondarySourceColorHct: secondarySourceColor, + tertiarySourceColorHct: tertiarySourceColor, + neutralSourceColorHct: neutralSourceColor, + neutralVariantSourceColorHct: neutralVariantSourceColor, + errorSourceColorHct: errorSourceColor, isDark: isDark, contrastLevel: contrastLevel), FlexSchemeVariant.fidelity => SchemeFidelity( - sourceColorHct: sourceColor, + sourceColorHct: primarySourceColor, + secondarySourceColorHct: secondarySourceColor, + tertiarySourceColorHct: tertiarySourceColor, + neutralSourceColorHct: neutralSourceColor, + neutralVariantSourceColorHct: neutralVariantSourceColor, + errorSourceColorHct: errorSourceColor, isDark: isDark, contrastLevel: contrastLevel), FlexSchemeVariant.content => SchemeContent( - sourceColorHct: sourceColor, + sourceColorHct: primarySourceColor, + secondarySourceColorHct: secondarySourceColor, + tertiarySourceColorHct: tertiarySourceColor, + neutralSourceColorHct: neutralSourceColor, + neutralVariantSourceColorHct: neutralVariantSourceColor, + errorSourceColorHct: errorSourceColor, isDark: isDark, contrastLevel: contrastLevel), FlexSchemeVariant.monochrome => SchemeMonochrome( - sourceColorHct: sourceColor, + sourceColorHct: primarySourceColor, + errorSourceColorHct: errorSourceColor, isDark: isDark, contrastLevel: contrastLevel), FlexSchemeVariant.neutral => SchemeNeutral( - sourceColorHct: sourceColor, + sourceColorHct: primarySourceColor, + secondarySourceColorHct: secondarySourceColor, + tertiarySourceColorHct: tertiarySourceColor, + neutralSourceColorHct: neutralSourceColor, + neutralVariantSourceColorHct: neutralVariantSourceColor, + errorSourceColorHct: errorSourceColor, isDark: isDark, contrastLevel: contrastLevel), FlexSchemeVariant.vibrant => SchemeVibrant( - sourceColorHct: sourceColor, + sourceColorHct: primarySourceColor, + secondarySourceColorHct: secondarySourceColor, + tertiarySourceColorHct: tertiarySourceColor, + neutralSourceColorHct: neutralSourceColor, + neutralVariantSourceColorHct: neutralVariantSourceColor, + errorSourceColorHct: errorSourceColor, isDark: isDark, contrastLevel: contrastLevel), FlexSchemeVariant.expressive => SchemeExpressive( - sourceColorHct: sourceColor, + sourceColorHct: primarySourceColor, + secondarySourceColorHct: secondarySourceColor, + tertiarySourceColorHct: tertiarySourceColor, + neutralSourceColorHct: neutralSourceColor, + neutralVariantSourceColorHct: neutralVariantSourceColor, + errorSourceColorHct: errorSourceColor, isDark: isDark, contrastLevel: contrastLevel), FlexSchemeVariant.rainbow => SchemeRainbow( - sourceColorHct: sourceColor, + sourceColorHct: primarySourceColor, + secondarySourceColorHct: secondarySourceColor, + tertiarySourceColorHct: tertiarySourceColor, + neutralSourceColorHct: neutralSourceColor, + neutralVariantSourceColorHct: neutralVariantSourceColor, + errorSourceColorHct: errorSourceColor, isDark: isDark, contrastLevel: contrastLevel), FlexSchemeVariant.fruitSalad => SchemeFruitSalad( - sourceColorHct: sourceColor, + sourceColorHct: primarySourceColor, + secondarySourceColorHct: secondarySourceColor, + tertiarySourceColorHct: tertiarySourceColor, + neutralSourceColorHct: neutralSourceColor, + neutralVariantSourceColorHct: neutralVariantSourceColor, + errorSourceColorHct: errorSourceColor, isDark: isDark, contrastLevel: contrastLevel), }; diff --git a/lib/src/mcu/dynamiccolor/dynamic_scheme.dart b/lib/src/mcu/dynamiccolor/dynamic_scheme.dart index b523193..493dbe6 100644 --- a/lib/src/mcu/dynamiccolor/dynamic_scheme.dart +++ b/lib/src/mcu/dynamiccolor/dynamic_scheme.dart @@ -76,8 +76,9 @@ class DynamicScheme { required this.tertiaryPalette, required this.neutralPalette, required this.neutralVariantPalette, + TonalPalette? customErrorPalette, }) : sourceColorHct = Hct.fromInt(sourceColorArgb), - errorPalette = TonalPalette.of(25.0, 84.0); + errorPalette = customErrorPalette ?? TonalPalette.of(25.0, 84.0); /// Get the the rotated hue of the source color. static double getRotatedHue( diff --git a/lib/src/mcu/scheme/scheme_content.dart b/lib/src/mcu/scheme/scheme_content.dart index c2984bd..2baa835 100644 --- a/lib/src/mcu/scheme/scheme_content.dart +++ b/lib/src/mcu/scheme/scheme_content.dart @@ -36,6 +36,11 @@ class SchemeContent extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + Hct? secondarySourceColorHct, + Hct? tertiarySourceColorHct, + Hct? neutralSourceColorHct, + Hct? neutralVariantSourceColorHct, + Hct? errorSourceColorHct, }) : super( sourceColorArgb: sourceColorHct.toInt(), variant: Variant.content, @@ -43,24 +48,42 @@ class SchemeContent extends DynamicScheme { sourceColorHct.hue, sourceColorHct.chroma, ), - secondaryPalette: TonalPalette.of( - sourceColorHct.hue, - math.max(sourceColorHct.chroma - 32.0, sourceColorHct.chroma * 0.5), - ), - tertiaryPalette: TonalPalette.fromHct( - DislikeAnalyzer.fixIfDisliked( - TemperatureCache(sourceColorHct) - .analogous(count: 3, divisions: 6) - .last, - ), - ), + secondaryPalette: secondarySourceColorHct != null + ? TonalPalette.of( + secondarySourceColorHct.hue, + math.max(secondarySourceColorHct.chroma - 32.0, + secondarySourceColorHct.chroma * 0.5), + ) + : TonalPalette.of( + sourceColorHct.hue, + math.max(sourceColorHct.chroma - 32.0, + sourceColorHct.chroma * 0.5), + ), + tertiaryPalette: tertiarySourceColorHct != null + ? TonalPalette.of( + tertiarySourceColorHct.hue, + tertiarySourceColorHct.chroma, + ) + : TonalPalette.fromHct( + DislikeAnalyzer.fixIfDisliked( + TemperatureCache(sourceColorHct) + .analogous(count: 3, divisions: 6) + .last, + ), + ), neutralPalette: TonalPalette.of( - sourceColorHct.hue, - sourceColorHct.chroma / 8.0, + neutralSourceColorHct?.hue ?? sourceColorHct.hue, + (neutralSourceColorHct?.chroma ?? sourceColorHct.chroma) / 8.0, ), neutralVariantPalette: TonalPalette.of( - sourceColorHct.hue, - (sourceColorHct.chroma / 8.0) + 4.0, + neutralVariantSourceColorHct?.hue ?? sourceColorHct.hue, + ((neutralVariantSourceColorHct?.chroma ?? sourceColorHct.chroma) / + 8.0) + + 4.0, ), + customErrorPalette: errorSourceColorHct == null + ? null + : TonalPalette.of( + errorSourceColorHct.hue, errorSourceColorHct.chroma), ); } diff --git a/lib/src/mcu/scheme/scheme_expressive.dart b/lib/src/mcu/scheme/scheme_expressive.dart index 3f0a821..29a5e95 100644 --- a/lib/src/mcu/scheme/scheme_expressive.dart +++ b/lib/src/mcu/scheme/scheme_expressive.dart @@ -67,6 +67,11 @@ class SchemeExpressive extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + Hct? secondarySourceColorHct, + Hct? tertiarySourceColorHct, + Hct? neutralSourceColorHct, + Hct? neutralVariantSourceColorHct, + Hct? errorSourceColorHct, }) : super( sourceColorArgb: sourceColorHct.toInt(), variant: Variant.expressive, @@ -76,16 +81,26 @@ class SchemeExpressive extends DynamicScheme { ), secondaryPalette: TonalPalette.of( DynamicScheme.getRotatedHue( - sourceColorHct, hues, secondaryRotations), + secondarySourceColorHct ?? sourceColorHct, + hues, + secondaryRotations), 24.0, ), tertiaryPalette: TonalPalette.of( DynamicScheme.getRotatedHue( - sourceColorHct, hues, tertiaryRotations), + tertiarySourceColorHct ?? sourceColorHct, + hues, + tertiaryRotations), 32.0, ), - neutralPalette: TonalPalette.of(sourceColorHct.hue + 15.0, 8.0), - neutralVariantPalette: - TonalPalette.of(sourceColorHct.hue + 15.0, 12.0), + neutralPalette: TonalPalette.of( + (neutralSourceColorHct?.hue ?? sourceColorHct.hue) + 15.0, 8.0), + neutralVariantPalette: TonalPalette.of( + (neutralVariantSourceColorHct?.hue ?? sourceColorHct.hue) + 15.0, + 12.0), + customErrorPalette: errorSourceColorHct == null + ? null + : TonalPalette.of( + errorSourceColorHct.hue, errorSourceColorHct.chroma), ); } diff --git a/lib/src/mcu/scheme/scheme_fidelity.dart b/lib/src/mcu/scheme/scheme_fidelity.dart index a5dd025..3ef24a9 100644 --- a/lib/src/mcu/scheme/scheme_fidelity.dart +++ b/lib/src/mcu/scheme/scheme_fidelity.dart @@ -35,6 +35,11 @@ class SchemeFidelity extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + Hct? secondarySourceColorHct, + Hct? tertiarySourceColorHct, + Hct? neutralSourceColorHct, + Hct? neutralVariantSourceColorHct, + Hct? errorSourceColorHct, }) : super( sourceColorArgb: sourceColorHct.toInt(), variant: Variant.fidelity, @@ -42,22 +47,40 @@ class SchemeFidelity extends DynamicScheme { sourceColorHct.hue, sourceColorHct.chroma, ), - secondaryPalette: TonalPalette.of( - sourceColorHct.hue, - math.max(sourceColorHct.chroma - 32.0, sourceColorHct.chroma * 0.5), - ), - tertiaryPalette: TonalPalette.fromHct( - DislikeAnalyzer.fixIfDisliked( - TemperatureCache(sourceColorHct).complement, - ), - ), + secondaryPalette: secondarySourceColorHct != null + ? TonalPalette.of( + secondarySourceColorHct.hue, + math.max(secondarySourceColorHct.chroma - 32.0, + secondarySourceColorHct.chroma * 0.5), + ) + : TonalPalette.of( + sourceColorHct.hue, + math.max(sourceColorHct.chroma - 32.0, + sourceColorHct.chroma * 0.5), + ), + tertiaryPalette: tertiarySourceColorHct != null + ? TonalPalette.of( + tertiarySourceColorHct.hue, + tertiarySourceColorHct.chroma, + ) + : TonalPalette.fromHct( + DislikeAnalyzer.fixIfDisliked( + TemperatureCache(sourceColorHct).complement, + ), + ), neutralPalette: TonalPalette.of( - sourceColorHct.hue, - sourceColorHct.chroma / 8.0, + neutralSourceColorHct?.hue ?? sourceColorHct.hue, + (neutralSourceColorHct?.chroma ?? sourceColorHct.chroma) / 8.0, ), neutralVariantPalette: TonalPalette.of( - sourceColorHct.hue, - (sourceColorHct.chroma / 8.0) + 4.0, + neutralVariantSourceColorHct?.hue ?? sourceColorHct.hue, + ((neutralVariantSourceColorHct?.chroma ?? sourceColorHct.chroma) / + 8.0) + + 4.0, ), + customErrorPalette: errorSourceColorHct == null + ? null + : TonalPalette.of( + errorSourceColorHct.hue, errorSourceColorHct.chroma), ); } diff --git a/lib/src/mcu/scheme/scheme_fruit_salad.dart b/lib/src/mcu/scheme/scheme_fruit_salad.dart index 099034e..9721d7e 100644 --- a/lib/src/mcu/scheme/scheme_fruit_salad.dart +++ b/lib/src/mcu/scheme/scheme_fruit_salad.dart @@ -25,6 +25,11 @@ class SchemeFruitSalad extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + Hct? secondarySourceColorHct, + Hct? tertiarySourceColorHct, + Hct? neutralSourceColorHct, + Hct? neutralVariantSourceColorHct, + Hct? errorSourceColorHct, }) : super( sourceColorArgb: sourceColorHct.toInt(), variant: Variant.fruitSalad, @@ -33,20 +38,25 @@ class SchemeFruitSalad extends DynamicScheme { 48.0, ), secondaryPalette: TonalPalette.of( - MathUtils.sanitizeDegreesDouble(sourceColorHct.hue - 50.0), + MathUtils.sanitizeDegreesDouble( + (secondarySourceColorHct?.hue ?? sourceColorHct.hue) - 50.0), 36.0, ), tertiaryPalette: TonalPalette.of( - sourceColorHct.hue, + tertiarySourceColorHct?.hue ?? sourceColorHct.hue, 36.0, ), neutralPalette: TonalPalette.of( - sourceColorHct.hue, + neutralSourceColorHct?.hue ?? sourceColorHct.hue, 10.0, ), neutralVariantPalette: TonalPalette.of( - sourceColorHct.hue, + neutralVariantSourceColorHct?.hue ?? sourceColorHct.hue, 16.0, ), + customErrorPalette: errorSourceColorHct == null + ? null + : TonalPalette.of( + errorSourceColorHct.hue, errorSourceColorHct.chroma), ); } diff --git a/lib/src/mcu/scheme/scheme_monochrome.dart b/lib/src/mcu/scheme/scheme_monochrome.dart index a7ae51b..b0a8b52 100644 --- a/lib/src/mcu/scheme/scheme_monochrome.dart +++ b/lib/src/mcu/scheme/scheme_monochrome.dart @@ -23,6 +23,7 @@ class SchemeMonochrome extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + Hct? errorSourceColorHct, }) : super( sourceColorArgb: sourceColorHct.toInt(), variant: Variant.monochrome, @@ -31,5 +32,9 @@ class SchemeMonochrome extends DynamicScheme { tertiaryPalette: TonalPalette.of(sourceColorHct.hue, 0.0), neutralPalette: TonalPalette.of(sourceColorHct.hue, 0.0), neutralVariantPalette: TonalPalette.of(sourceColorHct.hue, 0.0), + customErrorPalette: errorSourceColorHct == null + ? null + : TonalPalette.of( + errorSourceColorHct.hue, errorSourceColorHct.chroma), ); } diff --git a/lib/src/mcu/scheme/scheme_neutral.dart b/lib/src/mcu/scheme/scheme_neutral.dart index c3fed1f..6bd1c60 100644 --- a/lib/src/mcu/scheme/scheme_neutral.dart +++ b/lib/src/mcu/scheme/scheme_neutral.dart @@ -24,13 +24,26 @@ class SchemeNeutral extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + Hct? secondarySourceColorHct, + Hct? tertiarySourceColorHct, + Hct? neutralSourceColorHct, + Hct? neutralVariantSourceColorHct, + Hct? errorSourceColorHct, }) : super( sourceColorArgb: sourceColorHct.toInt(), variant: Variant.neutral, primaryPalette: TonalPalette.of(sourceColorHct.hue, 12.0), - secondaryPalette: TonalPalette.of(sourceColorHct.hue, 8.0), - tertiaryPalette: TonalPalette.of(sourceColorHct.hue, 16.0), - neutralPalette: TonalPalette.of(sourceColorHct.hue, 2.0), - neutralVariantPalette: TonalPalette.of(sourceColorHct.hue, 2.0), + secondaryPalette: TonalPalette.of( + secondarySourceColorHct?.hue ?? sourceColorHct.hue, 8.0), + tertiaryPalette: TonalPalette.of( + tertiarySourceColorHct?.hue ?? sourceColorHct.hue, 16.0), + neutralPalette: TonalPalette.of( + neutralSourceColorHct?.hue ?? sourceColorHct.hue, 2.0), + neutralVariantPalette: TonalPalette.of( + neutralVariantSourceColorHct?.hue ?? sourceColorHct.hue, 2.0), + customErrorPalette: errorSourceColorHct == null + ? null + : TonalPalette.of( + errorSourceColorHct.hue, errorSourceColorHct.chroma), ); } diff --git a/lib/src/mcu/scheme/scheme_rainbow.dart b/lib/src/mcu/scheme/scheme_rainbow.dart index fd0d8b3..26ea247 100644 --- a/lib/src/mcu/scheme/scheme_rainbow.dart +++ b/lib/src/mcu/scheme/scheme_rainbow.dart @@ -29,6 +29,11 @@ class SchemeRainbow extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + Hct? secondarySourceColorHct, + Hct? tertiarySourceColorHct, + Hct? neutralSourceColorHct, + Hct? neutralVariantSourceColorHct, + Hct? errorSourceColorHct, }) : super( sourceColorArgb: sourceColorHct.toInt(), variant: Variant.rainbow, @@ -36,21 +41,35 @@ class SchemeRainbow extends DynamicScheme { sourceColorHct.hue, 48.0, ), - secondaryPalette: TonalPalette.of( - sourceColorHct.hue, - 16.0, - ), - tertiaryPalette: TonalPalette.of( - MathUtils.sanitizeDegreesDouble(sourceColorHct.hue + 60.0), - 24.0, - ), + secondaryPalette: secondarySourceColorHct != null + ? TonalPalette.of( + secondarySourceColorHct.hue, + 16.0, + ) + : TonalPalette.of( + sourceColorHct.hue, + 16.0, + ), + tertiaryPalette: tertiarySourceColorHct != null + ? TonalPalette.of( + tertiarySourceColorHct.hue, + 24.0, + ) + : TonalPalette.of( + MathUtils.sanitizeDegreesDouble(sourceColorHct.hue + 60.0), + 24.0, + ), neutralPalette: TonalPalette.of( - sourceColorHct.hue, + neutralSourceColorHct?.hue ?? sourceColorHct.hue, 0.0, ), neutralVariantPalette: TonalPalette.of( - sourceColorHct.hue, + neutralVariantSourceColorHct?.hue ?? sourceColorHct.hue, 0.0, ), + customErrorPalette: errorSourceColorHct == null + ? null + : TonalPalette.of( + errorSourceColorHct.hue, errorSourceColorHct.chroma), ); } diff --git a/lib/src/mcu/scheme/scheme_tonal_spot.dart b/lib/src/mcu/scheme/scheme_tonal_spot.dart index 5b7388a..33b1ee1 100644 --- a/lib/src/mcu/scheme/scheme_tonal_spot.dart +++ b/lib/src/mcu/scheme/scheme_tonal_spot.dart @@ -27,16 +27,29 @@ class SchemeTonalSpot extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + Hct? secondarySourceColorHct, + Hct? tertiarySourceColorHct, + Hct? neutralSourceColorHct, + Hct? neutralVariantSourceColorHct, + Hct? errorSourceColorHct, }) : super( sourceColorArgb: sourceColorHct.toInt(), variant: Variant.tonalSpot, primaryPalette: TonalPalette.of(sourceColorHct.hue, 36.0), - secondaryPalette: TonalPalette.of(sourceColorHct.hue, 16.0), + secondaryPalette: TonalPalette.of( + secondarySourceColorHct?.hue ?? sourceColorHct.hue, 16.0), tertiaryPalette: TonalPalette.of( - MathUtils.sanitizeDegreesDouble(sourceColorHct.hue + 60.0), + MathUtils.sanitizeDegreesDouble( + tertiarySourceColorHct?.hue ?? (sourceColorHct.hue + 60.0)), 24.0, ), - neutralPalette: TonalPalette.of(sourceColorHct.hue, 6.0), - neutralVariantPalette: TonalPalette.of(sourceColorHct.hue, 8.0), + neutralPalette: TonalPalette.of( + neutralSourceColorHct?.hue ?? sourceColorHct.hue, 6.0), + neutralVariantPalette: TonalPalette.of( + neutralVariantSourceColorHct?.hue ?? sourceColorHct.hue, 8.0), + customErrorPalette: errorSourceColorHct == null + ? null + : TonalPalette.of( + errorSourceColorHct.hue, errorSourceColorHct.chroma), ); } diff --git a/lib/src/mcu/scheme/scheme_vibrant.dart b/lib/src/mcu/scheme/scheme_vibrant.dart index d55560b..c435484 100644 --- a/lib/src/mcu/scheme/scheme_vibrant.dart +++ b/lib/src/mcu/scheme/scheme_vibrant.dart @@ -72,21 +72,36 @@ class SchemeVibrant extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + Hct? secondarySourceColorHct, + Hct? tertiarySourceColorHct, + Hct? neutralSourceColorHct, + Hct? neutralVariantSourceColorHct, + Hct? errorSourceColorHct, }) : super( sourceColorArgb: sourceColorHct.toInt(), variant: Variant.vibrant, primaryPalette: TonalPalette.of(sourceColorHct.hue, 200.0), secondaryPalette: TonalPalette.of( DynamicScheme.getRotatedHue( - sourceColorHct, hues, secondaryRotations), + secondarySourceColorHct ?? sourceColorHct, + hues, + secondaryRotations), 24.0, ), tertiaryPalette: TonalPalette.of( DynamicScheme.getRotatedHue( - sourceColorHct, hues, tertiaryRotations), + tertiarySourceColorHct ?? sourceColorHct, + hues, + tertiaryRotations), 32.0, ), - neutralPalette: TonalPalette.of(sourceColorHct.hue, 10.0), - neutralVariantPalette: TonalPalette.of(sourceColorHct.hue, 12.0), + neutralPalette: TonalPalette.of( + neutralSourceColorHct?.hue ?? sourceColorHct.hue, 10.0), + neutralVariantPalette: TonalPalette.of( + neutralVariantSourceColorHct?.hue ?? sourceColorHct.hue, 12.0), + customErrorPalette: errorSourceColorHct == null + ? null + : TonalPalette.of( + errorSourceColorHct.hue, errorSourceColorHct.chroma), ); } diff --git a/pubspec.yaml b/pubspec.yaml index a1a61d6..1a5b160 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flex_seed_scheme description: A more flexible and powerful version of Flutter's ColorScheme.fromSeed. Use multiple seed colors, custom chroma and tone mapping. -version: 2.1.0-dev.2 +version: 3.0.0 homepage: https://github.com/rydmike/flex_seed_scheme repository: https://github.com/rydmike/flex_seed_scheme issue_tracker: https://github.com/rydmike/flex_seed_scheme/issues diff --git a/test/flex_seed_scheme_test.dart b/test/flex_seed_scheme_test.dart index 5d48af3..a470b1a 100644 --- a/test/flex_seed_scheme_test.dart +++ b/test/flex_seed_scheme_test.dart @@ -751,7 +751,9 @@ void main() { const Color seedColor = Colors.orange; for (final FlexSchemeVariant schemeVariant in FlexSchemeVariant.values) { final DynamicScheme dynamicScheme = SeedColorScheme.buildDynamicScheme( - Brightness.light, seedColor, schemeVariant); + brightness: Brightness.light, + primarySeedColor: seedColor, + variant: schemeVariant); final ColorScheme colorScheme = SeedColorScheme.fromSeeds( primaryKey: seedColor, variant: schemeVariant, From e1ec1abc7f2fda234bb56f47a904de4e0c4e0160 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Fri, 21 Jun 2024 00:18:26 +0300 Subject: [PATCH 21/37] Doc updates and config label --- example/pubspec.lock | 2 +- lib/src/flex/flex_scheme_variant.dart | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 3894240..681fed5 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -71,7 +71,7 @@ packages: path: ".." relative: true source: path - version: "2.1.0-dev.2" + version: "3.0.0" flutter: dependency: "direct main" description: flutter diff --git a/lib/src/flex/flex_scheme_variant.dart b/lib/src/flex/flex_scheme_variant.dart index 9453bc2..0768c42 100644 --- a/lib/src/flex/flex_scheme_variant.dart +++ b/lib/src/flex/flex_scheme_variant.dart @@ -9,11 +9,11 @@ import 'flex_tones.dart'; /// property variant. /// /// [FlexSchemeVariant] values that use [isFlutterScheme] set to true, use the -/// Flutter SDK algorithm to construct a [ColorScheme] identical to -/// [ColorScheme.fromSeed], or more exactly will do in next stable after -/// Flutter 3.22.x +/// Flutter SDK and MCU algorithm to construct a [ColorScheme] identical to +/// [ColorScheme.fromSeed] and its variant property that was added in +/// Flutter 3.22.2. /// -/// The [tonalSpot] is the only variant available in Flutter 3.22 and it builds +/// The [tonalSpot] is variant is the default Flutter 3.22 and later, it builds /// the default Material-3 style 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. @@ -28,10 +28,9 @@ import 'flex_tones.dart'; /// algorithm to construct a [ColorScheme] based on the provided seed colors. /// These variants are mapped to built-in [FlexTones] configurations that can /// be used to construct a [ColorScheme] with a more flexible algorithm than -/// the Flutter SDK's [ColorScheme.fromSeed]. A key feature is that all tonal -/// palettes can use their own seed color. +/// the Flutter SDK's [ColorScheme.fromSeed]. /// -/// By using [tones] function and a given [Brightness], it will returns the +/// By using the [tones] function and a given [Brightness], it will return the /// corresponding [FlexTones] made with same named [FlexTones] constructor, for /// cases where [isFlutterScheme] set to false. /// @@ -42,13 +41,17 @@ import 'flex_tones.dart'; /// /// This enum also contains labels for variant names, a short description and /// configuration details, as well as an icon for each tone and shade value to -/// adjust used color value on the icon. These properties can optionally be +/// adjust used color value on the icon. +/// +/// These properties can optionally be /// used when building UIs that present the different scheme variants. They /// serve no other purpose. They can also be ignored and you can use the enum /// values as input and use them to build your own UI for selecting and /// describing the scheme variants. These values are used in the example app -/// and also in the `FlexColorScheme` package example app, like the -/// Themes Playground. +/// and also in the `FlexColorScheme` package example apps, like the +/// Themes Playground. Any changes in releases in the enum property values +/// [variantName],[description], [configDetails], [icon] and [shade] are not +/// considered breaking changes, only patches. enum FlexSchemeVariant { /// A Dynamic Color theme with low to medium colorfulness and a Tertiary /// Tonal Palette]with a hue related to the source color. The default @@ -91,8 +94,8 @@ enum FlexSchemeVariant { 'Secondary - Max of: chroma from key -32 or *0.5\n' 'Tertiary - TemperatureCache complement hue or key hue and chroma\n' 'Error - Chroma from key, unbound. Default Hue 25, Chroma 84\n' - 'Neutral - Chroma from key/8\n' - 'Neutral variant - Chroma from key/8 +4\n' + 'Neutral - Chroma from key div 8\n' + 'Neutral variant - Chroma from key div 8 plus 4\n' 'Variant style: Material Color Utilities (MCU)', icon: Icons.grain_outlined, shade: 0, From 1043f44b5ad3110c1de991ea814df473852835de Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Fri, 21 Jun 2024 00:31:45 +0300 Subject: [PATCH 22/37] Changed `FlexTones.chroma` tone `secondaryTone` from 60 to 50 in light mode --- CHANGELOG.md | 4 +++- lib/src/flex/flex_tones.dart | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7043ec5..aaf6367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. * **BREAKING** * The API for `SeedColorScheme.buildDynamicScheme` was changed to enable support for multiple seed colors on the MCU based `DynamicScheme` APIs and its extended schemes. - + * **NEW** * Added support for `contrastLevel` to `SeedColorScheme.fromSeeds`. This allows you to set the contrast level of the generated color scheme, when using `SeedColorScheme.fromSeeds` with the `variant` property. The `contrastLevel` parameter indicates the contrast level between color pairs, such as `primary` and `onPrimary`.The value 0.0 is the default (normal); -1.0 is the lowest; 1.0 is the highest. From Material Design guideline, the medium and high contrast, correspond to 0.5 and 1.0 respectively. @@ -23,6 +23,8 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. * **COLOR VALUE BREAKING** + * Changed `FlexTones.chroma` tone `secondaryTone` from 60 to 50 in light mode for better chroma fidelity when using this `FlexTone`. + * Updated `MaterialDynamicColors` to use the correct expressive on-colors spec. This brings the internal Material Color Utilities (MCU) up to version 0.12.0 as published on pub. This changes the contrast curve for light/dark colors and dark mode color tone for the colors `onPrimaryContainer`, `onSecondaryContainer`, `onTertiaryContainer` and `onErrorContainer`. The dark mode tone is changed from 10 to 30, which is the correct spec. The mentioned colors also change slightly for light mode in some dynamic schemes due to the changed contrast curve, which was changed from `ContrastCurve(4.5, 7, 11, 21)` to `ContrastCurve(3, 4.5, 7, 11)` for the listed on container colors. * Prior to MCU version 0.12.0 the `MaterialDynamicColors` used the wrong spec. Flutter stable 3.22.x and Flutter master still use an MCU version lower than 0.12.0 and have the wrong on color tones in dark mode. This will be corrected in Flutter SDK when Flutter is updated to use MCU 0.12.0 or later. With FSS 2.1.0 you get the correct spec already now. However, the mentioned `MaterialDynamicColors` on container colors will now differ slightly in light mode and more in dark mode, from the wrong ones that are currently produced by MCU versions before 0.12.0 that Flutter SDK still uses. When Flutter SDK bumps its version to MCU 0.12.0 r later, the mentioned FSS produced colors will again be identical to the SDK produced values. diff --git a/lib/src/flex/flex_tones.dart b/lib/src/flex/flex_tones.dart index 50a4269..02a0930 100644 --- a/lib/src/flex/flex_tones.dart +++ b/lib/src/flex/flex_tones.dart @@ -830,7 +830,7 @@ class FlexTones with Diagnosticable { primaryTone: 40, primaryContainerTone: 80, onPrimaryContainerTone: 4, - secondaryTone: 60, + secondaryTone: 50, secondaryContainerTone: 92, onSecondaryContainerTone: 10, tertiaryTone: 50, From 70c64a31d87e1ab3b6de36b671f686bf56d7b788 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Fri, 21 Jun 2024 16:56:19 +0300 Subject: [PATCH 23/37] FEATURE: Make using the new expressive on colors optional and default to not using it for now. --- CHANGELOG.md | 52 ++++++--- example/lib/home/views/pages/home_page.dart | 41 ++++--- .../views/widgets/show_tonal_palette.dart | 1 + example/lib/main.dart | 2 +- .../theme/controllers/theme_controller.dart | 9 ++ example/lib/theme/model/app_theme.dart | 12 +- lib/src/flex/flex_seed_scheme.dart | 106 ++++++++++++++---- lib/src/flex/flex_tones.dart | 59 +++++++++- lib/src/mcu/dynamiccolor/dynamic_scheme.dart | 24 ++++ .../dynamiccolor/material_dynamic_colors.dart | 27 ++++- lib/src/mcu/scheme/scheme_content.dart | 1 + lib/src/mcu/scheme/scheme_expressive.dart | 1 + lib/src/mcu/scheme/scheme_fidelity.dart | 1 + lib/src/mcu/scheme/scheme_fruit_salad.dart | 1 + lib/src/mcu/scheme/scheme_monochrome.dart | 1 + lib/src/mcu/scheme/scheme_neutral.dart | 1 + lib/src/mcu/scheme/scheme_rainbow.dart | 1 + lib/src/mcu/scheme/scheme_tonal_spot.dart | 1 + lib/src/mcu/scheme/scheme_vibrant.dart | 1 + 19 files changed, 280 insertions(+), 62 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aaf6367..54b770a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,38 +4,56 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. ## 3.0.0 -**June 20, 2024** +**June 21, 2024** + +Bring the bundled forked version of the package [Material Color Utilities (MCU)](https://pub.dev/packages/material_color_utilities) to feature parity with version 0.12.0 of the original package. The internal fork for the first time nw also adds features and capabilities that do not exist in the original package. Previously FSS only bundled MCU to avoid version conflicts with Flutter SDK when using different channels. Different Flutter channels typically pin incompatible versions of MCU. + +The bundled forked version of MCU also has more tests than the original, allowing us to detect when any new MCU version "silently" changes color results it produced in the past. + * **BREAKING** - * The API for `SeedColorScheme.buildDynamicScheme` was changed to enable support for multiple seed colors on the MCU based `DynamicScheme` APIs and its extended schemes. + * The API for `SeedColorScheme.buildDynamicScheme` was changed to enable support for multiple seed colors on the MCU based `DynamicScheme` APIs and its extended schemes. For most normal use cases you will not notice this, as it is quite a low-level API in FSS that end users normally do not use. * **NEW** - * Added support for `contrastLevel` to `SeedColorScheme.fromSeeds`. This allows you to set the contrast level of the generated color scheme, when using `SeedColorScheme.fromSeeds` with the `variant` property. The `contrastLevel` parameter indicates the contrast level between color pairs, such as `primary` and `onPrimary`.The value 0.0 is the default (normal); -1.0 is the lowest; 1.0 is the highest. From Material Design guideline, the medium and high contrast, correspond to 0.5 and 1.0 respectively. - * The `contrastLevel` in Flutter SDK is not yet available in `ColorScheme.fromSeed` on Flutter stable 3.22.x, but is available on the master channel. With FSS you can use it already in Flutter 3.22.x. + * The forked internal MCU version received new features. First `DynamicScheme` can accept an optional `customErrorPalette` and then `SchemeTonalSpot`, `SchemeContent`, `SchemeFidelity`, `SchemeExpressive`, `SchemeFruitSalad`, `SchemeMonochrome`, `SchemeNeutral`, `SchemeRainbow` and `SchemeVibrant` that extend `DynamicScheme` all received properties to support individual seed colors for all tonal palettes. + + * The above addition enables `SeedColorScheme.fromSeeds` to support using all its key seed colors also when using MCU based `DynamicScheme` variants and not just for `FlexTones` based `tones` and `variants`. + + * Added support for `contrastLevel` to `SeedColorScheme.fromSeeds`. This allows you to set the desired contrast level of the generated color scheme when using `SeedColorScheme.fromSeeds` with the `variant` property, for variants that are based on MCU's `DynamicScheme`. Such variants have their `isFlutterScheme` set to true. + * The `contrastLevel` parameter indicates the contrast level between color pairs, such as `primary` and `onPrimary`.The value 0.0 is the default (normal); -1.0 is the lowest; 1.0 is the highest. From Material Design guideline, the medium and high contrast, correspond to 0.5 and 1.0 respectively. + * The `contrastLevel` in Flutter SDK is not yet available in `ColorScheme.fromSeed` in Flutter stable 3.22.x, but is available on the master channel 3.23.x. With FSS you can use it already in Flutter 3.22.x. * **NOTE:** Using `contrastLevel` has no effect when using `tones`. However, with `tones` you can create custom tones with even more flexibility in seed generation to make schemes with higher or less contrast. Two pre-configured high contrast tones exist earlier via `FlexTones.highContrast` and `FlexTones.ultraContrast`. - + + * Updated `MaterialDynamicColors` to optionally use the new Material expressive on-colors spec for none surface on-container colors. This feature is not on by default. You can opt in on this new standard by setting `useExpressiveOnContainerColors` to true in `SeedColorScheme.fromSeeds`. + * This option is only available when using MCU based `DynamicScheme` variants and not when using `FlexTones` based `tones` and `variants`. Additionally, it only applies to variants that are based on MCU's `DynamicScheme`. Such variants have their `isFlutterScheme` set to true. + * Opting in changes the light mode color tone for the colors `onPrimaryContainer`, `onSecondaryContainer`, `onTertiaryContainer` and `onErrorContainer` from 10 to 30 making them more color expressive, but they also have less contrast. + * The accepted min contrast curve is thus now `ContrastCurve(3, 4.5, 7, 11)` instead of `ContrastCurve(4.5, 7, 11, 21)` for the on-container colors. Meaning normal contrast of 4.5 is now accepted when it was 7 before. + * Prior to MCU version 0.12.0 the `MaterialDynamicColors` used an older M3 spec. Flutter stable 3.22.x and Flutter master 3.23.x still use MCU versions lower than 0.12.0 and default to the older color tones 10 in light mode. This will be changed in Flutter SDK when Flutter is updated to use MCU 0.12.0 or later. With FSS 3.0.0 you can opt in on using the new spec already now. But FSS still also defaults to the older spec with more contrast. When Flutter stable changes to use the new spec, FSS will also change to use it as default. While Flutter and MCU will then no longer offer the older higher contrast version, FSS will continue to do so. + * The optional usage of the expressive colors for on-container colors is also a customization of MCU features in the forked version. We see value in being able to offer both the higher contrast older version and the new more color expressive one. + * The `tones` configuration class `FlexTones` got a new built-in modifier, `monochromeSurfaces()`. It can be applied to any predefined or custom `FlexTones` to make the surface colors monochrome and use pure greyscale for the neutral and neutral variant tonal palettes, with no color tint from their key color or primary key seed color. - * The forked internal MCU version received new features. First `DynamicScheme` can accept an optional `customErrorPalette` and then `SchemeTonalSpot`, `SchemeContent`, `SchemeFidelity`, `SchemeExpressive`, `SchemeFruitSalad`, `SchemeMonochrome`, `SchemeNeutral`, `SchemeRainbow` and `SchemeVibrant` that extend `DynamicScheme` all received properties to support individual seed colors for all tonal palettes. - - * The above addition enables `SeedColorScheme.fromSeeds` to support using all its key seed colors also when using MCU based `DynamicScheme` variants and not just for `FlexTones` based `tones` and `variants`. + * The `tones` configuration class `FlexTones` also got the new modifier, `expressiveOnContainer()`. It can be applied to any predefined or custom `FlexTones` to make a returned `FlexTones` instance where the tones for light mode on container tones are set to 30 for more color expressive container text and icons on none surface containers. + * This modifier only impacts none surface on-container tones that are dark and thus only has any impact on the light theme mode on-container colors. + * The impacted on container colors are `onPrimaryContainerTone`,`onSecondaryContainerTone`, `onTertiaryContainerTone` and `onErrorContainerTone`. + * This feature brings optional light mode expressive on-container colors to any predefined or custom `FlexTones` configuration. The expressive on-color in light mode containers are a new change to Material Design 3 ColorScheme. It was introduced in Material Color Utilities (MCU) package v0.12.0. + * This modifier is equivalent to setting the `SeedColorScheme.fromSeeds` and its `useExpressiveOnContainerColors` to true when using MCU dynamic scheme variant based seeded color schemes. -* **COLOR VALUE BREAKING** - * Changed `FlexTones.chroma` tone `secondaryTone` from 60 to 50 in light mode for better chroma fidelity when using this `FlexTone`. +* **COLOR VALUE STYLE BREAKING** + * Changed `FlexTones.chroma` tone `secondaryTone` from 60 to 50 in light mode for better chroma fidelity when using `FlexTone.chroma` in light mode. - * Updated `MaterialDynamicColors` to use the correct expressive on-colors spec. This brings the internal Material Color Utilities (MCU) up to version 0.12.0 as published on pub. This changes the contrast curve for light/dark colors and dark mode color tone for the colors `onPrimaryContainer`, `onSecondaryContainer`, `onTertiaryContainer` and `onErrorContainer`. The dark mode tone is changed from 10 to 30, which is the correct spec. The mentioned colors also change slightly for light mode in some dynamic schemes due to the changed contrast curve, which was changed from `ContrastCurve(4.5, 7, 11, 21)` to `ContrastCurve(3, 4.5, 7, 11)` for the listed on container colors. - * Prior to MCU version 0.12.0 the `MaterialDynamicColors` used the wrong spec. Flutter stable 3.22.x and Flutter master still use an MCU version lower than 0.12.0 and have the wrong on color tones in dark mode. This will be corrected in Flutter SDK when Flutter is updated to use MCU 0.12.0 or later. With FSS 2.1.0 you get the correct spec already now. However, the mentioned `MaterialDynamicColors` on container colors will now differ slightly in light mode and more in dark mode, from the wrong ones that are currently produced by MCU versions before 0.12.0 that Flutter SDK still uses. When Flutter SDK bumps its version to MCU 0.12.0 r later, the mentioned FSS produced colors will again be identical to the SDK produced values. + * **CHANGE** - * Revert Flutter constraint back to `>=3.22.0`. Since beta and master are now on `3.23.0` or higher, this constraint can now be used by master and beta channels without any issue. - * Updated the descriptions of `FlexSchemeVariant`. - * The default example was significantly revised to include support for the new features and to show old features not demonstrated before. + * Revert Flutter constraint back to `>=3.22.0` from `>=3.22.0-0.3.pre` that was only used by FSS dev release `2.1.0-dev.1`. Since beta and master are now on `3.23.0` or higher versions, the `>=3.22.0` constraint can now be used by master and beta channels without any issue. + * Improved the descriptions and config info strings of `FlexSchemeVariant` enum values. + * The EXAMPLE APP was extensively revised to include support for all the new features and to also show some old features not demonstrated before. * **FIX** - * SAMPLE: The key color to seed the error palette was not used in the main example in dark mode. + * EXAMPLE APP: The key color to seed the error palette was not used in the main example in dark mode. ## 2.1.0-dev.1 @@ -45,7 +63,7 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. This is a temp pre-release of FFS 2.1.0. **FIX** -* [FIX #13](https://github.com/rydmike/flex_seed_scheme/issues/13). Sets Flutter version constraint to flutter: '>=3.22.0-0.3.pre', so that the package can also be used on **beta** and **stable** channels, while they are still on 3.22.0-a.b.pre versions, which is considered smaller than **3.22.0**, used in the stable release of the package. You can use this version of the package if you need to use **beta** or **master** channel before they have been bumped to 3.23.x. This release is apart from the version constraint difference identical to the `2.0.0` release. +* [FIX #13](https://github.com/rydmike/flex_seed_scheme/issues/13). Sets Flutter version constraint to flutter: `>=3.22.0-0.3.pre`, so that the package can also be used on **beta** and **stable** channels, while they are still on 3.22.0-a.b.pre versions, which is considered smaller than **3.22.0**, used in the stable release of the package. You can use this version of the package if you need to use **beta** or **master** channel before they have been bumped to 3.23.x. This release is apart from the version constraint difference identical to the `2.0.0` release. ## 2.0.0 diff --git a/example/lib/home/views/pages/home_page.dart b/example/lib/home/views/pages/home_page.dart index d11bfa7..ee3e567 100644 --- a/example/lib/home/views/pages/home_page.dart +++ b/example/lib/home/views/pages/home_page.dart @@ -26,12 +26,14 @@ class HomePage extends StatelessWidget { final FlexTones tones = controller.usedVariant.isFlutterScheme ? FlexTones.material(brightness) + .expressiveOnContainer(controller.useExpressiveOn) : controller.usedVariant .tones(brightness) .monochromeSurfaces(controller.useMonoSurfaces) .onMainsUseBW(controller.keepMainOnColorsBW) .onSurfacesUseBW(controller.keepSurfaceOnColorsBW) - .surfacesUseBW(controller.keepLightSurfaceColorsWhite); + .surfacesUseBW(controller.keepLightSurfaceColorsWhite) + .expressiveOnContainer(controller.useExpressiveOn); return Scaffold( appBar: AppBar( @@ -77,6 +79,23 @@ class HomePage extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16), child: ShowInputColors(controller: controller), ), + SwitchListTile( + title: const Text('ColorScheme'), + subtitle: const Text('Show color values'), + value: controller.showColorValue, + onChanged: controller.setShowColorValue, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ColorSchemeView( + showColorValue: controller.showColorValue, + tones: tones, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ShowTonalPalette(controller: controller), + ), ListTileSlider( dense: true, title: const Text('Contrast level'), @@ -94,22 +113,12 @@ class HomePage extends StatelessWidget { sliderLabel: 'Contrast', ), SwitchListTile( - title: const Text('ColorScheme'), - subtitle: const Text('Show color values'), - value: controller.showColorValue, - onChanged: controller.setShowColorValue, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: ColorSchemeView( - showColorValue: controller.showColorValue, - tones: tones, - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: ShowTonalPalette(controller: controller), + dense: true, + title: const Text('Use expressive on-colors on light containers'), + value: controller.useExpressiveOn, + onChanged: controller.setUseExpressiveOn, ), + const Divider(), if (controller.usedVariant.isFlutterScheme) const ListTile( dense: true, diff --git a/example/lib/home/views/widgets/show_tonal_palette.dart b/example/lib/home/views/widgets/show_tonal_palette.dart index f925669..901f549 100644 --- a/example/lib/home/views/widgets/show_tonal_palette.dart +++ b/example/lib/home/views/widgets/show_tonal_palette.dart @@ -44,6 +44,7 @@ class ShowTonalPalette extends StatelessWidget { controller.useErrorKey ? controller.errorSeedColor : null, variant: controller.usedVariant, contrastLevel: controller.contrastLevel, + useExpressiveOnContainerColors: controller.useExpressiveOn, ); // Assign the tonals for the schemes to the int lists using tone indexes diff --git a/example/lib/main.dart b/example/lib/main.dart index 182c25a..ea9bfca 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -22,7 +22,7 @@ class MyApp extends StatelessWidget { builder: (BuildContext context, Widget? child) { return MaterialApp( debugShowCheckedModeBanner: false, - title: 'ColorScheme from Seeds', + title: 'SeedColorScheme.fromSeeds', themeMode: controller.themeMode, theme: AppTheme.light(controller), darkTheme: AppTheme.dark(controller), diff --git a/example/lib/theme/controllers/theme_controller.dart b/example/lib/theme/controllers/theme_controller.dart index 5e57923..952a67e 100644 --- a/example/lib/theme/controllers/theme_controller.dart +++ b/example/lib/theme/controllers/theme_controller.dart @@ -170,6 +170,15 @@ class ThemeController with ChangeNotifier { if (notify) notifyListeners(); } + bool _useExpressiveOn = false; + bool get useExpressiveOn => _useExpressiveOn; + void setUseExpressiveOn(bool? value, [bool notify = true]) { + if (value == null) return; + if (value == _useExpressiveOn) return; + _useExpressiveOn = value; + if (notify) notifyListeners(); + } + Color _primarySeedColor = AppColor.primary; Color get primarySeedColor => _primarySeedColor; void setPrimarySeedColor(Color? value, [bool notify = true]) { diff --git a/example/lib/theme/model/app_theme.dart b/example/lib/theme/model/app_theme.dart index 373456c..53fcbd2 100644 --- a/example/lib/theme/model/app_theme.dart +++ b/example/lib/theme/model/app_theme.dart @@ -32,6 +32,10 @@ class AppTheme { // The contrast level only has any effect when using above variant based // scheming strategy and the variant is one where isFlutterScheme is true. contrastLevel: controller.contrastLevel, + // Use expressive on container colors if opted in on that setting. + // Used as new default in Material-3 after MCU v0.12.0. Flutter stable + // 3.22.x and master 3.23.x do not yet use this design. + useExpressiveOnContainerColors: controller.useExpressiveOn, // Tone chroma config and tone mapping is optional. If you do not add it // you get a config matching Flutter's Material 3 ColorScheme.fromSeed. // @@ -45,7 +49,10 @@ class AppTheme { .monochromeSurfaces(controller.useMonoSurfaces) .onMainsUseBW(controller.keepMainOnColorsBW) .onSurfacesUseBW(controller.keepSurfaceOnColorsBW) - .surfacesUseBW(controller.keepLightSurfaceColorsWhite), + .surfacesUseBW(controller.keepLightSurfaceColorsWhite) + // This is equivalent to useExpressiveOnContainerColors = true, + // but for tones based schemes. + .expressiveOnContainer(controller.useExpressiveOn), // Pin input seed colors in light mode to corresponding main colors // when set to be pinned in the UI. primary: controller.pinPrimary ? controller.primarySeedColor : null, @@ -97,7 +104,8 @@ class AppTheme { .monochromeSurfaces(controller.useMonoSurfaces) .onMainsUseBW(controller.keepMainOnColorsBW) .onSurfacesUseBW(controller.keepSurfaceOnColorsBW) - .surfacesUseBW(controller.keepDarkSurfaceColorsBlack), + .surfacesUseBW(controller.keepDarkSurfaceColorsBlack) + .expressiveOnContainer(controller.useExpressiveOn), // Pin input seed colors in dark mode to corresponding container colors // when set to be pinned in the UI. // Typically input seed "brand" colors have high chroma and ar dark, hence diff --git a/lib/src/flex/flex_seed_scheme.dart b/lib/src/flex/flex_seed_scheme.dart index 19c18a5..63c2d7d 100644 --- a/lib/src/flex/flex_seed_scheme.dart +++ b/lib/src/flex/flex_seed_scheme.dart @@ -593,6 +593,12 @@ extension SeedColorScheme on ColorScheme { /// to provide access to the DynamicSchemeVariant that are available /// in Flutter 3.22.2 and later. With FSS you can use them in /// Flutter 3.22.0 already. + /// + /// An assert is used to check that a none null value has not been assign to + /// [tones] and [variant] at the same time, since they are mutually + /// exclusive. Setting both to a value throws in debug mode, if both are + /// set in a release build, [variant] will be used. Both can be null, in + /// that case default [tones] with value [FlexTones.material] will be used. FlexTones? tones, /// An optional way to select the used algorithm for seeded [ColorScheme] @@ -619,12 +625,19 @@ extension SeedColorScheme on ColorScheme { /// use the [FlexTones] modifiers [monochromeSurfaces], [onMainsUseBW], /// [onSurfacesUseBW] and [surfacesUseBW], since they operate on the /// [FlexTones] configurations passed in to [tones]. + /// + /// An assert is used to check that a none null value has not been assign to + /// [tones] and [variant] at the same time, since they are mutually + /// exclusive. Setting both to a value throws in debug mode, if both are + /// set in a release build, [variant] will be used. Both can be null, in + /// that case default [tones] with value [FlexTones.material] will be used. FlexSchemeVariant? variant, /// The [contrastLevel] parameter indicates the contrast level between color - /// pairs, such as [primary] and [onPrimary]. 0.0 is the default (normal); - /// -1.0 is the lowest; 1.0 is the highest. From Material Design guideline, - /// the medium and high contrast correspond to 0.5 and 1.0 respectively. + /// pairs, such as [primary] and [onPrimary]. The value 0.0 is the default + /// (normal) contrast; -1.0 is the lowest; 1.0 is the highest. + /// From Material Design guideline, the normal, medium and high contrast + /// options correspond to 0.0, 0.5 and 1.0 respectively. /// /// The contrast level property is only used when seed generating the /// [ColorScheme] based on [variant]. When using [tones] the [contrastLevel] @@ -636,6 +649,37 @@ extension SeedColorScheme on ColorScheme { /// contrast tone mappings. double contrastLevel = 0.0, + /// Use expressive on container colors for light mode. + /// + /// This property only impacts MCU based DynamicSchemes. In FSS it means + /// [variant] based schemes that have [FlexSchemeVariant.isFlutterScheme] + /// set to true. To used the same expressive on container colors as MCU + /// in FSS [tones] based schemes, use the [FlexTones] modifier + /// [FlexTones.expressiveOnContainer]. + /// + /// Material specification and MCU v0.12.0 changes the light mode on colors + /// for none surface containers from tone 10 to tone 30. It also sets the + /// min `ContrastCurve` from ContrastCurve(4.5, 7.0, 11.0, 21.0) to + /// ContrastCurve(3.0, 4.5, 7.0, 11.0), making min contrast for normal + /// contrast 4.5 instead of past 7.0. + /// + /// This change is not yet used by Flutter and not fully documented in the + /// M3 guide. In the FSS MCU fork we are making this change a deliberate + /// opt-in feature and default to not opting in on it. This default may be + /// adjusted later to opt-in by default, but FSS will continue to offer + /// the older version with better contrast too. Any later change to opt-in + /// by default will only be considered color style breaking and will not + /// bump major version of FSS, only minor. + /// + /// Defaults to false in FSS version 3.0.0. + /// + /// The result you get with false, corresponds to used results in MCU until + /// version 0.11.1. Version 0.12.0 of MCU it corresponds to setting + /// this flag to true. This is a breaking change in MCU 0.12.0 and will + /// change the light mode color schemes produced by all DynamicColor based + /// Material color schemes. + final bool useExpressiveOnContainerColors = false, + /// Override color for the seed generated [primary] color. /// /// You may want to assign the [primaryKey] to this color in light @@ -809,7 +853,9 @@ extension SeedColorScheme on ColorScheme { @Deprecated('Use surfaceContainerHighest instead.') Color? surfaceVariant, }) { // Assert that a none null value has not been assign to tones and variant - // at the same time, since they are mutually exclusive, both can be null. + // at the same time, since they are mutually exclusive, both can be null, in + // that case default tones will be used. Setting both throws in debug mode, + // if both are set used on release mode, variant will be used. assert(tones == null || variant == null, 'Only one of tones or variant can be provided, not both.'); @@ -824,6 +870,7 @@ extension SeedColorScheme on ColorScheme { neutralVariantSeedColor: neutralVariantKey, variant: variant, contrastLevel: contrastLevel, + useExpressiveOnContainerColors: useExpressiveOnContainerColors, ); return ColorScheme( primary: @@ -1029,6 +1076,7 @@ extension SeedColorScheme on ColorScheme { Color? neutralSeedColor, Color? neutralVariantSeedColor, double contrastLevel = 0.0, + bool useExpressiveOnContainerColors = false, }) { assert( contrastLevel >= -1.0 && contrastLevel <= 1.0, @@ -1064,14 +1112,16 @@ extension SeedColorScheme on ColorScheme { FlexSchemeVariant.chroma || FlexSchemeVariant.tonalSpot => SchemeTonalSpot( - sourceColorHct: primarySourceColor, - secondarySourceColorHct: secondarySourceColor, - tertiarySourceColorHct: tertiarySourceColor, - neutralSourceColorHct: neutralSourceColor, - neutralVariantSourceColorHct: neutralVariantSourceColor, - errorSourceColorHct: errorSourceColor, - isDark: isDark, - contrastLevel: contrastLevel), + sourceColorHct: primarySourceColor, + secondarySourceColorHct: secondarySourceColor, + tertiarySourceColorHct: tertiarySourceColor, + neutralSourceColorHct: neutralSourceColor, + neutralVariantSourceColorHct: neutralVariantSourceColor, + errorSourceColorHct: errorSourceColor, + isDark: isDark, + contrastLevel: contrastLevel, + useExpressiveOnContainerColors: useExpressiveOnContainerColors, + ), FlexSchemeVariant.fidelity => SchemeFidelity( sourceColorHct: primarySourceColor, secondarySourceColorHct: secondarySourceColor, @@ -1080,7 +1130,9 @@ extension SeedColorScheme on ColorScheme { neutralVariantSourceColorHct: neutralVariantSourceColor, errorSourceColorHct: errorSourceColor, isDark: isDark, - contrastLevel: contrastLevel), + contrastLevel: contrastLevel, + useExpressiveOnContainerColors: useExpressiveOnContainerColors, + ), FlexSchemeVariant.content => SchemeContent( sourceColorHct: primarySourceColor, secondarySourceColorHct: secondarySourceColor, @@ -1089,12 +1141,16 @@ extension SeedColorScheme on ColorScheme { neutralVariantSourceColorHct: neutralVariantSourceColor, errorSourceColorHct: errorSourceColor, isDark: isDark, - contrastLevel: contrastLevel), + contrastLevel: contrastLevel, + useExpressiveOnContainerColors: useExpressiveOnContainerColors, + ), FlexSchemeVariant.monochrome => SchemeMonochrome( sourceColorHct: primarySourceColor, errorSourceColorHct: errorSourceColor, isDark: isDark, - contrastLevel: contrastLevel), + contrastLevel: contrastLevel, + useExpressiveOnContainerColors: useExpressiveOnContainerColors, + ), FlexSchemeVariant.neutral => SchemeNeutral( sourceColorHct: primarySourceColor, secondarySourceColorHct: secondarySourceColor, @@ -1103,7 +1159,9 @@ extension SeedColorScheme on ColorScheme { neutralVariantSourceColorHct: neutralVariantSourceColor, errorSourceColorHct: errorSourceColor, isDark: isDark, - contrastLevel: contrastLevel), + contrastLevel: contrastLevel, + useExpressiveOnContainerColors: useExpressiveOnContainerColors, + ), FlexSchemeVariant.vibrant => SchemeVibrant( sourceColorHct: primarySourceColor, secondarySourceColorHct: secondarySourceColor, @@ -1112,7 +1170,9 @@ extension SeedColorScheme on ColorScheme { neutralVariantSourceColorHct: neutralVariantSourceColor, errorSourceColorHct: errorSourceColor, isDark: isDark, - contrastLevel: contrastLevel), + contrastLevel: contrastLevel, + useExpressiveOnContainerColors: useExpressiveOnContainerColors, + ), FlexSchemeVariant.expressive => SchemeExpressive( sourceColorHct: primarySourceColor, secondarySourceColorHct: secondarySourceColor, @@ -1121,7 +1181,9 @@ extension SeedColorScheme on ColorScheme { neutralVariantSourceColorHct: neutralVariantSourceColor, errorSourceColorHct: errorSourceColor, isDark: isDark, - contrastLevel: contrastLevel), + contrastLevel: contrastLevel, + useExpressiveOnContainerColors: useExpressiveOnContainerColors, + ), FlexSchemeVariant.rainbow => SchemeRainbow( sourceColorHct: primarySourceColor, secondarySourceColorHct: secondarySourceColor, @@ -1130,7 +1192,9 @@ extension SeedColorScheme on ColorScheme { neutralVariantSourceColorHct: neutralVariantSourceColor, errorSourceColorHct: errorSourceColor, isDark: isDark, - contrastLevel: contrastLevel), + contrastLevel: contrastLevel, + useExpressiveOnContainerColors: useExpressiveOnContainerColors, + ), FlexSchemeVariant.fruitSalad => SchemeFruitSalad( sourceColorHct: primarySourceColor, secondarySourceColorHct: secondarySourceColor, @@ -1139,7 +1203,9 @@ extension SeedColorScheme on ColorScheme { neutralVariantSourceColorHct: neutralVariantSourceColor, errorSourceColorHct: errorSourceColor, isDark: isDark, - contrastLevel: contrastLevel), + contrastLevel: contrastLevel, + useExpressiveOnContainerColors: useExpressiveOnContainerColors, + ), }; } } diff --git a/lib/src/flex/flex_tones.dart b/lib/src/flex/flex_tones.dart index 02a0930..c0ff33f 100644 --- a/lib/src/flex/flex_tones.dart +++ b/lib/src/flex/flex_tones.dart @@ -910,6 +910,10 @@ class FlexTones with Diagnosticable { /// If set to false, the function is a no op and just returns the [FlexTones] /// object unmodified. This is typically used to control applying the tint /// removal via a controller. + /// + /// **NOTE**: If some [FlexTones] modifiers change same properties, the uses + /// order in which they are applied matters. The last one applied will be + /// the one that is used. FlexTones onMainsUseBW([bool useBW = true]) { // ignore: avoid_returning_this if (!useBW) return this; @@ -947,6 +951,10 @@ class FlexTones with Diagnosticable { /// If set to false, the function is a no op and just returns the [FlexTones] /// object unmodified. This is typically used to control applying the tint /// removal via a controller. + /// + /// **NOTE**: If some [FlexTones] modifiers change same properties, the uses + /// order in which they are applied matters. The last one applied will be + /// the one that is used. FlexTones onSurfacesUseBW([bool useBW = true]) { // ignore: avoid_returning_this if (!useBW) return this; @@ -972,6 +980,10 @@ class FlexTones with Diagnosticable { /// If set to false, the function is a no op and just returns the [FlexTones] /// object unmodified. This is typically used to control applying the tint /// removal via a controller. + /// + /// **NOTE**: If some [FlexTones] modifiers change same properties, the uses + /// order in which they are applied matters. The last one applied will be + /// the one that is used. FlexTones surfacesUseBW([bool useBW = true]) { // ignore: avoid_returning_this if (!useBW) return this; @@ -981,8 +993,6 @@ class FlexTones with Diagnosticable { ); } - // TODO(rydmike): Add tests for monochromeSurfaces. - /// Returns a new [FlexTones] instance where the neutral and neutral variant /// chrome is set to 0. This will result in that regardless of the seed color /// the neutral and neutral variant tonal colors will be a pure grey scale @@ -993,6 +1003,10 @@ class FlexTones with Diagnosticable { /// effective. If set to false, the function is a no op and just returns the /// [FlexTones] object unmodified. This is typically used to control applying /// the tint removal via a controller. + /// + /// **NOTE**: If some [FlexTones] modifiers change same properties, the uses + /// order in which they are applied matters. The last one applied will be + /// the one that is used. FlexTones monochromeSurfaces([bool useMonochrome = true]) { // ignore: avoid_returning_this if (!useMonochrome) return this; @@ -1004,6 +1018,47 @@ class FlexTones with Diagnosticable { ); } + /// Returns a new [FlexTones] instance where the tones for light mode on + /// container tones are set to 30 for more color expressive container text + /// and icons on none surface containers. + /// + /// This [FlexTones] modifier only impacts none surface on-container tones + /// that are dark and thus only has any impact on the light theme mode + /// on-container colors. + /// + /// The impacted on container colors are [onPrimaryContainerTone], + /// [onSecondaryContainerTone], [onTertiaryContainerTone] and + /// [onErrorContainerTone]. + /// + /// This feature brings optional light mode expressive on container colors to + /// any predefined or custom [FlexTones] configuration. The expressive on + /// color in light mode containers are a new change to Material Design 3 + /// ColorScheme. It was introduced in Material Color Utilities (MCU) + /// lib v0.12.0. + /// + /// This modifier is equivalent to setting the + /// [SeedColorScheme.fromSeeds] and its [useExpressiveOnContainerColors] to + /// true when using MCU dynamic scheme variant based seeded color schemes. + /// + /// The [useExpressive] flag is true by default, making the function + /// effective. If set to false, the function is a no op and just returns the + /// [FlexTones] object unmodified. This is typically used to control applying + /// modifier via a controller. + /// + /// **NOTE**: If some [FlexTones] modifiers change same properties, the uses + /// order in which they are applied matters. The last one applied will be + /// the one that is used. + FlexTones expressiveOnContainer([bool useExpressive = true]) { + // ignore: avoid_returning_this + if ((!useExpressive) || (onPrimaryContainerTone > 60)) return this; + return copyWith( + onPrimaryContainerTone: 30, + onSecondaryContainerTone: 30, + onTertiaryContainerTone: 30, + onErrorContainerTone: 30, + ); + } + /// Tone used for [ColorScheme.primary] from primary [FlexTonalPalette]. final int primaryTone; diff --git a/lib/src/mcu/dynamiccolor/dynamic_scheme.dart b/lib/src/mcu/dynamiccolor/dynamic_scheme.dart index 493dbe6..4cf7aa2 100644 --- a/lib/src/mcu/dynamiccolor/dynamic_scheme.dart +++ b/lib/src/mcu/dynamiccolor/dynamic_scheme.dart @@ -39,6 +39,29 @@ class DynamicScheme { /// standard (i.e. the design as spec'd), and 1 represents maximum contrast. final double contrastLevel; + /// Use expressive on container colors for light mode. + /// + /// Material spec and MCU v0.12.0 changes light mode on colors for + /// containers from tone 10 to tone 30 can the `ContrastCurve` from + /// ContrastCurve(4.5, 7.0, 11.0, 21.0) to + /// ContrastCurve(3.0, 4.5, 7.0, 11.0), making min contrast for normal 4.5 + /// instead of past 7.0. + /// + /// This change is not yet used by Flutter and not fully documented in the + /// M3 guide. In this MCY fork we are making this change a deliberate opt-in + /// feature and default to not opting in on it. This default may be adjusted + /// later to opt-in by default, but FSS will continue to offer the older + /// version with better contrast too. + /// + /// Defaults to false in FSS version 3.0.0. + /// + /// The result you get with false, corresponds to used results in MCU until + /// version 0.11.1. Version 0.12.0 of MCU it corresponds to setting + /// this flag to true. This is a breaking change in MCU 0.12.0 and will + /// change the light mode color schemes produced by all DynamicColor based + /// Material color schemes. + final bool useExpressiveOnContainerColors; + /// Given a tone, produces a color. Hue and chroma of the color are specified /// in the design specification of the variant. Usually colorful. final TonalPalette primaryPalette; @@ -70,6 +93,7 @@ class DynamicScheme { required this.sourceColorArgb, required this.variant, this.contrastLevel = 0.0, + this.useExpressiveOnContainerColors = false, required this.isDark, required this.primaryPalette, required this.secondaryPalette, diff --git a/lib/src/mcu/dynamiccolor/material_dynamic_colors.dart b/lib/src/mcu/dynamiccolor/material_dynamic_colors.dart index ff94cc6..7cd32b5 100644 --- a/lib/src/mcu/dynamiccolor/material_dynamic_colors.dart +++ b/lib/src/mcu/dynamiccolor/material_dynamic_colors.dart @@ -28,6 +28,9 @@ bool _isFidelity(DynamicScheme scheme) => bool _isMonochrome(DynamicScheme scheme) => scheme.variant == Variant.monochrome; +bool _useExpressiveOnContainers(DynamicScheme scheme) => + scheme.useExpressiveOnContainerColors; + /// Tokens, or named colors, in the Material Design system. class MaterialDynamicColors { /// Required tone delta for content accent. @@ -318,7 +321,11 @@ class MaterialDynamicColors { if (_isMonochrome(s)) { return s.isDark ? 0 : 100; } - return s.isDark ? 90 : 30; + if (_useExpressiveOnContainers(s)) { + return s.isDark ? 90 : 30; + } else { + return s.isDark ? 90 : 10; + } }, background: (DynamicScheme s) => MaterialDynamicColors.primaryContainer, contrastCurve: ContrastCurve(3, 4.5, 7, 11), @@ -404,7 +411,11 @@ class MaterialDynamicColors { return s.isDark ? 90 : 10; } if (!_isFidelity(s)) { - return s.isDark ? 90 : 30; + if (_useExpressiveOnContainers(s)) { + return s.isDark ? 90 : 30; + } else { + return s.isDark ? 90 : 10; + } } return DynamicColor.foregroundTone( MaterialDynamicColors.secondaryContainer.tone(s), 4.5); @@ -482,7 +493,11 @@ class MaterialDynamicColors { return s.isDark ? 0 : 100; } if (!_isFidelity(s)) { - return s.isDark ? 90 : 30; + if (_useExpressiveOnContainers(s)) { + return s.isDark ? 90 : 30; + } else { + return s.isDark ? 90 : 10; + } } return DynamicColor.foregroundTone( MaterialDynamicColors.tertiaryContainer.tone(s), 4.5); @@ -540,7 +555,11 @@ class MaterialDynamicColors { if (_isMonochrome(s)) { return s.isDark ? 90 : 10; } - return s.isDark ? 90 : 30; + if (_useExpressiveOnContainers(s)) { + return s.isDark ? 90 : 30; + } else { + return s.isDark ? 90 : 10; + } }, background: (DynamicScheme s) => MaterialDynamicColors.errorContainer, contrastCurve: ContrastCurve(3, 4.5, 7, 11), diff --git a/lib/src/mcu/scheme/scheme_content.dart b/lib/src/mcu/scheme/scheme_content.dart index 2baa835..90b0045 100644 --- a/lib/src/mcu/scheme/scheme_content.dart +++ b/lib/src/mcu/scheme/scheme_content.dart @@ -36,6 +36,7 @@ class SchemeContent extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + super.useExpressiveOnContainerColors, Hct? secondarySourceColorHct, Hct? tertiarySourceColorHct, Hct? neutralSourceColorHct, diff --git a/lib/src/mcu/scheme/scheme_expressive.dart b/lib/src/mcu/scheme/scheme_expressive.dart index 29a5e95..1fa393a 100644 --- a/lib/src/mcu/scheme/scheme_expressive.dart +++ b/lib/src/mcu/scheme/scheme_expressive.dart @@ -67,6 +67,7 @@ class SchemeExpressive extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + super.useExpressiveOnContainerColors, Hct? secondarySourceColorHct, Hct? tertiarySourceColorHct, Hct? neutralSourceColorHct, diff --git a/lib/src/mcu/scheme/scheme_fidelity.dart b/lib/src/mcu/scheme/scheme_fidelity.dart index 3ef24a9..6bb4453 100644 --- a/lib/src/mcu/scheme/scheme_fidelity.dart +++ b/lib/src/mcu/scheme/scheme_fidelity.dart @@ -35,6 +35,7 @@ class SchemeFidelity extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + super.useExpressiveOnContainerColors, Hct? secondarySourceColorHct, Hct? tertiarySourceColorHct, Hct? neutralSourceColorHct, diff --git a/lib/src/mcu/scheme/scheme_fruit_salad.dart b/lib/src/mcu/scheme/scheme_fruit_salad.dart index 9721d7e..507304a 100644 --- a/lib/src/mcu/scheme/scheme_fruit_salad.dart +++ b/lib/src/mcu/scheme/scheme_fruit_salad.dart @@ -25,6 +25,7 @@ class SchemeFruitSalad extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + super.useExpressiveOnContainerColors, Hct? secondarySourceColorHct, Hct? tertiarySourceColorHct, Hct? neutralSourceColorHct, diff --git a/lib/src/mcu/scheme/scheme_monochrome.dart b/lib/src/mcu/scheme/scheme_monochrome.dart index b0a8b52..9ac0784 100644 --- a/lib/src/mcu/scheme/scheme_monochrome.dart +++ b/lib/src/mcu/scheme/scheme_monochrome.dart @@ -23,6 +23,7 @@ class SchemeMonochrome extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + super.useExpressiveOnContainerColors, Hct? errorSourceColorHct, }) : super( sourceColorArgb: sourceColorHct.toInt(), diff --git a/lib/src/mcu/scheme/scheme_neutral.dart b/lib/src/mcu/scheme/scheme_neutral.dart index 6bd1c60..3ad3c15 100644 --- a/lib/src/mcu/scheme/scheme_neutral.dart +++ b/lib/src/mcu/scheme/scheme_neutral.dart @@ -24,6 +24,7 @@ class SchemeNeutral extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + super.useExpressiveOnContainerColors, Hct? secondarySourceColorHct, Hct? tertiarySourceColorHct, Hct? neutralSourceColorHct, diff --git a/lib/src/mcu/scheme/scheme_rainbow.dart b/lib/src/mcu/scheme/scheme_rainbow.dart index 26ea247..bf43e42 100644 --- a/lib/src/mcu/scheme/scheme_rainbow.dart +++ b/lib/src/mcu/scheme/scheme_rainbow.dart @@ -29,6 +29,7 @@ class SchemeRainbow extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + super.useExpressiveOnContainerColors, Hct? secondarySourceColorHct, Hct? tertiarySourceColorHct, Hct? neutralSourceColorHct, diff --git a/lib/src/mcu/scheme/scheme_tonal_spot.dart b/lib/src/mcu/scheme/scheme_tonal_spot.dart index 33b1ee1..306bf47 100644 --- a/lib/src/mcu/scheme/scheme_tonal_spot.dart +++ b/lib/src/mcu/scheme/scheme_tonal_spot.dart @@ -27,6 +27,7 @@ class SchemeTonalSpot extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + super.useExpressiveOnContainerColors, Hct? secondarySourceColorHct, Hct? tertiarySourceColorHct, Hct? neutralSourceColorHct, diff --git a/lib/src/mcu/scheme/scheme_vibrant.dart b/lib/src/mcu/scheme/scheme_vibrant.dart index c435484..a4362be 100644 --- a/lib/src/mcu/scheme/scheme_vibrant.dart +++ b/lib/src/mcu/scheme/scheme_vibrant.dart @@ -72,6 +72,7 @@ class SchemeVibrant extends DynamicScheme { required Hct sourceColorHct, required super.isDark, required super.contrastLevel, + super.useExpressiveOnContainerColors, Hct? secondarySourceColorHct, Hct? tertiarySourceColorHct, Hct? neutralSourceColorHct, From 23e422e7a7a184080ae4719ca88ced0962fc6562 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Fri, 21 Jun 2024 22:25:04 +0300 Subject: [PATCH 24/37] FIX: Example surfacesUseBW tone labels not changing in dark mode --- example/lib/home/views/pages/home_page.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example/lib/home/views/pages/home_page.dart b/example/lib/home/views/pages/home_page.dart index ee3e567..15ad96f 100644 --- a/example/lib/home/views/pages/home_page.dart +++ b/example/lib/home/views/pages/home_page.dart @@ -32,7 +32,9 @@ class HomePage extends StatelessWidget { .monochromeSurfaces(controller.useMonoSurfaces) .onMainsUseBW(controller.keepMainOnColorsBW) .onSurfacesUseBW(controller.keepSurfaceOnColorsBW) - .surfacesUseBW(controller.keepLightSurfaceColorsWhite) + .surfacesUseBW(isLight + ? controller.keepLightSurfaceColorsWhite + : controller.keepDarkSurfaceColorsBlack) .expressiveOnContainer(controller.useExpressiveOn); return Scaffold( From 97490eeea0678bb0a5f4b27a6ca45d9b961ea30f Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Fri, 21 Jun 2024 22:26:30 +0300 Subject: [PATCH 25/37] FIX: material3Legacy was incorrect --- lib/src/flex/flex_tones.dart | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/src/flex/flex_tones.dart b/lib/src/flex/flex_tones.dart index c0ff33f..2035a23 100644 --- a/lib/src/flex/flex_tones.dart +++ b/lib/src/flex/flex_tones.dart @@ -384,14 +384,14 @@ class FlexTones with Diagnosticable { brightness == Brightness.light ? const FlexTones.light( primaryChroma: 36, - primaryMinChroma: 0, + primaryMinChroma: 36, secondaryChroma: 16, tertiaryChroma: 24, useCam16: false, ) : const FlexTones.dark( primaryChroma: 36, - primaryMinChroma: 0, + primaryMinChroma: 36, secondaryChroma: 16, tertiaryChroma: 24, useCam16: false, @@ -413,19 +413,22 @@ class FlexTones with Diagnosticable { factory FlexTones.material3Legacy(Brightness brightness) => brightness == Brightness.light ? const FlexTones.light( + surfaceTone: 99, + backgroundTone: 99, primaryChroma: 48, - primaryMinChroma: 0, + primaryMinChroma: 48, secondaryChroma: 16, tertiaryChroma: 24, + neutralChroma: 4, ) : const FlexTones.dark( - surfaceTone: 8, - backgroundTone: 8, - onErrorContainerTone: 90, + surfaceTone: 10, + backgroundTone: 10, primaryChroma: 48, - primaryMinChroma: 0, + primaryMinChroma: 48, secondaryChroma: 16, tertiaryChroma: 24, + neutralChroma: 4, ); /// Creates a tonal palette extraction setup that results in M3 like From 6c6666f1fb8acaef94f218d01f6897028c21b9b8 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Fri, 21 Jun 2024 22:36:50 +0300 Subject: [PATCH 26/37] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54b770a..dddc81f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ The bundled forked version of MCU also has more tests than the original, allowin * **FIX** + * The `FlexTones.material3Legacy` was corrected. It had some incorrect tones and chroma in its configuration. The mistakes were fixed. * EXAMPLE APP: The key color to seed the error palette was not used in the main example in dark mode. From b5b578169229303fdac3307ac3d4e9d50648dd04 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Fri, 21 Jun 2024 22:52:17 +0300 Subject: [PATCH 27/37] Update assert description --- lib/src/flex/flex_seed_scheme.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/flex/flex_seed_scheme.dart b/lib/src/flex/flex_seed_scheme.dart index 63c2d7d..3d2cae1 100644 --- a/lib/src/flex/flex_seed_scheme.dart +++ b/lib/src/flex/flex_seed_scheme.dart @@ -1080,7 +1080,7 @@ extension SeedColorScheme on ColorScheme { }) { assert( contrastLevel >= -1.0 && contrastLevel <= 1.0, - 'contrastLevel must be between -1.0 and 1.0 inclusive.', + 'contrastLevel must be between [-1.0 to 1.0].', ); final bool isDark = brightness == Brightness.dark; final Hct primarySourceColor = Hct.fromInt(primarySeedColor.value); From f10898c24415f968b7945f858e611147e265b6e8 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 23 Jun 2024 17:11:36 +0300 Subject: [PATCH 28/37] Update labels and example app texts --- example/lib/home/views/pages/home_page.dart | 8 ++++++++ lib/src/flex/flex_scheme_variant.dart | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/example/lib/home/views/pages/home_page.dart b/example/lib/home/views/pages/home_page.dart index 15ad96f..2cc65e3 100644 --- a/example/lib/home/views/pages/home_page.dart +++ b/example/lib/home/views/pages/home_page.dart @@ -120,6 +120,14 @@ class HomePage extends StatelessWidget { value: controller.useExpressiveOn, onChanged: controller.setUseExpressiveOn, ), + const ListTile( + dense: true, + title: Text('Some MCU based seed strategies, like Fidelity and ' + 'Content, dynamically adjust ' + 'tones for contrast, this also applies when using contrast ' + 'level. The shown baseline tone mappings are not always used ' + 'when using MCU dynamic schemes'), + ), const Divider(), if (controller.usedVariant.isFlutterScheme) const ListTile( diff --git a/lib/src/flex/flex_scheme_variant.dart b/lib/src/flex/flex_scheme_variant.dart index 0768c42..d6b3529 100644 --- a/lib/src/flex/flex_scheme_variant.dart +++ b/lib/src/flex/flex_scheme_variant.dart @@ -78,7 +78,7 @@ enum FlexSchemeVariant { isFlutterScheme: true, ), - /// A scheme that places the source color in Scheme primaryContainer. + /// A scheme that places the source/seed color in primaryContainer. /// /// Primary Container is the source color, adjusted for color relativity. /// It maintains constant appearance in light mode and dark mode. @@ -88,8 +88,8 @@ enum FlexSchemeVariant { /// TemperatureCache. It also maintains constant appearance. fidelity( variantName: 'Fidelity', - description: 'Color palettes match the seed color, also when ' - 'it is bright and uses high chroma', + description: 'Color palettes match seed color, also when ' + 'it is bright and uses high chroma. Seed appears as primary container', configDetails: 'Primary - Chroma from key color\n' 'Secondary - Max of: chroma from key -32 or *0.5\n' 'Tertiary - TemperatureCache complement hue or key hue and chroma\n' From 604cc6eeb8e266e898fa081aa1e00d32f1fce756 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 23 Jun 2024 17:49:15 +0300 Subject: [PATCH 29/37] Example: Add surface customization example --- .../home/views/widgets/show_input_colors.dart | 66 +++++++++++++++++++ .../views/widgets/show_tonal_palette.dart | 8 +++ .../theme/controllers/theme_controller.dart | 18 +++++ example/lib/theme/model/app_theme.dart | 10 +++ 4 files changed, 102 insertions(+) diff --git a/example/lib/home/views/widgets/show_input_colors.dart b/example/lib/home/views/widgets/show_input_colors.dart index 4a5ce13..a3c8f5a 100644 --- a/example/lib/home/views/widgets/show_input_colors.dart +++ b/example/lib/home/views/widgets/show_input_colors.dart @@ -45,6 +45,9 @@ class ShowInputColors extends StatelessWidget { final Color onTertiary = _onColor(tertiary); final Color error = controller.errorSeedColor; final Color onError = _onColor(error); + final Color neutrals = controller.neutralSeedColor; + final Color onNeutrals = _onColor(neutrals); + final Color surface = colorScheme.surface; // Grab the card border from the theme card shape @@ -358,6 +361,69 @@ class ShowInputColors extends StatelessWidget { ), ], ), + // Neutral color + Column( + children: [ + RepaintBoundary( + key: const ValueKey('input_neutral'), + child: SizedBox( + width: boxWidth, + height: boxHeight, + child: Card( + margin: EdgeInsets.zero, + elevation: 0, + clipBehavior: Clip.hardEdge, + child: Material( + color: controller.useNeutralKey ? neutrals : surface, + child: ColorPickerInkWellDialog( + color: + controller.useNeutralKey ? neutrals : surface, + onChanged: controller.setNeutralSeedColor, + recentColors: controller.recentColors, + onRecentColorsChanged: controller.setRecentColors, + wasCancelled: (bool cancelled) { + if (cancelled) { + controller.setNeutralSeedColor(neutrals); + } + }, + enabled: controller.useNeutralKey, + child: ColorNameValue( + key: ValueKey('ipc neutral $neutrals'), + color: + controller.useNeutralKey ? neutrals : surface, + textColor: controller.useNeutralKey + ? onNeutrals + : surface, + label: 'surfaces', + showInputColor: false, + showMaterialName: true, + ), + ), + ), + ), + ), + ), + SizedBox( + width: boxWidth, + child: SwitchListTile( + activeColor: colorScheme.outline, + contentPadding: const EdgeInsets.symmetric(horizontal: 4), + dense: true, + title: const Text('Surfaces'), + value: controller.useNeutralKey, + onChanged: controller.setUseNeutralKey, + ), + ), + const SizedBox( + width: boxWidth, + child: ListTile( + contentPadding: EdgeInsets.symmetric(horizontal: 4), + dense: true, + title: Text('Also surfaceTint'), + ), + ), + ], + ), ], ), ], diff --git a/example/lib/home/views/widgets/show_tonal_palette.dart b/example/lib/home/views/widgets/show_tonal_palette.dart index 901f549..0d1e646 100644 --- a/example/lib/home/views/widgets/show_tonal_palette.dart +++ b/example/lib/home/views/widgets/show_tonal_palette.dart @@ -42,6 +42,10 @@ class ShowTonalPalette extends StatelessWidget { controller.useTertiaryKey ? controller.tertiarySeedColor : null, errorSeedColor: controller.useErrorKey ? controller.errorSeedColor : null, + neutralSeedColor: + controller.useNeutralKey ? controller.neutralSeedColor : null, + neutralVariantSeedColor: + controller.useNeutralKey ? controller.neutralSeedColor : null, variant: controller.usedVariant, contrastLevel: controller.contrastLevel, useExpressiveOnContainerColors: controller.useExpressiveOn, @@ -83,6 +87,10 @@ class ShowTonalPalette extends StatelessWidget { ? controller.tertiarySeedColor.value : null, error: controller.useErrorKey ? controller.errorSeedColor.value : null, + neutral: + controller.useNeutralKey ? controller.neutralSeedColor.value : null, + neutralVariant: + controller.useNeutralKey ? controller.neutralSeedColor.value : null, // Tone config details we get from active FlexTones. primaryChroma: tones.primaryChroma, primaryMinChroma: tones.primaryMinChroma, diff --git a/example/lib/theme/controllers/theme_controller.dart b/example/lib/theme/controllers/theme_controller.dart index 952a67e..57a2bab 100644 --- a/example/lib/theme/controllers/theme_controller.dart +++ b/example/lib/theme/controllers/theme_controller.dart @@ -71,6 +71,15 @@ class ThemeController with ChangeNotifier { if (notify) notifyListeners(); } + bool _useNeutralKey = false; + bool get useNeutralKey => _useNeutralKey; + void setUseNeutralKey(bool? value, [bool notify = true]) { + if (value == null) return; + if (value == _useNeutralKey) return; + _useNeutralKey = value; + if (notify) notifyListeners(); + } + bool _pinPrimary = false; bool get pinPrimary => _pinPrimary; void setPinPrimary(bool? value, [bool notify = true]) { @@ -215,6 +224,15 @@ class ThemeController with ChangeNotifier { if (notify) notifyListeners(); } + Color _neutralSeedColor = AppColor.primary; + Color get neutralSeedColor => _neutralSeedColor; + void setNeutralSeedColor(Color? value, [bool notify = true]) { + if (value == null) return; + if (value == _neutralSeedColor) return; + _neutralSeedColor = value; + if (notify) notifyListeners(); + } + // Recently used colors, we keep the list of recently used colors in the // color picker for custom colors only during the session we don't persist it. // It is of course possible to persist, but not needed in this app. diff --git a/example/lib/theme/model/app_theme.dart b/example/lib/theme/model/app_theme.dart index 53fcbd2..c505c5c 100644 --- a/example/lib/theme/model/app_theme.dart +++ b/example/lib/theme/model/app_theme.dart @@ -26,6 +26,9 @@ class AppTheme { tertiaryKey: controller.useTertiaryKey ? controller.tertiarySeedColor : null, errorKey: controller.useErrorKey ? controller.errorSeedColor : null, + neutralKey: controller.useNeutralKey ? controller.neutralSeedColor : null, + neutralVariantKey: + controller.useNeutralKey ? controller.neutralSeedColor : null, variant: controller.usedVariant.isFlutterScheme ? controller.usedVariant : null, @@ -65,6 +68,8 @@ class AppTheme { error: controller.pinError && controller.useErrorKey ? controller.errorSeedColor : null, + surfaceTint: + controller.useNeutralKey ? controller.neutralSeedColor : null, ); // Light mode theme @@ -93,6 +98,9 @@ class AppTheme { tertiaryKey: controller.useTertiaryKey ? controller.tertiarySeedColor : null, errorKey: controller.useErrorKey ? controller.errorSeedColor : null, + neutralKey: controller.useNeutralKey ? controller.neutralSeedColor : null, + neutralVariantKey: + controller.useNeutralKey ? controller.neutralSeedColor : null, variant: controller.usedVariant.isFlutterScheme ? controller.usedVariant : null, @@ -123,6 +131,8 @@ class AppTheme { errorContainer: controller.pinError && controller.useErrorKey ? controller.errorSeedColor : null, + surfaceTint: + controller.useNeutralKey ? controller.neutralSeedColor : null, ); // Dark mode theme From 6211f0eb108e9ab112a89b429e157c4e7d53953d Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 23 Jun 2024 18:56:02 +0300 Subject: [PATCH 30/37] ADD: Tests for new feature to get back to 100% cov --- test/flex_seed_scheme_test.dart | 316 ++++++++++++++++++++++++++ test/flex_tones_test.dart | 18 +- test/mcu/dynamic_scheme_test.dart | 76 +++---- test/mcu/scheme_expressive_test.dart | 11 + test/mcu/scheme_fruit_salad_test.dart | 11 + test/mcu/scheme_neutral_test.dart | 11 + test/mcu/scheme_rainbow_test.dart | 11 + test/mcu/scheme_tonal_spot_test.dart | 22 ++ test/mcu/scheme_vibrant_test.dart | 11 + 9 files changed, 438 insertions(+), 49 deletions(-) diff --git a/test/flex_seed_scheme_test.dart b/test/flex_seed_scheme_test.dart index a470b1a..3edbdd4 100644 --- a/test/flex_seed_scheme_test.dart +++ b/test/flex_seed_scheme_test.dart @@ -567,6 +567,7 @@ void main() { }); const Color neutralSeedColor = Color(0xFF76777C); const Color neutralVariantSeedColor = Color(0xFF767871); + test( 'FCS7.010-l: GIVEN a SeedColorScheme.fromSeeds using five seeds ' 'and tones map FlexTones.light for with neutrals from key incl its ' @@ -906,5 +907,320 @@ void main() { ); expect(scheme, scheme2); }); + + test( + 'FCS7.015-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' + 'and variant tonalSpot for a light scheme ' + 'EXPECT scheme equal to using tones material with same seeds', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + variant: FlexSchemeVariant.tonalSpot, + ); + final ColorScheme scheme2 = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + tones: FlexTones.material(Brightness.light), + ); + expect(scheme, scheme2); + }); + + test( + 'FCS7.015-d: GIVEN a SeedColorScheme.fromSeeds using six seeds ' + 'and variant tonalSpot for a dark scheme ' + 'EXPECT scheme equal to using tones material with same seeds', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.dark, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + variant: FlexSchemeVariant.tonalSpot, + ); + final ColorScheme scheme2 = SeedColorScheme.fromSeeds( + brightness: Brightness.dark, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + tones: FlexTones.material(Brightness.dark), + ); + expect(scheme, scheme2); + }); + + test( + 'FCS7.016-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' + 'and variant tonalSpot for a light scheme and ' + 'useExpressiveOnContainerColors set to true ' + 'EXPECT scheme equal to using tones material with same seeds ' + 'and using modifier expressiveOnContainer', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + useExpressiveOnContainerColors: true, + variant: FlexSchemeVariant.tonalSpot, + ); + final ColorScheme scheme2 = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + tones: FlexTones.material(Brightness.light).expressiveOnContainer(), + ); + expect(scheme, scheme2); + }); + + test( + 'FCS7.016-d: GIVEN a SeedColorScheme.fromSeeds using six seeds ' + 'and variant tonalSpot for a dark scheme and ' + 'useExpressiveOnContainerColors set to true ' + 'EXPECT scheme equal to using tones material with same seeds ' + 'and using modifier expressiveOnContainer', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.dark, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + useExpressiveOnContainerColors: true, + variant: FlexSchemeVariant.tonalSpot, + ); + final ColorScheme scheme2 = SeedColorScheme.fromSeeds( + brightness: Brightness.dark, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + tones: FlexTones.material(Brightness.dark).expressiveOnContainer(), + ); + expect(scheme, scheme2); + }); + + test( + 'FCS7.017-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' + 'and variant content for a light scheme ' + 'EXPECT some given checked color result', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + variant: FlexSchemeVariant.content, + ); + expect(scheme.primary, const Color(0xff4f378a)); + expect(scheme.primaryContainer, const Color(0xff6750a4)); + expect(scheme.secondary, const Color(0xff4d5f7d)); + expect(scheme.secondaryContainer, const Color(0xffc8dbfe)); + expect(scheme.tertiary, const Color(0xff1f5205)); + expect(scheme.tertiaryContainer, const Color(0xff376b1e)); + expect(scheme.error, const Color(0xffba1a1a)); + expect(scheme.errorContainer, const Color(0xffffdad6)); + expect(scheme.surface, const Color(0xfffcf8f8)); + expect(scheme.surfaceContainer, const Color(0xfff1eded)); + }); + test( + 'FCS7.018-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' + 'and variant expressive for a light scheme ' + 'EXPECT some given checked color result', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + variant: FlexSchemeVariant.expressive, + ); + expect(scheme.primary, const Color(0xff006b5a)); + expect(scheme.primaryContainer, const Color(0xff95f4dc)); + expect(scheme.secondary, const Color(0xff7b5266)); + expect(scheme.secondaryContainer, const Color(0xffffd8e8)); + expect(scheme.tertiary, const Color(0xff1f695e)); + expect(scheme.tertiaryContainer, const Color(0xffa9f0e2)); + expect(scheme.error, const Color(0xffba1a1a)); + expect(scheme.errorContainer, const Color(0xffffdad6)); + expect(scheme.surface, const Color(0xfffbf8ff)); + expect(scheme.surfaceContainer, const Color(0xffeeedf8)); + }); + test( + 'FCS7.019-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' + 'and variant fidelity for a light scheme ' + 'EXPECT some given checked color result', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + variant: FlexSchemeVariant.fidelity, + ); + expect(scheme.primary, const Color(0xff4f378a)); + expect(scheme.primaryContainer, const Color(0xff6750a4)); + expect(scheme.secondary, const Color(0xff4d5f7d)); + expect(scheme.secondaryContainer, const Color(0xffc8dbfe)); + expect(scheme.tertiary, const Color(0xff1f5205)); + expect(scheme.tertiaryContainer, const Color(0xff376b1e)); + expect(scheme.error, const Color(0xffba1a1a)); + expect(scheme.errorContainer, const Color(0xffffdad6)); + expect(scheme.surface, const Color(0xfffcf8f8)); + expect(scheme.surfaceContainer, const Color(0xfff1eded)); + }); + test( + 'FCS7.020-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' + 'and fruitSalad content for a light scheme ' + 'EXPECT some given checked color result', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + variant: FlexSchemeVariant.fruitSalad, + ); + expect(scheme.primary, const Color(0xff00639c)); + expect(scheme.primaryContainer, const Color(0xffcee5ff)); + expect(scheme.secondary, const Color(0xff006875)); + expect(scheme.secondaryContainer, const Color(0xff9eeffe)); + expect(scheme.tertiary, const Color(0xff446732)); + expect(scheme.tertiaryContainer, const Color(0xffc5efab)); + expect(scheme.error, const Color(0xffba1a1a)); + expect(scheme.errorContainer, const Color(0xffffdad6)); + expect(scheme.surface, const Color(0xfff9f9ff)); + expect(scheme.surfaceContainer, const Color(0xffeaeefa)); + }); + test( + 'FCS7.021-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' + 'and monochrome content for a light scheme ' + 'EXPECT some given checked color result', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + variant: FlexSchemeVariant.monochrome, + ); + expect(scheme.primary, const Color(0xff000000)); + expect(scheme.primaryContainer, const Color(0xff3b3b3b)); + expect(scheme.secondary, const Color(0xff5e5e5e)); + expect(scheme.secondaryContainer, const Color(0xffd4d4d4)); + expect(scheme.tertiary, const Color(0xff3b3b3b)); + expect(scheme.tertiaryContainer, const Color(0xff747474)); + expect(scheme.error, const Color(0xffba1a1a)); + expect(scheme.errorContainer, const Color(0xffffdad6)); + expect(scheme.surface, const Color(0xfff9f9f9)); + expect(scheme.surfaceContainer, const Color(0xffeeeeee)); + }); + test( + 'FCS7.022-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' + 'and neutral content for a light scheme ' + 'EXPECT some given checked color result', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + variant: FlexSchemeVariant.neutral, + ); + expect(scheme.primary, const Color(0xff615c6b)); + expect(scheme.primaryContainer, const Color(0xffe7dff2)); + expect(scheme.secondary, const Color(0xff5b5e66)); + expect(scheme.secondaryContainer, const Color(0xffe0e2ec)); + expect(scheme.tertiary, const Color(0xff55624c)); + expect(scheme.tertiaryContainer, const Color(0xffd8e7cb)); + expect(scheme.error, const Color(0xffba1a1a)); + expect(scheme.errorContainer, const Color(0xffffdad6)); + expect(scheme.surface, const Color(0xfffbf8fa)); + expect(scheme.surfaceContainer, const Color(0xfff0edee)); + }); + test( + 'FCS7.023-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' + 'and rainbow content for a light scheme ' + 'EXPECT some given checked color result', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + variant: FlexSchemeVariant.rainbow, + ); + expect(scheme.primary, const Color(0xff6750a4)); + expect(scheme.primaryContainer, const Color(0xffe9ddff)); + expect(scheme.secondary, const Color(0xff555f71)); + expect(scheme.secondaryContainer, const Color(0xffd9e3f8)); + expect(scheme.tertiary, const Color(0xff4f6442)); + expect(scheme.tertiaryContainer, const Color(0xffd1eabe)); + expect(scheme.error, const Color(0xffba1a1a)); + expect(scheme.errorContainer, const Color(0xffffdad6)); + expect(scheme.surface, const Color(0xfff9f9f9)); + expect(scheme.surfaceContainer, const Color(0xffeeeeee)); + }); + test( + 'FCS7.024-l: GIVEN a SeedColorScheme.fromSeeds using six seeds ' + 'and vibrant content for a light scheme ' + 'EXPECT some given checked color result', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + errorKey: errorSeedColor, + neutralKey: neutralSeedColor, + neutralVariantKey: neutralVariantSeedColor, + variant: FlexSchemeVariant.vibrant, + ); + expect(scheme.primary, const Color(0xff6f19ff)); + expect(scheme.primaryContainer, const Color(0xffe9ddff)); + expect(scheme.secondary, const Color(0xff565d7e)); + expect(scheme.secondaryContainer, const Color(0xffdde1ff)); + expect(scheme.tertiary, const Color(0xff2b6952)); + expect(scheme.tertiaryContainer, const Color(0xffb0f0d2)); + expect(scheme.error, const Color(0xffba1a1a)); + expect(scheme.errorContainer, const Color(0xffffdad6)); + expect(scheme.surface, const Color(0xfff9f9ff)); + expect(scheme.surfaceContainer, const Color(0xffeaeefa)); + }); }); } diff --git a/test/flex_tones_test.dart b/test/flex_tones_test.dart index d1f4fdc..72a5e93 100644 --- a/test/flex_tones_test.dart +++ b/test/flex_tones_test.dart @@ -424,7 +424,7 @@ void main() { FlexTones.material(Brightness.light), equals(const FlexTones.light( primaryChroma: 36, - primaryMinChroma: 0, + primaryMinChroma: 36, secondaryChroma: 16, tertiaryChroma: 24, )), @@ -435,7 +435,7 @@ void main() { FlexTones.material(Brightness.dark), equals(const FlexTones.dark( primaryChroma: 36, - primaryMinChroma: 0, + primaryMinChroma: 36, secondaryChroma: 16, tertiaryChroma: 24, )), @@ -446,10 +446,13 @@ void main() { expect( FlexTones.material3Legacy(Brightness.light), equals(const FlexTones.light( + surfaceTone: 99, + backgroundTone: 99, primaryChroma: 48, - primaryMinChroma: 0, + primaryMinChroma: 48, secondaryChroma: 16, tertiaryChroma: 24, + neutralChroma: 4, )), ); }); @@ -458,13 +461,14 @@ void main() { expect( FlexTones.material3Legacy(Brightness.dark), equals(const FlexTones.dark( - surfaceTone: 8, - backgroundTone: 8, + surfaceTone: 10, + backgroundTone: 10, onErrorContainerTone: 90, primaryChroma: 48, - primaryMinChroma: 0, + primaryMinChroma: 48, secondaryChroma: 16, tertiaryChroma: 24, + neutralChroma: 4, )), ); }); @@ -899,7 +903,7 @@ void main() { primaryTone: 40, primaryContainerTone: 80, onPrimaryContainerTone: 4, - secondaryTone: 60, + secondaryTone: 50, secondaryContainerTone: 92, onSecondaryContainerTone: 10, tertiaryTone: 50, diff --git a/test/mcu/dynamic_scheme_test.dart b/test/mcu/dynamic_scheme_test.dart index c38697f..a397a32 100644 --- a/test/mcu/dynamic_scheme_test.dart +++ b/test/mcu/dynamic_scheme_test.dart @@ -122,13 +122,12 @@ void main() { MaterialDynamicColors.onSecondary.getArgb(dynamicScheme)); expect(colorScheme.secondaryContainer.value, MaterialDynamicColors.secondaryContainer.getArgb(dynamicScheme)); - // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 - // expect(colorScheme.onSecondaryContainer.value, - // MaterialDynamicColors.onSecondaryContainer.getArgb(dynamicScheme)); - // TODO(rydmike): Using hard coded value test until Flutter updates. - expect(colorScheme.onSecondaryContainer.value, 4280883206); - expect(4284039724, + expect(colorScheme.onSecondaryContainer.value, MaterialDynamicColors.onSecondaryContainer.getArgb(dynamicScheme)); + // TODO(rydmike): Use hard coded values to test expressive colors + // expect(colorScheme.onSecondaryContainer.value, 4280883206); + // expect(4284039724, + // MaterialDynamicColors.onSecondaryContainer.getArgb(dynamicScheme)); // expect(colorScheme.secondaryFixed.value, MaterialDynamicColors.secondaryFixed.getArgb(dynamicScheme)); @@ -144,13 +143,12 @@ void main() { MaterialDynamicColors.onTertiary.getArgb(dynamicScheme)); expect(colorScheme.tertiaryContainer.value, MaterialDynamicColors.tertiaryContainer.getArgb(dynamicScheme)); - // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 - // expect(colorScheme.onTertiaryContainer.value, - // MaterialDynamicColors.onTertiaryContainer.getArgb(dynamicScheme)); - // TODO(rydmike): Using hard coded value test until Flutter updates. - expect(colorScheme.onTertiaryContainer.value, 4279639553); - expect(4282469156, + expect(colorScheme.onTertiaryContainer.value, MaterialDynamicColors.onTertiaryContainer.getArgb(dynamicScheme)); + // TODO(rydmike): Use hard coded values to test expressive colors + // expect(colorScheme.onTertiaryContainer.value, 4279639553); + // expect(4282469156, + // MaterialDynamicColors.onTertiaryContainer.getArgb(dynamicScheme)); // expect(colorScheme.tertiaryFixed.value, MaterialDynamicColors.tertiaryFixed.getArgb(dynamicScheme)); @@ -166,13 +164,12 @@ void main() { MaterialDynamicColors.onError.getArgb(dynamicScheme)); expect(colorScheme.errorContainer.value, MaterialDynamicColors.errorContainer.getArgb(dynamicScheme)); - // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 - // expect(colorScheme.onErrorContainer.value, - // MaterialDynamicColors.onErrorContainer.getArgb(dynamicScheme)); - // TODO(rydmike): Using hard coded value test until Flutter updates. - expect(colorScheme.onErrorContainer.value, 4282449922); - expect(4287823882, + expect(colorScheme.onErrorContainer.value, MaterialDynamicColors.onErrorContainer.getArgb(dynamicScheme)); + // TODO(rydmike): Use hard coded values to test expressive colors + // expect(colorScheme.onErrorContainer.value, 4282449922); + // expect(4287823882, + // MaterialDynamicColors.onErrorContainer.getArgb(dynamicScheme)); // expect(colorScheme.background.value, MaterialDynamicColors.background.getArgb(dynamicScheme)); @@ -219,12 +216,11 @@ void main() { expect(colorScheme.primary.value, dynamicScheme.primary); expect(colorScheme.onPrimary.value, dynamicScheme.onPrimary); expect(colorScheme.primaryContainer.value, dynamicScheme.primaryContainer); - // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 - // expect( - // colorScheme.onPrimaryContainer.value, dynamicScheme.onPrimaryContainer); - // TODO(rydmike): Using hard coded value test until Flutter updates. - expect(colorScheme.onPrimaryContainer.value, 4281079296); - expect(4285086720, dynamicScheme.onPrimaryContainer); + expect( + colorScheme.onPrimaryContainer.value, dynamicScheme.onPrimaryContainer); + // TODO(rydmike): Use hard coded values to test expressive colors + // expect(colorScheme.onPrimaryContainer.value, 4281079296); + // expect(4285086720, dynamicScheme.onPrimaryContainer); // expect(colorScheme.primaryFixed.value, dynamicScheme.primaryFixed); expect(colorScheme.primaryFixedDim.value, dynamicScheme.primaryFixedDim); @@ -235,12 +231,11 @@ void main() { expect(colorScheme.onSecondary.value, dynamicScheme.onSecondary); expect( colorScheme.secondaryContainer.value, dynamicScheme.secondaryContainer); - // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 - // expect(colorScheme.onSecondaryContainer.value, - // dynamicScheme.onSecondaryContainer); - // TODO(rydmike): Using hard coded value test until Flutter updates. - expect(colorScheme.onSecondaryContainer.value, 4280883206); - expect(4284039724, dynamicScheme.onSecondaryContainer); + expect(colorScheme.onSecondaryContainer.value, + dynamicScheme.onSecondaryContainer); + // TODO(rydmike): Use hard coded values to test expressive colors + // expect(colorScheme.onSecondaryContainer.value, 4280883206); + // expect(4284039724, dynamicScheme.onSecondaryContainer); // expect(colorScheme.secondaryFixed.value, dynamicScheme.secondaryFixed); expect( @@ -252,12 +247,11 @@ void main() { expect(colorScheme.onTertiary.value, dynamicScheme.onTertiary); expect( colorScheme.tertiaryContainer.value, dynamicScheme.tertiaryContainer); - // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 - // expect(colorScheme.onTertiaryContainer.value, - // dynamicScheme.onTertiaryContainer); - // TODO(rydmike): Using hard coded value test until Flutter updates. - expect(colorScheme.onTertiaryContainer.value, 4279639553); - expect(4282469156, dynamicScheme.onTertiaryContainer); + expect(colorScheme.onTertiaryContainer.value, + dynamicScheme.onTertiaryContainer); + // TODO(rydmike): Use hard coded values to test expressive colors + // expect(colorScheme.onTertiaryContainer.value, 4279639553); + // expect(4282469156, dynamicScheme.onTertiaryContainer); // expect(colorScheme.tertiaryFixed.value, dynamicScheme.tertiaryFixed); expect(colorScheme.tertiaryFixedDim.value, dynamicScheme.tertiaryFixedDim); @@ -267,12 +261,10 @@ void main() { expect(colorScheme.error.value, dynamicScheme.error); expect(colorScheme.onError.value, dynamicScheme.onError); expect(colorScheme.errorContainer.value, dynamicScheme.errorContainer); - // TODO(rydmike): This test cannot pass until Flutter updates to MCU 0.12.0 - // expect(colorScheme.onErrorContainer.value, - // dynamicScheme.onErrorContainer); - // TODO(rydmike): Using hard coded value test until Flutter updates. - expect(colorScheme.onErrorContainer.value, 4282449922); - expect(4287823882, dynamicScheme.onErrorContainer); + expect(colorScheme.onErrorContainer.value, dynamicScheme.onErrorContainer); + // TODO(rydmike): Use hard coded values to test expressive colors + // expect(colorScheme.onErrorContainer.value, 4282449922); + // expect(4287823882, dynamicScheme.onErrorContainer); // expect(colorScheme.background.value, dynamicScheme.background); expect(colorScheme.onBackground.value, dynamicScheme.onBackground); diff --git a/test/mcu/scheme_expressive_test.dart b/test/mcu/scheme_expressive_test.dart index 0db3397..e4a7802 100644 --- a/test/mcu/scheme_expressive_test.dart +++ b/test/mcu/scheme_expressive_test.dart @@ -99,11 +99,22 @@ void main() { final SchemeExpressive scheme = SchemeExpressive( sourceColorHct: Hct.fromInt(0xff0000ff), isDark: false, + useExpressiveOnContainerColors: true, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), isColor(0xff005234)); }); + test('lightTheme_standardContrast_onPrimaryContainer', () { + final SchemeExpressive scheme = SchemeExpressive( + sourceColorHct: Hct.fromInt(0xff0000ff), + isDark: false, + useExpressiveOnContainerColors: false, + contrastLevel: 0.0); + expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), + isColor(0xff002112)); + }); + test('lightTheme_maxContrast_onPrimaryContainer', () { final SchemeExpressive scheme = SchemeExpressive( sourceColorHct: Hct.fromInt(0xff0000ff), diff --git a/test/mcu/scheme_fruit_salad_test.dart b/test/mcu/scheme_fruit_salad_test.dart index 506c317..a9ced0f 100644 --- a/test/mcu/scheme_fruit_salad_test.dart +++ b/test/mcu/scheme_fruit_salad_test.dart @@ -126,11 +126,22 @@ void main() { final SchemeFruitSalad scheme = SchemeFruitSalad( sourceColorHct: Hct.fromInt(0xff0000ff), isDark: false, + useExpressiveOnContainerColors: true, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), isColor(0xff004D67)); }); + test('lightTheme_standardContrast_onPrimaryContainer', () { + final SchemeFruitSalad scheme = SchemeFruitSalad( + sourceColorHct: Hct.fromInt(0xff0000ff), + isDark: false, + useExpressiveOnContainerColors: false, + contrastLevel: 0.0); + expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), + isColor(0xff001E2B)); + }); + test('lightTheme_maxContrast_onPrimaryContainer', () { final SchemeFruitSalad scheme = SchemeFruitSalad( sourceColorHct: Hct.fromInt(0xff0000ff), diff --git a/test/mcu/scheme_neutral_test.dart b/test/mcu/scheme_neutral_test.dart index fba9d2d..c51eb30 100644 --- a/test/mcu/scheme_neutral_test.dart +++ b/test/mcu/scheme_neutral_test.dart @@ -99,11 +99,22 @@ void main() { final SchemeNeutral scheme = SchemeNeutral( sourceColorHct: Hct.fromInt(0xff0000ff), isDark: false, + useExpressiveOnContainerColors: true, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), isColor(0xff454654)); }); + test('lightTheme_standardContrast_onPrimaryContainer', () { + final SchemeNeutral scheme = SchemeNeutral( + sourceColorHct: Hct.fromInt(0xff0000ff), + isDark: false, + useExpressiveOnContainerColors: false, + contrastLevel: 0.0); + expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), + isColor(0xff1A1B27)); + }); + test('lightTheme_maxContrast_onPrimaryContainer', () { final SchemeNeutral scheme = SchemeNeutral( sourceColorHct: Hct.fromInt(0xff0000ff), diff --git a/test/mcu/scheme_rainbow_test.dart b/test/mcu/scheme_rainbow_test.dart index 00f3f32..10f0e5a 100644 --- a/test/mcu/scheme_rainbow_test.dart +++ b/test/mcu/scheme_rainbow_test.dart @@ -126,11 +126,22 @@ void main() { final SchemeRainbow scheme = SchemeRainbow( sourceColorHct: Hct.fromInt(0xff0000ff), isDark: false, + useExpressiveOnContainerColors: true, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), isColor(0xff383E8F)); }); + test('lightTheme_standardContrast_onPrimaryContainer', () { + final SchemeRainbow scheme = SchemeRainbow( + sourceColorHct: Hct.fromInt(0xff0000ff), + isDark: false, + useExpressiveOnContainerColors: false, + contrastLevel: 0.0); + expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), + isColor(0xff050865)); + }); + test('lightTheme_maxContrast_onPrimaryContainer', () { final SchemeRainbow scheme = SchemeRainbow( sourceColorHct: Hct.fromInt(0xff0000ff), diff --git a/test/mcu/scheme_tonal_spot_test.dart b/test/mcu/scheme_tonal_spot_test.dart index eea2a34..65f2b0f 100644 --- a/test/mcu/scheme_tonal_spot_test.dart +++ b/test/mcu/scheme_tonal_spot_test.dart @@ -100,11 +100,22 @@ void main() { final SchemeNeutral scheme = SchemeNeutral( sourceColorHct: Hct.fromInt(0xff0000ff), isDark: false, + useExpressiveOnContainerColors: true, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), isColor(0xff454654)); }); + test('lightTheme_standardContrast_onPrimaryContainer', () { + final SchemeNeutral scheme = SchemeNeutral( + sourceColorHct: Hct.fromInt(0xff0000ff), + isDark: false, + useExpressiveOnContainerColors: false, + contrastLevel: 0.0); + expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), + isColor(0xff1A1B27)); + }); + test('lightTheme_maxContrast_onPrimaryContainer', () { final SchemeNeutral scheme = SchemeNeutral( sourceColorHct: Hct.fromInt(0xff0000ff), @@ -332,11 +343,22 @@ void main() { final SchemeTonalSpot scheme = SchemeTonalSpot( sourceColorHct: Hct.fromInt(0xff0000ff), isDark: false, + useExpressiveOnContainerColors: true, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), isColor(0xff3E4278)); }); + test('lightTheme_standardContrast_onPrimaryContainer', () { + final SchemeTonalSpot scheme = SchemeTonalSpot( + sourceColorHct: Hct.fromInt(0xff0000ff), + isDark: false, + useExpressiveOnContainerColors: false, + contrastLevel: 0.0); + expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), + isColor(0xff11144B)); + }); + test('lightTheme_maxContrast_onPrimaryContainer', () { final SchemeTonalSpot scheme = SchemeTonalSpot( sourceColorHct: Hct.fromInt(0xff0000ff), diff --git a/test/mcu/scheme_vibrant_test.dart b/test/mcu/scheme_vibrant_test.dart index ee3222f..de61bf3 100644 --- a/test/mcu/scheme_vibrant_test.dart +++ b/test/mcu/scheme_vibrant_test.dart @@ -99,11 +99,22 @@ void main() { final SchemeVibrant scheme = SchemeVibrant( sourceColorHct: Hct.fromInt(0xff0000ff), isDark: false, + useExpressiveOnContainerColors: true, contrastLevel: 0.0); expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), isColor(0xff0000EF)); }); + test('lightTheme_standardContrast_onPrimaryContainer', () { + final SchemeVibrant scheme = SchemeVibrant( + sourceColorHct: Hct.fromInt(0xff0000ff), + isDark: false, + useExpressiveOnContainerColors: false, + contrastLevel: 0.0); + expect(MaterialDynamicColors.onPrimaryContainer.getArgb(scheme), + isColor(0xff00006E)); + }); + test('lightTheme_maxContrast_onPrimaryContainer', () { final SchemeVibrant scheme = SchemeVibrant( sourceColorHct: Hct.fromInt(0xff0000ff), From 2d6fc50d72d6030fd2bc4ea7679ccf7d74af9ab9 Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 23 Jun 2024 19:49:22 +0300 Subject: [PATCH 31/37] ADD: Tests for material3Legacy that it really matches the legacy --- CHANGELOG.md | 10 +-- lib/src/mcu/scheme/scheme.dart | 4 + test/flex_seed_scheme_test.dart | 140 ++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dddc81f..9e56d64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to the **FlexSeedScheme** (FSS) package are documented here. ## 3.0.0 -**June 21, 2024** +**June 23, 2024** Bring the bundled forked version of the package [Material Color Utilities (MCU)](https://pub.dev/packages/material_color_utilities) to feature parity with version 0.12.0 of the original package. The internal fork for the first time nw also adds features and capabilities that do not exist in the original package. Previously FSS only bundled MCU to avoid version conflicts with Flutter SDK when using different channels. Different Flutter channels typically pin incompatible versions of MCU. @@ -12,13 +12,13 @@ The bundled forked version of MCU also has more tests than the original, allowin * **BREAKING** - * The API for `SeedColorScheme.buildDynamicScheme` was changed to enable support for multiple seed colors on the MCU based `DynamicScheme` APIs and its extended schemes. For most normal use cases you will not notice this, as it is quite a low-level API in FSS that end users normally do not use. + * The API for `SeedColorScheme.buildDynamicScheme` was changed to enable support for multiple seed colors on the MCU based `DynamicScheme` APIs and its extended schemes. For most normal use cases, you will not notice this, as it is quite a low-level API in FSS that end users normally do not use. * **NEW** - * The forked internal MCU version received new features. First `DynamicScheme` can accept an optional `customErrorPalette` and then `SchemeTonalSpot`, `SchemeContent`, `SchemeFidelity`, `SchemeExpressive`, `SchemeFruitSalad`, `SchemeMonochrome`, `SchemeNeutral`, `SchemeRainbow` and `SchemeVibrant` that extend `DynamicScheme` all received properties to support individual seed colors for all tonal palettes. + * The forked internal MCU version received new features. The `DynamicScheme` can now accept an optional `customErrorPalette` and then `SchemeTonalSpot`, `SchemeContent`, `SchemeFidelity`, `SchemeExpressive`, `SchemeFruitSalad`, `SchemeMonochrome`, `SchemeNeutral`, `SchemeRainbow` and `SchemeVibrant` that extend `DynamicScheme` all received properties to support individual seed colors for all tonal palettes. - * The above addition enables `SeedColorScheme.fromSeeds` to support using all its key seed colors also when using MCU based `DynamicScheme` variants and not just for `FlexTones` based `tones` and `variants`. + * The above addition enables `SeedColorScheme.fromSeeds` to support using all its key seed colors also when using MCU based `DynamicScheme` variants and not just for `FlexTones` based `tones` and `variants`. When using key seed colors with MCU variants, they still respect their original design intent. * Added support for `contrastLevel` to `SeedColorScheme.fromSeeds`. This allows you to set the desired contrast level of the generated color scheme when using `SeedColorScheme.fromSeeds` with the `variant` property, for variants that are based on MCU's `DynamicScheme`. Such variants have their `isFlutterScheme` set to true. * The `contrastLevel` parameter indicates the contrast level between color pairs, such as `primary` and `onPrimary`.The value 0.0 is the default (normal); -1.0 is the lowest; 1.0 is the highest. From Material Design guideline, the medium and high contrast, correspond to 0.5 and 1.0 respectively. @@ -53,7 +53,7 @@ The bundled forked version of MCU also has more tests than the original, allowin * **FIX** - * The `FlexTones.material3Legacy` was corrected. It had some incorrect tones and chroma in its configuration. The mistakes were fixed. + * The `FlexTones.material3Legacy` was corrected. It had some incorrect tones and chroma in its configuration. The mistakes were fixed. Tests were added to check the FlexTones.material3Legacy compared to the MCU deprecated Scheme based Colors, for colors that exist in both. * EXAMPLE APP: The key color to seed the error palette was not used in the main example in dark mode. diff --git a/lib/src/mcu/scheme/scheme.dart b/lib/src/mcu/scheme/scheme.dart index c0d8bd4..99196bb 100644 --- a/lib/src/mcu/scheme/scheme.dart +++ b/lib/src/mcu/scheme/scheme.dart @@ -213,6 +213,10 @@ class Scheme { error: palette.error.get(80), onError: palette.error.get(20), errorContainer: palette.error.get(30), + // TODO(rydmike): This tone is wrong, based on both past and current + // spec it should be tone 90. MCU has always used the wrong tone here. + // whereas FSS has used the one from the spec. We will skip this color + // in the legacy test due to the error in the legacy Scheme. onErrorContainer: palette.error.get(80), background: palette.neutral.get(10), onBackground: palette.neutral.get(90), diff --git a/test/flex_seed_scheme_test.dart b/test/flex_seed_scheme_test.dart index 3edbdd4..fed966c 100644 --- a/test/flex_seed_scheme_test.dart +++ b/test/flex_seed_scheme_test.dart @@ -1,5 +1,6 @@ import 'package:flex_seed_scheme/flex_seed_scheme.dart'; import 'package:flex_seed_scheme/src/mcu/dynamiccolor/material_dynamic_colors.dart'; +import 'package:flex_seed_scheme/src/mcu/scheme/scheme.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -1222,5 +1223,144 @@ void main() { expect(scheme.surface, const Color(0xfff9f9ff)); expect(scheme.surfaceContainer, const Color(0xffeaeefa)); }); + + test( + 'FCS7.026-l: GIVEN a SeedColorScheme.fromSeeds using primary seed ' + 'and variant material3Legacy for a light scheme ' + 'EXPECT it to be equal to one made with tone material3Legacy', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + variant: FlexSchemeVariant.material3Legacy, + ); + final ColorScheme scheme2 = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + tones: FlexTones.material3Legacy(Brightness.light), + ); + expect(scheme, scheme2); + }); + + test( + 'FCS7.026-d: GIVEN a SeedColorScheme.fromSeeds using primary seed ' + 'and variant material3Legacy for a dark scheme ' + 'EXPECT it to be equal to one made with tone material3Legacy', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.dark, + primaryKey: primarySeedColor, + variant: FlexSchemeVariant.material3Legacy, + ); + final ColorScheme scheme2 = SeedColorScheme.fromSeeds( + brightness: Brightness.dark, + primaryKey: primarySeedColor, + tones: FlexTones.material3Legacy(Brightness.dark), + ); + expect(scheme, scheme2); + }); + + test( + 'FCS7.027-l: GIVEN a SeedColorScheme.fromSeeds using primary seed ' + 'and variant material3Legacy for a light scheme ' + 'EXPECT its colors to be equal colors in a scheme made with legacy ' + 'MCU Scheme for colors that existed in it', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + variant: FlexSchemeVariant.material3Legacy, + ); + final Scheme scheme2 = Scheme.light(primarySeedColor.value); + expect(scheme.primary, Color(scheme2.primary)); + expect(scheme.onPrimary, Color(scheme2.onPrimary)); + expect(scheme.primaryContainer, Color(scheme2.primaryContainer)); + expect(scheme.onPrimaryContainer, Color(scheme2.onPrimaryContainer)); + // + expect(scheme.secondary, Color(scheme2.secondary)); + expect(scheme.onSecondary, Color(scheme2.onSecondary)); + expect(scheme.secondaryContainer, Color(scheme2.secondaryContainer)); + expect(scheme.onSecondaryContainer, Color(scheme2.onSecondaryContainer)); + // + expect(scheme.tertiary, Color(scheme2.tertiary)); + expect(scheme.onTertiary, Color(scheme2.onTertiary)); + expect(scheme.tertiaryContainer, Color(scheme2.tertiaryContainer)); + expect(scheme.onTertiaryContainer, Color(scheme2.onTertiaryContainer)); + // + expect(scheme.error, Color(scheme2.error)); + expect(scheme.onError, Color(scheme2.onError)); + expect(scheme.errorContainer, Color(scheme2.errorContainer)); + expect(scheme.onErrorContainer, Color(scheme2.onErrorContainer)); + // + expect(scheme.outline, Color(scheme2.outline)); + expect(scheme.outlineVariant, Color(scheme2.outlineVariant)); + // + expect(scheme.surface, Color(scheme2.surface)); + expect(scheme.onSurface, Color(scheme2.onSurface)); + expect(scheme.onSurfaceVariant, Color(scheme2.onSurfaceVariant)); + expect(scheme.inverseSurface, Color(scheme2.inverseSurface)); + expect(scheme.onInverseSurface, Color(scheme2.inverseOnSurface)); + expect(scheme.inversePrimary, Color(scheme2.inversePrimary)); + // + expect(scheme.shadow, Color(scheme2.shadow)); + expect(scheme.scrim, Color(scheme2.scrim)); + expect(scheme.surfaceTint, Color(scheme2.primary)); + // + expect(scheme.background, Color(scheme2.background)); + expect(scheme.onBackground, Color(scheme2.onBackground)); + expect(scheme.surfaceVariant, Color(scheme2.surfaceVariant)); + }); + + test( + 'FCS7.027-2: GIVEN a SeedColorScheme.fromSeeds using primary seed ' + 'and variant material3Legacy for a dark scheme ' + 'EXPECT its colors to be equal colors in a scheme made with legacy ' + 'MCU Scheme for colors that existed in it', () { + final ColorScheme scheme = SeedColorScheme.fromSeeds( + brightness: Brightness.dark, + primaryKey: primarySeedColor, + variant: FlexSchemeVariant.material3Legacy, + ); + final Scheme scheme2 = Scheme.dark(primarySeedColor.value); + expect(scheme.primary, Color(scheme2.primary)); + expect(scheme.onPrimary, Color(scheme2.onPrimary)); + expect(scheme.primaryContainer, Color(scheme2.primaryContainer)); + expect(scheme.onPrimaryContainer, Color(scheme2.onPrimaryContainer)); + // + expect(scheme.secondary, Color(scheme2.secondary)); + expect(scheme.onSecondary, Color(scheme2.onSecondary)); + expect(scheme.secondaryContainer, Color(scheme2.secondaryContainer)); + expect(scheme.onSecondaryContainer, Color(scheme2.onSecondaryContainer)); + // + expect(scheme.tertiary, Color(scheme2.tertiary)); + expect(scheme.onTertiary, Color(scheme2.onTertiary)); + expect(scheme.tertiaryContainer, Color(scheme2.tertiaryContainer)); + expect(scheme.onTertiaryContainer, Color(scheme2.onTertiaryContainer)); + // + expect(scheme.error, Color(scheme2.error)); + expect(scheme.onError, Color(scheme2.onError)); + expect(scheme.errorContainer, Color(scheme2.errorContainer)); + // TODO(rydmike): We are skipping this color in the test. MCU has always + // used the wrong tone here. Whereas FSS has used the one from the spec. + // It it should be tone 90, MCU uses 80, which is wrong. Even way back + // machine on web shows that it was always 90, at least for a few years + // back. + // expect(scheme.onErrorContainer, Color(scheme2.onErrorContainer)); + // + expect(scheme.outline, Color(scheme2.outline)); + expect(scheme.outlineVariant, Color(scheme2.outlineVariant)); + // + expect(scheme.surface, Color(scheme2.surface)); + expect(scheme.onSurface, Color(scheme2.onSurface)); + expect(scheme.onSurfaceVariant, Color(scheme2.onSurfaceVariant)); + expect(scheme.inverseSurface, Color(scheme2.inverseSurface)); + expect(scheme.onInverseSurface, Color(scheme2.inverseOnSurface)); + expect(scheme.inversePrimary, Color(scheme2.inversePrimary)); + // + expect(scheme.shadow, Color(scheme2.shadow)); + expect(scheme.scrim, Color(scheme2.scrim)); + expect(scheme.surfaceTint, Color(scheme2.primary)); + // + expect(scheme.background, Color(scheme2.background)); + expect(scheme.onBackground, Color(scheme2.onBackground)); + expect(scheme.surfaceVariant, Color(scheme2.surfaceVariant)); + }); }); } From 6ded0941fa69b28368504ef90526c94cdeeb0dbf Mon Sep 17 00:00:00 2001 From: Rydmike <39990307+rydmike@users.noreply.github.com> Date: Sun, 23 Jun 2024 21:40:45 +0300 Subject: [PATCH 32/37] Update: Readme and add assets --- CHANGELOG.md | 2 +- README.md | 141 ++++++++++++++++++++--------- doc_assets/colorscheme_dark_v2.png | Bin 0 -> 150745 bytes doc_assets/colorscheme_v2.png | Bin 0 -> 149646 bytes doc_assets/corepalettes_v2.png | Bin 0 -> 52446 bytes 5 files changed, 99 insertions(+), 44 deletions(-) create mode 100644 doc_assets/colorscheme_dark_v2.png create mode 100644 doc_assets/colorscheme_v2.png create mode 100644 doc_assets/corepalettes_v2.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e56d64..5d53b1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ The bundled forked version of MCU also has more tests than the original, allowin * **NOTE:** Using `contrastLevel` has no effect when using `tones`. However, with `tones` you can create custom tones with even more flexibility in seed generation to make schemes with higher or less contrast. Two pre-configured high contrast tones exist earlier via `FlexTones.highContrast` and `FlexTones.ultraContrast`. * Updated `MaterialDynamicColors` to optionally use the new Material expressive on-colors spec for none surface on-container colors. This feature is not on by default. You can opt in on this new standard by setting `useExpressiveOnContainerColors` to true in `SeedColorScheme.fromSeeds`. - * This option is only available when using MCU based `DynamicScheme` variants and not when using `FlexTones` based `tones` and `variants`. Additionally, it only applies to variants that are based on MCU's `DynamicScheme`. Such variants have their `isFlutterScheme` set to true. + * This option is only available when using MCU based `DynamicScheme` variants and not when using `FlexTones` based `tones` and `variants`, plus it only applies to variants that are based on MCU's `DynamicScheme`. Such variants have their `isFlutterScheme` set to true. * Opting in changes the light mode color tone for the colors `onPrimaryContainer`, `onSecondaryContainer`, `onTertiaryContainer` and `onErrorContainer` from 10 to 30 making them more color expressive, but they also have less contrast. * The accepted min contrast curve is thus now `ContrastCurve(3, 4.5, 7, 11)` instead of `ContrastCurve(4.5, 7, 11, 21)` for the on-container colors. Meaning normal contrast of 4.5 is now accepted when it was 7 before. * Prior to MCU version 0.12.0 the `MaterialDynamicColors` used an older M3 spec. Flutter stable 3.22.x and Flutter master 3.23.x still use MCU versions lower than 0.12.0 and default to the older color tones 10 in light mode. This will be changed in Flutter SDK when Flutter is updated to use MCU 0.12.0 or later. With FSS 3.0.0 you can opt in on using the new spec already now. But FSS still also defaults to the older spec with more contrast. When Flutter stable changes to use the new spec, FSS will also change to use it as default. While Flutter and MCU will then no longer offer the older higher contrast version, FSS will continue to do so. diff --git a/README.md b/README.md index 942334c..9264d1f 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,9 @@ A more flexible and powerful version of Flutter's `ColorScheme.fromSeed`. Use this package like `ColorScheme.fromSeed` with the following additional capabilities: -* Use separate seed key colors to generate seed-based tonal palettes for primary and optionally secondary, tertiary as well as error, neutral and neutral variant colors in `ColorScheme`. +* Use separate key seed colors to generate seed-based tonal palettes for primary and optionally secondary, tertiary as well as error, neutral and neutral variant colors used by the `ColorScheme`. * Change the chroma limits and values used in the Material-3 default strategy for tonal palette generation in the new Google HCT (Hue-Chroma-Tone) color space. -* Use new Material-3 design `ColorScheme` variants like `fidelity` and `monochrome` that arrived in Flutter SDK v3.22.2. With FSS you can use them already with Flutter 3.22.0. -* Change which tones in the generated core tonal palettes are used by which `ColorScheme` color. -* Use `FlexPaletteType.extended`, that gives access to all the new tones that are used in revised Material-3 design for surfaces and fixed main color. The new tones are used and supported by the standard `ColorScheme` in Flutter 3.22 and later. It is recommended to use `FlexPaletteType.extended` if creating new custom `FlexTones`. In the package version 2.0.0 and later, `FlexPaletteType.extended` is the default palette type. The older palette type `FlexPaletteType.common` is available for backwards compatibility, but should be avoided in Flutter 3.22 and later. +* Use new Material-3 design `ColorScheme` variants like `fidelity`, `content` and `monochrome` that arrived in Flutter SDK v3.22.2, with the twist that you can use multiple seed colors also with them and adjust the contrast level. > [!NOTE] > FlexSeedScheme still produces color values for the in Flutter 3.22 deprecated `ColorScheme` colors `background`, `onBackground` and `surfaceVariant`. It does so because despite the colors being deprecated, they are still used in `ThemeData` creation and in a few widgets by the Flutter SDK in both stable 3.22 and also in master 3.23. The colors are not used in the official Material-3 design, but are still used in the Flutter SDK. Since FSS references these deprecated colors, that it has to do to produce a correct and functional `ColorScheme`, it loses 10 score points in **pub.dev** pana analyzer. However, without doing this the `ColorScheme` produced by FSS would not be fully functional and correct for the Flutter SDK. So it is better to lose 10 points than to not work correctly. Whenever Flutter fully stops using these deprecated colors, FSS will be updated to reflect this and no longer reference them. @@ -48,7 +46,7 @@ neutral variant palettes, that are used to define all the surface and background `ColorScheme`. We recommend sticking to the default that uses primary key color's hue with very low chroma. -Chroma limits that differ from Material 3 defaults for tonal palette generation, can also be +Chroma limits that differ from Material-3 defaults for tonal palette generation, can also be defined. Additionally, tone mapping, that defines which tone is used by what `ColorScheme` color, can be customized. Both are done via `FlexTones` passed in to `tones`. @@ -109,18 +107,18 @@ bottom: * Neutral tonal palette * Neutral variant tonal palette -palettes +palettes The color tones in the above palettes are then mapped to `ColorScheme` colors. Mapping is different for light and dark theme mode to create a color scheme with suitable contrast. With the example `FlexTones.vivid` setup used in `tones`, the light `ColorScheme` is mapped as shown below: -colorscheme +colorscheme And the dark `ColorScheme` becomes: -colorscheme_dark +colorscheme_dark We can, for example, see that in light mode, the primary tone 30, is assigned to `ColorScheme.primary` color and tone 90 to `primaryContainer`. In dark mode they get tones 80 and 20. Similar assignments @@ -129,11 +127,13 @@ It is this mapping that `FlexTones` gives you control over. ### Variants -If you want to use the `ColorScheme` variants that Flutter SDK provides via `ColorScheme.fromSeed` then use `SeedColorScheme.fromSeeds` with the `variant` property of enum type `FlexSchemeVariant`. +If you want to use the same scheme seed generation variants `DynamicSchemeVariant`, that Flutter SDK introduced to `ColorScheme.fromSeed` version 3.22.2 and later, then use `SeedColorScheme.fromSeeds` with the `variant` property of enum type `FlexSchemeVariant`. -If the variant `FlexSchemeVariant` style is one that is also provided by Flutter SDK, it has `FlexSchemeVariant` property `isFlutterScheme` set to true. In that case, all other key colors except `primaryKey` are ignored. This is because the Flutter SDK `ColorScheme` variant system only supports one seed color. +If the variant `FlexSchemeVariant` style is one that is also provided by Flutter SDK, it has `FlexSchemeVariant` property `isFlutterScheme` set to true. -The `FlexSchemeVariant` also include the predefined `FlexTone` based variants. You can use the `variant` option as a way to select `ColorScheme` seed generation variant that is based on both the in Flutter 3.22.2 new Flutter SDK options and the FlexSeedScheme predefined FlexTones `tones` based seed generation options. +The `FlexSchemeVariant` also include the predefined `FlexTone` based variants. You can use the `variant` option as a way to select `ColorScheme` seed generation variant that is based on both the in Flutter 3.22.2 new SDK `dynamicSchemeVariant` and the FlexSeedScheme predefined FlexTones `tones` based seed generation options. + +Starting with FlexSeedSeed scheme version 3.0.0, when using `SeedColorScheme.fromSeeds` with a `variant` that is based on MCU and Flutter SDK `DynamicSchemeVariant`, you can use all the same seed key as with `FlexTones` based schemes using the `tones` property. You are no longer limited to using only your primary or main brand color as seed color, even when using the Flutter SDK `DynamicSchemeVariant` based variants. In previous versions of FlexSeedScheme, you could only use a single seed color with the `dynamicSchemeVariant` based variants. FSS uses a custom fork of MCU to enable this feature. ```dart // Make a light ColorScheme from a seeds using variant style fidelity. @@ -141,19 +141,23 @@ The `FlexSchemeVariant` also include the predefined `FlexTone` based variants. Y final ColorScheme schemeLight = SeedColorScheme.fromSeeds( brightness: Brightness.light, primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, variant: FlexSchemeVariant.fidelity, ); // Make a dark ColorScheme from a seeds using variant style fidelity. final ColorScheme schemeDark = SeedColorScheme.fromSeeds( brightness: Brightness.dark, primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, variant: FlexSchemeVariant.fidelity, ); ``` ## Define ThemeData -In your `MaterialApp` you then define your light and dark mode themes using the seed +In your `MaterialApp` you define your light and dark mode themes using the seed generated `ColorScheme`s just as you would with any other `ColorScheme`. For example: ```dart @@ -163,14 +167,8 @@ generated `ColorScheme`s just as you would with any other `ColorScheme`. For exa debugShowCheckedModeBanner: false, title: 'SeedColorScheme.fromSeeds Demo', themeMode: ThemeMode.system, - theme: ThemeData( - colorScheme: schemeLight, - useMaterial3: true, - ), - darkTheme: ThemeData( - colorScheme: schemeDark, - useMaterial3: true, - ), + theme: ThemeData(colorScheme: schemeLight), + darkTheme: ThemeData(colorScheme: schemeDark), home: HomePage(), ); } @@ -178,12 +176,9 @@ generated `ColorScheme`s just as you would with any other `ColorScheme`. For exa ## Override Color Values -All colors in the seed produced `ColorScheme` can be overridden by providing each `ColorScheme` -color property in `SeedColorScheme.fromSeeds` a given color value. This feature is equivalent to -`ColorScheme.fromSeed`. +All colors in the seed produced `ColorScheme` can be overridden by providing each color property in `SeedColorScheme.fromSeeds` a given override color value. This feature is equivalent to the one that exists in `ColorScheme.fromSeed`. -This is typically used to assign a given color value to `primary` color, which is often used as app -brand color. When the brand color is used as `primaryKey` seed color, it typically does +This is typically used to assign a given color value to `primary` color, which is often used as the brand color. When the brand color is used as `primaryKey` seed color, it typically does not end up as the `primary` color in the seed generated `ColorScheme`. Having a given brand color as `primary` color is often desired. To get the seed color as your `primary` brand color, assign the color used as `primaryKey` to `primary` color as well. @@ -195,7 +190,7 @@ color used as `primaryKey` to `primary` color as well. // Use a "brand" seed color as primary color in the result. primary: primarySeedColor, primaryKey: primarySeedColor, - // + // Additional brand colors used as seed keys. secondaryKey: secondarySeedColor, tertiaryKey: tertiarySeedColor, tones: FlexTones.vivid(Brightness.light), @@ -221,13 +216,12 @@ light mode, consider still using the light mode colors as the seed source, and o appropriate dark mode colors to `primary`, `secondary` and `tertiary` as needed. If there is a spec that calls for completely different main colors in dark mode, with different -hues, then seeding from them and also setting `primary`, `secondary` and `tertiary` to these +hues, then seeding from them in dark mode and also setting `primary`, `secondary` and `tertiary` to these color values is appropriate. ## ColorScheme Generation Strategy -In the above example, we used a predefined tone mapping and chroma setup `ColorScheme` generation strategy -called `FlexTones.vivid`. There are currently **eleven** predefined configurations available: +In the above example, we used a predefined tone mapping and chroma setup `ColorScheme` generation strategy called `FlexTones.vivid`. There are currently **eleven** predefined `FlexTones` configurations available: * `FlexTones.material`, default and same as Flutter SDK M3 setup in Flutter 3.22.0 and later. * `FlexTones.material3Legacy`, same as Flutter SDK M3 default setup in Flutter before 3.22.0. @@ -283,7 +277,7 @@ const FlexTones myDarkTones = FlexTones.dark( By using `paletteType` with value `FlexPaletteType.extended`, you can create seed generated `ColorScheme`s that use and access new color tones that exists in the late 2022 revised `ColorScheme` for surface colors and even more colors for **fixed** and **fixedDim** main colors that arrived in the Material-3 design during later half of 2023. -The `ColorScheme` colors that use these new tones are now finally also available in Flutter 3.22 or later. For more information and the latest updates, see [Material-3 color-roles](https://m3.material.io/styles/color/the-color-system/color-roles) specification. +The `ColorScheme` colors that use these new tones are now also available in Flutter 3.22 or later. For more information and the latest updates, see [Material-3 color-roles](https://m3.material.io/styles/color/the-color-system/color-roles) specification. The updated Material-3 color system adds tones `[4, 6, 12, 17, 22, 24]`, they are used for new dark mode surfaces in revised Material-3 dark surface colors. Likewise, the added tones `[87, 92, 94, 96, 98]` are for light mode surfaces in the updated Material-3 color system. By default `paletteType` of `FlexTones.extended` is now used to enable support for the tones in the updated specification and also adding three more custom tones `[2, 5, 97]`. The `paletteType` with value `FlexPaletteType.extended` is now default, it produces 27 tones `[0, 2, 4, 5, 6, 10, 12, 17, 20, 22, 24, 30, 40, 50, 60, 70, 80, 87, 90, 92, 94, 95, 96, 97, 98, 99, 100]`. @@ -303,7 +297,7 @@ There are two high contrast `FlexTones` configuration pre-made for this. They ar `FlexTones.highContrast`, a colorful high-contrast version, and `FlexTones.ultraContrast`, a less colorful version, a more dark on light in light theme mode, and light on dark in dark mode. -The `FlexTones.canyPop` also creates **very** high contrast theme, with colors that are vibrant and pop, it can also be used for high contrast themes. Depending on seed color input values and its chroma value, `FlexTones.chroma` can also be very vibrant and high contrast. It can also be monochromatic, i.e. gray-scale if input seed colors have chroma value zero. +The `FlexTones.canyPop` also creates high contrast theme, with colors that are vibrant and pop, it can also be used for high contrast themes. Depending on seed color input values and its chroma value, `FlexTones.chroma` can also be very vibrant and high contrast. It can also be monochromatic, i.e. gray-scale if input seed colors have chroma value zero. ```dart // Make a high contrast light ColorScheme from the seeds. @@ -348,7 +342,7 @@ For other platforms, you need to use a user setting and toggle themes based on i ### Contrast Level -When using a `variant` that is based on the equivalent Flutter SDK `DynamicScheme`, indicated by that it has its `FlexSchemeVariant` property `isFlutterScheme` set to true, you can also provide a `contrastLevel` for the seed generation. +When using a `variant` that is based on the equivalent Flutter SDK `DynamicSchemeVariant`, indicated by that it has its `FlexSchemeVariant` property `isFlutterScheme` set to true, you can also provide a `contrastLevel` for the seed generation. The `contrastLevel` parameter is used to indicate the contrast level between color pairs, such as `primary` and `onPrimary`. The value 0.0 is the default normal contrast; -1.0 is the lowest; 1.0 is the highest. From **Material Design guideline**, the medium and high contrast, correspond to 0.5 and 1.0 respectively. The `contrastLevel` is used to adjust the contrast between the main color and its on color pair. The `contraslLevel` must be from -1.0 to 1.0. @@ -369,13 +363,13 @@ The `contrastLevel` parameter is used to indicate the contrast level between col ); ``` -The contrast level provides a quick way to vary the contrast, but it can only be used with the `DynamicSchemeVariant` based variants, not with the `FlexTones` based variants. With `FlexTones` based variants, you can use multiple seed colors, but with the `DynamicSchemeVariant` only a single primary seed color. However, with `tones` based `FlexTones` you can create custom tones with even more flexibility in seed generation to make schemes with higher or less contrast. Two pre-configured high contrast tones exist earlier via `FlexTones.highContrast` and `FlexTones.ultraContrast`. You can use them as they are, or as examples of how to make your custom versions. +The contrast level provides a quick way to vary the contrast, but it can only be used with the `DynamicSchemeVariant` based variants, not with the `FlexTones` based variants. With `FlexTones` based variants, you can use multiple seed colors, but with the `DynamicSchemeVariant` only a single primary seed color. However, with `tones` based `FlexTones` you can create custom tones with even more flexibility in seed generation to make schemes with higher or less contrast. Two pre-configured high contrast tones exist for this purpose `FlexTones.highContrast` and `FlexTones.ultraContrast`. You can use them as they are, or as examples of how to make your custom versions. ### Black and White Contrast Another way to modify `FlexTones` configurations for contrast and accessibility, is by forcing all **main** contrasting on colors and all surfaces on colors to only use black and white contrasting colors. -If we remove the Material-3 guide used color system's colored contrasting colors, we can improve +If we do not use the Material-3 guide's colored contrasting colors, we can improve color accessibility and contrast on any `FlexTones` configuration. > [!NOTE] @@ -411,7 +405,7 @@ The surface colors made black by `surfacesUseBW()` are: * `background` (deprecated in Flutter 3.22) * `surface` -Here is a usage example, using both these modifiers. You cna use them individually +Here is a usage example, using both these modifiers. You can use them individually too, and you don't have to use them in both light and dark mode. ```dart @@ -452,7 +446,7 @@ In dark mode `surfacesUseBW()` can be used create seeded color schemes with true background and surface colors, but you may prefer to keep the primary seed color based slightly primary color tinted backgrounds in dark mode. -This modifier will make the `surface` color and still also the deprecated `background` color plain white in light mode and true black in dark mode. +This modifier will make the `surface` color and still also the deprecated `background` color plain white in light mode and full black in dark mode. ```dart // Make a Material 3 seeded light ColorScheme, but with always @@ -472,7 +466,7 @@ final ColorScheme schemeLightOnBW = SeedColorScheme.fromSeeds( #### FlexTones Modifier `monochromeSurfaces()` -A new `FlexTones` modifier in FSS version 2.1.0 is `monochromeSurfaces()`. It can be applied to any predefined or custom `FlexTones` to make all the surface colors monochrome and use pure greyscale for the neutral and neutral variant tonal palettes. Surface colors will then have no color tint from their own key color or from the primary seed key color. For those tired of tinted surface colors in Material-3, this is a useful helper. +A new `FlexTones` modifier in FSS version 3.0.0 is `monochromeSurfaces()`. It can be applied to any predefined or custom `FlexTones` to make all the surface colors monochrome and use pure greyscale for the neutral and neutral variant tonal palettes. Surface colors will then have no color tint from their own key color or from the primary seed key color. For those tired of tinted surface colors in Material-3, this is a useful helper. ```dart // Make a vivid Material 3 seeded light ColorScheme, where all surface colors @@ -486,24 +480,85 @@ final ColorScheme schemeLight = SeedColorScheme.fromSeeds( ); ``` +## Expressive On Container Colors + +By setting `useExpressiveOnContainerColors` to true in `SeedColorScheme.fromSeeds` you can opt in on using the new Material expressive on-colors specification for none surface on-container colors in light theme mode. + +Opting in changes the light mode color tone for the colors `onPrimaryContainer`, `onSecondaryContainer`, `onTertiaryContainer` and `onErrorContainer` from 10 to 30, making them more color expressive, but they then also have less contrast. The accepted min contrast curve is now `ContrastCurve(3, 4.5, 7, 11)` instead of `ContrastCurve(4.5, 7, 11, 21)` for the on-container colors. Meaning normal contrast of 4.5 is now accepted when it was 7 before. + +This option is only available when using MCU based `DynamicScheme` variants and not when using `FlexTones` based `tones` and `variants` based on `FlexTones`. It thus only applies to variants that are based on MCU's `DynamicScheme` and have their `isFlutterScheme` set to true. + +Prior to MCU version 0.12.0 the `MaterialDynamicColors` used an older M3 spec. Flutter stable 3.22.x and Flutter master 3.23.x still use MCU versions lower than 0.12.0 and default to the older color tones 10 in light mode. This will be changed in Flutter SDK when Flutter is updated to use MCU 0.12.0 or later. With FSS 3.0.0 you can opt in on using the new spec already now. FSS still defaults to the older spec with more contrast. When Flutter stable changes to use the new spec, FSS will also change to use it as default. While Flutter and MCU will then no longer offer the older higher contrast version, FSS will continue to do so. + +The optional usage of the expressive colors for on-container colors is a customization of MCU features in the forked version. We see value in being able to offer both the higher contrast older, still current, version and the new more color expressive one. + +```dart + // Make a light ColorScheme from a seeds using variant style vibrant. + final ColorScheme schemeLight = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + variant: FlexSchemeVariant.vibrant, + // Use the coming new standard. It does nothing in dark mode, + // so no point in using it there and it defaults to false. + useExpressiveOnContainerColors: true, + ); + // Make a dark ColorScheme from a seeds using variant style vibrant. + final ColorScheme schemeDark = SeedColorScheme.fromSeeds( + brightness: Brightness.dark, + primaryKey: primarySeedColor, + variant: FlexSchemeVariant.vibrant, + ); +``` + +The variants `fidelity` and `content` have their own algorithm for the on colors that `useExpressiveOnContainerColors` impacts that are already expressive. Thus the flag does nothing to their on colors. Likewise `monochrome` is excluded from the expressive on colors feature, as it is intended to be monochrome. However, even for these variants the `useExpressiveOnContainerColors` will change the `onErrorContainer` color, to be more expressive in light mode. + + +### FlexTones Modifier `expressiveOnContainer()` + +The `tones` configuration class `FlexTones` got the modifier `expressiveOnContainer()`. It can be applied to any predefined or custom `FlexTones` to make a returned `FlexTones` instance where the tones for light mode on container tones are set to 30 for more color expressive container text and icons on none surface containers. + +This modifier only impacts none surface on-container tones that are dark and thus only has any impact on the light theme mode on-container colors. The impacted on container colors are `onPrimaryContainerTone`,`onSecondaryContainerTone`, `onTertiaryContainerTone` and `onErrorContainerTone`. + +This feature brings optional light mode expressive on-container colors to any predefined or custom `FlexTones` configuration. The expressive on-color in light mode containers are a new coming change to Material Design 3 ColorScheme. This modifier is equivalent to setting the `SeedColorScheme.fromSeeds` and its `useExpressiveOnContainerColors` property to true, when using MCU dynamic scheme variant based seeded color schemes. + + + +```dart +// Make a Material 3 seeded light ColorScheme, but with always +// black and white contrasting onColors and ensure that background +// and surface colors are always white. +final ColorScheme schemeLightOnBW = SeedColorScheme.fromSeeds( + brightness: Brightness.light, + primaryKey: primarySeedColor, + secondaryKey: secondarySeedColor, + tertiaryKey: tertiarySeedColor, + tones: FlexTones.material(Brightness.light) + .onMainsUseBW() + .expressiveOnContainer(), +); +``` + +Note that when it comes to `FlexTones` modifiers, if they impact the same colors, their order matters. The last one applied will be the one that is used for conflicting value changes. + + ## [Example Application](https://rydmike.com/flexseedscheme/demo-v2) The included example application uses above color seeding and custom tone mapping. You can also choose any of the built-in pre-configured tone mappings as used seeding strategy. When you select seeding strategy, basic info about is displayed. -You can try a web version of this example for version 2 of FSS [**here in V2 demo**](https://rydmike.com/flexseedscheme/demo-v2). The older demo for version 1 of FSS is still available [**here in V1 demo**](https://rydmike.com/flexseedscheme/demo-v1). +You can try a web version of this example for version 3 of FSS [**here in V3 demo**](https://rydmike.com/flexseedscheme/demo-v3). The older demos for version 1 and 2 of FSS are still available [**here in V1 demo**](https://rydmike.com/flexseedscheme/demo-v1) and [**here in V2 demo**](https://rydmike.com/flexseedscheme/demo-v2) You can choose to use secondary and primary seed colors as additional keys to generate the color schemes. You can also toggle keeping contrasting onColors black & white, or force background and surface colors to be white in light mode and true black in dark mode. You can change the seed colors with a color picker by tapping on the seed colors. You can also modify the default error seed color. -With the app we can compare results from `FlexSeedScheme.fromSeeds`, to using the single seed color +With the app we can compare results from `SeedColorScheme.fromSeeds`, to using the single seed color based `ColorScheme.fromSeed` seed generated default Material-3 `ColorScheme` available in Flutter. -Both use the same key color as their primary seed color, but `ColorScheme.fromSeed` can only use it as its single seed color, we cannot use hues from our secondary and tertiary key colors for the seed produced tonal palettes, nor change how and its tones are mapped to the generated `ColorScheme`. +Both can use use a single key color as their primary seed color, but `ColorScheme.fromSeed` can only use it as its single seed color, we cannot use hues from our secondary and tertiary key colors for the seed produced tonal palettes, nor change how and its tones are mapped to the generated `ColorScheme`. -With `ColorScheme.fromSeed` we can also not customize the colorfulness (chromacity) of its seed generated secondary and tertiary colors. Like we can with `FlexSeedScheme.fromSeeds`, demonstrated here by choosing different `FlexTones` configurations. The tonal palette tones to `ColorScheme` color mappings can also not be modified, like we can do with `FlexSeedScheme.fromSeeds` and its different mappings in each `FlexTones` seeding strategy. +With `ColorScheme.fromSeed` we can also not customize the colorfulness (chromacity) of its seed generated secondary and tertiary colors, other than using the predefined `DynamicSchemeVariant`s. With `SeedColorScheme.fromSeeds` we can use both `DynamicSchemeVariant` and `FlexTones` configurations. The tonal palette tones to `ColorScheme` color mappings can be modified with `SeedColorScheme.fromSeeds` and its different mappings in each `FlexTones` seeding strategy. You can create custom `FlexTones` configurations to create your own tonal palettes and mapping of its tones to `ColorScheme` colors. -The seed generated tonal palettes are also displayed in the example application. You can toggle it to show the default `FlexPaletteType.common` or the `FlexPaletteType.extended` version. Only `FlexTones.candyPop` and `chroma`, as well as the custom mappings used in the example custom FlexTones` in the example app, use the extended tones. More schemes might use the extended tones later when Flutter SDK also starts using them. +The seed generated tonal palettes are also displayed in the example application. ## Scheme Generation Strategies @@ -511,7 +566,7 @@ Below some example color schemes made with FlexSeedScheme using `SeedColorScheme ### Default Strategy Material-3 Theme -This example shows how to recreate the default Material-3 theme using a single primary seed color and the default `tones` strategy `FlexTones.material`. This is no different from using Flutter's default `ColorScheme.fromSeed`, it is shown here for comparison. In the package, there are also tests to ensure that this strategy produces the same result. +This example shows how to recreate the default Material-3 theme using a single primary seed color and the default `tones` strategy `FlexTones.material`. This is no different from using Flutter's default `ColorScheme.fromSeed` with its default `dynamicSchemeVariant = DynamicSchemeVariant.tonalSpot`. It is shown here for comparison. In the package, there are also tests to ensure that this strategy produces the same result. | Light from single seed - Material 3 tones | Dark from single seed - Material 3 tones | |---------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------| diff --git a/doc_assets/colorscheme_dark_v2.png b/doc_assets/colorscheme_dark_v2.png new file mode 100644 index 0000000000000000000000000000000000000000..e9c701f5a6bc7cd63c1bf28b9567da871057ebf6 GIT binary patch literal 150745 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

8yqXN|HOBUU5c8$FCZ3xt*B3A-OnX#_I<4{v_&z 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