Skip to content
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b83bd2c
Add chat functionality, UI enhancements, and first update controller …
annikaholmqvist94 Oct 29, 2025
67cc5f2
Update .gitignore
annikaholmqvist94 Nov 3, 2025
1b9b0b8
Implement message sending/receiving logic with Ntfy integration and u…
annikaholmqvist94 Nov 4, 2025
d3a2149
Add file attachment button and stub method in controller
annikaholmqvist94 Nov 4, 2025
6584d5b
Implement file upload functionality with Ntfy integration
annikaholmqvist94 Nov 6, 2025
17546f9
Add unit tests for message receiving and file sending
annikaholmqvist94 Nov 6, 2025
e80bd27
Refactor `HelloModel` for JavaFX thread update tests for enhanced mes…
annikaholmqvist94 Nov 7, 2025
a69df76
Add validation for empty or whitespace messages in `HelloModel.sendMe…
annikaholmqvist94 Nov 7, 2025
0877a20
Add topic-based messaging and file sharing with dynamic UI updates an…
annikaholmqvist94 Nov 12, 2025
37fe2ce
Add support for dynamic topic selection update tests and model logic…
annikaholmqvist94 Nov 12, 2025
32adbde
Add fixed-topic chat functionality, update file sending message/file …
annikaholmqvist94 Nov 13, 2025
fe3d0d0
update tests for enhanced verification and UI consistency.
annikaholmqvist94 Nov 13, 2025
fc0902a
update tests for enhanced verification and UI consistency.
annikaholmqvist94 Nov 13, 2025
0afcc08
update tests for enhanced verification and UI consistency.
annikaholmqvist94 Nov 13, 2025
065761a
Add Javadoc comments for improved code clarity and maintainability.
annikaholmqvist94 Nov 14, 2025
e04e60b
Refactor `HelloModel` to enhance async message/file sending logic and…
annikaholmqvist94 Nov 14, 2025
10acd5d
Add `HOST_NAME` validation in `HelloController` and improve error han…
annikaholmqvist94 Nov 14, 2025
5b0a5bf
Improve file attachment handling in `HelloController`, enhance error …
annikaholmqvist94 Nov 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target/
/.idea/
.env
Empty file modified mvnw
100644 → 100755
Empty file.
30 changes: 30 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,32 @@
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>tools.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.wiremock</groupId>
<artifactId>wiremock</artifactId>
<version>4.0.0-beta.15</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.0</version>
</dependency>
Comment thread
annikaholmqvist94 marked this conversation as resolved.
Outdated
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
Expand All @@ -63,6 +84,15 @@
<noManPages>true</noManPages>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>25</source>
<target>25</target>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</plugins>
</build>
</project>
154 changes: 145 additions & 9 deletions src/main/java/com/example/HelloController.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,158 @@
package com.example;

import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.geometry.Pos;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.text.TextFlow;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Arrays;

/**
* Controller layer: mediates between the view (FXML) and the model.
*/
public class HelloController {

private final HelloModel model = new HelloModel();
// Uppdaterad för att använda den externa Ntfy-servern: https://ntfy.fungover.org
// Topicen "mytopic" läggs till automatiskt av modellen.
private final HelloModel model = new HelloModel(new NtfyConnectionImpl("https://ntfy.fungover.org"));

@FXML
public ListView<NtfyMessageDto> chatListView;
@FXML
public Label topicLabel;

@FXML
public Label attachedFileLabel;

@FXML
private Label messageLabel;
private Button sendButton;

@FXML
private TextField messageInput;

@FXML
private Button attachFile;

@FXML
private void initialize() {
if (messageLabel != null) {
messageLabel.setText(model.getGreeting());
if (sendButton != null) {
sendButton.setText(model.getGreeting());

// Bindning: Knappen är inaktiverad ENDAST om BÅDE meddelandet är tomt OCH fil inte är bifogad
sendButton.disableProperty().bind(
Bindings.createBooleanBinding(() -> {
boolean isMessageEmpty = model.messageToSendProperty().get() == null ||
model.messageToSendProperty().get().trim().isEmpty();
boolean isFileNotAttached = model.fileToSendProperty().get() == null;

return isMessageEmpty && isFileNotAttached;
},
model.messageToSendProperty(),
model.fileToSendProperty())
);
}

if (topicLabel != null) {
// Visar den fasta topicen
topicLabel.textProperty().bind(
Bindings.concat("Fixed Topic: ", model.currentTopicProperty())
);
}

// Hanterar visning av bifogad fil
if (attachedFileLabel != null) {
model.fileToSendProperty().addListener((obs, oldFile, newFile) -> {
if (newFile != null) {
attachedFileLabel.setText("Attached file: " + newFile.getName());
attachedFileLabel.setStyle("-fx-font-style: italic;" +
" -fx-font-size: 12px;" +
" -fx-text-fill: #008000;");
} else {
attachedFileLabel.setText("No file attached");
attachedFileLabel.setStyle("-fx-font-style: italic; " +
"-fx-font-size: 12px; " +
"-fx-text-fill: #333;");
}
});
attachedFileLabel.setText("No file attached");
}

if (messageInput!=null){
messageInput.textProperty().bindBidirectional(model.messageToSendProperty());
}

if(chatListView!=null){
chatListView.setItems(model.getMessages());
// Använd den enkla CellFactoryn
chatListView.setCellFactory(param -> new SimpleMessageCell());
}
}

/**
* En mycket enkel ListCell som enbart visar texten utan anpassad layout (bubblor/färger)
* men hanterar att visa "[File Uploaded]" när meddelandetexten är tom.
*/
private static class SimpleMessageCell extends ListCell<NtfyMessageDto> {

@Override
protected void updateItem(NtfyMessageDto item, boolean empty) {
super.updateItem(item, empty);

if (empty || item == null) {
setText(null);
setGraphic(null);
setStyle(null);
} else {
// Hämta meddelandet eller visa filstatus om meddelandet är tomt
String displayMessage = item.message() != null && !item.message().trim().isEmpty()
? item.message()
: (item.event().equals("file") ? "[File Uploaded]" : "");

// Lägg till prefix för att visa om det är skickat lokalt
String prefix = item.isLocal() ? "(Sent) " : "";

setText(prefix + displayMessage);
setGraphic(null);

// Mycket enkel stil utan bubblor/färger. Använd standard utseende.
setStyle(null);
}
}
}
Comment thread
annikaholmqvist94 marked this conversation as resolved.

@FXML
protected void sendMessage() {
if (model.fileToSendProperty().get() != null) {
// Om en fil är bifogad, skicka filen och rensa bilagan i modellen
model.sendFile();
} else {
// Annars, skicka textmeddelandet
model.sendMessage();
}

if (messageInput!=null){
messageInput.requestFocus();
}
}

@FXML
protected void attachFile() {
// Hämta scenen från en av kontrollerna
Stage stage = (Stage) (chatListView.getScene().getWindow());

FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Choose file to attach");

File selectedFile = fileChooser.showOpenDialog(stage);

if (selectedFile != null) {
model.setFileToSend(selectedFile);
}
Comment thread
annikaholmqvist94 marked this conversation as resolved.
}
}
}
4 changes: 4 additions & 0 deletions src/main/java/com/example/HelloFX.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example;

import io.github.cdimascio.dotenv.Dotenv;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
Expand All @@ -10,6 +11,9 @@ public class HelloFX extends Application {

@Override
public void start(Stage stage) throws Exception {



FXMLLoader fxmlLoader = new FXMLLoader(HelloFX.class.getResource("hello-view.fxml"));
Parent root = fxmlLoader.load();
Scene scene = new Scene(root, 640, 480);
Expand Down
151 changes: 145 additions & 6 deletions src/main/java/com/example/HelloModel.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,154 @@
package com.example;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.cdimascio.dotenv.Dotenv;
import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
* Model layer: encapsulates application data and business logic.
*/
public class HelloModel {
/**
* Returns a greeting based on the current Java and JavaFX versions.
*/
private final NtfyConnection connection;
private final StringProperty messageToSend = new SimpleStringProperty("");
private final StringProperty currentTopic = new SimpleStringProperty("mytopic");
private final ObservableList<NtfyMessageDto> messages = FXCollections.observableArrayList();
private final ObjectProperty<File> fileToSend = new SimpleObjectProperty<>(null); // Ny egenskap för filbilaga

public HelloModel(NtfyConnection connection) {
this.connection = connection;
connection.connect(currentTopic.get(), this::receiveMessage);
}

public String getGreeting() {
String javaVersion = System.getProperty("java.version");
String javafxVersion = System.getProperty("javafx.version");
return "Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".";
return "Skicka meddelande";
}

// NY METOD: Hanterar att köra koden på JavaFX-tråden ELLER direkt i testmiljö
private static void runOnFx(Runnable task) {
try {
if (Platform.isFxApplicationThread()) task.run();
else Platform.runLater(task);
} catch (IllegalStateException notInitialized) {
// JavaFX toolkit not initialized (e.g., unit tests or CI without graphics): run inline
task.run();
}
}


public void sendMessage() {
String message = messageToSend.get();
if (message != null && !message.trim().isEmpty()) {

// 1. Skapa den lokala DTO:n (med isLocal = true)
NtfyMessageDto localMessage = new NtfyMessageDto(
UUID.randomUUID().toString(),
System.currentTimeMillis() / 1000L,
"message",
currentTopic.get(),
message.trim(),
true // Markera som lokalt skickat
);

// 2. Lägg till i listan (UI-uppdatering) PÅ RÄTT TRÅD
runOnFx(() -> messages.add(localMessage));

// 3. Skicka meddelandet via anslutningen
connection.send(message, currentTopic.get());

// 4. Rensa meddelandefältet efter skickning
messageToSend.set("");
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Comment thread
annikaholmqvist94 marked this conversation as resolved.

// Argumentlös metod, som controlleren använder för att skicka den bifogade filen
public void sendFile() {
File file = fileToSend.get();
if (file != null) {

// 1. Skapa den lokala DTO:n för fil (ofta med tom message)
NtfyMessageDto localFileMessage = new NtfyMessageDto(
UUID.randomUUID().toString(),
System.currentTimeMillis() / 1000L,
"file", // Använd "file" event om det är en fil
currentTopic.get(),
"Fil skickad: " + file.getName(), // Detta meddelande visas bara i logik, CellFactory hanterar visning
true
);

// 2. Lägg till i listan (UI-uppdatering) PÅ RÄTT TRÅD
runOnFx(() -> messages.add(localFileMessage));

// 3. Skicka filen
connection.sendFile(file, currentTopic.get());

// 4. Rensa filbilagan efter skickning
fileToSend.set(null);
}
}
Comment thread
annikaholmqvist94 marked this conversation as resolved.

// Används av HelloController för att hämta filbilagan
public ObjectProperty<File> fileToSendProperty() {
return fileToSend;
}

// Används av HelloController för att ställa in filbilagan
public void setFileToSend(File file) {
this.fileToSend.set(file);
}

private void receiveMessage(NtfyMessageDto message) {
// ANVÄNDER runOnFx FÖR ATT SÄKRA ATT UPPDATERINGEN SKER PÅ RÄTT TRÅD (eller direkt i testmiljö)
runOnFx(() -> messages.add(message));
}

public ObservableList<NtfyMessageDto> getMessages() {
return messages;
}

public StringProperty messageToSendProperty() {
return messageToSend;
}

public StringProperty currentTopicProperty() {
return currentTopic;
}

public void reconnectToTopic(String newTopic) {
if (!currentTopic.get().equals(newTopic)) {
// connection.disconnect(currentTopic.get()); // Förutsätter att disconnect implementeras i NtfyConnection
currentTopic.set(newTopic);

// Säkerställ att rensningen sker på FX-tråden om vi kör i en FX-miljö
runOnFx(messages::clear);

connection.connect(newTopic, this::receiveMessage);
}
}

// KORRIGERAD: Denna metod måste ta en String för att matcha testet!
public void setMessageToSend(String message) {
this.messageToSend.set(message);
}
}
Loading
Loading