feat(labels): print seed labels with QR from an inventory selection
Add multi-select to the inventory and a "Print labels" flow that renders a PDF sheet of labels (small stickers or large cards), each carrying the seed's common name, variety, scientific name, year/origin and a tanemaki://seed QR that holds the seed's data offline (future scan-to-import). - SeedLabelCodec: compact, versioned tanemaki://seed URI (encode/decode) - LabelSheetService: pure-Dart PDF grid (pdf + barcode + DejaVu), RTL-aware - VarietyRepository.labelRows: one row per lot, locale-picked common name - InventoryCubit selection mode; contextual app bar + label print sheet - i18n en/es/pt (printLabels.*) Tests: codec, PDF service (both formats + RTL), labelRows, selection cubit, inventory widget selection.
This commit is contained in:
parent
0cecb943f0
commit
bf091bf852
19 changed files with 1465 additions and 116 deletions
|
|
@ -20,6 +20,8 @@ class InventoryState extends Equatable {
|
|||
this.needsReproductionOnly = false,
|
||||
this.sharingOnly = false,
|
||||
this.loading = true,
|
||||
this.selectionMode = false,
|
||||
this.selectedIds = const {},
|
||||
});
|
||||
|
||||
final List<VarietyListItem> items;
|
||||
|
|
@ -49,6 +51,13 @@ class InventoryState extends Equatable {
|
|||
|
||||
final bool loading;
|
||||
|
||||
/// Whether the list is in multi-select mode — used to pick a subset of the
|
||||
/// inventory to print labels for.
|
||||
final bool selectionMode;
|
||||
|
||||
/// Ids of the varieties currently selected in [selectionMode].
|
||||
final Set<String> selectedIds;
|
||||
|
||||
/// Categories present across all items, in display order (deduped), so the
|
||||
/// UI can offer one chip per category actually in use.
|
||||
List<String> get categories {
|
||||
|
|
@ -101,6 +110,8 @@ class InventoryState extends Equatable {
|
|||
bool? needsReproductionOnly,
|
||||
bool? sharingOnly,
|
||||
bool? loading,
|
||||
bool? selectionMode,
|
||||
Set<String>? selectedIds,
|
||||
}) {
|
||||
return InventoryState(
|
||||
items: items ?? this.items,
|
||||
|
|
@ -113,6 +124,8 @@ class InventoryState extends Equatable {
|
|||
needsReproductionOnly ?? this.needsReproductionOnly,
|
||||
sharingOnly: sharingOnly ?? this.sharingOnly,
|
||||
loading: loading ?? this.loading,
|
||||
selectionMode: selectionMode ?? this.selectionMode,
|
||||
selectedIds: selectedIds ?? this.selectedIds,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -127,6 +140,8 @@ class InventoryState extends Equatable {
|
|||
needsReproductionOnly,
|
||||
sharingOnly,
|
||||
loading,
|
||||
selectionMode,
|
||||
selectedIds,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -177,6 +192,36 @@ class InventoryCubit extends Cubit<InventoryState> {
|
|||
void toggleSharingOnly() =>
|
||||
emit(state.copyWith(sharingOnly: !state.sharingOnly));
|
||||
|
||||
/// Enters multi-select mode with an empty selection (from the toolbar).
|
||||
void startSelection() =>
|
||||
emit(state.copyWith(selectionMode: true, selectedIds: const {}));
|
||||
|
||||
/// Enters multi-select mode with [id] as the first selected variety
|
||||
/// (triggered by a long-press on a tile).
|
||||
void enterSelection(String id) =>
|
||||
emit(state.copyWith(selectionMode: true, selectedIds: {id}));
|
||||
|
||||
/// Toggles a variety in the selection (add if absent, remove if present).
|
||||
/// An empty selection stays in selection mode; the user exits explicitly.
|
||||
void toggleSelection(String id) {
|
||||
final next = Set<String>.of(state.selectedIds);
|
||||
if (!next.remove(id)) next.add(id);
|
||||
emit(state.copyWith(selectionMode: true, selectedIds: next));
|
||||
}
|
||||
|
||||
/// Selects every currently visible variety, so "filter to a category, then
|
||||
/// select all" is the way to pick a subset of the inventory to print.
|
||||
void selectAllVisible() => emit(
|
||||
state.copyWith(
|
||||
selectionMode: true,
|
||||
selectedIds: state.visibleItems.map((i) => i.id).toSet(),
|
||||
),
|
||||
);
|
||||
|
||||
/// Leaves selection mode and clears the selection.
|
||||
void exitSelection() =>
|
||||
emit(state.copyWith(selectionMode: false, selectedIds: const {}));
|
||||
|
||||
/// Clears all filters (search is left untouched).
|
||||
void clearFilters() => emit(
|
||||
state.copyWith(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue