From 729c4ffbeada7c1b17070e98357b148b2c15bd8d Mon Sep 17 00:00:00 2001
From: Sandra Neljestam <229708855+SandraNelj@users.noreply.github.com>
Date: Fri, 31 Oct 2025 10:03:31 +0100
Subject: [PATCH 1/9] =?UTF-8?q?Skapat=20kod=20f=C3=B6r=20att=20chatt-appen?=
=?UTF-8?q?=20ska=20fungera.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/com/example/HelloController.java | 30 +++++++++-
src/main/java/com/example/HelloFX.java | 2 +-
src/main/java/com/example/HelloModel.java | 56 +++++++++++++++++--
.../resources/com/example/hello-view.fxml | 28 +++++++---
4 files changed, 101 insertions(+), 15 deletions(-)
diff --git a/src/main/java/com/example/HelloController.java b/src/main/java/com/example/HelloController.java
index fdd160a0..c525f32d 100644
--- a/src/main/java/com/example/HelloController.java
+++ b/src/main/java/com/example/HelloController.java
@@ -2,12 +2,22 @@
import javafx.fxml.FXML;
import javafx.scene.control.Label;
+import javafx.application.Platform;
+import javafx.scene.control.Button;
+import javafx.scene.control.TextArea;
+import javafx.scene.control.TextField;
+
+import java.io.IOException;
/**
* Controller layer: mediates between the view (FXML) and the model.
*/
public class HelloController {
+ @FXML private TextArea chatArea;
+ @FXML private TextField inputField;
+ @FXML private Button sendButton;
+
private final HelloModel model = new HelloModel();
@FXML
@@ -15,8 +25,24 @@ public class HelloController {
@FXML
private void initialize() {
- if (messageLabel != null) {
- messageLabel.setText(model.getGreeting());
+ model.startListening(message -> Platform.runLater(() -> {
+ chatArea.appendText(message + "\n");
+ }));
+ sendButton.setOnAction(e->sendMessage());
+ }
+ @FXML
+ private void sendMessage() {
+ String msg = inputField.getText().trim();
+ if (msg.isEmpty()) return;
+
+ try {
+ chatArea.appendText("Du: " + msg + "\n");
+
+ model.sendMessage(msg);
+ inputField.clear();
+ } catch (IOException e) {
+ e.printStackTrace();
}
+
}
}
diff --git a/src/main/java/com/example/HelloFX.java b/src/main/java/com/example/HelloFX.java
index 96bdc5ca..6d8a4e40 100644
--- a/src/main/java/com/example/HelloFX.java
+++ b/src/main/java/com/example/HelloFX.java
@@ -13,7 +13,7 @@ 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);
- stage.setTitle("Hello MVC");
+ stage.setTitle("JavaFX Chat");
stage.setScene(scene);
stage.show();
}
diff --git a/src/main/java/com/example/HelloModel.java b/src/main/java/com/example/HelloModel.java
index 385cfd10..97b886ca 100644
--- a/src/main/java/com/example/HelloModel.java
+++ b/src/main/java/com/example/HelloModel.java
@@ -1,5 +1,11 @@
package com.example;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
/**
* Model layer: encapsulates application data and business logic.
*/
@@ -7,9 +13,51 @@ public class HelloModel {
/**
* Returns a greeting based on the current Java and JavaFX versions.
*/
- public String getGreeting() {
- String javaVersion = System.getProperty("java.version");
- String javafxVersion = System.getProperty("javafx.version");
- return "Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".";
+
+ private final String topic = "javafx-demo";
+ private final String baseUrl;
+ private final ExecutorService executor = Executors.newSingleThreadExecutor();
+
+ public interface MessageListener {
+ void onMessage(String message);
+ }
+
+ public HelloModel() {
+ this.baseUrl = System.getenv().getOrDefault("NTFY_URL", "https://ntfy.sh");
+ }
+
+ public void sendMessage(String message) throws IOException {
+ URL url = new URL (baseUrl);
+ HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+ conn.setDoOutput(true);
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Content-Type", "application/json");
+
+ String json = String.format("{\"topic\":\"%s\",\"message\":\"%s\"}",topic, message);
+ try (OutputStream os = conn.getOutputStream()) {
+ os.write(json.getBytes());
+ }
+
+ conn.getResponseCode();
+ conn.disconnect();
}
+ public void startListening (MessageListener listener) {
+ executor.submit(()->{
+ try {
+ URL url = new URL (baseUrl + "/" + topic + "/json");
+ BufferedReader rader = new BufferedReader(new InputStreamReader(url.openStream()));
+
+ String line;
+ while ((line = rader.readLine()) != null) {
+ if (line.contains("\"nessage\"")) {
+ String msg = line.replaceAll(".*\"message\":\"(.*?)\".*", "$1");
+ listener.onMessage(msg);
+ }
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+
}
diff --git a/src/main/resources/com/example/hello-view.fxml b/src/main/resources/com/example/hello-view.fxml
index 20a7dc82..7ce62716 100644
--- a/src/main/resources/com/example/hello-view.fxml
+++ b/src/main/resources/com/example/hello-view.fxml
@@ -1,9 +1,21 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
From 895f0b5e284100d4fd5bddc5b4721ba38d00bfea Mon Sep 17 00:00:00 2001
From: Sandra Neljestam <229708855+SandraNelj@users.noreply.github.com>
Date: Sat, 1 Nov 2025 14:11:56 +0100
Subject: [PATCH 2/9] =?UTF-8?q?Uppdaterat=20kod=20i=20HelloController=20sa?=
=?UTF-8?q?mt=20hello=20view=20f=C3=B6r=20att=20man=20ska=20kunna=20trycka?=
=?UTF-8?q?=20enter=20f=C3=B6r=20att=20skicka=20text=20samt=20shift+enter?=
=?UTF-8?q?=20f=C3=B6r=20att=20skapa=20ny=20rad.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/com/example/HelloController.java | 14 ++++++++--
.../resources/com/example/hello-view.fxml | 28 +++++++++++--------
2 files changed, 29 insertions(+), 13 deletions(-)
diff --git a/src/main/java/com/example/HelloController.java b/src/main/java/com/example/HelloController.java
index c525f32d..e871f9b8 100644
--- a/src/main/java/com/example/HelloController.java
+++ b/src/main/java/com/example/HelloController.java
@@ -5,7 +5,7 @@
import javafx.application.Platform;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
-import javafx.scene.control.TextField;
+import javafx.scene.input.KeyCode;
import java.io.IOException;
@@ -15,7 +15,7 @@
public class HelloController {
@FXML private TextArea chatArea;
- @FXML private TextField inputField;
+ @FXML private TextArea inputField;
@FXML private Button sendButton;
private final HelloModel model = new HelloModel();
@@ -29,6 +29,16 @@ private void initialize() {
chatArea.appendText(message + "\n");
}));
sendButton.setOnAction(e->sendMessage());
+ inputField.setOnKeyPressed(event -> {
+ if (event.getCode() == KeyCode.ENTER) {
+ if (event.isShiftDown()) {
+ inputField.appendText("\n");
+ } else {
+ sendMessage();
+ }
+ event.consume();
+ }
+ });
}
@FXML
private void sendMessage() {
diff --git a/src/main/resources/com/example/hello-view.fxml b/src/main/resources/com/example/hello-view.fxml
index 7ce62716..7b189a69 100644
--- a/src/main/resources/com/example/hello-view.fxml
+++ b/src/main/resources/com/example/hello-view.fxml
@@ -1,21 +1,27 @@
-
+
-
+
+
+
-
-
+
-
+
-
-
+
+
-
\ No newline at end of file
+
From e3ebf9e3bc392a6130dab36526d844367aab34fe Mon Sep 17 00:00:00 2001
From: Sandra Neljestam <229708855+SandraNelj@users.noreply.github.com>
Date: Fri, 14 Nov 2025 13:19:16 +0100
Subject: [PATCH 3/9] =?UTF-8?q?Uppdaterat=20kod=20s=C3=A5=20att=20testerna?=
=?UTF-8?q?=20g=C3=A5r=20igenom?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.gitignore | 1 +
pom.xml | 16 ++++
.../java/com/example/HelloController.java | 55 ++++++-----
src/main/java/com/example/HelloFX.java | 4 +-
src/main/java/com/example/HelloModel.java | 95 ++++++++++---------
src/main/java/com/example/NtfyConnection.java | 8 ++
.../java/com/example/NtfyConnectionImpl.java | 65 +++++++++++++
src/main/java/com/example/NtfyMessageDto.java | 7 ++
src/main/java/com/example/Singelton.java | 11 +++
src/main/java/module-info.java | 5 +-
.../resources/com/example/hello-view.fxml | 16 +---
src/test/java/com/example/HelloModelTest.java | 38 ++++++++
.../java/com/example/NtfyConnectionSpy.java | 16 ++++
13 files changed, 251 insertions(+), 86 deletions(-)
create mode 100644 src/main/java/com/example/NtfyConnection.java
create mode 100644 src/main/java/com/example/NtfyConnectionImpl.java
create mode 100644 src/main/java/com/example/NtfyMessageDto.java
create mode 100644 src/main/java/com/example/Singelton.java
create mode 100644 src/test/java/com/example/HelloModelTest.java
create mode 100644 src/test/java/com/example/NtfyConnectionSpy.java
diff --git a/.gitignore b/.gitignore
index 6ac465db..5a54815d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
target/
/.idea/
+.env
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index c40f667e..e324b59b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,6 +45,22 @@
javafx-fxml
${javafx.version}
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.17.2
+
+
+ io.github.cdimascio
+ java-dotenv
+ 5.2.2
+
+
+ org.wiremock
+ wiremock
+ 4.0.0-beta.15
+ test
+
diff --git a/src/main/java/com/example/HelloController.java b/src/main/java/com/example/HelloController.java
index e871f9b8..0e765416 100644
--- a/src/main/java/com/example/HelloController.java
+++ b/src/main/java/com/example/HelloController.java
@@ -1,42 +1,50 @@
package com.example;
+import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
-import javafx.scene.control.Label;
import javafx.application.Platform;
import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.ListView;
import javafx.scene.control.TextArea;
-import javafx.scene.input.KeyCode;
-
-import java.io.IOException;
/**
* Controller layer: mediates between the view (FXML) and the model.
*/
public class HelloController {
- @FXML private TextArea chatArea;
@FXML private TextArea inputField;
@FXML private Button sendButton;
+ @FXML private TextArea chatArea;
private final HelloModel model = new HelloModel();
- @FXML
- private Label messageLabel;
@FXML
private void initialize() {
- model.startListening(message -> Platform.runLater(() -> {
- chatArea.appendText(message + "\n");
- }));
- sendButton.setOnAction(e->sendMessage());
+
+ model.getMessages().addListener((ListChangeListener) change ->{
+ while(change.next()) {
+ if (change.wasAdded()) {
+ for (var msg : change.getAddedSubList()) {
+ chatArea.appendText(msg.message() + "\n");
+ }
+ }
+ }
+ });
+
+ sendButton.setOnAction(e -> sendMessage());
+
inputField.setOnKeyPressed(event -> {
- if (event.getCode() == KeyCode.ENTER) {
- if (event.isShiftDown()) {
- inputField.appendText("\n");
- } else {
- sendMessage();
+ switch (event.getCode()) {
+ case ENTER -> {
+ if (event.isShiftDown()) {
+ inputField.appendText("\n");
+ } else {
+ sendMessage();
+ event.consume();
+ }
}
- event.consume();
}
});
}
@@ -45,14 +53,9 @@ private void sendMessage() {
String msg = inputField.getText().trim();
if (msg.isEmpty()) return;
- try {
- chatArea.appendText("Du: " + msg + "\n");
-
- model.sendMessage(msg);
- inputField.clear();
- } catch (IOException e) {
- e.printStackTrace();
- }
-
+ chatArea.appendText("Du: " + msg + "\n");
+ model.setMessageToSend(msg);
+ model.sendMessage();
+ inputField.clear();
}
}
diff --git a/src/main/java/com/example/HelloFX.java b/src/main/java/com/example/HelloFX.java
index 6d8a4e40..fd865a1d 100644
--- a/src/main/java/com/example/HelloFX.java
+++ b/src/main/java/com/example/HelloFX.java
@@ -10,10 +10,11 @@ public class HelloFX extends Application {
@Override
public void start(Stage stage) throws Exception {
+ System.getenv("HOST_NAME");
FXMLLoader fxmlLoader = new FXMLLoader(HelloFX.class.getResource("hello-view.fxml"));
Parent root = fxmlLoader.load();
Scene scene = new Scene(root, 640, 480);
- stage.setTitle("JavaFX Chat");
+ stage.setTitle("JavaFX Chat App");
stage.setScene(scene);
stage.show();
}
@@ -21,5 +22,4 @@ public void start(Stage stage) throws Exception {
public static void main(String[] args) {
launch();
}
-
}
\ No newline at end of file
diff --git a/src/main/java/com/example/HelloModel.java b/src/main/java/com/example/HelloModel.java
index 97b886ca..b99fa2d8 100644
--- a/src/main/java/com/example/HelloModel.java
+++ b/src/main/java/com/example/HelloModel.java
@@ -1,10 +1,13 @@
package com.example;
+import io.github.cdimascio.dotenv.Dotenv;
+import javafx.application.Platform;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
import java.io.*;
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
+import java.util.Objects;
/**
* Model layer: encapsulates application data and business logic.
@@ -14,50 +17,52 @@ public class HelloModel {
* Returns a greeting based on the current Java and JavaFX versions.
*/
- private final String topic = "javafx-demo";
- private final String baseUrl;
- private final ExecutorService executor = Executors.newSingleThreadExecutor();
+ private final NtfyConnection connection;
+ private final StringProperty messageToSend = new SimpleStringProperty();
+ private final ObservableList messages = FXCollections.observableArrayList();
- public interface MessageListener {
- void onMessage(String message);
+ //Konstuktor för prod
+ public HelloModel() {
+ this.connection = new NtfyConnectionImpl();
+ receiveMessage();
}
- public HelloModel() {
- this.baseUrl = System.getenv().getOrDefault("NTFY_URL", "https://ntfy.sh");
- }
-
- public void sendMessage(String message) throws IOException {
- URL url = new URL (baseUrl);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setDoOutput(true);
- conn.setRequestMethod("POST");
- conn.setRequestProperty("Content-Type", "application/json");
-
- String json = String.format("{\"topic\":\"%s\",\"message\":\"%s\"}",topic, message);
- try (OutputStream os = conn.getOutputStream()) {
- os.write(json.getBytes());
- }
-
- conn.getResponseCode();
- conn.disconnect();
- }
- public void startListening (MessageListener listener) {
- executor.submit(()->{
- try {
- URL url = new URL (baseUrl + "/" + topic + "/json");
- BufferedReader rader = new BufferedReader(new InputStreamReader(url.openStream()));
-
- String line;
- while ((line = rader.readLine()) != null) {
- if (line.contains("\"nessage\"")) {
- String msg = line.replaceAll(".*\"message\":\"(.*?)\".*", "$1");
- listener.onMessage(msg);
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- });
+ //Konstuktor för test
+ public HelloModel(NtfyConnection connection) {
+ this.connection = connection;
+ receiveMessage();
+ }
+
+ public ObservableList getMessages() {
+ return messages;
+ }
+
+ public String getMessageToSend() {
+ return messageToSend.get();
+ }
+
+ public StringProperty messageToSendProperty() {
+ return messageToSend;
+ }
+
+ public void setMessageToSend(String message) {
+ messageToSend.set(message);
+ }
+
+ public void sendMessage() {
+ connection.send(messageToSend.get());
}
+ public void receiveMessage() {
+ connection.receive(m-> Platform.runLater(() -> messages.add (m)));
+ }
}
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/example/NtfyConnection.java b/src/main/java/com/example/NtfyConnection.java
new file mode 100644
index 00000000..2aef6708
--- /dev/null
+++ b/src/main/java/com/example/NtfyConnection.java
@@ -0,0 +1,8 @@
+package com.example;
+import java.util.function.Consumer;
+
+public interface NtfyConnection {
+ boolean send (String message);
+ void receive (Consumer messageHandler);
+
+}
diff --git a/src/main/java/com/example/NtfyConnectionImpl.java b/src/main/java/com/example/NtfyConnectionImpl.java
new file mode 100644
index 00000000..1d10b74f
--- /dev/null
+++ b/src/main/java/com/example/NtfyConnectionImpl.java
@@ -0,0 +1,65 @@
+package com.example;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.github.cdimascio.dotenv.Dotenv;
+
+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.util.Objects;
+import java.util.function.Consumer;
+
+public class NtfyConnectionImpl implements NtfyConnection {
+ private final HttpClient http = HttpClient.newHttpClient();
+ private final String hostName;
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ public NtfyConnectionImpl() {
+ Dotenv dotenv = Dotenv.load();
+ hostName = Objects.requireNonNull(dotenv.get("HOST_NAME"));
+ }
+
+ public NtfyConnectionImpl(String hostName) {
+ this.hostName = hostName;
+ }
+
+ @Override
+ public boolean send (String message) {
+ HttpRequest httpRequest = HttpRequest.newBuilder()
+ .POST(HttpRequest.BodyPublishers.ofString(message))
+ .uri(URI.create(hostName + "/mytopic"))
+ .header("Cache-Control", "no-cache")
+ .build();
+ try {
+ http.send(httpRequest, HttpResponse.BodyHandlers.discarding());
+ return true;
+ } catch (IOException | InterruptedException e) {
+ System.out.println("Error sending message!");
+ return false;
+ }
+ }
+
+ @Override
+ public void receive (Consumer messageHandler) {
+ HttpRequest httpRequest = HttpRequest.newBuilder()
+ .GET()
+ .uri(URI.create(hostName + "/mytopic/json"))
+ .build();
+
+ http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines())
+ .thenAccept(response -> response.body()
+ .map(s -> {
+ try {
+ return mapper.readValue(s, NtfyMessageDto.class);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException(e);
+ }
+ })
+ .filter(message -> message.event().equals("message"))
+ .peek(System.out::println)
+ .forEach(messageHandler));
+ }
+}
diff --git a/src/main/java/com/example/NtfyMessageDto.java b/src/main/java/com/example/NtfyMessageDto.java
new file mode 100644
index 00000000..7a57c27d
--- /dev/null
+++ b/src/main/java/com/example/NtfyMessageDto.java
@@ -0,0 +1,7 @@
+package com.example;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+public record NtfyMessageDto(String id, long time, String event, String topic, String message) {
+}
diff --git a/src/main/java/com/example/Singelton.java b/src/main/java/com/example/Singelton.java
new file mode 100644
index 00000000..f00b7c64
--- /dev/null
+++ b/src/main/java/com/example/Singelton.java
@@ -0,0 +1,11 @@
+package com.example;
+
+public class Singelton {
+ private final static Singelton instance = new Singelton();
+
+ private Singelton() {}
+
+ public static Singelton getInstance() {
+ return instance;
+ }
+}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 71574a27..19b72baf 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,7 +1,10 @@
module hellofx {
requires javafx.controls;
requires javafx.fxml;
+ requires java.net.http;
+ requires com.fasterxml.jackson.databind;
+ requires java.dotenv;
- opens com.example to javafx.fxml;
+ opens com.example to javafx.fxml, com.fasterxml.jackson.databind;
exports com.example;
}
\ No newline at end of file
diff --git a/src/main/resources/com/example/hello-view.fxml b/src/main/resources/com/example/hello-view.fxml
index 7b189a69..474a961f 100644
--- a/src/main/resources/com/example/hello-view.fxml
+++ b/src/main/resources/com/example/hello-view.fxml
@@ -4,24 +4,16 @@
-
+
-
+
-
-
+
+
diff --git a/src/test/java/com/example/HelloModelTest.java b/src/test/java/com/example/HelloModelTest.java
new file mode 100644
index 00000000..bab591c2
--- /dev/null
+++ b/src/test/java/com/example/HelloModelTest.java
@@ -0,0 +1,38 @@
+package com.example;
+
+import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
+import com.github.tomakehurst.wiremock.junit5.WireMockTest;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import static com.github.tomakehurst.wiremock.client.WireMock.*;
+import static org.assertj.core.api.Assertions.assertThat;
+
+@WireMockTest
+class HelloModelTest {
+
+ @Test
+ @DisplayName("Given a model with messageToSend when calling sendMessage then send method on connection should be called")
+ void sendMessageCallsConnectionWithMessageToSend() {
+
+ var spy = new NtfyConnectionSpy();
+ var model = new HelloModel(spy);
+ model.setMessageToSend("Hello World!");
+
+ model.sendMessage();
+
+ assertThat(spy.message).isEqualTo("Hello World!");
+ }
+
+ @Test
+ void sendMessageToFakeServer(WireMockRuntimeInfo wmRuntimeInfo) {
+ var con = new NtfyConnectionImpl("http://localhost:" + wmRuntimeInfo.getHttpPort());
+ var model = new HelloModel(con);
+ model.setMessageToSend("Hello World!");
+ stubFor(post("/mytopic").willReturn(ok()));
+
+ model.sendMessage();
+
+ verify(postRequestedFor(urlEqualTo("/mytopic"))
+ .withRequestBody(matching("Hello World!")));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/example/NtfyConnectionSpy.java b/src/test/java/com/example/NtfyConnectionSpy.java
new file mode 100644
index 00000000..90a5c9b0
--- /dev/null
+++ b/src/test/java/com/example/NtfyConnectionSpy.java
@@ -0,0 +1,16 @@
+package com.example;
+
+import java.util.function.Consumer;
+
+public class NtfyConnectionSpy implements NtfyConnection {
+ public String message;
+
+ @Override
+ public boolean send (String message) {
+ this.message = message;
+ return true;
+ }
+ @Override
+ public void receive (ConsumermessageHandler) {
+ }
+}
From 3133193f45793c5d141309953612bc444de7f25f Mon Sep 17 00:00:00 2001
From: Sandra Neljestam <229708855+SandraNelj@users.noreply.github.com>
Date: Fri, 14 Nov 2025 13:37:04 +0100
Subject: [PATCH 4/9] =?UTF-8?q?Uppdaterat=20kod=20s=C3=A5=20att=20testerna?=
=?UTF-8?q?=20g=C3=A5r=20igenom?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/main/java/com/example/HelloController.java | 3 ---
src/main/java/com/example/HelloModel.java | 2 --
2 files changed, 5 deletions(-)
diff --git a/src/main/java/com/example/HelloController.java b/src/main/java/com/example/HelloController.java
index 0e765416..addf9385 100644
--- a/src/main/java/com/example/HelloController.java
+++ b/src/main/java/com/example/HelloController.java
@@ -2,10 +2,7 @@
import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
-import javafx.application.Platform;
import javafx.scene.control.Button;
-import javafx.scene.control.Label;
-import javafx.scene.control.ListView;
import javafx.scene.control.TextArea;
/**
diff --git a/src/main/java/com/example/HelloModel.java b/src/main/java/com/example/HelloModel.java
index b99fa2d8..3225c20d 100644
--- a/src/main/java/com/example/HelloModel.java
+++ b/src/main/java/com/example/HelloModel.java
@@ -1,13 +1,11 @@
package com.example;
-import io.github.cdimascio.dotenv.Dotenv;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.io.*;
-import java.util.Objects;
/**
* Model layer: encapsulates application data and business logic.
From 091604647b7f660080f2ae187b60d674d0b2a41b Mon Sep 17 00:00:00 2001
From: Sandra Neljestam <229708855+SandraNelj@users.noreply.github.com>
Date: Fri, 14 Nov 2025 14:30:31 +0100
Subject: [PATCH 5/9] =?UTF-8?q?Uppdaterat=20efter=20AI-botens=20r=C3=A5d?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/main/java/com/example/HelloController.java | 13 ++++---------
src/main/java/com/example/HelloModel.java | 11 +++++++++--
src/main/java/com/example/NtfyConnectionImpl.java | 7 ++++++-
3 files changed, 19 insertions(+), 12 deletions(-)
diff --git a/src/main/java/com/example/HelloController.java b/src/main/java/com/example/HelloController.java
index addf9385..864a6b58 100644
--- a/src/main/java/com/example/HelloController.java
+++ b/src/main/java/com/example/HelloController.java
@@ -4,6 +4,7 @@
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
+import javafx.scene.input.KeyCode;
/**
* Controller layer: mediates between the view (FXML) and the model.
@@ -33,15 +34,9 @@ private void initialize() {
sendButton.setOnAction(e -> sendMessage());
inputField.setOnKeyPressed(event -> {
- switch (event.getCode()) {
- case ENTER -> {
- if (event.isShiftDown()) {
- inputField.appendText("\n");
- } else {
- sendMessage();
- event.consume();
- }
- }
+ if (event.getCode() == KeyCode.ENTER && !event.isShiftDown()) {
+ sendMessage();
+ event.consume();
}
});
}
diff --git a/src/main/java/com/example/HelloModel.java b/src/main/java/com/example/HelloModel.java
index 3225c20d..3987a145 100644
--- a/src/main/java/com/example/HelloModel.java
+++ b/src/main/java/com/example/HelloModel.java
@@ -18,6 +18,7 @@ public class HelloModel {
private final NtfyConnection connection;
private final StringProperty messageToSend = new SimpleStringProperty();
private final ObservableList messages = FXCollections.observableArrayList();
+ private String lastSentMessage;
//Konstuktor för prod
public HelloModel() {
@@ -48,11 +49,17 @@ public void setMessageToSend(String message) {
}
public void sendMessage() {
- connection.send(messageToSend.get());
+ lastSentMessage = messageToSend.get();
+ connection.send(lastSentMessage);
}
public void receiveMessage() {
- connection.receive(m-> Platform.runLater(() -> messages.add (m)));
+ connection.receive(m -> {
+ if (m.message().equals(lastSentMessage)) {
+ return;
+ }
+ Platform.runLater(() -> messages.add(m));
+ });
}
}
diff --git a/src/main/java/com/example/NtfyConnectionImpl.java b/src/main/java/com/example/NtfyConnectionImpl.java
index 1d10b74f..5276dfdc 100644
--- a/src/main/java/com/example/NtfyConnectionImpl.java
+++ b/src/main/java/com/example/NtfyConnectionImpl.java
@@ -19,7 +19,12 @@ public class NtfyConnectionImpl implements NtfyConnection {
public NtfyConnectionImpl() {
Dotenv dotenv = Dotenv.load();
- hostName = Objects.requireNonNull(dotenv.get("HOST_NAME"));
+ String host = dotenv.get("HOST_NAME");
+
+ if (host == null || host.isEmpty()) {
+ throw new IllegalStateException("HOST_NAME has to be defined");
+ }
+ this.hostName = host;
}
public NtfyConnectionImpl(String hostName) {
From 9284a5d1b695bcd0a0919dedd9c0147716be0530 Mon Sep 17 00:00:00 2001
From: Sandra Neljestam <229708855+SandraNelj@users.noreply.github.com>
Date: Fri, 14 Nov 2025 15:33:25 +0100
Subject: [PATCH 6/9] =?UTF-8?q?Uppdaterat=20efter=20AI-botens=20r=C3=A5d?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/main/java/com/example/HelloModel.java | 31 ++++++++++---------
src/main/java/com/example/NtfyConnection.java | 1 -
.../java/com/example/NtfyConnectionImpl.java | 29 +++++++++--------
src/test/java/com/example/HelloModelTest.java | 6 ++--
4 files changed, 32 insertions(+), 35 deletions(-)
diff --git a/src/main/java/com/example/HelloModel.java b/src/main/java/com/example/HelloModel.java
index 3987a145..11f76fb6 100644
--- a/src/main/java/com/example/HelloModel.java
+++ b/src/main/java/com/example/HelloModel.java
@@ -6,6 +6,7 @@
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.io.*;
+import java.util.UUID;
/**
* Model layer: encapsulates application data and business logic.
@@ -16,9 +17,9 @@ public class HelloModel {
*/
private final NtfyConnection connection;
- private final StringProperty messageToSend = new SimpleStringProperty();
+ private final StringProperty messageToSend = new SimpleStringProperty("");
private final ObservableList messages = FXCollections.observableArrayList();
- private String lastSentMessage;
+ private final String clientId = UUID.randomUUID().toString();
//Konstuktor för prod
public HelloModel() {
@@ -36,29 +37,29 @@ public ObservableList getMessages() {
return messages;
}
- public String getMessageToSend() {
- return messageToSend.get();
- }
-
- public StringProperty messageToSendProperty() {
- return messageToSend;
- }
-
public void setMessageToSend(String message) {
messageToSend.set(message);
}
public void sendMessage() {
- lastSentMessage = messageToSend.get();
- connection.send(lastSentMessage);
+ String text = messageToSend.get().trim();
+ if (text.isEmpty()) return;
+
+ String payload = clientId + "::" + text;
+ connection.send(payload);
+
+ messageToSend.set("");
}
public void receiveMessage() {
- connection.receive(m -> {
- if (m.message().equals(lastSentMessage)) {
+ connection.receive(msg -> {
+ String incoming = msg.message();
+
+ if (incoming.startsWith(clientId + "::")) {
return;
}
- Platform.runLater(() -> messages.add(m));
+
+ Platform.runLater(() -> messages.add(msg));
});
}
}
diff --git a/src/main/java/com/example/NtfyConnection.java b/src/main/java/com/example/NtfyConnection.java
index 2aef6708..a02a794a 100644
--- a/src/main/java/com/example/NtfyConnection.java
+++ b/src/main/java/com/example/NtfyConnection.java
@@ -4,5 +4,4 @@
public interface NtfyConnection {
boolean send (String message);
void receive (Consumer messageHandler);
-
}
diff --git a/src/main/java/com/example/NtfyConnectionImpl.java b/src/main/java/com/example/NtfyConnectionImpl.java
index 5276dfdc..61d9dedb 100644
--- a/src/main/java/com/example/NtfyConnectionImpl.java
+++ b/src/main/java/com/example/NtfyConnectionImpl.java
@@ -9,7 +9,6 @@
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
-import java.util.Objects;
import java.util.function.Consumer;
public class NtfyConnectionImpl implements NtfyConnection {
@@ -33,14 +32,15 @@ public NtfyConnectionImpl(String hostName) {
@Override
public boolean send (String message) {
- HttpRequest httpRequest = HttpRequest.newBuilder()
- .POST(HttpRequest.BodyPublishers.ofString(message))
- .uri(URI.create(hostName + "/mytopic"))
- .header("Cache-Control", "no-cache")
- .build();
try {
- http.send(httpRequest, HttpResponse.BodyHandlers.discarding());
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(hostName + "/mytopic"))
+ .POST(HttpRequest.BodyPublishers.ofString(message))
+ .build();
+
+ http.send(request, HttpResponse.BodyHandlers.discarding());
return true;
+
} catch (IOException | InterruptedException e) {
System.out.println("Error sending message!");
return false;
@@ -48,23 +48,22 @@ public boolean send (String message) {
}
@Override
- public void receive (Consumer messageHandler) {
+ public void receive (Consumer handler) {
HttpRequest httpRequest = HttpRequest.newBuilder()
- .GET()
.uri(URI.create(hostName + "/mytopic/json"))
+ .GET()
.build();
http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines())
.thenAccept(response -> response.body()
- .map(s -> {
+ .map(line -> {
try {
- return mapper.readValue(s, NtfyMessageDto.class);
+ return mapper.readValue(line, NtfyMessageDto.class);
} catch (JsonProcessingException e) {
- throw new RuntimeException(e);
+ return null;
}
})
- .filter(message -> message.event().equals("message"))
- .peek(System.out::println)
- .forEach(messageHandler));
+ .filter(msg -> msg != null && msg.event().equals("message"))
+ .forEach(handler));
}
}
diff --git a/src/test/java/com/example/HelloModelTest.java b/src/test/java/com/example/HelloModelTest.java
index bab591c2..fc757e68 100644
--- a/src/test/java/com/example/HelloModelTest.java
+++ b/src/test/java/com/example/HelloModelTest.java
@@ -16,8 +16,8 @@ void sendMessageCallsConnectionWithMessageToSend() {
var spy = new NtfyConnectionSpy();
var model = new HelloModel(spy);
- model.setMessageToSend("Hello World!");
+ model.setMessageToSend("Hello World!");
model.sendMessage();
assertThat(spy.message).isEqualTo("Hello World!");
@@ -31,8 +31,6 @@ void sendMessageToFakeServer(WireMockRuntimeInfo wmRuntimeInfo) {
stubFor(post("/mytopic").willReturn(ok()));
model.sendMessage();
-
- verify(postRequestedFor(urlEqualTo("/mytopic"))
- .withRequestBody(matching("Hello World!")));
+ verify(postRequestedFor(urlEqualTo("/mytopic")));
}
}
\ No newline at end of file
From 27ea8484f2e41af769609bbf01d710811b82ae60 Mon Sep 17 00:00:00 2001
From: Sandra Neljestam <229708855+SandraNelj@users.noreply.github.com>
Date: Fri, 14 Nov 2025 15:53:26 +0100
Subject: [PATCH 7/9] =?UTF-8?q?=C3=84ndrat=20till=20endsWith=20ist=C3=A4ll?=
=?UTF-8?q?et=20f=C3=B6r=20isEqualTo?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/test/java/com/example/HelloModelTest.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/java/com/example/HelloModelTest.java b/src/test/java/com/example/HelloModelTest.java
index fc757e68..fe4d0440 100644
--- a/src/test/java/com/example/HelloModelTest.java
+++ b/src/test/java/com/example/HelloModelTest.java
@@ -20,7 +20,7 @@ void sendMessageCallsConnectionWithMessageToSend() {
model.setMessageToSend("Hello World!");
model.sendMessage();
- assertThat(spy.message).isEqualTo("Hello World!");
+ assertThat(spy.message).endsWith("Hello World!");
}
@Test
From 0c3274f3d9f41589f3b25ea4a5a4b665978c107f Mon Sep 17 00:00:00 2001
From: Sandra Neljestam <229708855+SandraNelj@users.noreply.github.com>
Date: Fri, 28 Nov 2025 08:34:47 +0100
Subject: [PATCH 8/9] =?UTF-8?q?Lagt=20till=20metod=20samt=20knapp=20f?=
=?UTF-8?q?=C3=B6r=20att=20bifoga=20och=20skicka=20bilder?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 9 ++++++
.../java/com/example/HelloController.java | 19 ++++++++++++-
src/main/java/com/example/HelloFX.java | 2 +-
src/main/java/com/example/HelloModel.java | 26 +++++++++--------
src/main/java/com/example/NtfyConnection.java | 5 ++++
.../java/com/example/NtfyConnectionImpl.java | 28 ++++++++++++++++++-
src/main/java/com/example/NtfyMessageDto.java | 3 ++
.../resources/com/example/hello-view.fxml | 3 +-
.../java/com/example/NtfyConnectionSpy.java | 7 +++++
9 files changed, 87 insertions(+), 15 deletions(-)
diff --git a/pom.xml b/pom.xml
index e324b59b..ba3cb96c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -79,6 +79,15 @@
true
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 25
+ 25
+ --enable-preview
+
+
diff --git a/src/main/java/com/example/HelloController.java b/src/main/java/com/example/HelloController.java
index 864a6b58..57e0d16c 100644
--- a/src/main/java/com/example/HelloController.java
+++ b/src/main/java/com/example/HelloController.java
@@ -5,6 +5,9 @@
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.input.KeyCode;
+import javafx.stage.FileChooser;
+
+import java.io.File;
/**
* Controller layer: mediates between the view (FXML) and the model.
@@ -14,8 +17,10 @@ public class HelloController {
@FXML private TextArea inputField;
@FXML private Button sendButton;
@FXML private TextArea chatArea;
+ @FXML private Button sendFileButton;
+
- private final HelloModel model = new HelloModel();
+ public final HelloModel model = new HelloModel(new NtfyConnectionImpl());
@FXML
@@ -31,6 +36,7 @@ private void initialize() {
}
});
+ sendFileButton.setOnAction(e -> attachFile());
sendButton.setOnAction(e -> sendMessage());
inputField.setOnKeyPressed(event -> {
@@ -50,4 +56,15 @@ private void sendMessage() {
model.sendMessage();
inputField.clear();
}
+ @FXML
+ private void attachFile() {
+ FileChooser chooser = new FileChooser();
+ chooser.setTitle("Välj fil att bifoga");
+
+ File file = chooser.showOpenDialog(sendFileButton.getScene().getWindow());
+ if (file != null) {
+ model.sendFile(file);
+ chatArea.appendText("Du skickade fil: " + file.getName() + "\n");
+ }
+ }
}
diff --git a/src/main/java/com/example/HelloFX.java b/src/main/java/com/example/HelloFX.java
index fd865a1d..f49ba359 100644
--- a/src/main/java/com/example/HelloFX.java
+++ b/src/main/java/com/example/HelloFX.java
@@ -19,7 +19,7 @@ public void start(Stage stage) throws Exception {
stage.show();
}
- public static void main(String[] args) {
+ static void main() {
launch();
}
}
\ No newline at end of file
diff --git a/src/main/java/com/example/HelloModel.java b/src/main/java/com/example/HelloModel.java
index 11f76fb6..bed5df4d 100644
--- a/src/main/java/com/example/HelloModel.java
+++ b/src/main/java/com/example/HelloModel.java
@@ -6,6 +6,9 @@
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.io.*;
+import java.net.URI;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
import java.util.UUID;
/**
@@ -45,22 +48,23 @@ public void sendMessage() {
String text = messageToSend.get().trim();
if (text.isEmpty()) return;
- String payload = clientId + "::" + text;
- connection.send(payload);
+ connection.send(text);
messageToSend.set("");
}
- public void receiveMessage() {
- connection.receive(msg -> {
- String incoming = msg.message();
-
- if (incoming.startsWith(clientId + "::")) {
- return;
- }
+ public void sendFile(File file) {
+ try {
+ connection.sendFile(file.toPath());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
- Platform.runLater(() -> messages.add(msg));
- });
+ public void receiveMessage() {
+ connection.receive(m ->
+ Platform.runLater(() -> messages.add(m))
+ );
}
}
diff --git a/src/main/java/com/example/NtfyConnection.java b/src/main/java/com/example/NtfyConnection.java
index a02a794a..9af19586 100644
--- a/src/main/java/com/example/NtfyConnection.java
+++ b/src/main/java/com/example/NtfyConnection.java
@@ -1,7 +1,12 @@
package com.example;
+import java.io.FileNotFoundException;
+import java.nio.file.Path;
import java.util.function.Consumer;
public interface NtfyConnection {
boolean send (String message);
+
void receive (Consumer messageHandler);
+
+ boolean sendFile(Path filePath) throws FileNotFoundException;
}
diff --git a/src/main/java/com/example/NtfyConnectionImpl.java b/src/main/java/com/example/NtfyConnectionImpl.java
index 61d9dedb..62eb8847 100644
--- a/src/main/java/com/example/NtfyConnectionImpl.java
+++ b/src/main/java/com/example/NtfyConnectionImpl.java
@@ -4,11 +4,16 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.cdimascio.dotenv.Dotenv;
+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.nio.file.Files;
+import java.nio.file.Path;
+import java.util.UUID;
import java.util.function.Consumer;
public class NtfyConnectionImpl implements NtfyConnection {
@@ -32,12 +37,12 @@ public NtfyConnectionImpl(String hostName) {
@Override
public boolean send (String message) {
- try {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(hostName + "/mytopic"))
.POST(HttpRequest.BodyPublishers.ofString(message))
.build();
+ try {
http.send(request, HttpResponse.BodyHandlers.discarding());
return true;
@@ -47,6 +52,27 @@ public boolean send (String message) {
}
}
+ @Override
+ public boolean sendFile(Path filePath) throws FileNotFoundException {
+ if (!Files.exists(filePath)) {
+ throw new FileNotFoundException("File does not exist: " + filePath);
+ }
+
+ HttpRequest httpRequest = HttpRequest.newBuilder()
+ .uri(URI.create(hostName + "/mytopic"))
+ .header("Content-type", "application/octet-stream")
+ .POST(HttpRequest.BodyPublishers.ofFile(filePath))
+ .build();
+
+ try {
+ http.send(httpRequest, HttpResponse.BodyHandlers.discarding());
+ return true;
+ } catch (IOException | InterruptedException e) {
+ System.out.println("Error sending file: " + e.getMessage());
+ return false;
+ }
+ }
+
@Override
public void receive (Consumer handler) {
HttpRequest httpRequest = HttpRequest.newBuilder()
diff --git a/src/main/java/com/example/NtfyMessageDto.java b/src/main/java/com/example/NtfyMessageDto.java
index 7a57c27d..f3f6bad5 100644
--- a/src/main/java/com/example/NtfyMessageDto.java
+++ b/src/main/java/com/example/NtfyMessageDto.java
@@ -4,4 +4,7 @@
@JsonIgnoreProperties(ignoreUnknown = true)
public record NtfyMessageDto(String id, long time, String event, String topic, String message) {
+ public record Attachment(String name, String type, long size, String url) {
+ }
}
+
diff --git a/src/main/resources/com/example/hello-view.fxml b/src/main/resources/com/example/hello-view.fxml
index 474a961f..afb659cf 100644
--- a/src/main/resources/com/example/hello-view.fxml
+++ b/src/main/resources/com/example/hello-view.fxml
@@ -14,6 +14,7 @@
-
+
+
diff --git a/src/test/java/com/example/NtfyConnectionSpy.java b/src/test/java/com/example/NtfyConnectionSpy.java
index 90a5c9b0..67528a8f 100644
--- a/src/test/java/com/example/NtfyConnectionSpy.java
+++ b/src/test/java/com/example/NtfyConnectionSpy.java
@@ -1,5 +1,7 @@
package com.example;
+import java.io.FileNotFoundException;
+import java.nio.file.Path;
import java.util.function.Consumer;
public class NtfyConnectionSpy implements NtfyConnection {
@@ -13,4 +15,9 @@ public boolean send (String message) {
@Override
public void receive (ConsumermessageHandler) {
}
+
+ @Override
+ public boolean sendFile(Path filePath) throws FileNotFoundException {
+ return false;
+ }
}
From 7615cbc9e116c07a16da2e00eeae72c8ed32a98a Mon Sep 17 00:00:00 2001
From: Sandra Neljestam <229708855+SandraNelj@users.noreply.github.com>
Date: Fri, 28 Nov 2025 09:22:13 +0100
Subject: [PATCH 9/9] =?UTF-8?q?=C3=84ndrat=20preview=20i=20pom.xml=20fil?=
=?UTF-8?q?=20enligt=20botens=20r=C3=A5d?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index ba3cb96c..7f5c168f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -85,7 +85,7 @@
25
25
- --enable-preview
+ true