feat(ui): align inventory + detail with mockups (seed icons, green theme)

Visual pass toward docs/mockups (06 inventory, 07 item):
- Bundle the seedks icon font (chars a–k → seed pictograms: jar, packet, spoon…)
  and a SeedGlyph/QuantityKindIcon helper.
- Green app bar + pale-green canvas theme (buildTaneTheme).
- Inventory list: rounded-square photo thumbnail or green initial avatar, bold
  title, scientific-name subtitle, trailing edit pencil, styled category
  headers, rounded search field, seed-glyph empty state.
- Quantity selectors (quick-add + add-lot) show the seed pictogram + word;
  detail lot lines use the kind's glyph.
- Detail header restyled to mockup 07: category link + scientific name on the
  left, photo on the right.

watchInventory now also returns the linked species' scientific name (subtitle).
37 app tests green; seedks font verified in the Linux asset bundle.
This commit is contained in:
vjrj 2026-07-08 00:14:43 +02:00
parent e41ec9872b
commit 48e9d15772
12 changed files with 329 additions and 49 deletions

View file

@ -12,6 +12,7 @@ class VarietyListItem extends Equatable {
required this.id,
required this.label,
this.category,
this.scientificName,
this.photo,
});
@ -19,11 +20,14 @@ class VarietyListItem extends Equatable {
final String label;
final String? category;
/// Scientific name of the linked species, shown as the tile subtitle.
final String? scientificName;
/// First photo (encrypted BLOB) for the avatar, or null show an initial.
final Uint8List? photo;
@override
List<Object?> get props => [id, label, category, photo];
List<Object?> get props => [id, label, category, scientificName, photo];
}
/// One germination test on a lot; [rate] is derived (0..1), null when it can't
@ -154,12 +158,18 @@ class VarietyRepository {
]);
return query.watch().asyncMap((rows) async {
final photos = await _firstPhotosFor(rows.map((v) => v.id).toList());
final sciNames = await _scientificNamesFor(
rows.map((v) => v.speciesId).whereType<String>().toSet(),
);
return rows
.map(
(v) => VarietyListItem(
id: v.id,
label: v.label,
category: v.category,
scientificName: v.speciesId == null
? null
: sciNames[v.speciesId],
photo: photos[v.id],
),
)
@ -189,6 +199,17 @@ class VarietyRepository {
return byVariety;
}
/// Maps each of [speciesIds] to its scientific name (one query).
Future<Map<String, String>> _scientificNamesFor(
Set<String> speciesIds,
) async {
if (speciesIds.isEmpty) return const {};
final rows = await (_db.select(
_db.species,
)..where((s) => s.id.isIn(speciesIds))).get();
return {for (final s in rows) s.id: s.scientificName};
}
/// The 20-second quick-add: a [label] (required) plus an optional [category],
/// an optional [quantity] (creates a Lot) and an optional [photoBytes]
/// (stored as an encrypted BLOB Attachment). Returns the new Variety id.