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:
parent
e41ec9872b
commit
48e9d15772
12 changed files with 329 additions and 49 deletions
|
|
@ -6,6 +6,8 @@ import '../data/species_repository.dart';
|
|||
import '../data/variety_repository.dart';
|
||||
import '../i18n/strings.g.dart';
|
||||
import '../state/variety_detail_cubit.dart';
|
||||
import 'seed_glyph.dart';
|
||||
import 'theme.dart';
|
||||
import 'quantity_kind_l10n.dart';
|
||||
|
||||
/// Read + edit view of a single variety: its photo, category, notes, other
|
||||
|
|
@ -72,33 +74,7 @@ class _DetailView extends StatelessWidget {
|
|||
body: ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
if (detail.scientificName != null) ...[
|
||||
Text(
|
||||
detail.scientificName!,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
fontStyle: FontStyle.italic,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
if (detail.photo != null)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Image.memory(
|
||||
detail.photo!,
|
||||
height: 200,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
if (detail.category != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Chip(label: Text(detail.category!)),
|
||||
),
|
||||
],
|
||||
_DetailHeader(detail: detail),
|
||||
if (detail.vernacularNames.isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
_SectionTitle(t.detail.names),
|
||||
|
|
@ -134,7 +110,9 @@ class _DetailView extends StatelessWidget {
|
|||
for (final lot in detail.lots)
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: const Icon(Icons.inventory_2_outlined),
|
||||
leading: lot.quantity == null
|
||||
? const Icon(Icons.inventory_2_outlined)
|
||||
: QuantityKindIcon(lot.quantity!.kind, size: 28),
|
||||
title: Text(_lotSubtitle(t, lot)),
|
||||
subtitle: lot.storageLocation == null
|
||||
? null
|
||||
|
|
@ -548,6 +526,7 @@ Future<void> _showAddLotSheet(BuildContext context, VarietyDetailCubit cubit) {
|
|||
children: [
|
||||
for (final kind in _addLotKinds)
|
||||
ChoiceChip(
|
||||
avatar: QuantityKindIcon(kind, size: 18),
|
||||
label: Text(quantityKindLabel(t, kind)),
|
||||
selected: selectedKind == kind,
|
||||
onSelected: (_) => setState(() => selectedKind = kind),
|
||||
|
|
@ -598,6 +577,61 @@ String? _nullIfBlank(String value) {
|
|||
return trimmed.isEmpty ? null : trimmed;
|
||||
}
|
||||
|
||||
/// Mockup-07 header: category link + scientific name on the left, the photo on
|
||||
/// the right.
|
||||
class _DetailHeader extends StatelessWidget {
|
||||
const _DetailHeader({required this.detail});
|
||||
|
||||
final VarietyDetail detail;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasText = detail.category != null || detail.scientificName != null;
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (detail.category != null)
|
||||
Text(
|
||||
detail.category!,
|
||||
style: const TextStyle(
|
||||
color: seedLink,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
if (detail.scientificName != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
detail.scientificName!,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontStyle: FontStyle.italic,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
if (detail.photo != null) ...[
|
||||
if (hasText) const SizedBox(width: 12),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Image.memory(
|
||||
detail.photo!,
|
||||
width: 140,
|
||||
height: 140,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionTitle extends StatelessWidget {
|
||||
const _SectionTitle(this.text);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue