diff --git a/src/main/java/envoy/client/ui/controller/ChatScene.java b/src/main/java/envoy/client/ui/controller/ChatScene.java index 2cf116b..c5e6909 100644 --- a/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -2,7 +2,10 @@ package envoy.client.ui.controller; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; +import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -12,10 +15,12 @@ import javafx.collections.FXCollections; import javafx.fxml.FXML; import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; +import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.paint.Color; +import javafx.stage.FileChooser; import envoy.client.data.*; import envoy.client.data.audio.AudioRecorder; @@ -60,6 +65,9 @@ public final class ChatScene implements Restorable { @FXML private Button voiceButton; + @FXML + private Button attachmentButton; + @FXML private Button settingsButton; @@ -87,12 +95,15 @@ public final class ChatScene implements Restorable { private AudioRecorder recorder; private boolean recording; private Attachment pendingAttachment; - private boolean postingPermanentlyDisabled = false; + private boolean postingPermanentlyDisabled; - private static final Settings settings = Settings.getInstance(); - private static final EventBus eventBus = EventBus.getInstance(); - private static final Logger logger = EnvoyLog.getLogger(ChatScene.class); - private static final int MAX_MESSAGE_LENGTH = 255; + private static final Settings settings = Settings.getInstance(); + private static final EventBus eventBus = EventBus.getInstance(); + private static final Logger logger = EnvoyLog.getLogger(ChatScene.class); + + private static final Image DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20); + private static final int MAX_MESSAGE_LENGTH = 255; + private static final int DEFAULT_ICON_SIZE = 16; /** * Initializes the appearance of certain visual components. @@ -106,8 +117,10 @@ public final class ChatScene implements Restorable { messageList.setCellFactory(MessageListCellFactory::new); userList.setCellFactory(ContactListCellFactory::new); - settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", 16))); - voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", 20))); + settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE))); + voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE))); + attachmentButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE))); + attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE); // Listen to received messages eventBus.register(MessageCreationEvent.class, e -> { @@ -226,7 +239,7 @@ public final class ChatScene implements Restorable { recording = false; } pendingAttachment = null; - attachmentView.setVisible(false); + updateAttachmentView(false); remainingChars.setVisible(true); remainingChars @@ -234,6 +247,7 @@ public final class ChatScene implements Restorable { } messageTextArea.setDisable(currentChat == null || postingPermanentlyDisabled); voiceButton.setDisable(!recorder.isSupported()); + attachmentButton.setDisable(false); } /** @@ -266,18 +280,17 @@ public final class ChatScene implements Restorable { recording = true; Platform.runLater(() -> { voiceButton.setText("Recording"); - voiceButton.setGraphic(new ImageView(IconUtil.loadIcon("microphone_recording", 24))); + voiceButton.setGraphic(new ImageView(IconUtil.loadIcon("microphone_recording", DEFAULT_ICON_SIZE))); }); recorder.start(); } else { pendingAttachment = new Attachment(recorder.finish(), AttachmentType.VOICE); recording = false; Platform.runLater(() -> { - voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", 20))); + voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE))); voiceButton.setText(null); checkPostConditions(false); - attachmentView.setImage(IconUtil.loadIconThemeSensitive("attachment_present", 20)); - attachmentView.setVisible(true); + updateAttachmentView(true); }); } } catch (final EnvoyException e) { @@ -287,6 +300,52 @@ public final class ChatScene implements Restorable { }).start(); } + @FXML + private void attachmentButtonClicked() { + + // Display file chooser + final var fileChooser = new FileChooser(); + fileChooser.setTitle("Add Attachment"); + fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); + fileChooser.getExtensionFilters() + .addAll(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"), + new FileChooser.ExtensionFilter("Videos", "*.mp4"), + new FileChooser.ExtensionFilter("All Files", "*.*")); + final var file = fileChooser.showOpenDialog(sceneContext.getStage()); + + if (file != null) { + + // Check max file size + if (file.length() > 16E6) { + new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 16MB!").showAndWait(); + return; + } + + // Get attachment type (default is document) + AttachmentType type = AttachmentType.DOCUMENT; + switch (fileChooser.getSelectedExtensionFilter().getDescription()) { + case "Pictures": + type = AttachmentType.PICTURE; + break; + case "Videos": + type = AttachmentType.VIDEO; + break; + } + + // Create the pending attachment + try { + final var fileBytes = Files.readAllBytes(file.toPath()); + pendingAttachment = new Attachment(fileBytes, type); + // Setting the preview image as image of the attachmentView + if (type == AttachmentType.PICTURE) + attachmentView.setImage(new Image(new ByteArrayInputStream(fileBytes), DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE, true, true)); + attachmentView.setVisible(true); + } catch (final IOException e) { + new Alert(AlertType.ERROR, "The selected file could not be loaded!").showAndWait(); + } + } + } + /** * Checks the text length of the {@code messageTextArea}, adjusts the * {@code remainingChars} label and checks whether to send the message @@ -373,16 +432,16 @@ public final class ChatScene implements Restorable { final var text = messageTextArea.getText().strip(); try { // Creating the message and its metadata - final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator()) + final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator()) .setText(text); - // Setting an attachment, if present - if (pendingAttachment != null) { + // Setting an attachment, if present + if (pendingAttachment != null) { builder.setAttachment(pendingAttachment); pendingAttachment = null; - attachmentView.setVisible(false); + updateAttachmentView(false); } - // Building the final message - final var message = currentChat.getRecipient() instanceof Group ? builder.buildGroupMessage((Group) currentChat.getRecipient()) + // Building the final message + final var message = currentChat.getRecipient() instanceof Group ? builder.buildGroupMessage((Group) currentChat.getRecipient()) : builder.build(); // Send message @@ -428,8 +487,22 @@ public final class ChatScene implements Restorable { infoLabel.setVisible(true); } + /** + * Updates the {@code attachmentView} in terms of visibility.
+ * Additionally resets the shown image to + * {@code DEFAULT_ATTACHMENT_VIEW_IMAGE} if another image is currently + * present. + * + * @param visible whether the {@code attachmentView} should be displayed + * @since Envoy Client v0.1-beta + */ + private void updateAttachmentView(boolean visible) { + if (!attachmentView.getImage().equals(DEFAULT_ATTACHMENT_VIEW_IMAGE)) attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE); + attachmentView.setVisible(visible); + } + // Context menu actions - + @FXML private void deleteContact() { try {} catch (final NullPointerException e) {} } diff --git a/src/main/java/envoy/client/ui/listcell/MessageControl.java b/src/main/java/envoy/client/ui/listcell/MessageControl.java index 55be558..8b7115b 100644 --- a/src/main/java/envoy/client/ui/listcell/MessageControl.java +++ b/src/main/java/envoy/client/ui/listcell/MessageControl.java @@ -2,6 +2,7 @@ package envoy.client.ui.listcell; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; +import java.io.ByteArrayInputStream; import java.time.format.DateTimeFormatter; import java.util.Map; import java.util.logging.Level; @@ -67,6 +68,7 @@ public class MessageControl extends Label { if (message.hasAttachment()) { switch (message.getAttachment().getType()) { case PICTURE: + vbox.getChildren().add(new ImageView(new Image(new ByteArrayInputStream(message.getAttachment().getData()), 256, 256, true, true))); break; case VIDEO: break; @@ -90,9 +92,7 @@ public class MessageControl extends Label { statusIcon.setPreserveRatio(true); vbox.getChildren().add(statusIcon); getStyleClass().add("own-message"); - } else { - getStyleClass().add("received-message"); - } + } else getStyleClass().add("received-message"); // Adjusting height and weight of the cell to the corresponding ListView paddingProperty().setValue(new Insets(5, 20, 5, 20)); setContextMenu(contextMenu); diff --git a/src/main/resources/fxml/ChatScene.fxml b/src/main/resources/fxml/ChatScene.fxml index 4a6ac3a..fd48b75 100644 --- a/src/main/resources/fxml/ChatScene.fxml +++ b/src/main/resources/fxml/ChatScene.fxml @@ -51,7 +51,7 @@ - + @@ -82,24 +82,29 @@ - - + - +