From deec1d6faf0361569121937701e5835f78d4cd9c Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Tue, 24 Mar 2026 02:20:07 +0100
Subject: [PATCH] fix: clean force-reconnect on resume with intentionalClose flag

---
 lib/services/mqtt_service.dart |   42 +++++++++++++++---------------------------
 1 files changed, 15 insertions(+), 27 deletions(-)

diff --git a/lib/services/mqtt_service.dart b/lib/services/mqtt_service.dart
index 4891638..34e638c 100644
--- a/lib/services/mqtt_service.dart
+++ b/lib/services/mqtt_service.dart
@@ -516,34 +516,22 @@
       case AppLifecycleState.resumed:
         if (_intentionalClose) break;
         _mqttLog('MQTT: app resumed, status=$_status');
-        if (_status != ConnectionStatus.connected) {
-          // Already knows it's disconnected — just reconnect
-          connect();
-        } else {
-          // Thinks it's connected — verify by sending a ping command.
-          // If the connection is dead, the publish will fail or we won't
-          // get a pong back. Set a watchdog timer.
-          _mqttLog('MQTT: sending health check...');
-          _publish('pailot/control/in', {
-            'type': 'command',
-            'command': 'ping',
-            'msgId': DateTime.now().millisecondsSinceEpoch.toString(),
-            'ts': DateTime.now().millisecondsSinceEpoch,
-          }, MqttQos.atLeastOnce);
-          // If no pong within 3s, force reconnect
-          Future.delayed(const Duration(seconds: 3), () {
-            if (_status == ConnectionStatus.connected) {
-              // Check if client is still actually connected
-              final client = _client;
-              if (client == null || client.connectionStatus?.state != MqttConnectionState.connected) {
-                _mqttLog('MQTT: health check failed, reconnecting...');
-                _client = null;
-                _setStatus(ConnectionStatus.reconnecting);
-                connect();
-              }
-            }
-          });
+        // iOS kills TCP sockets during suspend. Always force a clean
+        // reconnect to avoid the "looks connected but dead" state.
+        final resumeClient = _client;
+        if (resumeClient != null) {
+          _intentionalClose = true; // Prevent _onDisconnected from cascading
+          resumeClient.autoReconnect = false;
+          resumeClient.disconnect();
+          _client = null;
+          _updatesSub?.cancel();
+          _updatesSub = null;
+          _intentionalClose = false;
         }
+        _setStatus(ConnectionStatus.reconnecting);
+        Future.delayed(const Duration(milliseconds: 300), () {
+          if (!_intentionalClose) connect();
+        });
       case AppLifecycleState.paused:
         break;
       default:

--
Gitblit v1.3.1