Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 167 additions & 18 deletions src/main/java/com/example/HelloController.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
package com.example;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.stage.FileChooser;

import java.awt.*;
import java.io.File;
import java.net.URI;

/**
* Controller layer: mediates between the view (FXML) and the model.
Expand All @@ -12,61 +24,164 @@ public class HelloController {

private final HelloModel model = new HelloModel(new NtfyConnectionImpl());
public ListView<NtfyMessageDto> messageView;
private boolean lastActionWasFile = false;

@FXML
private Label messageLabel;

@FXML
private TextField messageField;

@FXML
private void attachFile() {
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(
new FileChooser.ExtensionFilter("Images", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.txt")
);

File file = chooser.showOpenDialog(messageField.getScene().getWindow());
if (file != null) {
lastActionWasFile = true;
model.sendFile(file);
}
}
Comment on lines +36 to +47
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Misleading file filter label and missing error feedback.

Line 39 labels the filter as "Images" but includes *.txt files. Additionally, there's no user feedback if model.sendFile(file) fails (note that the return value issue was flagged in HelloModel.java review).

Apply this diff to fix the label:

         chooser.getExtensionFilters().add(
-                new FileChooser.ExtensionFilter("Images", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.txt")
+                new FileChooser.ExtensionFilter("Images and Text", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.txt")
         );

Or separate the filters:

         chooser.getExtensionFilters().addAll(
-                new FileChooser.ExtensionFilter("Images", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.txt")
+                new FileChooser.ExtensionFilter("Images", "*.png", "*.jpg", "*.jpeg", "*.gif"),
+                new FileChooser.ExtensionFilter("Text Files", "*.txt")
         );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private void attachFile() {
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(
new FileChooser.ExtensionFilter("Images", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.txt")
);
File file = chooser.showOpenDialog(messageField.getScene().getWindow());
if (file != null) {
lastActionWasFile = true;
model.sendFile(file);
}
}
private void attachFile() {
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(
new FileChooser.ExtensionFilter("Images and Text", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.txt")
);
File file = chooser.showOpenDialog(messageField.getScene().getWindow());
if (file != null) {
lastActionWasFile = true;
model.sendFile(file);
}
}
Suggested change
private void attachFile() {
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().add(
new FileChooser.ExtensionFilter("Images", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.txt")
);
File file = chooser.showOpenDialog(messageField.getScene().getWindow());
if (file != null) {
lastActionWasFile = true;
model.sendFile(file);
}
}
private void attachFile() {
FileChooser chooser = new FileChooser();
chooser.getExtensionFilters().addAll(
new FileChooser.ExtensionFilter("Images", "*.png", "*.jpg", "*.jpeg", "*.gif"),
new FileChooser.ExtensionFilter("Text Files", "*.txt")
);
File file = chooser.showOpenDialog(messageField.getScene().getWindow());
if (file != null) {
lastActionWasFile = true;
model.sendFile(file);
}
}
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloController.java around lines 36 to 47, the
FileChooser filter is mislabeled "Images" while including "*.txt" and there is
no handling if model.sendFile(file) fails; change the filter to either separate
image and text filters (e.g., an "Images" filter with only image extensions plus
a separate "Text" filter with "*.txt") or rename the combined filter to
something like "Images and Text"; also do not set lastActionWasFile before
confirming success—call model.sendFile(file), check its return/throw for
failure, and on failure show user feedback (e.g., an alert/dialog or status
message) and avoid changing lastActionWasFile, while on success set
lastActionWasFile = true.


@FXML
private void initialize() {

if (messageLabel != null) {
messageLabel.setText(model.getGreeting());
}

messageView.setItems(model.getMessages());

// quickfix :D prevent listview cell selection which forces a re-render and
// causes image messages to flicker or disappear when clicking or scrolling.
messageView.setSelectionModel(new NoSelectionModel<>());


messageView.setCellFactory(list -> new ListCell<>() {
private final Label messageLabel = new Label();
private final HBox bubble = new HBox(messageLabel);

private final Label textLabel = new Label();
private final HBox textBubble = new HBox(textLabel);

{
bubble.setPadding(new Insets(5, 10, 5, 10));
bubble.setMaxWidth(200);
messageLabel.setWrapText(true);
bubble.getStyleClass().add("chat-bubble");
textBubble.setPadding(new Insets(5, 10, 5, 10));
textBubble.setMaxWidth(200);
textLabel.setWrapText(true);
textBubble.getStyleClass().add("chat-bubble");
}

@Override
protected void updateItem(NtfyMessageDto item, boolean empty) {
super.updateItem(item, empty);

if (empty || item == null) {
setText(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);
return;
}

if (item.attachment() != null &&
item.attachment().type() != null &&
item.attachment().type().startsWith("image") &&
item.attachment().url() != null) {

try {
Image image = new Image(item.attachment().url(), 200, 0, true, true);
ImageView imageView = new ImageView(image);
imageView.setPreserveRatio(true);

HBox imageBubble = new HBox(imageView);
imageBubble.setPadding(new Insets(5, 10, 5, 10));
imageBubble.setMaxWidth(200);
imageBubble.getStyleClass().add("chat-bubble");

setText(null);
setGraphic(imageBubble);
} catch (Exception e) {
Label err = new Label("[Image failed to load]");
HBox bubble = new HBox(err);
bubble.setPadding(new Insets(5, 10, 5, 10));
bubble.getStyleClass().add("chat-bubble");
setGraphic(bubble);
}
return;
}

if (item.attachment() != null && item.attachment().url() != null) {

String fileName = item.attachment().name();
String fileUrl = item.attachment().url();

Label icon = new Label("📄");
icon.setStyle("-fx-font-size: 20px;");

Label fileLabel = new Label(fileName);
fileLabel.setStyle("-fx-text-fill: white; -fx-font-size: 14px;");

Button openBtn = new Button("Open file");
openBtn.setOnAction(e -> {
System.out.println("Opening: " + fileUrl);
new Thread(() -> {
try {
URI uri = new URI(fileUrl);
Desktop.getDesktop().browse(uri);
} catch (Exception ex) {
ex.printStackTrace();
}
}).start();
Comment on lines +124 to +133
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate URLs before opening in browser.

The code opens attachment URLs directly in the browser without validation. This could potentially expose users to malicious links if the server is compromised or sends malicious URLs.

Apply this diff to add basic URL validation:

                     Button openBtn = new Button("Open file");
                     openBtn.setOnAction(e -> {
                         System.out.println("Opening: " + fileUrl);
+                        if (!isValidUrl(fileUrl)) {
+                            System.err.println("Invalid or unsafe URL: " + fileUrl);
+                            return;
+                        }
                         new Thread(() -> {
                             try {
                                 URI uri = new URI(fileUrl);
                                 Desktop.getDesktop().browse(uri);
                             } catch (Exception ex) {
                                 ex.printStackTrace();
                             }
                         }).start();
                     });

Add a validation helper method:

private boolean isValidUrl(String url) {
    if (url == null || url.isEmpty()) return false;
    try {
        URI uri = new URI(url);
        String scheme = uri.getScheme();
        return "http".equals(scheme) || "https".equals(scheme);
    } catch (Exception e) {
        return false;
    }
}
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloController.java around lines 124 to 133, the
code directly opens fileUrl in the user's browser; add and use a URL validation
helper to ensure the URL is non-null, non-empty, parses as a URI and uses only
"http" or "https" scheme before launching Desktop.browse. Implement a private
boolean isValidUrl(String url) as described in the review and check it before
starting the new Thread — if invalid, log or show an error and return; only
construct the URI and call Desktop.getDesktop().browse(uri) when
isValidUrl(fileUrl) returns true, and avoid swallowing exceptions silently (log
the exception).

});


HBox fileBox = new HBox(10, icon, fileLabel, openBtn);
fileBox.setPadding(new Insets(5, 10, 5, 10));
fileBox.setMaxWidth(200);
fileBox.getStyleClass().add("chat-bubble");

setText(null);
setGraphic(fileBox);
return;
}


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"));

Label msgLabel = new Label(formattedTime + "\n" + item.message());
msgLabel.setWrapText(true);
msgLabel.setMaxWidth(250);

HBox msgBubble = new HBox(msgLabel);
msgBubble.setPadding(new Insets(5, 10, 5, 10));
msgBubble.setMaxWidth(300);
msgBubble.getStyleClass().add("chat-bubble");

setText(null);
setGraphic(msgBubble);
}

});

model.messageToSendProperty().bind(messageField.textProperty());

}


public void sendMessage(ActionEvent actionEvent) {
String message = messageField.getText();
if(message == null || message.isBlank()){
if(!lastActionWasFile && (message == null || message.isBlank())){
showTemporaryAlert("You must write something before sending!");
return;
}

model.sendMessage();
if (message != null && !message.isBlank()) {
model.sendMessage();
}

messageField.clear();
lastActionWasFile = false;
}

private void showTemporaryAlert(String alertMessage) {
Expand All @@ -83,4 +198,38 @@ private void showTemporaryAlert(String alertMessage) {
} catch (InterruptedException e) {}
}).start();
}

private static class NoSelectionModel<T> extends MultipleSelectionModel<T> {

/*
Quickfix to prevent the ListView from selecting cells when the user clicks on them.
Otherwise it triggers full re render of the listcells when which causes message bubbles
or images to flicker or dissapears on click or scroll
*/

@Override
public ObservableList<Integer> getSelectedIndices() {
return FXCollections.emptyObservableList();
}

@Override
public ObservableList<T> getSelectedItems() {
return FXCollections.emptyObservableList();
}

@Override public void selectIndices(int index, int... indices) { }
@Override public void selectAll() { }
@Override public void clearAndSelect(int index) { }
@Override public void select(int index) { }
@Override public void select(T obj) { }
@Override public void clearSelection(int index) { }
@Override public void clearSelection() { }
@Override public boolean isSelected(int index) { return false; }
@Override public boolean isEmpty() { return true; }
@Override public void selectPrevious() { }
@Override public void selectNext() { }
@Override public void selectFirst() { }
@Override public void selectLast() { }
}
}

6 changes: 6 additions & 0 deletions src/main/java/com/example/HelloModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

import java.io.File;


/**
* Model layer: encapsulates application data and business logic.
Expand Down Expand Up @@ -52,4 +54,8 @@ public void sendMessage() {
public void receiveMessage() {
connection.receive(m->Platform.runLater(()->messages.add(m)));
}

public void sendFile(File file) {
connection.sendFile(file);
}
Comment on lines +58 to +60
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle the boolean return value from connection.sendFile.

The connection.sendFile(file) method returns a boolean indicating success or failure, but this return value is being ignored. This prevents callers from knowing whether the file upload succeeded and may lead to silent failures.

Consider either:

  1. Returning the boolean from this method so callers can handle failures
  2. Logging errors or notifying the user when the send fails

Apply this diff to propagate the return value:

-    public void sendFile(File file) {
-        connection.sendFile(file);
+    public boolean sendFile(File file) {
+        return connection.sendFile(file);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void sendFile(File file) {
connection.sendFile(file);
}
public boolean sendFile(File file) {
return connection.sendFile(file);
}
🤖 Prompt for AI Agents
In src/main/java/com/example/HelloModel.java around lines 58-60, the method
ignores the boolean result of connection.sendFile(file); change the method to
propagate the result by updating the signature to return boolean and return
connection.sendFile(file), and then update all callers to handle the returned
boolean (log, notify user, or retry on false). If you prefer to keep a void API
instead, check the boolean result inside this method and handle failure by
logging an error or throwing an exception so failures are not silent; ensure
unit tests and call sites are adjusted accordingly.

}
2 changes: 1 addition & 1 deletion src/main/java/com/example/NtfyConnection.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@

public interface NtfyConnection {
public boolean send(String message);

boolean sendFile(java.io.File file);
public void receive(Consumer<NtfyMessageDto> messageHandler);
}
52 changes: 45 additions & 7 deletions src/main/java/com/example/NtfyConnectionImpl.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.example;
import io.github.cdimascio.dotenv.Dotenv;
import tools.jackson.databind.ObjectMapper;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
Expand Down Expand Up @@ -28,7 +30,7 @@ public NtfyConnectionImpl(String hostName){
public boolean send(String message) {
HttpRequest httpRequest = HttpRequest.newBuilder()
.POST(HttpRequest.BodyPublishers.ofString(message))
.uri(URI.create(hostName + "/mytopic"))
.uri(URI.create(hostName + "/adam"))
.build();
try {
// TODO: handle long blocking send requests to not freeze the JavaFX thread
Expand All @@ -46,17 +48,53 @@ public boolean send(String message) {

@Override
public void receive(Consumer<NtfyMessageDto> messageHandler) {
String startId = "8TuugOLkvDz1"; // just to make the app wont load 10000000 messages
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove or use the unused startId variable.

The startId variable is declared but never used. Line 54 uses a different hardcoded ID ("3hPbr2dcIUiU") in the URL. Either use this variable in the query parameter or remove it.

Apply this diff to use the variable consistently:

-        String startId = "8TuugOLkvDz1"; // just to make the app wont load 10000000 messages
+        String startId = "3hPbr2dcIUiU"; // just to make the app wont load 10000000 messages
         HttpRequest httpRequest = HttpRequest.newBuilder()
                 .GET()
-                .uri(URI.create(hostName + "/adam/json?since=3hPbr2dcIUiU"))
+                .uri(URI.create(hostName + "/adam/json?since=" + startId))
                 .build();

Or remove it if not needed:

-        String startId = "8TuugOLkvDz1"; // just to make the app wont load 10000000 messages
         HttpRequest httpRequest = HttpRequest.newBuilder()
                 .GET()
                 .uri(URI.create(hostName + "/adam/json?since=3hPbr2dcIUiU"))
                 .build();

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/main/java/com/example/NtfyConnectionImpl.java around line 51, the local
variable startId ("8TuugOLkvDz1") is declared but never used while the code
later hardcodes a different ID ("3hPbr2dcIUiU") into the URL; either remove the
unused startId declaration or replace the hardcoded ID usage with this variable
so the same startId is used for the query parameter. Update the URL construction
to reference startId (or delete the declaration) and run a quick compile to
ensure no unused-variable warnings remain.

HttpRequest httpRequest = HttpRequest.newBuilder()
.GET()
.uri(URI.create(hostName + "/mytopic/json?since=wBuD2KGEaAe0"))
.uri(URI.create(hostName + "/adam/json?since=3hPbr2dcIUiU"))
.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));
.peek(s -> System.out.println(s))
.map(s -> {
try {
return mapper.readValue(s, NtfyMessageDto.class);
} catch (Exception e) {
System.out.println("JSON parse fail: " + s);
return null;
}
})
.filter(msg -> msg != null)
.filter(msg -> "message".equals(msg.event()))
.forEach(messageHandler)
);
}

@Override
public boolean sendFile(File file){
try {
String mime = java.nio.file.Files.probeContentType(file.toPath());

if(mime == null) mime = "application/octet-stream";

long size = file.length();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused size variable.

The size variable is calculated but never used in the method.

Apply this diff:

             if(mime == null) mime =  "application/octet-stream";
 
-            long size = file.length();
-
             HttpRequest request = HttpRequest.newBuilder()
🤖 Prompt for AI Agents
In src/main/java/com/example/NtfyConnectionImpl.java around line 83, the local
variable `size` is assigned with `long size = file.length();` but never used;
remove this unused variable declaration so the method no longer computes file
length unnecessarily and clean up any resulting unused-import or warning (simply
delete the `long size = file.length();` line).


HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(hostName + "/adam"))
.header("Filename", file.getName())
.header("Content-Type", mime)
.PUT(HttpRequest.BodyPublishers.ofFile(file.toPath()))
.build();

http.sendAsync(request, HttpResponse.BodyHandlers.discarding());
return true;

} catch (Exception e) {
e.printStackTrace();
return false;
}
}
Comment on lines +77 to 99
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: sendFile returns before the async operation completes.

Line 92 uses sendAsync, which returns immediately without waiting for the HTTP request to complete. However, line 93 returns true, incorrectly indicating success before the actual upload finishes. This means:

  • The method will always return true (unless an exception occurs before the request starts)
  • The caller cannot know if the file upload actually succeeded or failed
  • Network errors or server rejections will go undetected

Apply this diff to use synchronous sending:

-            http.sendAsync(request, HttpResponse.BodyHandlers.discarding());
+            var response = http.send(request, HttpResponse.BodyHandlers.discarding());
+            return response.statusCode() >= 200 && response.statusCode() < 300;
-            return true;
 
         } catch (Exception e) {
             e.printStackTrace();
             return false;
         }

Or handle the CompletableFuture properly:

-    public boolean sendFile(File file){
+    public CompletableFuture<Boolean> sendFile(File file){
         try {
             // ... mime detection code ...
             
-            http.sendAsync(request, HttpResponse.BodyHandlers.discarding());
-            return true;
+            return http.sendAsync(request, HttpResponse.BodyHandlers.discarding())
+                    .thenApply(response -> response.statusCode() >= 200 && response.statusCode() < 300);
 
         } catch (Exception e) {
             e.printStackTrace();
-            return false;
+            return CompletableFuture.completedFuture(false);
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public boolean sendFile(File file){
try {
String mime = java.nio.file.Files.probeContentType(file.toPath());
if(mime == null) mime = "application/octet-stream";
long size = file.length();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(hostName + "/adam"))
.header("Filename", file.getName())
.header("Content-Type", mime)
.PUT(HttpRequest.BodyPublishers.ofFile(file.toPath()))
.build();
http.sendAsync(request, HttpResponse.BodyHandlers.discarding());
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public boolean sendFile(File file){
try {
String mime = java.nio.file.Files.probeContentType(file.toPath());
if(mime == null) mime = "application/octet-stream";
long size = file.length();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(hostName + "/adam"))
.header("Filename", file.getName())
.header("Content-Type", mime)
.PUT(HttpRequest.BodyPublishers.ofFile(file.toPath()))
.build();
var response = http.send(request, HttpResponse.BodyHandlers.discarding());
return response.statusCode() >= 200 && response.statusCode() < 300;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
🤖 Prompt for AI Agents
In src/main/java/com/example/NtfyConnectionImpl.java around lines 77 to 99,
sendFile currently calls http.sendAsync(...) and immediately returns true, which
reports success before the upload completes; change this to wait for the request
to finish and only return true on a successful HTTP response (e.g., 2xx). Either
replace sendAsync with http.send(...) to perform a synchronous request and
inspect the HttpResponse status code, or keep sendAsync but call .get() (or
otherwise handle the CompletableFuture), catch
ExecutionException/InterruptedException, and evaluate the resulting
HttpResponse.statusCode() before returning; on non-successful status codes or
exceptions return false and log/propagate the error appropriately.

}
20 changes: 19 additions & 1 deletion src/main/java/com/example/NtfyMessageDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,22 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public record NtfyMessageDto(String id, long time, String event, String topic, String message) {}
public record NtfyMessageDto(
String id,
long time,
long expires,
String event,
String topic,
String message,
Attachment attachment
) {
@JsonIgnoreProperties(ignoreUnknown = true)
public record Attachment(
String name,
String type,
String url,
long expires,
long size
){}

}
1 change: 1 addition & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
requires java.net.http;
requires tools.jackson.databind;
requires javafx.graphics;
requires java.desktop;

opens com.example to javafx.fxml;
exports com.example;
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/com/example/hello-view.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
</VBox.margin>

<TextField fx:id="messageField" prefHeight="40" promptText="Write something here.." HBox.hgrow="ALWAYS" />
<Button text="📎" onAction="#attachFile" prefHeight="40" prefWidth="40"/>

<Button onAction="#sendMessage" prefHeight="40" prefWidth="80" text="Send" />
</HBox>

Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/com/example/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
-fx-font-size: 12px;
-fx-font-weight: bold;
-fx-padding: 8 24 8 24;
-fx-cursor: hand;/
-fx-cursor: hand;
-fx-border-color: transparent;
-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.25), 8, 0, 0, 3);
-fx-transition: all 0.3s ease;
Expand Down
Loading