Matthias Nott
2026-03-25 29f7a2c444d60fa155451d7e7f65cf637a1b7f41
lib/screens/chat_screen.dart
....@@ -1,3 +1,4 @@
1
+import 'dart:async';
12 import 'dart:convert';
23 import 'dart:io';
34
....@@ -63,6 +64,7 @@
6364 final List<Map<String, dynamic>> _pendingMessages = [];
6465 final Map<String, List<Message>> _catchUpPending = {};
6566 List<String>? _cachedSessionOrder;
67
+ Timer? _typingTimer;
6668
6769 @override
6870 void initState() {
....@@ -132,10 +134,13 @@
132134 }
133135 }
134136
137
+ bool _isLoadingMore = false;
135138 void _onScroll() {
136
- if (_scrollController.position.pixels >=
137
- _scrollController.position.maxScrollExtent - 100) {
138
- ref.read(messagesProvider.notifier).loadMore();
139
+ if (!_isLoadingMore &&
140
+ _scrollController.position.pixels >=
141
+ _scrollController.position.maxScrollExtent - 100) {
142
+ _isLoadingMore = true;
143
+ ref.read(messagesProvider.notifier).loadMore().then((_) => _isLoadingMore = false);
139144 }
140145 }
141146
....@@ -247,6 +252,15 @@
247252 // Strict: only show typing for the ACTIVE session, ignore all others
248253 if (activeId != null && typingSession == activeId) {
249254 ref.read(isTypingProvider.notifier).state = typing;
255
+ // Auto-clear after 10s in case typing_end is missed
256
+ if (typing) {
257
+ _typingTimer?.cancel();
258
+ _typingTimer = Timer(const Duration(seconds: 10), () {
259
+ if (mounted) ref.read(isTypingProvider.notifier).state = false;
260
+ });
261
+ } else {
262
+ _typingTimer?.cancel();
263
+ }
250264 }
251265 case 'typing_end':
252266 final endSession = msg['sessionId'] as String?;