Skip to content

Commit 9e69734

Browse files
committed
Enhance chat UI and functionality with message handling, file sending, and drag-and-drop support
1 parent 081b820 commit 9e69734

15 files changed

Lines changed: 445 additions & 59 deletions

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@
5454
<artifactId>jackson-databind</artifactId>
5555
<version>3.0.1</version>
5656
</dependency>
57+
<dependency>
58+
<groupId>org.wiremock</groupId>
59+
<artifactId>wiremock</artifactId>
60+
<version>4.0.0-beta.15</version>
61+
<scope>test</scope>
62+
</dependency>
5763
</dependencies>
5864
<build>
5965
<plugins>
Lines changed: 131 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,147 @@
11
package com.example;
22

3+
import javafx.application.Platform;
4+
import javafx.collections.ListChangeListener;
35
import javafx.event.ActionEvent;
46
import javafx.fxml.FXML;
5-
import javafx.scene.control.Label;
6-
import javafx.scene.control.ListView;
7+
import javafx.scene.Node;
8+
import javafx.scene.control.*;
9+
import javafx.scene.input.DragEvent;
10+
import javafx.scene.input.Dragboard;
11+
import javafx.scene.input.TransferMode;
12+
import javafx.scene.layout.HBox;
13+
import javafx.stage.FileChooser;
14+
15+
import javax.swing.event.HyperlinkListener;
16+
import javax.tools.Tool;
17+
import java.awt.Desktop;
18+
import java.io.File;
19+
import java.io.FileNotFoundException;
20+
import java.net.URI;
21+
import java.nio.file.Path;
22+
import java.time.format.DateTimeFormatter;
23+
import java.util.List;
724

825
public class HelloController {
926

1027
private final HelloModel model = new HelloModel(new NtfyConnectionImpl());
11-
public ListView<NtfyMessageDto> messageView;
1228

13-
@FXML
14-
private Label messageLabel;
29+
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
1530

16-
@FXML
17-
private void initialize() {
18-
if (messageLabel != null) {
19-
messageLabel.setText(model.getGreeting());
20-
}
31+
@FXML private ListView<NtfyMessageDto> messageView;
32+
@FXML private TextArea messageInput;
33+
34+
@FXML private void initialize() {
2135
messageView.setItems(model.getMessages());
36+
messageView.setCellFactory(lv -> new ListCell<>() {
37+
@Override
38+
protected void updateItem(NtfyMessageDto item, boolean empty) {
39+
super.updateItem(item, empty);
40+
if (empty || item == null) {
41+
setText(null);
42+
setGraphic(null);
43+
return;
44+
}
45+
String text = item.message();
46+
var att = item.attachment();
47+
48+
if (att != null && att.url() != null && !att.url().isBlank()) {
49+
Label msg = new Label(text != null ? text + " ": "");
50+
msg.setWrapText(true);
51+
52+
String linkText = att.name() != null && !att.name().isBlank()
53+
? att.name()
54+
: "Attachment";
55+
Hyperlink link = new Hyperlink(linkText);
56+
if (att.size() > 0) {
57+
link.setTooltip(new Tooltip(humanSize(att.size()) + (att.type() != null ? " - " + att.type() : "")));
58+
}
59+
link.setOnAction(e -> openInBrowser(att.url()));
60+
61+
HBox row = new HBox(8.0, (Node) msg, (Node) link);
62+
row.setFillHeight(true);
63+
64+
setText(null);
65+
setGraphic(row);
66+
} else {
67+
setText(text != null ? text : "");
68+
setGraphic(null);
69+
}
70+
}
71+
});
72+
73+
model.getMessages().addListener((ListChangeListener<NtfyMessageDto>)
74+
c -> Platform.runLater(() -> {
75+
if (!messageView.getItems(). isEmpty()) {
76+
messageView.scrollTo(messageView.getItems().size() - 1);
77+
}
78+
})
79+
);
80+
messageView.setOnDragOver(this::handleDragOver);
81+
messageView.setOnDragDropped(this::handleDragDropped);
82+
model.loadInitialMessagesAsync();
83+
}
84+
85+
@FXML public void sendFile(ActionEvent actionEvent) throws FileNotFoundException {
86+
FileChooser chooser = new FileChooser();
87+
chooser.setTitle("Välj fil att skicka");
88+
File file = chooser.showOpenDialog(messageView.getScene().getWindow());
89+
if (file != null) {
90+
Path path = file.toPath();
91+
model.sendFile(path);
92+
}
2293
}
2394

2495
public void sendMessage(ActionEvent actionEvent) {
25-
model.sendMessage();
96+
String text = messageInput != null ? messageInput.getText() : "";
97+
if (text != null && !text.isBlank() && model.sendMessage(text)) {
98+
messageInput.clear();
99+
}
100+
}
101+
102+
private void handleDragOver(DragEvent e) {
103+
Dragboard db = e.getDragboard();
104+
if (db.hasFiles()) {
105+
e.acceptTransferModes(TransferMode.COPY);
106+
}
107+
e.consume();
108+
}
109+
110+
private void handleDragDropped(DragEvent e) {
111+
Dragboard db = e.getDragboard();
112+
boolean success = false;
113+
if (db.hasFiles()) {
114+
List<File> files = db.getFiles();
115+
for (File f : files) {
116+
try {
117+
model.sendFile(f.toPath());
118+
} catch (FileNotFoundException ex) {
119+
throw new RuntimeException(ex);
120+
}
121+
}
122+
success = true;
123+
}
124+
e.setDropCompleted(success);
125+
e.consume();
126+
}
127+
128+
private void openInBrowser(String url) {
129+
try {
130+
if (Desktop.isDesktopSupported()) {
131+
Desktop.getDesktop().browse(URI.create(url));
132+
} else {
133+
// fallback: ingen Desktop, gör inget (kan ersättas med HostServices)
134+
}
135+
} catch (Exception ignored) {
136+
}
137+
}
138+
139+
private static String humanSize(long bytes) {
140+
// kort & enkel
141+
String[] units = {"B","KB","MB","GB","TB"};
142+
double v = bytes;
143+
int i = 0;
144+
while (v >= 1024 && i < units.length - 1) { v /= 1024; i++; }
145+
return String.format("%.1f %s", v, units[i]);
26146
}
27147
}

src/main/java/com/example/HelloFX.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.example;
22

3-
import io.github.cdimascio.dotenv.Dotenv;
43
import javafx.application.Application;
54
import javafx.fxml.FXMLLoader;
65
import javafx.scene.Parent;
Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,65 @@
11
package com.example;
22

3-
import io.github.cdimascio.dotenv.Dotenv;
43
import javafx.application.Platform;
54
import javafx.beans.property.SimpleStringProperty;
65
import javafx.beans.property.StringProperty;
76
import javafx.collections.FXCollections;
87
import javafx.collections.ObservableList;
9-
import tools.jackson.databind.ObjectMapper;
10-
11-
import java.io.IOException;
12-
import java.net.URI;
13-
import java.net.http.HttpClient;
14-
import java.net.http.HttpRequest;
15-
import java.net.http.HttpResponse;
16-
import java.util.Objects;
17-
18-
/**
19-
* Model layer: encapsulates application data and business logic.
20-
*/
8+
import java.io.FileNotFoundException;
9+
import java.nio.file.Path;
10+
import java.util.concurrent.CompletableFuture;
11+
2112
public class HelloModel {
2213

2314
private final NtfyConnection connection;
2415
private final ObservableList<NtfyMessageDto> messages = FXCollections.observableArrayList();
25-
private final StringProperty messageToSend = new SimpleStringProperty();
16+
//private final StringProperty messageToSend = new SimpleStringProperty();
2617

2718
public HelloModel(NtfyConnection connection) {
2819
this.connection = connection;
29-
receiveMessage();
3020
}
3121

3222
public ObservableList<NtfyMessageDto> getMessages() {
3323
return messages;
3424
}
3525

36-
public String getMessageToSend() {
26+
/*public String getMessageToSend() {
3727
return messageToSend.get();
28+
}*/
29+
30+
public void loadInitialMessagesAsync() {
31+
CompletableFuture
32+
.supplyAsync(connection::fetchHistory)
33+
.thenAccept(list -> Platform.runLater(() -> {
34+
messages.setAll(list);
35+
subscribeLive(); // start streaming after history
36+
}))
37+
.exceptionally(ex -> null);
38+
}
39+
40+
private void subscribeLive() {
41+
connection.receive(m -> Platform.runLater(() -> messages.add(m)));
3842
}
39-
public StringProperty messageToSendProperty() {
43+
44+
/*public StringProperty messageToSendProperty() {
4045
return messageToSend;
4146
}
4247
4348
public void setMessageToSend(String message) {
4449
messageToSend.set(message);
45-
}
50+
}*/
4651

47-
public String getGreeting() {
48-
String javaVersion = System.getProperty("java.version");
49-
String javafxVersion = System.getProperty("javafx.version");
50-
return "Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".";
52+
public boolean sendMessage(String text) {
53+
return connection.send(text);
5154
}
5255

53-
public void sendMessage() {
54-
connection.send(messageToSend.get());
56+
public boolean sendFile(Path path) throws FileNotFoundException {
57+
return connection.sendFile(path);
5558
}
5659

5760
public void receiveMessage() {
5861
connection.receive(m -> Platform.runLater(() -> messages.add(m)));
5962
}
63+
64+
6065
}
Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package com.example;
22

3+
import java.io.FileNotFoundException;
4+
import java.nio.file.Path;
5+
import java.util.List;
36
import java.util.function.Consumer;
47

58
public interface NtfyConnection {
69

7-
public boolean send(String message);
10+
boolean send(String message);
811

9-
public void receive(Consumer<NtfyMessageDto> messageHandler);
12+
void receive(Consumer<NtfyMessageDto> messageHandler);
1013

14+
List<NtfyMessageDto> fetchHistory();
1115

16+
boolean sendFile(Path path) throws FileNotFoundException;
1217
}

0 commit comments

Comments
 (0)