import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../core/config/sync_config.dart'; import '../core/services/connectivity_service.dart'; import '../core/services/receipt_service.dart'; import '../core/services/sound_service.dart'; import '../data/datasources/local_store.dart'; import '../data/repositories/customer_repository_impl.dart'; import '../data/repositories/product_repository_impl.dart'; import '../data/datasources/remote_catalogue_source.dart'; import '../data/remote/http_order_transport.dart'; import '../data/remote/mqtt_order_transport.dart'; import '../data/remote/order_transport.dart'; import '../data/remote/simulated_order_transport.dart'; import '../data/repositories/sync_repository_impl.dart'; import '../data/repositories/transaction_repository_impl.dart'; import '../data/sync/sync_engine.dart'; import '../domain/repositories/customer_repository.dart'; import '../domain/repositories/product_repository.dart'; import '../domain/repositories/sync_repository.dart'; import '../domain/repositories/transaction_repository.dart'; import '../domain/usecases/checkout_sale.dart'; /// Root data source. Overridden in tests with an in-memory double. final localStoreProvider = Provider((ref) => LocalStore.instance); // ---------------------------------------------------------- Repositories final productRepositoryProvider = Provider( (ref) => ProductRepositoryImpl(ref.watch(localStoreProvider)), ); final customerRepositoryProvider = Provider( (ref) => CustomerRepositoryImpl(ref.watch(localStoreProvider)), ); final transactionRepositoryProvider = Provider( (ref) => TransactionRepositoryImpl(ref.watch(localStoreProvider)), ); /// Mirrors the Settings "Simulate offline" switch so the header can show it. /// /// Without this the terminal claims LIVE while every call is being failed on /// purpose, which reads as a real network fault. final simulateOfflineProvider = StateProvider((ref) => false); /// Simulated back-office endpoints. Held as singletons so the offline toggle /// in Settings affects every call. final remoteCatalogueProvider = Provider( (ref) => RemoteCatalogueSource( isOffline: () => ref.read(simulateOfflineProvider), ), ); // ------------------------------------------------------------------- Sync /// How this terminal reaches the back office. /// /// Defaults to the simulated route so a fresh install is usable with no broker /// and no endpoint; Settings re-points it. final syncConfigProvider = StateProvider((ref) => const SyncConfig()); /// Real network state, folded with the Settings offline switch. final connectivityServiceProvider = Provider((ref) { final service = ConnectivityService( isSimulatedOffline: () => ref.read(simulateOfflineProvider), ); ref.onDispose(service.dispose); return service; }); /// The wire itself. Rebuilt when the configuration changes, and the old one is /// closed so a re-pointed terminal does not keep a stale broker session open. final orderTransportProvider = Provider((ref) { final config = ref.watch(syncConfigProvider); final transport = switch (config.transport) { TransportKind.mqtt => MqttOrderTransport(config: config), TransportKind.http => HttpOrderTransport(config: config), TransportKind.simulated => SimulatedOrderTransport( isOffline: () => ref.read(simulateOfflineProvider), ), }; ref.onDispose(transport.dispose); return transport; }); final syncRepositoryProvider = Provider( (ref) => SyncRepositoryImpl( ref.watch(localStoreProvider), ref.watch(remoteCatalogueProvider), ref.watch(orderTransportProvider), batchSize: ref.watch(syncConfigProvider).batchSize, ), ); /// Decides when bills are uploaded. Started once, by the app shell. final syncEngineProvider = Provider((ref) { final transport = ref.watch(orderTransportProvider); final engine = SyncEngine( repository: ref.watch(syncRepositoryProvider), connectivity: ref.watch(connectivityServiceProvider).onlineChanges, downlink: transport.downlink, onCatalogueChanged: () async { await ref.read(syncRepositoryProvider).importCatalogue(); ref.invalidate(localStoreProvider); }, ); ref.onDispose(engine.dispose); return engine; }); /// Live engine state for the header pill and the events screen. final syncEngineStateProvider = StreamProvider((ref) { final engine = ref.watch(syncEngineProvider); return engine.states.map((s) => s); }); // ------------------------------------------------------------- Use cases final checkoutSaleProvider = Provider( (ref) => CheckoutSale( productRepository: ref.watch(productRepositoryProvider), customerRepository: ref.watch(customerRepositoryProvider), transactionRepository: ref.watch(transactionRepositoryProvider), ), ); // -------------------------------------------------------------- Services final soundServiceProvider = Provider((ref) => SoundService.instance); final receiptServiceProvider = Provider((ref) => ReceiptService.instance); // --------------------------------------------------------------- Session class CashierSession { const CashierSession({ required this.name, required this.role, required this.terminalId, }); final String name; final String role; final String terminalId; } final cashierSessionProvider = StateProvider( (ref) => const CashierSession( name: 'Suriya', role: 'ADMIN', terminalId: 'TERM-01', ), ); /// Ticks once a minute to drive the header clock without rebuilding on every /// frame. final clockProvider = StreamProvider((ref) async* { yield DateTime.now(); yield* Stream.periodic(const Duration(seconds: 20), (_) => DateTime.now()); });