Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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.
67 changes: 36 additions & 31 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,70 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>javafx</artifactId>
<groupId>org.example</groupId>
<artifactId>JavaFXChatApp</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.release>25</maven.compiler.release>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.jupiter.version>6.0.0</junit.jupiter.version>
<assertj.core.version>3.27.6</assertj.core.version>
<mockito.version>5.20.0</mockito.version>
<javafx.version>25</javafx.version>
<junit.jupiter.version>5.13.4</junit.jupiter.version>
</properties>

<dependencies>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>25</version>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.core.version}</version>
<scope>test</scope>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>25</version>
</dependency>


<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>3.2.0</version>
</dependency>


<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>


<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.jupiter.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>

<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.8</version>
<configuration>
<mainClass>com.example.HelloFX</mainClass>
<options>
<option>--enable-native-access=javafx.graphics</option>
</options>
<launcher>javafx</launcher>
<stripDebug>true</stripDebug>
<noHeaderFiles>true</noHeaderFiles>
<noManPages>true</noManPages>
</configuration>
</plugin>


<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>
</project>
54 changes: 45 additions & 9 deletions src/main/java/com/example/HelloController.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,58 @@
package com.example;

import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.stage.FileChooser;
import javafx.scene.control.Button;

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 chatArea;

@FXML
private TextField inputField;

@FXML
private Button sendButton;

@FXML
private Label messageLabel;
private Button attachButton;

private HelloModel model;

@FXML
public void initialize() {
// Läser BACKEND_URL och TOPIC från .env via HelloModel
model = new HelloModel();

// Lyssna på inkommande meddelanden
model.listen(msg -> {
Platform.runLater(() -> chatArea.appendText(msg + "\n"));
System.out.println("📩 Mottaget: " + msg);
});
}

@FXML
protected void onSendButtonClick() {
String message = inputField.getText().trim();
if (!message.isEmpty()) {
model.sendMessage(message);
inputField.clear();
}
}

@FXML
private void initialize() {
if (messageLabel != null) {
messageLabel.setText(model.getGreeting());
protected void onAttachFileClick() {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Välj en fil att skicka");
File file = fileChooser.showOpenDialog(chatArea.getScene().getWindow());
if (file != null) {
model.sendFile(file);
}
}
}
13 changes: 10 additions & 3 deletions src/main/java/com/example/HelloFX.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,23 @@ public class HelloFX extends Application {

@Override
public void start(Stage stage) throws Exception {
// Ladda FXML
FXMLLoader fxmlLoader = new FXMLLoader(HelloFX.class.getResource("hello-view.fxml"));
Parent root = fxmlLoader.load();

// Skapa scenen
Scene scene = new Scene(root, 640, 480);
stage.setTitle("Hello MVC");

// Koppla in CSS-styling
scene.getStylesheets().add(HelloFX.class.getResource("style.css").toExternalForm());

// Sätt titel
stage.setTitle("Java Chat");
stage.setScene(scene);
stage.show();
}

public static void main(String[] args) {
launch();
}

}
}
145 changes: 135 additions & 10 deletions src/main/java/com/example/HelloModel.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,140 @@
package com.example;

/**
* Model layer: encapsulates application data and business logic.
*/
import io.github.cdimascio.dotenv.Dotenv;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;

import java.io.File;
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.util.concurrent.CompletableFuture;

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 static final ObjectMapper mapper = new ObjectMapper();

private final HttpClient client = HttpClient.newHttpClient();
private final String topic;
private final String backendUrl;

/** Standardkonstruktor som läser från .env */
public HelloModel() {
Dotenv dotenv = Dotenv.configure().ignoreIfMissing().load();
this.backendUrl = dotenv.get("BACKEND_URL", System.getenv("BACKEND_URL"));
this.topic = dotenv.get("TOPIC", System.getenv("TOPIC"));
if (backendUrl == null || topic == null) {
throw new IllegalStateException("BACKEND_URL eller TOPIC saknas i .env");
}
}

/** Alternativ konstruktor för tester */
HelloModel(String topic, String backendUrl) {
if (backendUrl == null || backendUrl.isBlank()) {
throw new IllegalArgumentException("backendUrl must not be null/blank");
}
this.backendUrl = backendUrl;
this.topic = topic;
}

public void sendMessage(String message) {
String sender = "[Eric Chat App]";
String fullMessage = sender + " " + message;

String json = "{\"message\": \"" + fullMessage.replace("\"", "\\\"") + "\"}";
String url = backendUrl + "/" + topic;

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();

client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenAccept(response -> {
if (response.statusCode() >= 300) {
System.err.println("⚠️ Misslyckades att skicka: " + response.statusCode() + " - " + response.body());
}
})
.exceptionally(ex -> {
System.err.println("⚠️ Nätverksfel vid sendMessage: " + ex.getMessage());
return null;
});
}

public void sendFile(File file) {
try {
String url = backendUrl + "/" + topic;
String contentType = Files.probeContentType(file.toPath());
if (contentType == null) contentType = "application/octet-stream";

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Content-Type", contentType)
.header("X-Filename", file.getName())
.header("Title", "File: " + file.getName())
.POST(HttpRequest.BodyPublishers.ofFile(file.toPath()))
.build();

client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenAccept(response -> {
if (response.statusCode() >= 300) {
System.err.println("⚠️ Filupload misslyckades: " + response.statusCode() + " - " + response.body());
} else {
System.out.println("✅ Fil skickad: " + file.getName());
}
})
.exceptionally(ex -> {
System.err.println("⚠️ Nätverksfel vid sendFile: " + ex.getMessage());
return null;
});
} catch (Exception e) {
System.err.println("⚠️ Kunde inte läsa/skicka fil: " + e.getMessage());
}
}

public CompletableFuture<Void> listen(MessageHandler handler) {
String url = backendUrl + "/" + topic + "/json";
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).build();

return client.sendAsync(request, HttpResponse.BodyHandlers.ofLines())
.thenAccept(response -> response.body().forEach(line -> {
String parsed = parseIncomingLine(line);
if (!parsed.isEmpty()) {
handler.onMessage(parsed);
System.out.println("📩 Meddelande: " + parsed);
}
}))
.exceptionally(ex -> {
System.err.println("⚠️ Nätverksfel vid listen: " + ex.getMessage());
return null;
});
}

String parseIncomingLine(String line) {
try {
JsonNode outer = mapper.readTree(line);
String raw = outer.path("message").asText("");
if (raw.isEmpty()) return "";

String clean = raw.startsWith("{")
? mapper.readTree(raw).path("message").asText(raw)
: raw;

if (!clean.contains("[Eric Chat App]") && !clean.contains("[Javafx-chat]")) {
clean = "[Javafx-chat] " + clean;
}

return "💬 " + clean;
} catch (Exception e) {
System.err.println("⚠️ Kunde inte tolka rad: " + line + " | " + e.getMessage());
return "";
}
}

public interface MessageHandler {
void onMessage(String message);
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/example/NtfyMessageDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public record NtfyMessageDto(String event, String topic, String message) {}
8 changes: 6 additions & 2 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
module hellofx {
requires javafx.controls;
requires javafx.fxml;
requires java.net.http;
requires io.github.cdimascio.dotenv.java;
requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.annotation;

opens com.example to javafx.fxml;
opens com.example to javafx.fxml, com.fasterxml.jackson.databind;
exports com.example;
}
}
Loading
Loading