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..ba3cb96c 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
+
@@ -63,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 fdd160a0..57e0d16c 100644
--- a/src/main/java/com/example/HelloController.java
+++ b/src/main/java/com/example/HelloController.java
@@ -1,22 +1,70 @@
package com.example;
+import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
-import javafx.scene.control.Label;
+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.
*/
public class HelloController {
- private final HelloModel model = new HelloModel();
+ @FXML private TextArea inputField;
+ @FXML private Button sendButton;
+ @FXML private TextArea chatArea;
+ @FXML private Button sendFileButton;
+
+
+ public final HelloModel model = new HelloModel(new NtfyConnectionImpl());
- @FXML
- private Label messageLabel;
@FXML
private void initialize() {
- if (messageLabel != null) {
- messageLabel.setText(model.getGreeting());
+
+ model.getMessages().addListener((ListChangeListener) change ->{
+ while(change.next()) {
+ if (change.wasAdded()) {
+ for (var msg : change.getAddedSubList()) {
+ chatArea.appendText(msg.message() + "\n");
+ }
+ }
+ }
+ });
+
+ sendFileButton.setOnAction(e -> attachFile());
+ sendButton.setOnAction(e -> sendMessage());
+
+ inputField.setOnKeyPressed(event -> {
+ if (event.getCode() == KeyCode.ENTER && !event.isShiftDown()) {
+ sendMessage();
+ event.consume();
+ }
+ });
+ }
+ @FXML
+ private void sendMessage() {
+ String msg = inputField.getText().trim();
+ if (msg.isEmpty()) return;
+
+ chatArea.appendText("Du: " + msg + "\n");
+ model.setMessageToSend(msg);
+ 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 96bdc5ca..f49ba359 100644
--- a/src/main/java/com/example/HelloFX.java
+++ b/src/main/java/com/example/HelloFX.java
@@ -10,16 +10,16 @@ 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("Hello MVC");
+ stage.setTitle("JavaFX Chat App");
stage.setScene(scene);
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 385cfd10..bed5df4d 100644
--- a/src/main/java/com/example/HelloModel.java
+++ b/src/main/java/com/example/HelloModel.java
@@ -1,5 +1,16 @@
package com.example;
+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.URI;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.UUID;
+
/**
* Model layer: encapsulates application data and business logic.
*/
@@ -7,9 +18,61 @@ 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 NtfyConnection connection;
+ private final StringProperty messageToSend = new SimpleStringProperty("");
+ private final ObservableList messages = FXCollections.observableArrayList();
+ private final String clientId = UUID.randomUUID().toString();
+
+ //Konstuktor för prod
+ public HelloModel() {
+ this.connection = new NtfyConnectionImpl();
+ receiveMessage();
+ }
+
+ //Konstuktor för test
+ public HelloModel(NtfyConnection connection) {
+ this.connection = connection;
+ receiveMessage();
+ }
+
+ public ObservableList getMessages() {
+ return messages;
+ }
+
+ public void setMessageToSend(String message) {
+ messageToSend.set(message);
+ }
+
+ public void sendMessage() {
+ String text = messageToSend.get().trim();
+ if (text.isEmpty()) return;
+
+ connection.send(text);
+
+ messageToSend.set("");
+ }
+
+ public void sendFile(File file) {
+ try {
+ connection.sendFile(file.toPath());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ 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..9af19586
--- /dev/null
+++ b/src/main/java/com/example/NtfyConnection.java
@@ -0,0 +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
new file mode 100644
index 00000000..62eb8847
--- /dev/null
+++ b/src/main/java/com/example/NtfyConnectionImpl.java
@@ -0,0 +1,95 @@
+package com.example;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+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 {
+ private final HttpClient http = HttpClient.newHttpClient();
+ private final String hostName;
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ public NtfyConnectionImpl() {
+ Dotenv dotenv = Dotenv.load();
+ 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) {
+ this.hostName = hostName;
+ }
+
+ @Override
+ public boolean send (String message) {
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(hostName + "/mytopic"))
+ .POST(HttpRequest.BodyPublishers.ofString(message))
+ .build();
+
+ try {
+ http.send(request, HttpResponse.BodyHandlers.discarding());
+ return true;
+
+ } catch (IOException | InterruptedException e) {
+ System.out.println("Error sending message!");
+ return false;
+ }
+ }
+
+ @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()
+ .uri(URI.create(hostName + "/mytopic/json"))
+ .GET()
+ .build();
+
+ http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines())
+ .thenAccept(response -> response.body()
+ .map(line -> {
+ try {
+ return mapper.readValue(line, NtfyMessageDto.class);
+ } catch (JsonProcessingException e) {
+ return null;
+ }
+ })
+ .filter(msg -> msg != null && msg.event().equals("message"))
+ .forEach(handler));
+ }
+}
diff --git a/src/main/java/com/example/NtfyMessageDto.java b/src/main/java/com/example/NtfyMessageDto.java
new file mode 100644
index 00000000..f3f6bad5
--- /dev/null
+++ b/src/main/java/com/example/NtfyMessageDto.java
@@ -0,0 +1,10 @@
+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) {
+ public record Attachment(String name, String type, long size, String url) {
+ }
+}
+
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 20a7dc82..afb659cf 100644
--- a/src/main/resources/com/example/hello-view.fxml
+++ b/src/main/resources/com/example/hello-view.fxml
@@ -1,9 +1,20 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/com/example/HelloModelTest.java b/src/test/java/com/example/HelloModelTest.java
new file mode 100644
index 00000000..fe4d0440
--- /dev/null
+++ b/src/test/java/com/example/HelloModelTest.java
@@ -0,0 +1,36 @@
+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).endsWith("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")));
+ }
+}
\ 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..67528a8f
--- /dev/null
+++ b/src/test/java/com/example/NtfyConnectionSpy.java
@@ -0,0 +1,23 @@
+package com.example;
+
+import java.io.FileNotFoundException;
+import java.nio.file.Path;
+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) {
+ }
+
+ @Override
+ public boolean sendFile(Path filePath) throws FileNotFoundException {
+ return false;
+ }
+}