add desktop chat notifications
This commit is contained in:
@@ -5,6 +5,7 @@ import javafx.fxml.FXMLLoader;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.util.DesktopNotificationService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
@@ -26,5 +27,9 @@ public class PetShopApplication extends Application {
|
||||
|
||||
stage.setScene(scene);
|
||||
stage.show();
|
||||
|
||||
DesktopNotificationService.getInstance().init(
|
||||
stage.getIcons().isEmpty() ? null : stage.getIcons().get(0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import org.example.petshopdesktop.api.dto.chat.ConversationResponse;
|
||||
import org.example.petshopdesktop.api.dto.chat.MessageRequest;
|
||||
import org.example.petshopdesktop.api.dto.chat.MessageResponse;
|
||||
import org.example.petshopdesktop.auth.UserSession;
|
||||
import org.example.petshopdesktop.util.DesktopNotificationService;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
@@ -72,6 +73,16 @@ public class ChatRealtimeClient implements WebSocket.Listener {
|
||||
updateNotificationState();
|
||||
}
|
||||
|
||||
public void markConversationReplied(Long conversationId, Long senderId) {
|
||||
synchronized (lock) {
|
||||
ConversationResponse conv = globalConversations.get(conversationId);
|
||||
if (conv != null) {
|
||||
conv.setLastSenderId(senderId);
|
||||
}
|
||||
}
|
||||
updateNotificationState();
|
||||
}
|
||||
|
||||
public boolean hasActionableChats() {
|
||||
synchronized (lock) {
|
||||
UserSession session = UserSession.getInstance();
|
||||
@@ -333,7 +344,13 @@ public class ChatRealtimeClient implements WebSocket.Listener {
|
||||
if (messageListener != null) {
|
||||
messageListener.accept(message);
|
||||
}
|
||||
|
||||
|
||||
Long currentUserId = UserSession.getInstance().getUserId();
|
||||
if (message.getSenderId() != null && !message.getSenderId().equals(currentUserId)) {
|
||||
DesktopNotificationService.getInstance()
|
||||
.notifyNewMessage(message.getSenderDisplayName(), message.getContent());
|
||||
}
|
||||
|
||||
// Also update globalConversation last sender if this is the active conversation
|
||||
synchronized (lock) {
|
||||
ConversationResponse conv = globalConversations.get(message.getConversationId());
|
||||
|
||||
@@ -196,6 +196,7 @@ public class ChatController {
|
||||
MessageResponse response = ChatApi.getInstance().sendMessage(convId, request);
|
||||
Platform.runLater(() -> {
|
||||
btnSend.setDisable(false);
|
||||
realtimeClient.markConversationReplied(convId, UserSession.getInstance().getUserId());
|
||||
appendMessageIfSelected(response);
|
||||
if (selectedAttachmentFile != null) {
|
||||
clearLocalAttachment();
|
||||
@@ -380,6 +381,7 @@ public class ChatController {
|
||||
List<ConversationResponse> response = ChatApi.getInstance().listConversations();
|
||||
response.sort(Comparator.comparing(ChatController::conversationSortTime, Comparator.nullsLast(Comparator.reverseOrder())));
|
||||
Platform.runLater(() -> {
|
||||
realtimeClient.initializeState(response);
|
||||
conversations.setAll(response);
|
||||
refreshSections();
|
||||
restoreSelection();
|
||||
@@ -471,6 +473,7 @@ public class ChatController {
|
||||
.findFirst()
|
||||
.ifPresent(conversation -> {
|
||||
conversation.setLastMessage(message.getContent());
|
||||
conversation.setLastSenderId(message.getSenderId());
|
||||
conversation.setUpdatedAt(message.getTimestamp());
|
||||
conversations.sort(Comparator.comparing(ChatController::conversationSortTime, Comparator.nullsLast(Comparator.reverseOrder())));
|
||||
refreshSections();
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.example.petshopdesktop.util;
|
||||
|
||||
import javafx.embed.swing.SwingFXUtils;
|
||||
import javafx.scene.image.Image;
|
||||
|
||||
import java.awt.SystemTray;
|
||||
import java.awt.Toolkit;
|
||||
import java.awt.TrayIcon;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public class DesktopNotificationService {
|
||||
private static final DesktopNotificationService INSTANCE = new DesktopNotificationService();
|
||||
|
||||
private TrayIcon trayIcon;
|
||||
private boolean trayInitialized = false;
|
||||
|
||||
private DesktopNotificationService() {}
|
||||
|
||||
public static DesktopNotificationService getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void init(Image appIcon) {
|
||||
if (!SystemTray.isSupported()) return;
|
||||
try {
|
||||
BufferedImage image;
|
||||
if (appIcon != null) {
|
||||
image = SwingFXUtils.fromFXImage(appIcon, null);
|
||||
} else {
|
||||
image = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
|
||||
}
|
||||
trayIcon = new TrayIcon(image, "Leon's Pet Store");
|
||||
trayIcon.setImageAutoSize(true);
|
||||
SystemTray.getSystemTray().add(trayIcon);
|
||||
trayInitialized = true;
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
public void notifyNewMessage(String senderName, String content) {
|
||||
Thread t = new Thread(() -> {
|
||||
Toolkit.getDefaultToolkit().beep();
|
||||
showNotification(senderName, content);
|
||||
});
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
}
|
||||
|
||||
private void showNotification(String senderName, String content) {
|
||||
String title = senderName != null ? senderName : "New Message";
|
||||
String body = content != null
|
||||
? (content.length() > 100 ? content.substring(0, 100) + "..." : content)
|
||||
: "Attachment";
|
||||
|
||||
if (trayInitialized && trayIcon != null) {
|
||||
trayIcon.displayMessage(title, body, TrayIcon.MessageType.INFO);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
new ProcessBuilder("notify-send", title, body).start();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user