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:
vjrj 2026-07-10 19:14:04 +02:00
parent 432acded29
commit efb369eb81
19 changed files with 1465 additions and 116 deletions

View file

@ -62,6 +62,7 @@ class TranslationsEs extends Translations with BaseTranslations<AppLocale, Trans
@override late final _Translations$provenance$es provenance = _Translations$provenance$es._(_root);
@override late final _Translations$abundance$es abundance = _Translations$abundance$es._(_root);
@override late final _Translations$share$es share = _Translations$share$es._(_root);
@override late final _Translations$printLabels$es printLabels = _Translations$printLabels$es._(_root);
@override late final _Translations$cropCalendar$es cropCalendar = _Translations$cropCalendar$es._(_root);
@override late final _Translations$needsReproduction$es needsReproduction = _Translations$needsReproduction$es._(_root);
@override late final _Translations$preservation$es preservation = _Translations$preservation$es._(_root);
@ -487,6 +488,30 @@ class _Translations$share$es extends Translations$share$en {
@override String get cancelled => 'Cancelado';
}
// Path: printLabels
class _Translations$printLabels$es extends Translations$printLabels$en {
_Translations$printLabels$es._(TranslationsEs root) : this._root = root, super.internal(root);
final TranslationsEs _root; // ignore: unused_field
// Translations
@override String get action => 'Imprimir etiquetas';
@override String get title => 'Imprimir etiquetas';
@override String get selectHint => 'Elige las semillas para imprimir sus etiquetas';
@override String get selectAll => 'Seleccionar todo';
@override String selected({required Object n}) => '${n} seleccionadas';
@override String get none => 'Selecciona semillas primero';
@override String get format => 'Tamaño de etiqueta';
@override String get formatStickers => 'Pegatinas pequeñas';
@override String get formatStickersHint => 'Muchas etiquetas pequeñas por hoja — para pegar en sobres';
@override String get formatCards => 'Tarjetas grandes';
@override String get formatCardsHint => 'Menos etiquetas, más grandes — para botes y cajas';
@override String count({required Object n}) => '${n} etiquetas';
@override String get save => 'Guardar etiquetas';
@override String get saved => 'Etiquetas guardadas';
@override String get cancelled => 'Cancelado';
}
// Path: cropCalendar
class _Translations$cropCalendar$es extends Translations$cropCalendar$en {
_Translations$cropCalendar$es._(TranslationsEs root) : this._root = root, super.internal(root);
@ -1228,6 +1253,21 @@ extension on TranslationsEs {
'share.catalogTitle' => 'Lo que comparto',
'share.catalogSaved' => 'Catálogo guardado',
'share.cancelled' => 'Cancelado',
'printLabels.action' => 'Imprimir etiquetas',
'printLabels.title' => 'Imprimir etiquetas',
'printLabels.selectHint' => 'Elige las semillas para imprimir sus etiquetas',
'printLabels.selectAll' => 'Seleccionar todo',
'printLabels.selected' => ({required Object n}) => '${n} seleccionadas',
'printLabels.none' => 'Selecciona semillas primero',
'printLabels.format' => 'Tamaño de etiqueta',
'printLabels.formatStickers' => 'Pegatinas pequeñas',
'printLabels.formatStickersHint' => 'Muchas etiquetas pequeñas por hoja — para pegar en sobres',
'printLabels.formatCards' => 'Tarjetas grandes',
'printLabels.formatCardsHint' => 'Menos etiquetas, más grandes — para botes y cajas',
'printLabels.count' => ({required Object n}) => '${n} etiquetas',
'printLabels.save' => 'Guardar etiquetas',
'printLabels.saved' => 'Etiquetas guardadas',
'printLabels.cancelled' => 'Cancelado',
'cropCalendar.add' => 'Calendario de cultivo',
'cropCalendar.title' => 'Calendario de cultivo',
'cropCalendar.sow' => 'Siembra',