-
Notifications
You must be signed in to change notification settings - Fork 66
Feature/message input #18
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
Changes from all commits
580a773
355bd72
dc4902f
c40b881
fbe8512
a87220b
b2c4ba8
24a48b9
0be71a4
2277cc9
160076d
0784bee
f79e183
5f17e8b
e182c0b
06d034f
a882f7f
a998140
a6c7037
2b1c16f
43a30fb
2963b54
6543c26
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,4 @@ | ||
| target/ | ||
| /.idea/ | ||
| .env | ||
| package-lock.json |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,86 @@ | ||
| package com.example; | ||
|
|
||
| import javafx.event.ActionEvent; | ||
| import javafx.fxml.FXML; | ||
| import javafx.scene.control.Label; | ||
| import javafx.geometry.Insets; | ||
| import javafx.scene.control.*; | ||
| import javafx.scene.layout.HBox; | ||
|
|
||
| /** | ||
| * Controller layer: mediates between the view (FXML) and the model. | ||
| */ | ||
| public class HelloController { | ||
|
|
||
| private final HelloModel model = new HelloModel(); | ||
| private final HelloModel model = new HelloModel(new NtfyConnectionImpl()); | ||
| public ListView<NtfyMessageDto> messageView; | ||
|
|
||
| @FXML | ||
| private Label messageLabel; | ||
|
|
||
| @FXML | ||
| private TextField messageField; | ||
|
|
||
| @FXML | ||
| private void initialize() { | ||
| if (messageLabel != null) { | ||
| messageLabel.setText(model.getGreeting()); | ||
| } | ||
| messageView.setItems(model.getMessages()); | ||
|
|
||
| messageView.setCellFactory(list -> new ListCell<>() { | ||
| private final Label messageLabel = new Label(); | ||
| private final HBox bubble = new HBox(messageLabel); | ||
| { | ||
| bubble.setPadding(new Insets(5, 10, 5, 10)); | ||
| bubble.setMaxWidth(200); | ||
| messageLabel.setWrapText(true); | ||
| bubble.getStyleClass().add("chat-bubble"); | ||
| } | ||
|
|
||
| @Override | ||
| protected void updateItem(NtfyMessageDto item, boolean empty) { | ||
| super.updateItem(item, empty); | ||
| if (empty || item == null) { | ||
| setGraphic(null); | ||
| } else { | ||
| // Format tid + text | ||
| java.time.LocalTime time = java.time.Instant.ofEpochSecond(item.time()) | ||
| .atZone(java.time.ZoneId.systemDefault()) | ||
| .toLocalTime(); | ||
| String formattedTime = time.format(java.time.format.DateTimeFormatter.ofPattern("HH:mm")); | ||
|
|
||
| messageLabel.setText(formattedTime + "\n" + item.message()); | ||
| setGraphic(bubble); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| model.messageToSendProperty().bind(messageField.textProperty()); | ||
|
|
||
| } | ||
|
|
||
| public void sendMessage(ActionEvent actionEvent) { | ||
| String message = messageField.getText(); | ||
| if(message == null || message.isBlank()){ | ||
| showTemporaryAlert("You must write something before sending!"); | ||
| return; | ||
| } | ||
|
|
||
| model.sendMessage(); | ||
| messageField.clear(); | ||
| } | ||
|
|
||
| private void showTemporaryAlert(String alertMessage) { | ||
| Alert alert = new Alert(Alert.AlertType.WARNING); | ||
| alert.setHeaderText(null); | ||
| alert.setContentText(alertMessage); | ||
| alert.initOwner(messageField.getScene().getWindow()); | ||
|
|
||
| alert.show(); | ||
|
|
||
| new Thread(() -> { | ||
| try { | ||
| Thread.sleep(2000); | ||
| } catch (InterruptedException e) {} | ||
| }).start(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,55 @@ | ||
| 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; | ||
|
|
||
|
|
||
| /** | ||
| * Model layer: encapsulates application data and business logic. | ||
| */ | ||
| public class HelloModel { | ||
|
|
||
| private final NtfyConnection connection; | ||
|
|
||
| private final ObservableList<NtfyMessageDto> messages = FXCollections.observableArrayList(); | ||
| private final StringProperty messageToSend = new SimpleStringProperty(); | ||
|
|
||
| public HelloModel(NtfyConnection connection) { | ||
| this.connection = connection; | ||
| receiveMessage(); | ||
| } | ||
|
|
||
| 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); | ||
| } | ||
|
|
||
| /** | ||
| * 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 + "."; | ||
| return "Chat Client by Adam"; | ||
| } | ||
|
|
||
| public void sendMessage() { | ||
| connection.send(messageToSend.get()); | ||
|
|
||
| } | ||
|
|
||
| public void receiveMessage() { | ||
| connection.receive(m->Platform.runLater(()->messages.add(m))); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,17 @@ | ||||||||||||||||||||||||||||||||||
| package com.example; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| public class ManyParameters { | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| public ManyParameters(String computerName, int timeout, String method, int size, byte[] data){ | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+5
to
+7
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. Constructor body is empty—parameters are not stored. The constructor accepts five parameters but doesn't assign them to any fields or perform any operations. This means instances of If this class is meant to store these parameters, add corresponding fields and assignments: public class ManyParameters {
+ private final String computerName;
+ private final int timeout;
+ private final String method;
+ private final int size;
+ private final byte[] data;
+
public ManyParameters(String computerName, int timeout, String method, int size, byte[] data){
-
+ this.computerName = computerName;
+ this.timeout = timeout;
+ this.method = method;
+ this.size = size;
+ this.data = data;
}
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| static void main(){ | ||||||||||||||||||||||||||||||||||
| ManyParametersBuilder builder = new ManyParametersBuilder(); | ||||||||||||||||||||||||||||||||||
| builder | ||||||||||||||||||||||||||||||||||
| .setComputerName("localhost") // Fluent API | ||||||||||||||||||||||||||||||||||
| .setTimeout(10) | ||||||||||||||||||||||||||||||||||
| .setSize(0) | ||||||||||||||||||||||||||||||||||
| .createManyParameters(); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+9
to
+16
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. Non-standard main method signature. The Apply this diff to make it a valid entry point: - static void main(){
+ public static void main(String[] args){
ManyParametersBuilder builder = new ManyParametersBuilder();📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| package com.example; | ||
|
|
||
| public class ManyParametersBuilder { | ||
| private String computerName; | ||
| private int timeout = 0; | ||
| private String method; | ||
| private int size = 0; | ||
| private byte[] data = null; | ||
|
|
||
| public ManyParametersBuilder setComputerName(String computerName) { | ||
| this.computerName = computerName; | ||
| return this; | ||
| } | ||
|
|
||
| public ManyParametersBuilder setTimeout(int timeout) { | ||
| this.timeout = timeout; | ||
| return this; | ||
| } | ||
|
|
||
| public ManyParametersBuilder setMethod(String method) { | ||
| this.method = method; | ||
| return this; | ||
| } | ||
|
|
||
| public ManyParametersBuilder setSize(int size) { | ||
| this.size = size; | ||
| return this; | ||
| } | ||
|
|
||
| public ManyParametersBuilder setData(byte[] data) { | ||
| this.data = data; | ||
| return this; | ||
| } | ||
|
|
||
| public ManyParameters createManyParameters() { | ||
| return new ManyParameters(computerName, timeout, method, size, data); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.example; | ||
|
|
||
| import java.util.function.Consumer; | ||
|
|
||
| public interface NtfyConnection { | ||
| public boolean send(String message); | ||
|
|
||
| public void receive(Consumer<NtfyMessageDto> messageHandler); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,62 @@ | ||||||||||||||||||||||||||||||||||||
| package com.example; | ||||||||||||||||||||||||||||||||||||
| import io.github.cdimascio.dotenv.Dotenv; | ||||||||||||||||||||||||||||||||||||
| import tools.jackson.databind.ObjectMapper; | ||||||||||||||||||||||||||||||||||||
| 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.util.Objects; | ||||||||||||||||||||||||||||||||||||
| 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(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| NtfyConnectionImpl(){ | ||||||||||||||||||||||||||||||||||||
| Dotenv dotenv = Dotenv.load(); | ||||||||||||||||||||||||||||||||||||
| hostName = Objects.requireNonNull(dotenv.get("HOST_NAME")); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| public NtfyConnectionImpl(String hostName){ | ||||||||||||||||||||||||||||||||||||
| this.hostName = hostName; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||
| public boolean send(String message) { | ||||||||||||||||||||||||||||||||||||
| HttpRequest httpRequest = HttpRequest.newBuilder() | ||||||||||||||||||||||||||||||||||||
| .POST(HttpRequest.BodyPublishers.ofString(message)) | ||||||||||||||||||||||||||||||||||||
| .uri(URI.create(hostName + "/mytopic")) | ||||||||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| // TODO: handle long blocking send requests to not freeze the JavaFX thread | ||||||||||||||||||||||||||||||||||||
| // 1. Use thread send message? | ||||||||||||||||||||||||||||||||||||
| // 2. Use async? | ||||||||||||||||||||||||||||||||||||
| var response = http.send(httpRequest, HttpResponse.BodyHandlers.ofString()); | ||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||
| } catch (IOException e) { | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
+39
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. Return false when the server rejects the message. - var response = http.send(httpRequest, HttpResponse.BodyHandlers.ofString());
- return true;
+ var response = http.send(httpRequest, HttpResponse.BodyHandlers.ofString());
+ int status = response.statusCode();
+ return status >= 200 && status < 300;🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| System.out.println("Error sending message"); | ||||||||||||||||||||||||||||||||||||
| } catch (InterruptedException e) { | ||||||||||||||||||||||||||||||||||||
| System.out.println("Interrupted sending message"); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||
| public void receive(Consumer<NtfyMessageDto> messageHandler) { | ||||||||||||||||||||||||||||||||||||
| HttpRequest httpRequest = HttpRequest.newBuilder() | ||||||||||||||||||||||||||||||||||||
| .GET() | ||||||||||||||||||||||||||||||||||||
| .uri(URI.create(hostName + "/mytopic/json?since=wBuD2KGEaAe0")) | ||||||||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| http.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofLines()) | ||||||||||||||||||||||||||||||||||||
| .thenAccept(response -> response.body() | ||||||||||||||||||||||||||||||||||||
| .map(s-> | ||||||||||||||||||||||||||||||||||||
| mapper.readValue(s, NtfyMessageDto.class)) | ||||||||||||||||||||||||||||||||||||
| .filter(message -> message.event().equals("message")) | ||||||||||||||||||||||||||||||||||||
| .peek(System.out::println) | ||||||||||||||||||||||||||||||||||||
| .forEach(messageHandler)); | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+55
to
+60
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. Handle checked exceptions inside the stream pipeline. - .map(s->
- mapper.readValue(s, NtfyMessageDto.class))
+ .map(s -> {
+ try {
+ return mapper.readValue(s, NtfyMessageDto.class);
+ } catch (IOException e) {
+ throw new UncheckedIOException("Failed to parse Ntfy message", e);
+ }
+ })📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| 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 id, long time, String event, String topic, String message) {} |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,13 @@ | ||||||||||
| package com.example; | ||||||||||
|
|
||||||||||
| public class Singelton { | ||||||||||
| private final static Singelton instance = new Singelton(); | ||||||||||
|
Comment on lines
+3
to
+4
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. Fix the class name typo: "Singelton" should be "Singleton". The class name contains a spelling error that affects the class name, filename, and all type references throughout the codebase. This impacts code professionalism and maintainability. Apply this diff to correct the spelling: -public class Singelton {
- private final static Singelton instance = new Singelton();
+public class Singleton {
+ private final static Singleton instance = new Singleton();Also rename the file from 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| private Singelton() { | ||||||||||
|
|
||||||||||
| } | ||||||||||
|
|
||||||||||
| public static Singelton getInstance() { | ||||||||||
| return instance; | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
Comment on lines
+3
to
+13
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 the purpose and usage of this class. This singleton class appears to be unused and unrelated to the PR objectives (adding message input functionality). The class has no state or behavior, and according to the PR summary, no other files reference it. Run the following script to verify if this class is referenced anywhere in the codebase: If the class is indeed unused, consider removing it to keep the codebase clean and aligned with YAGNI principles. If it's intended for future use, please add a comment explaining its purpose or defer its addition until it's actually needed. 🏁 Script executed: #!/bin/bash
# Description: Search for any references to Singelton class in the codebase
# Search for class usage (accounting for the typo)
rg -n --type=java -C3 '\bSingelton\b'Length of output: 784 Remove this unused class. The verification confirms that Additionally, note the typo in the class name: "Singelton" should be "Singleton". 🤖 Prompt for AI Agents |
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,22 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <?import javafx.scene.layout.StackPane?> | ||
| <?import javafx.scene.control.Label?> | ||
|
|
||
| <StackPane xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.example.HelloController"> | ||
| <children> | ||
| <Label fx:id="messageLabel" text="Hello, JavaFX!" /> | ||
| </children> | ||
| </StackPane> | ||
|
|
||
| <?import javafx.scene.control.*?> | ||
| <?import javafx.scene.layout.*?> | ||
|
|
||
| <?import javafx.geometry.Insets?> | ||
| <VBox alignment="CENTER" stylesheets="@style.css" xmlns="http://javafx.com/javafx/17.0.12" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.HelloController"> | ||
|
|
||
| <Label fx:id="messageLabel" text="Adams Chat Client" /> | ||
| <ListView fx:id="messageView" /> | ||
|
|
||
|
|
||
| <HBox alignment="CENTER_RIGHT" spacing="5" prefHeight="40" maxWidth="600"> | ||
| <VBox.margin> | ||
| <Insets top="10" left="10" right="10" bottom="10" /> | ||
| </VBox.margin> | ||
|
|
||
| <TextField fx:id="messageField" prefHeight="40" promptText="Write something here.." HBox.hgrow="ALWAYS" /> | ||
| <Button onAction="#sendMessage" prefHeight="40" prefWidth="80" text="Send" /> | ||
| </HBox> | ||
|
|
||
| </VBox> |
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.
Fix binding to avoid IllegalStateException.
Line 57 binds the model's
messageToSendproperty to theTextField, turning it into a read-only binding. Any later call toHelloModel.setMessageToSend(...)will now throwRuntimeException: A bound value cannot be setonce the view is initialized. Use a bidirectional binding so both the control and model can update the value safely.📝 Committable suggestion
🤖 Prompt for AI Agents