From 5315e582475703624534d517ee912f427930adce Mon Sep 17 00:00:00 2001 From: Janez T Date: Sat, 18 Oct 2025 23:05:00 +0200 Subject: [PATCH] feat: Enhance message synchronization with response handling and improve logging in MessagesTab --- .claude/settings.local.json | 5 +++- lib/providers/connection_provider.dart | 35 +++++++++++++++++++++++--- lib/screens/messages_tab.dart | 17 +++++++++---- 3 files changed, 48 insertions(+), 9 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 15b3e6c..da0e963 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,10 @@ { "permissions": { "allow": [ - "Bash(flutter analyze lib)" + "Bash(flutter analyze lib)", + "Read(//Users/dz0ny/meshcore-sar/MeshCore/**)", + "Bash(flutter analyze lib/providers/connection_provider.dart)", + "Bash(flutter analyze lib/providers/connection_provider.dart --fatal-infos)" ], "deny": [], "ask": [] diff --git a/lib/providers/connection_provider.dart b/lib/providers/connection_provider.dart index 6663d00..8b9d057 100644 --- a/lib/providers/connection_provider.dart +++ b/lib/providers/connection_provider.dart @@ -95,6 +95,9 @@ class ConnectionProvider with ChangeNotifier { DateTime? _lastSyncNextRequestedAt; static const Duration _minSyncNextInterval = Duration(milliseconds: 150); + // Completer to wait for response before sending next sync request + Completer? _syncResponseCompleter; + // Lightweight guards for other commands that can be double-tapped bool _isLoginInProgress = false; DateTime? _lastLoginRequestedAt; @@ -260,6 +263,11 @@ class ConnectionProvider with ChangeNotifier { // Parse SAR markers final enhancedMessage = SarMessageParser.enhanceMessage(message); onMessageReceived?.call(enhancedMessage); + + // Complete sync response completer (message received = continue syncing) + if (_syncResponseCompleter != null && !_syncResponseCompleter!.isCompleted) { + _syncResponseCompleter!.complete(true); + } }; _bleService.onTelemetryReceived = (publicKey, lppData) { @@ -284,6 +292,11 @@ class ConnectionProvider with ChangeNotifier { _bleService.onNoMoreMessages = () { debugPrint('📥 [Provider] Received NoMoreMessages signal'); _noMoreMessages = true; + + // Complete sync response completer (no more messages = stop syncing) + if (_syncResponseCompleter != null && !_syncResponseCompleter!.isCompleted) { + _syncResponseCompleter!.complete(false); + } }; _bleService.onMessageWaiting = () { @@ -1273,6 +1286,9 @@ class ConnectionProvider with ChangeNotifier { '📤 [Provider] Sync iteration ${i + 1}: Sending CMD_SYNC_NEXT_MESSAGE', ); + // Create new completer for this request + _syncResponseCompleter = Completer(); + // Respect the minimum interval between requests final now = DateTime.now(); if (_lastSyncNextRequestedAt != null) { @@ -1287,10 +1303,22 @@ class ConnectionProvider with ChangeNotifier { _lastSyncNextRequestedAt = DateTime.now(); count++; - // Small delay to allow response to be processed - await Future.delayed(const Duration(milliseconds: 150)); + // Wait for response (true = message received, false = no more messages) + // Timeout after 2 seconds to prevent hanging + final hasMore = await _syncResponseCompleter!.future.timeout( + const Duration(seconds: 2), + onTimeout: () { + debugPrint('⚠️ [Provider] Sync timeout - no response after 2s'); + return false; + }, + ); - debugPrint(' After iteration ${i + 1}: _noMoreMessages=$_noMoreMessages'); + debugPrint(' After iteration ${i + 1}: hasMore=$hasMore, _noMoreMessages=$_noMoreMessages'); + + if (!hasMore) { + debugPrint(' ✅ No more messages available, stopping sync'); + break; + } } if (!_noMoreMessages && count >= 100) { @@ -1310,6 +1338,7 @@ class ConnectionProvider with ChangeNotifier { return count; } finally { _isSyncingMessages = false; + _syncResponseCompleter = null; } } diff --git a/lib/screens/messages_tab.dart b/lib/screens/messages_tab.dart index 02a7e8a..9704d96 100644 --- a/lib/screens/messages_tab.dart +++ b/lib/screens/messages_tab.dart @@ -113,7 +113,6 @@ class _MessagesTabState extends State { _focusNode.unfocus(); if (!mounted) return; - ToastLogger.success(context, 'Message sent to public channel'); } catch (e) { if (!mounted) return; ToastLogger.error(context, 'Failed to send: $e'); @@ -254,7 +253,9 @@ class _MessagesTabState extends State { } try { - debugPrint('🔄 [MessagesTab] Manual refresh triggered - syncing messages'); + debugPrint( + '🔄 [MessagesTab] Manual refresh triggered - syncing messages', + ); final messageCount = await connectionProvider.syncAllMessages(); if (!mounted) return; if (messageCount > 0) { @@ -307,14 +308,18 @@ class _MessagesTabState extends State { ), const SizedBox(height: 16), Text( - AppLocalizations.of(context)!.noMessagesYet, + AppLocalizations.of( + context, + )!.noMessagesYet, style: Theme.of( context, ).textTheme.titleLarge, ), const SizedBox(height: 8), Text( - AppLocalizations.of(context)!.pullDownToSync, + AppLocalizations.of( + context, + )!.pullDownToSync, style: Theme.of( context, ).textTheme.bodyMedium, @@ -971,7 +976,9 @@ class _MessageBubble extends StatelessWidget { Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceVariant.withValues(alpha: 0.5), + color: Theme.of( + context, + ).colorScheme.surfaceVariant.withValues(alpha: 0.5), borderRadius: BorderRadius.circular(8), ), child: Text(