From 11618f0cfd9b699148ed799323979c14906d2e3e Mon Sep 17 00:00:00 2001 From: Harkamal Randhawa Date: Sun, 19 Apr 2026 17:33:48 -0600 Subject: [PATCH] fix read state tracking --- .../api/ChatRealtimeClient.java | 25 +++++++++++++++++-- .../controllers/ChatController.java | 7 +++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/desktop/src/main/java/org/example/petshopdesktop/api/ChatRealtimeClient.java b/desktop/src/main/java/org/example/petshopdesktop/api/ChatRealtimeClient.java index 93f90a73..a505c514 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/api/ChatRealtimeClient.java +++ b/desktop/src/main/java/org/example/petshopdesktop/api/ChatRealtimeClient.java @@ -14,8 +14,10 @@ import java.nio.charset.StandardCharsets; import java.time.Duration; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.atomic.AtomicInteger; @@ -43,6 +45,7 @@ public class ChatRealtimeClient implements WebSocket.Listener { private volatile String currentStatus = "Chat disconnected"; private final Map globalConversations = new HashMap<>(); + private final Set readConversationIds = new HashSet<>(); private final List> notificationListeners = new ArrayList<>(); private boolean lastNotificationState = false; @@ -79,10 +82,24 @@ public class ChatRealtimeClient implements WebSocket.Listener { if (conv != null) { conv.setLastSenderId(senderId); } + readConversationIds.add(conversationId); } updateNotificationState(); } + public void markConversationRead(Long conversationId) { + synchronized (lock) { + readConversationIds.add(conversationId); + } + updateNotificationState(); + } + + public boolean isConversationRead(Long conversationId) { + synchronized (lock) { + return readConversationIds.contains(conversationId); + } + } + public boolean hasActionableChats() { synchronized (lock) { UserSession session = UserSession.getInstance(); @@ -98,7 +115,8 @@ public class ChatRealtimeClient implements WebSocket.Listener { // Needs reply (assigned to me and last sender was someone else - customer) if (currentUserId != null && currentUserId.equals(conv.getStaffId())) { - if (conv.getLastSenderId() != null && !conv.getLastSenderId().equals(currentUserId)) { + if (conv.getLastSenderId() != null && !conv.getLastSenderId().equals(currentUserId) + && !readConversationIds.contains(conv.getId())) { return true; } } @@ -302,6 +320,7 @@ public class ChatRealtimeClient implements WebSocket.Listener { conversationsSubscriptionId = null; conversationMessagesSubscriptionId = null; globalConversations.clear(); + readConversationIds.clear(); updateNotificationState(); } @@ -352,13 +371,15 @@ public class ChatRealtimeClient implements WebSocket.Listener { .notifyNewMessage(message.getSenderDisplayName(), message.getContent()); } - // Also update globalConversation last sender if this is the active conversation synchronized (lock) { ConversationResponse conv = globalConversations.get(message.getConversationId()); if (conv != null) { conv.setLastMessage(message.getContent()); conv.setLastSenderId(message.getSenderId()); } + if (message.getSenderId() != null && !message.getSenderId().equals(currentUserId)) { + readConversationIds.remove(message.getConversationId()); + } } updateNotificationState(); } else { diff --git a/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java b/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java index 4417bd23..3ba9e995 100644 --- a/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java +++ b/desktop/src/main/java/org/example/petshopdesktop/controllers/ChatController.java @@ -282,6 +282,8 @@ public class ChatController { realtimeClient.subscribeToConversation(newValue.getId()); } updateChatState(newValue); + realtimeClient.markConversationRead(newValue.getId()); + refreshSections(); } private void updateChatState(ConversationResponse conv) { @@ -320,7 +322,8 @@ public class ChatController { Long currentUserId = session.getUserId(); boolean needsPickup = item.getHumanRequestedAt() != null && item.getStaffId() == null; boolean needsReply = currentUserId != null && currentUserId.equals(item.getStaffId()) - && item.getLastSenderId() != null && !item.getLastSenderId().equals(currentUserId); + && item.getLastSenderId() != null && !item.getLastSenderId().equals(currentUserId) + && !ChatRealtimeClient.getInstance().isConversationRead(item.getId()); if (needsPickup || needsReply) { title.setStyle("-fx-font-weight: bold; -fx-text-fill: #FF6B6B;"); @@ -460,6 +463,8 @@ public class ChatController { vbMessages.getChildren().add(createMessageBubble(message)); if (message.getId() != null) renderedMessageIds.add(message.getId()); scrollMessagesToBottom(); + realtimeClient.markConversationRead(message.getConversationId()); + refreshSections(); } } catch (Exception e) { ActivityLogger.getInstance().logException(