2020-06-08 10:36:30 +02:00
|
|
|
package envoy.client.ui.controller;
|
2020-03-26 20:23:25 +01:00
|
|
|
|
2020-11-06 08:58:13 +01:00
|
|
|
import static envoy.client.ui.SceneInfo.SETTINGS_SCENE;
|
|
|
|
|
2020-06-22 22:51:10 +02:00
|
|
|
import java.awt.Toolkit;
|
|
|
|
import java.awt.datatransfer.StringSelection;
|
2020-09-02 11:21:36 +02:00
|
|
|
import java.io.*;
|
2020-07-09 16:05:09 +02:00
|
|
|
import java.nio.file.Files;
|
2020-07-27 12:00:49 +02:00
|
|
|
import java.time.LocalDateTime;
|
|
|
|
import java.time.format.DateTimeFormatter;
|
2020-10-19 22:16:18 +02:00
|
|
|
import java.util.Map;
|
2020-09-02 11:21:36 +02:00
|
|
|
import java.util.logging.*;
|
2020-03-26 20:23:25 +01:00
|
|
|
|
2020-07-11 15:23:57 +02:00
|
|
|
import javafx.animation.RotateTransition;
|
2020-04-18 19:46:04 +02:00
|
|
|
import javafx.application.Platform;
|
2020-09-25 19:19:54 +02:00
|
|
|
import javafx.collections.ObservableList;
|
2020-07-31 18:46:32 +02:00
|
|
|
import javafx.collections.transformation.FilteredList;
|
2020-09-02 11:21:36 +02:00
|
|
|
import javafx.fxml.*;
|
2020-10-08 16:12:24 +02:00
|
|
|
import javafx.geometry.Pos;
|
2020-04-18 19:46:04 +02:00
|
|
|
import javafx.scene.control.*;
|
2020-06-10 13:05:44 +02:00
|
|
|
import javafx.scene.control.Alert.AlertType;
|
2020-09-02 11:21:36 +02:00
|
|
|
import javafx.scene.image.*;
|
|
|
|
import javafx.scene.input.*;
|
2021-02-19 11:09:07 +01:00
|
|
|
import javafx.scene.layout.HBox;
|
2020-06-07 23:28:25 +02:00
|
|
|
import javafx.scene.paint.Color;
|
2020-07-17 13:56:36 +02:00
|
|
|
import javafx.scene.shape.Rectangle;
|
2020-07-09 16:05:09 +02:00
|
|
|
import javafx.stage.FileChooser;
|
2020-07-11 15:23:57 +02:00
|
|
|
import javafx.util.Duration;
|
2020-04-18 19:46:04 +02:00
|
|
|
|
2021-02-19 11:09:07 +01:00
|
|
|
import dev.kske.eventbus.core.*;
|
|
|
|
import dev.kske.eventbus.core.Event;
|
2020-10-19 18:17:51 +02:00
|
|
|
|
|
|
|
import envoy.data.*;
|
|
|
|
import envoy.data.Attachment.AttachmentType;
|
|
|
|
import envoy.data.Message.MessageStatus;
|
|
|
|
import envoy.event.*;
|
2020-11-06 08:58:13 +01:00
|
|
|
import envoy.event.contact.UserOperation;
|
2020-10-19 18:17:51 +02:00
|
|
|
import envoy.exception.EnvoyException;
|
|
|
|
import envoy.util.EnvoyLog;
|
|
|
|
|
2020-07-05 14:38:19 +02:00
|
|
|
import envoy.client.data.*;
|
2020-07-05 12:04:25 +02:00
|
|
|
import envoy.client.data.audio.AudioRecorder;
|
2020-10-19 22:01:43 +02:00
|
|
|
import envoy.client.data.shortcuts.KeyboardMapping;
|
2020-08-23 20:26:22 +02:00
|
|
|
import envoy.client.event.*;
|
2020-09-02 11:21:36 +02:00
|
|
|
import envoy.client.net.*;
|
2020-07-16 17:35:15 +02:00
|
|
|
import envoy.client.ui.*;
|
2020-10-02 15:23:21 +02:00
|
|
|
import envoy.client.ui.chatscene.*;
|
2020-10-08 16:12:24 +02:00
|
|
|
import envoy.client.ui.control.*;
|
2020-07-18 11:50:49 +02:00
|
|
|
import envoy.client.ui.listcell.*;
|
2020-09-25 14:29:23 +02:00
|
|
|
import envoy.client.util.*;
|
2020-09-08 20:41:01 +02:00
|
|
|
|
2020-03-26 20:23:25 +01:00
|
|
|
/**
|
2020-09-25 14:29:23 +02:00
|
|
|
* Controller for the chat scene.
|
2020-04-02 22:03:43 +02:00
|
|
|
*
|
2020-03-26 20:23:25 +01:00
|
|
|
* @author Kai S. K. Engelbart
|
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
2021-02-19 11:09:07 +01:00
|
|
|
public final class ChatScene implements Restorable, KeyboardMapping {
|
2020-03-26 20:23:25 +01:00
|
|
|
|
2020-03-28 10:39:15 +01:00
|
|
|
@FXML
|
2020-03-28 15:32:24 +01:00
|
|
|
private ListView<Message> messageList;
|
|
|
|
|
|
|
|
@FXML
|
2020-07-12 17:10:22 +02:00
|
|
|
private ListView<Chat> chatList;
|
2020-03-28 15:32:24 +01:00
|
|
|
|
|
|
|
@FXML
|
|
|
|
private Button postButton;
|
|
|
|
|
2020-07-03 23:32:22 +02:00
|
|
|
@FXML
|
|
|
|
private Button voiceButton;
|
|
|
|
|
2020-07-09 16:05:09 +02:00
|
|
|
@FXML
|
|
|
|
private Button attachmentButton;
|
|
|
|
|
2020-03-28 15:32:24 +01:00
|
|
|
@FXML
|
|
|
|
private Button settingsButton;
|
|
|
|
|
2020-08-22 18:14:26 +02:00
|
|
|
@FXML
|
|
|
|
private Button messageSearchButton;
|
|
|
|
|
|
|
|
@FXML
|
|
|
|
private Button newGroupButton;
|
|
|
|
|
2020-09-01 20:57:18 +02:00
|
|
|
@FXML
|
|
|
|
private Button newContactButton;
|
|
|
|
|
2020-06-07 23:28:25 +02:00
|
|
|
@FXML
|
|
|
|
private Label remainingChars;
|
|
|
|
|
2020-06-26 23:16:03 +02:00
|
|
|
@FXML
|
|
|
|
private Label infoLabel;
|
|
|
|
|
2020-06-23 18:32:34 +02:00
|
|
|
@FXML
|
2020-10-08 14:34:21 +02:00
|
|
|
private Label topBarContactLabel;
|
2020-06-23 18:32:34 +02:00
|
|
|
|
2020-07-05 23:25:07 +02:00
|
|
|
@FXML
|
2020-10-08 14:34:21 +02:00
|
|
|
private Label topBarStatusLabel;
|
2020-07-05 23:25:07 +02:00
|
|
|
|
2020-07-16 21:13:46 +02:00
|
|
|
@FXML
|
2020-10-08 14:34:21 +02:00
|
|
|
private ImageView attachmentView;
|
2020-07-16 21:13:46 +02:00
|
|
|
|
2020-07-17 13:56:36 +02:00
|
|
|
@FXML
|
|
|
|
private ImageView clientProfilePic;
|
|
|
|
|
|
|
|
@FXML
|
|
|
|
private ImageView recipientProfilePic;
|
|
|
|
|
2020-10-08 14:34:21 +02:00
|
|
|
@FXML
|
|
|
|
private TextArea messageTextArea;
|
|
|
|
|
2020-07-31 18:46:32 +02:00
|
|
|
@FXML
|
|
|
|
private TextArea contactSearch;
|
2020-09-01 20:14:02 +02:00
|
|
|
|
2020-08-22 21:02:49 +02:00
|
|
|
@FXML
|
|
|
|
private TabPane tabPane;
|
2020-09-01 20:14:02 +02:00
|
|
|
|
2020-08-22 21:50:05 +02:00
|
|
|
@FXML
|
|
|
|
private Tab contactSearchTab;
|
2020-09-01 20:14:02 +02:00
|
|
|
|
2020-08-23 20:15:52 +02:00
|
|
|
@FXML
|
|
|
|
private Tab groupCreationTab;
|
2020-07-31 18:46:32 +02:00
|
|
|
|
2020-09-01 20:57:18 +02:00
|
|
|
@FXML
|
|
|
|
private HBox contactSpecificOnlineOperations;
|
2020-03-28 10:39:15 +01:00
|
|
|
|
2020-10-08 16:12:24 +02:00
|
|
|
@FXML
|
|
|
|
private HBox ownContactControl;
|
|
|
|
|
2020-09-02 11:21:36 +02:00
|
|
|
private Chat currentChat;
|
|
|
|
private FilteredList<Chat> chats;
|
|
|
|
private Attachment pendingAttachment;
|
|
|
|
private boolean postingPermanentlyDisabled;
|
2020-09-02 11:31:21 +02:00
|
|
|
private boolean isCustomAttachmentImage;
|
2020-10-02 15:23:21 +02:00
|
|
|
private ChatSceneCommands commands;
|
2020-07-23 17:18:53 +02:00
|
|
|
|
2020-10-02 15:23:21 +02:00
|
|
|
private final LocalDB localDB = context.getLocalDB();
|
|
|
|
private final Client client = context.getClient();
|
|
|
|
private final WriteProxy writeProxy = context.getWriteProxy();
|
|
|
|
private final SceneContext sceneContext = context.getSceneContext();
|
|
|
|
private final AudioRecorder recorder = new AudioRecorder();
|
2020-10-19 18:17:51 +02:00
|
|
|
private final Tooltip onlyIfOnlineTooltip =
|
|
|
|
new Tooltip("You need to be online to do this");
|
2020-07-10 20:53:28 +02:00
|
|
|
|
2020-10-19 18:17:51 +02:00
|
|
|
private static Image DEFAULT_ATTACHMENT_VIEW_IMAGE =
|
|
|
|
IconUtil.loadIconThemeSensitive("attachment_present", 20);
|
2020-03-26 20:23:25 +01:00
|
|
|
|
2020-09-02 11:21:36 +02:00
|
|
|
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 Context context = Context.getInstance();
|
|
|
|
private static final int MAX_MESSAGE_LENGTH = 255;
|
|
|
|
private static final int DEFAULT_ICON_SIZE = 16;
|
2020-03-26 20:23:25 +01:00
|
|
|
|
2020-06-09 17:11:17 +02:00
|
|
|
/**
|
|
|
|
* Initializes the appearance of certain visual components.
|
|
|
|
*
|
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
2020-03-28 10:39:15 +01:00
|
|
|
@FXML
|
2020-03-28 15:32:24 +01:00
|
|
|
private void initialize() {
|
2020-09-08 20:41:01 +02:00
|
|
|
eventBus.registerListener(this);
|
2020-10-02 15:23:21 +02:00
|
|
|
commands = new ChatSceneCommands(messageList, this);
|
2020-09-08 20:41:01 +02:00
|
|
|
|
2020-03-29 22:15:05 +02:00
|
|
|
// Initialize message and user rendering
|
2020-07-18 11:50:49 +02:00
|
|
|
messageList.setCellFactory(MessageListCell::new);
|
2020-10-17 16:40:13 +02:00
|
|
|
chatList.setCellFactory(ChatListCell::new);
|
2020-03-29 22:15:05 +02:00
|
|
|
|
2020-09-25 19:19:54 +02:00
|
|
|
// JavaFX provides an internal way of populating the context menu of a text
|
|
|
|
// area.
|
2020-09-20 15:27:23 +02:00
|
|
|
// We, however, need additional functionality.
|
2020-10-19 18:17:51 +02:00
|
|
|
messageTextArea.setContextMenu(
|
|
|
|
new TextInputContextMenu(messageTextArea, e -> checkKeyCombination(null)));
|
2020-09-20 15:27:23 +02:00
|
|
|
|
|
|
|
// Set the icons of buttons and image views
|
2020-10-19 18:17:51 +02:00
|
|
|
settingsButton.setGraphic(
|
2020-10-22 16:09:09 +02:00
|
|
|
new ImageView(IconUtil.loadIconThemeSensitive("settings", 22)));
|
2020-10-19 18:17:51 +02:00
|
|
|
voiceButton.setGraphic(
|
|
|
|
new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
|
|
|
|
attachmentButton.setGraphic(
|
|
|
|
new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
|
2020-07-10 20:53:28 +02:00
|
|
|
attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
|
2020-10-19 18:17:51 +02:00
|
|
|
messageSearchButton.setGraphic(
|
|
|
|
new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
|
2020-07-17 13:56:36 +02:00
|
|
|
clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
2020-09-02 10:17:50 +02:00
|
|
|
onlyIfOnlineTooltip.setShowDelay(Duration.millis(250));
|
|
|
|
final var clip = new Rectangle();
|
2020-07-17 13:56:36 +02:00
|
|
|
clip.setWidth(43);
|
|
|
|
clip.setHeight(43);
|
|
|
|
clip.setArcHeight(43);
|
|
|
|
clip.setArcWidth(43);
|
|
|
|
clientProfilePic.setClip(clip);
|
2020-09-01 20:14:02 +02:00
|
|
|
|
2020-09-25 19:19:54 +02:00
|
|
|
chatList.setItems(chats = new FilteredList<>(localDB.getChats()));
|
2020-10-08 16:12:24 +02:00
|
|
|
|
|
|
|
// Set the design of the box in the upper-left corner
|
|
|
|
generateOwnStatusControl();
|
2020-09-01 20:14:02 +02:00
|
|
|
|
2020-08-24 21:54:25 +02:00
|
|
|
Platform.runLater(() -> {
|
2020-09-01 20:57:18 +02:00
|
|
|
final var online = client.isOnline();
|
2020-10-08 16:12:24 +02:00
|
|
|
|
2020-09-01 20:57:18 +02:00
|
|
|
// no check will be performed in case it has already been disabled - a negative
|
|
|
|
// GroupCreationResult might have been returned
|
2020-10-19 18:17:51 +02:00
|
|
|
if (!newGroupButton.isDisabled())
|
|
|
|
newGroupButton.setDisable(!online);
|
2020-09-01 20:57:18 +02:00
|
|
|
newContactButton.setDisable(!online);
|
2020-10-19 18:17:51 +02:00
|
|
|
if (online)
|
|
|
|
try {
|
|
|
|
Tooltip.uninstall(contactSpecificOnlineOperations, onlyIfOnlineTooltip);
|
|
|
|
contactSearchTab.setContent(new FXMLLoader()
|
|
|
|
.load(getClass().getResourceAsStream("/fxml/ContactSearchTab.fxml")));
|
|
|
|
groupCreationTab.setContent(new FXMLLoader()
|
|
|
|
.load(getClass().getResourceAsStream("/fxml/GroupCreationTab.fxml")));
|
|
|
|
} catch (final IOException e) {
|
|
|
|
logger.log(Level.SEVERE, "An error occurred when attempting to load tabs: ", e);
|
|
|
|
}
|
2020-09-01 20:14:02 +02:00
|
|
|
else {
|
2020-09-01 20:57:18 +02:00
|
|
|
Tooltip.install(contactSpecificOnlineOperations, onlyIfOnlineTooltip);
|
|
|
|
updateInfoLabel("You are offline", "info-label-warning");
|
2020-08-24 21:54:25 +02:00
|
|
|
}
|
|
|
|
});
|
2020-09-08 20:41:01 +02:00
|
|
|
}
|
2020-09-01 20:14:02 +02:00
|
|
|
|
2021-02-19 11:09:07 +01:00
|
|
|
@Event(BackEvent.class)
|
2020-10-19 18:17:51 +02:00
|
|
|
private void onBackEvent() {
|
|
|
|
tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal());
|
|
|
|
}
|
2020-09-08 20:41:01 +02:00
|
|
|
|
2021-02-19 11:09:07 +01:00
|
|
|
@Event
|
|
|
|
@Polymorphic
|
2020-09-16 15:52:58 +02:00
|
|
|
private void onMessage(Message message) {
|
2020-09-08 20:41:01 +02:00
|
|
|
|
|
|
|
// The sender of the message is the recipient of the chat
|
|
|
|
// Exceptions: this user is the sender (sync) or group message (group is
|
|
|
|
// recipient)
|
2020-09-30 20:50:58 +02:00
|
|
|
final var ownMessage = message.getSenderID() == localDB.getUser().getID();
|
2020-10-19 18:17:51 +02:00
|
|
|
final var recipientID =
|
|
|
|
message instanceof GroupMessage || ownMessage ? message.getRecipientID()
|
|
|
|
: message.getSenderID();
|
2020-09-25 11:22:59 +02:00
|
|
|
|
2020-09-08 20:41:01 +02:00
|
|
|
localDB.getChat(recipientID).ifPresent(chat -> {
|
2020-10-05 18:50:45 +02:00
|
|
|
Platform.runLater(() -> {
|
|
|
|
chat.insert(message);
|
2020-09-25 11:22:59 +02:00
|
|
|
|
2020-10-05 18:50:45 +02:00
|
|
|
// Read current chat or increment unread amount
|
|
|
|
if (chat.equals(currentChat)) {
|
|
|
|
currentChat.read(writeProxy);
|
|
|
|
scrollToMessageListEnd();
|
2020-10-19 18:17:51 +02:00
|
|
|
} else if (!ownMessage && message.getStatus() != MessageStatus.READ)
|
|
|
|
chat.incrementUnreadAmount();
|
2020-09-08 20:41:01 +02:00
|
|
|
|
2020-10-05 18:50:45 +02:00
|
|
|
// Move chat with most recent unread messages to the top
|
2020-09-08 20:41:01 +02:00
|
|
|
chats.getSource().remove(chat);
|
|
|
|
((ObservableList<Chat>) chats.getSource()).add(0, chat);
|
|
|
|
|
2020-10-19 18:17:51 +02:00
|
|
|
if (chat.equals(currentChat))
|
|
|
|
chatList.getSelectionModel().select(0);
|
2020-06-10 13:05:44 +02:00
|
|
|
});
|
2020-03-29 22:15:05 +02:00
|
|
|
});
|
2020-09-08 20:41:01 +02:00
|
|
|
}
|
2020-03-29 22:15:05 +02:00
|
|
|
|
2020-09-08 20:41:01 +02:00
|
|
|
@Event
|
|
|
|
private void onMessageStatusChange(MessageStatusChange evt) {
|
|
|
|
|
2020-09-25 19:19:54 +02:00
|
|
|
// Update UI if in current chat and the current user was the sender of the
|
|
|
|
// message
|
2020-10-19 18:17:51 +02:00
|
|
|
if (currentChat != null)
|
|
|
|
localDB.getMessage(evt.getID())
|
|
|
|
.filter(msg -> msg.getSenderID() == client.getSender().getID())
|
|
|
|
.ifPresent(msg -> Platform.runLater(messageList::refresh));
|
2020-09-08 20:41:01 +02:00
|
|
|
}
|
|
|
|
|
2020-10-08 14:34:21 +02:00
|
|
|
@Event
|
|
|
|
private void onUserStatusChange(UserStatusChange statusChange) {
|
|
|
|
Platform.runLater(() -> {
|
|
|
|
chatList.refresh();
|
2020-10-10 11:10:17 +02:00
|
|
|
|
|
|
|
// Replacing the display in the top bar
|
2020-10-08 14:34:21 +02:00
|
|
|
if (currentChat != null && currentChat.getRecipient().getID() == statusChange.getID()) {
|
|
|
|
topBarStatusLabel.getStyleClass().clear();
|
|
|
|
topBarStatusLabel.setText(statusChange.get().toString());
|
|
|
|
topBarStatusLabel.getStyleClass().add(statusChange.get().toString().toLowerCase());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2020-09-08 20:41:01 +02:00
|
|
|
|
|
|
|
@Event
|
2020-10-11 23:04:25 +02:00
|
|
|
private void onUserOperation(UserOperation operation) {
|
2020-10-17 16:40:13 +02:00
|
|
|
|
|
|
|
// All ADD dependent logic resides in LocalDB
|
2020-10-19 18:17:51 +02:00
|
|
|
if (operation.getOperationType().equals(ElementOperation.REMOVE))
|
|
|
|
Platform.runLater(() -> disableChat(new ContactDisabled(operation.get())));
|
2020-10-11 23:04:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Event
|
|
|
|
private void onGroupResize(GroupResize resize) {
|
|
|
|
final var chatFound = localDB.getChat(resize.getGroupID());
|
2020-10-17 16:40:13 +02:00
|
|
|
chatFound.ifPresent(chat -> Platform.runLater(() -> {
|
2020-10-11 23:04:25 +02:00
|
|
|
chatList.refresh();
|
|
|
|
|
|
|
|
// Update the top-bar status label if all conditions apply
|
2020-10-19 18:17:51 +02:00
|
|
|
if (currentChat != null && currentChat.getRecipient().equals(chat.getRecipient()))
|
|
|
|
topBarStatusLabel
|
|
|
|
.setText(chat.getRecipient().getContacts().size() + " member"
|
|
|
|
+ (currentChat.getRecipient().getContacts().size() != 1 ? "s" : ""));
|
2020-10-17 16:40:13 +02:00
|
|
|
}));
|
2020-09-08 20:41:01 +02:00
|
|
|
}
|
2020-08-22 18:14:26 +02:00
|
|
|
|
2021-02-19 11:09:07 +01:00
|
|
|
@Event(NoAttachments.class)
|
2020-09-08 20:41:01 +02:00
|
|
|
private void onNoAttachments() {
|
|
|
|
Platform.runLater(() -> {
|
|
|
|
attachmentButton.setDisable(true);
|
|
|
|
voiceButton.setDisable(true);
|
|
|
|
final var alert = new Alert(AlertType.ERROR);
|
|
|
|
alert.setTitle("No attachments possible");
|
|
|
|
alert.setHeaderText("Your current server does not support attachments.");
|
|
|
|
alert.setContentText("If this is unplanned, please contact your server administrator.");
|
|
|
|
alert.showAndWait();
|
2020-09-02 11:10:05 +02:00
|
|
|
});
|
2020-03-28 10:39:15 +01:00
|
|
|
}
|
|
|
|
|
2021-02-19 11:09:07 +01:00
|
|
|
@Event
|
|
|
|
@Priority(150)
|
2020-10-19 18:17:51 +02:00
|
|
|
private void onGroupCreationResult(GroupCreationResult result) {
|
|
|
|
Platform.runLater(() -> newGroupButton.setDisable(result.get() == null));
|
|
|
|
}
|
2020-09-08 20:41:01 +02:00
|
|
|
|
2021-02-19 11:09:07 +01:00
|
|
|
@Event(ThemeChangeEvent.class)
|
2020-09-08 20:41:01 +02:00
|
|
|
private void onThemeChange() {
|
2020-11-22 12:26:08 +01:00
|
|
|
ChatControl.reloadDefaultChatIcons();
|
2020-10-19 18:17:51 +02:00
|
|
|
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)));
|
2020-09-08 20:41:01 +02:00
|
|
|
DEFAULT_ATTACHMENT_VIEW_IMAGE = IconUtil.loadIconThemeSensitive("attachment_present", 20);
|
2020-10-19 18:17:51 +02:00
|
|
|
attachmentView.setImage(
|
|
|
|
isCustomAttachmentImage ? attachmentView.getImage() : DEFAULT_ATTACHMENT_VIEW_IMAGE);
|
|
|
|
messageSearchButton.setGraphic(
|
|
|
|
new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE)));
|
2020-09-08 20:41:01 +02:00
|
|
|
clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
|
|
|
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
|
|
|
|
messageList.setCellFactory(MessageListCell::new);
|
2020-10-07 23:43:30 +02:00
|
|
|
if (currentChat != null)
|
2020-10-19 18:17:51 +02:00
|
|
|
if (currentChat.getRecipient() instanceof User)
|
|
|
|
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
|
|
|
else
|
|
|
|
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
|
2020-09-08 20:41:01 +02:00
|
|
|
}
|
|
|
|
|
2021-02-19 11:09:07 +01:00
|
|
|
@Event(Logout.class)
|
|
|
|
@Priority(200)
|
2020-10-19 18:17:51 +02:00
|
|
|
private void onLogout() {
|
|
|
|
eventBus.removeListener(this);
|
|
|
|
}
|
2020-09-24 18:00:59 +02:00
|
|
|
|
2021-02-19 11:09:07 +01:00
|
|
|
@Event(AccountDeletion.class)
|
2020-10-19 22:01:43 +02:00
|
|
|
private void onAccountDeletion() {
|
|
|
|
Platform.runLater(chatList::refresh);
|
|
|
|
}
|
|
|
|
|
2020-07-04 15:26:12 +02:00
|
|
|
@Override
|
2020-10-19 18:17:51 +02:00
|
|
|
public void onRestore() {
|
|
|
|
updateRemainingCharsLabel();
|
|
|
|
}
|
2020-07-04 15:26:12 +02:00
|
|
|
|
2020-06-09 17:11:17 +02:00
|
|
|
/**
|
|
|
|
* Actions to perform when the list of contacts has been clicked.
|
|
|
|
*
|
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
2020-03-26 20:23:25 +01:00
|
|
|
@FXML
|
2020-07-12 17:10:22 +02:00
|
|
|
private void chatListClicked() {
|
2020-10-19 18:17:51 +02:00
|
|
|
if (chatList.getSelectionModel().isEmpty())
|
|
|
|
return;
|
2020-10-11 23:04:25 +02:00
|
|
|
final var chat = chatList.getSelectionModel().getSelectedItem();
|
2020-10-19 18:17:51 +02:00
|
|
|
if (chat == null)
|
|
|
|
return;
|
2020-07-13 19:02:40 +02:00
|
|
|
|
2020-10-11 23:04:25 +02:00
|
|
|
final var user = chat.getRecipient();
|
2020-06-12 10:48:33 +02:00
|
|
|
if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) {
|
2020-03-28 15:32:24 +01:00
|
|
|
|
|
|
|
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
|
|
|
|
|
2020-06-26 09:08:41 +02:00
|
|
|
// Load the chat
|
|
|
|
currentChat = localDB.getChat(user.getID()).get();
|
2020-03-30 21:27:07 +02:00
|
|
|
|
2020-09-25 19:19:54 +02:00
|
|
|
messageList.setItems(currentChat.getMessages());
|
2020-07-15 21:44:57 +02:00
|
|
|
final var scrollIndex = messageList.getItems().size() - currentChat.getUnreadAmount();
|
2020-07-01 19:35:15 +02:00
|
|
|
messageList.scrollTo(scrollIndex);
|
|
|
|
logger.log(Level.FINEST, "Loading chat with " + user + " at index " + scrollIndex);
|
2020-06-07 23:28:25 +02:00
|
|
|
|
2020-06-12 10:48:33 +02:00
|
|
|
// Read the current chat
|
2020-09-25 19:19:54 +02:00
|
|
|
currentChat.read(writeProxy);
|
2020-06-07 23:28:25 +02:00
|
|
|
|
2020-07-03 23:32:22 +02:00
|
|
|
// Discard the pending attachment
|
2020-07-05 12:21:07 +02:00
|
|
|
if (recorder.isRecording()) {
|
|
|
|
recorder.cancel();
|
2020-10-19 18:17:51 +02:00
|
|
|
voiceButton.setGraphic(new ImageView(
|
|
|
|
IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
|
2020-10-13 11:30:19 +02:00
|
|
|
voiceButton.setText(null);
|
2020-07-05 12:21:07 +02:00
|
|
|
}
|
2020-07-03 23:32:22 +02:00
|
|
|
pendingAttachment = null;
|
2020-07-10 20:53:28 +02:00
|
|
|
updateAttachmentView(false);
|
2020-07-03 23:32:22 +02:00
|
|
|
|
2020-06-07 23:28:25 +02:00
|
|
|
remainingChars.setVisible(true);
|
|
|
|
remainingChars
|
2020-10-19 18:17:51 +02:00
|
|
|
.setText(String.format("remaining chars: %d/%d",
|
|
|
|
MAX_MESSAGE_LENGTH - messageTextArea.getText().length(), MAX_MESSAGE_LENGTH));
|
2020-03-28 15:32:24 +01:00
|
|
|
}
|
2020-10-11 23:04:25 +02:00
|
|
|
|
|
|
|
// Enable or disable the necessary UI controls
|
|
|
|
final var chatEditable = currentChat == null || currentChat.isDisabled();
|
|
|
|
messageTextArea.setDisable(chatEditable || postingPermanentlyDisabled);
|
|
|
|
voiceButton.setDisable(!recorder.isSupported() || chatEditable);
|
|
|
|
attachmentButton.setDisable(chatEditable);
|
2020-07-12 17:10:22 +02:00
|
|
|
chatList.refresh();
|
2020-07-16 21:13:46 +02:00
|
|
|
|
2020-10-11 23:04:25 +02:00
|
|
|
// Design the top bar
|
2020-07-17 13:56:36 +02:00
|
|
|
if (currentChat != null) {
|
|
|
|
topBarContactLabel.setText(currentChat.getRecipient().getName());
|
2020-10-11 23:04:25 +02:00
|
|
|
topBarContactLabel.setVisible(true);
|
2020-10-17 16:40:13 +02:00
|
|
|
topBarStatusLabel.setVisible(true);
|
2020-07-17 13:56:36 +02:00
|
|
|
if (currentChat.getRecipient() instanceof User) {
|
2020-09-30 20:50:58 +02:00
|
|
|
final var status = ((User) currentChat.getRecipient()).getStatus().toString();
|
2020-07-17 13:56:36 +02:00
|
|
|
topBarStatusLabel.setText(status);
|
2020-10-11 23:04:25 +02:00
|
|
|
topBarStatusLabel.getStyleClass().clear();
|
2020-07-17 13:56:36 +02:00
|
|
|
topBarStatusLabel.getStyleClass().add(status.toLowerCase());
|
|
|
|
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
|
|
|
} else {
|
2020-10-19 18:17:51 +02:00
|
|
|
topBarStatusLabel
|
|
|
|
.setText(currentChat.getRecipient().getContacts().size() + " member"
|
2020-10-16 20:21:22 +02:00
|
|
|
+ (currentChat.getRecipient().getContacts().size() != 1 ? "s" : ""));
|
2020-10-08 22:07:37 +02:00
|
|
|
topBarStatusLabel.getStyleClass().clear();
|
2020-07-17 13:56:36 +02:00
|
|
|
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
|
|
|
|
}
|
2020-09-30 20:50:58 +02:00
|
|
|
final var clip = new Rectangle();
|
2020-07-17 13:56:36 +02:00
|
|
|
clip.setWidth(43);
|
|
|
|
clip.setHeight(43);
|
|
|
|
clip.setArcHeight(43);
|
|
|
|
clip.setArcWidth(43);
|
|
|
|
recipientProfilePic.setClip(clip);
|
|
|
|
messageSearchButton.setVisible(true);
|
2020-07-22 00:03:12 +02:00
|
|
|
}
|
2020-03-26 20:23:25 +01:00
|
|
|
}
|
|
|
|
|
2020-06-09 17:11:17 +02:00
|
|
|
/**
|
|
|
|
* Actions to perform when the Settings Button has been clicked.
|
|
|
|
*
|
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
2020-03-28 15:32:24 +01:00
|
|
|
@FXML
|
2020-10-19 18:17:51 +02:00
|
|
|
private void settingsButtonClicked() {
|
2020-11-06 08:58:13 +01:00
|
|
|
sceneContext.load(SETTINGS_SCENE);
|
2020-10-19 18:17:51 +02:00
|
|
|
}
|
2020-06-07 20:46:04 +02:00
|
|
|
|
2020-06-09 17:11:17 +02:00
|
|
|
/**
|
|
|
|
* Actions to perform when the "Add Contact" - Button has been clicked.
|
|
|
|
*
|
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
2020-06-07 20:46:04 +02:00
|
|
|
@FXML
|
2020-10-19 18:17:51 +02:00
|
|
|
private void addContactButtonClicked() {
|
|
|
|
tabPane.getSelectionModel().select(Tabs.CONTACT_SEARCH.ordinal());
|
|
|
|
}
|
2020-09-01 20:14:02 +02:00
|
|
|
|
2020-08-23 20:15:52 +02:00
|
|
|
@FXML
|
2020-10-19 18:17:51 +02:00
|
|
|
private void groupCreationButtonClicked() {
|
|
|
|
tabPane.getSelectionModel().select(Tabs.GROUP_CREATION.ordinal());
|
|
|
|
}
|
2020-03-28 15:32:24 +01:00
|
|
|
|
2020-07-03 23:32:22 +02:00
|
|
|
@FXML
|
|
|
|
private void voiceButtonClicked() {
|
|
|
|
new Thread(() -> {
|
|
|
|
try {
|
2020-10-13 11:30:19 +02:00
|
|
|
if (!recorder.isRecording()) {
|
2020-07-06 22:35:06 +02:00
|
|
|
Platform.runLater(() -> {
|
|
|
|
voiceButton.setText("Recording");
|
2020-10-19 18:17:51 +02:00
|
|
|
voiceButton.setGraphic(new ImageView(
|
|
|
|
IconUtil.loadIcon("microphone_recording", DEFAULT_ICON_SIZE)));
|
2020-07-06 22:35:06 +02:00
|
|
|
});
|
2020-07-03 23:32:22 +02:00
|
|
|
recorder.start();
|
|
|
|
} else {
|
2020-10-13 11:30:19 +02:00
|
|
|
pendingAttachment = new Attachment(recorder.finish(), "Voice_recording_"
|
2020-10-19 18:17:51 +02:00
|
|
|
+ DateTimeFormatter.ofPattern("yyyy_MM_dd-HH_mm_ss")
|
|
|
|
.format(LocalDateTime.now())
|
|
|
|
+ "." + AudioRecorder.FILE_FORMAT,
|
|
|
|
AttachmentType.VOICE);
|
2020-07-05 23:25:07 +02:00
|
|
|
Platform.runLater(() -> {
|
2020-10-19 18:17:51 +02:00
|
|
|
voiceButton.setGraphic(new ImageView(
|
|
|
|
IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
|
2020-07-06 22:35:06 +02:00
|
|
|
voiceButton.setText(null);
|
2020-07-05 23:25:07 +02:00
|
|
|
checkPostConditions(false);
|
2020-07-10 20:53:28 +02:00
|
|
|
updateAttachmentView(true);
|
2020-07-05 23:25:07 +02:00
|
|
|
});
|
2020-07-03 23:32:22 +02:00
|
|
|
}
|
2020-07-05 23:25:07 +02:00
|
|
|
} catch (final EnvoyException e) {
|
2020-07-05 14:25:58 +02:00
|
|
|
logger.log(Level.SEVERE, "Could not record audio: ", e);
|
2020-10-19 18:17:51 +02:00
|
|
|
Platform
|
|
|
|
.runLater(new Alert(AlertType.ERROR, "Could not record audio")::showAndWait);
|
2020-07-03 23:32:22 +02:00
|
|
|
}
|
|
|
|
}).start();
|
|
|
|
}
|
|
|
|
|
2020-07-09 16:05:09 +02:00
|
|
|
@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()
|
2020-07-09 22:47:29 +02:00
|
|
|
.addAll(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"),
|
2020-10-19 18:17:51 +02:00
|
|
|
new FileChooser.ExtensionFilter("Videos", "*.mp4"),
|
|
|
|
new FileChooser.ExtensionFilter("All Files", "*.*"));
|
2020-07-09 16:05:09 +02:00
|
|
|
final var file = fileChooser.showOpenDialog(sceneContext.getStage());
|
|
|
|
|
2020-07-09 22:47:29 +02:00
|
|
|
if (file != null) {
|
2020-07-09 16:05:09 +02:00
|
|
|
|
|
|
|
// Check max file size
|
|
|
|
if (file.length() > 16E6) {
|
2020-10-19 18:17:51 +02:00
|
|
|
new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 16MB!")
|
|
|
|
.showAndWait();
|
2020-07-09 16:05:09 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get attachment type (default is document)
|
2020-08-01 09:43:15 +02:00
|
|
|
var type = AttachmentType.DOCUMENT;
|
2020-07-09 16:05:09 +02:00
|
|
|
switch (fileChooser.getSelectedExtensionFilter().getDescription()) {
|
|
|
|
case "Pictures":
|
|
|
|
type = AttachmentType.PICTURE;
|
|
|
|
break;
|
|
|
|
case "Videos":
|
|
|
|
type = AttachmentType.VIDEO;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the pending attachment
|
|
|
|
try {
|
2020-07-10 20:53:28 +02:00
|
|
|
final var fileBytes = Files.readAllBytes(file.toPath());
|
2020-07-27 12:00:49 +02:00
|
|
|
pendingAttachment = new Attachment(fileBytes, file.getName(), type);
|
2020-07-16 18:23:06 +02:00
|
|
|
checkPostConditions(false);
|
2020-07-10 20:53:28 +02:00
|
|
|
// Setting the preview image as image of the attachmentView
|
2020-09-02 11:10:05 +02:00
|
|
|
if (type == AttachmentType.PICTURE) {
|
2020-10-19 18:17:51 +02:00
|
|
|
attachmentView.setImage(new Image(new ByteArrayInputStream(fileBytes),
|
|
|
|
DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE, true, true));
|
2020-09-02 11:10:05 +02:00
|
|
|
isCustomAttachmentImage = true;
|
|
|
|
}
|
2020-07-09 16:05:09 +02:00
|
|
|
attachmentView.setVisible(true);
|
2020-07-09 22:47:29 +02:00
|
|
|
} catch (final IOException e) {
|
2020-07-09 16:05:09 +02:00
|
|
|
new Alert(AlertType.ERROR, "The selected file could not be loaded!").showAndWait();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-23 17:18:53 +02:00
|
|
|
/**
|
2020-10-19 18:17:51 +02:00
|
|
|
* Rotates every element in our application by {@code rotations}*360° in {@code an}.
|
2020-07-23 17:18:53 +02:00
|
|
|
*
|
|
|
|
* @param rotations the amount of times the scene is rotated by 360°
|
|
|
|
* @param animationTime the time in seconds that this animation lasts
|
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
2020-10-02 15:23:21 +02:00
|
|
|
public void doABarrelRoll(int rotations, double animationTime) {
|
2020-08-01 21:40:20 +02:00
|
|
|
// Limiting the rotations and duration
|
|
|
|
rotations = Math.min(rotations, 100000);
|
|
|
|
rotations = Math.max(rotations, 1);
|
|
|
|
animationTime = Math.min(animationTime, 150);
|
|
|
|
animationTime = Math.max(animationTime, 0.25);
|
2020-08-02 20:26:22 +02:00
|
|
|
|
2020-08-01 21:40:20 +02:00
|
|
|
// contains all Node objects in ChatScene
|
2020-08-02 20:26:22 +02:00
|
|
|
final var rotatableNodes = ReflectionUtil.getAllDeclaredNodeVariables(this);
|
2020-07-11 15:23:57 +02:00
|
|
|
for (final var node : rotatableNodes) {
|
2020-07-23 17:18:53 +02:00
|
|
|
// Sets the animation duration to {animationTime}
|
2020-10-19 18:17:51 +02:00
|
|
|
final var rotateTransition =
|
|
|
|
new RotateTransition(Duration.seconds(animationTime), node);
|
2020-07-23 17:18:53 +02:00
|
|
|
// rotates every element {rotations} times
|
|
|
|
rotateTransition.setByAngle(rotations * 360);
|
2020-07-11 15:23:57 +02:00
|
|
|
rotateTransition.play();
|
|
|
|
// This is needed as for some strange reason objects could stop before being
|
|
|
|
// rotated back to 0°
|
|
|
|
rotateTransition.setOnFinished(e -> node.setRotate(0));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-13 22:38:49 +02:00
|
|
|
/**
|
2020-10-19 18:17:51 +02:00
|
|
|
* Checks the text length of the {@code messageTextArea}, adjusts the {@code remainingChars}
|
|
|
|
* label and checks whether to send the message automatically.
|
2020-06-13 22:38:49 +02:00
|
|
|
*
|
2020-06-14 16:03:02 +02:00
|
|
|
* @param e the key event that will be analyzed for a post request
|
2020-06-13 22:38:49 +02:00
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
|
|
|
@FXML
|
|
|
|
private void checkKeyCombination(KeyEvent e) {
|
2020-09-19 17:27:54 +02:00
|
|
|
|
2020-06-14 16:03:02 +02:00
|
|
|
// Checks whether the text is too long
|
2020-06-13 22:38:49 +02:00
|
|
|
messageTextUpdated();
|
2020-09-19 17:27:54 +02:00
|
|
|
|
2020-07-25 17:12:24 +02:00
|
|
|
// Sending an IsTyping event if none has been sent for
|
|
|
|
// IsTyping#millisecondsActive
|
2020-10-19 18:17:51 +02:00
|
|
|
if (client.isOnline() && currentChat.getLastWritingEvent()
|
|
|
|
+ IsTyping.millisecondsActive <= System.currentTimeMillis()) {
|
2020-10-27 23:01:44 +01:00
|
|
|
client.send(new IsTyping(currentChat.getRecipient().getID()));
|
2020-07-25 16:26:13 +02:00
|
|
|
currentChat.lastWritingEventWasNow();
|
|
|
|
}
|
2020-09-19 17:27:54 +02:00
|
|
|
|
|
|
|
// KeyPressed will be called before the char has been added to the text, hence
|
|
|
|
// this is needed for the first char
|
2020-10-19 18:17:51 +02:00
|
|
|
if (messageTextArea.getText().length() == 1 && e != null)
|
|
|
|
checkPostConditions(e);
|
2020-09-20 16:06:13 +02:00
|
|
|
|
2020-09-20 15:27:23 +02:00
|
|
|
// This is needed for the messageTA context menu
|
2020-10-19 18:17:51 +02:00
|
|
|
else if (e == null)
|
|
|
|
checkPostConditions(false);
|
2020-06-13 22:38:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param e the keys that have been pressed
|
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
|
|
|
@FXML
|
|
|
|
private void checkPostConditions(KeyEvent e) {
|
2020-09-20 16:06:13 +02:00
|
|
|
final var enterPressed = e.getCode() == KeyCode.ENTER;
|
2020-10-19 18:17:51 +02:00
|
|
|
final var messagePosted =
|
|
|
|
enterPressed ? settings.isEnterToSend() ? !e.isControlDown() : e.isControlDown()
|
|
|
|
: false;
|
2020-09-19 17:27:54 +02:00
|
|
|
if (messagePosted) {
|
|
|
|
|
|
|
|
// Removing an inserted line break if added by pressing enter
|
|
|
|
final var text = messageTextArea.getText();
|
2020-09-20 16:06:13 +02:00
|
|
|
final var textPosition = messageTextArea.getCaretPosition() - 1;
|
|
|
|
if (!e.isControlDown() && !text.isEmpty() && text.charAt(textPosition) == '\n')
|
2020-10-19 18:17:51 +02:00
|
|
|
messageTextArea
|
|
|
|
.setText(new StringBuilder(text).deleteCharAt(textPosition).toString());
|
2020-09-20 16:06:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// if control is pressed, the enter press is originally invalidated. Here it'll
|
|
|
|
// be inserted again
|
|
|
|
else if (enterPressed && e.isControlDown()) {
|
|
|
|
var caretPosition = messageTextArea.getCaretPosition();
|
2020-10-19 18:17:51 +02:00
|
|
|
messageTextArea.setText(new StringBuilder(messageTextArea.getText())
|
|
|
|
.insert(caretPosition, '\n').toString());
|
2020-09-20 16:06:13 +02:00
|
|
|
messageTextArea.positionCaret(++caretPosition);
|
2020-09-19 17:27:54 +02:00
|
|
|
}
|
|
|
|
checkPostConditions(messagePosted);
|
2020-07-03 23:32:22 +02:00
|
|
|
}
|
|
|
|
|
2020-07-16 18:23:06 +02:00
|
|
|
private void checkPostConditions(boolean postMessage) {
|
2020-06-26 23:16:03 +02:00
|
|
|
if (!postingPermanentlyDisabled) {
|
2020-10-19 18:17:51 +02:00
|
|
|
if (!postButton.isDisabled() && postMessage)
|
|
|
|
postMessage();
|
|
|
|
postButton.setDisable(messageTextArea.getText().isBlank() && pendingAttachment == null
|
|
|
|
|| currentChat == null);
|
2020-06-27 09:34:30 +02:00
|
|
|
} else {
|
|
|
|
final var noMoreMessaging = "Go online to send messages";
|
|
|
|
if (!infoLabel.getText().equals(noMoreMessaging))
|
|
|
|
// Informing the user that he is a f*cking moron and should use Envoy online
|
|
|
|
// because he ran out of messageIDs to use
|
2020-09-01 20:57:18 +02:00
|
|
|
updateInfoLabel(noMoreMessaging, "info-label-error");
|
2020-06-27 09:34:30 +02:00
|
|
|
}
|
2020-06-13 22:38:49 +02:00
|
|
|
}
|
|
|
|
|
2020-06-09 17:11:17 +02:00
|
|
|
/**
|
|
|
|
* Actions to perform when the text was updated in the messageTextArea.
|
|
|
|
*
|
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
2020-03-28 15:32:24 +01:00
|
|
|
@FXML
|
2020-06-09 17:11:17 +02:00
|
|
|
private void messageTextUpdated() {
|
2020-06-07 23:28:25 +02:00
|
|
|
// Truncating messages that are too long and staying at the same position
|
|
|
|
if (messageTextArea.getText().length() >= MAX_MESSAGE_LENGTH) {
|
|
|
|
messageTextArea.setText(messageTextArea.getText().substring(0, MAX_MESSAGE_LENGTH));
|
|
|
|
messageTextArea.positionCaret(MAX_MESSAGE_LENGTH);
|
|
|
|
messageTextArea.setScrollTop(Double.MAX_VALUE);
|
|
|
|
}
|
2020-06-14 16:11:46 +02:00
|
|
|
updateRemainingCharsLabel();
|
2020-03-28 15:32:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-06-14 16:03:02 +02:00
|
|
|
* Sets the text and text color of the {@code remainingChars} label.
|
2020-03-28 15:32:24 +01:00
|
|
|
*
|
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
2020-06-14 16:11:46 +02:00
|
|
|
private void updateRemainingCharsLabel() {
|
2020-08-01 09:43:15 +02:00
|
|
|
final var currentLength = messageTextArea.getText().length();
|
|
|
|
final var remainingLength = MAX_MESSAGE_LENGTH - currentLength;
|
2020-10-19 18:17:51 +02:00
|
|
|
remainingChars
|
|
|
|
.setText(String.format("remaining chars: %d/%d", remainingLength, MAX_MESSAGE_LENGTH));
|
2020-06-13 22:38:49 +02:00
|
|
|
remainingChars.setTextFill(Color.rgb(currentLength, remainingLength, 0, 1));
|
2020-03-28 15:32:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-10-19 18:17:51 +02:00
|
|
|
* Sends a new {@link Message} or {@link GroupMessage} to the server based on the text entered
|
|
|
|
* in the {@code messageTextArea} and the given attachment.
|
2020-03-28 15:32:24 +01:00
|
|
|
*
|
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
2020-06-10 13:05:44 +02:00
|
|
|
@FXML
|
2020-03-28 15:32:24 +01:00
|
|
|
private void postMessage() {
|
2020-06-26 23:16:03 +02:00
|
|
|
postingPermanentlyDisabled = !(client.isOnline() || localDB.getIDGenerator().hasNext());
|
|
|
|
if (postingPermanentlyDisabled) {
|
|
|
|
postButton.setDisable(true);
|
2020-06-27 09:34:30 +02:00
|
|
|
messageTextArea.setDisable(true);
|
|
|
|
messageTextArea.clear();
|
2020-09-01 20:57:18 +02:00
|
|
|
updateInfoLabel("You need to go online to send more messages", "info-label-error");
|
2020-06-26 23:16:03 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-07-25 16:26:13 +02:00
|
|
|
final var text = messageTextArea.getText().strip();
|
2020-10-06 22:00:55 +02:00
|
|
|
if (!commands.getChatSceneCommands().executeIfPresent(text)) {
|
2020-07-05 17:01:11 +02:00
|
|
|
// Creating the message and its metadata
|
2020-10-19 18:17:51 +02:00
|
|
|
final var builder = new MessageBuilder(localDB.getUser().getID(),
|
|
|
|
currentChat.getRecipient().getID(), localDB.getIDGenerator())
|
|
|
|
.setText(text);
|
2020-07-09 16:05:09 +02:00
|
|
|
// Setting an attachment, if present
|
|
|
|
if (pendingAttachment != null) {
|
2020-07-03 23:32:22 +02:00
|
|
|
builder.setAttachment(pendingAttachment);
|
|
|
|
pendingAttachment = null;
|
2020-07-10 20:53:28 +02:00
|
|
|
updateAttachmentView(false);
|
2020-07-03 23:32:22 +02:00
|
|
|
}
|
2020-07-09 16:05:09 +02:00
|
|
|
// Building the final message
|
2020-10-19 18:17:51 +02:00
|
|
|
final var message = currentChat.getRecipient() instanceof Group
|
|
|
|
? builder.buildGroupMessage((Group) currentChat.getRecipient())
|
|
|
|
: builder.build();
|
2020-03-28 15:32:24 +01:00
|
|
|
|
|
|
|
// Send message
|
|
|
|
writeProxy.writeMessage(message);
|
|
|
|
|
|
|
|
// Add message to LocalDB and update UI
|
2020-07-01 08:45:39 +02:00
|
|
|
currentChat.insert(message);
|
2020-07-11 14:18:38 +02:00
|
|
|
// Moving currentChat to the top
|
|
|
|
Platform.runLater(() -> {
|
2020-07-31 18:46:32 +02:00
|
|
|
chats.getSource().remove(currentChat);
|
2020-07-31 22:52:42 +02:00
|
|
|
((ObservableList<Chat>) chats.getSource()).add(0, currentChat);
|
2020-07-11 14:18:38 +02:00
|
|
|
localDB.getChats().remove(currentChat);
|
|
|
|
localDB.getChats().add(0, currentChat);
|
2020-10-11 23:04:25 +02:00
|
|
|
chatList.getSelectionModel().select(0);
|
2020-07-11 14:18:38 +02:00
|
|
|
});
|
2020-06-22 20:53:44 +02:00
|
|
|
scrollToMessageListEnd();
|
2020-03-28 15:32:24 +01:00
|
|
|
|
|
|
|
// Request a new ID generator if all IDs were used
|
2020-10-19 18:17:51 +02:00
|
|
|
if (!localDB.getIDGenerator().hasNext() && client.isOnline())
|
|
|
|
client.requestIDGenerator();
|
2020-03-28 15:32:24 +01:00
|
|
|
}
|
2020-06-10 13:05:44 +02:00
|
|
|
|
|
|
|
// Clear text field and disable post button
|
|
|
|
messageTextArea.setText("");
|
|
|
|
postButton.setDisable(true);
|
2020-06-14 16:11:46 +02:00
|
|
|
updateRemainingCharsLabel();
|
2020-09-02 11:10:05 +02:00
|
|
|
isCustomAttachmentImage = false;
|
2020-03-26 20:23:25 +01:00
|
|
|
}
|
2020-06-22 20:53:44 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Scrolls to the bottom of the {@code messageList}.
|
|
|
|
*
|
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
2020-10-19 18:17:51 +02:00
|
|
|
private void scrollToMessageListEnd() {
|
|
|
|
messageList.scrollTo(messageList.getItems().size() - 1);
|
|
|
|
}
|
2020-06-22 22:51:10 +02:00
|
|
|
|
2020-06-26 23:16:03 +02:00
|
|
|
/**
|
|
|
|
* Updates the {@code infoLabel}.
|
|
|
|
*
|
2020-07-01 19:35:15 +02:00
|
|
|
* @param text the text to use
|
2020-10-19 18:17:51 +02:00
|
|
|
* @param infoLabelID the id the the {@code infoLabel} should have so that it can be styled
|
|
|
|
* accordingly in CSS
|
2020-06-26 23:16:03 +02:00
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
2020-07-01 19:35:15 +02:00
|
|
|
private void updateInfoLabel(String text, String infoLabelID) {
|
2020-06-26 23:16:03 +02:00
|
|
|
infoLabel.setText(text);
|
2020-07-01 19:35:15 +02:00
|
|
|
infoLabel.setId(infoLabelID);
|
2020-06-26 23:16:03 +02:00
|
|
|
infoLabel.setVisible(true);
|
|
|
|
}
|
|
|
|
|
2020-07-10 20:53:28 +02:00
|
|
|
/**
|
2020-07-10 23:05:57 +02:00
|
|
|
* Updates the {@code attachmentView} in terms of visibility.<br>
|
2020-10-19 18:17:51 +02:00
|
|
|
* Additionally resets the shown image to {@code DEFAULT_ATTACHMENT_VIEW_IMAGE} if another image
|
|
|
|
* is currently present.
|
2020-07-10 20:53:28 +02:00
|
|
|
*
|
|
|
|
* @param visible whether the {@code attachmentView} should be displayed
|
|
|
|
* @since Envoy Client v0.1-beta
|
|
|
|
*/
|
|
|
|
private void updateAttachmentView(boolean visible) {
|
2020-10-19 18:17:51 +02:00
|
|
|
if (!(attachmentView.getImage() == null
|
|
|
|
|| attachmentView.getImage().equals(DEFAULT_ATTACHMENT_VIEW_IMAGE)))
|
2020-10-11 23:04:25 +02:00
|
|
|
attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
|
2020-07-10 20:53:28 +02:00
|
|
|
attachmentView.setVisible(visible);
|
|
|
|
}
|
|
|
|
|
2021-02-19 11:09:07 +01:00
|
|
|
@Event(OwnStatusChange.class)
|
|
|
|
@Priority(50)
|
2020-10-08 16:12:24 +02:00
|
|
|
private void generateOwnStatusControl() {
|
2020-10-10 11:10:17 +02:00
|
|
|
|
|
|
|
// Update the own user status if present
|
2020-10-22 16:09:09 +02:00
|
|
|
if (ownContactControl.getChildren().get(1) instanceof ContactControl)
|
|
|
|
((ContactControl) ownContactControl.getChildren().get(1)).replaceInfoLabel();
|
2020-10-10 11:10:17 +02:00
|
|
|
else {
|
|
|
|
|
|
|
|
// Else prepend it to the HBox children
|
|
|
|
final var ownUserControl = new ContactControl(localDB.getUser());
|
|
|
|
ownUserControl.setAlignment(Pos.CENTER_LEFT);
|
2021-02-19 11:09:07 +01:00
|
|
|
HBox.setHgrow(ownUserControl, javafx.scene.layout.Priority.NEVER);
|
2020-10-22 16:09:09 +02:00
|
|
|
ownContactControl.getChildren().add(1, ownUserControl);
|
2020-10-10 11:10:17 +02:00
|
|
|
}
|
2020-10-08 16:12:24 +02:00
|
|
|
}
|
|
|
|
|
2020-10-11 23:04:25 +02:00
|
|
|
/**
|
2020-10-19 18:17:51 +02:00
|
|
|
* Redesigns the UI when the {@link Chat} of the given contact has been marked as disabled.
|
2020-10-11 23:04:25 +02:00
|
|
|
*
|
2020-10-17 16:40:13 +02:00
|
|
|
* @param event the contact whose chat got disabled
|
2020-10-11 23:04:25 +02:00
|
|
|
* @since Envoy Client v0.3-beta
|
|
|
|
*/
|
2020-10-17 16:40:13 +02:00
|
|
|
@Event
|
|
|
|
public void disableChat(ContactDisabled event) {
|
|
|
|
chatList.refresh();
|
|
|
|
final var recipient = event.get();
|
2020-10-16 20:21:22 +02:00
|
|
|
|
2020-10-17 16:40:13 +02:00
|
|
|
// Decrement member count for groups
|
|
|
|
if (recipient instanceof Group)
|
2020-10-19 18:17:51 +02:00
|
|
|
topBarStatusLabel.setText(recipient.getContacts().size() + " member"
|
|
|
|
+ (recipient.getContacts().size() != 1 ? "s" : ""));
|
2020-10-11 23:04:25 +02:00
|
|
|
if (currentChat != null && currentChat.getRecipient().equals(recipient)) {
|
|
|
|
messageTextArea.setDisable(true);
|
|
|
|
voiceButton.setDisable(true);
|
|
|
|
attachmentButton.setDisable(true);
|
|
|
|
pendingAttachment = null;
|
|
|
|
messageList.getStyleClass().clear();
|
|
|
|
messageList.getStyleClass().add("disabled-chat");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resets every component back to its inital state before a chat was selected.
|
|
|
|
*
|
|
|
|
* @since Envoy Client v0.3-beta
|
|
|
|
*/
|
|
|
|
public void resetState() {
|
|
|
|
currentChat = null;
|
|
|
|
chatList.getSelectionModel().clearSelection();
|
|
|
|
messageList.getItems().clear();
|
|
|
|
messageTextArea.setDisable(true);
|
|
|
|
attachmentView.setImage(null);
|
|
|
|
topBarContactLabel.setVisible(false);
|
|
|
|
topBarStatusLabel.setVisible(false);
|
|
|
|
messageSearchButton.setVisible(false);
|
|
|
|
messageTextArea.clear();
|
|
|
|
messageTextArea.setDisable(true);
|
|
|
|
attachmentButton.setDisable(true);
|
|
|
|
voiceButton.setDisable(true);
|
|
|
|
remainingChars.setVisible(false);
|
|
|
|
pendingAttachment = null;
|
|
|
|
recipientProfilePic.setImage(null);
|
2020-10-19 18:17:51 +02:00
|
|
|
if (recorder.isRecording())
|
|
|
|
recorder.cancel();
|
2020-10-11 23:04:25 +02:00
|
|
|
}
|
2020-06-23 18:32:34 +02:00
|
|
|
|
|
|
|
@FXML
|
|
|
|
private void copyAndPostMessage() {
|
2020-06-25 11:17:25 +02:00
|
|
|
final var messageText = messageTextArea.getText();
|
2020-10-19 18:17:51 +02:00
|
|
|
Toolkit.getDefaultToolkit().getSystemClipboard()
|
|
|
|
.setContents(new StringSelection(messageText), null);
|
2020-07-18 09:48:08 +02:00
|
|
|
final var image = attachmentView.getImage();
|
|
|
|
final var messageAttachment = pendingAttachment;
|
2020-06-25 11:17:25 +02:00
|
|
|
postMessage();
|
|
|
|
messageTextArea.setText(messageText);
|
|
|
|
updateRemainingCharsLabel();
|
|
|
|
postButton.setDisable(messageText.isBlank());
|
2020-07-18 09:48:08 +02:00
|
|
|
attachmentView.setImage(image);
|
2020-10-19 18:17:51 +02:00
|
|
|
if (attachmentView.getImage() != null)
|
|
|
|
attachmentView.setVisible(true);
|
2020-07-18 09:48:08 +02:00
|
|
|
pendingAttachment = messageAttachment;
|
2020-03-26 20:23:25 +01:00
|
|
|
}
|
2020-07-31 22:52:42 +02:00
|
|
|
|
2020-09-30 20:50:58 +02:00
|
|
|
/**
|
|
|
|
* Clears the current message selection.
|
|
|
|
*
|
|
|
|
* @since Envoy Client v0.3-beta
|
|
|
|
*/
|
2020-10-19 18:17:51 +02:00
|
|
|
public void clearMessageSelection() {
|
|
|
|
messageList.getSelectionModel().clearSelection();
|
|
|
|
}
|
2020-09-30 20:50:58 +02:00
|
|
|
|
2020-07-31 18:46:32 +02:00
|
|
|
@FXML
|
|
|
|
private void searchContacts() {
|
|
|
|
chats.setPredicate(contactSearch.getText().isBlank() ? c -> true
|
2020-10-19 18:17:51 +02:00
|
|
|
: c -> c.getRecipient().getName().toLowerCase()
|
|
|
|
.contains(contactSearch.getText().toLowerCase()));
|
2020-07-31 18:46:32 +02:00
|
|
|
}
|
2020-10-19 22:01:43 +02:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public Map<KeyCombination, Runnable> getKeyboardShortcuts() {
|
2020-10-19 22:16:18 +02:00
|
|
|
return Map.<KeyCombination, Runnable>of(
|
2020-10-19 22:01:43 +02:00
|
|
|
|
2020-10-19 22:16:18 +02:00
|
|
|
// Delete text before the caret with "Control" + U
|
|
|
|
new KeyCodeCombination(KeyCode.U, KeyCombination.CONTROL_DOWN), () -> {
|
|
|
|
messageTextArea
|
|
|
|
.setText(
|
|
|
|
messageTextArea.getText().substring(messageTextArea.getCaretPosition()));
|
|
|
|
checkPostConditions(false);
|
2020-10-19 22:01:43 +02:00
|
|
|
|
2020-10-19 22:16:18 +02:00
|
|
|
// Delete text after the caret with "Control" + K
|
|
|
|
}, new KeyCodeCombination(KeyCode.K, KeyCombination.CONTROL_DOWN), () -> {
|
|
|
|
messageTextArea
|
|
|
|
.setText(
|
|
|
|
messageTextArea.getText().substring(0, messageTextArea.getCaretPosition()));
|
|
|
|
checkPostConditions(false);
|
|
|
|
messageTextArea.positionCaret(messageTextArea.getText().length());
|
|
|
|
});
|
2020-10-19 22:01:43 +02:00
|
|
|
}
|
2020-03-26 20:23:25 +01:00
|
|
|
}
|