From 7747e467ed7d5316c62a439a5e3e58bf891f0d73 Mon Sep 17 00:00:00 2001
From: Matthias Nott <mnott@mnsoft.org>
Date: Thu, 11 Jun 2026 09:27:33 +0200
Subject: [PATCH] fix: clear unread counts on app foreground so badge doesn't ghost

---
 lib/screens/chat_screen.dart |   50 +++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 47 insertions(+), 3 deletions(-)

diff --git a/lib/screens/chat_screen.dart b/lib/screens/chat_screen.dart
index 20b3f6a..8738410 100644
--- a/lib/screens/chat_screen.dart
+++ b/lib/screens/chat_screen.dart
@@ -93,7 +93,10 @@
     // Load persisted state BEFORE connecting
     final prefs = await SharedPreferences.getInstance();
     _lastSeq = prefs.getInt('lastSeq') ?? 0;
-    // Restore persisted unread counts
+    // Restore persisted unread counts (kept loaded so any pre-resume read code
+    // sees a valid state), but the app is foregrounding right now → user is
+    // here, so everything is "seen." Clear immediately so a stale 15 doesn't
+    // re-paint the badge on next background.
     final savedUnreads = prefs.getString('unreadCounts');
     if (savedUnreads != null && mounted) {
       try {
@@ -103,6 +106,7 @@
       } catch (_) {}
     }
     _unreadCountsLoaded = true;
+    _clearAllUnreads();
     // Restore saved session order and active session
     _cachedSessionOrder = prefs.getStringList('sessionOrder');
     final savedSessionId = prefs.getString('activeSessionId');
@@ -156,8 +160,10 @@
       if (_ws != null && !_ws!.isConnected) {
         _ws!.connect();
       }
-      // Don't update badge here — provider might not have loaded persisted counts yet.
-      // Native applicationDidBecomeActive reads correct value from UserDefaults.
+      // App is foregrounded → user is here → everything previously unread is
+      // now considered seen. Clear so we don't re-paint a stale badge when the
+      // user backgrounds the app without having explicitly switched sessions.
+      _clearAllUnreads();
     } else if (state == AppLifecycleState.paused && _unreadCountsLoaded) {
       // Set badge to total unread count when going to background
       _updateBadgeFromUnreads();
@@ -167,6 +173,19 @@
   void _updateBadgeFromUnreads() {
     final counts = ref.read(unreadCountsProvider);
     _persistUnreadCounts(counts);
+  }
+
+  /// Clear in-memory + persisted unread counts and remove the icon badge.
+  /// Called on cold-start (after load) and on resume — the app being open
+  /// means the user has effectively seen what was pending.
+  void _clearAllUnreads() {
+    if (!mounted) return;
+    ref.read(unreadCountsProvider.notifier).state = <String, int>{};
+    SharedPreferences.getInstance().then((prefs) {
+      prefs.setString('unreadCounts', jsonEncode(<String, int>{}));
+      prefs.setInt('badgeCount', 0);
+    });
+    PushService.clearBadge();
   }
 
   // ignore: unused_field
@@ -214,6 +233,7 @@
       }
     };
     _ws!.onMessage = _handleMessage;
+    _ws!.onDebugStateRequest = _handleDebugStateRequest;
     _ws!.onOpen = () {
       _sessionReady = false; // Gate messages until sessions arrive
       _pendingMessages.clear();
@@ -522,6 +542,30 @@
     }
   }
 
+  /// Respond to a pailot_debug_state request from the server.
+  /// Reads the in-memory session list and active session from providers
+  /// and publishes exactly what the app is currently rendering.
+  void _handleDebugStateRequest(String requestId) {
+    final sessions = ref.read(sessionsProvider);
+    final activeSessionId = ref.read(activeSessionIdProvider);
+
+    final sessionPayloads = sessions.map((s) => {
+      'sessionId': s.id,
+      'index': s.index,
+      'displayedName': s.name,   // exactly what is shown in the drawer
+      'type': s.type,
+      if (s.kind != null) 'kind': s.kind,
+      'isActive': s.id == activeSessionId,
+    }).toList();
+
+    _ws?.publishDebugStateResponse(
+      requestId: requestId,
+      activeSessionId: activeSessionId,
+      sessions: sessionPayloads,
+      platform: Platform.operatingSystem,
+    );
+  }
+
   void _handleIncomingMessage(Map<String, dynamic> msg) {
     final sessionId = msg['sessionId'] as String?;
     final content = msg['content'] as String? ??

--
Gitblit v1.3.1