11package com .example ;
22
33import javafx .application .Platform ;
4+ import javafx .beans .binding .Bindings ;
45import javafx .event .ActionEvent ;
56import javafx .fxml .FXML ;
7+ import javafx .geometry .Insets ;
8+ import javafx .geometry .Pos ;
69import javafx .scene .control .*;
7- import java .time .Instant ;
10+ import javafx .scene .layout .HBox ;
11+
812import java .time .ZoneId ;
913import java .time .format .DateTimeFormatter ;
1014
15+ /**
16+ * Controller layer: mediates between the view (FXML) and the model.
17+ */
18+
1119public class HelloController {
1220
1321 private final HelloModel model = new HelloModel (new NtfyConnectionImpl ());
1422
15- @ FXML private Label messageLabel ;
16- @ FXML private ListView <NtfyMessageDto > messageView ;
17- @ FXML private TextArea messageInput ;
23+ @ FXML
24+ private Button sendButton ;
25+
26+ @ FXML
27+ private Label messageLabel ;
28+
29+ @ FXML
30+ private Label topicLabel ;
31+
32+ @ FXML
33+ private ListView <NtfyMessageDto > messageView ;
34+
35+ @ FXML
36+ private TextArea messageInput ;
37+
38+ @ FXML
39+ private TextField topicInput ;
40+
41+ @ FXML
42+ private Button changeTopicButton ;
1843
1944 private final DateTimeFormatter timeFormatter =
20- DateTimeFormatter .ofPattern ("HH:mm:ss" ).withZone (ZoneId .systemDefault ());
45+ DateTimeFormatter .ofPattern ("HH:mm:ss" )
46+ .withZone (ZoneId .systemDefault ());
2147
2248 @ FXML
2349 private void initialize () {
24- //visa välkomstmeddelande
2550 messageLabel .setText (model .getGreeting ());
2651
52+ Platform .runLater (() -> messageInput .requestFocus ());
53+
54+ topicLabel .setText ("/" + model .getCurrentTopic ());
55+ model .currentTopicProperty ().addListener ((obs , oldVal , newVal ) -> {
56+ topicLabel .setText ("/" + newVal );
57+ });
58+
2759 messageView .setItems (model .getMessages ());
2860
2961 messageInput .textProperty ().bindBidirectional (model .messageToSendProperty ());
3062
31- //formatera meddelanden i ListView
63+ sendButton .disableProperty ().bind (Bindings .createBooleanBinding (
64+ () -> {
65+ String text = messageInput .getText ();
66+ return text == null || text .trim ().isEmpty ();
67+ },
68+ messageInput .textProperty ()
69+ ));
70+
71+ if (changeTopicButton != null ) {
72+ changeTopicButton .disableProperty ().bind (Bindings .createBooleanBinding (
73+ () -> {
74+ String text = topicInput .getText ();
75+ return text == null || text .trim ().isEmpty ();
76+ },
77+ topicInput .textProperty ()
78+ ));
79+ }
80+
81+
3282 messageView .setCellFactory (lv -> new ListCell <>() {
3383 @ Override
3484 protected void updateItem (NtfyMessageDto msg , boolean empty ) {
3585 super .updateItem (msg , empty );
36- if (empty || msg == null ) {
86+
87+ if (empty || msg == null || msg .message () == null || msg .message ().isBlank ()) {
3788 setText (null );
89+ setGraphic (null );
3890 } else {
39- setText ("[" + timeFormatter .format (Instant .ofEpochMilli (msg .time ())) + "] " + msg .message ());
91+ // Skapa bubble-label
92+ Label bubble = new Label (msg .message ());
93+ bubble .setWrapText (true );
94+ bubble .setMaxWidth (250 );
95+ bubble .setPadding (new Insets (10 ));
96+ bubble .getStyleClass ().add ("chat-bubble" ); // Basstyle
97+
98+ HBox container = new HBox (bubble );
99+ container .setPadding (new Insets (5 ));
100+
101+ // Använd CSS-klasser för skickat/mottaget
102+ if (model .getUserId ().equals (msg .id ())) {
103+ bubble .getStyleClass ().add ("chat-bubble-sent" );
104+ container .setAlignment (Pos .CENTER_RIGHT );
105+ } else {
106+ bubble .getStyleClass ().add ("chat-bubble-received" );
107+ container .setAlignment (Pos .CENTER_LEFT );
108+ }
109+
110+ setText (null );
111+ setGraphic (container );
40112 }
41113 }
42114 });
43115
44- // Scrolla automatiskt till senaste meddelandet
116+
117+ // Scrolla ner till senaste meddelandet
45118 model .getMessages ().addListener ((javafx .collections .ListChangeListener <NtfyMessageDto >) change -> {
46- while (change .next ()) {
47- if (change .wasAdded ()) {
48- Platform .runLater (() -> {
49- int size = messageView .getItems ().size ();
50- if (size > 0 ) {
51- messageView .scrollTo (size - 1 );
52- }
53- });
119+ Platform .runLater (() -> {
120+ if (!messageView .getItems ().isEmpty ()) {
121+ messageView .scrollTo (messageView .getItems ().size () - 1 );
54122 }
55- }
123+ });
56124 });
57125 }
58126
59127 @ FXML
60- private void sendMessage (ActionEvent event ) {
61- //skicka asynkront – HelloModel hanterar rensning och callback
128+ private void sendMessage (ActionEvent actionEvent ) {
62129 model .sendMessageAsync (success -> {
63- if (!success ) {
130+ if (success ) {
131+ Platform .runLater (() -> messageInput .clear ());
132+ Platform .runLater (() -> messageInput .requestFocus ());
133+ } else {
64134 Platform .runLater (() -> {
65- Alert alert = new Alert (Alert .AlertType .ERROR , "Kunde inte skicka meddelandet." );
66- alert .show ();
135+ Alert alert = new Alert (Alert .AlertType .ERROR );
136+ alert .setTitle ("Send Failed" );
137+ alert .setHeaderText ("Failed to send message" );
138+ alert .setContentText ("Could not send your message. Please try again." );
139+ alert .showAndWait ();
67140 });
68141 }
69142 });
70143 }
144+
145+ @ FXML
146+ private void changeTopic (ActionEvent actionEvent ) {
147+ String newTopic = topicInput .getText ();
148+ if (newTopic != null && !newTopic .isBlank ()) {
149+ model .setCurrentTopic (newTopic );
150+ topicInput .clear ();
151+ }
152+ }
71153}
0 commit comments