fix: improve chat message deduplication, add conversation refresh logic, and update UI components for consistency

This commit is contained in:
pradeepkumar
2026-04-18 11:34:48 +05:30
parent 164d590d31
commit afa1f3489a
6 changed files with 47 additions and 8 deletions

View File

@@ -117,10 +117,8 @@ class _AboutCta {
// ── Defaults ──
const _defaultStats = [
_AboutStatItem(value: '15k+', label: 'Verified Agents'),
_AboutStatItem(value: '98%', label: 'Customer Satisfaction'),
];
// Empty defaults — only CMS-provided content should render (matches web behavior)
const _defaultStats = <_AboutStatItem>[];
const _defaultFeatures = [
_AboutFeatureItem(

View File

@@ -1651,12 +1651,13 @@ class _AgentEditProfileScreenState
await showModalBottomSheet<void>(
context: context,
useRootNavigator: true,
isScrollControlled: true,
backgroundColor: Colors.white,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
builder: (ctx) {
builder: (sheetCtx) {
return StatefulBuilder(
builder: (ctx, setSheetState) {
final filtered = options.where((o) {
@@ -1754,7 +1755,7 @@ class _AgentEditProfileScreenState
Expanded(
child: OutlinedButton(
onPressed: () =>
Navigator.pop(ctx),
Navigator.of(ctx).pop(),
style: OutlinedButton.styleFrom(
padding:
const EdgeInsets.symmetric(
@@ -1772,7 +1773,7 @@ class _AgentEditProfileScreenState
child: ElevatedButton(
onPressed: () {
onChanged(workingSelected);
Navigator.pop(ctx);
Navigator.of(ctx).pop();
},
style: ElevatedButton.styleFrom(
backgroundColor:

View File

@@ -585,6 +585,24 @@ class MessagingNotifier extends StateNotifier<MessagingState> {
}
}
/// Refetch a single conversation — used when opening chat to get fresh
/// presence (isOnline/lastSeenAt) since the list may be stale. Mirrors
/// web's selectConversation which calls getConversation on open.
Future<void> refreshConversation(String conversationId) async {
try {
final fresh = await _repository.getConversation(conversationId);
if (!mounted) return;
final updated = state.conversations.map((c) {
return c.id == conversationId
? c.copyWith(otherParty: fresh.otherParty)
: c;
}).toList();
state = state.copyWith(conversations: updated);
} catch (_) {
// Non-fatal — existing cached data remains in place
}
}
Future<void> loadUnreadCount() async {
try {
final count = await _repository.getUnreadCount();

View File

@@ -54,6 +54,9 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
final notifier = ref.read(messagingProvider.notifier);
notifier.setActiveConversation(widget.conversationId);
notifier.loadMessages(widget.conversationId);
// Refetch conversation to get fresh presence (isOnline/lastSeenAt) —
// the list snapshot may be stale. Matches web's selectConversation flow.
notifier.refreshConversation(widget.conversationId);
// Initialize mute/star state from conversation data
final conversations = ref.read(messagingProvider).conversations;
final conv = conversations.where((c) => c.id == widget.conversationId).firstOrNull;
@@ -120,6 +123,9 @@ class _ChatScreenState extends ConsumerState<ChatScreen>
});
// Refresh messages to catch anything missed while in background
ref.read(messagingProvider.notifier).loadMessages(widget.conversationId);
// Refresh conversation to pick up any presence changes that happened
// while the app was backgrounded (socket status events were missed)
ref.read(messagingProvider.notifier).refreshConversation(widget.conversationId);
// Now that user is actively viewing, mark as read
ref.read(messagingProvider.notifier).markAsRead(widget.conversationId);
}

View File

@@ -77,6 +77,9 @@ class SupportChatNotifier extends StateNotifier<SupportChatState> {
}
}
// Track message IDs we've sent via REST to avoid socket echo duplicates
final Set<String> _sentMessageIds = {};
Future<void> sendMessage(String content) async {
final chat = state.chat;
if (chat == null || content.trim().isEmpty) return;
@@ -85,6 +88,7 @@ class SupportChatNotifier extends StateNotifier<SupportChatState> {
state = state.copyWith(isSending: true);
try {
final message = await _repo.sendMessage(chat.id, content.trim());
_sentMessageIds.add(message.id);
// Dedup — socket broadcast may have already added this message
if (!state.messages.any((m) => m.id == message.id)) {
state = state.copyWith(
@@ -121,7 +125,9 @@ class SupportChatNotifier extends StateNotifier<SupportChatState> {
void addIncomingMessage(SupportMessage message) {
if (state.chat == null || message.chatId != state.chat!.id) return;
// Skip if already in list OR if we sent this via REST (socket echo)
if (state.messages.any((m) => m.id == message.id)) return;
if (_sentMessageIds.contains(message.id)) return;
state = state.copyWith(messages: [...state.messages, message]);
_repo.markAsRead(state.chat!.id);

View File

@@ -333,7 +333,17 @@ class _SupportChatScreenState extends ConsumerState<SupportChatScreen> {
}
Widget _buildMessageArea(SupportChatState state, String currentUserId) {
final groups = _groupByDate(state.messages);
// Filter out any messages with empty content (phantom/blank cards)
// and deduplicate by ID (socket echo + REST response race condition)
final seen = <String>{};
final cleanMessages = <SupportMessage>[];
for (final msg in state.messages) {
if (msg.content.trim().isEmpty) continue;
if (seen.contains(msg.id)) continue;
seen.add(msg.id);
cleanMessages.add(msg);
}
final groups = _groupByDate(cleanMessages);
return Container(
color: const Color(0xFFF9F9F9),