-
Notifications
You must be signed in to change notification settings - Fork 66
Initial implementation of JavaFX Chat App with ntfy integration #34
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
4679f9f
efce903
76fea66
e9a9a5f
1a1094b
6aaef56
33f17eb
d91e839
a730854
69e40f6
9ab7852
8e718d1
cc3f95d
ed2f976
c6476ce
585f221
c0bfc48
d7226af
becd5aa
082373e
14c7a72
17ad122
945a5ec
32a53ad
5e1358e
c7c7211
868b2ec
504748d
4879421
e2f6d60
e930949
a45ea5c
c5d06b7
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 |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| NTFY_BASE_URL=https://ntfy.sh | ||
| NTFY_TOPIC= //Add your topic here |
| 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,230 @@ | ||||||||||||||||||||||||||||||||||||||||||||||
| package com.example; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| import com.example.client.ChatNetworkClient; | ||||||||||||||||||||||||||||||||||||||||||||||
| import com.example.domain.ChatModel; | ||||||||||||||||||||||||||||||||||||||||||||||
| import com.example.domain.NtfyEventResponse; | ||||||||||||||||||||||||||||||||||||||||||||||
| import com.example.domain.NtfyMessage; | ||||||||||||||||||||||||||||||||||||||||||||||
| import javafx.animation.KeyFrame; | ||||||||||||||||||||||||||||||||||||||||||||||
| import javafx.animation.Timeline; | ||||||||||||||||||||||||||||||||||||||||||||||
| import javafx.event.ActionEvent; | ||||||||||||||||||||||||||||||||||||||||||||||
| import javafx.fxml.FXML; | ||||||||||||||||||||||||||||||||||||||||||||||
| import javafx.scene.control.*; | ||||||||||||||||||||||||||||||||||||||||||||||
| import javafx.scene.control.Label; | ||||||||||||||||||||||||||||||||||||||||||||||
| import javafx.scene.control.TextField; | ||||||||||||||||||||||||||||||||||||||||||||||
| import javafx.scene.image.Image; | ||||||||||||||||||||||||||||||||||||||||||||||
| import javafx.scene.image.ImageView; | ||||||||||||||||||||||||||||||||||||||||||||||
| import javafx.scene.layout.VBox; | ||||||||||||||||||||||||||||||||||||||||||||||
| import javafx.stage.FileChooser; | ||||||||||||||||||||||||||||||||||||||||||||||
| import org.slf4j.Logger; | ||||||||||||||||||||||||||||||||||||||||||||||
| import org.slf4j.LoggerFactory; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||
| * Controller layer: mediates between the view (FXML) and the model. | ||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||
| public class HelloController { | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.awt.*; | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.File; | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.io.IOException; | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.net.URI; | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.net.URISyntaxException; | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.Instant; | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.LocalTime; | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.ZoneId; | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Arrays; | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.UUID; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| private final HelloModel model = new HelloModel(); | ||||||||||||||||||||||||||||||||||||||||||||||
| public class HelloController { | ||||||||||||||||||||||||||||||||||||||||||||||
| private static final Logger log = LoggerFactory.getLogger(HelloController.class); | ||||||||||||||||||||||||||||||||||||||||||||||
| private ChatNetworkClient client; | ||||||||||||||||||||||||||||||||||||||||||||||
| private String baseUrl; | ||||||||||||||||||||||||||||||||||||||||||||||
| private String topic; | ||||||||||||||||||||||||||||||||||||||||||||||
| private File selectedFile = null; | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+35
to
+38
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. Add null checks to prevent NPE from uninitialized fields. The Consider adding validation: public void setClient(ChatNetworkClient client, String baseUrl, String topic) {
+ if (client == null || baseUrl == null || topic == null) {
+ throw new IllegalArgumentException("Client, baseUrl, and topic must not be null");
+ }
this.client = client;
this.baseUrl = baseUrl;
this.topic = topic;
}And add a safety check in private void onSend() {
+ if (client == null) {
+ showStatus("Client not initialized");
+ return;
+ }
String txt = messageInput.getText();Also applies to: 51-55 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @FXML | ||||||||||||||||||||||||||||||||||||||||||||||
| private Label messageLabel; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @FXML | ||||||||||||||||||||||||||||||||||||||||||||||
| private void initialize() { | ||||||||||||||||||||||||||||||||||||||||||||||
| if (messageLabel != null) { | ||||||||||||||||||||||||||||||||||||||||||||||
| messageLabel.setText(model.getGreeting()); | ||||||||||||||||||||||||||||||||||||||||||||||
| private ListView<NtfyEventResponse> messagesList; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @FXML | ||||||||||||||||||||||||||||||||||||||||||||||
| private TextField messageInput; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @FXML | ||||||||||||||||||||||||||||||||||||||||||||||
| private TextField titleInput; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @FXML | ||||||||||||||||||||||||||||||||||||||||||||||
| private TextField tagsInput; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| public void setClient(ChatNetworkClient client, String baseUrl, String topic) { | ||||||||||||||||||||||||||||||||||||||||||||||
| this.client = client; | ||||||||||||||||||||||||||||||||||||||||||||||
| this.baseUrl = baseUrl; | ||||||||||||||||||||||||||||||||||||||||||||||
| this.topic = topic; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| public void setModel(ChatModel model) { | ||||||||||||||||||||||||||||||||||||||||||||||
| messagesList.setItems(model.getMessages()); | ||||||||||||||||||||||||||||||||||||||||||||||
| messagesList.setCellFactory(list -> new MessageCell()); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| private static String formatTime(long epochSeconds) { | ||||||||||||||||||||||||||||||||||||||||||||||
| Instant instant = Instant.ofEpochSecond(epochSeconds); | ||||||||||||||||||||||||||||||||||||||||||||||
| LocalTime time = LocalTime.ofInstant(instant, ZoneId.systemDefault()); | ||||||||||||||||||||||||||||||||||||||||||||||
| return time.toString(); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| private void showStatus(String text) { | ||||||||||||||||||||||||||||||||||||||||||||||
| messageLabel.setText(text); | ||||||||||||||||||||||||||||||||||||||||||||||
| Timeline t = new Timeline(new KeyFrame(javafx.util.Duration.seconds(3), | ||||||||||||||||||||||||||||||||||||||||||||||
| ev -> messageLabel.setText(""))); | ||||||||||||||||||||||||||||||||||||||||||||||
| t.setCycleCount(1); | ||||||||||||||||||||||||||||||||||||||||||||||
| t.play(); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @FXML | ||||||||||||||||||||||||||||||||||||||||||||||
| private void onPickAttachment() { | ||||||||||||||||||||||||||||||||||||||||||||||
| FileChooser chooser = new FileChooser(); | ||||||||||||||||||||||||||||||||||||||||||||||
| chooser.setTitle("Select attachment"); | ||||||||||||||||||||||||||||||||||||||||||||||
| File file = chooser.showOpenDialog(messageInput.getScene().getWindow()); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (file != null) { | ||||||||||||||||||||||||||||||||||||||||||||||
| selectedFile = file; | ||||||||||||||||||||||||||||||||||||||||||||||
| messageLabel.setText("Attachment selected: " + file.getName()); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| @FXML | ||||||||||||||||||||||||||||||||||||||||||||||
| private void onSend() { | ||||||||||||||||||||||||||||||||||||||||||||||
| String txt = messageInput.getText(); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if ((txt == null || txt.isBlank()) && selectedFile == null) { | ||||||||||||||||||||||||||||||||||||||||||||||
| showStatus("Nothing to send"); | ||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| String title = titleInput.getText(); | ||||||||||||||||||||||||||||||||||||||||||||||
| if (title != null && title.isBlank()) title = null; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| String tagsRaw = tagsInput.getText(); | ||||||||||||||||||||||||||||||||||||||||||||||
| List<String> tags = null; | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (tagsRaw != null && !tagsRaw.isBlank()) { | ||||||||||||||||||||||||||||||||||||||||||||||
| tags = java.util.Arrays.stream(tagsRaw.split(",")) | ||||||||||||||||||||||||||||||||||||||||||||||
| .map(String::trim) | ||||||||||||||||||||||||||||||||||||||||||||||
| .filter(s -> !s.isEmpty()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .toList(); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| NtfyMessage msg = new NtfyMessage.Builder() | ||||||||||||||||||||||||||||||||||||||||||||||
| .id(UUID.randomUUID().toString()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .time(System.currentTimeMillis()) | ||||||||||||||||||||||||||||||||||||||||||||||
| .event("message") | ||||||||||||||||||||||||||||||||||||||||||||||
| .topic(topic) | ||||||||||||||||||||||||||||||||||||||||||||||
| .message(txt) | ||||||||||||||||||||||||||||||||||||||||||||||
| .title(title) | ||||||||||||||||||||||||||||||||||||||||||||||
| .tags(tags) | ||||||||||||||||||||||||||||||||||||||||||||||
| .attach(null) | ||||||||||||||||||||||||||||||||||||||||||||||
| .filename(null) | ||||||||||||||||||||||||||||||||||||||||||||||
| .build(); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+115
to
+125
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. Timestamp unit mismatch: milliseconds vs seconds. Line 113 sets Fix the timestamp to use seconds: NtfyMessage msg = new NtfyMessage.Builder()
.id(UUID.randomUUID().toString())
- .time(System.currentTimeMillis())
+ .time(System.currentTimeMillis() / 1000)
.event("message")
.topic(topic)
.message(txt)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| client.send(baseUrl, msg, selectedFile); | ||||||||||||||||||||||||||||||||||||||||||||||
| showStatus(selectedFile == null ? "Message sent" : "Attachment sent"); | ||||||||||||||||||||||||||||||||||||||||||||||
| } catch (InterruptedException | IOException e) { | ||||||||||||||||||||||||||||||||||||||||||||||
| showStatus("Error sending: " + e.getMessage()); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| messageInput.clear(); | ||||||||||||||||||||||||||||||||||||||||||||||
| titleInput.clear(); | ||||||||||||||||||||||||||||||||||||||||||||||
| tagsInput.clear(); | ||||||||||||||||||||||||||||||||||||||||||||||
| selectedFile = null; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| private static final class MessageCell extends ListCell<NtfyEventResponse> { | ||||||||||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||||||||||
| protected void updateItem(NtfyEventResponse msg, boolean empty) { | ||||||||||||||||||||||||||||||||||||||||||||||
| super.updateItem(msg, empty); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (empty || msg == null) { | ||||||||||||||||||||||||||||||||||||||||||||||
| setText(null); | ||||||||||||||||||||||||||||||||||||||||||||||
| setGraphic(null); | ||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| setText(null); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| VBox container = new VBox(); | ||||||||||||||||||||||||||||||||||||||||||||||
| container.setSpacing(6); | ||||||||||||||||||||||||||||||||||||||||||||||
| container.getStyleClass().add("message-bubble"); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| container.setStyle("-fx-alignment: CENTER_LEFT;"); | ||||||||||||||||||||||||||||||||||||||||||||||
| if (msg.title() != null) { | ||||||||||||||||||||||||||||||||||||||||||||||
| Label titleLabel = new Label(msg.title()); | ||||||||||||||||||||||||||||||||||||||||||||||
| titleLabel.getStyleClass().add("message-title"); | ||||||||||||||||||||||||||||||||||||||||||||||
| container.getChildren().add(titleLabel); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (msg.attachment() != null) { | ||||||||||||||||||||||||||||||||||||||||||||||
| NtfyEventResponse.Attachment att = msg.attachment(); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (att.type() != null && att.type().startsWith("image")) { | ||||||||||||||||||||||||||||||||||||||||||||||
| Image image = new Image(att.url(), 300, 0, true, true); | ||||||||||||||||||||||||||||||||||||||||||||||
| ImageView imageView = new ImageView(image); | ||||||||||||||||||||||||||||||||||||||||||||||
| container.getChildren().add(imageView); | ||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+168
to
+171
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. Add error handling for image loading. Loading an image directly from a URL on line 165 is synchronous and can fail if the URL is invalid or the network is unavailable. This could block the UI thread or cause rendering issues. Consider adding error handling with a fallback: -Image image = new Image(att.url(), 300, 0, true, true);
-ImageView imageView = new ImageView(image);
-container.getChildren().add(imageView);
+try {
+ Image image = new Image(att.url(), 300, 0, true, true, true); // backgroundLoading=true
+ if (!image.isError()) {
+ ImageView imageView = new ImageView(image);
+ container.getChildren().add(imageView);
+ } else {
+ Label errorLabel = new Label("Failed to load image: " + att.name());
+ errorLabel.getStyleClass().add("error-text");
+ container.getChildren().add(errorLabel);
+ }
+} catch (Exception e) {
+ Label errorLabel = new Label("Error loading image: " + e.getMessage());
+ errorLabel.getStyleClass().add("error-text");
+ container.getChildren().add(errorLabel);
+}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||
| Label fileLabel = getFileLabel(att); | ||||||||||||||||||||||||||||||||||||||||||||||
| container.getChildren().add(fileLabel); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (msg.message() != null && !msg.message().isBlank()) { | ||||||||||||||||||||||||||||||||||||||||||||||
| Label messageLabel = new Label(msg.message()); | ||||||||||||||||||||||||||||||||||||||||||||||
| messageLabel.setWrapText(true); | ||||||||||||||||||||||||||||||||||||||||||||||
| messageLabel.getStyleClass().add("message-text"); | ||||||||||||||||||||||||||||||||||||||||||||||
| container.getChildren().add(messageLabel); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (msg.tags() != null && !msg.tags().isEmpty()) { | ||||||||||||||||||||||||||||||||||||||||||||||
| Label tagsLabel = new Label(String.join(", ", msg.tags())); | ||||||||||||||||||||||||||||||||||||||||||||||
| tagsLabel.getStyleClass().add("message-tags"); | ||||||||||||||||||||||||||||||||||||||||||||||
| container.getChildren().add(tagsLabel); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (msg.time() != null) { | ||||||||||||||||||||||||||||||||||||||||||||||
| Label timeLabel = new Label(formatTime(msg.time())); | ||||||||||||||||||||||||||||||||||||||||||||||
| timeLabel.getStyleClass().add("message-time"); | ||||||||||||||||||||||||||||||||||||||||||||||
| container.getChildren().add(timeLabel); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| setGraphic(container); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // Helper method to allow user to open file | ||||||||||||||||||||||||||||||||||||||||||||||
| private static Label getFileLabel(NtfyEventResponse.Attachment att) { | ||||||||||||||||||||||||||||||||||||||||||||||
| Label fileLabel = new Label("Open file: " + (att.name() != null ? att.name() : att.url())); | ||||||||||||||||||||||||||||||||||||||||||||||
| fileLabel.setStyle("-fx-text-fill: #2c75ff; -fx-underline: true;"); | ||||||||||||||||||||||||||||||||||||||||||||||
| fileLabel.setOnMouseClicked(ev -> { | ||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| String url = att.url(); | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| // method that works on linux as Desktop is not always supported and crashes application | ||||||||||||||||||||||||||||||||||||||||||||||
| if (System.getProperty("os.name").toLowerCase().contains("linux")) { | ||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||
| new ProcessBuilder("xdg-open", url).start(); | ||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||
| }catch (IOException e) { | ||||||||||||||||||||||||||||||||||||||||||||||
| log.error("Error opening file: {}", url, e); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| if (Desktop.isDesktopSupported()) { | ||||||||||||||||||||||||||||||||||||||||||||||
| Desktop.getDesktop().browse(new URI(url)); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||
| } catch (IOException | URISyntaxException ex) { | ||||||||||||||||||||||||||||||||||||||||||||||
| log.error("Failed to open attachment: {}", ex.getMessage()); | ||||||||||||||||||||||||||||||||||||||||||||||
| log.error(Arrays.toString(ex.getStackTrace())); | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||
| return fileLabel; | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,25 +1,59 @@ | ||
| package com.example; | ||
|
|
||
| import com.example.client.ChatNetworkClient; | ||
| import com.example.client.NtfyHttpClient; | ||
| import com.example.domain.ChatModel; | ||
| import com.example.domain.NtfyMessage; | ||
| import javafx.application.Application; | ||
| import javafx.fxml.FXMLLoader; | ||
| import javafx.scene.Parent; | ||
| import javafx.scene.Scene; | ||
| import javafx.stage.Stage; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import java.util.Objects; | ||
| import java.util.Properties; | ||
|
|
||
| import static com.example.utils.EnvLoader.loadEnv; | ||
|
|
||
| public class HelloFX extends Application { | ||
| private static final Logger log = LoggerFactory.getLogger("MAIN"); | ||
| static final ChatModel model = new ChatModel(); | ||
|
|
||
| @Override | ||
| public void start(Stage stage) throws Exception { | ||
| 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"); | ||
| Properties env = loadEnv(); | ||
| String baseUrl = env.getProperty("NTFY_BASE_URL", "https://ntfy.sh"); | ||
| String topic = env.getProperty("NTFY_TOPIC"); | ||
|
|
||
| if (topic == null || topic.isBlank()) { | ||
| throw new IllegalStateException("NTFY_TOPIC is not set"); | ||
| } | ||
|
|
||
| FXMLLoader loader = new FXMLLoader(HelloFX.class.getResource("hello-view.fxml")); | ||
| Parent root = loader.load(); | ||
|
|
||
| HelloController controller = loader.getController(); | ||
| controller.setModel(model); | ||
|
|
||
| ChatNetworkClient client = new NtfyHttpClient(model); | ||
| controller.setClient(client, baseUrl, topic); | ||
|
|
||
| client.subscribe(baseUrl, topic); | ||
|
|
||
| Scene scene = new Scene(root); | ||
| scene.getStylesheets().add( | ||
| Objects.requireNonNull(HelloFX.class.getResource("styles.css")).toExternalForm() | ||
| ); | ||
|
|
||
| stage.setScene(scene); | ||
| stage.show(); | ||
| } | ||
|
|
||
| public static void main(String[] args) { | ||
| launch(); | ||
| launch(args); | ||
|
|
||
| } | ||
|
|
||
| } |
Uh oh!
There was an error while loading. Please reload this page.