| .. | .. |
|---|
| 223 | 223 | // Re-register APNs token after reconnect so daemon always has a fresh token |
|---|
| 224 | 224 | _push?.onMqttConnected(); |
|---|
| 225 | 225 | }; |
|---|
| 226 | | - _ws!.onResume = () async { |
|---|
| 227 | | - // App came back from background — reload messages and catch up. |
|---|
| 228 | | - _chatLog('onResume: reloading messages and sending catch_up'); |
|---|
| 226 | + _ws!.onResume = () { |
|---|
| 227 | + // App came back from background. The in-memory state already has |
|---|
| 228 | + // any messages received while suspended (addMessage was called). |
|---|
| 229 | + // Just rebuild the UI and scroll to bottom to show them. |
|---|
| 230 | + _chatLog('onResume: rebuilding UI and sending catch_up'); |
|---|
| 229 | 231 | _sendCommand('catch_up', {'lastSeq': _lastSeq}); |
|---|
| 230 | | - // Force reload current session messages from provider (triggers rebuild) |
|---|
| 231 | | - final activeId = ref.read(activeSessionIdProvider); |
|---|
| 232 | | - if (activeId != null) { |
|---|
| 233 | | - // Re-save then re-load to ensure UI matches persisted state |
|---|
| 234 | | - await ref.read(messagesProvider.notifier).switchSession(activeId); |
|---|
| 235 | | - } |
|---|
| 236 | 232 | if (mounted) { |
|---|
| 237 | 233 | setState(() {}); |
|---|
| 238 | | - _scrollToBottom(); |
|---|
| 234 | + // Scroll after the frame rebuilds |
|---|
| 235 | + WidgetsBinding.instance.addPostFrameCallback((_) { |
|---|
| 236 | + if (mounted) _scrollToBottom(); |
|---|
| 237 | + }); |
|---|
| 239 | 238 | } |
|---|
| 240 | 239 | }; |
|---|
| 241 | 240 | _ws!.onError = (error) { |
|---|
| .. | .. |
|---|
| 260 | 259 | // sent immediately if already connected. |
|---|
| 261 | 260 | _push = PushService(mqttService: _ws!); |
|---|
| 262 | 261 | _push!.onNotificationTap = (data) { |
|---|
| 263 | | - // Switch to the session immediately, then request catch_up. |
|---|
| 264 | | - // The MQTT connection auto-reconnects on resume, and onResume |
|---|
| 265 | | - // already sends catch_up. We just need to be on the right session. |
|---|
| 262 | + // Only switch if tapping a notification for a DIFFERENT session. |
|---|
| 263 | + // If already on this session, the message is already displayed — |
|---|
| 264 | + // calling switchSession would reload from disk and lose it. |
|---|
| 266 | 265 | final sessionId = data['sessionId'] as String?; |
|---|
| 267 | | - if (sessionId != null && mounted) { |
|---|
| 266 | + final activeId = ref.read(activeSessionIdProvider); |
|---|
| 267 | + if (sessionId != null && sessionId != activeId && mounted) { |
|---|
| 268 | 268 | _switchSession(sessionId); |
|---|
| 269 | 269 | } |
|---|
| 270 | 270 | }; |
|---|
| .. | .. |
|---|
| 414 | 414 | ); |
|---|
| 415 | 415 | } |
|---|
| 416 | 416 | |
|---|
| 417 | + _chatLog('catch_up msg: session=${msgSessionId?.substring(0, 8) ?? "NULL"} active=${activeId?.substring(0, 8)} match=${msgSessionId == activeId || msgSessionId == null} content="${content.substring(0, content.length.clamp(0, 40))}"'); |
|---|
| 417 | 418 | if (msgSessionId == null || msgSessionId == activeId) { |
|---|
| 418 | 419 | // Active session or no session: add directly to chat |
|---|
| 419 | 420 | ref.read(messagesProvider.notifier).addMessage(message); |
|---|