Closes the physical↔digital loop the labels opened: the QR on a printed envelope (tane://seed, already encoded by seed_label_codec) can now be scanned from the inventory bar. A known label opens its variety; an unknown one asks before adding anything, then creates the variety (species linked by exact scientific name when bundled) with a lot carrying the label's year and origin. Scanner is pure ZXing (zxing_barcode_scanner, MIT) — no ML Kit, no Play Services, F-Droid-safe, the stack Ğ1nkgo ships. Mobile-only; other platforms don't show the button (OCR precedent). The post-scan flow is a plain function (handleScannedPayload) so it's widget-tested without a camera.
66 lines
2.4 KiB
Dart
66 lines
2.4 KiB
Dart
import 'package:equatable/equatable.dart';
|
|
|
|
import '../data/export_import/seed_label_codec.dart';
|
|
import '../data/species_repository.dart';
|
|
import '../data/variety_repository.dart';
|
|
|
|
/// What scanning a QR yielded: not a seed label at all, a variety already in
|
|
/// the collection ([varietyId] set), or a seed not held yet ([data] set — the
|
|
/// UI asks before adding anything).
|
|
class SeedScanResult extends Equatable {
|
|
const SeedScanResult.notALabel() : varietyId = null, data = null;
|
|
const SeedScanResult.match(String this.varietyId) : data = null;
|
|
const SeedScanResult.unknown(SeedLabelData this.data) : varietyId = null;
|
|
|
|
final String? varietyId;
|
|
final SeedLabelData? data;
|
|
|
|
@override
|
|
List<Object?> get props => [varietyId, data];
|
|
}
|
|
|
|
/// Resolves a scanned QR payload against the collection: decodes the
|
|
/// `tane://seed` label and looks the variety up by its label (the identity a
|
|
/// keeper printed on the envelope). This is how a physical packet finds its
|
|
/// way back to its record — even a re-saved one whose digital thread broke.
|
|
Future<SeedScanResult> resolveScannedLabel(
|
|
VarietyRepository repository,
|
|
String raw,
|
|
) async {
|
|
final data = SeedLabelCodec.decode(raw);
|
|
if (data == null) return const SeedScanResult.notALabel();
|
|
final varietyId = await repository.findVarietyIdByLabel(data.varietyLabel);
|
|
return varietyId == null
|
|
? SeedScanResult.unknown(data)
|
|
: SeedScanResult.match(varietyId);
|
|
}
|
|
|
|
/// Adds the scanned seed to the collection — a variety named as the label
|
|
/// says, linked to the bundled species when the scientific name matches
|
|
/// exactly, with one lot carrying the label's harvest year and origin.
|
|
/// Called only after the person confirmed the add prompt.
|
|
Future<String> importScannedLabel({
|
|
required VarietyRepository repository,
|
|
required SpeciesRepository species,
|
|
required SeedLabelData data,
|
|
}) async {
|
|
final varietyId = await repository.addQuickVariety(label: data.varietyLabel);
|
|
|
|
final scientific = data.scientificName?.trim();
|
|
if (scientific != null && scientific.isNotEmpty) {
|
|
final matches = await species.search(scientific, limit: 4);
|
|
for (final m in matches) {
|
|
if (m.scientificName.toLowerCase() == scientific.toLowerCase()) {
|
|
await repository.linkSpecies(varietyId, m.id);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
await repository.addLot(
|
|
varietyId: varietyId,
|
|
harvestYear: data.year,
|
|
originName: data.origin,
|
|
);
|
|
return varietyId;
|
|
}
|