From 9aea0e7837ae0ea3d417f3d2afd8554cd71f123d Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Wed, 25 Mar 2026 15:27:00 +0100
Subject: [PATCH] feat: show connection status detail in app bar during connect

---
 lib/providers/providers.dart   |    2 ++
 lib/services/mqtt_service.dart |    5 +++++
 lib/screens/chat_screen.dart   |   26 +++++++++++++++++++++++---
 3 files changed, 30 insertions(+), 3 deletions(-)

diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart
index b477237..fce1edf 100644
--- a/lib/providers/providers.dart
+++ b/lib/providers/providers.dart
@@ -58,6 +58,8 @@
 final wsStatusProvider =
     StateProvider<ConnectionStatus>((ref) => ConnectionStatus.disconnected);
 
+final connectionDetailProvider = StateProvider<String>((ref) => '');
+
 // --- Sessions ---
 
 final sessionsProvider = StateProvider<List<Session>>((ref) => []);
diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart
index 2e1155a..f3c13f8 100644
--- a/lib/screens/chat_screen.dart
+++ b/lib/screens/chat_screen.dart
@@ -160,6 +160,14 @@
     _ws!.onStatusChanged = (status) {
       if (mounted) {
         ref.read(wsStatusProvider.notifier).state = status;
+        if (status == ConnectionStatus.connected) {
+          ref.read(connectionDetailProvider.notifier).state = '';
+        }
+      }
+    };
+    _ws!.onStatusDetail = (detail) {
+      if (mounted) {
+        ref.read(connectionDetailProvider.notifier).state = detail;
       }
     };
     _ws!.onMessage = _handleMessage;
@@ -1301,6 +1309,7 @@
     final messages = ref.watch(messagesProvider);
     final wsStatus = ref.watch(wsStatusProvider);
     final isTyping = ref.watch(isTypingProvider);
+    final connectionDetail = ref.watch(connectionDetailProvider);
     final sessions = ref.watch(sessionsProvider);
     final activeSession = ref.watch(activeSessionProvider);
     final unreadCounts = ref.watch(unreadCountsProvider);
@@ -1319,9 +1328,20 @@
             _scaffoldKey.currentState?.openDrawer();
           },
         ),
-        title: Text(
-          activeSession?.name ?? 'PAILot',
-          style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
+        title: Column(
+          crossAxisAlignment: CrossAxisAlignment.center,
+          mainAxisSize: MainAxisSize.min,
+          children: [
+            Text(
+              activeSession?.name ?? 'PAILot',
+              style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
+            ),
+            if (connectionDetail.isNotEmpty && wsStatus != ConnectionStatus.connected)
+              Text(
+                connectionDetail,
+                style: TextStyle(fontSize: 11, color: Colors.grey.shade400),
+              ),
+          ],
         ),
         actions: [
           StatusDot(status: wsStatus),
diff --git a/lib/services/mqtt_service.dart b/lib/services/mqtt_service.dart
index 4e34697..8277d8f 100644
--- a/lib/services/mqtt_service.dart
+++ b/lib/services/mqtt_service.dart
@@ -56,6 +56,7 @@
 
   // Callbacks
   void Function(ConnectionStatus status)? onStatusChanged;
+  void Function(String detail)? onStatusDetail; // "Probing local...", "Scanning network..."
   void Function(Map<String, dynamic> message)? onMessage;
   void Function()? onOpen;
   void Function()? onClose;
@@ -117,6 +118,7 @@
     if (config.vpnHost != null && config.vpnHost!.isNotEmpty) hosts.add(config.vpnHost!);
     if (config.host.isNotEmpty) hosts.add(config.host);
     _mqttLog('MQTT: probing ${hosts.length} hosts in parallel: ${hosts.join(", ")}');
+    onStatusDetail?.call('Probing ${hosts.length} hosts...');
 
     // Probe all configured hosts in parallel — first to respond wins
     String? winner;
@@ -143,6 +145,7 @@
 
     if (winner != null && !_intentionalClose) {
       _mqttLog('MQTT: probe winner: $winner, connecting...');
+      onStatusDetail?.call('Connecting to $winner...');
       try {
         if (await _tryConnect(winner, clientId, timeout: 5000)) return;
       } catch (e) {
@@ -152,6 +155,7 @@
 
     // All hosts failed — retry after delay
     _mqttLog('MQTT: all attempts failed, retrying in 5s');
+    onStatusDetail?.call('No server found, retrying...');
     _setStatus(ConnectionStatus.reconnecting);
     Future.delayed(const Duration(seconds: 5), () {
       if (!_intentionalClose && _status != ConnectionStatus.connected) {
@@ -201,6 +205,7 @@
 
     // Fallback: scan local subnet for MQTT port (handles Personal Hotspot)
     _mqttLog('MQTT: Bonjour failed, trying subnet scan...');
+    onStatusDetail?.call('Scanning local network...');
     return _scanSubnetForMqtt();
   }
 

--
Gitblit v1.3.1