|
|
|
@ -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 -> {
|
|
|
|
@ -229,7 +242,7 @@ public final class ChatScene implements Restorable {
|
|
|
|
|
recording = false;
|
|
|
|
|
}
|
|
|
|
|
pendingAttachment = null;
|
|
|
|
|
attachmentView.setVisible(false);
|
|
|
|
|
updateAttachmentView(false);
|
|
|
|
|
|
|
|
|
|
remainingChars.setVisible(true);
|
|
|
|
|
remainingChars
|
|
|
|
@ -237,6 +250,7 @@ public final class ChatScene implements Restorable {
|
|
|
|
|
}
|
|
|
|
|
messageTextArea.setDisable(currentChat == null || postingPermanentlyDisabled);
|
|
|
|
|
voiceButton.setDisable(!recorder.isSupported());
|
|
|
|
|
attachmentButton.setDisable(false);
|
|
|
|
|
userList.refresh();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -270,18 +284,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) {
|
|
|
|
@ -291,6 +304,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
|
|
|
|
@ -377,16 +436,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
|
|
|
|
@ -432,8 +491,22 @@ public final class ChatScene implements Restorable {
|
|
|
|
|
infoLabel.setVisible(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Updates the {@code attachmentView} in terms of visibility.<br>
|
|
|
|
|
* 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) {} }
|
|
|
|
|
|
|
|
|
|