fix: improve chat auto-scroll logic, reduce socket timeout, and prevent duplicate message sends
This commit is contained in:
@@ -277,7 +277,7 @@ class SocketService {
|
||||
});
|
||||
|
||||
return completer.future.timeout(
|
||||
const Duration(seconds: 10),
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () => null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -366,6 +366,7 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
|
||||
Future<void> sendMessage(String conversationId, String content) async {
|
||||
final currentUserId = _currentUserId;
|
||||
if (currentUserId == null) return;
|
||||
if (state.isSending) return; // Prevent double-send
|
||||
|
||||
final tempId = 'temp_${DateTime.now().millisecondsSinceEpoch}';
|
||||
final now = DateTime.now().toIso8601String();
|
||||
@@ -401,11 +402,9 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
|
||||
);
|
||||
|
||||
try {
|
||||
// Send via socket so the backend broadcasts new_message to the other user.
|
||||
// The REST endpoint does NOT emit socket events, so using REST here
|
||||
// would prevent real-time delivery to the other participant.
|
||||
ChatMessage? realMessage;
|
||||
|
||||
// Try socket first (enables real-time delivery to other user)
|
||||
if (_socket.isConnected) {
|
||||
realMessage = await _socket.sendMessage(conversationId, {
|
||||
'content': content,
|
||||
@@ -413,19 +412,35 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
|
||||
});
|
||||
}
|
||||
|
||||
// Fallback to REST if socket send failed or not connected
|
||||
realMessage ??= await _repository.sendMessage(
|
||||
// Fallback to REST if socket failed or not connected
|
||||
if (realMessage == null) {
|
||||
try {
|
||||
realMessage = await _repository.sendMessage(
|
||||
conversationId,
|
||||
content: content,
|
||||
);
|
||||
} catch (restError) {
|
||||
_log.e('REST send also failed', error: restError);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
final messagesAfterSend =
|
||||
List<ChatMessage>.from(state.messages[conversationId] ?? []);
|
||||
final tempIndex = messagesAfterSend.indexWhere((m) => m.id == tempId);
|
||||
if (tempIndex != -1) {
|
||||
messagesAfterSend[tempIndex] = realMessage;
|
||||
} else {
|
||||
// Temp message was removed (e.g., by a reload) — add the real one
|
||||
messagesAfterSend.insert(0, realMessage);
|
||||
}
|
||||
|
||||
// Deduplicate — socket broadcast might have already added this message
|
||||
final seen = <String>{};
|
||||
messagesAfterSend.removeWhere((m) => !seen.add(m.id));
|
||||
|
||||
final finalMessages =
|
||||
Map<String, List<ChatMessage>>.from(state.messages)
|
||||
..[conversationId] = messagesAfterSend;
|
||||
@@ -457,6 +472,8 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
|
||||
isSending: false,
|
||||
);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
|
||||
final messagesAfterError =
|
||||
List<ChatMessage>.from(state.messages[conversationId] ?? []);
|
||||
messagesAfterError.removeWhere((m) => m.id == tempId);
|
||||
@@ -465,7 +482,6 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
|
||||
Map<String, List<ChatMessage>>.from(state.messages)
|
||||
..[conversationId] = messagesAfterError;
|
||||
|
||||
// Show specific error for connection-required failures
|
||||
final errorMsg = e.toString().contains('connection')
|
||||
? 'You must be connected to send messages'
|
||||
: 'Failed to send message';
|
||||
|
||||
@@ -360,11 +360,16 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
|
||||
orElse: () => null,
|
||||
);
|
||||
|
||||
// Auto-scroll only when a NEW message is added (not on any rebuild)
|
||||
if (messages.length > _previousMessageCount && _previousMessageCount > 0) {
|
||||
// Only scroll if user is near the bottom (not scrolled up reading history)
|
||||
if (_scrollController.hasClients &&
|
||||
_scrollController.position.pixels < 150) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollToBottom(animated: true);
|
||||
if (mounted) _scrollToBottom(animated: true);
|
||||
});
|
||||
}
|
||||
}
|
||||
_previousMessageCount = messages.length;
|
||||
|
||||
final otherPartyName = conversation?.otherParty.name ?? 'Chat';
|
||||
|
||||
Reference in New Issue
Block a user