-
Notifications
You must be signed in to change notification settings - Fork 66
Model #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Model #19
Changes from all commits
ba9d641
87e581f
906b4b2
404b1f1
16b1396
5de28bf
61b6033
ffa9303
05dae89
3270b81
1d1a4b2
e323a3c
c37ddfa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| target/ | ||
| /.idea/ | ||
| .env |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package com.example; | ||
|
|
||
| import javafx.application.Platform; | ||
|
|
||
| public class FxUtils { | ||
|
|
||
| /** | ||
| * Runs a task on the JavaFX thread. | ||
| * If already on the thread, runs immediately. | ||
| * If not, schedules it to run later. | ||
| * If JavaFX is not initialized, runs on the current thread. | ||
| */ | ||
|
|
||
| public static void runOnFx(Runnable task) { | ||
| try { | ||
| if (Platform.isFxApplicationThread()) { | ||
| task.run(); | ||
| } else { | ||
| Platform.runLater(task); | ||
| } | ||
| } catch (IllegalStateException notInitialized) { | ||
| //headless | ||
| task.run(); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,22 +1,156 @@ | ||||||||||||
| package com.example; | ||||||||||||
|
|
||||||||||||
| import javafx.application.Platform; | ||||||||||||
| import javafx.beans.binding.Bindings; | ||||||||||||
| import javafx.event.ActionEvent; | ||||||||||||
| import javafx.fxml.FXML; | ||||||||||||
| import javafx.scene.control.Label; | ||||||||||||
| import javafx.geometry.Insets; | ||||||||||||
| import javafx.geometry.Pos; | ||||||||||||
| import javafx.scene.control.*; | ||||||||||||
| import javafx.scene.layout.HBox; | ||||||||||||
|
|
||||||||||||
| import java.time.ZoneId; | ||||||||||||
| import java.time.format.DateTimeFormatter; | ||||||||||||
|
|
||||||||||||
| /** | ||||||||||||
| * Controller layer: mediates between the view (FXML) and the model. | ||||||||||||
| * Controller for the chat app. | ||||||||||||
| * <p> | ||||||||||||
| * Connects the FXML view to the HelloModel. | ||||||||||||
| * Handles sending messages, changing topics, and updating the UI. | ||||||||||||
| */ | ||||||||||||
|
|
||||||||||||
| public class HelloController { | ||||||||||||
|
|
||||||||||||
| private final HelloModel model = new HelloModel(); | ||||||||||||
| private final HelloModel model = new HelloModel(new NtfyConnectionImpl()); | ||||||||||||
|
|
||||||||||||
| @FXML | ||||||||||||
| private Button sendButton; | ||||||||||||
|
|
||||||||||||
| @FXML | ||||||||||||
| private Label messageLabel; | ||||||||||||
|
|
||||||||||||
| @FXML | ||||||||||||
| private Label topicLabel; | ||||||||||||
|
|
||||||||||||
| @FXML | ||||||||||||
| private ListView<NtfyMessageDto> messageView; | ||||||||||||
|
|
||||||||||||
| @FXML | ||||||||||||
| private TextArea messageInput; | ||||||||||||
|
|
||||||||||||
| @FXML | ||||||||||||
| private TextField topicInput; | ||||||||||||
|
|
||||||||||||
| @FXML | ||||||||||||
| private Button changeTopicButton; | ||||||||||||
|
|
||||||||||||
| private final DateTimeFormatter timeFormatter = | ||||||||||||
| DateTimeFormatter.ofPattern("HH:mm:ss") | ||||||||||||
| .withZone(ZoneId.systemDefault()); | ||||||||||||
|
Comment on lines
+47
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Remove unused The - private final DateTimeFormatter timeFormatter =
- DateTimeFormatter.ofPattern("HH:mm:ss")
- .withZone(ZoneId.systemDefault());
-
@FXML
private void initialize() {Also remove the unused imports: -import java.time.ZoneId;
-import java.time.format.DateTimeFormatter;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||
|
|
||||||||||||
| @FXML | ||||||||||||
| private void initialize() { | ||||||||||||
| if (messageLabel != null) { | ||||||||||||
| messageLabel.setText(model.getGreeting()); | ||||||||||||
| messageLabel.setText(model.getGreeting()); | ||||||||||||
|
|
||||||||||||
| Platform.runLater(() -> messageInput.requestFocus()); | ||||||||||||
|
|
||||||||||||
| topicLabel.setText("/" + model.getCurrentTopic()); | ||||||||||||
| model.currentTopicProperty().addListener((obs, oldVal, newVal) -> { | ||||||||||||
| topicLabel.setText("/" + newVal); | ||||||||||||
| }); | ||||||||||||
|
|
||||||||||||
| messageView.setItems(model.getMessages()); | ||||||||||||
|
|
||||||||||||
| messageInput.textProperty().bindBidirectional(model.messageToSendProperty()); | ||||||||||||
|
|
||||||||||||
| sendButton.disableProperty().bind(Bindings.createBooleanBinding( | ||||||||||||
| () -> { | ||||||||||||
| String text = messageInput.getText(); | ||||||||||||
| return text == null || text.trim().isEmpty(); | ||||||||||||
| }, | ||||||||||||
| messageInput.textProperty() | ||||||||||||
| )); | ||||||||||||
|
|
||||||||||||
| if (changeTopicButton != null) { | ||||||||||||
| changeTopicButton.disableProperty().bind(Bindings.createBooleanBinding( | ||||||||||||
| () -> { | ||||||||||||
| String text = topicInput.getText(); | ||||||||||||
| return text == null || text.trim().isEmpty(); | ||||||||||||
| }, | ||||||||||||
| topicInput.textProperty() | ||||||||||||
| )); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| messageView.setCellFactory(lv -> new ListCell<>() { | ||||||||||||
| @Override | ||||||||||||
| protected void updateItem(NtfyMessageDto msg, boolean empty) { | ||||||||||||
| super.updateItem(msg, empty); | ||||||||||||
|
|
||||||||||||
| if (empty || msg == null || msg.message() == null || msg.message().isBlank()) { | ||||||||||||
| setText(null); | ||||||||||||
| setGraphic(null); | ||||||||||||
| } else { | ||||||||||||
| // Skapa bubble-label | ||||||||||||
| Label bubble = new Label(msg.message()); | ||||||||||||
| bubble.setWrapText(true); | ||||||||||||
| bubble.setMaxWidth(250); | ||||||||||||
| bubble.setPadding(new Insets(10)); | ||||||||||||
| bubble.getStyleClass().add("chat-bubble"); // Basstyle | ||||||||||||
|
|
||||||||||||
| HBox container = new HBox(bubble); | ||||||||||||
| container.setPadding(new Insets(5)); | ||||||||||||
|
|
||||||||||||
| // Använd CSS-klasser för skickat/mottaget | ||||||||||||
| if (model.getUserId().equals(msg.id())) { | ||||||||||||
| bubble.getStyleClass().add("chat-bubble-sent"); | ||||||||||||
| container.setAlignment(Pos.CENTER_RIGHT); | ||||||||||||
| } else { | ||||||||||||
| bubble.getStyleClass().add("chat-bubble-received"); | ||||||||||||
| container.setAlignment(Pos.CENTER_LEFT); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| setText(null); | ||||||||||||
| setGraphic(container); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| }); | ||||||||||||
|
Comment on lines
+85
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainVerify sender identification logic. Line 105 compares If Verify the NtfyMessageDto structure and confirm which field contains the sender/user information: 🏁 Script executed: #!/bin/bash
# Check the NtfyMessageDto record definition
rg -A 5 "record NtfyMessageDto"Length of output: 190 Length of output: 1712 🏁 Script executed: #!/bin/bash
# Check the HelloModel class to see what getUserId() returns and how it's used
rg -A 10 "getUserId\(\)" src/main/java/com/example/HelloModel.javaLength of output: 277 🏁 Script executed: #!/bin/bash
# Look for any service/repository code that creates NtfyMessageDto
rg "NtfyMessageDto" -B 3 -A 3 -n --type java | head -50Length of output: 3933 🏁 Script executed: #!/bin/bash
# Find the real NtfyConnection implementation (not the spy)
rg "class.*NtfyConnection" --type java -A 5Length of output: 1021 🏁 Script executed: #!/bin/bash
# Look for where getUserId is set/initialized in the connection
rg "getUserId" --type java -B 3 -A 3 -n | head -60Length of output: 2147 🏁 Script executed: #!/bin/bash
# Search for any parsing or mapping of Ntfy messages/responses
rg -i "ntfy\|parse\|message" src/main/java/com/example/ --type java -n | grep -i "parse\|json\|response\|send" | head -30Length of output: 0 🏁 Script executed: #!/bin/bash
# Get the full NtfyConnectionImpl implementation, especially the receive method
cat src/main/java/com/example/NtfyConnectionImpl.javaLength of output: 3888 🏁 Script executed: #!/bin/bash
# Search for where NtfyMessageDto is constructed
rg "new NtfyMessageDto\|NtfyMessageDto(" --type java -B 5 -A 2 -nLength of output: 120 💡 Result: Short answer: ntfy's JSON message format does not include a built‑in "sender" or "user" field. Messages (when published as JSON or received via /json or SSE) include id, time, topic, message, title, tags, priority, actions, click/attachment, etc., but not a publisher/sender property — identification of who published is handled by authentication (Basic auth or access tokens) at the HTTP layer, not as a JSON field in the message. See ntfy publish/JSON and subscribe docs and the auth/config docs for details. [1][2][3][4] References
Fix sender identification logic — The comparison at line 105 ( The current implementation cannot distinguish sent from received messages without additional logic. Consider either:
🤖 Prompt for AI Agents |
||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| // Scrolla ner till senaste meddelandet | ||||||||||||
| model.getMessages().addListener((javafx.collections.ListChangeListener<NtfyMessageDto>) change -> { | ||||||||||||
| Platform.runLater(() -> { | ||||||||||||
| if (!messageView.getItems().isEmpty()) { | ||||||||||||
| messageView.scrollTo(messageView.getItems().size() - 1); | ||||||||||||
| } | ||||||||||||
| }); | ||||||||||||
| }); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| @FXML | ||||||||||||
| private void sendMessage(ActionEvent actionEvent) { | ||||||||||||
| model.sendMessageAsync(success -> { | ||||||||||||
| if (success) { | ||||||||||||
| Platform.runLater(() -> messageInput.clear()); | ||||||||||||
| Platform.runLater(() -> messageInput.requestFocus()); | ||||||||||||
| } else { | ||||||||||||
| Platform.runLater(() -> { | ||||||||||||
| Alert alert = new Alert(Alert.AlertType.ERROR); | ||||||||||||
| alert.setTitle("Send Failed"); | ||||||||||||
| alert.setHeaderText("Failed to send message"); | ||||||||||||
| alert.setContentText("Could not send your message. Please try again."); | ||||||||||||
| alert.showAndWait(); | ||||||||||||
| }); | ||||||||||||
| } | ||||||||||||
| }); | ||||||||||||
| } | ||||||||||||
|
|
||||||||||||
| @FXML | ||||||||||||
| private void changeTopic(ActionEvent actionEvent) { | ||||||||||||
| String newTopic = topicInput.getText(); | ||||||||||||
| if (newTopic != null && !newTopic.isBlank()) { | ||||||||||||
| model.setCurrentTopic(newTopic); | ||||||||||||
| topicInput.clear(); | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| } | ||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,15 +1,109 @@ | ||||||||||||||||||||||||||||
| 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.util.function.Consumer; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import static com.example.FxUtils.runOnFx; | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Model layer: encapsulates application data and business logic. | ||||||||||||||||||||||||||||
| * Model layer for the chatapp RuneChat. | ||||||||||||||||||||||||||||
| * <p> | ||||||||||||||||||||||||||||
| * Manages messages, the current topic, and sending/receiving messages via NtfyConnection. | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public class HelloModel { | ||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||
| * Returns a greeting based on the current Java and JavaFX versions. | ||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| private final NtfyConnection connection; | ||||||||||||||||||||||||||||
| private final ObservableList<NtfyMessageDto> messages = FXCollections.observableArrayList(); | ||||||||||||||||||||||||||||
| private final StringProperty messageToSend = new SimpleStringProperty(); | ||||||||||||||||||||||||||||
| private final StringProperty currentTopic = new SimpleStringProperty(); | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public HelloModel(NtfyConnection connection) { | ||||||||||||||||||||||||||||
| this.connection = connection; | ||||||||||||||||||||||||||||
| this.currentTopic.set(connection.getCurrentTopic()); | ||||||||||||||||||||||||||||
| receiveMessage(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+26
to
+30
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid starting async operations in the constructor. Line 23 calls Consider refactoring to separate construction from initialization: public HelloModel(NtfyConnection connection) {
this.connection = connection;
this.currentTopic.set(connection.getCurrentTopic());
- receiveMessage();
}
+
+ public void start() {
+ receiveMessage();
+ }Then call 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public ObservableList<NtfyMessageDto> getMessages() { | ||||||||||||||||||||||||||||
| return messages; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public String getMessageToSend() { | ||||||||||||||||||||||||||||
| return messageToSend.get(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public StringProperty messageToSendProperty() { | ||||||||||||||||||||||||||||
| return messageToSend; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public void setMessageToSend(String message) { | ||||||||||||||||||||||||||||
| messageToSend.set(message); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public String getCurrentTopic() { | ||||||||||||||||||||||||||||
| return currentTopic.get(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public StringProperty currentTopicProperty() { | ||||||||||||||||||||||||||||
| return currentTopic; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public void setCurrentTopic(String topic) { | ||||||||||||||||||||||||||||
| if (topic != null && !topic.isBlank()) { | ||||||||||||||||||||||||||||
| connection.setCurrentTopic(topic); | ||||||||||||||||||||||||||||
| this.currentTopic.set(topic); | ||||||||||||||||||||||||||||
| messages.clear(); | ||||||||||||||||||||||||||||
| receiveMessage(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
Comment on lines
+56
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential duplicate message listeners when changing topics. Line 55 calls Consider implementing a mechanism to:
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public String getUserId() { | ||||||||||||||||||||||||||||
| return connection.getUserId(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public String getGreeting() { | ||||||||||||||||||||||||||||
| String javaVersion = System.getProperty("java.version"); | ||||||||||||||||||||||||||||
| String javafxVersion = System.getProperty("javafx.version"); | ||||||||||||||||||||||||||||
| return "Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + "."; | ||||||||||||||||||||||||||||
| return "RuneChat"; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public boolean canSendMessage() { | ||||||||||||||||||||||||||||
| String msg = messageToSend.get(); | ||||||||||||||||||||||||||||
| return msg != null && !msg.isBlank(); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public void sendMessageAsync(Consumer<Boolean> callback) { | ||||||||||||||||||||||||||||
| String msg = messageToSend.get(); | ||||||||||||||||||||||||||||
| if (msg == null || msg.isBlank()) { | ||||||||||||||||||||||||||||
| System.out.println("Nothing to send!"); | ||||||||||||||||||||||||||||
| callback.accept(false); | ||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| connection.send(msg, success -> { | ||||||||||||||||||||||||||||
| if (success) { | ||||||||||||||||||||||||||||
| runOnFx(() -> { | ||||||||||||||||||||||||||||
| if (msg.equals(messageToSend.get())) { | ||||||||||||||||||||||||||||
| messageToSend.set(""); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| callback.accept(true); | ||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||
| System.out.println("Failed to send message!"); | ||||||||||||||||||||||||||||
| callback.accept(false); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| public void receiveMessage() { | ||||||||||||||||||||||||||||
| connection.receive(m -> { | ||||||||||||||||||||||||||||
| if (m == null || m.message() == null || m.message().isBlank()) return; | ||||||||||||||||||||||||||||
| runOnFx(() -> messages.add(m)); | ||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Beta version of WireMock in use.
You're using WireMock
4.0.0-beta.15, which is a pre-release version. While this is test-scoped, beta versions may have stability issues or breaking changes. Consider whether a stable release would be more appropriate for your testing needs.🌐 Web query:
💡 Result:
The latest stable WireMock for Maven is org.wiremock:wiremock version 3.13.1 (released June 11, 2025). [1][2]
Maven dependency:
org.wiremock
wiremock
3.13.1
test
Sources: [1] GitHub releases — WireMock 3.13.1. [2] Maven Central / MVNRepository entry for org.wiremock:wiremock 3.13.1.
Consider upgrading to stable WireMock 3.13.1 for improved stability.
You're using WireMock
4.0.0-beta.15, which is a pre-release version. The latest stable version is WireMock 3.13.1 (released June 11, 2025). While beta versions may be intentional for forward compatibility testing, they can introduce stability issues and breaking changes. Upgrading to the stable release would provide better reliability for your test suite.🤖 Prompt for AI Agents