Develop #1
@@ -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<String> 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<String, BorderPane> privateChatViews = new HashMap<>();
|
||||
private final Map<String, TextArea> privateTextAreas = new HashMap<>();
|
||||
private final Map<String, TextField> 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<String> 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<String> 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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
|
||||
<BorderPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="com.university.chat.Client.UI.ChatController">
|
||||
|
||||
<top>
|
||||
<BorderPane fx:id="titleBar" styleClass="title-bar">
|
||||
<center><Label text="UniChat" styleClass="title-text"/></center>
|
||||
<right>
|
||||
<HBox>
|
||||
<Button text="—" styleClass="title-button" onAction="#minimize"/>
|
||||
<Button text="□" styleClass="title-button" onAction="#maximize"/>
|
||||
<Button text="✕" styleClass="title-button, title-button-close" onAction="#closeApp"/>
|
||||
</HBox>
|
||||
</right>
|
||||
</BorderPane>
|
||||
</top>
|
||||
|
||||
<center>
|
||||
|
||||
<HBox styleClass="content-wrapper" spacing="20">
|
||||
<VBox spacing="10" prefWidth="200">
|
||||
<Label text="Online Users"/>
|
||||
<ListView fx:id="usersList" VBox.vgrow="ALWAYS"/>
|
||||
</VBox>
|
||||
|
||||
<VBox spacing="10" HBox.hgrow="ALWAYS">
|
||||
<Label text="Private Chat"/>
|
||||
<AnchorPane fx:id="chatAnchor" style="-fx-background-color: #262638; -fx-background-radius: 8;" VBox.vgrow="ALWAYS"/>
|
||||
</VBox>
|
||||
|
||||
<VBox spacing="10" prefWidth="250">
|
||||
<Label text="Public Chat"/>
|
||||
<TextArea fx:id="publicChatArea" editable="false" VBox.vgrow="ALWAYS"/>
|
||||
|
||||
<VBox fx:id="fileTransferBox" spacing="4" visible="false" managed="false">
|
||||
<Label fx:id="fileTransferLabel" text="Sending file..."/>
|
||||
<ProgressBar fx:id="fileTransferProgress" progress="0.0" prefWidth="260"/>
|
||||
</VBox>
|
||||
|
||||
<HBox spacing="6">
|
||||
<Button text="📎" onAction="#sendFile"/>
|
||||
<TextField fx:id="messageField" HBox.hgrow="ALWAYS" promptText="Message..."/>
|
||||
<Button text="Send" onAction="#sendPublicMessage"/>
|
||||
</HBox>
|
||||
|
||||
</VBox>
|
||||
</HBox>
|
||||
</center>
|
||||
</BorderPane>
|
||||
Reference in New Issue
Block a user