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 @@ - - - - - - - + + + + + + + + + + + +