| .. | .. |
|---|
| 65 | 65 | String? _playingMessageId; |
|---|
| 66 | 66 | int _lastSeq = 0; |
|---|
| 67 | 67 | bool _isCatchingUp = false; |
|---|
| 68 | + bool _catchUpReceived = false; |
|---|
| 68 | 69 | bool _screenshotForChat = false; |
|---|
| 69 | 70 | // FIFO dedup queue: O(1) eviction by removing from front when over cap. |
|---|
| 70 | 71 | final List<int> _seenSeqsList = []; |
|---|
| .. | .. |
|---|
| 222 | 223 | // Re-register APNs token after reconnect so daemon always has a fresh token |
|---|
| 223 | 224 | _push?.onMqttConnected(); |
|---|
| 224 | 225 | }; |
|---|
| 225 | | - _ws!.onResume = () { |
|---|
| 226 | | - // App came back from background — connection may or may not be alive. |
|---|
| 227 | | - // Try catch_up first, but if no response comes, force reconnect. |
|---|
| 228 | | - _chatLog('onResume: sending catch_up with lastSeq=$_lastSeq'); |
|---|
| 229 | | - final seqBefore = _lastSeq; |
|---|
| 226 | + _ws!.onResume = () async { |
|---|
| 227 | + // App came back from background — reload messages and catch up. |
|---|
| 228 | + _chatLog('onResume: reloading messages and sending catch_up'); |
|---|
| 230 | 229 | _sendCommand('catch_up', {'lastSeq': _lastSeq}); |
|---|
| 231 | | - // Force UI rebuild for any buffered messages |
|---|
| 232 | | - Future.delayed(const Duration(milliseconds: 300), () { |
|---|
| 233 | | - if (mounted) { |
|---|
| 234 | | - setState(() {}); |
|---|
| 235 | | - _scrollToBottom(); |
|---|
| 236 | | - } |
|---|
| 237 | | - }); |
|---|
| 238 | | - // If catch_up didn't produce a response in 2s, connection is dead — reconnect |
|---|
| 239 | | - Future.delayed(const Duration(seconds: 2), () { |
|---|
| 240 | | - if (!mounted) return; |
|---|
| 241 | | - if (_lastSeq == seqBefore) { |
|---|
| 242 | | - // No new messages arrived — connection likely dead |
|---|
| 243 | | - _chatLog('onResume: no catch_up response after 2s, forcing reconnect'); |
|---|
| 244 | | - _ws?.forceReconnect(); |
|---|
| 245 | | - } |
|---|
| 246 | | - }); |
|---|
| 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 | + if (mounted) { |
|---|
| 237 | + setState(() {}); |
|---|
| 238 | + _scrollToBottom(); |
|---|
| 239 | + } |
|---|
| 247 | 240 | }; |
|---|
| 248 | 241 | _ws!.onError = (error) { |
|---|
| 249 | 242 | debugPrint('MQTT error: $error'); |
|---|
| .. | .. |
|---|
| 259 | 252 | ); |
|---|
| 260 | 253 | |
|---|
| 261 | 254 | await _ws!.connect(); |
|---|
| 255 | + |
|---|
| 256 | + // Attach MQTT to trace service for auto-publishing logs to server |
|---|
| 257 | + TraceService.instance.attachMqtt(_ws!); |
|---|
| 262 | 258 | |
|---|
| 263 | 259 | // Initialize push notifications after MQTT is set up so token can be |
|---|
| 264 | 260 | // sent immediately if already connected. |
|---|
| .. | .. |
|---|
| 371 | 367 | final sessionId = msg['sessionId'] as String?; |
|---|
| 372 | 368 | if (sessionId != null) _incrementUnread(sessionId); |
|---|
| 373 | 369 | case 'catch_up': |
|---|
| 370 | + _catchUpReceived = true; |
|---|
| 374 | 371 | final serverSeq = msg['serverSeq'] as int?; |
|---|
| 375 | 372 | if (serverSeq != null) { |
|---|
| 376 | 373 | // Always sync to server's seq — if server restarted, its seq may be lower |
|---|