tane/apps/app_seeds/lib/ui/theme.dart
vjrj d897d3934c polish(ux): selected filter chips fill solid for clear contrast
The selected state used a pale tonal container (seedPrimaryContainer)
that sat almost on top of the pale canvas, so 'on' barely read. Selected
chips now fill solid — seedGreen for the plain attribute/market chips and
the swatch's own ink for family/form chips — with white label, icon and
checkmark. Idle chips stay light with dark ink, so the on/off jump is
unmistakable. White-on-ink AA is locked by a new contrast test.
2026-07-11 07:48:27 +02:00

128 lines
5.1 KiB
Dart

import 'package:flutter/material.dart';
/// Material 3 palette from the Seedees v2 redesign
/// (docs/mockups/seedees_material3.html): green app bars over a pale-green
/// canvas, a saturated green for primary actions (FAB, buttons, links, category
/// headers) and a soft green container for selected/tonal surfaces.
const seedGreen = Color(0xFF2F7D34); // primary (actions, links, headers)
const seedGreenDark = Color(0xFF22521E); // status bar / darker green
// App-bar green. Nudged down from the mockups' #3E8C38 so its white title
// meets WCAG AA (4.5:1) — see test/ui/theme_contrast_test.dart.
const seedAppBar = Color(0xFF3A8434);
const seedCanvas = Color(0xFFE7F1E0); // surface / scaffold
const seedPrimaryContainer = Color(0xFFD3E8C8); // tonal green container
const seedOnPrimaryContainer = Color(0xFF2F6D2A);
const seedField = Color(0xFFF3F7EE); // outlined field fill
const seedAvatar = Color(0xFFCFE7C3); // list avatar disc
const seedOnAvatar = Color(0xFF3B7A34);
const seedOutline = Color(0xFFC3CBB6);
const seedDivider = Color(0xFFE4E9DD);
const seedTitle = Color(0xFF1B2416); // heading text on canvas
const seedOnSurface = Color(0xFF1B2416);
const seedOnSurfaceVariant = Color(0xFF5C6B52); // scientific names, secondary
// Hints and secondary text/icons. Darkened from the mockups' #8A9880 (which
// fell below WCAG AA) to keep ≥4.5:1 on white, the canvas and the field fill —
// see test/ui/theme_contrast_test.dart.
const seedMuted = Color(0xFF617057);
const seedRating = Color(0xFFE8A200); // stars
ThemeData buildTaneTheme() {
final scheme =
ColorScheme.fromSeed(
seedColor: seedGreen,
brightness: Brightness.light,
).copyWith(
primary: seedGreen,
onPrimary: Colors.white,
primaryContainer: seedPrimaryContainer,
onPrimaryContainer: seedOnPrimaryContainer,
secondaryContainer: seedPrimaryContainer,
onSecondaryContainer: seedOnPrimaryContainer,
tertiary: seedRating,
onTertiary: Colors.white,
surface: seedCanvas,
onSurface: seedOnSurface,
surfaceContainerHighest: seedField,
onSurfaceVariant: seedOnSurfaceVariant,
outline: seedMuted,
outlineVariant: seedOutline,
);
return ThemeData(
useMaterial3: true,
colorScheme: scheme,
scaffoldBackgroundColor: seedCanvas,
// Green app bars — aligned with the v2 mockups and the real app.
appBarTheme: const AppBarTheme(
backgroundColor: seedAppBar,
foregroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: false,
iconTheme: IconThemeData(color: Colors.white),
titleTextStyle: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
// Squircle FAB (20px radius) as in the mockups, in the saturated primary.
floatingActionButtonTheme: FloatingActionButtonThemeData(
backgroundColor: seedGreen,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
),
drawerTheme: const DrawerThemeData(
backgroundColor: Colors.white,
surfaceTintColor: Colors.transparent,
),
bottomSheetTheme: const BottomSheetThemeData(
backgroundColor: seedCanvas,
surfaceTintColor: Colors.transparent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(26)),
),
),
// Pill "Save" actions across sheets and dialogs, as in the v3 mockups.
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: seedGreen,
foregroundColor: Colors.white,
shape: const StadiumBorder(),
padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 13),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(foregroundColor: seedGreen),
),
// Green floating labels on the edit-seed / add-batch text fields. (Borders
// are left to each field so the inventory search keeps its borderless pill.)
inputDecorationTheme: const InputDecorationTheme(
floatingLabelStyle: TextStyle(color: seedGreen),
),
dividerTheme: const DividerThemeData(
space: 1,
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,
),
),
);
}