diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 8923511..c5cd293 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -489,7 +489,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 13;
+ CURRENT_PROJECT_VERSION = 14;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -511,7 +511,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 13;
+ CURRENT_PROJECT_VERSION = 14;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests;
@@ -529,7 +529,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 13;
+ CURRENT_PROJECT_VERSION = 14;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests;
@@ -545,7 +545,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 13;
+ CURRENT_PROJECT_VERSION = 14;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.meshcore.sar.meshcoreSarApp.RunnerTests;
@@ -676,7 +676,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 13;
+ CURRENT_PROJECT_VERSION = 14;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -699,7 +699,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
- CURRENT_PROJECT_VERSION = 13;
+ CURRENT_PROJECT_VERSION = 14;
DEVELOPMENT_TEAM = JND55328G8;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 303cf37..06b3c1c 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -21,7 +21,7 @@
CFBundleSignature
????
CFBundleVersion
- 13
+ 14
LSRequiresIPhoneOS
UILaunchStoryboardName
diff --git a/lib/providers/connection_provider.dart b/lib/providers/connection_provider.dart
index 5b7212f..18aa012 100644
--- a/lib/providers/connection_provider.dart
+++ b/lib/providers/connection_provider.dart
@@ -73,6 +73,23 @@ class ConnectionProvider with ChangeNotifier {
// Message sync state
bool _noMoreMessages = false;
+ // Prevent overlapping/too-frequent sync requests
+ bool _isSyncingMessages = false;
+ DateTime? _lastSyncNextRequestedAt;
+ static const Duration _minSyncNextInterval = Duration(milliseconds: 150);
+
+ // Lightweight guards for other commands that can be double-tapped
+ bool _isLoginInProgress = false;
+ DateTime? _lastLoginRequestedAt;
+ static const Duration _minLoginInterval = Duration(seconds: 1);
+
+ bool _isStatusRequestInProgress = false;
+ DateTime? _lastStatusRequestedAt;
+ static const Duration _minStatusInterval = Duration(milliseconds: 200);
+
+ bool _isAdvertInProgress = false;
+ DateTime? _lastAdvertRequestedAt;
+ static const Duration _minAdvertInterval = Duration(milliseconds: 500);
// Helper instances
final RoomLoginManager _roomLoginManager = RoomLoginManager();
@@ -899,10 +916,24 @@ class ConnectionProvider with ChangeNotifier {
}
try {
+ if (_isAdvertInProgress) return;
+ // Throttle rapid advert requests
+ final now = DateTime.now();
+ if (_lastAdvertRequestedAt != null) {
+ final elapsed = now.difference(_lastAdvertRequestedAt!);
+ if (elapsed < _minAdvertInterval) {
+ final wait = _minAdvertInterval - elapsed;
+ await Future.delayed(wait);
+ }
+ }
+ _isAdvertInProgress = true;
await _bleService.sendSelfAdvert(floodMode: floodMode);
+ _lastAdvertRequestedAt = DateTime.now();
} catch (e) {
_error = 'Failed to send advertisement: $e';
notifyListeners();
+ } finally {
+ _isAdvertInProgress = false;
}
}
@@ -1017,6 +1048,12 @@ class ConnectionProvider with ChangeNotifier {
/// Sync messages from device queue
/// Call this repeatedly until no more messages are available
Future syncNextMessage() async {
+ // Prevent re-entrancy and too-fast triggers
+ if (_isSyncingMessages) {
+ // Another sync (single or loop) is in progress
+ return false;
+ }
+
if (!_bleService.isConnected) {
_error = 'Not connected to device';
notifyListeners();
@@ -1024,17 +1061,36 @@ class ConnectionProvider with ChangeNotifier {
}
try {
+ // Enforce a small gap between consecutive requests
+ final now = DateTime.now();
+ if (_lastSyncNextRequestedAt != null) {
+ final elapsed = now.difference(_lastSyncNextRequestedAt!);
+ if (elapsed < _minSyncNextInterval) {
+ final remaining = _minSyncNextInterval - elapsed;
+ await Future.delayed(remaining);
+ }
+ }
+
+ _isSyncingMessages = true;
await _bleService.syncNextMessage();
+ _lastSyncNextRequestedAt = DateTime.now();
return true;
} catch (e) {
_error = 'Failed to sync message: $e';
notifyListeners();
return false;
+ } finally {
+ _isSyncingMessages = false;
}
}
/// Sync all waiting messages from device
Future syncAllMessages() async {
+ if (_isSyncingMessages) {
+ // Already syncing; avoid overlapping loops
+ return 0;
+ }
+
if (!_bleService.isConnected) {
_error = 'Not connected to device';
notifyListeners();
@@ -1045,6 +1101,7 @@ class ConnectionProvider with ChangeNotifier {
_noMoreMessages = false; // Reset flag
try {
+ _isSyncingMessages = true;
print('🔄 [Provider] Starting message sync loop...');
print(' Initial _noMoreMessages state: $_noMoreMessages');
@@ -1065,7 +1122,18 @@ class ConnectionProvider with ChangeNotifier {
'📤 [Provider] Sync iteration ${i + 1}: Sending CMD_SYNC_NEXT_MESSAGE',
);
+ // Respect the minimum interval between requests
+ final now = DateTime.now();
+ if (_lastSyncNextRequestedAt != null) {
+ final elapsed = now.difference(_lastSyncNextRequestedAt!);
+ if (elapsed < _minSyncNextInterval) {
+ final remaining = _minSyncNextInterval - elapsed;
+ await Future.delayed(remaining);
+ }
+ }
+
await _bleService.syncNextMessage();
+ _lastSyncNextRequestedAt = DateTime.now();
count++;
// Small delay to allow response to be processed
@@ -1089,6 +1157,8 @@ class ConnectionProvider with ChangeNotifier {
_error = 'Failed to sync messages: $e';
notifyListeners();
return count;
+ } finally {
+ _isSyncingMessages = false;
}
}
@@ -1121,13 +1191,27 @@ class ConnectionProvider with ChangeNotifier {
}
try {
+ if (_isLoginInProgress) return;
+ // Throttle rapid login attempts
+ final now = DateTime.now();
+ if (_lastLoginRequestedAt != null) {
+ final elapsed = now.difference(_lastLoginRequestedAt!);
+ if (elapsed < _minLoginInterval) {
+ final wait = _minLoginInterval - elapsed;
+ await Future.delayed(wait);
+ }
+ }
+ _isLoginInProgress = true;
await _bleService.loginToRoom(
roomPublicKey: roomPublicKey,
password: password,
);
+ _lastLoginRequestedAt = DateTime.now();
} catch (e) {
_error = 'Failed to send login request: $e';
notifyListeners();
+ } finally {
+ _isLoginInProgress = false;
}
}
@@ -1151,10 +1235,24 @@ class ConnectionProvider with ChangeNotifier {
}
try {
+ if (_isStatusRequestInProgress) return;
+ // Throttle rapid status requests
+ final now = DateTime.now();
+ if (_lastStatusRequestedAt != null) {
+ final elapsed = now.difference(_lastStatusRequestedAt!);
+ if (elapsed < _minStatusInterval) {
+ final wait = _minStatusInterval - elapsed;
+ await Future.delayed(wait);
+ }
+ }
+ _isStatusRequestInProgress = true;
await _bleService.sendStatusRequest(contactPublicKey);
+ _lastStatusRequestedAt = DateTime.now();
} catch (e) {
_error = 'Failed to send status request: $e';
notifyListeners();
+ } finally {
+ _isStatusRequestInProgress = false;
}
}