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.
This commit is contained in:
vjrj 2026-07-11 08:05:56 +02:00
parent 89addb1ed7
commit b1fd4c38b5
5 changed files with 68 additions and 59 deletions

View file

@ -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))),

View file

@ -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<bool> 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,
),
);
}
}

View file

@ -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 = <Widget>[
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),
),

View file

@ -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 = <Widget>[
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),
),

View file

@ -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,
),
),
);
}