Implement chat features
This commit is contained in:
@@ -11,7 +11,9 @@ import java.net.http.WebSocket;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
@@ -39,6 +41,10 @@ public class ChatRealtimeClient implements WebSocket.Listener {
|
||||
private Consumer<String> statusListener;
|
||||
private volatile String currentStatus = "Chat disconnected";
|
||||
|
||||
private final Map<Long, ConversationResponse> globalConversations = new HashMap<>();
|
||||
private final List<Consumer<Boolean>> notificationListeners = new ArrayList<>();
|
||||
private boolean lastNotificationState = false;
|
||||
|
||||
private ChatRealtimeClient() {
|
||||
this.httpClient = HttpClient.newBuilder()
|
||||
.connectTimeout(Duration.ofSeconds(10))
|
||||
@@ -49,6 +55,59 @@ public class ChatRealtimeClient implements WebSocket.Listener {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void addNotificationListener(Consumer<Boolean> listener) {
|
||||
synchronized (lock) {
|
||||
notificationListeners.add(listener);
|
||||
listener.accept(lastNotificationState);
|
||||
}
|
||||
}
|
||||
|
||||
public void initializeState(List<ConversationResponse> conversations) {
|
||||
synchronized (lock) {
|
||||
globalConversations.clear();
|
||||
for (ConversationResponse conv : conversations) {
|
||||
globalConversations.put(conv.getId(), conv);
|
||||
}
|
||||
}
|
||||
updateNotificationState();
|
||||
}
|
||||
|
||||
public boolean hasActionableChats() {
|
||||
synchronized (lock) {
|
||||
UserSession session = UserSession.getInstance();
|
||||
Long currentUserId = session.getUserId();
|
||||
for (ConversationResponse conv : globalConversations.values()) {
|
||||
if ("CLOSED".equals(conv.getStatus())) continue;
|
||||
|
||||
// Needs pickup
|
||||
if (conv.getHumanRequestedAt() != null && conv.getStaffId() == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateNotificationState() {
|
||||
boolean currentState = hasActionableChats();
|
||||
List<Consumer<Boolean>> listeners;
|
||||
synchronized (lock) {
|
||||
if (currentState == lastNotificationState) return;
|
||||
lastNotificationState = currentState;
|
||||
listeners = new ArrayList<>(notificationListeners);
|
||||
}
|
||||
for (Consumer<Boolean> listener : listeners) {
|
||||
listener.accept(currentState);
|
||||
}
|
||||
}
|
||||
|
||||
public void setConversationListener(Consumer<ConversationResponse> conversationListener) {
|
||||
this.conversationListener = conversationListener;
|
||||
}
|
||||
@@ -230,6 +289,8 @@ public class ChatRealtimeClient implements WebSocket.Listener {
|
||||
destinationBySubscription.clear();
|
||||
conversationsSubscriptionId = null;
|
||||
conversationMessagesSubscriptionId = null;
|
||||
globalConversations.clear();
|
||||
updateNotificationState();
|
||||
}
|
||||
|
||||
private void handleFrame(String frame) {
|
||||
@@ -272,11 +333,25 @@ public class ChatRealtimeClient implements WebSocket.Listener {
|
||||
if (messageListener != null) {
|
||||
messageListener.accept(message);
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
updateNotificationState();
|
||||
} else {
|
||||
ConversationResponse conversation = ApiClient.getInstance().getObjectMapper().readValue(bodyPart, ConversationResponse.class);
|
||||
synchronized (lock) {
|
||||
globalConversations.put(conversation.getId(), conversation);
|
||||
}
|
||||
if (conversationListener != null) {
|
||||
conversationListener.accept(conversation);
|
||||
}
|
||||
updateNotificationState();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
publishStatus("Chat update failed");
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.example.petshopdesktop.api.dto.chat;
|
||||
|
||||
public class ChatAttachmentResponse {
|
||||
private String url;
|
||||
private String fileName;
|
||||
private String mimeType;
|
||||
private long size;
|
||||
|
||||
public ChatAttachmentResponse() {
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return url;
|
||||
}
|
||||
|
||||
public void setUrl(String url) {
|
||||
this.url = url;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String fileName) {
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
public String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
public void setMimeType(String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void setSize(long size) {
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ public class ConversationResponse {
|
||||
private String status;
|
||||
private String mode;
|
||||
private String lastMessage;
|
||||
private Long lastSenderId;
|
||||
private LocalDateTime humanRequestedAt;
|
||||
private LocalDateTime createdAt;
|
||||
private LocalDateTime updatedAt;
|
||||
@@ -16,6 +17,14 @@ public class ConversationResponse {
|
||||
public ConversationResponse() {
|
||||
}
|
||||
|
||||
public Long getLastSenderId() {
|
||||
return lastSenderId;
|
||||
}
|
||||
|
||||
public void setLastSenderId(Long lastSenderId) {
|
||||
this.lastSenderId = lastSenderId;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ public class MessageResponse {
|
||||
private Long id;
|
||||
private Long conversationId;
|
||||
private Long senderId;
|
||||
private String senderAvatarUrl;
|
||||
private String content;
|
||||
private LocalDateTime timestamp;
|
||||
private Boolean isRead;
|
||||
@@ -41,6 +42,14 @@ public class MessageResponse {
|
||||
this.senderId = senderId;
|
||||
}
|
||||
|
||||
public String getSenderAvatarUrl() {
|
||||
return senderAvatarUrl;
|
||||
}
|
||||
|
||||
public void setSenderAvatarUrl(String senderAvatarUrl) {
|
||||
this.senderAvatarUrl = senderAvatarUrl;
|
||||
}
|
||||
|
||||
public String getContent() {
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ package org.example.petshopdesktop.api.endpoints;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import org.example.petshopdesktop.api.ApiClient;
|
||||
import org.example.petshopdesktop.api.dto.chat.ChatAttachmentResponse;
|
||||
import org.example.petshopdesktop.api.dto.chat.ConversationRequest;
|
||||
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 java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public class ChatApi {
|
||||
@@ -42,4 +44,8 @@ public class ChatApi {
|
||||
public MessageResponse sendMessage(Long conversationId, MessageRequest request) throws Exception {
|
||||
return apiClient.post("/api/v1/chat/conversations/" + conversationId + "/messages", request, MessageResponse.class);
|
||||
}
|
||||
|
||||
public ChatAttachmentResponse uploadAttachment(Path filePath) throws Exception {
|
||||
return apiClient.postMultipart("/api/v1/chat/attachments", "file", filePath, ChatAttachmentResponse.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.example.petshopdesktop.controllers;
|
||||
import javafx.application.Platform;
|
||||
import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.Label;
|
||||
@@ -15,7 +16,11 @@ import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Priority;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import javafx.scene.paint.ImagePattern;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.scene.image.Image;
|
||||
import org.example.petshopdesktop.api.ChatRealtimeClient;
|
||||
import org.example.petshopdesktop.api.dto.chat.ChatAttachmentResponse;
|
||||
import org.example.petshopdesktop.api.dto.chat.ConversationResponse;
|
||||
import org.example.petshopdesktop.api.dto.chat.MessageRequest;
|
||||
import org.example.petshopdesktop.api.dto.chat.MessageResponse;
|
||||
@@ -53,6 +58,9 @@ public class ChatController {
|
||||
@FXML
|
||||
private Button btnRefresh;
|
||||
|
||||
@FXML
|
||||
private Button btnAttachment;
|
||||
|
||||
@FXML
|
||||
private Label lblConversationTitle;
|
||||
|
||||
@@ -63,6 +71,7 @@ public class ChatController {
|
||||
private final Map<Long, String> customerLabels = new HashMap<>();
|
||||
private final ChatRealtimeClient realtimeClient = ChatRealtimeClient.getInstance();
|
||||
private ConversationResponse selectedConversation;
|
||||
private ChatAttachmentResponse selectedAttachment;
|
||||
|
||||
@FXML
|
||||
public void initialize() {
|
||||
@@ -78,7 +87,19 @@ public class ChatController {
|
||||
}
|
||||
|
||||
Label title = new Label(getConversationTitle(item));
|
||||
title.setStyle("-fx-font-weight: bold; -fx-text-fill: #1f2937;");
|
||||
|
||||
// Bold title if needs attention
|
||||
UserSession session = UserSession.getInstance();
|
||||
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);
|
||||
|
||||
if (needsPickup || needsReply) {
|
||||
title.setStyle("-fx-font-weight: bold; -fx-text-fill: #FF6B6B;");
|
||||
} else {
|
||||
title.setStyle("-fx-font-weight: bold; -fx-text-fill: #1f2937;");
|
||||
}
|
||||
Label preview = new Label(item.getLastMessage() == null ? "" : item.getLastMessage());
|
||||
preview.setStyle("-fx-text-fill: #64748b;");
|
||||
preview.setWrapText(true);
|
||||
@@ -134,14 +155,78 @@ public class ChatController {
|
||||
}
|
||||
|
||||
String content = txtMessage.getText() == null ? "" : txtMessage.getText().trim();
|
||||
if (content.isEmpty()) {
|
||||
if (content.isEmpty() && selectedAttachment == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Long convId = selectedConversation.getId();
|
||||
var attachment = selectedAttachment;
|
||||
|
||||
txtMessage.clear();
|
||||
btnSend.setDisable(true);
|
||||
selectedAttachment = null;
|
||||
btnAttachment.setText("📎");
|
||||
btnAttachment.setStyle("-fx-background-color: #e2e8f0; -fx-background-radius: 12; -fx-text-fill: #475569; -fx-cursor: hand;");
|
||||
|
||||
lblChatStatus.setText("Sending message...");
|
||||
sendMessage(selectedConversation.getId(), content);
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
MessageRequest request = new MessageRequest(content);
|
||||
if (attachment != null) {
|
||||
request.setAttachmentUrl(attachment.getUrl());
|
||||
request.setAttachmentName(attachment.getFileName());
|
||||
request.setAttachmentMimeType(attachment.getMimeType());
|
||||
request.setAttachmentSizeBytes(attachment.getSize());
|
||||
}
|
||||
MessageResponse response = ChatApi.getInstance().sendMessage(convId, request);
|
||||
Platform.runLater(() -> {
|
||||
btnSend.setDisable(false);
|
||||
appendMessageIfSelected(response);
|
||||
lblChatStatus.setText("Message sent");
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
txtMessage.setText(content);
|
||||
btnSend.setDisable(false);
|
||||
selectedAttachment = attachment;
|
||||
btnAttachment.setText("📎 " + attachment.getFileName());
|
||||
lblChatStatus.setText("Chat send failed");
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ChatController.sendMessage",
|
||||
e,
|
||||
"Sending chat message for conversation " + convId);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@FXML
|
||||
void btnAttachmentClicked(ActionEvent event) {
|
||||
java.io.File file = org.example.petshopdesktop.util.FilePickerSupport.pickAnyFile(btnAttachment.getScene().getWindow());
|
||||
if (file == null) return;
|
||||
|
||||
btnAttachment.setDisable(true);
|
||||
lblChatStatus.setText("Uploading attachment...");
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
var response = ChatApi.getInstance().uploadAttachment(file.toPath());
|
||||
Platform.runLater(() -> {
|
||||
selectedAttachment = response;
|
||||
btnAttachment.setText("📎 " + response.getFileName());
|
||||
btnAttachment.setStyle("-fx-background-color: #dcfce7; -fx-background-radius: 12; -fx-text-fill: #166534; -fx-cursor: hand;");
|
||||
lblChatStatus.setText("File ready to send");
|
||||
btnAttachment.setDisable(false);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("ChatController.btnAttachmentClicked", e, "Uploading chat attachment");
|
||||
Platform.runLater(() -> {
|
||||
lblChatStatus.setText("Upload failed");
|
||||
btnAttachment.setDisable(false);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadCustomers() {
|
||||
@@ -207,31 +292,6 @@ public class ChatController {
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void sendMessage(Long conversationId, String content) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
MessageResponse response = ChatApi.getInstance().sendMessage(conversationId, new MessageRequest(content));
|
||||
Platform.runLater(() -> {
|
||||
btnSend.setDisable(false);
|
||||
appendMessageIfSelected(response);
|
||||
if (selectedConversation != null && selectedConversation.getId().equals(conversationId)) {
|
||||
lblChatStatus.setText("Message sent");
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
Platform.runLater(() -> {
|
||||
txtMessage.setText(content);
|
||||
btnSend.setDisable(false);
|
||||
lblChatStatus.setText("Chat send failed");
|
||||
ActivityLogger.getInstance().logException(
|
||||
"ChatController.sendMessage",
|
||||
e,
|
||||
"Sending chat message for conversation " + conversationId);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void renderMessages(List<MessageResponse> messages) {
|
||||
vbMessages.getChildren().clear();
|
||||
for (MessageResponse message : messages) {
|
||||
@@ -307,6 +367,20 @@ public class ChatController {
|
||||
|
||||
private HBox createMessageBubble(MessageResponse message) {
|
||||
boolean mine = message.getSenderId() != null && message.getSenderId().equals(UserSession.getInstance().getUserId());
|
||||
|
||||
Circle avatar = new Circle(16);
|
||||
avatar.setFill(javafx.scene.paint.Color.web(mine ? "#0f766e" : "#cbd5e1"));
|
||||
if (message.getSenderAvatarUrl() != null && !message.getSenderAvatarUrl().isBlank()) {
|
||||
try {
|
||||
String fullUrl = org.example.petshopdesktop.api.ApiConfig.getInstance().getBaseUrl() + message.getSenderAvatarUrl();
|
||||
Image img = new Image(fullUrl, true);
|
||||
img.errorProperty().addListener((obs, old, err) -> {
|
||||
if (err) avatar.setFill(javafx.scene.paint.Color.web(mine ? "#0f766e" : "#cbd5e1"));
|
||||
});
|
||||
avatar.setFill(new ImagePattern(img));
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
Label author = new Label(resolveAuthorLabel(message));
|
||||
author.setStyle("-fx-font-weight: bold; -fx-text-fill: " + (mine ? "#ffffff" : "#1f2937") + ";");
|
||||
|
||||
@@ -324,17 +398,23 @@ public class ChatController {
|
||||
}
|
||||
|
||||
if (message.getAttachmentUrl() != null && !message.getAttachmentUrl().isBlank()) {
|
||||
String attachmentLabel = message.getAttachmentName();
|
||||
if (attachmentLabel == null || attachmentLabel.isBlank()) {
|
||||
attachmentLabel = "Attachment";
|
||||
}
|
||||
String attachmentLabel = "📎 " + (message.getAttachmentName() == null || message.getAttachmentName().isBlank() ? "Attachment" : message.getAttachmentName());
|
||||
if (message.getAttachmentSizeBytes() != null && message.getAttachmentSizeBytes() > 0) {
|
||||
attachmentLabel = attachmentLabel + " (" + formatSize(message.getAttachmentSizeBytes()) + ")";
|
||||
}
|
||||
Label attachment = new Label(attachmentLabel);
|
||||
attachment.setWrapText(true);
|
||||
attachment.setStyle("-fx-text-fill: " + (mine ? "#dbeafe" : "#0f766e") + "; -fx-underline: true;");
|
||||
bubble.getChildren().add(attachment);
|
||||
Button attachmentBtn = new Button(attachmentLabel);
|
||||
attachmentBtn.setStyle("-fx-background-color: " + (mine ? "rgba(255,255,255,0.15)" : "rgba(0,0,0,0.05)") + "; -fx-text-fill: " + (mine ? "#ffffff" : "#0f766e") + "; -fx-cursor: hand; -fx-background-radius: 8; -fx-padding: 6 10;");
|
||||
attachmentBtn.setOnAction(e -> {
|
||||
try {
|
||||
String fullUrl = org.example.petshopdesktop.api.ApiConfig.getInstance().getBaseUrl() + message.getAttachmentUrl();
|
||||
if (java.awt.Desktop.isDesktopSupported()) {
|
||||
java.awt.Desktop.getDesktop().browse(new java.net.URI(fullUrl));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
ActivityLogger.getInstance().logException("ChatController.attachmentOpen", ex, "Opening attachment URL");
|
||||
}
|
||||
});
|
||||
bubble.getChildren().add(attachmentBtn);
|
||||
}
|
||||
|
||||
bubble.getChildren().add(timestamp);
|
||||
@@ -346,10 +426,13 @@ public class ChatController {
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
HBox container = new HBox(12);
|
||||
container.setAlignment(javafx.geometry.Pos.BOTTOM_LEFT);
|
||||
|
||||
if (mine) {
|
||||
container.getChildren().addAll(spacer, bubble);
|
||||
container.getChildren().addAll(spacer, bubble, avatar);
|
||||
container.setAlignment(javafx.geometry.Pos.BOTTOM_RIGHT);
|
||||
} else {
|
||||
container.getChildren().addAll(bubble, spacer);
|
||||
container.getChildren().addAll(avatar, bubble, spacer);
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.scene.paint.ImagePattern;
|
||||
import javafx.scene.shape.Circle;
|
||||
import javafx.stage.Stage;
|
||||
import org.example.petshopdesktop.api.endpoints.ChatApi;
|
||||
import org.example.petshopdesktop.api.ChatRealtimeClient;
|
||||
import org.example.petshopdesktop.api.dto.auth.AvatarUploadResponse;
|
||||
import org.example.petshopdesktop.api.dto.auth.UserInfoResponse;
|
||||
@@ -55,7 +56,10 @@ public class MainLayoutController {
|
||||
private Button btnInventory;
|
||||
|
||||
@FXML
|
||||
private Button btnLogout;
|
||||
private Button btnChat;
|
||||
|
||||
@FXML
|
||||
private Circle chatBadge;
|
||||
|
||||
@FXML
|
||||
private Button btnPets;
|
||||
@@ -85,7 +89,7 @@ public class MainLayoutController {
|
||||
private Button btnAnalytics;
|
||||
|
||||
@FXML
|
||||
private Button btnChat;
|
||||
private Button btnLogout;
|
||||
|
||||
@FXML
|
||||
private StackPane logoContainer;
|
||||
@@ -263,6 +267,14 @@ public class MainLayoutController {
|
||||
refreshProfileHeader();
|
||||
applyRBAC();
|
||||
|
||||
ChatRealtimeClient.getInstance().addNotificationListener(hasActionable -> {
|
||||
Platform.runLater(() -> {
|
||||
if (chatBadge != null) {
|
||||
chatBadge.setVisible(hasActionable);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
UserSession session = UserSession.getInstance();
|
||||
if (session.isAdmin()) {
|
||||
loadView("analytics-view.fxml");
|
||||
@@ -391,7 +403,16 @@ public class MainLayoutController {
|
||||
|
||||
btnSalesHistory.setText(isAdmin ? "Sales History" : "Sales");
|
||||
|
||||
|
||||
// Initial chat state and subscription
|
||||
new Thread(() -> {
|
||||
try {
|
||||
var conversations = ChatApi.getInstance().listConversations();
|
||||
ChatRealtimeClient.getInstance().initializeState(conversations);
|
||||
ChatRealtimeClient.getInstance().subscribeToConversations();
|
||||
} catch (Exception e) {
|
||||
ActivityLogger.getInstance().logException("MainLayoutController.applyRBAC", e, "Initializing chat notifications");
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void loadView(String fxmlFile) {
|
||||
|
||||
@@ -24,6 +24,13 @@ public final class FilePickerSupport {
|
||||
return pickImageFileWithJavaFx(ownerWindow);
|
||||
}
|
||||
|
||||
public static File pickAnyFile(Window ownerWindow) {
|
||||
if (shouldUseAwtPicker()) {
|
||||
return pickAnyFileWithSwing();
|
||||
}
|
||||
return pickAnyFileWithJavaFx(ownerWindow);
|
||||
}
|
||||
|
||||
private static boolean shouldUseAwtPicker() {
|
||||
if (GraphicsEnvironment.isHeadless()) {
|
||||
return false;
|
||||
@@ -40,6 +47,12 @@ public final class FilePickerSupport {
|
||||
return chooser.showOpenDialog(ownerWindow);
|
||||
}
|
||||
|
||||
private static File pickAnyFileWithJavaFx(Window ownerWindow) {
|
||||
FileChooser chooser = new FileChooser();
|
||||
chooser.setTitle("Choose File Attachment");
|
||||
return chooser.showOpenDialog(ownerWindow);
|
||||
}
|
||||
|
||||
private static File pickImageFileWithSwing() {
|
||||
AtomicReference<File> selectedFile = new AtomicReference<>();
|
||||
Runnable dialogTask = () -> {
|
||||
@@ -74,4 +87,38 @@ public final class FilePickerSupport {
|
||||
|
||||
return selectedFile.get();
|
||||
}
|
||||
|
||||
private static File pickAnyFileWithSwing() {
|
||||
AtomicReference<File> selectedFile = new AtomicReference<>();
|
||||
Runnable dialogTask = () -> {
|
||||
try {
|
||||
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
JFileChooser chooser = new JFileChooser();
|
||||
chooser.setDialogTitle("Choose File Attachment");
|
||||
chooser.setAcceptAllFileFilterUsed(true);
|
||||
|
||||
int result = chooser.showOpenDialog((Component) null);
|
||||
if (result == JFileChooser.APPROVE_OPTION) {
|
||||
selectedFile.set(chooser.getSelectedFile());
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
if (java.awt.EventQueue.isDispatchThread()) {
|
||||
dialogTask.run();
|
||||
} else {
|
||||
java.awt.EventQueue.invokeAndWait(dialogTask);
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
} catch (InvocationTargetException ex) {
|
||||
throw new IllegalStateException("Failed to open Swing file picker", ex.getCause());
|
||||
}
|
||||
|
||||
return selectedFile.get();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user