feat(inventory): photo-first drafts + on-device OCR (digitization R2+R4)
Lower the bulk-digitization cliff with two more routes on top of the already-landed CSV import and "save and add another": - Photo-first drafts (capture now, catalogue later): burst-capture photos (camera or multi-gallery) into unnamed draft varieties, shown in a "to catalogue" tray, hidden from the main list until named. Adds Variety.isDraft (schema), addDraftVariety/watchDrafts/nameDraft, the triage sheet and the inventory banner. - On-device OCR label suggestion (Tesseract, offline, no Google): a "Suggest name from photo" button in the naming dialog behind a LabelTextExtractor interface (Tesseract on Android/iOS, no-op elsewhere). Reads the largest print via hOCR bounding boxes, drops boilerplate/low-confidence noise, preprocesses (grayscale, contrast, upscale) and sweeps rotations (0-315 deg) so tilted packets still read. Bundles tessdata_fast eng+spa; validated on-device against real packets. The photo is written to a temp file deleted immediately in a finally block (the plugin needs a path) - a bounded, documented exception to no-plaintext-at-rest. This commit also carries the co-developed schema evolution v5 to v8 that shares these files (organic flag, species viability years, crop calendar, lot provenance/abundance/preservation format, condition checks) plus their exports/migrations and i18n. Tests: CSV/draft/OCR unit + widget + migration green in isolation. Note: the full widget suite currently hangs (>10 min) - under investigation.
This commit is contained in:
parent
12a2ee2d64
commit
6809dc6143
89 changed files with 17141 additions and 228 deletions
|
|
@ -7,10 +7,12 @@ import 'package:go_router/go_router.dart';
|
|||
import 'data/species_repository.dart';
|
||||
import 'data/variety_repository.dart';
|
||||
import 'i18n/strings.g.dart';
|
||||
import 'services/onboarding_store.dart';
|
||||
import 'state/inventory_cubit.dart';
|
||||
import 'state/variety_detail_cubit.dart';
|
||||
import 'ui/about_screen.dart';
|
||||
import 'ui/home_screen.dart';
|
||||
import 'ui/intro_screen.dart';
|
||||
import 'ui/inventory_list_screen.dart';
|
||||
import 'ui/settings_screen.dart';
|
||||
import 'ui/theme.dart';
|
||||
|
|
@ -18,18 +20,40 @@ import 'ui/variety_detail_screen.dart';
|
|||
|
||||
/// Root widget. Provides the repositories to the tree and wires go_router:
|
||||
/// `/` is the home menu, `/inventory` the list, `/variety/:id` the item detail.
|
||||
/// When [showIntro] is set (first launch), the app opens on `/intro`.
|
||||
class TaneApp extends StatelessWidget {
|
||||
TaneApp({required this.repository, required this.species, super.key})
|
||||
: _router = _buildRouter(repository);
|
||||
TaneApp({
|
||||
required this.repository,
|
||||
required this.species,
|
||||
required this.onboarding,
|
||||
this.showIntro = false,
|
||||
super.key,
|
||||
}) : _router = _buildRouter(repository, onboarding, showIntro);
|
||||
|
||||
final VarietyRepository repository;
|
||||
final SpeciesRepository species;
|
||||
final OnboardingStore onboarding;
|
||||
final bool showIntro;
|
||||
final GoRouter _router;
|
||||
|
||||
static GoRouter _buildRouter(VarietyRepository repository) {
|
||||
static GoRouter _buildRouter(
|
||||
VarietyRepository repository,
|
||||
OnboardingStore onboarding,
|
||||
bool showIntro,
|
||||
) {
|
||||
return GoRouter(
|
||||
initialLocation: showIntro ? '/intro' : '/',
|
||||
routes: [
|
||||
GoRoute(path: '/', builder: (context, state) => const HomeScreen()),
|
||||
GoRoute(
|
||||
path: '/intro',
|
||||
builder: (context, state) => IntroScreen(
|
||||
onDone: () async {
|
||||
await onboarding.markIntroSeen();
|
||||
if (context.mounted) context.go('/');
|
||||
},
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/settings',
|
||||
builder: (context, state) => const SettingsScreen(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue