import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../core/exceptions.dart'; import 'api_client_provider.dart'; import 'capture_queue_provider.dart'; import 'chat_provider.dart'; /// Outcome of a single capture attempt — consumed by the UI for snackbars. class CaptureResult { final String message; final bool isError; const CaptureResult(this.message, {this.isError = false}); } /// The most recent capture result. UI watches this to show snackbars. /// Reset to null by the notifier before each new item so listeners always fire. final captureResultProvider = NotifierProvider<_CaptureResultNotifier, CaptureResult?>( _CaptureResultNotifier.new); class _CaptureResultNotifier extends Notifier { @override CaptureResult? build() => null; } /// In-memory sequential work queue for quick captures. final captureWorkQueueProvider = NotifierProvider>( CaptureWorkQueueNotifier.new, ); class CaptureWorkQueueNotifier extends Notifier> { bool _running = false; @override List build() => []; /// Add text to the queue and start the drain loop if not already running. void enqueue(String text) { state = [...state, text]; _drain(); } Future _drain() async { if (_running) return; _running = true; try { while (state.isNotEmpty) { final text = state.first; // Signal "no result yet" so the same result value can re-trigger watch. ref.read(captureResultProvider.notifier).state = null; try { // Create a new conversation, add it to the conversations list, then // send the message and kick off generation in the background. final conv = await ref.read(conversationsProvider.notifier).create(''); final chatRepo = ref.read(chatRepositoryProvider); await chatRepo.sendMessage(conv.id, text); // Fire-and-forget: drain the SSE stream so the server generates a // response (creating notes/tasks/etc.) without blocking the UI. chatRepo.streamGeneration(conv.id).drain().ignore(); state = state.length > 1 ? state.sublist(1) : []; ref.read(captureResultProvider.notifier).state = const CaptureResult('Sent to Fabled.'); } on NetworkException catch (_) { // Persist to offline queue and stop draining — still offline. await ref.read(captureQueueProvider.notifier).enqueue(text); state = state.length > 1 ? state.sublist(1) : []; ref.read(captureResultProvider.notifier).state = const CaptureResult( "You're offline — capture saved and will retry automatically.", ); break; } on AppException catch (e) { state = state.length > 1 ? state.sublist(1) : []; ref.read(captureResultProvider.notifier).state = CaptureResult(e.message, isError: true); } catch (_) { state = state.length > 1 ? state.sublist(1) : []; ref.read(captureResultProvider.notifier).state = const CaptureResult( 'Failed to send. Please try again.', isError: true); } } } finally { _running = false; } } }