|
1 | 1 | package com.example; |
2 | 2 |
|
3 | | -import com.example.client.ChatNetworkClient; |
4 | | -import com.example.domain.ChatModel; |
5 | | -import com.example.domain.NtfyEventResponse; |
6 | | -import com.example.domain.NtfyMessage; |
7 | | -import javafx.animation.KeyFrame; |
8 | | -import javafx.animation.Timeline; |
9 | | -import javafx.event.ActionEvent; |
10 | 3 | import javafx.fxml.FXML; |
11 | | -import javafx.scene.control.*; |
12 | 4 | import javafx.scene.control.Label; |
13 | | -import javafx.scene.control.TextField; |
14 | | -import javafx.scene.image.Image; |
15 | | -import javafx.scene.image.ImageView; |
16 | | -import javafx.scene.layout.VBox; |
17 | | -import javafx.stage.FileChooser; |
18 | | -import org.slf4j.Logger; |
19 | | -import org.slf4j.LoggerFactory; |
20 | | - |
21 | | -import java.awt.*; |
22 | | -import java.io.File; |
23 | | -import java.io.IOException; |
24 | | -import java.net.URI; |
25 | | -import java.net.URISyntaxException; |
26 | | -import java.time.Instant; |
27 | | -import java.time.LocalTime; |
28 | | -import java.time.ZoneId; |
29 | | -import java.util.Arrays; |
30 | | -import java.util.List; |
31 | | -import java.util.UUID; |
32 | 5 |
|
| 6 | +/** |
| 7 | + * Controller layer: mediates between the view (FXML) and the model. |
| 8 | + */ |
33 | 9 | public class HelloController { |
34 | | - private static final Logger log = LoggerFactory.getLogger(HelloController.class); |
35 | | - private ChatNetworkClient client; |
36 | | - private String baseUrl; |
37 | | - private String topic; |
38 | | - private File selectedFile = null; |
39 | | - |
40 | | - @FXML |
41 | | - private Label messageLabel; |
42 | | - |
43 | | - @FXML |
44 | | - private ListView<NtfyEventResponse> messagesList; |
45 | | - |
46 | | - @FXML |
47 | | - private TextField messageInput; |
48 | | - |
49 | | - @FXML |
50 | | - private TextField titleInput; |
51 | | - |
52 | | - @FXML |
53 | | - private TextField tagsInput; |
54 | | - |
55 | | - public void setClient(ChatNetworkClient client, String baseUrl, String topic) { |
56 | | - this.client = client; |
57 | | - this.baseUrl = baseUrl; |
58 | | - this.topic = topic; |
59 | | - } |
60 | | - |
61 | | - public void setModel(ChatModel model) { |
62 | | - messagesList.setItems(model.getMessages()); |
63 | | - messagesList.setCellFactory(list -> new MessageCell()); |
64 | | - } |
65 | | - |
66 | | - private static String formatTime(long epochSeconds) { |
67 | | - Instant instant = Instant.ofEpochSecond(epochSeconds); |
68 | | - LocalTime time = LocalTime.ofInstant(instant, ZoneId.systemDefault()); |
69 | | - return time.toString(); |
70 | | - } |
71 | | - |
72 | | - private void showStatus(String text) { |
73 | | - messageLabel.setText(text); |
74 | | - Timeline t = new Timeline(new KeyFrame(javafx.util.Duration.seconds(3), |
75 | | - ev -> messageLabel.setText(""))); |
76 | | - t.setCycleCount(1); |
77 | | - t.play(); |
78 | | - } |
79 | 10 |
|
| 11 | + private final HelloModel model = new HelloModel(); |
80 | 12 |
|
81 | 13 | @FXML |
82 | | - private void onPickAttachment() { |
83 | | - FileChooser chooser = new FileChooser(); |
84 | | - chooser.setTitle("Select attachment"); |
85 | | - File file = chooser.showOpenDialog(messageInput.getScene().getWindow()); |
86 | | - |
87 | | - if (file != null) { |
88 | | - selectedFile = file; |
89 | | - messageLabel.setText("Attachment selected: " + file.getName()); |
90 | | - } |
91 | | - } |
| 14 | + private Label messageLabel; |
92 | 15 |
|
93 | 16 | @FXML |
94 | | - private void onSend() { |
95 | | - String txt = messageInput.getText(); |
96 | | - |
97 | | - if ((txt == null || txt.isBlank()) && selectedFile == null) { |
98 | | - showStatus("Nothing to send"); |
99 | | - return; |
100 | | - } |
101 | | - |
102 | | - String title = titleInput.getText(); |
103 | | - if (title != null && title.isBlank()) title = null; |
104 | | - |
105 | | - String tagsRaw = tagsInput.getText(); |
106 | | - List<String> tags = null; |
107 | | - |
108 | | - if (tagsRaw != null && !tagsRaw.isBlank()) { |
109 | | - tags = java.util.Arrays.stream(tagsRaw.split(",")) |
110 | | - .map(String::trim) |
111 | | - .filter(s -> !s.isEmpty()) |
112 | | - .toList(); |
113 | | - } |
114 | | - |
115 | | - NtfyMessage msg = new NtfyMessage.Builder() |
116 | | - .id(UUID.randomUUID().toString()) |
117 | | - .time(System.currentTimeMillis()) |
118 | | - .event("message") |
119 | | - .topic(topic) |
120 | | - .message(txt) |
121 | | - .title(title) |
122 | | - .tags(tags) |
123 | | - .attach(null) |
124 | | - .filename(null) |
125 | | - .build(); |
126 | | - |
127 | | - try { |
128 | | - client.send(baseUrl, msg, selectedFile); |
129 | | - showStatus(selectedFile == null ? "Message sent" : "Attachment sent"); |
130 | | - } catch (InterruptedException | IOException e) { |
131 | | - showStatus("Error sending: " + e.getMessage()); |
132 | | - } |
133 | | - |
134 | | - messageInput.clear(); |
135 | | - titleInput.clear(); |
136 | | - tagsInput.clear(); |
137 | | - selectedFile = null; |
138 | | - } |
139 | | - |
140 | | - |
141 | | - private static final class MessageCell extends ListCell<NtfyEventResponse> { |
142 | | - @Override |
143 | | - protected void updateItem(NtfyEventResponse msg, boolean empty) { |
144 | | - super.updateItem(msg, empty); |
145 | | - |
146 | | - if (empty || msg == null) { |
147 | | - setText(null); |
148 | | - setGraphic(null); |
149 | | - return; |
150 | | - } |
151 | | - |
152 | | - setText(null); |
153 | | - |
154 | | - VBox container = new VBox(); |
155 | | - container.setSpacing(6); |
156 | | - container.getStyleClass().add("message-bubble"); |
157 | | - |
158 | | - container.setStyle("-fx-alignment: CENTER_LEFT;"); |
159 | | - if (msg.title() != null) { |
160 | | - Label titleLabel = new Label(msg.title()); |
161 | | - titleLabel.getStyleClass().add("message-title"); |
162 | | - container.getChildren().add(titleLabel); |
163 | | - } |
164 | | - |
165 | | - if (msg.attachment() != null) { |
166 | | - NtfyEventResponse.Attachment att = msg.attachment(); |
167 | | - |
168 | | - if (att.type() != null && att.type().startsWith("image")) { |
169 | | - Image image = new Image(att.url(), 300, 0, true, true); |
170 | | - ImageView imageView = new ImageView(image); |
171 | | - container.getChildren().add(imageView); |
172 | | - } else { |
173 | | - Label fileLabel = getFileLabel(att); |
174 | | - container.getChildren().add(fileLabel); |
175 | | - } |
176 | | - } |
177 | | - |
178 | | - if (msg.message() != null && !msg.message().isBlank()) { |
179 | | - Label messageLabel = new Label(msg.message()); |
180 | | - messageLabel.setWrapText(true); |
181 | | - messageLabel.getStyleClass().add("message-text"); |
182 | | - container.getChildren().add(messageLabel); |
183 | | - } |
184 | | - |
185 | | - if (msg.tags() != null && !msg.tags().isEmpty()) { |
186 | | - Label tagsLabel = new Label(String.join(", ", msg.tags())); |
187 | | - tagsLabel.getStyleClass().add("message-tags"); |
188 | | - container.getChildren().add(tagsLabel); |
189 | | - } |
190 | | - |
191 | | - if (msg.time() != null) { |
192 | | - Label timeLabel = new Label(formatTime(msg.time())); |
193 | | - timeLabel.getStyleClass().add("message-time"); |
194 | | - container.getChildren().add(timeLabel); |
195 | | - } |
196 | | - |
197 | | - setGraphic(container); |
198 | | - } |
199 | | - |
200 | | - // Helper method to allow user to open file |
201 | | - private static Label getFileLabel(NtfyEventResponse.Attachment att) { |
202 | | - Label fileLabel = new Label("Open file: " + (att.name() != null ? att.name() : att.url())); |
203 | | - fileLabel.setStyle("-fx-text-fill: #2c75ff; -fx-underline: true;"); |
204 | | - fileLabel.setOnMouseClicked(ev -> { |
205 | | - try { |
206 | | - String url = att.url(); |
207 | | - |
208 | | - // method that works on linux as Desktop is not always supported and crashes application |
209 | | - if (System.getProperty("os.name").toLowerCase().contains("linux")) { |
210 | | - try { |
211 | | - new ProcessBuilder("xdg-open", url).start(); |
212 | | - return; |
213 | | - }catch (IOException e) { |
214 | | - log.error("Error opening file: {}", url, e); |
215 | | - } |
216 | | - } |
217 | | - |
218 | | - if (Desktop.isDesktopSupported()) { |
219 | | - Desktop.getDesktop().browse(new URI(url)); |
220 | | - } |
221 | | - |
222 | | - } catch (IOException | URISyntaxException ex) { |
223 | | - log.error("Failed to open attachment: {}", ex.getMessage()); |
224 | | - log.error(Arrays.toString(ex.getStackTrace())); |
225 | | - } |
226 | | - }); |
227 | | - return fileLabel; |
| 17 | + private void initialize() { |
| 18 | + if (messageLabel != null) { |
| 19 | + messageLabel.setText(model.getGreeting()); |
228 | 20 | } |
229 | 21 | } |
230 | 22 | } |
0 commit comments