tane/apps/app_seeds/lib/state/variety_detail_cubit.dart
vjrj 3942975dba 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.
2026-07-08 10:51:02 +02:00

92 lines
2.2 KiB
Dart

import 'dart:async';
import 'package:commons_core/commons_core.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../data/variety_repository.dart';
import '../db/enums.dart';
class VarietyDetailState extends Equatable {
const VarietyDetailState({
this.detail,
this.loading = true,
this.deleted = false,
});
final VarietyDetail? detail;
final bool loading;
final bool deleted;
@override
List<Object?> get props => [detail, loading, deleted];
}
/// Streams one variety's detail and edits it through the repository.
class VarietyDetailCubit extends Cubit<VarietyDetailState> {
VarietyDetailCubit(this._repo, this.varietyId)
: super(const VarietyDetailState()) {
_sub = _repo
.watchVariety(varietyId)
.listen(
(d) => emit(
VarietyDetailState(
detail: d,
loading: false,
deleted: state.deleted,
),
),
);
}
final VarietyRepository _repo;
final String varietyId;
late final StreamSubscription<VarietyDetail?> _sub;
Future<void> updateFields({String? label, String? category, String? notes}) =>
_repo.updateVariety(
id: varietyId,
label: label,
category: category,
notes: notes,
);
Future<void> addLot({
LotType type = LotType.seed,
int? year,
Quantity? quantity,
}) => _repo.addLot(
varietyId: varietyId,
type: type,
harvestYear: year,
quantity: quantity,
);
Future<void> linkSpecies(String speciesId) =>
_repo.linkSpecies(varietyId, speciesId);
Future<void> addGerminationTest({
required String lotId,
int? testedOn,
int? sampleSize,
int? germinatedCount,
}) => _repo.addGerminationTest(
lotId: lotId,
testedOn: testedOn,
sampleSize: sampleSize,
germinatedCount: germinatedCount,
);
Future<void> deleteVariety() async {
await _repo.softDeleteVariety(varietyId);
emit(
VarietyDetailState(detail: state.detail, loading: false, deleted: true),
);
}
@override
Future<void> close() async {
await _sub.cancel();
return super.close();
}
}