From 079145b4e2526a0a478fbd589a875ac22aab7c8a Mon Sep 17 00:00:00 2001 From: mohammadreza Date: Wed, 24 Jun 2026 12:54:17 +0330 Subject: [PATCH 01/29] build: add javafx dependencies to project --- pom.xml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/pom.xml b/pom.xml index c7c56af..76fe42b 100644 --- a/pom.xml +++ b/pom.xml @@ -13,5 +13,31 @@ 25 UTF-8 + + + org.openjfx + javafx-controls + 21 + + + org.openjfx + javafx-fxml + 21 + + + + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + com.university.chat.Client.UI.Launcher + + + + \ No newline at end of file -- 2.54.0 From 8454be1cec41c331cc5738f1e2271d4bbf30f076 Mon Sep 17 00:00:00 2001 From: mohammadreza Date: Wed, 24 Jun 2026 12:56:42 +0330 Subject: [PATCH 02/29] feat: add javafx chat ui and styling --- .../chat/Client/UI/ChatController.java | 337 ++++++++++++++++++ src/main/resources/chat_style.css | 77 ++++ .../com/university/chat/Client/UI/chat.fxml | 52 +++ 3 files changed, 466 insertions(+) create mode 100644 src/main/java/com/university/chat/Client/UI/ChatController.java create mode 100644 src/main/resources/chat_style.css create mode 100644 src/main/resources/com/university/chat/Client/UI/chat.fxml diff --git a/src/main/java/com/university/chat/Client/UI/ChatController.java b/src/main/java/com/university/chat/Client/UI/ChatController.java new file mode 100644 index 0000000..e8f31bc --- /dev/null +++ b/src/main/java/com/university/chat/Client/UI/ChatController.java @@ -0,0 +1,337 @@ +package com.university.chat.Client.UI; + +import com.university.chat.Client.Net.chatClient; +import com.university.chat.Client.TransferProgress; +import com.university.chat.Common.ChatMessage; +import com.university.chat.Common.FileMessage; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.control.*; +import javafx.scene.layout.*; +import javafx.stage.FileChooser; +import javafx.stage.Stage; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.*; + +public class ChatController { + + @FXML + private ListView usersList; + + @FXML + private TextArea publicChatArea; + + @FXML + private AnchorPane chatAnchor; + + @FXML + private TextField messageField; + + @FXML + private BorderPane titleBar; + + private double xOffset = 0; + private double yOffset = 0; + + private chatClient client; + private String selectedUser = null; + + private final Map privateChatViews = new HashMap<>(); + private final Map privateTextAreas = new HashMap<>(); + private final Map privateInputFields = new HashMap<>(); + + @FXML + private VBox fileTransferBox; + + @FXML + private Label fileTransferLabel; + + @FXML + private ProgressBar fileTransferProgress; + + + @FXML + private void initialize() { + + titleBar.setOnMousePressed(event -> { + xOffset = event.getSceneX(); + yOffset = event.getSceneY(); + }); + + titleBar.setOnMouseDragged(event -> { + Stage stage = (Stage) titleBar.getScene().getWindow(); + stage.setX(event.getScreenX() - xOffset); + stage.setY(event.getScreenY() - yOffset); + }); + + usersList.getSelectionModel().selectedItemProperty().addListener((obs, oldVal, newVal) -> { + + if (newVal == null || newVal.isBlank()) { + selectedUser = null; + chatAnchor.getChildren().clear(); + return; + } + + String clickedUser = newVal.trim(); + + if (client != null && clickedUser.equals(client.getUsername())) { + selectedUser = null; + chatAnchor.getChildren().clear(); + return; + } + + selectedUser = clickedUser; + openPrivateChat(clickedUser); + }); + } + + public void setClient(chatClient client) { + + this.client = client; + + if (client.getServerListener() != null) { + client.getServerListener().setOnMessageReceived(this::handleIncomingMessage); + } + + try { + client.requestUserList(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void handleIncomingMessage(Object obj) { + + if (obj instanceof ChatMessage msg) { + + switch (msg.getType()) { + + case USER_LIST -> { + String content = msg.getContent(); + if (content != null) { + + List users = Arrays.stream(content.split(",")) + .map(String::trim) + .filter(u -> !u.isEmpty()) + .toList(); + + updateUsersList(users); + } + } + + case PUBLIC_MESSAGE -> Platform.runLater(() -> + publicChatArea.appendText(msg.getSender() + ": " + msg.getContent() + "\n")); + + case PRIVATE_MESSAGE -> Platform.runLater(() -> + appendPrivateMessage(msg.getSender(), + msg.getSender() + ": " + msg.getContent())); + + + } + } + else if (obj instanceof FileMessage msg) { + + Platform.runLater(() -> { + + FileChooser chooser = new FileChooser(); + chooser.setInitialFileName(msg.getFilename()); + + File file = chooser.showSaveDialog(usersList.getScene().getWindow()); + + if(file != null){ + try { + Files.write(file.toPath(), msg.getData()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + }); + } + } + + public void updateUsersList(List users) { + Platform.runLater(() -> + usersList.getItems().setAll(users)); + } + + @FXML + public void sendPublicMessage() { + + String text = messageField.getText(); + + if (text == null || text.trim().isEmpty()) return; + + text = text.trim(); + + try { + + client.sendPublicMessage(text); + publicChatArea.appendText("Me: " + text + "\n"); + messageField.clear(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + + private void sendPrivateMessage(String receiver) { + + TextField inputField = privateInputFields.get(receiver); + TextArea chatArea = privateTextAreas.get(receiver); + + if (inputField == null || chatArea == null) return; + + String text = inputField.getText(); + + if (text == null || text.trim().isEmpty()) return; + + text = text.trim(); + + try { + + client.sendPrivateMessage(receiver, text); + chatArea.appendText("Me: " + text + "\n"); + inputField.clear(); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + @FXML + private void sendFile() { + + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Select file to send"); + + Stage stage = (Stage) usersList.getScene().getWindow(); + File file = fileChooser.showOpenDialog(stage); + + if (file == null) return; + + try { + + updateFileTransfer(file.length()); + client.sendFile(selectedUser,file); + publicChatArea.appendText("Me sent file: " + file.getName() + "\n"); + } + catch (Exception e){ + e.printStackTrace(); + } + } + + + private void openPrivateChat(String user) { + + BorderPane chatView = privateChatViews.get(user); + + if (chatView == null) { + chatView = createPrivateChatView(user); + privateChatViews.put(user, chatView); + } + + chatAnchor.getChildren().setAll(chatView); + + AnchorPane.setTopAnchor(chatView, 0.0); + AnchorPane.setBottomAnchor(chatView, 0.0); + AnchorPane.setLeftAnchor(chatView, 0.0); + AnchorPane.setRightAnchor(chatView, 0.0); + } + + private BorderPane createPrivateChatView(String user) { + + TextArea chatArea = new TextArea(); + chatArea.setEditable(false); + chatArea.setWrapText(true); + + TextField inputField = new TextField(); + inputField.setPromptText("Message to " + user); + + Button sendButton = new Button("Send"); + + sendButton.setOnAction(e -> sendPrivateMessage(user)); + inputField.setOnAction(e -> sendPrivateMessage(user)); + + HBox bottom = new HBox(8, inputField, sendButton); + bottom.setPadding(new Insets(8)); + inputField.setPrefWidth(300); + + BorderPane root = new BorderPane(); + root.setCenter(chatArea); + root.setBottom(bottom); + + privateTextAreas.put(user, chatArea); + privateInputFields.put(user, inputField); + + return root; + } + + + private void appendPrivateMessage(String user, String line) { + + BorderPane view = privateChatViews.get(user); + + if (view == null) { + view = createPrivateChatView(user); + privateChatViews.put(user, view); + } + + TextArea area = privateTextAreas.get(user); + + if (area != null) { + area.appendText(line + "\n"); + } + } + + + public void showFileTransfer(String fileName) { + Platform.runLater(() -> { + fileTransferBox.setVisible(true); + fileTransferBox.setManaged(true); + fileTransferProgress.setProgress(0); + fileTransferLabel.setText("Sending " + fileName + "..."); + }); + } + + public void updateFileTransfer(long length) { + Platform.runLater(() -> { + + fileTransferProgress.setProgress(length); + fileTransferLabel.setText(String.format( + "Sending... %d", + (int) (length * 100) + )); + }); + } + + public void finishFileTransfer(String fileName) { + Platform.runLater(() -> { + fileTransferProgress.setProgress(1.0); + fileTransferLabel.setText(fileName + " sent successfully"); + }); + } + + + @FXML + private void closeApp() { + System.exit(0); + } + + @FXML + private void minimize() { + Stage stage = (Stage) usersList.getScene().getWindow(); + stage.setIconified(true); + } + + @FXML + private void maximize() { + Stage stage = (Stage) usersList.getScene().getWindow(); + stage.setMaximized(!stage.isMaximized()); + } +} diff --git a/src/main/resources/chat_style.css b/src/main/resources/chat_style.css new file mode 100644 index 0000000..0d1b889 --- /dev/null +++ b/src/main/resources/chat_style.css @@ -0,0 +1,77 @@ +.root { + -fx-font-family: "Segoe UI", "Helvetica", sans-serif; + -fx-background-color: #1e1e2f; +} + +.title-bar { + -fx-background-color: #262638; + -fx-padding: 5 10 5 10; + -fx-alignment: CENTER_LEFT; +} + +.title-text { + -fx-text-fill: #a0a0c0; + -fx-font-size: 13px; + -fx-font-weight: bold; +} + +.title-button { + -fx-background-color: transparent; + -fx-text-fill: white; + -fx-font-size: 14px; + -fx-cursor: hand; +} + +.title-button:hover { + -fx-background-color: #3b3b55; +} + +.title-button-close:hover { + -fx-background-color: #e81123; +} + + +.label { + -fx-text-fill: #d1d1e0; + } + +.text-field { + -fx-background-color: #2a2a3d; + -fx-text-fill: white; + -fx-background-radius: 6; + -fx-border-color: #3c3c55; + -fx-padding: 8; +} + +.button { + -fx-background-color: #4da3ff; + -fx-text-fill: white; + -fx-background-radius: 6; + -fx-cursor: hand; + -fx-padding: 6 15 6 15; +} + +.button:hover { + -fx-background-color: #61afff; +} + +.list-view, .text-area { + -fx-background-color: #262638; + -fx-control-inner-background: #262638; + -fx-text-fill: white; +} + + +.attach-button{ + -fx-font-size:16px; + -fx-background-color: transparent; +} + +.attach-button:hover{ + -fx-background-color:#3b3b55; +} + + +.content-wrapper { + -fx-padding: 20; +} diff --git a/src/main/resources/com/university/chat/Client/UI/chat.fxml b/src/main/resources/com/university/chat/Client/UI/chat.fxml new file mode 100644 index 0000000..853d706 --- /dev/null +++ b/src/main/resources/com/university/chat/Client/UI/chat.fxml @@ -0,0 +1,52 @@ + + + + + + + + +
+ + +