feat(inventory): local sharing — per-lot share terms, filter and printable catalog

The local half of the original Fase 2 (no network, no Offer entity yet):
- Lot sheet gains a 'do you share it?' choice (gift/swap first, sale last,
  never the default); declaring plenty of abundance reveals it with a nudge.
- Lot lines and list tiles badge shared batches; an 'I share' filter and
  a printable PDF catalog (paper bridge for fairs) round out the view.
- Catalog PDF embeds DejaVu (Latin-ext/Cyrillic/Greek); fonts are a
  per-locale resource like the OCR packs.
This commit is contained in:
vjrj 2026-07-09 22:19:55 +02:00
parent e3ec855630
commit ba87bf2719
20 changed files with 982 additions and 36 deletions

View file

@ -0,0 +1,66 @@
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:tane/services/file_service.dart';
import 'package:tane/services/share_catalog_service.dart';
/// Captures the bytes handed to the save dialog instead of touching disk.
class _RecordingFileService implements FileService {
Uint8List? saved;
String? savedName;
@override
Future<String?> saveFile({
required String suggestedName,
required Uint8List bytes,
}) async {
saved = bytes;
savedName = suggestedName;
return '/dev/null/$suggestedName';
}
@override
Future<Uint8List?> pickFileBytes({List<String>? allowedExtensions}) async =>
null;
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
test('buildPdf renders a well-formed PDF with the given rows', () async {
final service = ShareCatalogService(files: _RecordingFileService());
final bytes = await service.buildPdf(
title: 'What I share',
date: 'July 9, 2026',
rows: const [
ShareCatalogRow(
name: 'Rebordanes maize',
scientificName: 'Zea mays',
details: 'Seeds · Year 2024 · 2 cobs',
mode: 'To give away',
),
ShareCatalogRow(name: 'Alubia de Tolosa', mode: 'To swap'),
],
);
// %PDF header + non-trivial body; text content is compressed, so the
// human-readable row assertions live in the widget/data tests.
expect(String.fromCharCodes(bytes.take(5)), '%PDF-');
expect(bytes.length, greaterThan(1000));
});
test('saveCatalog hands the PDF to the save dialog', () async {
final files = _RecordingFileService();
final service = ShareCatalogService(files: files);
final saved = await service.saveCatalog(
title: 'What I share',
date: 'July 9, 2026',
suggestedName: 'tanemaki-catalog-2026-07-09.pdf',
rows: const [ShareCatalogRow(name: 'Maize', mode: 'To give away')],
);
expect(saved, isTrue);
expect(files.savedName, 'tanemaki-catalog-2026-07-09.pdf');
expect(String.fromCharCodes(files.saved!.take(5)), '%PDF-');
});
}