diff --git a/lib/app.dart b/lib/app.dart index ddba82b..15164f4 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -5,21 +5,17 @@ import 'package:go_router/go_router.dart'; import 'package:flutter_timezone/flutter_timezone.dart'; import 'core/constants.dart'; -import 'core/exceptions.dart'; import 'core/theme.dart'; import 'providers/api_client_provider.dart'; import 'providers/auth_provider.dart'; -import 'providers/capture_queue_provider.dart'; import 'providers/capture_work_queue_provider.dart'; import 'providers/briefing_provider.dart'; import 'providers/calendar_provider.dart'; import 'providers/chat_provider.dart'; import 'providers/knowledge_provider.dart'; import 'providers/news_provider.dart'; -import 'providers/notes_provider.dart'; import 'providers/settings_provider.dart'; import 'providers/update_provider.dart'; -import 'providers/tasks_provider.dart'; import 'screens/auth/login_screen.dart'; import 'screens/briefing/briefing_screen.dart'; import 'screens/knowledge/knowledge_screen.dart'; @@ -516,12 +512,6 @@ class _QuickCaptureBar extends ConsumerStatefulWidget { class _QuickCaptureBarState extends ConsumerState<_QuickCaptureBar> { final _controller = TextEditingController(); - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) => _drainOfflineQueue()); - } - @override void dispose() { _controller.dispose(); @@ -537,36 +527,6 @@ class _QuickCaptureBarState extends ConsumerState<_QuickCaptureBar> { ref.read(captureWorkQueueProvider.notifier).enqueue(text); } - Future _drainOfflineQueue() async { - if (!mounted) return; - final queue = ref.read(captureQueueProvider); - if (queue.isEmpty) return; - final api = ref.read(quickCaptureApiProvider); - for (final text in List.from(queue)) { - if (!mounted) break; - try { - final result = await api.capture(text); - // Dequeue before the mounted check — SharedPreferences doesn't need - // the widget alive, and skipping this would leave a ghost item. - await ref.read(captureQueueProvider.notifier).dequeue(text); - if (!mounted) break; - switch (result.type) { - case 'note': - ref.invalidate(notesProvider); - case 'task': - case 'todo': - ref.invalidate(tasksProvider); - } - } on NetworkException { - break; - } catch (_) { - // Server error or unexpected failure — drop from queue to prevent - // ghost items that can never be cleared. - await ref.read(captureQueueProvider.notifier).dequeue(text); - } - } - } - Future _toggleCaptureMic() async { final voice = ref.read(voiceProvider); if (voice.voiceModeActive) { @@ -596,10 +556,8 @@ class _QuickCaptureBarState extends ConsumerState<_QuickCaptureBar> { @override Widget build(BuildContext context) { final location = GoRouterState.of(context).matchedLocation; - final offlineQueueCount = ref.watch(captureQueueProvider).length; final workQueue = ref.watch(captureWorkQueueProvider); final isWorking = workQueue.isNotEmpty; - final totalPending = workQueue.length + offlineQueueCount; // Show snackbar when a result is published. ref.listen(captureResultProvider, (_, result) { @@ -634,12 +592,7 @@ class _QuickCaptureBarState extends ConsumerState<_QuickCaptureBar> { isDense: true, contentPadding: const EdgeInsets.symmetric( horizontal: 14, vertical: 10), - prefixIcon: totalPending > 0 - ? Badge( - label: Text('$totalPending'), - child: const Icon(Icons.cloud_upload_outlined), - ) - : isWorking + prefixIcon: isWorking ? const Padding( padding: EdgeInsets.all(12), child: SizedBox( diff --git a/lib/providers/capture_work_queue_provider.dart b/lib/providers/capture_work_queue_provider.dart index afa0f5a..6cd4cc3 100644 --- a/lib/providers/capture_work_queue_provider.dart +++ b/lib/providers/capture_work_queue_provider.dart @@ -2,9 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../core/exceptions.dart'; import 'api_client_provider.dart'; -import 'capture_queue_provider.dart'; -import 'notes_provider.dart'; -import 'tasks_provider.dart'; +import 'chat_provider.dart'; /// Outcome of a single capture attempt — consumed by the UI for snackbars. class CaptureResult { @@ -25,7 +23,6 @@ class _CaptureResultNotifier extends Notifier { } /// In-memory sequential work queue for quick captures. -/// Separate from [captureQueueProvider] (which is the offline persistence queue). final captureWorkQueueProvider = NotifierProvider>( CaptureWorkQueueNotifier.new, @@ -52,54 +49,38 @@ class CaptureWorkQueueNotifier extends Notifier> { // Signal "no result yet" so the same result value can re-trigger watch. ref.read(captureResultProvider.notifier).state = null; try { - final api = ref.read(quickCaptureApiProvider); - final result = await api.capture(text); + // 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(); - // Dequeue on success. state = state.length > 1 ? state.sublist(1) : []; - - // Invalidate content providers so lists refresh. - switch (result.type) { - case 'note': - ref.invalidate(notesProvider); - case 'task': - case 'todo': - ref.invalidate(tasksProvider); - } - - // Publish result for snackbar. - final msg = result.message.isNotEmpty - ? result.message - : '${_typeLabel(result.type)} created: ${result.title}'; - ref.read(captureResultProvider.notifier).state = CaptureResult(msg); + 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 = CaptureResult( - "You're offline — capture saved and will retry automatically.", + ref.read(captureResultProvider.notifier).state = const CaptureResult( + "You're offline — message not sent.", + isError: true, ); - 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 = - CaptureResult('Capture failed. Please try again.', isError: true); + ref.read(captureResultProvider.notifier).state = const CaptureResult( + 'Failed to send. Please try again.', + isError: true); } } } finally { _running = false; } } - - String _typeLabel(String type) => switch (type) { - 'note' => 'Note', - 'task' => 'Task', - 'event' => 'Event', - 'todo' => 'To-do', - _ => type, - }; }