-
Notifications
You must be signed in to change notification settings - Fork 66
Chat client nyast123 #47
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
base: main
Are you sure you want to change the base?
Changes from all commits
729c4ff
895f0b5
e3ebf9e
3133193
0916046
9284a5d
27ea848
0c3274f
7615cbc
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 |
|---|---|---|
| @@ -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<NtfyMessageDto>) 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"); | ||
| } | ||
|
Comment on lines
+60
to
68
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. Missing user feedback on file send failure.
File file = chooser.showOpenDialog(sendFileButton.getScene().getWindow());
if (file != null) {
- model.sendFile(file);
- chatArea.appendText("Du skickade fil: " + file.getName() + "\n");
+ boolean success = model.sendFile(file);
+ if (success) {
+ chatArea.appendText("Du skickade fil: " + file.getName() + "\n");
+ } else {
+ chatArea.appendText("Kunde inte skicka fil: " + file.getName() + "\n");
+ }
}Note: This requires changing
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -10,16 +10,16 @@ public class HelloFX extends Application { | |||
|
|
||||
| @Override | ||||
| public void start(Stage stage) throws Exception { | ||||
| System.getenv("HOST_NAME"); | ||||
|
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. Dead code: The call - System.getenv("HOST_NAME");📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||
| 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(); | ||||
| } | ||||
|
|
||||
| } | ||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,78 @@ | ||
| 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. | ||
| */ | ||
| 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<NtfyMessageDto> 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<NtfyMessageDto> 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)) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<NtfyMessageDto> messageHandler); | ||
|
|
||
| boolean sendFile(Path filePath) throws FileNotFoundException; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+45
to
+52
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. Restore interrupt status when catching When catching } catch (IOException | InterruptedException e) {
+ if (e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
System.out.println("Error sending message!");
return false;
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+38
to
+53
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 chain🏁 Script executed: #!/bin/bash
# 1. First, check the full context of the NtfyConnectionImpl class
cat -n src/main/java/com/example/NtfyConnectionImpl.java 2>/dev/null | head -100Repository: ithsjava25/ithsjava25-java-java-fx-and-network-javafx-classrom Length of output: 268 🌐 Web query:
💡 Result: Short answer
Extra notes (brief)
Sources
Blocking HTTP call may freeze the UI if invoked from the JavaFX Application Thread.
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @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; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+67
to
+73
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. Restore interrupt status in sendFile as well. } catch (IOException | InterruptedException e) {
+ if (e instanceof InterruptedException) {
+ Thread.currentThread().interrupt();
+ }
System.out.println("Error sending file: " + e.getMessage());
return false;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||
| public void receive (Consumer<NtfyMessageDto> 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)); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) { | ||
| } | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,20 @@ | ||
| <?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.geometry.*?> | ||
| <?import javafx.scene.control.*?> | ||
| <?import javafx.scene.layout.*?> | ||
|
|
||
| <VBox spacing="10" style="-fx-background-color: GRAY;" xmlns="http://javafx.com/javafx/17.0.12" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.HelloController"> | ||
|
|
||
| <padding> | ||
| <Insets bottom="10" left="10" right="10" top="10" /> | ||
| </padding> | ||
|
|
||
| <TextArea fx:id="chatArea" editable="false" wrapText="true" prefHeight="400" /> | ||
|
|
||
| <HBox spacing="5"> | ||
| <TextArea fx:id="inputField" prefRowCount="2" promptText="Skriv meddelande..." style="-fx-background-color: WHITE;" wrapText="true" HBox.hgrow="ALWAYS" /> | ||
| <Button fx:id="sendButton" text="Skicka" /> | ||
| <Button fx:id="sendFileButton" text="Bifoga fil"/> | ||
| </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.
Sent messages may appear twice in the chat.
Outgoing messages are appended locally with "Du:" prefix (line 54), but if the server echoes messages back, they will also appear via the
ListChangeListener. Consider filtering out own messages in the listener or not appending locally.🤖 Prompt for AI Agents