diff --git a/lib/app.dart b/lib/app.dart index ddba82b..294aec3 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -5,10 +5,10 @@ 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 'core/exceptions.dart'; import 'providers/capture_queue_provider.dart'; import 'providers/capture_work_queue_provider.dart'; import 'providers/briefing_provider.dart'; @@ -16,10 +16,8 @@ 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'; @@ -541,27 +539,19 @@ class _QuickCaptureBarState extends ConsumerState<_QuickCaptureBar> { 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. + final conv = + await ref.read(conversationsProvider.notifier).create(''); + final chatRepo = ref.read(chatRepositoryProvider); + await chatRepo.sendMessage(conv.id, text); + chatRepo.streamGeneration(conv.id).drain().ignore(); 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. + // Server error — drop from queue to prevent ghost items. await ref.read(captureQueueProvider.notifier).dequeue(text); } } diff --git a/lib/providers/capture_work_queue_provider.dart b/lib/providers/capture_work_queue_provider.dart index afa0f5a..28b8e1f 100644 --- a/lib/providers/capture_work_queue_provider.dart +++ b/lib/providers/capture_work_queue_provider.dart @@ -3,8 +3,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 +24,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,31 +50,24 @@ 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( + ref.read(captureResultProvider.notifier).state = const CaptureResult( "You're offline — capture saved and will retry automatically.", ); break; @@ -86,20 +77,13 @@ class CaptureWorkQueueNotifier extends Notifier> { 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, - }; } diff --git a/lib/screens/calendar/event_form_sheet.dart b/lib/screens/calendar/event_form_sheet.dart index 4197543..d1460ca 100644 --- a/lib/screens/calendar/event_form_sheet.dart +++ b/lib/screens/calendar/event_form_sheet.dart @@ -143,19 +143,19 @@ class _EventFormSheetState extends ConsumerState { Future _delete() async { final confirmed = await showDialog( context: context, - builder: (_) => AlertDialog( + builder: (dialogContext) => AlertDialog( title: const Text('Delete this event?'), actions: [ TextButton( - onPressed: () => Navigator.of(context).pop(false), + onPressed: () => Navigator.pop(dialogContext, false), child: const Text('Cancel'), ), TextButton( - onPressed: () => Navigator.of(context).pop(true), + onPressed: () => Navigator.pop(dialogContext, true), child: Text( 'Delete', - style: - TextStyle(color: Theme.of(context).colorScheme.error), + style: TextStyle( + color: Theme.of(dialogContext).colorScheme.error), ), ), ], diff --git a/lib/screens/chat/conversations_tab_screen.dart b/lib/screens/chat/conversations_tab_screen.dart index a82cb07..6c8f271 100644 --- a/lib/screens/chat/conversations_tab_screen.dart +++ b/lib/screens/chat/conversations_tab_screen.dart @@ -99,15 +99,15 @@ class ConversationsTabScreen extends ConsumerWidget { BuildContext context, WidgetRef ref, int id, String title) async { final ok = await showDialog( context: context, - builder: (_) => AlertDialog( + builder: (dialogContext) => AlertDialog( title: const Text('Delete conversation?'), content: Text('"$title" will be permanently deleted.'), actions: [ TextButton( - onPressed: () => Navigator.pop(context, false), + onPressed: () => Navigator.pop(dialogContext, false), child: const Text('Cancel')), FilledButton( - onPressed: () => Navigator.pop(context, true), + onPressed: () => Navigator.pop(dialogContext, true), child: const Text('Delete')), ], ),