From b1fd4c38b5508fb4998f44694ba75a223336b169 Mon Sep 17 00:00:00 2001 From: vjrj Date: Sat, 11 Jul 2026 08:05:56 +0200 Subject: [PATCH] polish(ux): drop the checkmark on selected filter chips The tick overlapped the leading icon on selected chips, reading as a confusing blob. The solid fill already signals 'on', so hide the checkmark. Also make the selected label reliably white via an explicit per-chip style: the global ChipTheme's secondaryLabelStyle was not applied to FilterChips, leaving dark text on green. Plain attribute/ market chips now share a PlainFilterChip widget; swatch and month chips just drop their tick. --- apps/app_seeds/lib/ui/calendar_screen.dart | 1 + apps/app_seeds/lib/ui/filter_chips.dart | 45 +++++++++++++++++++ .../lib/ui/inventory_list_screen.dart | 43 ++++++------------ apps/app_seeds/lib/ui/market_screen.dart | 19 ++++---- apps/app_seeds/lib/ui/theme.dart | 19 -------- 5 files changed, 68 insertions(+), 59 deletions(-) create mode 100644 apps/app_seeds/lib/ui/filter_chips.dart diff --git a/apps/app_seeds/lib/ui/calendar_screen.dart b/apps/app_seeds/lib/ui/calendar_screen.dart index 8ceba0d..ef05f1c 100644 --- a/apps/app_seeds/lib/ui/calendar_screen.dart +++ b/apps/app_seeds/lib/ui/calendar_screen.dart @@ -157,6 +157,7 @@ class _MonthStripState extends State<_MonthStrip> { final selected = m == widget.month; return ChoiceChip( key: Key('calendar.month.$m'), + showCheckmark: false, labelPadding: const EdgeInsets.symmetric(horizontal: 6), label: Text( _capitalise(fmt.format(DateTime(2000, m))), diff --git a/apps/app_seeds/lib/ui/filter_chips.dart b/apps/app_seeds/lib/ui/filter_chips.dart new file mode 100644 index 0000000..5599de1 --- /dev/null +++ b/apps/app_seeds/lib/ui/filter_chips.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; + +import 'theme.dart'; + +/// A filter chip for the horizontal filter rows: light with dark ink when idle, +/// a solid green fill with white content when selected — no checkmark, since the +/// fill already reads as "on" and a tick over the leading icon just muddles it. +/// White on [seedGreen] meets WCAG AA (see test/ui/theme_contrast_test.dart). +class PlainFilterChip extends StatelessWidget { + const PlainFilterChip({ + required this.label, + required this.selected, + required this.onSelected, + this.icon, + super.key, + }); + + final String label; + final bool selected; + final ValueChanged onSelected; + + /// Optional leading icon (attribute chips carry one; type/category chips do + /// not). + final IconData? icon; + + @override + Widget build(BuildContext context) { + final fg = selected ? Colors.white : seedOnSurface; + return FilterChip( + avatar: icon == null + ? null + : Icon(icon, size: 18, color: selected ? Colors.white : seedGreen), + label: Text(label), + selected: selected, + onSelected: onSelected, + showCheckmark: false, + selectedColor: seedGreen, + labelStyle: TextStyle( + color: fg, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ); + } +} diff --git a/apps/app_seeds/lib/ui/inventory_list_screen.dart b/apps/app_seeds/lib/ui/inventory_list_screen.dart index 1e2d99b..aee28d1 100644 --- a/apps/app_seeds/lib/ui/inventory_list_screen.dart +++ b/apps/app_seeds/lib/ui/inventory_list_screen.dart @@ -12,6 +12,7 @@ import '../state/inventory_cubit.dart'; import 'category_palette.dart'; import 'draft_triage.dart'; import 'edge_fade.dart'; +import 'filter_chips.dart'; import 'label_print_sheet.dart'; import 'quantity_kind_l10n.dart'; import 'quantity_picker.dart'; @@ -303,50 +304,34 @@ class _FilterBar extends StatelessWidget { // stable earthy tonality). Grouping + colour makes a long row scannable. final attrChips = [ if (hasSowCalendar) - FilterChip( + PlainFilterChip( key: const Key('inventory.filter.sowThisMonth'), - avatar: Icon( - Icons.calendar_month, - size: 18, - color: state.sowThisMonthOnly ? Colors.white : seedGreen, - ), - label: Text(t.calendar.filterChip), + icon: Icons.calendar_month, + label: t.calendar.filterChip, selected: state.sowThisMonthOnly, onSelected: (_) => cubit.toggleSowThisMonth(), ), if (hasShared) - FilterChip( + PlainFilterChip( key: const Key('inventory.filter.sharing'), - avatar: Icon( - Icons.volunteer_activism_outlined, - size: 18, - color: state.sharingOnly ? Colors.white : seedGreen, - ), - label: Text(t.share.filterChip), + icon: Icons.volunteer_activism_outlined, + label: t.share.filterChip, selected: state.sharingOnly, onSelected: (_) => cubit.toggleSharingOnly(), ), if (hasOrganic) - FilterChip( + PlainFilterChip( key: const Key('inventory.filter.organic'), - avatar: Icon( - Icons.eco, - size: 18, - color: state.organicOnly ? Colors.white : seedGreen, - ), - label: Text(t.editVariety.organic), + icon: Icons.eco, + label: t.editVariety.organic, selected: state.organicOnly, onSelected: (_) => cubit.toggleOrganicOnly(), ), if (hasNeedsReproduction) - FilterChip( + PlainFilterChip( key: const Key('inventory.filter.needsReproduction'), - avatar: Icon( - Icons.autorenew, - size: 18, - color: state.needsReproductionOnly ? Colors.white : seedGreen, - ), - label: Text(t.inventory.needsReproductionFilter), + icon: Icons.autorenew, + label: t.inventory.needsReproductionFilter, selected: state.needsReproductionOnly, onSelected: (_) => cubit.toggleNeedsReproductionOnly(), ), @@ -456,9 +441,9 @@ class _SwatchFilterChip extends StatelessWidget { avatar: icon == null ? null : Icon(icon, size: 18, color: ink), selected: selected, onSelected: (_) => onSelected(), + showCheckmark: false, backgroundColor: swatch.fill, selectedColor: swatch.ink, - checkmarkColor: Colors.white, side: BorderSide( color: swatch.ink.withValues(alpha: selected ? 0 : 0.35), ), diff --git a/apps/app_seeds/lib/ui/market_screen.dart b/apps/app_seeds/lib/ui/market_screen.dart index 5413717..3e0ed84 100644 --- a/apps/app_seeds/lib/ui/market_screen.dart +++ b/apps/app_seeds/lib/ui/market_screen.dart @@ -13,6 +13,7 @@ import '../services/social_service.dart'; import '../services/social_settings.dart'; import '../state/offers_cubit.dart'; import 'edge_fade.dart'; +import 'filter_chips.dart'; import 'market_widgets.dart'; import 'theme.dart'; @@ -488,28 +489,24 @@ class _MarketFilterBar extends StatelessWidget { final chips = [ if (hasOrganic) - FilterChip( + PlainFilterChip( key: const Key('market.filter.organic'), - avatar: Icon( - Icons.eco, - size: 18, - color: state.organicOnly ? Colors.white : seedGreen, - ), - label: Text(t.editVariety.organic), + icon: Icons.eco, + label: t.editVariety.organic, selected: state.organicOnly, onSelected: (_) => cubit.toggleOrganicOnly(), ), for (final type in types) - FilterChip( + PlainFilterChip( key: Key('market.filter.type.${type.name}'), - label: Text(offerTypeLabel(t, type)), + label: offerTypeLabel(t, type), selected: state.typeFilter.contains(type), onSelected: (_) => cubit.toggleType(type), ), for (final category in categories) - FilterChip( + PlainFilterChip( key: Key('market.filter.category.$category'), - label: Text(category), + label: category, selected: state.categoryFilter.contains(category), onSelected: (_) => cubit.toggleCategory(category), ), diff --git a/apps/app_seeds/lib/ui/theme.dart b/apps/app_seeds/lib/ui/theme.dart index 714ad7d..392e37a 100644 --- a/apps/app_seeds/lib/ui/theme.dart +++ b/apps/app_seeds/lib/ui/theme.dart @@ -105,24 +105,5 @@ ThemeData buildTaneTheme() { thickness: 1, color: seedDivider, ), - // Filter/choice chips: idle chips stay light with dark ink, but a *selected* - // chip fills solid green with white ink so "on" reads unmistakably against - // the pale canvas (the soft tonal default sat too close to it). White on - // seedGreen meets WCAG AA — see test/ui/theme_contrast_test.dart. Chips that - // carry their own hue (family/form swatches) override this per-swatch. - chipTheme: const ChipThemeData( - selectedColor: seedGreen, - checkmarkColor: Colors.white, - labelStyle: TextStyle( - color: seedOnSurface, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - secondaryLabelStyle: TextStyle( - color: Colors.white, - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), ); }