From 78c8411c0742584975f79d1d02bc7a1ebb7704bd Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 15 Jul 2026 15:54:56 +0200 Subject: [PATCH 1/8] add easy way to theme colors --- .../theme/semantics/stream_color_scheme.dart | 25 ++++++++++++++++++ .../lib/src/theme/stream_theme.dart | 26 +++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart b/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart index b7f0e192..137e95d1 100644 --- a/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart +++ b/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart @@ -484,6 +484,31 @@ class StreamColorScheme with _$StreamColorScheme { ); } + factory StreamColorScheme.fromColorScheme( + ColorScheme colorScheme, { + Brightness brightness = Brightness.light, + }) { + final brand = StreamColorSwatch.fromColor(colorScheme.primary, brightness: brightness); + final chrome = StreamColorSwatch.fromColor(colorScheme.surface, brightness: brightness); + + final base = brightness == Brightness.light + ? StreamColorScheme.light(brand: brand, chrome: chrome) + : StreamColorScheme.dark(brand: brand, chrome: chrome); + + return base.copyWith( + // Accent + accentError: colorScheme.error, + accentNeutral: colorScheme.secondary, + // // Text + textPrimary: colorScheme.onSurface, + textSecondary: colorScheme.onSurfaceVariant, + textTertiary: colorScheme.outline, + textLink: colorScheme.primary, + textOnAccent: colorScheme.onPrimary, + textOnInverse: colorScheme.onInverseSurface, + ); + } + const StreamColorScheme.raw({ required this.brand, required this.chrome, diff --git a/packages/stream_core_flutter/lib/src/theme/stream_theme.dart b/packages/stream_core_flutter/lib/src/theme/stream_theme.dart index 05960c99..0177dfb1 100644 --- a/packages/stream_core_flutter/lib/src/theme/stream_theme.dart +++ b/packages/stream_core_flutter/lib/src/theme/stream_theme.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:stream_core/stream_core.dart'; import 'package:theme_extensions_builder_annotation/theme_extensions_builder_annotation.dart'; import 'components/stream_app_bar_theme.dart'; @@ -40,6 +41,7 @@ import 'components/stream_snackbar_theme.dart'; import 'components/stream_stepper_theme.dart'; import 'components/stream_switch_theme.dart'; import 'components/stream_text_input_theme.dart'; +import 'primitives/stream_colors.dart'; import 'primitives/stream_icons.dart'; import 'primitives/stream_radius.dart'; import 'primitives/stream_spacing.dart'; @@ -255,13 +257,33 @@ class StreamTheme extends ThemeExtension with _$StreamTheme { /// /// This is a convenience factory that calls [StreamTheme] with /// [Brightness.dark]. - factory StreamTheme.dark() => StreamTheme(brightness: .dark); + factory StreamTheme.dark({Color? brandColor, Color? chromeColor}) => StreamTheme( + brightness: .dark, + colorScheme: StreamColorScheme.dark( + brand: brandColor?.let( + (brandColor) => StreamColorSwatch.fromColor(brandColor, brightness: .dark), + ), + chrome: chromeColor?.let( + (chromeColor) => StreamColorSwatch.fromColor(chromeColor, brightness: .dark), + ), + ), + ); /// Creates a light theme configuration. /// /// This is a convenience factory that calls [StreamTheme] with /// [Brightness.light]. - factory StreamTheme.light() => StreamTheme(brightness: .light); + factory StreamTheme.light({Color? brandColor, Color? chromeColor}) => StreamTheme( + brightness: .light, + colorScheme: StreamColorScheme.light( + brand: brandColor?.let( + (color) => StreamColorSwatch.fromColor(brandColor), + ), + chrome: chromeColor?.let( + (color) => StreamColorSwatch.fromColor(chromeColor), + ), + ), + ); const StreamTheme.raw({ required this.brightness, From f0edd41ca40289ea26e726ef12cfbddec55c5f16 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Thu, 16 Jul 2026 17:11:05 +0200 Subject: [PATCH 2/8] Automatic chrome color generation --- .../lib/config/theme_configuration.dart | 262 ++++++++++++------ .../theme_studio/color_picker_tile.dart | 49 +++- .../theme_customization_panel.dart | 128 ++++++++- .../internal/stream_color_swatch_helper.dart | 12 +- .../src/theme/primitives/stream_colors.dart | 8 +- .../theme/semantics/stream_color_scheme.dart | 25 -- .../lib/src/theme/stream_theme.dart | 66 +++-- 7 files changed, 406 insertions(+), 144 deletions(-) diff --git a/apps/design_system_gallery/lib/config/theme_configuration.dart b/apps/design_system_gallery/lib/config/theme_configuration.dart index e70a7833..c4ea9965 100644 --- a/apps/design_system_gallery/lib/config/theme_configuration.dart +++ b/apps/design_system_gallery/lib/config/theme_configuration.dart @@ -300,6 +300,149 @@ class ThemeConfiguration extends ChangeNotifier { } } + // ========================================================================= + // Is Customized - whether a color has been overridden (vs. using the + // SDK default derived by [StreamColorScheme]). + // ========================================================================= + + // Brand & Chrome + bool get brandIsCustom => _brandPrimaryColor != null; + bool get chromeIsCustom => _chromePrimaryColor != null; + + // Accent + bool get accentPrimaryIsCustom => _accentPrimary != null; + bool get accentSuccessIsCustom => _accentSuccess != null; + bool get accentWarningIsCustom => _accentWarning != null; + bool get accentErrorIsCustom => _accentError != null; + bool get accentNeutralIsCustom => _accentNeutral != null; + + // Text + bool get textPrimaryIsCustom => _textPrimary != null; + bool get textSecondaryIsCustom => _textSecondary != null; + bool get textTertiaryIsCustom => _textTertiary != null; + bool get textDisabledIsCustom => _textDisabled != null; + bool get textLinkIsCustom => _textLink != null; + bool get textOnAccentIsCustom => _textOnAccent != null; + + // Background + bool get backgroundAppIsCustom => _backgroundApp != null; + bool get backgroundSurfaceIsCustom => _backgroundSurface != null; + bool get backgroundSurfaceSubtleIsCustom => _backgroundSurfaceSubtle != null; + bool get backgroundSurfaceStrongIsCustom => _backgroundSurfaceStrong != null; + bool get backgroundSurfaceCardIsCustom => _backgroundSurfaceCard != null; + bool get backgroundOnAccentIsCustom => _backgroundOnAccent != null; + bool get backgroundHighlightIsCustom => _backgroundHighlight != null; + bool get backgroundScrimIsCustom => _backgroundScrim != null; + bool get backgroundOverlayLightIsCustom => _backgroundOverlayLight != null; + bool get backgroundOverlayDarkIsCustom => _backgroundOverlayDark != null; + bool get backgroundDisabledIsCustom => _backgroundDisabled != null; + bool get backgroundHoverIsCustom => _backgroundHover != null; + bool get backgroundPressedIsCustom => _backgroundPressed != null; + bool get backgroundSelectedIsCustom => _backgroundSelected != null; + bool get backgroundInverseIsCustom => _backgroundInverse != null; + bool get backgroundElevation0IsCustom => _backgroundElevation0 != null; + bool get backgroundElevation1IsCustom => _backgroundElevation1 != null; + bool get backgroundElevation2IsCustom => _backgroundElevation2 != null; + bool get backgroundElevation3IsCustom => _backgroundElevation3 != null; + + // Border Core + bool get borderDefaultIsCustom => _borderDefault != null; + bool get borderSubtleIsCustom => _borderSubtle != null; + bool get borderStrongIsCustom => _borderStrong != null; + bool get borderOnAccentIsCustom => _borderOnAccent != null; + bool get borderOnSurfaceIsCustom => _borderOnSurface != null; + bool get borderOpacitySubtleIsCustom => _borderOpacitySubtle != null; + bool get borderOpacityStrongIsCustom => _borderOpacityStrong != null; + + // Border Utility + bool get borderFocusIsCustom => _borderFocus != null; + bool get borderDisabledIsCustom => _borderDisabled != null; + bool get borderHoverIsCustom => _borderHover != null; + bool get borderPressedIsCustom => _borderPressed != null; + bool get borderActiveIsCustom => _borderActive != null; + bool get borderErrorIsCustom => _borderError != null; + bool get borderWarningIsCustom => _borderWarning != null; + bool get borderSuccessIsCustom => _borderSuccess != null; + bool get borderSelectedIsCustom => _borderSelected != null; + + // System + bool get systemTextIsCustom => _systemText != null; + bool get systemScrollbarIsCustom => _systemScrollbar != null; + + // Avatar Palette + bool get avatarPaletteIsCustom => _avatarPalette != null; + + // ========================================================================= + // Per-field Reset - reverts a single color back to the SDK default. + // ========================================================================= + + // Brand & Chrome + void resetBrand() => _update(() => _brandPrimaryColor = null); + void resetChrome() => _update(() => _chromePrimaryColor = null); + + // Accent + void resetAccentPrimary() => _update(() => _accentPrimary = null); + void resetAccentSuccess() => _update(() => _accentSuccess = null); + void resetAccentWarning() => _update(() => _accentWarning = null); + void resetAccentError() => _update(() => _accentError = null); + void resetAccentNeutral() => _update(() => _accentNeutral = null); + + // Text + void resetTextPrimary() => _update(() => _textPrimary = null); + void resetTextSecondary() => _update(() => _textSecondary = null); + void resetTextTertiary() => _update(() => _textTertiary = null); + void resetTextDisabled() => _update(() => _textDisabled = null); + void resetTextLink() => _update(() => _textLink = null); + void resetTextOnAccent() => _update(() => _textOnAccent = null); + + // Background + void resetBackgroundApp() => _update(() => _backgroundApp = null); + void resetBackgroundSurface() => _update(() => _backgroundSurface = null); + void resetBackgroundSurfaceSubtle() => _update(() => _backgroundSurfaceSubtle = null); + void resetBackgroundSurfaceStrong() => _update(() => _backgroundSurfaceStrong = null); + void resetBackgroundSurfaceCard() => _update(() => _backgroundSurfaceCard = null); + void resetBackgroundOnAccent() => _update(() => _backgroundOnAccent = null); + void resetBackgroundHighlight() => _update(() => _backgroundHighlight = null); + void resetBackgroundScrim() => _update(() => _backgroundScrim = null); + void resetBackgroundOverlayLight() => _update(() => _backgroundOverlayLight = null); + void resetBackgroundOverlayDark() => _update(() => _backgroundOverlayDark = null); + void resetBackgroundDisabled() => _update(() => _backgroundDisabled = null); + void resetBackgroundHover() => _update(() => _backgroundHover = null); + void resetBackgroundPressed() => _update(() => _backgroundPressed = null); + void resetBackgroundSelected() => _update(() => _backgroundSelected = null); + void resetBackgroundInverse() => _update(() => _backgroundInverse = null); + void resetBackgroundElevation0() => _update(() => _backgroundElevation0 = null); + void resetBackgroundElevation1() => _update(() => _backgroundElevation1 = null); + void resetBackgroundElevation2() => _update(() => _backgroundElevation2 = null); + void resetBackgroundElevation3() => _update(() => _backgroundElevation3 = null); + + // Border Core + void resetBorderDefault() => _update(() => _borderDefault = null); + void resetBorderSubtle() => _update(() => _borderSubtle = null); + void resetBorderStrong() => _update(() => _borderStrong = null); + void resetBorderOnAccent() => _update(() => _borderOnAccent = null); + void resetBorderOnSurface() => _update(() => _borderOnSurface = null); + void resetBorderOpacitySubtle() => _update(() => _borderOpacitySubtle = null); + void resetBorderOpacityStrong() => _update(() => _borderOpacityStrong = null); + + // Border Utility + void resetBorderFocus() => _update(() => _borderFocus = null); + void resetBorderDisabled() => _update(() => _borderDisabled = null); + void resetBorderHover() => _update(() => _borderHover = null); + void resetBorderPressed() => _update(() => _borderPressed = null); + void resetBorderActive() => _update(() => _borderActive = null); + void resetBorderError() => _update(() => _borderError = null); + void resetBorderWarning() => _update(() => _borderWarning = null); + void resetBorderSuccess() => _update(() => _borderSuccess = null); + void resetBorderSelected() => _update(() => _borderSelected = null); + + // System + void resetSystemText() => _update(() => _systemText = null); + void resetSystemScrollbar() => _update(() => _systemScrollbar = null); + + // Avatar Palette + void resetAvatarPalette() => _update(() => _avatarPalette = null); + void _update(VoidCallback setter) { setter(); _rebuildTheme(); @@ -307,7 +450,6 @@ class ThemeConfiguration extends ChangeNotifier { } void resetToDefaults() { - _brandPrimaryColor = null; // Accent _accentPrimary = null; _accentSuccess = null; @@ -374,120 +516,78 @@ class ThemeConfiguration extends ChangeNotifier { } void _rebuildTheme() { - final baseColorScheme = _brightness == Brightness.dark ? StreamColorScheme.dark() : StreamColorScheme.light(); - final isDark = _brightness == Brightness.dark; - - // Compute effective brand swatch (if brand primary is customized) + // Brand swatch, if the brand ("primary") color is customized. final effectiveBrand = _brandPrimaryColor != null ? StreamColorSwatch.fromColor(_brandPrimaryColor!, brightness: _brightness) : null; - // Compute effective chrome swatch (if chrome primary is customized) + // Chrome swatch. Mirrors StreamTheme.light()/.dark(): an explicit chrome color wins; + // otherwise, when a brand color is set but chrome isn't, derive chrome from brand at + // low saturation so chrome-dependent colors still pick up the brand's hue. final effectiveChrome = _chromePrimaryColor != null ? StreamColorSwatch.fromColor(_chromePrimaryColor!, brightness: _brightness) + : _brandPrimaryColor != null + ? StreamColorSwatch.fromColor(_brandPrimaryColor!, brightness: _brightness, saturation: 0.1) : null; - // Derived from brand: accentPrimary defaults to brand.shade500 (light) / shade400 (dark) - final effectiveAccentPrimary = _accentPrimary ?? _brandPrimaryColor; - - // Derived from brand: borderFocus defaults to brand.shade150 - final effectiveBorderFocus = _borderFocus ?? effectiveBrand?.shade150; - - // Derived from accentPrimary: textLink and borderActive - final effectiveTextLink = _textLink ?? effectiveAccentPrimary; - final effectiveBorderActive = _borderActive ?? effectiveAccentPrimary; - - // Derived from other accents: border utility colors - final effectiveBorderError = _borderError ?? _accentError; - final effectiveBorderWarning = _borderWarning ?? _accentWarning; - final effectiveBorderSuccess = _borderSuccess ?? _accentSuccess; - - // Derived from chrome: all chrome-dependent semantic colors. - // When chrome is customized via copyWith, the base scheme still holds values - // derived from the default chrome, so we must re-derive all of them explicitly. - // Extract subscript lookups to avoid Dart parsing ambiguity with ?[] inside ternaries. - final chromeShade0 = effectiveChrome?[0]; - final chromeShade1000 = effectiveChrome?[1000]; - - final effectiveAccentNeutral = _accentNeutral ?? effectiveChrome?.shade500; - final effectiveTextPrimary = _textPrimary ?? effectiveChrome?.shade900; - final effectiveTextSecondary = _textSecondary ?? effectiveChrome?.shade700; - final effectiveTextTertiary = _textTertiary ?? effectiveChrome?.shade500; - final effectiveTextDisabled = _textDisabled ?? effectiveChrome?.shade300; - final effectiveTextOnAccent = _textOnAccent ?? chromeShade0; - final effectiveBackgroundSurface = _backgroundSurface ?? effectiveChrome?.shade100; - final effectiveBackgroundSurfaceSubtle = _backgroundSurfaceSubtle ?? effectiveChrome?.shade50; - final effectiveBackgroundSurfaceStrong = _backgroundSurfaceStrong ?? effectiveChrome?.shade150; - final effectiveBackgroundSurfaceCard = - _backgroundSurfaceCard ?? (isDark ? effectiveChrome?.shade100 : effectiveChrome?.shade50); - final effectiveBackgroundOnAccent = _backgroundOnAccent ?? chromeShade0; - final effectiveBackgroundDisabled = _backgroundDisabled ?? effectiveChrome?.shade100; - final effectiveBackgroundInverse = _backgroundInverse ?? effectiveChrome?.shade900; - final effectiveBackgroundElevation0 = _backgroundElevation0 ?? (isDark ? chromeShade1000 : chromeShade0); - final effectiveBackgroundElevation1 = _backgroundElevation1 ?? (isDark ? effectiveChrome?.shade50 : chromeShade0); - final effectiveBackgroundElevation2 = _backgroundElevation2 ?? (isDark ? effectiveChrome?.shade100 : chromeShade0); - final effectiveBackgroundElevation3 = _backgroundElevation3 ?? (isDark ? effectiveChrome?.shade200 : chromeShade0); - // backgroundApp derives from backgroundElevation0 in both themes - final effectiveBackgroundApp = _backgroundApp ?? effectiveBackgroundElevation0; - final effectiveBorderOnAccent = _borderOnAccent ?? chromeShade0; - final effectiveBorderOnSurface = _borderOnSurface ?? effectiveChrome?.shade300; - final effectiveBorderDisabled = _borderDisabled ?? effectiveChrome?.shade100; - - final colorScheme = baseColorScheme.copyWith( + // Every other override is passed through as-is: StreamColorScheme.light()/.dark() + // already treat null as "use the SDK default" and derive dependent colors internally. + final buildScheme = _brightness == Brightness.dark ? StreamColorScheme.dark : StreamColorScheme.light; + final colorScheme = buildScheme( // Brand brand: effectiveBrand, // Chrome chrome: effectiveChrome, // Accent - accentPrimary: effectiveAccentPrimary, + accentPrimary: _accentPrimary, accentSuccess: _accentSuccess, accentWarning: _accentWarning, accentError: _accentError, - accentNeutral: effectiveAccentNeutral, + accentNeutral: _accentNeutral, // Text - textPrimary: effectiveTextPrimary, - textSecondary: effectiveTextSecondary, - textTertiary: effectiveTextTertiary, - textDisabled: effectiveTextDisabled, - textLink: effectiveTextLink, - textOnAccent: effectiveTextOnAccent, + textPrimary: _textPrimary, + textSecondary: _textSecondary, + textTertiary: _textTertiary, + textDisabled: _textDisabled, + textLink: _textLink, + textOnAccent: _textOnAccent, // Background - backgroundApp: effectiveBackgroundApp, - backgroundSurface: effectiveBackgroundSurface, - backgroundSurfaceSubtle: effectiveBackgroundSurfaceSubtle, - backgroundSurfaceStrong: effectiveBackgroundSurfaceStrong, - backgroundSurfaceCard: effectiveBackgroundSurfaceCard, - backgroundOnAccent: effectiveBackgroundOnAccent, + backgroundApp: _backgroundApp, + backgroundSurface: _backgroundSurface, + backgroundSurfaceSubtle: _backgroundSurfaceSubtle, + backgroundSurfaceStrong: _backgroundSurfaceStrong, + backgroundSurfaceCard: _backgroundSurfaceCard, + backgroundOnAccent: _backgroundOnAccent, backgroundHighlight: _backgroundHighlight, backgroundScrim: _backgroundScrim, backgroundOverlayLight: _backgroundOverlayLight, backgroundOverlayDark: _backgroundOverlayDark, - backgroundDisabled: effectiveBackgroundDisabled, + backgroundDisabled: _backgroundDisabled, backgroundHover: _backgroundHover, backgroundPressed: _backgroundPressed, backgroundSelected: _backgroundSelected, - backgroundInverse: effectiveBackgroundInverse, - backgroundElevation0: effectiveBackgroundElevation0, - backgroundElevation1: effectiveBackgroundElevation1, - backgroundElevation2: effectiveBackgroundElevation2, - backgroundElevation3: effectiveBackgroundElevation3, + backgroundInverse: _backgroundInverse, + backgroundElevation0: _backgroundElevation0, + backgroundElevation1: _backgroundElevation1, + backgroundElevation2: _backgroundElevation2, + backgroundElevation3: _backgroundElevation3, // Border Core borderDefault: _borderDefault, borderSubtle: _borderSubtle, borderStrong: _borderStrong, - borderOnAccent: effectiveBorderOnAccent, - borderOnSurface: effectiveBorderOnSurface, + borderOnAccent: _borderOnAccent, + borderOnSurface: _borderOnSurface, borderOpacitySubtle: _borderOpacitySubtle, borderOpacityStrong: _borderOpacityStrong, // Border Utility - borderFocus: effectiveBorderFocus, - borderDisabled: effectiveBorderDisabled, + borderFocus: _borderFocus, + borderDisabled: _borderDisabled, borderHover: _borderHover, borderPressed: _borderPressed, - borderActive: effectiveBorderActive, - borderError: effectiveBorderError, - borderWarning: effectiveBorderWarning, - borderSuccess: effectiveBorderSuccess, + borderActive: _borderActive, + borderError: _borderError, + borderWarning: _borderWarning, + borderSuccess: _borderSuccess, borderSelected: _borderSelected, // System systemText: _systemText, diff --git a/apps/design_system_gallery/lib/widgets/theme_studio/color_picker_tile.dart b/apps/design_system_gallery/lib/widgets/theme_studio/color_picker_tile.dart index c6a6aa9d..711952be 100644 --- a/apps/design_system_gallery/lib/widgets/theme_studio/color_picker_tile.dart +++ b/apps/design_system_gallery/lib/widgets/theme_studio/color_picker_tile.dart @@ -9,12 +9,25 @@ class ColorPickerTile extends StatelessWidget { required this.label, required this.color, required this.onColorChanged, + this.isDefault = false, + this.onReset, }); final String label; final Color color; final ValueChanged onColorChanged; + /// Whether [color] is still the SDK default (i.e. not overridden). + /// + /// When true, a "default" subtitle is shown below [label] and no reset + /// control is displayed. + final bool isDefault; + + /// Reverts this color back to its SDK default. + /// + /// Ignored (no reset control shown) when [isDefault] is true. + final VoidCallback? onReset; + @override Widget build(BuildContext context) { final colorScheme = context.streamColorScheme; @@ -59,12 +72,25 @@ class ColorPickerTile extends StatelessWidget { ), SizedBox(width: spacing.sm + spacing.xxs), Expanded( - child: Text( - label, - style: textTheme.metadataDefault.copyWith( - color: colorScheme.textPrimary, - fontFamily: 'monospace', - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + label, + style: textTheme.metadataDefault.copyWith( + color: colorScheme.textPrimary, + fontFamily: 'monospace', + ), + ), + if (isDefault) + Text( + 'default', + style: textTheme.metadataDefault.copyWith( + color: colorScheme.textTertiary, + ), + ), + ], ), ), Text( @@ -74,6 +100,17 @@ class ColorPickerTile extends StatelessWidget { fontFamily: 'monospace', ), ), + if (!isDefault && onReset != null) ...[ + SizedBox(width: spacing.xs + spacing.xxs), + Tooltip( + message: 'Reset to default', + child: InkWell( + onTap: onReset, + borderRadius: BorderRadius.all(radius.xs), + child: Icon(Icons.restart_alt, color: colorScheme.textSecondary, size: 14), + ), + ), + ], SizedBox(width: spacing.xs + spacing.xxs), Icon(Icons.edit, color: colorScheme.textSecondary, size: 12), ], diff --git a/apps/design_system_gallery/lib/widgets/theme_studio/theme_customization_panel.dart b/apps/design_system_gallery/lib/widgets/theme_studio/theme_customization_panel.dart index bb526982..3bcce9bb 100644 --- a/apps/design_system_gallery/lib/widgets/theme_studio/theme_customization_panel.dart +++ b/apps/design_system_gallery/lib/widgets/theme_studio/theme_customization_panel.dart @@ -219,7 +219,9 @@ class _ThemeCustomizationPanelState extends State { child: ColorPickerTile( label: 'brandPrimary', color: config.brandPrimaryColor, + isDefault: !config.brandIsCustom, onColorChanged: config.setBrandPrimaryColor, + onReset: config.resetBrand, ), ); } @@ -233,7 +235,9 @@ class _ThemeCustomizationPanelState extends State { child: ColorPickerTile( label: 'chromePrimary', color: config.chromePrimaryColor, + isDefault: !config.chromeIsCustom, onColorChanged: config.setChromePrimaryColor, + onReset: config.resetChrome, ), ); } @@ -249,27 +253,37 @@ class _ThemeCustomizationPanelState extends State { ColorPickerTile( label: 'accentPrimary', color: config.accentPrimary, + isDefault: !config.accentPrimaryIsCustom, onColorChanged: config.setAccentPrimary, + onReset: config.resetAccentPrimary, ), ColorPickerTile( label: 'accentSuccess', color: config.accentSuccess, + isDefault: !config.accentSuccessIsCustom, onColorChanged: config.setAccentSuccess, + onReset: config.resetAccentSuccess, ), ColorPickerTile( label: 'accentWarning', color: config.accentWarning, + isDefault: !config.accentWarningIsCustom, onColorChanged: config.setAccentWarning, + onReset: config.resetAccentWarning, ), ColorPickerTile( label: 'accentError', color: config.accentError, + isDefault: !config.accentErrorIsCustom, onColorChanged: config.setAccentError, + onReset: config.resetAccentError, ), ColorPickerTile( label: 'accentNeutral', color: config.accentNeutral, + isDefault: !config.accentNeutralIsCustom, onColorChanged: config.setAccentNeutral, + onReset: config.resetAccentNeutral, ), ], ), @@ -287,32 +301,44 @@ class _ThemeCustomizationPanelState extends State { ColorPickerTile( label: 'textPrimary', color: config.textPrimary, + isDefault: !config.textPrimaryIsCustom, onColorChanged: config.setTextPrimary, + onReset: config.resetTextPrimary, ), ColorPickerTile( label: 'textSecondary', color: config.textSecondary, + isDefault: !config.textSecondaryIsCustom, onColorChanged: config.setTextSecondary, + onReset: config.resetTextSecondary, ), ColorPickerTile( label: 'textTertiary', color: config.textTertiary, + isDefault: !config.textTertiaryIsCustom, onColorChanged: config.setTextTertiary, + onReset: config.resetTextTertiary, ), ColorPickerTile( label: 'textDisabled', color: config.textDisabled, + isDefault: !config.textDisabledIsCustom, onColorChanged: config.setTextDisabled, + onReset: config.resetTextDisabled, ), ColorPickerTile( label: 'textLink', color: config.textLink, + isDefault: !config.textLinkIsCustom, onColorChanged: config.setTextLink, + onReset: config.resetTextLink, ), ColorPickerTile( label: 'textOnAccent', color: config.textOnAccent, + isDefault: !config.textOnAccentIsCustom, onColorChanged: config.setTextOnAccent, + onReset: config.resetTextOnAccent, ), ], ), @@ -332,57 +358,79 @@ class _ThemeCustomizationPanelState extends State { ColorPickerTile( label: 'backgroundApp', color: config.backgroundApp, + isDefault: !config.backgroundAppIsCustom, onColorChanged: config.setBackgroundApp, + onReset: config.resetBackgroundApp, ), ColorPickerTile( label: 'backgroundInverse', color: config.backgroundInverse, + isDefault: !config.backgroundInverseIsCustom, onColorChanged: config.setBackgroundInverse, + onReset: config.resetBackgroundInverse, ), ColorPickerTile( label: 'backgroundOnAccent', color: config.backgroundOnAccent, + isDefault: !config.backgroundOnAccentIsCustom, onColorChanged: config.setBackgroundOnAccent, + onReset: config.resetBackgroundOnAccent, ), ColorPickerTile( label: 'backgroundHighlight', color: config.backgroundHighlight, + isDefault: !config.backgroundHighlightIsCustom, onColorChanged: config.setBackgroundHighlight, + onReset: config.resetBackgroundHighlight, ), ColorPickerTile( label: 'backgroundScrim', color: config.backgroundScrim, + isDefault: !config.backgroundScrimIsCustom, onColorChanged: config.setBackgroundScrim, + onReset: config.resetBackgroundScrim, ), ColorPickerTile( label: 'backgroundOverlayLight', color: config.backgroundOverlayLight, + isDefault: !config.backgroundOverlayLightIsCustom, onColorChanged: config.setBackgroundOverlayLight, + onReset: config.resetBackgroundOverlayLight, ), ColorPickerTile( label: 'backgroundOverlayDark', color: config.backgroundOverlayDark, + isDefault: !config.backgroundOverlayDarkIsCustom, onColorChanged: config.setBackgroundOverlayDark, + onReset: config.resetBackgroundOverlayDark, ), ColorPickerTile( label: 'backgroundDisabled', color: config.backgroundDisabled, + isDefault: !config.backgroundDisabledIsCustom, onColorChanged: config.setBackgroundDisabled, + onReset: config.resetBackgroundDisabled, ), ColorPickerTile( label: 'backgroundHover', color: config.backgroundHover, + isDefault: !config.backgroundHoverIsCustom, onColorChanged: config.setBackgroundHover, + onReset: config.resetBackgroundHover, ), ColorPickerTile( label: 'backgroundPressed', color: config.backgroundPressed, + isDefault: !config.backgroundPressedIsCustom, onColorChanged: config.setBackgroundPressed, + onReset: config.resetBackgroundPressed, ), ColorPickerTile( label: 'backgroundSelected', color: config.backgroundSelected, + isDefault: !config.backgroundSelectedIsCustom, onColorChanged: config.setBackgroundSelected, + onReset: config.resetBackgroundSelected, ), SizedBox(height: spacing.xs), Text( @@ -395,22 +443,30 @@ class _ThemeCustomizationPanelState extends State { ColorPickerTile( label: 'backgroundSurface', color: config.backgroundSurface, + isDefault: !config.backgroundSurfaceIsCustom, onColorChanged: config.setBackgroundSurface, + onReset: config.resetBackgroundSurface, ), ColorPickerTile( label: 'backgroundSurfaceSubtle', color: config.backgroundSurfaceSubtle, + isDefault: !config.backgroundSurfaceSubtleIsCustom, onColorChanged: config.setBackgroundSurfaceSubtle, + onReset: config.resetBackgroundSurfaceSubtle, ), ColorPickerTile( label: 'backgroundSurfaceStrong', color: config.backgroundSurfaceStrong, + isDefault: !config.backgroundSurfaceStrongIsCustom, onColorChanged: config.setBackgroundSurfaceStrong, + onReset: config.resetBackgroundSurfaceStrong, ), ColorPickerTile( label: 'backgroundSurfaceCard', color: config.backgroundSurfaceCard, + isDefault: !config.backgroundSurfaceCardIsCustom, onColorChanged: config.setBackgroundSurfaceCard, + onReset: config.resetBackgroundSurfaceCard, ), SizedBox(height: spacing.xs), Text( @@ -423,27 +479,30 @@ class _ThemeCustomizationPanelState extends State { ColorPickerTile( label: 'backgroundElevation0', color: config.backgroundElevation0, + isDefault: !config.backgroundElevation0IsCustom, onColorChanged: config.setBackgroundElevation0, + onReset: config.resetBackgroundElevation0, ), ColorPickerTile( label: 'backgroundElevation1', color: config.backgroundElevation1, + isDefault: !config.backgroundElevation1IsCustom, onColorChanged: config.setBackgroundElevation1, + onReset: config.resetBackgroundElevation1, ), ColorPickerTile( label: 'backgroundElevation2', color: config.backgroundElevation2, + isDefault: !config.backgroundElevation2IsCustom, onColorChanged: config.setBackgroundElevation2, + onReset: config.resetBackgroundElevation2, ), ColorPickerTile( label: 'backgroundElevation3', color: config.backgroundElevation3, + isDefault: !config.backgroundElevation3IsCustom, onColorChanged: config.setBackgroundElevation3, - ), - ColorPickerTile( - label: 'backgroundHighlight', - color: config.backgroundHighlight, - onColorChanged: config.setBackgroundHighlight, + onReset: config.resetBackgroundElevation3, ), ], ), @@ -461,37 +520,51 @@ class _ThemeCustomizationPanelState extends State { ColorPickerTile( label: 'borderDefault', color: config.borderDefault, + isDefault: !config.borderDefaultIsCustom, onColorChanged: config.setBorderDefault, + onReset: config.resetBorderDefault, ), ColorPickerTile( label: 'borderSubtle', color: config.borderSubtle, + isDefault: !config.borderSubtleIsCustom, onColorChanged: config.setBorderSubtle, + onReset: config.resetBorderSubtle, ), ColorPickerTile( label: 'borderStrong', color: config.borderStrong, + isDefault: !config.borderStrongIsCustom, onColorChanged: config.setBorderStrong, + onReset: config.resetBorderStrong, ), ColorPickerTile( label: 'borderOnAccent', color: config.borderOnAccent, + isDefault: !config.borderOnAccentIsCustom, onColorChanged: config.setBorderOnAccent, + onReset: config.resetBorderOnAccent, ), ColorPickerTile( label: 'borderOnSurface', color: config.borderOnSurface, + isDefault: !config.borderOnSurfaceIsCustom, onColorChanged: config.setBorderOnSurface, + onReset: config.resetBorderOnSurface, ), ColorPickerTile( label: 'borderOpacitySubtle', color: config.borderOpacitySubtle, + isDefault: !config.borderOpacitySubtleIsCustom, onColorChanged: config.setBorderOpacitySubtle, + onReset: config.resetBorderOpacitySubtle, ), ColorPickerTile( label: 'borderOpacityStrong', color: config.borderOpacityStrong, + isDefault: !config.borderOpacityStrongIsCustom, onColorChanged: config.setBorderOpacityStrong, + onReset: config.resetBorderOpacityStrong, ), ], ), @@ -509,47 +582,65 @@ class _ThemeCustomizationPanelState extends State { ColorPickerTile( label: 'borderFocus', color: config.borderFocus, + isDefault: !config.borderFocusIsCustom, onColorChanged: config.setBorderFocus, + onReset: config.resetBorderFocus, ), ColorPickerTile( label: 'borderActive', color: config.borderActive, + isDefault: !config.borderActiveIsCustom, onColorChanged: config.setBorderActive, + onReset: config.resetBorderActive, ), ColorPickerTile( label: 'borderHover', color: config.borderHover, + isDefault: !config.borderHoverIsCustom, onColorChanged: config.setBorderHover, + onReset: config.resetBorderHover, ), ColorPickerTile( label: 'borderPressed', color: config.borderPressed, + isDefault: !config.borderPressedIsCustom, onColorChanged: config.setBorderPressed, + onReset: config.resetBorderPressed, ), ColorPickerTile( label: 'borderDisabled', color: config.borderDisabled, + isDefault: !config.borderDisabledIsCustom, onColorChanged: config.setBorderDisabled, + onReset: config.resetBorderDisabled, ), ColorPickerTile( label: 'borderError', color: config.borderError, + isDefault: !config.borderErrorIsCustom, onColorChanged: config.setBorderError, + onReset: config.resetBorderError, ), ColorPickerTile( label: 'borderWarning', color: config.borderWarning, + isDefault: !config.borderWarningIsCustom, onColorChanged: config.setBorderWarning, + onReset: config.resetBorderWarning, ), ColorPickerTile( label: 'borderSuccess', color: config.borderSuccess, + isDefault: !config.borderSuccessIsCustom, onColorChanged: config.setBorderSuccess, + onReset: config.resetBorderSuccess, ), ColorPickerTile( label: 'borderSelected', color: config.borderSelected, + isDefault: !config.borderSelectedIsCustom, onColorChanged: config.setBorderSelected, + onReset: config.resetBorderSelected, ), ], ), @@ -567,12 +658,16 @@ class _ThemeCustomizationPanelState extends State { ColorPickerTile( label: 'systemText', color: config.systemText, + isDefault: !config.systemTextIsCustom, onColorChanged: config.setSystemText, + onReset: config.resetSystemText, ), ColorPickerTile( label: 'systemScrollbar', color: config.systemScrollbar, + isDefault: !config.systemScrollbarIsCustom, onColorChanged: config.setSystemScrollbar, + onReset: config.resetSystemScrollbar, ), ], ), @@ -582,7 +677,10 @@ class _ThemeCustomizationPanelState extends State { Widget _buildAvatarPaletteSection(BuildContext context) { final config = context.read(); final palette = config.avatarPalette; + final isDefault = !config.avatarPaletteIsCustom; final spacing = context.streamSpacing; + final colorScheme = context.streamColorScheme; + final textTheme = context.streamTextTheme; return SectionCard( title: 'Avatar Palette', @@ -623,6 +721,26 @@ class _ThemeCustomizationPanelState extends State { config.addAvatarPaletteEntry(_generateRandomAvatarPair(isDark: isDark)); }, ), + if (!isDefault) ...[ + SizedBox(height: spacing.sm), + InkWell( + onTap: config.resetAvatarPalette, + borderRadius: BorderRadius.all(context.streamRadius.sm), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.restart_alt, color: colorScheme.textTertiary, size: 14), + SizedBox(width: spacing.xs), + Text( + 'Reset to default palette', + style: textTheme.captionDefault.copyWith( + color: colorScheme.textTertiary, + ), + ), + ], + ), + ), + ], ], ), ); diff --git a/packages/stream_core_flutter/lib/src/theme/primitives/internal/stream_color_swatch_helper.dart b/packages/stream_core_flutter/lib/src/theme/primitives/internal/stream_color_swatch_helper.dart index 6b80fbaa..e3084f58 100644 --- a/packages/stream_core_flutter/lib/src/theme/primitives/internal/stream_color_swatch_helper.dart +++ b/packages/stream_core_flutter/lib/src/theme/primitives/internal/stream_color_swatch_helper.dart @@ -15,11 +15,21 @@ class StreamColorSwatchHelper { /// When [brightness] is [Brightness.dark], the lightness values are inverted /// so that lower shade numbers (e.g., 50) are darker and higher shade numbers /// (e.g., 900) are lighter. + /// + /// The [saturation] parameter controls the saturation of the base color. + /// The default value is null, which means the saturation is not changed. + /// + /// The return value is a map of color shades, indexed by the shade number. static Map generateShadeMap( Color baseColor, { Brightness brightness = Brightness.light, + double? saturation, }) { - final hslBase = HSLColor.fromColor(baseColor); + var hslBase = HSLColor.fromColor(baseColor); + if (saturation != null) { + hslBase = hslBase.withSaturation(saturation); + } + final centerLightness = hslBase.lightness; final shades = [50, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900]; diff --git a/packages/stream_core_flutter/lib/src/theme/primitives/stream_colors.dart b/packages/stream_core_flutter/lib/src/theme/primitives/stream_colors.dart index 6c51b226..e92dca73 100644 --- a/packages/stream_core_flutter/lib/src/theme/primitives/stream_colors.dart +++ b/packages/stream_core_flutter/lib/src/theme/primitives/stream_colors.dart @@ -269,10 +269,14 @@ abstract final class StreamColors { class StreamColorSwatch extends ColorSwatch { const StreamColorSwatch(super.primary, super._swatch); - factory StreamColorSwatch.fromColor(Color color, {Brightness brightness = Brightness.light}) { + factory StreamColorSwatch.fromColor( + Color color, { + Brightness brightness = Brightness.light, + double? saturation, + }) { return StreamColorSwatch( color.toARGB32(), - StreamColorSwatchHelper.generateShadeMap(color, brightness: brightness), + StreamColorSwatchHelper.generateShadeMap(color, brightness: brightness, saturation: saturation), ); } diff --git a/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart b/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart index 137e95d1..b7f0e192 100644 --- a/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart +++ b/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart @@ -484,31 +484,6 @@ class StreamColorScheme with _$StreamColorScheme { ); } - factory StreamColorScheme.fromColorScheme( - ColorScheme colorScheme, { - Brightness brightness = Brightness.light, - }) { - final brand = StreamColorSwatch.fromColor(colorScheme.primary, brightness: brightness); - final chrome = StreamColorSwatch.fromColor(colorScheme.surface, brightness: brightness); - - final base = brightness == Brightness.light - ? StreamColorScheme.light(brand: brand, chrome: chrome) - : StreamColorScheme.dark(brand: brand, chrome: chrome); - - return base.copyWith( - // Accent - accentError: colorScheme.error, - accentNeutral: colorScheme.secondary, - // // Text - textPrimary: colorScheme.onSurface, - textSecondary: colorScheme.onSurfaceVariant, - textTertiary: colorScheme.outline, - textLink: colorScheme.primary, - textOnAccent: colorScheme.onPrimary, - textOnInverse: colorScheme.onInverseSurface, - ); - } - const StreamColorScheme.raw({ required this.brand, required this.chrome, diff --git a/packages/stream_core_flutter/lib/src/theme/stream_theme.dart b/packages/stream_core_flutter/lib/src/theme/stream_theme.dart index 0177dfb1..462b278e 100644 --- a/packages/stream_core_flutter/lib/src/theme/stream_theme.dart +++ b/packages/stream_core_flutter/lib/src/theme/stream_theme.dart @@ -253,37 +253,55 @@ class StreamTheme extends ThemeExtension with _$StreamTheme { ); } - /// Creates a dark theme configuration. + /// Creates a light theme configuration. /// /// This is a convenience factory that calls [StreamTheme] with - /// [Brightness.dark]. - factory StreamTheme.dark({Color? brandColor, Color? chromeColor}) => StreamTheme( - brightness: .dark, - colorScheme: StreamColorScheme.dark( - brand: brandColor?.let( - (brandColor) => StreamColorSwatch.fromColor(brandColor, brightness: .dark), - ), - chrome: chromeColor?.let( - (chromeColor) => StreamColorSwatch.fromColor(chromeColor, brightness: .dark), + /// [Brightness.light]. + factory StreamTheme.light({Color? brandColor, Color? chromeColor}) { + final brand = brandColor?.let( + (brandColor) => StreamColorSwatch.fromColor(brandColor, brightness: .light), + ); + final chrome = + chromeColor?.let( + (chromeColor) => StreamColorSwatch.fromColor(chromeColor, brightness: .light), + ) ?? + brandColor?.let( + (brandColor) => StreamColorSwatch.fromColor(brandColor, brightness: .light, saturation: 0.1), + ); + + return StreamTheme( + brightness: .light, + colorScheme: StreamColorScheme.light( + brand: brand, + chrome: chrome, ), - ), - ); + ); + } - /// Creates a light theme configuration. + /// Creates a dark theme configuration. /// /// This is a convenience factory that calls [StreamTheme] with - /// [Brightness.light]. - factory StreamTheme.light({Color? brandColor, Color? chromeColor}) => StreamTheme( - brightness: .light, - colorScheme: StreamColorScheme.light( - brand: brandColor?.let( - (color) => StreamColorSwatch.fromColor(brandColor), - ), - chrome: chromeColor?.let( - (color) => StreamColorSwatch.fromColor(chromeColor), + /// [Brightness.dark]. + factory StreamTheme.dark({Color? brandColor, Color? chromeColor}) { + final brand = brandColor?.let( + (brandColor) => StreamColorSwatch.fromColor(brandColor, brightness: .dark), + ); + final chrome = + chromeColor?.let( + (chromeColor) => StreamColorSwatch.fromColor(chromeColor, brightness: .dark), + ) ?? + brandColor?.let( + (brandColor) => StreamColorSwatch.fromColor(brandColor, brightness: .dark, saturation: 0.1), + ); + + return StreamTheme( + brightness: .dark, + colorScheme: StreamColorScheme.dark( + brand: brand, + chrome: chrome, ), - ), - ); + ); + } const StreamTheme.raw({ required this.brightness, From 9be24e79615932e6027ea64452c58d3077ce4e74 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Fri, 17 Jul 2026 10:44:51 +0200 Subject: [PATCH 3/8] update changelog --- packages/stream_core_flutter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_core_flutter/CHANGELOG.md b/packages/stream_core_flutter/CHANGELOG.md index 80f0c538..04ae358d 100644 --- a/packages/stream_core_flutter/CHANGELOG.md +++ b/packages/stream_core_flutter/CHANGELOG.md @@ -10,6 +10,7 @@ - Added optional `semanticLabel` to `StreamBadgeNotification`, `StreamFileTypeIcon`, `StreamStepper`, `StreamMessageComposerAttachment`, and `StreamMessageComposerMediaAttachment`. - Added `excludeHeaderSemantics` to `StreamAppBar` and `StreamSheetHeader` for opting out of the default heading role and route naming on the title. - Added `onVisible` callback to `StreamSnackbar` — fires after the entrance animation completes (or synchronously when a screen reader is active). +- Added options for brand and chrome color customization to `StreamTheme.light` and `StreamTheme.dark`. ### 🐞 Fixed From aec4ef4cb54ebe96fece1894985dba445fa1b457 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Fri, 17 Jul 2026 11:02:50 +0200 Subject: [PATCH 4/8] Add tests for various seeds --- ...am_theme_color_generation_golden_test.dart | 90 ++++ .../stream_theme_color_generation_test.dart | 457 ++++++++++++++++++ 2 files changed, 547 insertions(+) create mode 100644 packages/stream_core_flutter/test/theme/stream_theme_color_generation_golden_test.dart create mode 100644 packages/stream_core_flutter/test/theme/stream_theme_color_generation_test.dart diff --git a/packages/stream_core_flutter/test/theme/stream_theme_color_generation_golden_test.dart b/packages/stream_core_flutter/test/theme/stream_theme_color_generation_golden_test.dart new file mode 100644 index 00000000..2a536612 --- /dev/null +++ b/packages/stream_core_flutter/test/theme/stream_theme_color_generation_golden_test.dart @@ -0,0 +1,90 @@ +import 'package:alchemist/alchemist.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_core_flutter/core.dart'; + +/// The shades every generated [StreamColorSwatch] exposes (see +/// `StreamColorSwatchHelper.generateShadeMap`). +const _shades = [0, 50, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000]; + +void main() { + group('StreamTheme color generation Golden Tests', () { + goldenTest( + 'renders the generated brand and chrome scales for custom seed colors', + fileName: 'stream_theme_color_generation', + builder: () => GoldenTestGroup( + scenarioConstraints: const BoxConstraints(maxWidth: 1200), + children: [ + GoldenTestScenario( + name: 'deep orange seed', + child: const _SeedColorPreview(seedColor: Color(0xFFFF5722)), + ), + GoldenTestScenario( + name: 'purple seed', + child: const _SeedColorPreview(seedColor: Color(0xFF9C27B0)), + ), + GoldenTestScenario( + name: 'yellow seed', + child: const _SeedColorPreview(seedColor: Color(0xFFFFEB3B)), + ), + GoldenTestScenario( + name: 'green seed', + child: const _SeedColorPreview(seedColor: Color(0xFF4CAF50)), + ), + GoldenTestScenario( + name: 'blue seed', + child: const _SeedColorPreview(seedColor: Color(0xFF2196F3)), + ), + GoldenTestScenario( + name: 'red seed', + child: const _SeedColorPreview(seedColor: Color(0xFFF44336)), + ), + ], + ), + ); + }); +} + +/// Renders the [StreamTheme.light] and [StreamTheme.dark] brand/chrome +/// scales generated from a single [seedColor] as plain color swatches, +/// pinning the HSL-based shade generation in `StreamColorSwatchHelper` +/// against regressions. See `stream_theme_color_generation_test.dart` for +/// assertions on the exact generated values. +class _SeedColorPreview extends StatelessWidget { + const _SeedColorPreview({required this.seedColor}); + + final Color seedColor; + + @override + Widget build(BuildContext context) { + final light = StreamTheme.light(brandColor: seedColor).colorScheme; + final dark = StreamTheme.dark(brandColor: seedColor).colorScheme; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _ColorScaleRow(swatch: light.brand), + _ColorScaleRow(swatch: light.chrome), + const SizedBox(height: 8), + _ColorScaleRow(swatch: dark.brand), + _ColorScaleRow(swatch: dark.chrome), + ], + ); + } +} + +class _ColorScaleRow extends StatelessWidget { + const _ColorScaleRow({required this.swatch}); + + final StreamColorSwatch swatch; + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + for (final shade in _shades) Container(width: 76, height: 56, color: swatch[shade]), + ], + ); + } +} diff --git a/packages/stream_core_flutter/test/theme/stream_theme_color_generation_test.dart b/packages/stream_core_flutter/test/theme/stream_theme_color_generation_test.dart new file mode 100644 index 00000000..d6f45098 --- /dev/null +++ b/packages/stream_core_flutter/test/theme/stream_theme_color_generation_test.dart @@ -0,0 +1,457 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_core_flutter/core.dart'; + +void main() { + group('StreamTheme.light color generation', () { + test('generates the exact brand scale for a deep orange seed', () { + final brand = StreamTheme.light(brandColor: const Color(0xFFFF5722)).colorScheme.brand; + + expect(brand[0], const Color(0xFFFFFFFF)); + expect(brand[50], const Color(0xFFFFECE5)); + expect(brand[100], const Color(0xFFFFDBD0)); + expect(brand[150], const Color(0xFFFFCBBA)); + expect(brand[200], const Color(0xFFFFBAA4)); + expect(brand[300], const Color(0xFFFF9979)); + expect(brand[400], const Color(0xFFFF784D)); + expect(brand[500], const Color(0xFFFF5722)); + expect(brand[600], const Color(0xFFE83800)); + expect(brand[700], const Color(0xFFAF2A00)); + expect(brand[800], const Color(0xFF761C00)); + expect(brand[900], const Color(0xFF3D0F00)); + expect(brand[1000], const Color(0xFF000000)); + }); + + test('generates the exact auto-derived chrome scale for a deep orange seed', () { + final chrome = StreamTheme.light(brandColor: const Color(0xFFFF5722)).colorScheme.chrome; + + expect(chrome[0], const Color(0xFFFFFFFF)); + expect(chrome[50], const Color(0xFFF4F2F1)); + expect(chrome[100], const Color(0xFFEAE6E5)); + expect(chrome[150], const Color(0xFFE0DBD9)); + expect(chrome[200], const Color(0xFFD6CFCD)); + expect(chrome[300], const Color(0xFFC3B8B5)); + expect(chrome[400], const Color(0xFFAFA29D)); + expect(chrome[500], const Color(0xFF9C8B85)); + expect(chrome[600], const Color(0xFF806E68)); + expect(chrome[700], const Color(0xFF60534F)); + expect(chrome[800], const Color(0xFF413835)); + expect(chrome[900], const Color(0xFF221D1C)); + expect(chrome[1000], const Color(0xFF000000)); + }); + + test('generates the exact brand scale for a purple seed', () { + final brand = StreamTheme.light(brandColor: const Color(0xFF9C27B0)).colorScheme.brand; + + expect(brand[0], const Color(0xFFFFFFFF)); + expect(brand[50], const Color(0xFFF8EAFA)); + expect(brand[100], const Color(0xFFF0D2F5)); + expect(brand[150], const Color(0xFFE8B9F0)); + expect(brand[200], const Color(0xFFDFA1EA)); + expect(brand[300], const Color(0xFFCF70DF)); + expect(brand[400], const Color(0xFFBE3FD4)); + expect(brand[500], const Color(0xFF9C27B0)); + expect(brand[600], const Color(0xFF802091)); + expect(brand[700], const Color(0xFF641971)); + expect(brand[800], const Color(0xFF481252)); + expect(brand[900], const Color(0xFF2C0B32)); + expect(brand[1000], const Color(0xFF000000)); + }); + + test('generates the exact auto-derived chrome scale for a purple seed', () { + final chrome = StreamTheme.light(brandColor: const Color(0xFF9C27B0)).colorScheme.chrome; + + expect(chrome[0], const Color(0xFFFFFFFF)); + expect(chrome[50], const Color(0xFFF3F1F4)); + expect(chrome[100], const Color(0xFFE5E1E6)); + expect(chrome[150], const Color(0xFFD7D0D9)); + expect(chrome[200], const Color(0xFFC9C0CB)); + expect(chrome[300], const Color(0xFFAE9FB0)); + expect(chrome[400], const Color(0xFF927E95)); + expect(chrome[500], const Color(0xFF736176)); + expect(chrome[600], const Color(0xFF5F4F61)); + expect(chrome[700], const Color(0xFF4A3E4C)); + expect(chrome[800], const Color(0xFF352D37)); + expect(chrome[900], const Color(0xFF211C22)); + expect(chrome[1000], const Color(0xFF000000)); + }); + + test('generates the exact brand scale for a yellow seed', () { + final brand = StreamTheme.light(brandColor: const Color(0xFFFFEB3B)).colorScheme.brand; + + expect(brand[0], const Color(0xFFFFFFFF)); + expect(brand[50], const Color(0xFFFFFCE5)); + expect(brand[100], const Color(0xFFFFFAD3)); + expect(brand[150], const Color(0xFFFFF9C0)); + expect(brand[200], const Color(0xFFFFF7AD)); + expect(brand[300], const Color(0xFFFFF387)); + expect(brand[400], const Color(0xFFFFEF61)); + expect(brand[500], const Color(0xFFFFEB3B)); + expect(brand[600], const Color(0xFFFBE100)); + expect(brand[700], const Color(0xFFBCA800)); + expect(brand[800], const Color(0xFF7C7000)); + expect(brand[900], const Color(0xFF3D3700)); + expect(brand[1000], const Color(0xFF000000)); + }); + + test('generates the exact auto-derived chrome scale for a yellow seed', () { + final chrome = StreamTheme.light(brandColor: const Color(0xFFFFEB3B)).colorScheme.chrome; + + expect(chrome[0], const Color(0xFFFFFFFF)); + expect(chrome[50], const Color(0xFFF4F3F1)); + expect(chrome[100], const Color(0xFFEBEBE7)); + expect(chrome[150], const Color(0xFFE2E2DC)); + expect(chrome[200], const Color(0xFFDAD9D2)); + expect(chrome[300], const Color(0xFFC9C8BD)); + expect(chrome[400], const Color(0xFFB8B6A8)); + expect(chrome[500], const Color(0xFFA7A593)); + expect(chrome[600], const Color(0xFF8A8771)); + expect(chrome[700], const Color(0xFF676554)); + expect(chrome[800], const Color(0xFF444338)); + expect(chrome[900], const Color(0xFF22211C)); + expect(chrome[1000], const Color(0xFF000000)); + }); + + test('generates the exact brand scale for a green seed', () { + final brand = StreamTheme.light(brandColor: const Color(0xFF4CAF50)).colorScheme.brand; + + expect(brand[0], const Color(0xFFFFFFFF)); + expect(brand[50], const Color(0xFFEDF7EE)); + expect(brand[100], const Color(0xFFDBEFDC)); + expect(brand[150], const Color(0xFFC9E8CA)); + expect(brand[200], const Color(0xFFB7E0B9)); + expect(brand[300], const Color(0xFF93D095)); + expect(brand[400], const Color(0xFF6FC072)); + expect(brand[500], const Color(0xFF4CAF50)); + expect(brand[600], const Color(0xFF3E8E41)); + expect(brand[700], const Color(0xFF2F6D32)); + expect(brand[800], const Color(0xFF214C23)); + expect(brand[900], const Color(0xFF132B14)); + expect(brand[1000], const Color(0xFF000000)); + }); + + test('generates the exact auto-derived chrome scale for a green seed', () { + final chrome = StreamTheme.light(brandColor: const Color(0xFF4CAF50)).colorScheme.chrome; + + expect(chrome[0], const Color(0xFFFFFFFF)); + expect(chrome[50], const Color(0xFFF1F4F1)); + expect(chrome[100], const Color(0xFFE3E8E3)); + expect(chrome[150], const Color(0xFFD4DCD5)); + expect(chrome[200], const Color(0xFFC6D1C7)); + expect(chrome[300], const Color(0xFFAAB9AA)); + expect(chrome[400], const Color(0xFF8DA28E)); + expect(chrome[500], const Color(0xFF718A72)); + expect(chrome[600], const Color(0xFF5C705C)); + expect(chrome[700], const Color(0xFF465647)); + expect(chrome[800], const Color(0xFF313C31)); + expect(chrome[900], const Color(0xFF1C221C)); + expect(chrome[1000], const Color(0xFF000000)); + }); + + test('generates the exact brand scale for a blue seed', () { + final brand = StreamTheme.light(brandColor: const Color(0xFF2196F3)).colorScheme.brand; + + expect(brand[0], const Color(0xFFFFFFFF)); + expect(brand[50], const Color(0xFFE7F4FE)); + expect(brand[100], const Color(0xFFD1E9FD)); + expect(brand[150], const Color(0xFFBBDFFB)); + expect(brand[200], const Color(0xFFA5D4FA)); + expect(brand[300], const Color(0xFF79C0F8)); + expect(brand[400], const Color(0xFF4DABF5)); + expect(brand[500], const Color(0xFF2196F3)); + expect(brand[600], const Color(0xFF0B7BD3)); + expect(brand[700], const Color(0xFF095DA0)); + expect(brand[800], const Color(0xFF063F6D)); + expect(brand[900], const Color(0xFF03223A)); + expect(brand[1000], const Color(0xFF000000)); + }); + + test('generates the exact auto-derived chrome scale for a blue seed', () { + final chrome = StreamTheme.light(brandColor: const Color(0xFF2196F3)).colorScheme.chrome; + + expect(chrome[0], const Color(0xFFFFFFFF)); + expect(chrome[50], const Color(0xFFF1F2F4)); + expect(chrome[100], const Color(0xFFE4E7E9)); + expect(chrome[150], const Color(0xFFD7DBDF)); + expect(chrome[200], const Color(0xFFCBD0D4)); + expect(chrome[300], const Color(0xFFB1B9BF)); + expect(chrome[400], const Color(0xFF98A2AB)); + expect(chrome[500], const Color(0xFF7E8B96)); + expect(chrome[600], const Color(0xFF64707A)); + expect(chrome[700], const Color(0xFF4C555D)); + expect(chrome[800], const Color(0xFF343A3F)); + expect(chrome[900], const Color(0xFF1C1F22)); + expect(chrome[1000], const Color(0xFF000000)); + }); + + test('generates the exact brand scale for a red seed', () { + final brand = StreamTheme.light(brandColor: const Color(0xFFF44336)).colorScheme.brand; + + expect(brand[0], const Color(0xFFFFFFFF)); + expect(brand[50], const Color(0xFFFEE8E7)); + expect(brand[100], const Color(0xFFFDD6D3)); + expect(brand[150], const Color(0xFFFCC4C0)); + expect(brand[200], const Color(0xFFFAB1AC)); + expect(brand[300], const Color(0xFFF88D85)); + expect(brand[400], const Color(0xFFF6685D)); + expect(brand[500], const Color(0xFFF44336)); + expect(brand[600], const Color(0xFFE21B0C)); + expect(brand[700], const Color(0xFFAA1409)); + expect(brand[800], const Color(0xFF720E06)); + expect(brand[900], const Color(0xFF3A0703)); + expect(brand[1000], const Color(0xFF000000)); + }); + + test('generates the exact auto-derived chrome scale for a red seed', () { + final chrome = StreamTheme.light(brandColor: const Color(0xFFF44336)).colorScheme.chrome; + + expect(chrome[0], const Color(0xFFFFFFFF)); + expect(chrome[50], const Color(0xFFF4F1F1)); + expect(chrome[100], const Color(0xFFEAE6E6)); + expect(chrome[150], const Color(0xFFE1DBDA)); + expect(chrome[200], const Color(0xFFD8CFCF)); + expect(chrome[300], const Color(0xFFC5B9B8)); + expect(chrome[400], const Color(0xFFB2A2A1)); + expect(chrome[500], const Color(0xFFA08C8A)); + expect(chrome[600], const Color(0xFF836D6B)); + expect(chrome[700], const Color(0xFF635251)); + expect(chrome[800], const Color(0xFF423736)); + expect(chrome[900], const Color(0xFF221C1C)); + expect(chrome[1000], const Color(0xFF000000)); + }); + + test('uses the seed color unchanged as brand shade 500', () { + const seed = Color(0xFF2196F3); + + final brand = StreamTheme.light(brandColor: seed).colorScheme.brand; + + expect(brand[500], seed); + }); + }); + + group('StreamTheme.dark color generation', () { + test('generates the exact brand scale for a deep orange seed', () { + final brand = StreamTheme.dark(brandColor: const Color(0xFFFF5722)).colorScheme.brand; + + expect(brand[0], const Color(0xFF000000)); + expect(brand[50], const Color(0xFF3D0F00)); + expect(brand[100], const Color(0xFF571500)); + expect(brand[150], const Color(0xFF701B00)); + expect(brand[200], const Color(0xFF892100)); + expect(brand[300], const Color(0xFFBC2D00)); + expect(brand[400], const Color(0xFFEE3900)); + expect(brand[500], const Color(0xFFFF5722)); + expect(brand[600], const Color(0xFFFF7C53)); + expect(brand[700], const Color(0xFFFFA184)); + expect(brand[800], const Color(0xFFFFC6B5)); + expect(brand[900], const Color(0xFFFFECE5)); + expect(brand[1000], const Color(0xFFFFFFFF)); + }); + + test('generates the exact auto-derived chrome scale for a deep orange seed', () { + final chrome = StreamTheme.dark(brandColor: const Color(0xFFFF5722)).colorScheme.chrome; + + expect(chrome[0], const Color(0xFF000000)); + expect(chrome[50], const Color(0xFF221D1C)); + expect(chrome[100], const Color(0xFF302927)); + expect(chrome[150], const Color(0xFF3E3532)); + expect(chrome[200], const Color(0xFF4B413E)); + expect(chrome[300], const Color(0xFF675954)); + expect(chrome[400], const Color(0xFF83716B)); + expect(chrome[500], const Color(0xFF9C8B85)); + expect(chrome[600], const Color(0xFFB2A4A0)); + expect(chrome[700], const Color(0xFFC8BEBB)); + expect(chrome[800], const Color(0xFFDED8D6)); + expect(chrome[900], const Color(0xFFF4F2F1)); + expect(chrome[1000], const Color(0xFFFFFFFF)); + }); + + test('generates the exact brand scale for a purple seed', () { + final brand = StreamTheme.dark(brandColor: const Color(0xFF9C27B0)).colorScheme.brand; + + expect(brand[0], const Color(0xFF000000)); + expect(brand[50], const Color(0xFF2C0B32)); + expect(brand[100], const Color(0xFF390E40)); + expect(brand[150], const Color(0xFF45114E)); + expect(brand[200], const Color(0xFF52145C)); + expect(brand[300], const Color(0xFF6A1B78)); + expect(brand[400], const Color(0xFF832194)); + expect(brand[500], const Color(0xFF9C27B0)); + expect(brand[600], const Color(0xFFC145D6)); + expect(brand[700], const Color(0xFFD37CE2)); + expect(brand[800], const Color(0xFFE6B3EE)); + expect(brand[900], const Color(0xFFF8EAFA)); + expect(brand[1000], const Color(0xFFFFFFFF)); + }); + + test('generates the exact auto-derived chrome scale for a purple seed', () { + final chrome = StreamTheme.dark(brandColor: const Color(0xFF9C27B0)).colorScheme.chrome; + + expect(chrome[0], const Color(0xFF000000)); + expect(chrome[50], const Color(0xFF211C22)); + expect(chrome[100], const Color(0xFF2A232B)); + expect(chrome[150], const Color(0xFF332B34)); + expect(chrome[200], const Color(0xFF3C333E)); + expect(chrome[300], const Color(0xFF4F4251)); + expect(chrome[400], const Color(0xFF615163)); + expect(chrome[500], const Color(0xFF736176)); + expect(chrome[600], const Color(0xFF958299)); + expect(chrome[700], const Color(0xFFB5A7B7)); + expect(chrome[800], const Color(0xFFD4CCD5)); + expect(chrome[900], const Color(0xFFF3F1F4)); + expect(chrome[1000], const Color(0xFFFFFFFF)); + }); + + test('generates the exact brand scale for a yellow seed', () { + final brand = StreamTheme.dark(brandColor: const Color(0xFFFFEB3B)).colorScheme.brand; + + expect(brand[0], const Color(0xFF000000)); + expect(brand[50], const Color(0xFF3D3700)); + expect(brand[100], const Color(0xFF595000)); + expect(brand[150], const Color(0xFF756900)); + expect(brand[200], const Color(0xFF918300)); + expect(brand[300], const Color(0xFFCAB500)); + expect(brand[400], const Color(0xFFFFE503)); + expect(brand[500], const Color(0xFFFFEB3B)); + expect(brand[600], const Color(0xFFFFEF66)); + expect(brand[700], const Color(0xFFFFF490)); + expect(brand[800], const Color(0xFFFFF8BB)); + expect(brand[900], const Color(0xFFFFFCE5)); + expect(brand[1000], const Color(0xFFFFFFFF)); + }); + + test('generates the exact auto-derived chrome scale for a yellow seed', () { + final chrome = StreamTheme.dark(brandColor: const Color(0xFFFFEB3B)).colorScheme.chrome; + + expect(chrome[0], const Color(0xFF000000)); + expect(chrome[50], const Color(0xFF22211C)); + expect(chrome[100], const Color(0xFF313028)); + expect(chrome[150], const Color(0xFF413F35)); + expect(chrome[200], const Color(0xFF504F41)); + expect(chrome[300], const Color(0xFF6F6D5B)); + expect(chrome[400], const Color(0xFF8E8B74)); + expect(chrome[500], const Color(0xFFA7A593)); + expect(chrome[600], const Color(0xFFBAB8AB)); + expect(chrome[700], const Color(0xFFCDCCC2)); + expect(chrome[800], const Color(0xFFE0E0DA)); + expect(chrome[900], const Color(0xFFF4F3F1)); + expect(chrome[1000], const Color(0xFFFFFFFF)); + }); + + test('generates the exact brand scale for a green seed', () { + final brand = StreamTheme.dark(brandColor: const Color(0xFF4CAF50)).colorScheme.brand; + + expect(brand[0], const Color(0xFF000000)); + expect(brand[50], const Color(0xFF132B14)); + expect(brand[100], const Color(0xFF19391A)); + expect(brand[150], const Color(0xFF1F4821)); + expect(brand[200], const Color(0xFF265728)); + expect(brand[300], const Color(0xFF327435)); + expect(brand[400], const Color(0xFF3F9243)); + expect(brand[500], const Color(0xFF4CAF50)); + expect(brand[600], const Color(0xFF73C276)); + expect(brand[700], const Color(0xFF9CD49E)); + expect(brand[800], const Color(0xFFC5E6C6)); + expect(brand[900], const Color(0xFFEDF7EE)); + expect(brand[1000], const Color(0xFFFFFFFF)); + }); + + test('generates the exact auto-derived chrome scale for a green seed', () { + final chrome = StreamTheme.dark(brandColor: const Color(0xFF4CAF50)).colorScheme.chrome; + + expect(chrome[0], const Color(0xFF000000)); + expect(chrome[50], const Color(0xFF1C221C)); + expect(chrome[100], const Color(0xFF252D25)); + expect(chrome[150], const Color(0xFF2F392F)); + expect(chrome[200], const Color(0xFF384439)); + expect(chrome[300], const Color(0xFF4B5C4C)); + expect(chrome[400], const Color(0xFF5E735F)); + expect(chrome[500], const Color(0xFF718A72)); + expect(chrome[600], const Color(0xFF91A591)); + expect(chrome[700], const Color(0xFFB1BFB1)); + expect(chrome[800], const Color(0xFFD1D9D1)); + expect(chrome[900], const Color(0xFFF1F4F1)); + expect(chrome[1000], const Color(0xFFFFFFFF)); + }); + + test('generates the exact brand scale for a blue seed', () { + final brand = StreamTheme.dark(brandColor: const Color(0xFF2196F3)).colorScheme.brand; + + expect(brand[0], const Color(0xFF000000)); + expect(brand[50], const Color(0xFF03223A)); + expect(brand[100], const Color(0xFF042F51)); + expect(brand[150], const Color(0xFF063C67)); + expect(brand[200], const Color(0xFF07497E)); + expect(brand[300], const Color(0xFF0964AB)); + expect(brand[400], const Color(0xFF0C7ED9)); + expect(brand[500], const Color(0xFF2196F3)); + expect(brand[600], const Color(0xFF52ADF6)); + expect(brand[700], const Color(0xFF84C5F8)); + expect(brand[800], const Color(0xFFB5DCFB)); + expect(brand[900], const Color(0xFFE7F4FE)); + expect(brand[1000], const Color(0xFFFFFFFF)); + }); + + test('generates the exact auto-derived chrome scale for a blue seed', () { + final chrome = StreamTheme.dark(brandColor: const Color(0xFF2196F3)).colorScheme.chrome; + + expect(chrome[0], const Color(0xFF000000)); + expect(chrome[50], const Color(0xFF1C1F22)); + expect(chrome[100], const Color(0xFF262B2F)); + expect(chrome[150], const Color(0xFF31373C)); + expect(chrome[200], const Color(0xFF3C4349)); + expect(chrome[300], const Color(0xFF515B63)); + expect(chrome[400], const Color(0xFF67737E)); + expect(chrome[500], const Color(0xFF7E8B96)); + expect(chrome[600], const Color(0xFF9BA5AD)); + expect(chrome[700], const Color(0xFFB8BFC5)); + expect(chrome[800], const Color(0xFFD4D9DC)); + expect(chrome[900], const Color(0xFFF1F2F4)); + expect(chrome[1000], const Color(0xFFFFFFFF)); + }); + + test('generates the exact brand scale for a red seed', () { + final brand = StreamTheme.dark(brandColor: const Color(0xFFF44336)).colorScheme.brand; + + expect(brand[0], const Color(0xFF000000)); + expect(brand[50], const Color(0xFF3A0703)); + expect(brand[100], const Color(0xFF530A05)); + expect(brand[150], const Color(0xFF6C0D06)); + expect(brand[200], const Color(0xFF851007)); + expect(brand[300], const Color(0xFFB7160A)); + expect(brand[400], const Color(0xFFE91C0D)); + expect(brand[500], const Color(0xFFF44336)); + expect(brand[600], const Color(0xFFF66C62)); + expect(brand[700], const Color(0xFFF9968E)); + expect(brand[800], const Color(0xFFFBBFBB)); + expect(brand[900], const Color(0xFFFEE8E7)); + expect(brand[1000], const Color(0xFFFFFFFF)); + }); + + test('generates the exact auto-derived chrome scale for a red seed', () { + final chrome = StreamTheme.dark(brandColor: const Color(0xFFF44336)).colorScheme.chrome; + + expect(chrome[0], const Color(0xFF000000)); + expect(chrome[50], const Color(0xFF221C1C)); + expect(chrome[100], const Color(0xFF302827)); + expect(chrome[150], const Color(0xFF3F3433)); + expect(chrome[200], const Color(0xFF4D403F)); + expect(chrome[300], const Color(0xFF6A5857)); + expect(chrome[400], const Color(0xFF87706E)); + expect(chrome[500], const Color(0xFFA08C8A)); + expect(chrome[600], const Color(0xFFB5A5A4)); + expect(chrome[700], const Color(0xFFCABEBE)); + expect(chrome[800], const Color(0xFFDFD8D7)); + expect(chrome[900], const Color(0xFFF4F1F1)); + expect(chrome[1000], const Color(0xFFFFFFFF)); + }); + + test('uses the seed color unchanged as brand shade 500', () { + const seed = Color(0xFF2196F3); + + final brand = StreamTheme.dark(brandColor: seed).colorScheme.brand; + + expect(brand[500], seed); + }); + }); +} From 4232a03d0a89bcaaf39d2d1b084f724323cd0514 Mon Sep 17 00:00:00 2001 From: renefloor <15101411+renefloor@users.noreply.github.com> Date: Fri, 17 Jul 2026 09:04:23 +0000 Subject: [PATCH 5/8] chore: Update Goldens --- .../goldens/ci/stream_theme_color_generation.png | Bin 0 -> 9731 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/stream_core_flutter/test/theme/goldens/ci/stream_theme_color_generation.png diff --git a/packages/stream_core_flutter/test/theme/goldens/ci/stream_theme_color_generation.png b/packages/stream_core_flutter/test/theme/goldens/ci/stream_theme_color_generation.png new file mode 100644 index 0000000000000000000000000000000000000000..7ac5830cdb1fd46df97bd3d77ebe47447e81ddf5 GIT binary patch literal 9731 zcmeHNYfw|y7Cve%TGZ&J7t|uyOKWNoQ7Ht^v|8?G@Xu;oNu-uLQfe?OgyDZGrbW z(B}m2c6GtB1jfA>wiQ3NuTmbC)=;7pB6H!M_Ve9K7GPLjkR-~qhqoBR zre0TN=s~^NYuDCYOJ?seZ0UzO?YT9U`gF#faxx~yf_l2l`?Id84Hx(oO?P|y)3LzaW1R1gj9Cj($`$5KbcEU4RfY6B@r)C1w_lI zwFGH~(P*Vy_=H<)#KNEG9=O&wKO&IqF9_HRPhAUHMx$L%&hM#6u!>yX7OoNZtrb7= zRPw@Hhy@&bj-jYIiO690k3-X=)3@vT79R2pChu5JF5l&tV*ZhRJSsmf-K{v26~cQA zpCr)~QE?`qh^l#o%aa6bBC*L2;)mHV<_bH`8l3t7%u~RMk^79aw{6aV`B0C+47xeV zFdK%HRp*-Q0XLL){>Kj&ysguo`;pEPvv_*xEn)5Hc%6{ACs=4u5=1QkM9UUi&D;m& z05k()mJr>a*3>0X_JLqm5)ce~Tg*|7<4O{s@gSK;0)*BwIW~py8AjRB5t}2*p`{H@ ze2e)Y&OS8;pj>PLU2d8&K5)f(YY)fK}b|I81Sd=jVa@ z)Kb(sGJTT4_Ajq)_VTSA7g-b(Ufc^)c>K5l8s-*EdcoM4>@p7+o5-6!1R5>k6WoK& zeg-enFNanUnjc8EXpNd(N03?xjM@mcx;|g5nr_8r-S-b$Vn^wSAiAq>8FZ8#f8Q_3 zG-^2H5y|$D<9sL?TQu$uBILa(7rWV!Fl@^^&VRmk^ZeoERYJ3{k+BrmqV1ga!S>8g zA{t1AJ%z!*CPuExeS#*QShDb=-HlYlBoKi8$zk~rk_=;kU{ocmC!`(U-~@}}IkbI_ zH{M7;MqxobQBLbaMA#%Py$Y^O!3R^6>jmMCt0PbkHFbDqxB_*gTr zWrjBthO1)GR01<$gd}330hwbHTHAkj4X3;W0vIK&EfW|;H4*00eWwQ(yW zO7yY~=_$s)8Eo|r*LB*AIdqno^@^9?5{=%9({1Z4lWqb8V~ShQCf`$f^?rRF0$io^ zq6df;DU%J4kMLs=iFkOVenxZ&Sd|aW(AU~zJZU1YCN(cKqzq8Iy}=%l0?jQh+Ts;I zM8;a2#QCF+no{CWLJ&DA0?`sW%moqJ(O3;4!M3NAbl+WrV4oH}PiXL{;xcuD&aD)$ z8qz;-kdnxO8E~?~c%*}+9V)(Iq-JVAkOgCVncRm_St*6o;OOv*GB-x$F$anT({nWR z$I8qupHA)PRJv{c1z7&@=BBgYpKHM#OozaXu&u-%BSQoWpFY?rc>{?V;B+=lR12TqYK3QUS{ z`TNVl3jx>;6o1(uoGn52AnZpmLUuPEiKh))V--Vnb@fY55p1A2?Mhl&gk<QZP<3z-Ri0ghpLeIC+sbZ>W=%6yMY)+XzOIy3 z5;hmZzVqvv%Dx>z|3cd1*1=qaVFL?gB8RtR)v&&rp=z~q-R!;s(qx4e_?b31pZ5E_ zd7jv&`~sE*%rf?$gcy0n1{2%?{d8736x?BUpuMv26T6Pj;)-qZ!7es(PXAPvzz*X6DlwAM*h^&a$tJubP z1CryI@Koqzn3ivMszEO7Y^@1^&XNv5K~WCv7Id{;qP`8{ zC%6bSo5%`4M2Nxz+Fb|;<&iF|r+ETWQ#m|@tcOG<$B;##k)bGgAVeeVyrdGa&caa{ zU%)!AeG}>q-@sSV_0k_X{hyUSt;r9@7YBMAem$yoJ-@6W;oHG`a~8h&k96k`qdfKW z|E2GIZehQ(%Z24zR=Vzt{_f7nmG+?9MW2Ppl@%rF=8A!a$y=9(#I#n)uAQ}`#2DRD;i zMl5{ZCQY*^v}`jCFOSwRgg95B0jXu`i~Ufc(qP>8-ptpe2)0I&vU6e|RF$nNp_nmO zIidc3PQ=3lyAVwgjG>oK-(cJ!X2kY>el#C+S0ji%c|o+DxDA~lgD%jNY{??bfXz8p!Xz9RG`{ebn9%0^6GW>I3ehT_8 zz+XMKZoC)ns-462vwd^KnZ}l|Umrb6hgWLmtV=>bWoD8sXIY}u1^|;A+EcpSj~7Tb zbJ|=2FU{gwZmF!jrd#0^o`<2porT9VEq0EO2h{XcABM7~wsI6P9&`B!h^vMW6{zX3 z1f}KuR>+}i6odny6srZ(D|5p8=rQ}J+vzdG;Y&>GfA&j9grc7uYer_zaER~aD|5>S zL%r_Ri Date: Fri, 17 Jul 2026 15:09:45 +0200 Subject: [PATCH 6/8] simplify stream theme construction --- .../theme/semantics/stream_color_scheme.dart | 26 +++++++++ .../lib/src/theme/stream_theme.dart | 54 ++++-------------- ...am_theme_color_generation_golden_test.dart | 6 +- .../stream_theme_color_generation_test.dart | 56 +++++++++---------- 4 files changed, 68 insertions(+), 74 deletions(-) diff --git a/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart b/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart index b7f0e192..a6f4a266 100644 --- a/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart +++ b/packages/stream_core_flutter/lib/src/theme/semantics/stream_color_scheme.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:stream_core/stream_core.dart' show Standard; import 'package:theme_extensions_builder_annotation/theme_extensions_builder_annotation.dart'; import '../../theme/primitives/stream_colors.dart'; @@ -207,6 +208,7 @@ class StreamColorScheme with _$StreamColorScheme { ]; return .raw( + brightness: .light, brand: brand, chrome: chrome, accentPrimary: accentPrimary, @@ -427,6 +429,7 @@ class StreamColorScheme with _$StreamColorScheme { ]; return .raw( + brightness: .dark, brand: brand, chrome: chrome, accentPrimary: accentPrimary, @@ -484,7 +487,27 @@ class StreamColorScheme with _$StreamColorScheme { ); } + factory StreamColorScheme.fromSeed({ + required Color brand, + Color? chrome, + Brightness brightness = Brightness.light, + }) { + final colorScheme = brightness == Brightness.light ? StreamColorScheme.light : StreamColorScheme.dark; + return colorScheme( + brand: StreamColorSwatch.fromColor(brand, brightness: brightness), + chrome: + chrome?.let( + (chromeColor) => StreamColorSwatch.fromColor( + chromeColor, + brightness: brightness, + ), + ) ?? + StreamColorSwatch.fromColor(brand, brightness: brightness, saturation: 0.1), + ); + } + const StreamColorScheme.raw({ + this.brightness = Brightness.light, required this.brand, required this.chrome, // Accent @@ -550,6 +573,9 @@ class StreamColorScheme with _$StreamColorScheme { required this.avatarPalette, }); + /// The brightness of the color scheme. + final Brightness brightness; + // ---- Brand ---- /// The brand color swatch with shades from 50 to 950. diff --git a/packages/stream_core_flutter/lib/src/theme/stream_theme.dart b/packages/stream_core_flutter/lib/src/theme/stream_theme.dart index 462b278e..46582697 100644 --- a/packages/stream_core_flutter/lib/src/theme/stream_theme.dart +++ b/packages/stream_core_flutter/lib/src/theme/stream_theme.dart @@ -106,7 +106,7 @@ class StreamTheme extends ThemeExtension with _$StreamTheme { /// * [StreamTheme.light], which creates a light theme. /// * [StreamTheme.dark], which creates a dark theme. factory StreamTheme({ - Brightness brightness = .light, + Brightness? brightness, TargetPlatform? platform, StreamIcons? icons, StreamRadius? radius, @@ -153,8 +153,14 @@ class StreamTheme extends ThemeExtension with _$StreamTheme { StreamStepperThemeData? stepperTheme, StreamSwitchThemeData? switchTheme, }) { + assert( + colorScheme == null || brightness == null || colorScheme.brightness == brightness, + 'colorScheme brightness must match theme brightness if provided', + ); + platform ??= defaultTargetPlatform; - final isDark = brightness == Brightness.dark; + final effectiveBrightness = brightness ?? colorScheme?.brightness ?? .light; + final isDark = effectiveBrightness == Brightness.dark; // Primitives icons ??= const StreamIcons(); @@ -206,7 +212,7 @@ class StreamTheme extends ThemeExtension with _$StreamTheme { switchTheme ??= const StreamSwitchThemeData(); return .raw( - brightness: brightness, + brightness: effectiveBrightness, icons: icons, radius: radius, spacing: spacing, @@ -257,51 +263,13 @@ class StreamTheme extends ThemeExtension with _$StreamTheme { /// /// This is a convenience factory that calls [StreamTheme] with /// [Brightness.light]. - factory StreamTheme.light({Color? brandColor, Color? chromeColor}) { - final brand = brandColor?.let( - (brandColor) => StreamColorSwatch.fromColor(brandColor, brightness: .light), - ); - final chrome = - chromeColor?.let( - (chromeColor) => StreamColorSwatch.fromColor(chromeColor, brightness: .light), - ) ?? - brandColor?.let( - (brandColor) => StreamColorSwatch.fromColor(brandColor, brightness: .light, saturation: 0.1), - ); - - return StreamTheme( - brightness: .light, - colorScheme: StreamColorScheme.light( - brand: brand, - chrome: chrome, - ), - ); - } + factory StreamTheme.light() => StreamTheme(brightness: .light); /// Creates a dark theme configuration. /// /// This is a convenience factory that calls [StreamTheme] with /// [Brightness.dark]. - factory StreamTheme.dark({Color? brandColor, Color? chromeColor}) { - final brand = brandColor?.let( - (brandColor) => StreamColorSwatch.fromColor(brandColor, brightness: .dark), - ); - final chrome = - chromeColor?.let( - (chromeColor) => StreamColorSwatch.fromColor(chromeColor, brightness: .dark), - ) ?? - brandColor?.let( - (brandColor) => StreamColorSwatch.fromColor(brandColor, brightness: .dark, saturation: 0.1), - ); - - return StreamTheme( - brightness: .dark, - colorScheme: StreamColorScheme.dark( - brand: brand, - chrome: chrome, - ), - ); - } + factory StreamTheme.dark() => StreamTheme(brightness: .dark); const StreamTheme.raw({ required this.brightness, diff --git a/packages/stream_core_flutter/test/theme/stream_theme_color_generation_golden_test.dart b/packages/stream_core_flutter/test/theme/stream_theme_color_generation_golden_test.dart index 2a536612..f45dbd1e 100644 --- a/packages/stream_core_flutter/test/theme/stream_theme_color_generation_golden_test.dart +++ b/packages/stream_core_flutter/test/theme/stream_theme_color_generation_golden_test.dart @@ -8,7 +8,7 @@ import 'package:stream_core_flutter/core.dart'; const _shades = [0, 50, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900, 1000]; void main() { - group('StreamTheme color generation Golden Tests', () { + group('StreamColorScheme color generation Golden Tests', () { goldenTest( 'renders the generated brand and chrome scales for custom seed colors', fileName: 'stream_theme_color_generation', @@ -57,8 +57,8 @@ class _SeedColorPreview extends StatelessWidget { @override Widget build(BuildContext context) { - final light = StreamTheme.light(brandColor: seedColor).colorScheme; - final dark = StreamTheme.dark(brandColor: seedColor).colorScheme; + final light = StreamColorScheme.fromSeed(brand: seedColor); + final dark = StreamColorScheme.fromSeed(brand: seedColor, brightness: .dark); return Column( mainAxisSize: MainAxisSize.min, diff --git a/packages/stream_core_flutter/test/theme/stream_theme_color_generation_test.dart b/packages/stream_core_flutter/test/theme/stream_theme_color_generation_test.dart index d6f45098..3df77805 100644 --- a/packages/stream_core_flutter/test/theme/stream_theme_color_generation_test.dart +++ b/packages/stream_core_flutter/test/theme/stream_theme_color_generation_test.dart @@ -3,9 +3,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:stream_core_flutter/core.dart'; void main() { - group('StreamTheme.light color generation', () { + group('StreamColorScheme.fromSeed light color generation', () { test('generates the exact brand scale for a deep orange seed', () { - final brand = StreamTheme.light(brandColor: const Color(0xFFFF5722)).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: const Color(0xFFFF5722)).brand; expect(brand[0], const Color(0xFFFFFFFF)); expect(brand[50], const Color(0xFFFFECE5)); @@ -23,7 +23,7 @@ void main() { }); test('generates the exact auto-derived chrome scale for a deep orange seed', () { - final chrome = StreamTheme.light(brandColor: const Color(0xFFFF5722)).colorScheme.chrome; + final chrome = StreamColorScheme.fromSeed(brand: const Color(0xFFFF5722)).chrome; expect(chrome[0], const Color(0xFFFFFFFF)); expect(chrome[50], const Color(0xFFF4F2F1)); @@ -41,7 +41,7 @@ void main() { }); test('generates the exact brand scale for a purple seed', () { - final brand = StreamTheme.light(brandColor: const Color(0xFF9C27B0)).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: const Color(0xFF9C27B0)).brand; expect(brand[0], const Color(0xFFFFFFFF)); expect(brand[50], const Color(0xFFF8EAFA)); @@ -59,7 +59,7 @@ void main() { }); test('generates the exact auto-derived chrome scale for a purple seed', () { - final chrome = StreamTheme.light(brandColor: const Color(0xFF9C27B0)).colorScheme.chrome; + final chrome = StreamColorScheme.fromSeed(brand: const Color(0xFF9C27B0)).chrome; expect(chrome[0], const Color(0xFFFFFFFF)); expect(chrome[50], const Color(0xFFF3F1F4)); @@ -77,7 +77,7 @@ void main() { }); test('generates the exact brand scale for a yellow seed', () { - final brand = StreamTheme.light(brandColor: const Color(0xFFFFEB3B)).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: const Color(0xFFFFEB3B)).brand; expect(brand[0], const Color(0xFFFFFFFF)); expect(brand[50], const Color(0xFFFFFCE5)); @@ -95,7 +95,7 @@ void main() { }); test('generates the exact auto-derived chrome scale for a yellow seed', () { - final chrome = StreamTheme.light(brandColor: const Color(0xFFFFEB3B)).colorScheme.chrome; + final chrome = StreamColorScheme.fromSeed(brand: const Color(0xFFFFEB3B)).chrome; expect(chrome[0], const Color(0xFFFFFFFF)); expect(chrome[50], const Color(0xFFF4F3F1)); @@ -113,7 +113,7 @@ void main() { }); test('generates the exact brand scale for a green seed', () { - final brand = StreamTheme.light(brandColor: const Color(0xFF4CAF50)).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: const Color(0xFF4CAF50)).brand; expect(brand[0], const Color(0xFFFFFFFF)); expect(brand[50], const Color(0xFFEDF7EE)); @@ -131,7 +131,7 @@ void main() { }); test('generates the exact auto-derived chrome scale for a green seed', () { - final chrome = StreamTheme.light(brandColor: const Color(0xFF4CAF50)).colorScheme.chrome; + final chrome = StreamColorScheme.fromSeed(brand: const Color(0xFF4CAF50)).chrome; expect(chrome[0], const Color(0xFFFFFFFF)); expect(chrome[50], const Color(0xFFF1F4F1)); @@ -149,7 +149,7 @@ void main() { }); test('generates the exact brand scale for a blue seed', () { - final brand = StreamTheme.light(brandColor: const Color(0xFF2196F3)).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: const Color(0xFF2196F3)).brand; expect(brand[0], const Color(0xFFFFFFFF)); expect(brand[50], const Color(0xFFE7F4FE)); @@ -167,7 +167,7 @@ void main() { }); test('generates the exact auto-derived chrome scale for a blue seed', () { - final chrome = StreamTheme.light(brandColor: const Color(0xFF2196F3)).colorScheme.chrome; + final chrome = StreamColorScheme.fromSeed(brand: const Color(0xFF2196F3)).chrome; expect(chrome[0], const Color(0xFFFFFFFF)); expect(chrome[50], const Color(0xFFF1F2F4)); @@ -185,7 +185,7 @@ void main() { }); test('generates the exact brand scale for a red seed', () { - final brand = StreamTheme.light(brandColor: const Color(0xFFF44336)).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: const Color(0xFFF44336)).brand; expect(brand[0], const Color(0xFFFFFFFF)); expect(brand[50], const Color(0xFFFEE8E7)); @@ -203,7 +203,7 @@ void main() { }); test('generates the exact auto-derived chrome scale for a red seed', () { - final chrome = StreamTheme.light(brandColor: const Color(0xFFF44336)).colorScheme.chrome; + final chrome = StreamColorScheme.fromSeed(brand: const Color(0xFFF44336)).chrome; expect(chrome[0], const Color(0xFFFFFFFF)); expect(chrome[50], const Color(0xFFF4F1F1)); @@ -223,15 +223,15 @@ void main() { test('uses the seed color unchanged as brand shade 500', () { const seed = Color(0xFF2196F3); - final brand = StreamTheme.light(brandColor: seed).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: seed).brand; expect(brand[500], seed); }); }); - group('StreamTheme.dark color generation', () { + group('StreamColorScheme.fromSeed dark color generation', () { test('generates the exact brand scale for a deep orange seed', () { - final brand = StreamTheme.dark(brandColor: const Color(0xFFFF5722)).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: const Color(0xFFFF5722), brightness: .dark).brand; expect(brand[0], const Color(0xFF000000)); expect(brand[50], const Color(0xFF3D0F00)); @@ -249,7 +249,7 @@ void main() { }); test('generates the exact auto-derived chrome scale for a deep orange seed', () { - final chrome = StreamTheme.dark(brandColor: const Color(0xFFFF5722)).colorScheme.chrome; + final chrome = StreamColorScheme.fromSeed(brand: const Color(0xFFFF5722), brightness: .dark).chrome; expect(chrome[0], const Color(0xFF000000)); expect(chrome[50], const Color(0xFF221D1C)); @@ -267,7 +267,7 @@ void main() { }); test('generates the exact brand scale for a purple seed', () { - final brand = StreamTheme.dark(brandColor: const Color(0xFF9C27B0)).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: const Color(0xFF9C27B0), brightness: .dark).brand; expect(brand[0], const Color(0xFF000000)); expect(brand[50], const Color(0xFF2C0B32)); @@ -285,7 +285,7 @@ void main() { }); test('generates the exact auto-derived chrome scale for a purple seed', () { - final chrome = StreamTheme.dark(brandColor: const Color(0xFF9C27B0)).colorScheme.chrome; + final chrome = StreamColorScheme.fromSeed(brand: const Color(0xFF9C27B0), brightness: .dark).chrome; expect(chrome[0], const Color(0xFF000000)); expect(chrome[50], const Color(0xFF211C22)); @@ -303,7 +303,7 @@ void main() { }); test('generates the exact brand scale for a yellow seed', () { - final brand = StreamTheme.dark(brandColor: const Color(0xFFFFEB3B)).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: const Color(0xFFFFEB3B), brightness: .dark).brand; expect(brand[0], const Color(0xFF000000)); expect(brand[50], const Color(0xFF3D3700)); @@ -321,7 +321,7 @@ void main() { }); test('generates the exact auto-derived chrome scale for a yellow seed', () { - final chrome = StreamTheme.dark(brandColor: const Color(0xFFFFEB3B)).colorScheme.chrome; + final chrome = StreamColorScheme.fromSeed(brand: const Color(0xFFFFEB3B), brightness: .dark).chrome; expect(chrome[0], const Color(0xFF000000)); expect(chrome[50], const Color(0xFF22211C)); @@ -339,7 +339,7 @@ void main() { }); test('generates the exact brand scale for a green seed', () { - final brand = StreamTheme.dark(brandColor: const Color(0xFF4CAF50)).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: const Color(0xFF4CAF50), brightness: .dark).brand; expect(brand[0], const Color(0xFF000000)); expect(brand[50], const Color(0xFF132B14)); @@ -357,7 +357,7 @@ void main() { }); test('generates the exact auto-derived chrome scale for a green seed', () { - final chrome = StreamTheme.dark(brandColor: const Color(0xFF4CAF50)).colorScheme.chrome; + final chrome = StreamColorScheme.fromSeed(brand: const Color(0xFF4CAF50), brightness: .dark).chrome; expect(chrome[0], const Color(0xFF000000)); expect(chrome[50], const Color(0xFF1C221C)); @@ -375,7 +375,7 @@ void main() { }); test('generates the exact brand scale for a blue seed', () { - final brand = StreamTheme.dark(brandColor: const Color(0xFF2196F3)).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: const Color(0xFF2196F3), brightness: .dark).brand; expect(brand[0], const Color(0xFF000000)); expect(brand[50], const Color(0xFF03223A)); @@ -393,7 +393,7 @@ void main() { }); test('generates the exact auto-derived chrome scale for a blue seed', () { - final chrome = StreamTheme.dark(brandColor: const Color(0xFF2196F3)).colorScheme.chrome; + final chrome = StreamColorScheme.fromSeed(brand: const Color(0xFF2196F3), brightness: .dark).chrome; expect(chrome[0], const Color(0xFF000000)); expect(chrome[50], const Color(0xFF1C1F22)); @@ -411,7 +411,7 @@ void main() { }); test('generates the exact brand scale for a red seed', () { - final brand = StreamTheme.dark(brandColor: const Color(0xFFF44336)).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: const Color(0xFFF44336), brightness: .dark).brand; expect(brand[0], const Color(0xFF000000)); expect(brand[50], const Color(0xFF3A0703)); @@ -429,7 +429,7 @@ void main() { }); test('generates the exact auto-derived chrome scale for a red seed', () { - final chrome = StreamTheme.dark(brandColor: const Color(0xFFF44336)).colorScheme.chrome; + final chrome = StreamColorScheme.fromSeed(brand: const Color(0xFFF44336), brightness: .dark).chrome; expect(chrome[0], const Color(0xFF000000)); expect(chrome[50], const Color(0xFF221C1C)); @@ -449,7 +449,7 @@ void main() { test('uses the seed color unchanged as brand shade 500', () { const seed = Color(0xFF2196F3); - final brand = StreamTheme.dark(brandColor: seed).colorScheme.brand; + final brand = StreamColorScheme.fromSeed(brand: seed, brightness: .dark).brand; expect(brand[500], seed); }); From 75a581e5270e67344c2d82665073c2f9ecf07a44 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Fri, 17 Jul 2026 15:11:53 +0200 Subject: [PATCH 7/8] fix fromColor map with saturation --- .../lib/src/theme/primitives/stream_colors.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/stream_core_flutter/lib/src/theme/primitives/stream_colors.dart b/packages/stream_core_flutter/lib/src/theme/primitives/stream_colors.dart index e92dca73..4f5d4ae3 100644 --- a/packages/stream_core_flutter/lib/src/theme/primitives/stream_colors.dart +++ b/packages/stream_core_flutter/lib/src/theme/primitives/stream_colors.dart @@ -274,10 +274,8 @@ class StreamColorSwatch extends ColorSwatch { Brightness brightness = Brightness.light, double? saturation, }) { - return StreamColorSwatch( - color.toARGB32(), - StreamColorSwatchHelper.generateShadeMap(color, brightness: brightness, saturation: saturation), - ); + final shadeMap = StreamColorSwatchHelper.generateShadeMap(color, brightness: brightness, saturation: saturation); + return StreamColorSwatch(shadeMap[500]!.toARGB32(), shadeMap); } /// The lightest shade. From 9467acdcfa3fe255558afe537eb31cae915428b3 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Fri, 17 Jul 2026 15:24:27 +0200 Subject: [PATCH 8/8] switch contex.read to context.watch --- .../theme_customization_panel.dart | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/design_system_gallery/lib/widgets/theme_studio/theme_customization_panel.dart b/apps/design_system_gallery/lib/widgets/theme_studio/theme_customization_panel.dart index 3bcce9bb..3ce90aa8 100644 --- a/apps/design_system_gallery/lib/widgets/theme_studio/theme_customization_panel.dart +++ b/apps/design_system_gallery/lib/widgets/theme_studio/theme_customization_panel.dart @@ -179,7 +179,7 @@ class _ThemeCustomizationPanelState extends State { } Widget _buildAppearanceSection(BuildContext context) { - final config = context.read(); + final config = context.watch(); final spacing = context.streamSpacing; return SectionCard( @@ -211,7 +211,7 @@ class _ThemeCustomizationPanelState extends State { } Widget _buildBrandSection(BuildContext context) { - final config = context.read(); + final config = context.watch(); return SectionCard( title: 'Brand Color', subtitle: 'brand', @@ -227,7 +227,7 @@ class _ThemeCustomizationPanelState extends State { } Widget _buildChromeSection(BuildContext context) { - final config = context.read(); + final config = context.watch(); return SectionCard( title: 'Chrome Color', subtitle: 'chrome', @@ -243,7 +243,7 @@ class _ThemeCustomizationPanelState extends State { } Widget _buildAccentColorsSection(BuildContext context) { - final config = context.read(); + final config = context.watch(); return SectionCard( title: 'Accent Colors', subtitle: 'accent*', @@ -291,7 +291,7 @@ class _ThemeCustomizationPanelState extends State { } Widget _buildTextColorsSection(BuildContext context) { - final config = context.read(); + final config = context.watch(); return SectionCard( title: 'Text Colors', subtitle: 'text*', @@ -346,7 +346,7 @@ class _ThemeCustomizationPanelState extends State { } Widget _buildBackgroundColorsSection(BuildContext context) { - final config = context.read(); + final config = context.watch(); final spacing = context.streamSpacing; return SectionCard( title: 'Background Colors', @@ -510,7 +510,7 @@ class _ThemeCustomizationPanelState extends State { } Widget _buildBorderCoreSection(BuildContext context) { - final config = context.read(); + final config = context.watch(); return SectionCard( title: 'Border Colors - Core', subtitle: 'border*', @@ -572,7 +572,7 @@ class _ThemeCustomizationPanelState extends State { } Widget _buildBorderUtilitySection(BuildContext context) { - final config = context.read(); + final config = context.watch(); return SectionCard( title: 'Border Colors - Utility', subtitle: 'border*', @@ -648,7 +648,7 @@ class _ThemeCustomizationPanelState extends State { } Widget _buildSystemColorsSection(BuildContext context) { - final config = context.read(); + final config = context.watch(); return SectionCard( title: 'System Colors', subtitle: 'system*', @@ -675,7 +675,7 @@ class _ThemeCustomizationPanelState extends State { } Widget _buildAvatarPaletteSection(BuildContext context) { - final config = context.read(); + final config = context.watch(); final palette = config.avatarPalette; final isDefault = !config.avatarPaletteIsCustom; final spacing = context.streamSpacing;