From 3206ddc1e5f3b26be5dd09823ad7b7450f74655b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Aug 2026 20:50:47 +0000 Subject: [PATCH 1/4] feat(theme): establish a deliberate colour system Colour was used as a signal in three unrelated ways with no single owner: category accents, stock availability, and personal tasted state. This consolidates all three into CategoryColorHelper and documents the model in the class doc, so colour choices have one place to live. - Remove getCategoryColor(), which had no call sites in lib/ at all. The "two category-colour paths that can drift" were never both live; the chips it was supposed to serve use colorScheme.primaryContainer. - Add getAvailabilityColor(), replacing the hex palette that was duplicated independently between the drinks list availability chip and the My Festival at-risk hint. Sold-out keeps tracking the theme error colour rather than a fixed hex. - Derive category accents from brightness so they adapt to dark mode: the hue is kept as-is for light, and lightness-lifted for dark. Deriving accents via ColorScheme.fromSeed(...).primary was measured and rejected: it desaturates the palette and collapses perry and apple juice to a summed-RGB distance of 2, destroying the at-a-glance category distinction the accent exists for. The lift keeps minimum separation at 63 against 72 today. A test pins that floor so a future derivation change cannot quietly reintroduce the collision. Light mode is byte-identical by construction, so only the six dark goldens changed; each was reviewed and shows an accent-colour change with no reflow. getTastedColor stays independent of getAvailabilityColor even though both are green today, since personal status and stock level are separate signals that may diverge. Fixes #469 --- lib/screens/drink_detail_screen.dart | 5 +- lib/screens/my_festival_screen.dart | 26 +- lib/utils/category_color_helper.dart | 183 +++++++----- lib/widgets/brewery_hero_panel.dart | 5 +- lib/widgets/drink_card.dart | 18 +- lib/widgets/drink_hero_panel.dart | 5 +- lib/widgets/style_hero_panel.dart | 5 +- test/category_color_helper_test.dart | 257 ++++++++++++----- test/goldens/brewery_screen_dark.png | Bin 15352 -> 15394 bytes .../drink_detail_screen_medium_name_dark.png | Bin 13124 -> 13143 bytes .../style_screen_with_description_dark.png | Bin 12740 -> 12778 bytes .../goldens/my_festival_screen_dark.png | Bin 7427 -> 7470 bytes test/screens/my_festival_screen_test.dart | 7 +- test/utils_test.dart | 265 ------------------ .../drink_card_search_excerpt_dark.png | Bin 6619 -> 6643 bytes .../drink_card_tasted_multiple_dark.png | Bin 4009 -> 4027 bytes 16 files changed, 346 insertions(+), 430 deletions(-) diff --git a/lib/screens/drink_detail_screen.dart b/lib/screens/drink_detail_screen.dart index 892d5054..1927bafd 100644 --- a/lib/screens/drink_detail_screen.dart +++ b/lib/screens/drink_detail_screen.dart @@ -501,7 +501,10 @@ class _SimilarDrinkCard extends StatelessWidget { decoration: BoxDecoration( border: Border( left: BorderSide( - color: CategoryColorHelper.getAccentColor(drink.category), + color: CategoryColorHelper.getAccentColor( + drink.category, + theme.brightness, + ), width: 4, ), ), diff --git a/lib/screens/my_festival_screen.dart b/lib/screens/my_festival_screen.dart index 17ead4a8..13872fa7 100644 --- a/lib/screens/my_festival_screen.dart +++ b/lib/screens/my_festival_screen.dart @@ -266,7 +266,10 @@ class _MyFestivalScreenState extends State { final availabilityPhrase = _availabilityPhrase(availability); return _buildRowCard( context, - accent: CategoryColorHelper.getAccentColor(drink.category), + accent: CategoryColorHelper.getAccentColor( + drink.category, + Theme.of(context).brightness, + ), child: Semantics( label: '${drink.name}, ${drink.abv.toStringAsFixed(1)}% ABV' @@ -313,7 +316,10 @@ class _MyFestivalScreenState extends State { final note = _noteText(entry); return _buildRowCard( context, - accent: CategoryColorHelper.getAccentColor(drink.category), + accent: CategoryColorHelper.getAccentColor( + drink.category, + Theme.of(context).brightness, + ), child: Semantics( label: '${drink.name}, by ${drink.breweryName}, $tastedLabel, ' @@ -471,21 +477,22 @@ class _MyFestivalScreenState extends State { AvailabilityStatus? status, ) { final theme = Theme.of(context); - final isDark = theme.brightness == Brightness.dark; - final Color color; + // Bound in the switch so the at-risk status is non-null below; the calm + // states all return early. + final AvailabilityStatus atRisk; final IconData icon; final String label; switch (status) { case AvailabilityStatus.out: - color = theme.colorScheme.error; + atRisk = AvailabilityStatus.out; icon = Icons.cancel; label = 'Sold Out'; case AvailabilityStatus.veryLow: - color = isDark ? const Color(0xFFFF7043) : const Color(0xFFBF360C); + atRisk = AvailabilityStatus.veryLow; icon = Icons.warning_amber; label = 'Nearly Gone'; case AvailabilityStatus.low: - color = isDark ? const Color(0xFFFF9800) : const Color(0xFFEF6C00); + atRisk = AvailabilityStatus.low; icon = Icons.warning; label = 'Low'; case AvailabilityStatus.plenty: @@ -494,6 +501,11 @@ class _MyFestivalScreenState extends State { case null: return null; } + final color = CategoryColorHelper.getAvailabilityColor( + atRisk, + theme.colorScheme, + theme.brightness, + ); return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( diff --git a/lib/utils/category_color_helper.dart b/lib/utils/category_color_helper.dart index a2358ff8..41e67180 100644 --- a/lib/utils/category_color_helper.dart +++ b/lib/utils/category_color_helper.dart @@ -1,38 +1,80 @@ import 'package:flutter/material.dart'; import '../models/models.dart'; -/// Helper class for getting category-specific colors +/// The app's colour system: the single source of truth for every colour used +/// as a *signal* rather than as chrome. /// -/// Provides consistent theme-aware colors for different beverage categories. -/// Colors are supplementary visual aids, not primary indicators (accessibility). +/// Three independent signals live here, deliberately kept separate so they can +/// evolve without dragging each other along: +/// +/// | Signal | Accessor | Derivation | +/// |-------------------|--------------------------|--------------------------------| +/// | Beverage category | [getAccentColor] | fixed hue, lightened for dark | +/// | Stock level | [getAvailabilityColor] | fixed pair + theme error | +/// | Personal status | [getTastedColor] | fixed pair | +/// +/// Colour is always a *supplementary* aid here — never the sole carrier of +/// meaning. Every surface that uses these also carries an icon or a text label, +/// so none of these values is subject to WCAG text-contrast minima (none has +/// text drawn on top of it; accents are 4px decorative edges). +/// +/// Do not hardcode any of these hex values at a call site. Adding one here and +/// referencing it is the whole point of this class. class CategoryColorHelper { CategoryColorHelper._(); - /// Solid accent colour for a beverage [category], used for the coloured left - /// edge of drink cards — both the list card and the similar-drinks carousel. - /// A fixed palette (independent of theme) so a category reads at a glance; - /// falls back to CBF navy for unknown categories. - static Color getAccentColor(String category) { - switch (category) { - case BeverageCategories.beer: - return const Color(0xFFF59E0B); // amber - case BeverageCategories.internationalBeer: - return const Color(0xFFEF4444); // red - case BeverageCategories.cider: - return const Color(0xFF22C55E); // green - case BeverageCategories.perry: - return const Color(0xFF84CC16); // lime - case BeverageCategories.mead: - return const Color(0xFFD97706); // honey gold - case BeverageCategories.wine: - return const Color(0xFF9333EA); // purple - case BeverageCategories.lowNo: - return const Color(0xFF06B6D4); // cyan - case BeverageCategories.appleJuice: - return const Color(0xFF65A30D); // apple green - default: - return const Color(0xFF2B3170); // CBF navy - } + /// Source hue per beverage category — also the literal light-mode accent. + /// + /// These are hand-picked to stay mutually distinguishable at a 4px width: + /// the closest pair (beer/mead) sits ~72 units apart in summed RGB distance. + /// If you add a category, check it does not collide with an existing hue — + /// `category_color_helper_test.dart` pins a minimum separation. + static const Map _categoryHues = { + BeverageCategories.beer: Color(0xFFF59E0B), // amber + BeverageCategories.internationalBeer: Color(0xFFEF4444), // red + BeverageCategories.cider: Color(0xFF22C55E), // green + BeverageCategories.perry: Color(0xFF84CC16), // lime + BeverageCategories.mead: Color(0xFFD97706), // honey gold + BeverageCategories.wine: Color(0xFF9333EA), // purple + BeverageCategories.lowNo: Color(0xFF06B6D4), // cyan + BeverageCategories.appleJuice: Color(0xFF65A30D), // apple green + }; + + /// Accent for an unrecognised category — CBF poster navy, matching + /// `appSeedColor`. + static const Color _fallbackHue = Color(0xFF2B3170); + + /// Lightness added / saturation retained when adapting a hue for a dark + /// surface. Tuned so the lifted palette keeps roughly the separation of the + /// light one rather than washing out to pastel. + static const double _darkLightnessLift = 0.18; + static const double _darkSaturationScale = 0.92; + + /// Adapt a light-mode hue for a dark surface by lifting its lightness while + /// preserving hue. + /// + /// Deliberately *not* `ColorScheme.fromSeed(...).primary`: that maps every + /// hue onto a Material tonal role, which desaturates the palette and — as + /// measured — collapses perry and apple juice onto the same colour, which + /// defeats the point of a per-category accent. + static Color _liftForDark(Color hue) { + final hsl = HSLColor.fromColor(hue); + return hsl + .withLightness((hsl.lightness + _darkLightnessLift).clamp(0.0, 1.0)) + .withSaturation((hsl.saturation * _darkSaturationScale).clamp(0.0, 1.0)) + .toColor(); + } + + /// Solid accent colour for a beverage [category], used for the 4px coloured + /// left edge shared by the drink list card, the hero panels, the My Festival + /// rows and the similar-drinks carousel. + /// + /// Derived from [brightness]: the fixed hue in light mode, a lightness-lifted + /// variant in dark mode so the edge reads against a dark surface. Falls back + /// to CBF navy for unknown categories. + static Color getAccentColor(String category, Brightness brightness) { + final hue = _categoryHues[category] ?? _fallbackHue; + return brightness == Brightness.dark ? _liftForDark(hue) : hue; } /// The most common category among [drinks] (by drink count) — used to pick @@ -59,57 +101,46 @@ class CategoryColorHelper { return dominant; } - /// The "tasted" indicator green, shared by the drink card status badge and - /// the similar-drinks carousel card. Darker in light mode for contrast, - /// lighter in dark mode. + /// Colour for a stock-level [status], shared by the drinks list availability + /// chip and the My Festival at-risk hint. + /// + /// [AvailabilityStatus.out] resolves to the theme's semantic error colour + /// rather than a fixed hex — "sold out" is the one availability state that + /// should track the app's error language, so [colorScheme] is required. + /// Every other state uses a fixed light/dark pair chosen for legibility on + /// both surfaces. + static Color getAvailabilityColor( + AvailabilityStatus status, + ColorScheme colorScheme, + Brightness brightness, + ) { + final isDark = brightness == Brightness.dark; + switch (status) { + case AvailabilityStatus.plenty: + return isDark ? const Color(0xFF4CAF50) : const Color(0xFF2E7D32); + case AvailabilityStatus.good: + return isDark ? const Color(0xFF8BC34A) : const Color(0xFF558B2F); + case AvailabilityStatus.low: + return isDark ? const Color(0xFFFF9800) : const Color(0xFFEF6C00); + case AvailabilityStatus.veryLow: + return isDark ? const Color(0xFFFF7043) : const Color(0xFFBF360C); + case AvailabilityStatus.out: + return colorScheme.error; + case AvailabilityStatus.unknown: + return isDark ? const Color(0xFF90A4AE) : const Color(0xFF546E7A); + } + } + + /// The "tasted" indicator green, shared by the drink card status badge, the + /// drink detail hero and the My Festival rows. Darker in light mode for + /// contrast, lighter in dark mode. + /// + /// Independent of [getAvailabilityColor] by design: personal status and + /// stock level are separate signals and may diverge visually later, even + /// though `plenty` happens to use the same green today. static Color getTastedColor(Brightness brightness) { return brightness == Brightness.dark ? const Color(0xFF4CAF50) : const Color(0xFF2E7D32); } - - /// Get color for a drink category - /// - /// Returns a theme-aware color based on the category name. - /// Falls back to outline color if category is not recognized. - static Color getCategoryColor(BuildContext context, String category) { - final theme = Theme.of(context); - final colorScheme = theme.colorScheme; - final brightness = theme.brightness; - final cat = category.toLowerCase(); - - if (cat.contains('beer')) { - // Amber-like color - return brightness == Brightness.dark - ? colorScheme.secondary.withValues(alpha: 0.8) - : colorScheme.secondary; - } else if (cat.contains('cider')) { - // Green-ish color - return brightness == Brightness.dark - ? const Color(0xFF8BC34A).withValues(alpha: 0.8) - : const Color(0xFF689F38); - } else if (cat.contains('perry')) { - // Lime-ish color - return brightness == Brightness.dark - ? const Color(0xFFCDDC39).withValues(alpha: 0.8) - : const Color(0xFFAFB42B); - } else if (cat.contains('mead')) { - // Yellow-ish color - return brightness == Brightness.dark - ? const Color(0xFFFFEB3B).withValues(alpha: 0.8) - : const Color(0xFFF9A825); - } else if (cat.contains('wine')) { - // Deep purple/red color - return brightness == Brightness.dark - ? const Color(0xFF9C27B0).withValues(alpha: 0.8) - : const Color(0xFF7B1FA2); - } else if (cat.contains('low') || cat.contains('no')) { - // Blue-ish color - return brightness == Brightness.dark - ? colorScheme.primary.withValues(alpha: 0.8) - : colorScheme.primary; - } - // Default fallback - return colorScheme.outline; - } } diff --git a/lib/widgets/brewery_hero_panel.dart b/lib/widgets/brewery_hero_panel.dart index 150db9c9..d03f2793 100644 --- a/lib/widgets/brewery_hero_panel.dart +++ b/lib/widgets/brewery_hero_panel.dart @@ -34,7 +34,10 @@ class BreweryHeroPanel extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final accent = CategoryColorHelper.getAccentColor(accentCategory); + final accent = CategoryColorHelper.getAccentColor( + accentCategory, + theme.brightness, + ); final hasNotes = producer.notes != null && producer.notes!.isNotEmpty; final cells = [ diff --git a/lib/widgets/drink_card.dart b/lib/widgets/drink_card.dart index 2d415fbf..03d949b0 100644 --- a/lib/widgets/drink_card.dart +++ b/lib/widgets/drink_card.dart @@ -30,7 +30,10 @@ class DrinkCard extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); final colorScheme = theme.colorScheme; - final accent = CategoryColorHelper.getAccentColor(drink.category); + final accent = CategoryColorHelper.getAccentColor( + drink.category, + theme.brightness, + ); final excerpt = searchQuery.trim().isEmpty ? null : const SearchMatchService().hiddenFieldExcerpt(drink, searchQuery); @@ -266,40 +269,37 @@ class _AvailabilityChip extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final isDark = theme.brightness == Brightness.dark; - Color color; + final color = CategoryColorHelper.getAvailabilityColor( + status, + theme.colorScheme, + theme.brightness, + ); String label; IconData icon; switch (status) { case AvailabilityStatus.plenty: - color = isDark ? const Color(0xFF4CAF50) : const Color(0xFF2E7D32); label = 'Available'; icon = Icons.check_circle; break; case AvailabilityStatus.good: - color = isDark ? const Color(0xFF8BC34A) : const Color(0xFF558B2F); label = 'Some Left'; icon = Icons.check_circle_outline; break; case AvailabilityStatus.low: - color = isDark ? const Color(0xFFFF9800) : const Color(0xFFEF6C00); label = 'Low'; icon = Icons.warning; break; case AvailabilityStatus.veryLow: - color = isDark ? const Color(0xFFFF7043) : const Color(0xFFBF360C); label = 'Nearly Gone'; icon = Icons.warning_amber; break; case AvailabilityStatus.out: - color = theme.colorScheme.error; label = 'Sold Out'; icon = Icons.cancel; break; case AvailabilityStatus.unknown: - color = isDark ? const Color(0xFF90A4AE) : const Color(0xFF546E7A); label = rawText ?? 'Unknown'; icon = Icons.info_outline; break; diff --git a/lib/widgets/drink_hero_panel.dart b/lib/widgets/drink_hero_panel.dart index 1d05e757..6eef7c4e 100644 --- a/lib/widgets/drink_hero_panel.dart +++ b/lib/widgets/drink_hero_panel.dart @@ -37,7 +37,10 @@ class DrinkHeroPanel extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final accent = CategoryColorHelper.getAccentColor(drink.category); + final accent = CategoryColorHelper.getAccentColor( + drink.category, + theme.brightness, + ); return Card( margin: const EdgeInsets.fromLTRB(16, 12, 16, 4), diff --git a/lib/widgets/style_hero_panel.dart b/lib/widgets/style_hero_panel.dart index 44a2bc48..fe43bdff 100644 --- a/lib/widgets/style_hero_panel.dart +++ b/lib/widgets/style_hero_panel.dart @@ -38,7 +38,10 @@ class StyleHeroPanel extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final accent = CategoryColorHelper.getAccentColor(category); + final accent = CategoryColorHelper.getAccentColor( + category, + theme.brightness, + ); final hasDescription = description != null && description!.isNotEmpty; final cells = [ diff --git a/test/category_color_helper_test.dart b/test/category_color_helper_test.dart index eaef4094..477f065d 100644 --- a/test/category_color_helper_test.dart +++ b/test/category_color_helper_test.dart @@ -1,96 +1,217 @@ -import 'package:cambridge_beer_festival/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:cambridge_beer_festival/models/models.dart'; +import 'package:cambridge_beer_festival/utils/utils.dart'; + +/// Summed per-channel RGB distance between two colours, in 0-255 units. +/// A crude but stable proxy for "can a user tell these apart at 4px wide". +int _distance(Color a, Color b) { + return (((a.r - b.r).abs() + (a.g - b.g).abs() + (a.b - b.b).abs()) * 255) + .round(); +} + +const _categories = [ + BeverageCategories.beer, + BeverageCategories.internationalBeer, + BeverageCategories.cider, + BeverageCategories.perry, + BeverageCategories.mead, + BeverageCategories.wine, + BeverageCategories.lowNo, + BeverageCategories.appleJuice, +]; void main() { - group('CategoryColorHelper', () { - /// Pumps a [Builder] under [brightness] and captures the colour the helper - /// returns for [category] alongside the active colour scheme. - Future<(Color result, ColorScheme scheme)> resolve( - WidgetTester tester, - Brightness brightness, - String category, - ) async { - late Color result; - late ColorScheme scheme; - await tester.pumpWidget( - MaterialApp( - theme: ThemeData(brightness: brightness), - home: Builder( - builder: (context) { - scheme = Theme.of(context).colorScheme; - result = CategoryColorHelper.getCategoryColor(context, category); - return const SizedBox(); - }, - ), + group('getAccentColor', () { + test('light mode returns the fixed category hue', () { + // Pinned so a derivation change cannot silently restyle light mode — + // these are the values the committed light goldens were rendered from. + expect( + CategoryColorHelper.getAccentColor( + BeverageCategories.beer, + Brightness.light, ), + const Color(0xFFF59E0B), ); - await tester.pumpAndSettle(); - return (result, scheme); - } + expect( + CategoryColorHelper.getAccentColor( + BeverageCategories.wine, + Brightness.light, + ), + const Color(0xFF9333EA), + ); + }); - testWidgets('beer categories use the secondary colour', (tester) async { - final (result, scheme) = await resolve(tester, Brightness.light, 'beer'); - expect(result, scheme.secondary); + test('dark mode lightens every category relative to light mode', () { + for (final category in _categories) { + final light = CategoryColorHelper.getAccentColor( + category, + Brightness.light, + ); + final dark = CategoryColorHelper.getAccentColor( + category, + Brightness.dark, + ); + expect( + HSLColor.fromColor(dark).lightness, + greaterThan(HSLColor.fromColor(light).lightness), + reason: '$category should be lifted for a dark surface', + ); + } }); - testWidgets('international-beer still matches the beer branch', ( - tester, - ) async { - final (result, scheme) = await resolve( - tester, - Brightness.light, - 'international-beer', - ); - expect(result, scheme.secondary); + test('dark mode preserves hue, so a category stays recognisable', () { + for (final category in _categories) { + final lightHue = HSLColor.fromColor( + CategoryColorHelper.getAccentColor(category, Brightness.light), + ).hue; + final darkHue = HSLColor.fromColor( + CategoryColorHelper.getAccentColor(category, Brightness.dark), + ).hue; + expect( + (lightHue - darkHue).abs(), + lessThan(1.0), + reason: '$category changed hue between light and dark', + ); + } }); - testWidgets('cider has a dedicated colour per theme', (tester) async { - final (light, _) = await resolve(tester, Brightness.light, 'cider'); - expect(light, const Color(0xFF689F38)); + // The regression this whole design exists to prevent: a derivation that + // maps distinct categories onto the same colour. ColorScheme.fromSeed(...) + // .primary was rejected precisely because it collapsed perry and apple + // juice to a distance of 2. + for (final brightness in Brightness.values) { + test('all categories stay mutually distinguishable in $brightness', () { + for (var i = 0; i < _categories.length; i++) { + for (var j = i + 1; j < _categories.length; j++) { + final a = CategoryColorHelper.getAccentColor( + _categories[i], + brightness, + ); + final b = CategoryColorHelper.getAccentColor( + _categories[j], + brightness, + ); + expect( + _distance(a, b), + greaterThan(50), + reason: + '${_categories[i]} and ${_categories[j]} are too close to ' + 'tell apart in $brightness', + ); + } + } + }); + } - final (dark, _) = await resolve(tester, Brightness.dark, 'cider'); - expect(dark, const Color(0xFF8BC34A).withValues(alpha: 0.8)); + test('unknown category falls back to CBF navy, adapted per brightness', () { + const navy = Color(0xFF2B3170); + expect( + CategoryColorHelper.getAccentColor('not-a-drink', Brightness.light), + navy, + ); + expect( + CategoryColorHelper.getAccentColor('not-a-drink', Brightness.dark), + isNot(navy), + ); }); - testWidgets('perry has a dedicated colour', (tester) async { - final (result, _) = await resolve(tester, Brightness.light, 'perry'); - expect(result, const Color(0xFFAFB42B)); + test('is pure — repeated calls agree', () { + for (final category in _categories) { + for (final brightness in Brightness.values) { + expect( + CategoryColorHelper.getAccentColor(category, brightness), + CategoryColorHelper.getAccentColor(category, brightness), + ); + } + } }); + }); - testWidgets('mead has a dedicated colour', (tester) async { - final (result, _) = await resolve(tester, Brightness.light, 'mead'); - expect(result, const Color(0xFFF9A825)); - }); + group('getAvailabilityColor', () { + final lightScheme = ColorScheme.fromSeed( + seedColor: const Color(0xFF2B3170), + ); + final darkScheme = ColorScheme.fromSeed( + seedColor: const Color(0xFF2B3170), + brightness: Brightness.dark, + ); - testWidgets('wine has a dedicated colour', (tester) async { - final (result, _) = await resolve(tester, Brightness.light, 'wine'); - expect(result, const Color(0xFF7B1FA2)); + test('returns the pinned pair for each fixed status', () { + // These are the exact values previously hardcoded in _AvailabilityChip; + // pinned so the consolidation stays a pure move, not a restyle. + const expected = { + AvailabilityStatus.plenty: (Color(0xFF2E7D32), Color(0xFF4CAF50)), + AvailabilityStatus.good: (Color(0xFF558B2F), Color(0xFF8BC34A)), + AvailabilityStatus.low: (Color(0xFFEF6C00), Color(0xFFFF9800)), + AvailabilityStatus.veryLow: (Color(0xFFBF360C), Color(0xFFFF7043)), + AvailabilityStatus.unknown: (Color(0xFF546E7A), Color(0xFF90A4AE)), + }; + for (final entry in expected.entries) { + expect( + CategoryColorHelper.getAvailabilityColor( + entry.key, + lightScheme, + Brightness.light, + ), + entry.value.$1, + reason: '${entry.key} light', + ); + expect( + CategoryColorHelper.getAvailabilityColor( + entry.key, + darkScheme, + Brightness.dark, + ), + entry.value.$2, + reason: '${entry.key} dark', + ); + } }); - testWidgets('low-no categories use the primary colour', (tester) async { - final (result, scheme) = await resolve( - tester, - Brightness.light, - 'low-no', + test('sold out tracks the theme error colour, not a fixed hex', () { + expect( + CategoryColorHelper.getAvailabilityColor( + AvailabilityStatus.out, + lightScheme, + Brightness.light, + ), + lightScheme.error, + ); + expect( + CategoryColorHelper.getAvailabilityColor( + AvailabilityStatus.out, + darkScheme, + Brightness.dark, + ), + darkScheme.error, ); - expect(result, scheme.primary); }); - testWidgets('matching is case-insensitive', (tester) async { - final (result, scheme) = await resolve(tester, Brightness.light, 'BEER'); - expect(result, scheme.secondary); + test('covers every AvailabilityStatus', () { + for (final status in AvailabilityStatus.values) { + expect( + () => CategoryColorHelper.getAvailabilityColor( + status, + lightScheme, + Brightness.light, + ), + returnsNormally, + ); + } }); + }); - testWidgets('unknown categories fall back to the outline colour', ( - tester, - ) async { - final (result, scheme) = await resolve( - tester, - Brightness.light, - 'spirits', + group('getTastedColor', () { + test('differs by brightness', () { + expect( + CategoryColorHelper.getTastedColor(Brightness.light), + const Color(0xFF2E7D32), + ); + expect( + CategoryColorHelper.getTastedColor(Brightness.dark), + const Color(0xFF4CAF50), ); - expect(result, scheme.outline); }); }); } diff --git a/test/goldens/brewery_screen_dark.png b/test/goldens/brewery_screen_dark.png index a5f822d3c90d372534238a6fa0e8271953316542..6e4261755aef9b096ae61c1c458d171ac8a23eb8 100644 GIT binary patch literal 15394 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 UEhLg10zWse>s&)#x%cS*1KC{oV*mgE literal 15352 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 m>XB^`252DS1cQ~4+(CnE$w zM^#mnbRdW-3W8|(80ZeJ%!z}4AX>K@s(K9I$DiTX=iq-THy!005T>1b7J~R7Ri*2C z-tQOiKJKRE0K#6!$e{HnmcvNCYK@U|-8w3--=1wWc&P5Am2|=Gn3RTYi{oVGunRWR z72mU-yxyBXd|b9@JN@2!h5YyL6)+D?GV2dELA^~Yz7T|LSAs*(1zK9@{*C|W zlH8u zQB@oC5eRufq&$Vpo1rYTyIX79QdOJkf^jWx(pAoVNZxwKaxsRpbANBgW9^4VNbQ3K zLY0={@}igRg9q2AJAzCQyAyHxB8Kr|yjFZAT{Z(w?IXRIakGsn=h0pa_|r36=S5|SaN~RCT`@w2?la3gGTzH6<^iOl%vT2n3-un8U+SM9$u-~GGwj3lWbN-O zw6(ToS5;}9O)GQ9nR(gz_}p`IbCdDfcz+``ig7D=qUlRRLqnn0l6hN)@XSQLx`q$p z=|NKhG3p16pSyUH5oPRgBC^qAqE24z=#iO0>*T%zqs?z#iFtXRL?T^G)D+^4w70WU z6Wv>i(-ZjK7js82u}NHxmzo;lnHUzWOfOqf!1ycVm6jfA>*ydRF3F_@>}-6@$x&bO zZj{wS>ABBNoYxmTk#W_w&rSku+3&u&{2=7!1H2a@z}Tc^4waR3S2DrTMh+}InYyKb zl@{#kHqm#zD=72byL<9`>jg3{6TCb!{@UtH>YOJ}c8-j^kKmU7P*ZcgZRCAdbMwpK zniNmwMZL=>d)LFBom@c3JB=ziHpJwZ-bRpx8_*`YG$y!|1>4pQ^Ibm~=dV%PV& zbl1W6Y*DMF=O5L`H468C7M6&34y(2x3vb<~j zdioMH!`CC_G*ghg=Xi*k3hyy5Qn9)!L`eDfVTk#}PT1sKEHIDg~5Md3hJX zB26ybQ?AzzW+bFQ^xI)+*BnguY#~@wljPkmi&9^_7?8 zK7{i6s~pGiyKk*pnpFjwMNHZRI+?F{o0ghtKDqfs#$)?!QsB0&S*gE%C^M}0YKK2) zrm1`{lkTbPVQRRaWx=(?pp|lVc6RsG@2VB#fTg~#UxS~8J}bgvvz5eBr!p+jnB2L@ zr2Vt#i||<$;yiV#>%cCTczZxY z$6h>jxIR(}T8#}qWgT?FLXQyAaQ%1QJalG>h2#FGX;F%asm5ZzlGutF0)-&Mq?64&r zHDpqqdXt`1!?N#@=resP!?4G9w{p5j!B?JItsBed~@Nxrl~#tNUzK0PQ-DC!42bM zQFcWi93<21igJxEr6wf>hTM2?=*jg%)OUQLloNImhQKm0YWKGM4RUhA7(>3Qo%3Zo zBB^*zgGEIo?ry%3wwG7(nJ+oA#!-x-WrEe#b^*vMsVQ<|;Ud=#CPihQefXm2y%ic6*l zt!T{ahIl~7*$3ddI3_|taoge~V zPTqw1D>X*KoCmiX8ye23Mn^~I1~Fj}Z$wPLq;v%Auf(EZNG%%I-AxzyEG#ciHxwlZk0f#O;^+8DO>saTGm!K)fR}qVSySol1ggAj1wfN-Zo|ze=;o^Iuz>XzRD(3){ zEBx}MbHhVwfGF&ZkQ=G!|LB;+f$$O`RAJFyBY zO_7AvE3kld3z>kq*35oG!I-xy=cFq3JRgDpQo?%V*>`L>Ubg&~KD%4vj|K`B2#i!z z&~L1)5c2R}pDbU$0oi=$$!ec@A?n{md{B4@^aFVq{IUriHe+)UI%xP{tvq4^u9_O< zMQ;y)m|)Cj>*1;s2gBDEU}Fobmmdzi#BP|{xjj5%ytyhL#l#^}=)PxR;XHE2)z$S4 z$+?D732 z+ZT+UUFe4{Fgsk~4dR!hy6fsZ`s|8|9ZPsYZgxsDx@6IC-5$lk4 z?_gDSudcoULm8^@yHioI2aumYa>vEEmTVo=Z~dp3Lr)xd1b;6ck5Vh#ZHDGQzn__&xS#dN=J}Fbf*FI4oBKgrCoL`7N3}B8*#nxW6 z@)ykiyPN-`c;d3{df&K+-gwx-a@*XcQvliO2T zd!KQ&e0J9blsBr%34EJMs4s z&i&h}v3Qf-)TG+KRI}apZ2BaexFyDyyt6xL&O!w};^O{F{Z}h`bM;d0e_NFxm{sFi z%n6BtSN~)sxN;s0DnCff|I8!)g|qzW)(g=kdg_z_^MD2Y6Jdw`iMh;Ku7iLZxTRY) zPh#y%Wne%?Y;-9rW&($En9H~B2vASn{N6|$Z8(nD?9PPkJWk%dBERtt(YM^WoXJbs zBKVjpM{wJ2DHKvKJ^o8e`uni=hcIy%WB2p(uc&r^|Ng5o2I07QimG}6tdM_ZHd-Cj zc)Z7Wef=rRAfc_|Jf8iK8mm`KQ0K#*o=LR~Nw20D(q)jK{#EEWu*M5f(M$nV%6HhF zMUSgr!-ma6*AI>@3%rBqS*{)rQGV5FbLlK+Ir4kkdk!q5kuVeI(MT>E4g%fxto=Ey zcdZc+EBfEg(n`eml@?65&z+?#PtTcScz+LfcknHV@?_zFiD;OV zW5KB6XXkhC61$eQ_maOR#>aQh%-G;tBtAzXVOx@X(0=QJ6?^}4>hLl7e;)gOPG>R= zl?;@;hJ;-?7%mvI|0{cwnht-is2f)Y~%Nx;Ig=n+W^Q`2@H$k@Tx*A%6fd*z|AXJvm3W=wK6Q%Z%K(k3OF7-~k*96ypjv5RVMxFcl66v}bQO}xz5)a3 z-f|d#+oq;&<%I?_|A#KKx$*Gj!fF9*e;A;kY`@alP!?i$hPsDniM2!Ig$w+1=9+I5 zNJ7by!X=6RV~s&KEG#U*O}}4DGM(=v#76&WyfMNwy&gWDl;c;b$xh(Wb-!#(c76T$ zxte-Lu`tIgdi^#-B534up0&QF;hh%Gct==NXwW(U5v2u-QvfYB9p@*SV@ySe`#Vc{ ze%C5aMK$i%TDx!e&T7c-SMCsOhcx;92s^yJ;K6d0{!O!`I->pBbbuOmLVpCG+5 zAP<6`f)ZS&Re68B1I!c@1sXId-6rD7d8z#!hUx3jAu&Qqz)yZ)8Kp^8A$lljK|>-X zUse;nXbGjn6bHcH!|%N?+A}1&n;Ax&=9_Z8H~z_l>K()wCU-3>pcXEU20VF-%Fze}xRb?Q~kvOgj)eM!MX2 z&Y$@&*~n*f$5-t1qMx{oOfjkvyU3M{ADFjRJoEawT1H!9i84f*02nXT!) zP@qa_$QvjOcc4(J0n-_zC1_S^ScZ>9VKXp*BD>_=gyo212XxDEd!?tR2c&9pCT|&F zl%u1g*oFdqYS)Q6)jf38*0_dZpohn;ii&^)UkCY)$Hu$dT3T8FE+_7;Vfgt1hdQPy zI4Ri+eE%f>{3$p50k)coE8jL6ucBNx`~A2^;MErumy+Vqx+KJBJN2aS@J zdjd5zH2NjjMIDJq|LKe+_f`q(~NI_1Op;Vld;sqwf zMY5V?7KM^9JvT(xLB3FOcRJ7{iOE5p=Xlg`+a*a^-K`Lo-3)ATf>7++=eH&IHZzKH z>9B7+9MeWE=Xjz+U}Qgkr_@D7V>tqP@Yz&pb$_kjpV-VFpyqEU(GgqRDiz5p^iU!D z0lfg#;J5pcd4V$*jCFM~m3#3z)n-dE z({tDoOX@+xR9mxNgsiH~fX2Du+ljm0h$9D2FaccqFZJo+MK`{{^?;p*xoza+3>=}} zNCPEYhx?D2DYMKeYvK!L8>e4p#759Vl22|L{m$qHJwrczQLMXAGG4p)gO?3iC-2X(Szh=Qy(^GjYgsa&{~*X#jimI)^sOi73HJhgj7 zH!(A18xc$e&qC=nyS~4FlV~%O7bL*eB_)DAdK7{>0mIi!)&7#|AH57j%8AA3SGM&O0V@ao}H&(EEY#&2&Cn%pGWA$KG| z_AxQH&PU51&C3}_KE3ganHj_EG)2AE0+8l`=H}otuWf1zSR{xKUpD<38Ms3X8!9_H z`je>hC7K(c^z@vavo8{v(sgQ!ta873m|2Ro0EL0`yx2#eam07 zCM8YfTqZaGb-jQ2TPrPOoNnNzk2cT;pBLfKzw|>NjeXe|{Zi?J2EkmNdNmfvAv&m! zuB8GvbTg3d?1YE8AqV@%aCpSzH&@^BqMmnyY!Yy>+It_QteU%k&aD0wQpn|-S zld6AD*#B7Bu}LBIw&hT#0)=c_-er?Jo^TtlrcSMcHh9}+#m3MpE`!yQ^2V_?uQD?+K#CH0J!9K6eLa5v`d3qtG+_R-$`Ltv&hSBp+kkk~ zKfF8Ts)}jhIYO0kg1hmqH1}O`PJOY#j9)*i3kj(MO*t57C@+)@bxUyTVHcT?hR-aI zTNoSt*3;en@T$R4=(1Xdr$EdQDvU$aCNS_e9v`4lmaeDE`~c|ZU}~_F5otiK>md;* z1=*R}j3l5Gs<1GoW-FZg`iu)$89?-2|8eUCSJmPBx?+XJ#o1k56`VZyw3QX1IvU77 zU0Yygt`8?6S*%s&wWKduHuRPx$sMJGO2J^9coh8a;PMc^ zYTYwk89JzeWf?AJ6_Jw6C-jkafq@2Fa%6}Ks%oBcJs3dE6XlqWJc;tkXVf~Vwb+1)EYLDd#K{IS2H_Trs=>XE2N)kUA zl?}{YWNiEM6_f#Ca~MBk!g4eqY8lOR*7dQHdp3|vfI@%%m>FYeEn`S9Bb|wF_}wu_ zFvURh0y0Tzl%Zro-;in$koQdv(>(!mrGJV2VD*5kT+Vx} zxD>1yph@eX)_+Wk|2V-IA|WlE4(vJeqs^9(^ic`^$?++uSmDbMLP9O^@5>iSkB~?8 z1ugMku4Jg3%g)cQc&T*8*wl2{*NE^W!R@}u^V?}dB}>e0ts`SM^JY$haZFAc0f(xu`!}b!ke)!0jE=^EIeM$MuAb(%_lHCS3)&bdS%0AwI3j)Hz8gTJy}eI27GzUe;t*@~gZGl?zV}vSt?LGnvW6=0 zeoGaAI{+2@mM)xkzbmxJZMq#B_d7=dAlGrf^yATkz>Ml;uGrs>iFKOx)J*zfn3k3% zjK3|;X}=ZVotc^GzF*HVXZ%As==(U9*Lg59Z+Ac1Lw+=`qtR*u2p|H{s`mH!rozap z(o9Sy2F}jOprF%c$X$VNeOPcch92gxVy#KA=B%|678T8@s;bHu-s~S#M9cEQe7`&Q zle}93d%0Mv6krMX$kNhN3xuFf4yti^R)>4?@)baUTlYN)*9Fxv05t0-UGhS2z&}ZV z8jX#3GmlvQd7uZ9*4KUIk5C7rFS3W_7&+|5CL&=#DXgD7oELf>JY3;YB7w0DL8Ej~ zK<)MWuSeO=@C{%`8J?4;;lHpts6IyG&O*khOe9NS(_x0o$3q_f|tpmX-BjXV8>)6~XUNG^1 z*R-~Hc2A86C+#*zo9w$TqCw(1keq62PHQQ59kw%v?fw!Yj z+lWc@m@T|=WyEc}!L7sR8ZBWvykkETcXQwvJNu6!X95-If4_$oYqc|XW7o_KuZD&h zxgHcR{P+gu?26(qT)@~qdSsM;(%LfBEj(heYjsrz&mU5F47imc+CTz?M<9`DgdU<2 z1v3Y{6Ub6Oz4NC}3A2UWGArA_J_}VYv{2sj@}(_jeWBzrR(%~zE-HK~34)GI9P9&DrM#pP=1${FTcYSY8Scnet=94-IqulzIthkxpZjoY zH~juA04?(}bSedw!&E9bUKNgT+m4pZ<59F5}NXhQ$ zafraCo&`T%*1RAHg1G*$+Ee6TN@@S7o5)oG;l)Af_h|mgcCcW&GDFLMby5Qs{fAJ# z+({p}+9*l-a$w%r$PDw!!{1PTT115ZI-C#gt#g?vX#kC>cR%9PL%wWLJa4 z!pNX$Yf926r4s=(CZLuL$$u_eoEm5}DRZ$fKKsmZzpF=URw{4}H)q}+M$jiJXliQO zIXL7lPI3D1QW^-p>5EgvX9(P1Wqel6dt8NbaxxM94xVWAigxJ2@sj!B9^?S=0-s&e`Rj?g&f*6ir`B8h&S{ew=B;B_NR<=ecA78g;6VOukIG)Il>*A2zu9RAO~jca$=4 za_7sH--lqC1p7MomcxyIuV#ojRJ6EO!6}rWKT4T#NNR`6%+`;n27M~7{W-GXaW4oaE1~od!rsDqPkE5gJ;G*8S?t!i}I%~cM zAU1+yVc^O@CvZx1iQ9A(sCWpnxsJBO&s}^s547iLiOW$spyuW70U_B{&%K#jf_18k zR;2v;IcuB*%AwiZWuB-8xS^RHR#HZ`Bo?${r)1{c4J688@-aISg zzxf+G$8-^>z`fF^fnMpL5&_!)dxMzb6UtDa?0ks2ZEM-9hx&{vsEEF6eF+4p2$&)Y zB#1j!`v1*t?8`VWKx6&EH7Q@mKb?8~@lBHm!1e+OW<6kx`XX^)4-RfQtAkRWN%vj& zzbGKUVHrA40E5&Q=hT(v9{p*3;0jcM04PnUtca!kE(}U@ z0L@Oh-o=>25V6^P#>Lm5fqv@8Nbbhhle8z8l~25=Gu}Q`5o=y2c=E_aUY3izc@(Rw z5EL}&eeD0A3jA-!z<-R2|8wQvOdtPasQ)q4|4Z5ChB*rq?qX&bqoxXq?*JJ83>mKY zY}`$n$`+Skz};)^YeyW{6JRp{k_=xpDJlm1g>5o0juRNlR+w_67b?K64)mah2Hn>g zROmrj9fbSlX`Eh8UpX&x=P@9O)jzxrB#L~aZsPd;;|$L``s~0Cv8knZ+cYM3(LVr- z`e=0NV^&u3;PZ}^`OV*mSYtD@GrR+yVBdg&VI0_`gS%NHe3@qDA`V@-MUCAX)ax>e z^kg1I?e|jMI)i6ao?r|;?s$s-)2S&}%_$x844vC8VTKZl%ws;10l66j=r9& ze2s^T9difD;erya*FBO^eRkkOJ1mukD4x(E8IhC|?9-yQyQDn9WRs!g zE3Z}b@c2DP9uB8Sd=z2C|tR>`qJZl@*Hx{)8>P$)k zYwj5-ylJtN+#SW>`Q67lcb?N1=fe3?XhL~=!4*mWRvC^wmZnOxrK>ygDSlAEVK2*!{Q{68P%+OwXLzRn}FUF)C1IDDILZ01`&EM_t{ z=%ZkQCHn-&uKwy{wT$xd&_iC4iFd{Odk)%qS?^oqH@}W0_)RwVm@4(MFg#~*aGw$6 z*0gaT>^Z_%#^^2lsu1JfwqehONu2q<5}?3B@k0X)(1p^3XC@<6*(1f&4}C7o9vZ!}CT-MwN44 z&vQYlFO_grrM5J1;e%(WYWyi}A11>t#O0Lc41g)&W`t!>c zZM@}0t5&grvxK1Hix$+9_Gvh3=MR372lauUxl;ORQCqLy$?SWfRgi=HPm0nC?>yH3 zuI1~*OV9M}IdhR@Q9+QaTZ`)JPh=b!wy!&@xF)L660l8>x8uwqSz82%NDn^Tx&PN3 z?KI#2F7J*<&kVPMu60gUk(?`d>5#(6zwgb_M-OBnG#vCnDQ9bcMUj$@y9tdx`=bi* zcMbA_U1-F>3S;4-fJ;c2GFO&HZ(iih6=({E<68PgK<0amNT!9GsD0eUVFv zzwY-7D{Nq<{<7cN`BJBhg-NR2Y7e3K(yc^~s*)~6GaJ#LZ~ltUi&MLY|4j~e@a;zy ztM8jLMz916x(OEAI(lyYFXKUa(O}XSuiU>clwy%wM-|5lB`LWr+J*XpvEwh4 za9Bj1-b?K691JJ{!;Q4w9}Ifx4FeHk?af0}E3d+wjP$@O~GGiNbU(oaY*he z6tDyx2E_+%u9esRff}VFL(d3W3)1Onjl8kPkCb?r!iDsOZkhQ{k)_`I%(QveO*R!r zFqPbf`wK+hyNl8rSl-F^PTqj|=XuV{b@#hap)X7Ad_D+aj9W1vw7jST+lp}gF5?@( zDS?Kb(F4TWW)Rz)3A_vP%e-1Tx-CC zJf1BJH#E%q(DmEr!B^S(nD7v^5J0%^<_DKo6_z=uyH9@||G2&V;UL{i_3P!7nzJ0X z;cRqa1A&x%vZ74$23+ROrC4pS<+Icj?7|0!c6iJC6dLd~fY-tCbzC4cM~0PZ77mIk zuT|#T)RWi2M(}K%i$LQ0xnni1?9^97ZhisK>Jd6Zxwp+z_0P^!w7)WW6RV^l8~*mM jf4RHpPinAq4QV(&(l9eFcX)^c$_f+^I09)dD8=PIR6M7^^icFGI!Od5kw`8it%9&B%pJK8ZtQ_ViD=G?b52 zHpreABO@bS`{!gJ%Zh^s4{mtUyFJ|~(~Z7c-)_@J#;6!m>6FvDRhBm~Yf=oIqW&zu z#V^@&b1qkNZ!LYPvJ0g&YZPs72MVk2CWwS=9Zl{^j{NlWWPHpw!P>c0m&2TgN7@Yy z-2%R-t0#68R#a5nrDA7Z&(AGat&EX~_-F1@-b%jy{^{+ExfHr-=vJzREP_GQ*!h9* zkal=xH6-)hHC2|7ZMefmj{@g5nse_);7X9Eaf9jJGW17iW&5kM@Ulg0tZPtiFO4?e zh>3~u){66JAoHDRougVnvcZ< zU!lQi0^iNzKYc1acyI@G*U6H)xrFQMOZfa!DVLNPWdB@8J7VB57lAc4F3xqN);&$- zQbq3dG1rgO6MHAGU1Zx=Zr6B9cS1+IeBtbKo!K|xZbq;e&1+HGT8W8?u;~E{Q;E{zf<~LvWZtoLU?4qb>|v|Cc5re?S$X;Vr_{ji?(Vq{ zf))79C7S^pF1Mr{t(UBK)l(RL1N=%&{;7DN__dUkUC8!y^tBxHM5JI+_spZ&9%ART zcgxHbO(;u$x9+KEZ&lsc;6BdX8{MBi%~!P}d&2kOoq>{T6fuRZt(P;De69s;F87{J zvCT=HJ9b`1MoNgo4R<=7hvTW7oLqiuD-(Hh^T-ismIr(*ktkj>-FJSt7M{M0UdUB` zI38AxnSklPOfynXNfC09!xR@}!mLx`Y>SsiJx))umN}W%ytE&D|Ne~y5p zY$tSgmOJrsJVn*iGiu~iNij~sJXcrM&Blp2`j-C z4Z-#+p$w|5YR!-;zrk%LO~F)UxV6LXWXhCc+@@WFVFhLrt!1Ed^5n_kDxarX+3^Vk z-N5pKen&kO|KGeRn*V|mpBs68Vs$B$E zRYe?zZ%eDuxBN{dlRr^Coi#0Wk3fUw(kb`}eonWL5PKF*k$x7>?W`FRNvVIj}>%D};|ERbf*BBA=a=uS_!z;{AM z=lF1~KeEUL-7^<75tIuFw)J86NkgU$GJ@w*Bg+n)Xo=#0mOK}~TrjgW+_Sft8}bXu z7WTbU@AK>f=m)0Pl-y?tStMjocJ}I#f=Vch)=*wj5j>6MWm7_v2W?Ev%o>6XidvI| z+Iu?^#eFBIwBJ#>jvj(a_ci*j^r=z4mMwTT*{43b#tEO38t%bdRv7cHHb5%o-#ml+ z245371ig83@(2{h%EScy3_R}t+l6*ogBQN6&{VS6R=RWDHV|3hiW>f8$E?}zEp$EG zNGIFKve!8UjhG4Z_E?qRXyVW;#HsA`9$-_5QAu&xM@jO%4Y9Qmq)cnY1enPy(&I9?f#*iI0 z+Ke4luu2KiXy1`DY0ZdtMnZrnvA|Z#?1tk#`>#gPZt@V zr4GkcgeB=6AZ)j)O(vqD9(y3q3eK_xp}E`?SWv+VL4)=udQalrCyqWN1rFCWkKy-2 zkeiG^Y=^I%!^pQ|M=b1awX8EiOCP+10+A#)!4`X3$w2X)y(kw)R4g{P`3{sVq~EWH z5?_@^d5{sy4Hi_2!b;z_+}zxRyr`(C+OI9Agq6It8Pi>ya_&^<;f3(OeWcTiS!lR%h${>mft(Ie9Y*$WqXUyR@dRImMA zy3p;lD`iM#X#B+gmPCn~(h{e2ZEAn2fw4hHbP5a&Yhi50@%y-vSPr2Cztbr)`sn8C ziyAPw9Lp8;1nBL>3qET#-kReec|kW1?fGjM^v5qwRq~rWxG&ncl37?#P_XPZ^uRoH zt&P#2q*Nv@+Pr>JKv=je=xe@>^g#dIS)8J-F9H>pm`8F5upgtQV~T=o<7po>82qoi z8vU0*#;B4Ltn+L?fBI?g zkqve|sr%jl9&dqLdY4^ki!BS3F4=D4>Tv4zd7fufSo^vC&V^*X{Ioh+*_E-$}0Ly{`LwqpR+WpvQ&OR27!C)#(bOnh-zM_Sc z#9=aEbm@U9s*;jYc8Wt_!%Rbs@%8;vgEpah8eT+wxv>yeSG^Q_n2Ua$egST{^7fVP z5p}OSwj*^bh1;8COG{fL*YC~Nmwx1mM}$oAr*8%n;JSt@voWtm4D|H}c;m%r_PVZm znjAukg%0$5s%s0p8R1`sc=|WY7`c$L@;a5WKDVMVAkQ?x!}ll5PRfymSDBb+ye z{p4TN9Dr)}|LFv^f77+;?|$~Z&M7W|@0`i!j4HGrW9t08cQ%|#3d9a5iCqo^&P4if z_S|1he7cSmAG)T$GL3(I?2TG`bMTY|=MiZ9>tBl&iEr$0t}?b#Ii+}t%RK*KMI#(f z-+HUnP1Ke7OCw2hHcwlu)@$qj=Ghy*kHxJ;je;984t~aZ=A-vOsjHJQPZ!X?h=w|df6F{<<+#rn zi%L{(o14QatHIF&IA^p$L#AdU@lRP2bI{qu>zK`Fk@B1aYRlO&8hI?3Z`OHfC@ra0HWvYP;#O}ywBd6RQdnoKodYXWRj9K0EI#LSHX?h1B5S!81iG=^s!y>*1x>_5mtt2u}F+akZ@oEM9GyTa+P3 z2*Nh|A{LTJ7bNzc`-e)k{hR5tZthM_?>EldVa9?Fg!#3B%J5(Ehg>gH)|IL=%23wn z-zFAjs{RJbUgj&-dbp0@6+|HR-=kD<52oI0{`LWZ8oge_0xIP{XdJXN%8nEF-Gd-1L8IJzPo+3HLf*kI+2nwf`>zWUPw=0iKP-O>;vg;hD&Ej|XI) zu-do3uijIYnQ!mtZ?OMFR7a*{Qk`j24&uAzYpilF*{_TJ)w%*i;Mf$o-d^b~yQ>A0 zzp_=W|GRyzFNU{$-zwuu`9s3tz!ogTRS{S<#!L0ph0M|*p+UaC{`K5@c%wCo%(+j_ z(;d{%sjkHzFWfe+;V*}){KPsTEBPOb4 z@Y=O&H^(G0Q!`vTgP|{54Hx&%zx$P~yw}Z-EFNrSXTM|}xJDPvKn5bka%N!x7Bxya-~V51VVufoG9lmJn#N~2Ap$z$2kBmxxR}u zy;4isgGjDD__(fGR(kF%SCs3+hlxT@Wg7Nr9s&J3A3w>oC}{w%&{K&gMK*Un)jCcl zAMW(E>;7y2dLji^90cWR9j_P~Itw6>mX_8?Iu`yePBX-^$h2qP)qdikPL-uCSODCe zc=+>4Zpb;obs%v8U2G~TnGz$(XEBXNaz%Nx-LX472H*tm7YpX5lDuZp{L_Kus#p`$ za1DV;^SaN!fDd=7>fhcD9h1kzgN9aw<9d>gc9PB`1s??{jBh-c>t8eJZ~gIq;m(N= zg>P#UDe%}#Wa{9zo=W)cMor|?CE}&jpLTY3tZNw$q)U*?B2@bQSL92g^keG*y2=~f zS6Yxi+DaEJFMF&9U=S0Rui;0`{bKRI z&G^5)^ZcI2S6?Z~sRBrd1dp)^vGs)1t+NiSF@qT1aeAyDp@^Uf1y0HMGF&%r(b)(0rl*M!}mqEO?FQA9Ueli z?BO}hD{uJVn+hJTAD=)_H*PF5VG00fJ9}<9wQdF9J!v5&G}G-39a3)h>c`;<1_nx3 z+_%|u05JB7UhmQD@r9xdU>;(Ip>trdX=h6&1icxKx%Pv%9UdJeIy-5!~I1Xk6KODsb%BDk^r2K?!vf5qv^&q%0EvrhW-AR_mT_fK+u6!I0L} zly?SiQR#IaxDJy|S`4XjI`nCweipO$QmdOUN!Wj9^fMW6=P5$%EUBp7;HT^pt+%pp z@J+684<5oEato`{nPtCPrY1a?C zmow6zgJ~8x@(Vwu_k8>aAeDRf?%!PBxKzu%OiW=c9#>nNedmj4q}8hUVhNN%wK!6h z(hp_Wlf(ams_Va&J;`bRx$J?LUZO=Ajyk+MA1Xvy-L>SVX%UC->u>m>5u&s z4a+H%SBZ4vqILIYeb?nrrX>Y9zKf|>?_P`#JpJ_hDyyo71!oML z+c)io8!3{_+cQ$Wv7$mkO^&J|Bpjv$NN)E?WqsXehcHIXKyMN}J|p|AZEYWrO&@ZE zAO1KU6aoh2p4^CoA4%2VG|CAnV}&UWrJk)7%@($0Q5`1li9!ZQ zr=cM=H|e?M=kJeiS}~-rBQh!XGpV2WT}MB|;%}ZwHYl)BJKS_*oS7?5TlTeY=BT=# zQ}^b!j|QX5m(XJQsiZ_|sAk)e;*}ky)3>;I)_ttXaNS3I3e6w#!$qzr=aJeB0hLX2 zJD+dFT8AxXyE+*j_GtgMba_!-82RH3(&1;P%F1Vel3gD;HJ&-sE`x3kXlhuutC(C> zWMa*FbKsO*y;<9x%WZey3<_<>4Li@E;X?gCXVUs)7;a0AYdZ(KE8*Y~fJ!2(s94<4 zz>fLi{uA$B|*Zd;C#$D$020iI@HJ3lcGnX49 z@kOUQWaSW<-By#U!+v_b$!ql7cCdMr)7Vu{e#1v@$qJv{YSXYKDnmZ4Bj3DCLj!gn zi$7$Fx<-^tNe}AIHZYgw=Z&@BO~--=neXkQKT{);)Oq^q55c)jsaxi-Z~LXi?Cf-z z`?Oc~PfdDG`T4e)oe`6hO3~w2Ynw!81UA=Rvy#DE*%&fY&cF)ndAz{<4+Zy6BW0_b z+3kjxQ4)kQHg6GPCKArHa@U6?bzQxB0$~5PvDL>;^cLz($7s}o`Nwmenz@wk9b~|i zva^j{v?`<7dbLeG3N?zc^O!(OXTD*}3Ojhp+Fx_iF0h$C&IqY-S+|zSd2Xp+aHwlK zA?LXcfoUjoT5gAvffc3xd^;%mTCU{4w|c&qtEai5RFpu>ROGS1&*3<~^FU?cg%AKiiFjGvfC6 z7ti4^>X+?`%l$eGnhc|+e1wI3Cz{`9?nOqLq80TA*nIzpz%fEL;0nn<5+SY6;xX3v zCHy@2o4%2ef_Uz}-QCo*I;%4CJ^qQQrr4~aJeie5x{vZ*xI0|Gnt-+!D?{#j1ZC85K7A)3JoKt= zETnq5s(iuK!^40^quDyH3Prc&ym;~A+4VOEmU#xP+DBpL@2=P+-`uQ)NxI9VE)Mj} z+!792NJY1csO=O-KCP8VEu~$Y%D6+V?Mmu6=XSf54_|%#L|0c=uV2~Zi$;KyIsmJff z#yv>PEglWVF1>7pV@dEs1{V)tV@RhYV@V(i$tftbPu_Ju6<<*K+4^N> zJRs&YEm~(N%e{I~OXq)MFgP+&(c9Za-u&WYZCz7wRzpLh1XTWn#?85@BCj$h6^eeh zkwo6oh>XdAkM9)fo!t2oX6)GYfC=pp)<>EfKO)F{>{d&o>)evbk>JbnZOeTe-njMx z06{-p+6?d}7Oo>@g#(*L-o8D?%T}_Na+38@WaEVF3xLCRKVEq1={oKEPRHg30XJj=r-B@la7 zAhxK^UlT5Pa9T0$)tS-4z`0>-a9FU3hyqzv)na!We?Z>RD@Z}TPS|c;uM9D0*3P~4 zODo7qrf?Qkc>c&oB0ZW)CM7vc>o6HL?w=|hn!C_#fN;6Oh(CBR(zoN)?W;eqUd^>* z^PO!!u7)>jur+O{DNbI@i4zU7nVTMZwd@0fY2k3(^DCnLfMa0y+NBzO$1WPQS8v1N zHf7Su-n!GqRwWTlGo*WuHo7eu)CIpuyYt3Z_jO&;lfl-&1cW^U18;S8#grirhVfa6 zG8zv-mOlTiJKf15&xnfA?ZmyMeS+=jJMWj)gLpRw(2tfc6dM;6g1XR1;@e?6C>&H@ ztPxuCb91*YrrJ_k0~Ff5Ku^235bb*m31Ai7_we1ritiydR81{VTo#2QjE(56%Ki$(t|B*@X!NIuBiPoadNXu zOLu$4q4P&SnplEp4BUi7+kWanJ4<_u9ka*Pw$M`$rH$n5G52?ohrR7A+U`^hm@xHR zW`h_+X#jlgz@>Dc{)*-nb}eQ zfV5b)QQ-{lu)W$^G=0mEKKoLGI)I>EvO_WE4`QY3CszS{a3u2+bLBl|ipZg^pyAao zOqMso*>z4lcP8@;^C5)8P(1Zv+PS^vhS~1_`0x;vrMAbI(H&=gTkverw(e32`9P0A zIQ2Wv23zy`57RCc!=Wg3N~(-3++ZokxM*+UudFl9Y%G~9z2nu7f$diLGM2pATXYUN zo(F+}Pv)_osvk6HJRYYB5DySD&}eiTI&iCQ=zc@ywqH6sqs=WWKve+=OFUc&->pne z=1uOpOQuk&B_|pIfdx#xS3f9)y#~oJPm>Wn{!Z1$ADFYInc@2ZRE7qH zoas3}>ImcQMCIFwZ)lB+Mdl?yFTjj!rAt7EVJeA&SwlS{OiCrDlB<)f5Yy2uFy9Yz z{3p1&%6p{U%T&^5+ZS?H0B8aINJ2%}6RqRj`P&j_p&CJ~4=>xc3ZQr$I+ce<$ow6} zG?Tm=p@k}Iex&{<;LXNwlW=|_cusLu)Lk0+Ms>>>Fp5)&(6Wys)@&ZqPCz#Xkt@{K z)zo7TK<-)AWv+pNmk9(o;MAyyJ=eKO#3`wL8^6>dz7KLPpPmi`m|+V-y}!`yHb6n= zZj1%!+dPH+oyy~M@di-UaOLrV_$8&Hexoacab#6PDXANv9TE`wda8Zm#xitd|3Gm; zTbmNt48SyH7s(B`E7m}Z28aI$jiG?l;T@(-J+*)#a9Ca3b?p; zg9O%RkK8x#YKa3Iq7d=$W(LRx*ozs}w$OP3u zq{M$8_xu9d>C<@lM2P^wSYJl0f(i*PBj9^npY_>-c~V!|N8hgK_Qd%GL8DV#(=kt= z9&H9S=E+@9E+!esNN`0-rY^d5QZ5UK#0Q651yv^K)d1E^Zgy6IvgG5q0oMvFGgYK1 z%Gyl24y65Cc6O|%6V+k~qWmWN!^lgEZ9I0r;A)O`H><46B-8Es3RW6#74_enWP+F` z6_4i|rvpk?a>qHj#pc<_roQ{tE(9{pVpd^gc6O@)!;i(Bw;s#sv@sV}mc;lih zitv{lEd7K2;nX95kG(@RNe}|e*s+l{;rL+n5=7{fYs9-ID-P%~Nihxv+X1>DH=lO^ z{?<9%FN!b2AEqJ?L5}WC>#XMull6x1e!&MoNiqAGbNOgy|ADYgE`&Hd^TQhOBT}~t zTpxleH@Tuj8a&t$nyOe_%Mz{S3mwW|L+qbWPSBFU<6p#dGzKus-^bA#fv!|9lGmVpz+*ebk3{> zNsr~`dJ3afLKoySRPT;4mI;k=^78jYWOOl%6~-I_yR}-Zucxc4ZBc)G zpekTI{;>)3KU3asFT$;j<|E$8Y>ggd*V}e5>O^z24OMZ5F5PyZ-oJb{6npsyJx-u1 zi|4V75xX7}G{~{{)B5or!0%5PrCfZT$D%dtNgzFmMIa_U}EkPM*y5MWm-@ zTQje~!6y?b_=h)Sxvq=-)$!)9fHu))=9zzIEg1g} cYoUX|TvRpvNaStK6a-$nR}HkwwHzP+AGM%n9RL6T literal 12740 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 diff --git a/test/screens/goldens/my_festival_screen_dark.png b/test/screens/goldens/my_festival_screen_dark.png index ef0289da38449d3b65dcb5917b3cd8648374912c..69fb383faa097c03a9b8570e8b8a5029dded89f1 100644 GIT binary patch literal 7470 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 Z`?`(Y@wM0jzVHOxoZKBB?>q6=zXR{D_AUSb literal 7427 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 K*PizI$A1BKUK$Gk diff --git a/test/screens/my_festival_screen_test.dart b/test/screens/my_festival_screen_test.dart index a51b11e1..f378a074 100644 --- a/test/screens/my_festival_screen_test.dart +++ b/test/screens/my_festival_screen_test.dart @@ -570,7 +570,12 @@ void main() { ); expect(edgeFinder, findsOneWidget); - final accent = CategoryColorHelper.getAccentColor('beer'); + // Read brightness from the pumped tree rather than assuming light, so + // this assertion holds if the harness theme ever changes. + final accent = CategoryColorHelper.getAccentColor( + 'beer', + Theme.of(tester.element(edgeFinder)).brightness, + ); final border = (tester.widget(edgeFinder).decoration as BoxDecoration) diff --git a/test/utils_test.dart b/test/utils_test.dart index 81c4d453..1498fc59 100644 --- a/test/utils_test.dart +++ b/test/utils_test.dart @@ -3,271 +3,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:cambridge_beer_festival/utils/utils.dart'; void main() { - group('CategoryColorHelper', () { - testWidgets('returns correct color for beer category', (tester) async { - await tester.pumpWidget( - MaterialApp( - home: Builder( - builder: (context) { - final color = CategoryColorHelper.getCategoryColor( - context, - 'beer', - ); - expect(color, isNotNull); - return Container(); - }, - ), - ), - ); - }); - - testWidgets('returns correct color for cider category in light theme', ( - tester, - ) async { - await tester.pumpWidget( - MaterialApp( - theme: ThemeData(brightness: Brightness.light), - home: Builder( - builder: (context) { - final color = CategoryColorHelper.getCategoryColor( - context, - 'cider', - ); - expect(color, const Color(0xFF689F38)); - return Container(); - }, - ), - ), - ); - }); - - testWidgets('returns correct color for cider category in dark theme', ( - tester, - ) async { - await tester.pumpWidget( - MaterialApp( - theme: ThemeData(brightness: Brightness.dark), - home: Builder( - builder: (context) { - final color = CategoryColorHelper.getCategoryColor( - context, - 'cider', - ); - expect(color, const Color(0xFF8BC34A).withValues(alpha: 0.8)); - return Container(); - }, - ), - ), - ); - }); - - testWidgets('returns correct color for perry category in light theme', ( - tester, - ) async { - await tester.pumpWidget( - MaterialApp( - theme: ThemeData(brightness: Brightness.light), - home: Builder( - builder: (context) { - final color = CategoryColorHelper.getCategoryColor( - context, - 'perry', - ); - expect(color, const Color(0xFFAFB42B)); - return Container(); - }, - ), - ), - ); - }); - - testWidgets('returns correct color for perry category in dark theme', ( - tester, - ) async { - await tester.pumpWidget( - MaterialApp( - theme: ThemeData(brightness: Brightness.dark), - home: Builder( - builder: (context) { - final color = CategoryColorHelper.getCategoryColor( - context, - 'perry', - ); - expect(color, const Color(0xFFCDDC39).withValues(alpha: 0.8)); - return Container(); - }, - ), - ), - ); - }); - - testWidgets('returns correct color for mead category in light theme', ( - tester, - ) async { - await tester.pumpWidget( - MaterialApp( - theme: ThemeData(brightness: Brightness.light), - home: Builder( - builder: (context) { - final color = CategoryColorHelper.getCategoryColor( - context, - 'mead', - ); - expect(color, const Color(0xFFF9A825)); - return Container(); - }, - ), - ), - ); - }); - - testWidgets('returns correct color for mead category in dark theme', ( - tester, - ) async { - await tester.pumpWidget( - MaterialApp( - theme: ThemeData(brightness: Brightness.dark), - home: Builder( - builder: (context) { - final color = CategoryColorHelper.getCategoryColor( - context, - 'mead', - ); - expect(color, const Color(0xFFFFEB3B).withValues(alpha: 0.8)); - return Container(); - }, - ), - ), - ); - }); - - testWidgets('returns correct color for wine category in light theme', ( - tester, - ) async { - await tester.pumpWidget( - MaterialApp( - theme: ThemeData(brightness: Brightness.light), - home: Builder( - builder: (context) { - final color = CategoryColorHelper.getCategoryColor( - context, - 'wine', - ); - expect(color, const Color(0xFF7B1FA2)); - return Container(); - }, - ), - ), - ); - }); - - testWidgets('returns correct color for wine category in dark theme', ( - tester, - ) async { - await tester.pumpWidget( - MaterialApp( - theme: ThemeData(brightness: Brightness.dark), - home: Builder( - builder: (context) { - final color = CategoryColorHelper.getCategoryColor( - context, - 'wine', - ); - expect(color, const Color(0xFF9C27B0).withValues(alpha: 0.8)); - return Container(); - }, - ), - ), - ); - }); - - testWidgets('returns correct color for low-no category in light theme', ( - tester, - ) async { - await tester.pumpWidget( - MaterialApp( - theme: ThemeData(brightness: Brightness.light), - home: Builder( - builder: (context) { - final color = CategoryColorHelper.getCategoryColor( - context, - 'low-no', - ); - expect(color, isNotNull); - return Container(); - }, - ), - ), - ); - }); - - testWidgets('returns correct color for low-no category in dark theme', ( - tester, - ) async { - await tester.pumpWidget( - MaterialApp( - theme: ThemeData(brightness: Brightness.dark), - home: Builder( - builder: (context) { - final color = CategoryColorHelper.getCategoryColor( - context, - 'low-no', - ); - expect(color, isNotNull); - return Container(); - }, - ), - ), - ); - }); - - testWidgets('returns valid color for unknown category', (tester) async { - await tester.pumpWidget( - MaterialApp( - theme: ThemeData(brightness: Brightness.light), - home: Builder( - builder: (context) { - final color = CategoryColorHelper.getCategoryColor( - context, - 'unknown', - ); - expect(color, isNotNull); - expect(color, isA()); - return Container(); - }, - ), - ), - ); - }); - - testWidgets('handles case-insensitive matching', (tester) async { - await tester.pumpWidget( - MaterialApp( - home: Builder( - builder: (context) { - final colorLower = CategoryColorHelper.getCategoryColor( - context, - 'beer', - ); - final colorUpper = CategoryColorHelper.getCategoryColor( - context, - 'BEER', - ); - final colorMixed = CategoryColorHelper.getCategoryColor( - context, - 'BeEr', - ); - - expect(colorLower, colorUpper); - expect(colorUpper, colorMixed); - return Container(); - }, - ), - ), - ); - }); - }); - group('BeverageTypeHelper', () { test('formatBeverageType formats dash-separated strings', () { expect(BeverageTypeHelper.formatBeverageType('beer'), 'Beer'); diff --git a/test/widgets/goldens/drink_card_search_excerpt_dark.png b/test/widgets/goldens/drink_card_search_excerpt_dark.png index 08a4aeafa04a406db85051bf9c91e2f597aa2514..efbc11bd2f32750eb6b50de84f624577b42b0dcb 100644 GIT binary patch literal 6643 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�{S^nOlb2gYFeXzXm=NLB=JG_ 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 LOb@AZ>%o5k=BAl_ diff --git a/test/widgets/goldens/drink_card_tasted_multiple_dark.png b/test/widgets/goldens/drink_card_tasted_multiple_dark.png index 05ae48fc6fdde9d24f843ac109e1c4687be8d5d1..6eb31d167d7dacfe913b0b06544c86e6020d14c8 100644 GIT binary patch delta 3555 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 vLN}+K;`AXpn-I7Jy~vl;g5d-wWk=b delta 3511 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 L^YL2Svq}E}sdy5` From b40532ec25580131484dda54838aa8c7c9809585 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Aug 2026 21:12:37 +0000 Subject: [PATCH 2/4] refactor(theme): tighten the colour helper API Address review feedback on the new colour system. Drop getAvailabilityColor's separate brightness parameter and read it from the ColorScheme instead, so a caller can no longer pass a dark scheme alongside Brightness.light. Nothing outside this PR calls it yet, so the signature is free to change now. Correct getAccentColor's doc comment: the unknown-category fallback is adapted for dark surfaces like any other hue, so the dark fallback is a lifted navy rather than the navy literal the comment claimed. No behaviour change; goldens unaffected. --- lib/screens/my_festival_screen.dart | 1 - lib/utils/category_color_helper.dart | 13 +++++++++---- lib/widgets/drink_card.dart | 1 - test/category_color_helper_test.dart | 20 +++----------------- 4 files changed, 12 insertions(+), 23 deletions(-) diff --git a/lib/screens/my_festival_screen.dart b/lib/screens/my_festival_screen.dart index 13872fa7..d7a220fd 100644 --- a/lib/screens/my_festival_screen.dart +++ b/lib/screens/my_festival_screen.dart @@ -504,7 +504,6 @@ class _MyFestivalScreenState extends State { final color = CategoryColorHelper.getAvailabilityColor( atRisk, theme.colorScheme, - theme.brightness, ); return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), diff --git a/lib/utils/category_color_helper.dart b/lib/utils/category_color_helper.dart index 41e67180..21c3d700 100644 --- a/lib/utils/category_color_helper.dart +++ b/lib/utils/category_color_helper.dart @@ -70,8 +70,11 @@ class CategoryColorHelper { /// rows and the similar-drinks carousel. /// /// Derived from [brightness]: the fixed hue in light mode, a lightness-lifted - /// variant in dark mode so the edge reads against a dark surface. Falls back - /// to CBF navy for unknown categories. + /// variant in dark mode so the edge reads against a dark surface. + /// + /// An unrecognised category falls back to CBF navy, which is adapted for + /// dark surfaces the same way a real category is — so the dark fallback is a + /// lifted navy, not the navy literal. static Color getAccentColor(String category, Brightness brightness) { final hue = _categoryHues[category] ?? _fallbackHue; return brightness == Brightness.dark ? _liftForDark(hue) : hue; @@ -109,12 +112,14 @@ class CategoryColorHelper { /// should track the app's error language, so [colorScheme] is required. /// Every other state uses a fixed light/dark pair chosen for legibility on /// both surfaces. + /// + /// Brightness is read from [colorScheme] rather than taken separately, so a + /// caller cannot pass a dark scheme alongside a light brightness. static Color getAvailabilityColor( AvailabilityStatus status, ColorScheme colorScheme, - Brightness brightness, ) { - final isDark = brightness == Brightness.dark; + final isDark = colorScheme.brightness == Brightness.dark; switch (status) { case AvailabilityStatus.plenty: return isDark ? const Color(0xFF4CAF50) : const Color(0xFF2E7D32); diff --git a/lib/widgets/drink_card.dart b/lib/widgets/drink_card.dart index 03d949b0..eaa0859d 100644 --- a/lib/widgets/drink_card.dart +++ b/lib/widgets/drink_card.dart @@ -273,7 +273,6 @@ class _AvailabilityChip extends StatelessWidget { final color = CategoryColorHelper.getAvailabilityColor( status, theme.colorScheme, - theme.brightness, ); String label; IconData icon; diff --git a/test/category_color_helper_test.dart b/test/category_color_helper_test.dart index 477f065d..b0914b4d 100644 --- a/test/category_color_helper_test.dart +++ b/test/category_color_helper_test.dart @@ -149,20 +149,12 @@ void main() { }; for (final entry in expected.entries) { expect( - CategoryColorHelper.getAvailabilityColor( - entry.key, - lightScheme, - Brightness.light, - ), + CategoryColorHelper.getAvailabilityColor(entry.key, lightScheme), entry.value.$1, reason: '${entry.key} light', ); expect( - CategoryColorHelper.getAvailabilityColor( - entry.key, - darkScheme, - Brightness.dark, - ), + CategoryColorHelper.getAvailabilityColor(entry.key, darkScheme), entry.value.$2, reason: '${entry.key} dark', ); @@ -174,7 +166,6 @@ void main() { CategoryColorHelper.getAvailabilityColor( AvailabilityStatus.out, lightScheme, - Brightness.light, ), lightScheme.error, ); @@ -182,7 +173,6 @@ void main() { CategoryColorHelper.getAvailabilityColor( AvailabilityStatus.out, darkScheme, - Brightness.dark, ), darkScheme.error, ); @@ -191,11 +181,7 @@ void main() { test('covers every AvailabilityStatus', () { for (final status in AvailabilityStatus.values) { expect( - () => CategoryColorHelper.getAvailabilityColor( - status, - lightScheme, - Brightness.light, - ), + () => CategoryColorHelper.getAvailabilityColor(status, lightScheme), returnsNormally, ); } From 80a4418db91b5d6c92de20dc4bbae6bcf9b1f938 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 20:31:39 +0000 Subject: [PATCH 3/4] fix(my-festival): correct app bar text contrast MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The app text theme bakes a colour into every style, so passing titleMedium/bodySmall straight into the AppBar painted near-black text over the navy bar, silently overriding AppBarTheme.foregroundColor. Measured against the bar's #2B3170: the title reached 1.45:1 and the subtitle 1.27:1, against WCAG AA's 4.5:1 minimum. Both now take the app bar's own foreground colour. My Festival is the only screen in lib/ that passes a text-theme style into an AppBar; every other screen inherits the bar's foreground and was already correct. Add a test that computes the real contrast ratio against the app bar's background and fails below 4.5:1. It pumps the actual app theme — the existing goldens use a stripped theme with no appBarTheme, which is why this never showed up in them. --- lib/screens/my_festival_screen.dart | 17 +++++- test/screens/my_festival_screen_test.dart | 70 +++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/lib/screens/my_festival_screen.dart b/lib/screens/my_festival_screen.dart index d7a220fd..48ad39e2 100644 --- a/lib/screens/my_festival_screen.dart +++ b/lib/screens/my_festival_screen.dart @@ -88,6 +88,8 @@ class _MyFestivalScreenState extends State { .toList(); final tasted = myFestivalEntries.tasted; final theme = Theme.of(context); + final appBarForeground = + theme.appBarTheme.foregroundColor ?? theme.colorScheme.onSurface; final totalCount = wantToTry.length + tasted.length; return PageTitle( @@ -95,16 +97,27 @@ class _MyFestivalScreenState extends State { contextLabel: provider.currentFestival.name, child: Scaffold( appBar: AppBar( + // The text theme bakes `colorScheme.onSurface` into every style, so + // using titleMedium/bodySmall unmodified here paints near-black text + // on the navy app bar (1.45:1 and 1.27:1 — far below WCAG AA). Force + // the app bar's own foreground colour back on. title: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( provider.currentFestival.name, - style: theme.textTheme.titleMedium, + style: theme.textTheme.titleMedium?.copyWith( + color: appBarForeground, + ), ), Text( '$totalCount in My Festival', - style: theme.textTheme.bodySmall, + // Same colour as the title, not a muted variant: the size and + // weight difference already carries the hierarchy, and a + // translucent variant would erode contrast on the navy bar. + style: theme.textTheme.bodySmall?.copyWith( + color: appBarForeground, + ), ), ], ), diff --git a/test/screens/my_festival_screen_test.dart b/test/screens/my_festival_screen_test.dart index f378a074..5cd5965e 100644 --- a/test/screens/my_festival_screen_test.dart +++ b/test/screens/my_festival_screen_test.dart @@ -1,5 +1,8 @@ +import 'dart:math' as math; + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:cambridge_beer_festival/app_theme.dart'; import 'package:cambridge_beer_festival/screens/screens.dart'; import 'package:cambridge_beer_festival/models/models.dart'; import 'package:cambridge_beer_festival/providers/providers.dart'; @@ -509,6 +512,73 @@ void main() { }); }); + group('app bar contrast', () { + // Regression: the text theme bakes colorScheme.onSurface into every + // style, so passing titleMedium/bodySmall straight into the AppBar + // painted near-black text on the navy bar — 1.45:1 and 1.27:1 against + // WCAG AA's 4.5:1 minimum. Assert the rendered text is light, and that + // it actually clears AA against the bar it sits on. + double relativeLuminance(Color c) { + double channel(double v) => v <= 0.03928 + ? v / 12.92 + : math.pow((v + 0.055) / 1.055, 2.4).toDouble(); + return 0.2126 * channel(c.r) + + 0.7152 * channel(c.g) + + 0.0722 * channel(c.b); + } + + double contrastRatio(Color a, Color b) { + final la = relativeLuminance(a); + final lb = relativeLuminance(b); + final lighter = la > lb ? la : lb; + final darker = la > lb ? lb : la; + return (lighter + 0.05) / (darker + 0.05); + } + + testWidgets('title and subtitle meet WCAG AA on the app bar', ( + tester, + ) async { + await setUpProvider(); + // Must pump the real app theme: with the default theme the app bar is + // not navy and the regression cannot reproduce. + await tester.pumpWidget( + createTestWidget(theme: buildAppTheme(Brightness.light)), + ); + await tester.pumpAndSettle(); + + final appBar = tester.widget(find.byType(AppBar)); + final context = tester.element(find.byType(MyFestivalScreen)); + final theme = Theme.of(context); + final background = + appBar.backgroundColor ?? + theme.appBarTheme.backgroundColor ?? + theme.colorScheme.surface; + + final titles = find.descendant( + of: find.byType(AppBar), + matching: find.byType(Text), + ); + expect(titles, findsWidgets); + + for (final text in tester.widgetList(titles)) { + final color = text.style?.color; + expect( + color, + isNotNull, + reason: 'app bar text should pin an explicit colour', + ); + expect( + contrastRatio(color!, background), + greaterThanOrEqualTo(4.5), + reason: + '"${text.data}" only reaches ' + '${contrastRatio(color, background).toStringAsFixed(2)}:1 ' + 'against the app bar', + ); + } + }); + }); + group('card styling', () { // A want-to-try drink with a style and a low-stock status, so the goldens // capture the enriched facts line and the availability hint together. From 30d6cd35e13bb22a8c17b8ae579d83fcc99e0ea2 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 7 Aug 2026 20:36:09 +0000 Subject: [PATCH 4/4] style(theme): make the light app bar a plain M3 surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The light-mode app bar was a solid navy slab, the only dark surface in an otherwise light UI. It now uses colorScheme.surface/onSurface in both themes, matching how dark mode already behaved and Material 3's default. The poster navy is unchanged as the seed and still leads through `primary`, the nav bar indicator and the category accents — only the app bar's own fill changes. Update app_theme_test to pin the new intent, and add a contrast check asserting the app bar title clears WCAG AA against its own background in both themes, so neither surface can drift into an unreadable pairing. No goldens changed: the screenshot tests build their own stripped theme with no appBarTheme, so they never covered the real app bar. --- lib/app_theme.dart | 16 ++++++------- test/app_theme_test.dart | 50 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/lib/app_theme.dart b/lib/app_theme.dart index a2a236e0..f493fef5 100644 --- a/lib/app_theme.dart +++ b/lib/app_theme.dart @@ -95,21 +95,19 @@ ThemeData buildAppTheme(Brightness brightness) { colorScheme: colorScheme, textTheme: textTheme, useMaterial3: true, + // The app bar is a plain Material 3 surface in both themes. Light mode + // previously used the poster navy as a solid slab, which made it the only + // dark surface in an otherwise light UI; the brand colour still leads + // through `primary`, the nav bar indicator and the category accents. appBarTheme: AppBarTheme( - backgroundColor: brightness == Brightness.light - ? appSeedColor - : colorScheme.surface, - foregroundColor: brightness == Brightness.light - ? Colors.white - : colorScheme.onSurface, + backgroundColor: colorScheme.surface, + foregroundColor: colorScheme.onSurface, elevation: 0, centerTitle: false, titleTextStyle: GoogleFonts.playfairDisplay( fontSize: 20, fontWeight: FontWeight.w700, - color: brightness == Brightness.light - ? Colors.white - : colorScheme.onSurface, + color: colorScheme.onSurface, ), ), navigationBarTheme: NavigationBarThemeData( diff --git a/test/app_theme_test.dart b/test/app_theme_test.dart index ab565dad..d9d86637 100644 --- a/test/app_theme_test.dart +++ b/test/app_theme_test.dart @@ -1,3 +1,5 @@ +import 'dart:math' as math; + import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -9,18 +11,58 @@ void main() { }); group('buildAppTheme', () { - testWidgets('light theme uses navy seed colour as AppBar background', ( + // The light app bar used to be a solid navy slab, which made it the only + // dark surface in an otherwise light UI. It is now a plain Material 3 + // surface in both themes; the seed colour still leads via `primary`, the + // nav bar indicator and the category accents. + testWidgets('light theme AppBar background is surface (not navy)', ( WidgetTester tester, ) async { final theme = buildAppTheme(Brightness.light); - expect(theme.appBarTheme.backgroundColor, equals(appSeedColor)); + expect(theme.appBarTheme.backgroundColor, isNot(equals(appSeedColor))); + expect( + theme.appBarTheme.backgroundColor, + equals(theme.colorScheme.surface), + ); }); - testWidgets('light theme uses white as AppBar foreground', ( + testWidgets('light theme AppBar foreground is onSurface', ( WidgetTester tester, ) async { final theme = buildAppTheme(Brightness.light); - expect(theme.appBarTheme.foregroundColor, equals(Colors.white)); + expect(theme.appBarTheme.foregroundColor, isNot(equals(Colors.white))); + expect( + theme.appBarTheme.foregroundColor, + equals(theme.colorScheme.onSurface), + ); + }); + + testWidgets('AppBar title contrasts with its background in both themes', ( + WidgetTester tester, + ) async { + for (final brightness in Brightness.values) { + final theme = buildAppTheme(brightness); + final background = theme.appBarTheme.backgroundColor!; + final title = theme.appBarTheme.titleTextStyle!.color!; + double channel(double v) => v <= 0.03928 + ? v / 12.92 + : math.pow((v + 0.055) / 1.055, 2.4).toDouble(); + double luminance(Color c) => + 0.2126 * channel(c.r) + + 0.7152 * channel(c.g) + + 0.0722 * channel(c.b); + final lt = luminance(title); + final lb = luminance(background); + final ratio = + ((lt > lb ? lt : lb) + 0.05) / ((lt > lb ? lb : lt) + 0.05); + expect( + ratio, + greaterThanOrEqualTo(4.5), + reason: + '$brightness app bar title is only ' + '${ratio.toStringAsFixed(2)}:1', + ); + } }); testWidgets('light theme primary colour equals seed colour', (