fix: improve chat message deduplication, add conversation refresh logic, and update UI components for consistency
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user