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.
108 lines
2.9 KiB
Dart
108 lines
2.9 KiB
Dart
import 'dart:typed_data';
|
|
|
|
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';
|
|
|
|
const Object _unset = Object();
|
|
|
|
/// Form state for the quick-add sheet. Only [label] is required; everything
|
|
/// else is progressive disclosure behind [expanded].
|
|
class QuickAddState extends Equatable {
|
|
const QuickAddState({
|
|
this.label = '',
|
|
this.quantity,
|
|
this.lotType = LotType.seed,
|
|
this.photoBytes,
|
|
this.expanded = false,
|
|
this.submitting = false,
|
|
this.submitted = false,
|
|
this.showLabelError = false,
|
|
});
|
|
|
|
final String label;
|
|
final Quantity? quantity;
|
|
final LotType lotType;
|
|
final Uint8List? photoBytes;
|
|
final bool expanded;
|
|
final bool submitting;
|
|
final bool submitted;
|
|
final bool showLabelError;
|
|
|
|
bool get hasValidLabel => label.trim().isNotEmpty;
|
|
|
|
QuickAddState copyWith({
|
|
String? label,
|
|
Object? quantity = _unset,
|
|
LotType? lotType,
|
|
Uint8List? photoBytes,
|
|
bool? expanded,
|
|
bool? submitting,
|
|
bool? submitted,
|
|
bool? showLabelError,
|
|
}) {
|
|
return QuickAddState(
|
|
label: label ?? this.label,
|
|
quantity: identical(quantity, _unset)
|
|
? this.quantity
|
|
: quantity as Quantity?,
|
|
lotType: lotType ?? this.lotType,
|
|
photoBytes: photoBytes ?? this.photoBytes,
|
|
expanded: expanded ?? this.expanded,
|
|
submitting: submitting ?? this.submitting,
|
|
submitted: submitted ?? this.submitted,
|
|
showLabelError: showLabelError ?? this.showLabelError,
|
|
);
|
|
}
|
|
|
|
@override
|
|
List<Object?> get props => [
|
|
label,
|
|
quantity,
|
|
lotType,
|
|
photoBytes,
|
|
expanded,
|
|
submitting,
|
|
submitted,
|
|
showLabelError,
|
|
];
|
|
}
|
|
|
|
/// Drives the 20-second quick-add flow and persists via [VarietyRepository].
|
|
class QuickAddCubit extends Cubit<QuickAddState> {
|
|
QuickAddCubit(this._repo) : super(const QuickAddState());
|
|
|
|
final VarietyRepository _repo;
|
|
|
|
void labelChanged(String value) =>
|
|
emit(state.copyWith(label: value, showLabelError: false));
|
|
|
|
void setQuantity(Quantity? quantity) =>
|
|
emit(state.copyWith(quantity: quantity));
|
|
|
|
void setLotType(LotType type) => emit(state.copyWith(lotType: type));
|
|
|
|
void photoPicked(Uint8List bytes) => emit(state.copyWith(photoBytes: bytes));
|
|
|
|
void toggleExpanded() => emit(state.copyWith(expanded: !state.expanded));
|
|
|
|
/// Validates and saves. No-op (sets [showLabelError]) if the label is empty.
|
|
Future<void> submit() async {
|
|
if (!state.hasValidLabel) {
|
|
emit(state.copyWith(showLabelError: true));
|
|
return;
|
|
}
|
|
if (state.submitting) return;
|
|
emit(state.copyWith(submitting: true));
|
|
await _repo.addQuickVariety(
|
|
label: state.label.trim(),
|
|
lotType: state.lotType,
|
|
quantity: state.quantity,
|
|
photoBytes: state.photoBytes,
|
|
);
|
|
emit(state.copyWith(submitting: false, submitted: true));
|
|
}
|
|
}
|