import 'dart:io'; import 'package:commons_core/commons_core.dart'; import 'package:flutter/services.dart' show rootBundle; import 'package:flutter/widgets.dart'; import 'package:get_it/get_it.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import '../data/species_catalog.dart'; import '../data/species_repository.dart'; import '../data/variety_repository.dart'; import '../db/database.dart'; import '../db/encrypted_executor.dart'; import '../i18n/strings.g.dart'; import '../security/secret_store.dart'; import '../security/secure_key_store.dart'; import '../services/export_import_service.dart'; import '../services/file_picker_file_service.dart'; import '../services/file_service.dart'; import '../services/ocr/label_text_extractor.dart'; import '../services/ocr/ocr_language.dart'; import '../services/ocr/tesseract_label_extractor.dart'; import '../services/onboarding_store.dart'; import '../services/share_catalog_service.dart'; /// The app's service locator. Kept to the composition root — widgets get their /// repositories from here (or via BlocProvider), never by reaching into it deep /// in the tree. final GetIt getIt = GetIt.instance; /// Wires the encrypted DB, keystore and repositories. Call once from `main` /// before `runApp`; the DB key must exist before the DB opens. Future configureDependencies() async { final secretStore = FlutterSecretStore(); final keyStore = SecureKeyStore(store: secretStore); final dbKeyHex = await keyStore.databaseKeyHex(); final rootSeedHex = await keyStore.rootSeedHex(); final database = AppDatabase( openEncryptedExecutor(await _databaseFile(), dbKeyHex), ); // Until real key derivation lands, the node/author id is a stable per-install // slice of the root seed. It becomes the user's public key in the social layer. final nodeId = rootSeedHex.substring(0, 16); // Seed the bundled species catalog (idempotent) before the UI opens. final speciesRepository = SpeciesRepository(database, idGen: IdGen()); await speciesRepository.seedBundled(await loadBundledSpecies()); final varietyRepository = VarietyRepository( database, idGen: IdGen(), nodeId: nodeId, ); const fileService = FilePickerFileService(); // OCR label suggestions only where a native Tesseract engine exists; every // other platform (desktop, web) degrades to the no-op and hides the button. // The language pack(s) follow the user's locale(s) — never a fixed region — // limited to what we actually bundle, always unioned with the Latin baseline. final LabelTextExtractor labelExtractor = (Platform.isAndroid || Platform.isIOS) ? TesseractLabelExtractor(language: await _resolveOcrLanguage()) : const NoOpLabelTextExtractor(); getIt ..registerSingleton(keyStore) ..registerSingleton(database) ..registerSingleton(speciesRepository) ..registerSingleton(varietyRepository) ..registerSingleton(fileService) ..registerSingleton(labelExtractor) ..registerSingleton(OnboardingStore(secretStore)) ..registerSingleton( ExportImportService(repository: varietyRepository, files: fileService), ) ..registerSingleton( ShareCatalogService(files: fileService), ); } Future _databaseFile() async { final dir = await getApplicationDocumentsDirectory(); return File(p.join(dir.path, 'tane_inventory.sqlite')); } /// Picks the Tesseract language pack(s) for the current locale(s), limited to /// the bundled packs listed in `tessdata_config.json`. Prefers the app's chosen /// language, then the device's ordered locales. Future _resolveOcrLanguage() async { final available = parseAvailableOcrLanguages( await rootBundle.loadString('assets/tessdata_config.json'), ); final preferred = [ LocaleSettings.currentLocale.languageCode, for (final l in WidgetsBinding.instance.platformDispatcher.locales) l.languageCode, ]; return resolveOcrLanguages(preferredLocales: preferred, available: available); }