diff --git a/pom.xml b/pom.xml
index c40f667e..47427c21 100644
--- a/pom.xml
+++ b/pom.xml
@@ -45,9 +45,25 @@
javafx-fxml
${javafx.version}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.11.0
+
+ 25
+ 25
+ 25
+
+
+
+
org.openjfx
javafx-maven-plugin
@@ -55,7 +71,7 @@
com.example.HelloFX
-
+
javafx
true
@@ -63,6 +79,7 @@
true
+
diff --git a/src/main/java/com/example/ChatController.java b/src/main/java/com/example/ChatController.java
new file mode 100644
index 00000000..2cc49f68
--- /dev/null
+++ b/src/main/java/com/example/ChatController.java
@@ -0,0 +1,42 @@
+package com.example;
+
+import javafx.fxml.FXML;
+import javafx.scene.control.ListView;
+import javafx.scene.control.TextField;
+import javafx.application.Platform;
+
+public class ChatController {
+
+ @FXML
+ private ListView messagesList;
+
+ @FXML
+ private TextField inputField;
+
+ private final ChatModel model = new ChatModel();
+
+
+ @FXML
+ private void onSend() {
+ String message = inputField.getText().trim();
+ if (!message.isEmpty()) {
+ messagesList.getItems().add("Me: " + message);
+ model.sendMessage(message);
+ inputField.clear();
+ }
+ }
+
+
+ @FXML
+ private void initialize(){
+ model.subscribe(msg -> {
+ Platform.runLater(() -> messagesList.getItems().add("Friend: " + msg));
+ });
+ }
+
+
+
+
+
+
+}
diff --git a/src/main/java/com/example/ChatModel.java b/src/main/java/com/example/ChatModel.java
new file mode 100644
index 00000000..c1947f0c
--- /dev/null
+++ b/src/main/java/com/example/ChatModel.java
@@ -0,0 +1,117 @@
+package com.example;
+
+import javafx.application.Platform;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.Collections;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class ChatModel {
+
+ private final String sendUrl;
+ private final String subscribeUrl;
+ private final String clientId;
+ private final Set sentMessages = Collections.newSetFromMap(new ConcurrentHashMap<>());
+
+ public ChatModel() {
+ String topic = System.getenv().getOrDefault("NTFY_TOPIC", "https://ntfy.sh/newchatroom3");
+
+ sendUrl = topic;
+ subscribeUrl = topic + "/sse";
+ clientId = UUID.randomUUID().toString();
+ }
+
+ public void sendMessage(String message) {
+ sentMessages.add(message);
+
+ new Thread(() -> {
+ try { Thread.sleep(5000); } catch (Exception ignored) {}
+ sentMessages.remove(message);
+ }).start();
+
+ HttpClient client = HttpClient.newHttpClient();
+
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(sendUrl))
+ .header("Content-Type", "application/json")
+ .header("X-Client-ID", clientId)
+ .header("Title", "Friend: ")
+ .POST(HttpRequest.BodyPublishers.ofString(message))
+ .build();
+
+ client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
+ .thenAccept(response -> System.out.println("Sent, status=" + response.statusCode()))
+ .exceptionally(e -> { e.printStackTrace(); return null; });
+ }
+
+
+ public void subscribe(Consumer onMessageReceived) {
+ HttpClient client = HttpClient.newHttpClient();
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(subscribeUrl))
+ .header("Accept", "text/event-stream")
+ .build();
+
+ client.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream())
+ .thenAccept(response -> {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(response.body()))) {
+ String line;
+ boolean firstMessageSkipped = false;
+ while ((line = reader.readLine()) != null) {
+ if (line.startsWith("data:")) {
+ if (!firstMessageSkipped) {
+ firstMessageSkipped = true;
+ continue;
+ }
+
+ String raw = line.substring(5).trim();
+ String msg = parseMessage(raw);
+
+ if (msg != null && !sentMessages.contains(msg)) {
+ Platform.runLater(() -> onMessageReceived.accept(msg));
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ })
+ .exceptionally(e -> { e.printStackTrace(); return null; });
+ }
+
+ private String parseMessage(String data) {
+ try {
+ Matcher eventMatcher = Pattern.compile("\"event\"\\s*:\\s*\"(.*?)\"").matcher(data);
+ if (eventMatcher.find()) {
+ String event = eventMatcher.group(1);
+
+ if (!"message".equals(event)) {
+ return null;
+ }
+ }
+
+ Matcher msgMatcher = Pattern.compile("\"message\"\\s*:\\s*\"(.*?)\"").matcher(data);
+ if (msgMatcher.find()) {
+ return msgMatcher.group(1).replace("\\\"", "\"");
+ }
+ } catch (Exception ignored) {}
+
+ return null;
+ }
+
+
+
+}
+
+
+
diff --git a/src/main/java/com/example/HelloFX.java b/src/main/java/com/example/HelloFX.java
index 96bdc5ca..be82aace 100644
--- a/src/main/java/com/example/HelloFX.java
+++ b/src/main/java/com/example/HelloFX.java
@@ -10,10 +10,10 @@ public class HelloFX extends Application {
@Override
public void start(Stage stage) throws Exception {
- FXMLLoader fxmlLoader = new FXMLLoader(HelloFX.class.getResource("hello-view.fxml"));
+ FXMLLoader fxmlLoader = new FXMLLoader(HelloFX.class.getResource("chat-view.fxml"));
Parent root = fxmlLoader.load();
Scene scene = new Scene(root, 640, 480);
- stage.setTitle("Hello MVC");
+ stage.setTitle("Chat App");
stage.setScene(scene);
stage.show();
}
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 71574a27..0a19298c 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,6 +1,8 @@
module hellofx {
requires javafx.controls;
requires javafx.fxml;
+ requires java.net.http;
+
opens com.example to javafx.fxml;
exports com.example;
diff --git a/src/main/resources/com/example/chat-view.fxml b/src/main/resources/com/example/chat-view.fxml
new file mode 100644
index 00000000..667d541b
--- /dev/null
+++ b/src/main/resources/com/example/chat-view.fxml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+