feat(inventory): informal quantity scale (number + unit) + seed/plant lots
Rework quantities the way seeds are actually described — a number of an informal
unit — and let a lot hold seeds or plants (plantel).
Quantity model (commons_core):
- Quantity is now { kind, count?, label } — "3 cobs", "2 packets", "a few".
Count is optional; uncountable vibes (a few, some, pinch) carry no number.
- QuantityKind reorganised into an ascending scale: vibes → containers
(teaspoon/spoon/cup/jar/sack) → seed & fruit forms → plant units, each tagged
countable/not. Stored by name; count reuses the existing numeric column.
Lot type (schema v2):
- Lots gain a `type` (seed | plant); germination stays meaningful for seeds.
schemaVersion 2 with a from1To2 migration (adds the column), drift_schema_v2
exported, migration test covers v1→v2.
UI:
- New QuantityPicker: a horizontal scale of large seed-glyph icons + a number
stepper (shown only for countable units), plus a seed/plant SegmentedButton.
Used in quick-add and add-lot. Lot lines render "3 cobs" / "a few".
- i18n: unit singular/plural (ES/EN, Weblate-friendly), lot-type labels.
Build: slang moved to its CLI (disabled in build_runner to avoid a multi-file
collision); CI runs `dart run slang` before build_runner. 38 tests green,
Linux migrates the existing v1 DB and runs.
This commit is contained in:
parent
48e9d15772
commit
3942975dba
26 changed files with 4220 additions and 315 deletions
|
|
@ -2,46 +2,49 @@ import 'package:commons_core/commons_core.dart';
|
|||
|
||||
import '../i18n/strings.g.dart';
|
||||
|
||||
/// Localized display label for a [QuantityKind]. The enum name is the stable
|
||||
/// storage key; the label is always resolved through i18n (never hardcoded).
|
||||
String quantityKindLabel(Translations t, QuantityKind kind) {
|
||||
final q = t.quantityKind;
|
||||
switch (kind) {
|
||||
case QuantityKind.aFew:
|
||||
return q.aFew;
|
||||
case QuantityKind.some:
|
||||
return q.some;
|
||||
case QuantityKind.plenty:
|
||||
return q.plenty;
|
||||
case QuantityKind.handful:
|
||||
return q.handful;
|
||||
case QuantityKind.pinch:
|
||||
return q.pinch;
|
||||
case QuantityKind.jar:
|
||||
return q.jar;
|
||||
case QuantityKind.packet:
|
||||
return q.packet;
|
||||
case QuantityKind.cob:
|
||||
return q.cob;
|
||||
case QuantityKind.head:
|
||||
return q.head;
|
||||
case QuantityKind.pod:
|
||||
return q.pod;
|
||||
case QuantityKind.ear:
|
||||
return q.ear;
|
||||
case QuantityKind.fruit:
|
||||
return q.fruit;
|
||||
case QuantityKind.bulb:
|
||||
return q.bulb;
|
||||
case QuantityKind.tuber:
|
||||
return q.tuber;
|
||||
case QuantityKind.seedHead:
|
||||
return q.seedHead;
|
||||
case QuantityKind.bunch:
|
||||
return q.bunch;
|
||||
case QuantityKind.grams:
|
||||
return q.grams;
|
||||
case QuantityKind.count:
|
||||
return q.count;
|
||||
/// Singular + plural label for a [QuantityKind]. For uncountable *vibe* kinds
|
||||
/// (a few, some…) both are the same word.
|
||||
(String, String) _unit(Translations t, QuantityKind kind) => switch (kind) {
|
||||
QuantityKind.aFew => (t.unit.aFew, t.unit.aFew),
|
||||
QuantityKind.some => (t.unit.some, t.unit.some),
|
||||
QuantityKind.plenty => (t.unit.plenty, t.unit.plenty),
|
||||
QuantityKind.pinch => (t.unit.pinch, t.unit.pinch),
|
||||
QuantityKind.handful => (t.unit.handful.singular, t.unit.handful.plural),
|
||||
QuantityKind.teaspoon => (t.unit.teaspoon.singular, t.unit.teaspoon.plural),
|
||||
QuantityKind.spoon => (t.unit.spoon.singular, t.unit.spoon.plural),
|
||||
QuantityKind.cup => (t.unit.cup.singular, t.unit.cup.plural),
|
||||
QuantityKind.jar => (t.unit.jar.singular, t.unit.jar.plural),
|
||||
QuantityKind.sack => (t.unit.sack.singular, t.unit.sack.plural),
|
||||
QuantityKind.packet => (t.unit.packet.singular, t.unit.packet.plural),
|
||||
QuantityKind.cob => (t.unit.cob.singular, t.unit.cob.plural),
|
||||
QuantityKind.pod => (t.unit.pod.singular, t.unit.pod.plural),
|
||||
QuantityKind.ear => (t.unit.ear.singular, t.unit.ear.plural),
|
||||
QuantityKind.head => (t.unit.head.singular, t.unit.head.plural),
|
||||
QuantityKind.fruit => (t.unit.fruit.singular, t.unit.fruit.plural),
|
||||
QuantityKind.bulb => (t.unit.bulb.singular, t.unit.bulb.plural),
|
||||
QuantityKind.tuber => (t.unit.tuber.singular, t.unit.tuber.plural),
|
||||
QuantityKind.seedHead => (t.unit.seedHead.singular, t.unit.seedHead.plural),
|
||||
QuantityKind.bunch => (t.unit.bunch.singular, t.unit.bunch.plural),
|
||||
QuantityKind.plant => (t.unit.plant.singular, t.unit.plant.plural),
|
||||
QuantityKind.pot => (t.unit.pot.singular, t.unit.pot.plural),
|
||||
QuantityKind.tray => (t.unit.tray.singular, t.unit.tray.plural),
|
||||
QuantityKind.grams => (t.unit.grams.singular, t.unit.grams.plural),
|
||||
QuantityKind.count => (t.unit.count.singular, t.unit.count.plural),
|
||||
};
|
||||
|
||||
/// The unit's singular name — for chips/scale items ("cob", "packet").
|
||||
String unitLabel(Translations t, QuantityKind kind) => _unit(t, kind).$1;
|
||||
|
||||
/// A full, human quantity: "3 cobs", "2 packets", "a few".
|
||||
String quantityDisplay(Translations t, Quantity q) {
|
||||
final n = q.count;
|
||||
final (one, other) = _unit(t, q.kind);
|
||||
if (!q.kind.countable || n == null) {
|
||||
// Vibe amount, or a countable unit with no number → bare unit name.
|
||||
return (n != null && n != 1) ? other : one;
|
||||
}
|
||||
return '${_formatCount(n)} ${n == 1 ? one : other}';
|
||||
}
|
||||
|
||||
String _formatCount(num n) =>
|
||||
n == n.roundToDouble() ? n.toInt().toString() : n.toString();
|
||||
|
|
|
|||
256
apps/app_seeds/lib/ui/quantity_picker.dart
Normal file
256
apps/app_seeds/lib/ui/quantity_picker.dart
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
import 'package:commons_core/commons_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../db/enums.dart';
|
||||
import '../i18n/strings.g.dart';
|
||||
import 'quantity_kind_l10n.dart';
|
||||
import 'seed_glyph.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
/// Informal seed amounts, ascending: vibes → containers → seed/fruit forms.
|
||||
const seedScaleKinds = <QuantityKind>[
|
||||
QuantityKind.aFew,
|
||||
QuantityKind.handful,
|
||||
QuantityKind.teaspoon,
|
||||
QuantityKind.spoon,
|
||||
QuantityKind.cup,
|
||||
QuantityKind.jar,
|
||||
QuantityKind.sack,
|
||||
QuantityKind.packet,
|
||||
QuantityKind.cob,
|
||||
QuantityKind.pod,
|
||||
QuantityKind.ear,
|
||||
QuantityKind.head,
|
||||
QuantityKind.fruit,
|
||||
QuantityKind.bulb,
|
||||
QuantityKind.tuber,
|
||||
QuantityKind.bunch,
|
||||
];
|
||||
|
||||
/// Units for a plant/seedling lot.
|
||||
const plantScaleKinds = <QuantityKind>[
|
||||
QuantityKind.plant,
|
||||
QuantityKind.pot,
|
||||
QuantityKind.tray,
|
||||
];
|
||||
|
||||
List<QuantityKind> kindsForType(LotType type) =>
|
||||
type == LotType.seed ? seedScaleKinds : plantScaleKinds;
|
||||
|
||||
/// Seed vs plant/seedling segmented selector.
|
||||
class LotTypeSelector extends StatelessWidget {
|
||||
const LotTypeSelector({
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final LotType value;
|
||||
final ValueChanged<LotType> onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.t;
|
||||
return SegmentedButton<LotType>(
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: LotType.seed,
|
||||
label: Text(t.lotType.seed),
|
||||
icon: const SeedGlyph(SeedGlyphs.jars, size: 18),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: LotType.plant,
|
||||
label: Text(t.lotType.plant),
|
||||
icon: const Icon(Icons.local_florist_outlined, size: 18),
|
||||
),
|
||||
],
|
||||
selected: {value},
|
||||
showSelectedIcon: false,
|
||||
onSelectionChanged: (s) => onChanged(s.first),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A horizontal scale of large unit icons + an optional number stepper. Emits a
|
||||
/// [Quantity] (or null while nothing is picked) via [onChanged].
|
||||
class QuantityPicker extends StatefulWidget {
|
||||
const QuantityPicker({
|
||||
required this.type,
|
||||
required this.onChanged,
|
||||
this.value,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final LotType type;
|
||||
final Quantity? value;
|
||||
final ValueChanged<Quantity?> onChanged;
|
||||
|
||||
@override
|
||||
State<QuantityPicker> createState() => _QuantityPickerState();
|
||||
}
|
||||
|
||||
class _QuantityPickerState extends State<QuantityPicker> {
|
||||
QuantityKind? _kind;
|
||||
final _countController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_kind = widget.value?.kind;
|
||||
final c = widget.value?.count;
|
||||
if (c != null) _countController.text = _fmt(c);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(QuantityPicker old) {
|
||||
super.didUpdateWidget(old);
|
||||
// Switching seed/plant, or the owner clearing the value, drops a stale
|
||||
// selection. We only update local fields here (no onChanged during build);
|
||||
// the owner clears its own value alongside the type change.
|
||||
if (widget.value == null && _kind != null) {
|
||||
_kind = null;
|
||||
_countController.clear();
|
||||
} else if (widget.type != old.type &&
|
||||
_kind != null &&
|
||||
!kindsForType(widget.type).contains(_kind)) {
|
||||
_kind = null;
|
||||
_countController.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_countController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _emit() {
|
||||
final kind = _kind;
|
||||
if (kind == null) {
|
||||
widget.onChanged(null);
|
||||
return;
|
||||
}
|
||||
final n = num.tryParse(_countController.text.trim().replaceAll(',', '.'));
|
||||
widget.onChanged(Quantity(kind: kind, count: kind.countable ? n : null));
|
||||
}
|
||||
|
||||
void _select(QuantityKind kind) {
|
||||
setState(() => _kind = kind);
|
||||
_emit();
|
||||
}
|
||||
|
||||
void _bump(int delta) {
|
||||
final current = int.tryParse(_countController.text.trim()) ?? 0;
|
||||
final next = (current + delta).clamp(0, 9999);
|
||||
_countController.text = next == 0 ? '' : '$next';
|
||||
_emit();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final t = context.t;
|
||||
final kinds = kindsForType(widget.type);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 76,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: kinds.length,
|
||||
separatorBuilder: (_, _) => const SizedBox(width: 4),
|
||||
itemBuilder: (context, i) {
|
||||
final kind = kinds[i];
|
||||
return _ScaleItem(
|
||||
kind: kind,
|
||||
label: unitLabel(t, kind),
|
||||
selected: _kind == kind,
|
||||
onTap: () => _select(kind),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (_kind?.countable ?? false) ...[
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
IconButton.filledTonal(
|
||||
onPressed: () => _bump(-1),
|
||||
icon: const Icon(Icons.remove),
|
||||
),
|
||||
SizedBox(
|
||||
width: 64,
|
||||
child: TextField(
|
||||
key: const Key('quantity.count'),
|
||||
controller: _countController,
|
||||
textAlign: TextAlign.center,
|
||||
keyboardType: TextInputType.number,
|
||||
decoration: const InputDecoration(isDense: true),
|
||||
onChanged: (_) => _emit(),
|
||||
),
|
||||
),
|
||||
IconButton.filledTonal(
|
||||
onPressed: () => _bump(1),
|
||||
icon: const Icon(Icons.add),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
unitLabel(t, _kind!),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _fmt(num n) =>
|
||||
n == n.roundToDouble() ? n.toInt().toString() : n.toString();
|
||||
|
||||
class _ScaleItem extends StatelessWidget {
|
||||
const _ScaleItem({
|
||||
required this.kind,
|
||||
required this.label,
|
||||
required this.selected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final QuantityKind kind;
|
||||
final String label;
|
||||
final bool selected;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = selected ? seedGreenDark : Colors.black54;
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Container(
|
||||
width: 68,
|
||||
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: selected ? seedGreen.withValues(alpha: 0.15) : null,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
QuantityKindIcon(kind, size: 30, color: color),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
label,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(fontSize: 11, color: color),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:commons_core/commons_core.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
|
@ -8,24 +7,12 @@ import 'package:image_picker/image_picker.dart';
|
|||
import '../data/variety_repository.dart';
|
||||
import '../i18n/strings.g.dart';
|
||||
import '../state/quick_add_cubit.dart';
|
||||
import 'quantity_kind_l10n.dart';
|
||||
import 'seed_glyph.dart';
|
||||
import 'quantity_picker.dart';
|
||||
|
||||
/// Picks a photo and returns its bytes (or null if cancelled). Injected so
|
||||
/// widget tests can supply a fake instead of the camera plugin.
|
||||
typedef PhotoPicker = Future<Uint8List?> Function();
|
||||
|
||||
/// A short, high-frequency subset of quantity units shown up front to keep the
|
||||
/// add flow fast; the rest live behind "Add more…".
|
||||
const _quickKinds = <QuantityKind>[
|
||||
QuantityKind.aFew,
|
||||
QuantityKind.handful,
|
||||
QuantityKind.packet,
|
||||
QuantityKind.pod,
|
||||
QuantityKind.cob,
|
||||
QuantityKind.grams,
|
||||
];
|
||||
|
||||
Future<void> showQuickAddSheet(
|
||||
BuildContext context, {
|
||||
required VarietyRepository repository,
|
||||
|
|
@ -94,22 +81,24 @@ class QuickAddSheet extends StatelessWidget {
|
|||
onSubmitted: (_) => cubit.submit(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
LotTypeSelector(
|
||||
value: state.lotType,
|
||||
onChanged: (type) {
|
||||
cubit
|
||||
..setLotType(type)
|
||||
..setQuantity(null);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
t.quickAdd.quantity,
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
for (final kind in _quickKinds)
|
||||
ChoiceChip(
|
||||
avatar: QuantityKindIcon(kind, size: 18),
|
||||
label: Text(quantityKindLabel(t, kind)),
|
||||
selected: state.quantityKind == kind,
|
||||
onSelected: (_) => cubit.selectQuantity(kind),
|
||||
),
|
||||
],
|
||||
QuantityPicker(
|
||||
type: state.lotType,
|
||||
value: state.quantity,
|
||||
onChanged: cubit.setQuantity,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Align(
|
||||
|
|
|
|||
|
|
@ -46,9 +46,13 @@ String? seedGlyphForKind(QuantityKind kind) => switch (kind) {
|
|||
QuantityKind.aFew => SeedGlyphs.scattered,
|
||||
QuantityKind.some => SeedGlyphs.smallSpoon,
|
||||
QuantityKind.plenty => SeedGlyphs.jars,
|
||||
QuantityKind.handful => SeedGlyphs.bigSpoon,
|
||||
QuantityKind.pinch => SeedGlyphs.scattered,
|
||||
QuantityKind.handful => SeedGlyphs.bigSpoon,
|
||||
QuantityKind.teaspoon => SeedGlyphs.smallSpoon,
|
||||
QuantityKind.spoon => SeedGlyphs.bigSpoon,
|
||||
QuantityKind.cup => SeedGlyphs.mug,
|
||||
QuantityKind.jar => SeedGlyphs.jar,
|
||||
QuantityKind.sack => SeedGlyphs.sack,
|
||||
QuantityKind.packet => SeedGlyphs.envelope,
|
||||
_ => null,
|
||||
};
|
||||
|
|
@ -59,6 +63,9 @@ IconData _materialIconForKind(QuantityKind kind) => switch (kind) {
|
|||
QuantityKind.cob || QuantityKind.ear => Icons.grass,
|
||||
QuantityKind.head || QuantityKind.seedHead => Icons.filter_vintage_outlined,
|
||||
QuantityKind.fruit => Icons.local_florist_outlined,
|
||||
QuantityKind.plant => Icons.local_florist_outlined,
|
||||
QuantityKind.pot => Icons.yard_outlined,
|
||||
QuantityKind.tray => Icons.grid_on_outlined,
|
||||
QuantityKind.bulb ||
|
||||
QuantityKind.tuber ||
|
||||
QuantityKind.bunch => Icons.spa_outlined,
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||
|
||||
import '../data/species_repository.dart';
|
||||
import '../data/variety_repository.dart';
|
||||
import '../db/enums.dart';
|
||||
import '../i18n/strings.g.dart';
|
||||
import '../state/variety_detail_cubit.dart';
|
||||
import 'quantity_kind_l10n.dart';
|
||||
import 'quantity_picker.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
|
||||
/// names and lots. Edits go through [VarietyDetailCubit]; the view is reactive.
|
||||
|
|
@ -144,20 +146,11 @@ String _lotSubtitle(Translations t, VarietyLot lot) {
|
|||
t.detail.year(year: lot.harvestYear!)
|
||||
else
|
||||
t.detail.noYear,
|
||||
if (lot.quantity != null) _quantityLabel(t, lot.quantity!),
|
||||
if (lot.quantity != null) quantityDisplay(t, lot.quantity!),
|
||||
];
|
||||
return parts.join(' · ');
|
||||
}
|
||||
|
||||
String _quantityLabel(Translations t, Quantity q) {
|
||||
final kind = quantityKindLabel(t, q.kind);
|
||||
if (q.precise != null) return '${_trimDouble(q.precise!)} $kind';
|
||||
return kind;
|
||||
}
|
||||
|
||||
String _trimDouble(double v) =>
|
||||
v == v.roundToDouble() ? v.toStringAsFixed(0) : v.toString();
|
||||
|
||||
String _germinationPercent(Translations t, double rate) =>
|
||||
t.germination.result(percent: (rate * 100).round());
|
||||
|
||||
|
|
@ -485,7 +478,8 @@ class _EditVarietySheetState extends State<_EditVarietySheet> {
|
|||
Future<void> _showAddLotSheet(BuildContext context, VarietyDetailCubit cubit) {
|
||||
final t = context.t;
|
||||
final yearController = TextEditingController();
|
||||
QuantityKind? selectedKind;
|
||||
var selectedType = LotType.seed;
|
||||
Quantity? selectedQuantity;
|
||||
return showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
|
|
@ -506,6 +500,14 @@ Future<void> _showAddLotSheet(BuildContext context, VarietyDetailCubit cubit) {
|
|||
style: Theme.of(sheetContext).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
LotTypeSelector(
|
||||
value: selectedType,
|
||||
onChanged: (type) => setState(() {
|
||||
selectedType = type;
|
||||
selectedQuantity = null;
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
key: const Key('addLot.year'),
|
||||
controller: yearController,
|
||||
|
|
@ -521,17 +523,10 @@ Future<void> _showAddLotSheet(BuildContext context, VarietyDetailCubit cubit) {
|
|||
style: Theme.of(sheetContext).textTheme.labelLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
for (final kind in _addLotKinds)
|
||||
ChoiceChip(
|
||||
avatar: QuantityKindIcon(kind, size: 18),
|
||||
label: Text(quantityKindLabel(t, kind)),
|
||||
selected: selectedKind == kind,
|
||||
onSelected: (_) => setState(() => selectedKind = kind),
|
||||
),
|
||||
],
|
||||
QuantityPicker(
|
||||
type: selectedType,
|
||||
value: selectedQuantity,
|
||||
onChanged: (q) => selectedQuantity = q,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
|
|
@ -545,10 +540,9 @@ Future<void> _showAddLotSheet(BuildContext context, VarietyDetailCubit cubit) {
|
|||
key: const Key('addLot.save'),
|
||||
onPressed: () {
|
||||
cubit.addLot(
|
||||
type: selectedType,
|
||||
year: int.tryParse(yearController.text.trim()),
|
||||
quantity: selectedKind == null
|
||||
? null
|
||||
: Quantity(kind: selectedKind!),
|
||||
quantity: selectedQuantity,
|
||||
);
|
||||
Navigator.of(sheetContext).pop();
|
||||
},
|
||||
|
|
@ -563,15 +557,6 @@ Future<void> _showAddLotSheet(BuildContext context, VarietyDetailCubit cubit) {
|
|||
);
|
||||
}
|
||||
|
||||
const _addLotKinds = <QuantityKind>[
|
||||
QuantityKind.aFew,
|
||||
QuantityKind.handful,
|
||||
QuantityKind.packet,
|
||||
QuantityKind.pod,
|
||||
QuantityKind.cob,
|
||||
QuantityKind.grams,
|
||||
];
|
||||
|
||||
String? _nullIfBlank(String value) {
|
||||
final trimmed = value.trim();
|
||||
return trimmed.isEmpty ? null : trimmed;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue