This repository has been archived on 2021-12-05. You can view files and clone it, but cannot push or open issues or pull requests.
envoy/src/main/java/envoy/client/ui/controller/ChatScene.java

400 lines
12 KiB
Java
Raw Normal View History

package envoy.client.ui.controller;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.control.*;
2020-06-10 13:05:44 +02:00
import javafx.scene.control.Alert.AlertType;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import envoy.client.data.Chat;
import envoy.client.data.LocalDB;
import envoy.client.data.Settings;
import envoy.client.event.MessageCreationEvent;
import envoy.client.net.Client;
import envoy.client.net.WriteProxy;
import envoy.client.ui.IconUtil;
import envoy.client.ui.SceneContext;
import envoy.client.ui.listcell.ContactListCellFactory;
import envoy.client.ui.listcell.MessageControl;
import envoy.client.ui.listcell.MessageListCellFactory;
import envoy.data.*;
import envoy.event.EventBus;
import envoy.event.MessageStatusChange;
import envoy.event.UserStatusChange;
import envoy.event.contact.ContactOperation;
import envoy.util.EnvoyLog;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>ChatSceneController.java</strong><br>
* Created: <strong>26.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public final class ChatScene {
@FXML
private Label contactLabel;
@FXML
private ListView<Message> messageList;
@FXML
private ListView<Contact> userList;
@FXML
private Button postButton;
@FXML
private Button settingsButton;
@FXML
private TextArea messageTextArea;
@FXML
private Label remainingChars;
@FXML
private Label infoLabel;
@FXML
private MenuItem deleteContactMenuItem;
private LocalDB localDB;
private Client client;
private WriteProxy writeProxy;
private SceneContext sceneContext;
private boolean postingPermanentlyDisabled = false;
private Chat currentChat;
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;
/**
* Initializes the appearance of certain visual components.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void initialize() {
// Initialize message and user rendering
messageList.setCellFactory(MessageListCellFactory::new);
userList.setCellFactory(ContactListCellFactory::new);
settingsButton.setGraphic(new ImageView(IconUtil.load("/icons/settings.png", 16)));
// Listen to received messages
eventBus.register(MessageCreationEvent.class, e -> {
2020-06-10 13:05:44 +02:00
final var message = e.get();
localDB.getChat(message.getSenderID()).ifPresent(chat -> {
chat.insert(message);
2020-06-10 13:05:44 +02:00
if (chat.equals(currentChat)) {
try {
currentChat.read(writeProxy);
2020-06-18 22:20:34 +02:00
} catch (final IOException e1) {
logger.log(Level.WARNING, "Could not read current chat: ", e1);
}
Platform.runLater(() -> { messageList.refresh(); scrollToMessageListEnd(); });
}
2020-06-10 13:05:44 +02:00
});
});
// Listen to message status changes
eventBus.register(MessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(message -> {
message.setStatus(e.get());
// Update UI if in current chat
2020-06-09 15:41:01 +02:00
if (currentChat != null && message.getSenderID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh);
2020-06-10 13:05:44 +02:00
}));
2020-06-09 15:41:01 +02:00
// Listen to user status changes
eventBus.register(UserStatusChange.class,
2020-06-13 22:36:52 +02:00
e -> userList.getItems()
.stream()
.filter(c -> c.getID() == e.getID())
.findAny()
.ifPresent(u -> { ((User) u).setStatus(e.get()); Platform.runLater(userList::refresh); }));
// Listen to contacts changes
eventBus.register(ContactOperation.class, e -> {
final var contact = e.get();
2020-06-09 15:41:01 +02:00
switch (e.getOperationType()) {
case ADD:
localDB.getUsers().put(contact.getName(), contact);
localDB.getChats().add(new Chat(contact));
Platform.runLater(() -> userList.getItems().add(contact));
break;
case REMOVE:
localDB.getUsers().remove(contact.getName());
localDB.getChats().removeIf(c -> c.getRecipient().getID() == contact.getID());
Platform.runLater(() -> userList.getItems().removeIf(c -> c.getID() == contact.getID()));
break;
}
});
}
/**
* Initializes all necessary data via dependency injection-
*
* @param sceneContext the scene context used to load other scenes
* @param localDB the local database form which chats and users are loaded
* @param client the client used to request ID generators
* @param writeProxy the write proxy used to send messages and other data to
* the server
* @since Envoy Client v0.1-beta
*/
public void initializeData(SceneContext sceneContext, LocalDB localDB, Client client, WriteProxy writeProxy) {
this.sceneContext = sceneContext;
this.localDB = localDB;
this.client = client;
this.writeProxy = writeProxy;
2020-06-09 14:12:27 +02:00
userList.setItems(FXCollections.observableList(localDB.getChats().stream().map(Chat::getRecipient).collect(Collectors.toList())));
contactLabel.setText(localDB.getUser().getName());
MessageControl.setUser(localDB.getUser());
if (!client.isOnline()) updateInfoLabel("You are offline", "infoLabel-info");
}
/**
* Actions to perform when the list of contacts has been clicked.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void userListClicked() {
final Contact user = userList.getSelectionModel().getSelectedItem();
if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) {
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
// Load the chat
currentChat = localDB.getChat(user.getID()).get();
messageList.setItems(FXCollections.observableList(currentChat.getMessages()));
final var scrollIndex = messageList.getItems().size() - 1;
messageList.scrollTo(scrollIndex);
logger.log(Level.FINEST, "Loading chat with " + user + " at index " + scrollIndex);
deleteContactMenuItem.setText("Delete " + user.getName());
// Read the current chat
try {
currentChat.read(writeProxy);
2020-06-18 22:20:34 +02:00
} catch (final IOException e) {
logger.log(Level.WARNING, "Could not read current chat.", e);
}
remainingChars.setVisible(true);
remainingChars
.setText(String.format("remaining chars: %d/%d", MAX_MESSAGE_LENGTH - messageTextArea.getText().length(), MAX_MESSAGE_LENGTH));
}
messageTextArea.setDisable(currentChat == null || postingPermanentlyDisabled);
}
/**
* Actions to perform when the Settings Button has been clicked.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void settingsButtonClicked() {
sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE);
sceneContext.<SettingsScene>getController().initializeData(sceneContext);
}
/**
* Actions to perform when the "Add Contact" - Button has been clicked.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void addContactButtonClicked() {
sceneContext.load(SceneContext.SceneInfo.CONTACT_SEARCH_SCENE);
sceneContext.<ContactSearchScene>getController().initializeData(sceneContext, localDB);
}
/**
* Checks the text length of the {@code messageTextArea}, adjusts the
* {@code remainingChars} label and checks whether to send the message
* automatically.
*
* @param e the key event that will be analyzed for a post request
* @since Envoy Client v0.1-beta
*/
@FXML
private void checkKeyCombination(KeyEvent e) {
// Checks whether the text is too long
messageTextUpdated();
// Automatic sending of messages via (ctrl +) enter
checkPostConditions(e);
}
/**
* @param e the keys that have been pressed
* @since Envoy Client v0.1-beta
*/
@FXML
private void checkPostConditions(KeyEvent e) {
if (!postingPermanentlyDisabled) {
if (!postButton.isDisabled() && (settings.isEnterToSend() && e.getCode() == KeyCode.ENTER
|| !settings.isEnterToSend() && e.getCode() == KeyCode.ENTER && e.isControlDown()))
postMessage();
postButton.setDisable(messageTextArea.getText().isBlank() || currentChat == null);
} 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
updateInfoLabel(noMoreMessaging, "infoLabel-error");
}
}
/**
* Actions to perform when the text was updated in the messageTextArea.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void messageTextUpdated() {
// 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);
}
updateRemainingCharsLabel();
}
/**
* Sets the text and text color of the {@code remainingChars} label.
*
* @since Envoy Client v0.1-beta
*/
private void updateRemainingCharsLabel() {
final int currentLength = messageTextArea.getText().length();
final int remainingLength = MAX_MESSAGE_LENGTH - currentLength;
remainingChars.setText(String.format("remaining chars: %d/%d", remainingLength, MAX_MESSAGE_LENGTH));
remainingChars.setTextFill(Color.rgb(currentLength, remainingLength, 0, 1));
}
/**
* Sends a new message to the server based on the text entered in the
* messageTextArea.
*
* @since Envoy Client v0.1-beta
*/
2020-06-10 13:05:44 +02:00
@FXML
private void postMessage() {
postingPermanentlyDisabled = !(client.isOnline() || localDB.getIDGenerator().hasNext());
if (postingPermanentlyDisabled) {
postButton.setDisable(true);
messageTextArea.setDisable(true);
messageTextArea.clear();
updateInfoLabel("You need to go online to send more messages", "infoLabel-error");
return;
}
final var text = messageTextArea.getText().strip();
if (text.isBlank()) throw new IllegalArgumentException("A message without visible text can not be sent.");
try {
2020-06-10 13:05:44 +02:00
// Create and send message
final var message = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
.setText(text)
2020-06-10 13:05:44 +02:00
.build();
// Send message
writeProxy.writeMessage(message);
// Add message to LocalDB and update UI
currentChat.insert(message);
messageList.refresh();
scrollToMessageListEnd();
// Request a new ID generator if all IDs were used
if (!localDB.getIDGenerator().hasNext() && client.isOnline()) client.requestIdGenerator();
} catch (final IOException e) {
logger.log(Level.SEVERE, "Error while sending message: ", e);
2020-06-10 13:05:44 +02:00
new Alert(AlertType.ERROR, "An error occured while sending the message!").showAndWait();
}
2020-06-10 13:05:44 +02:00
// Clear text field and disable post button
messageTextArea.setText("");
postButton.setDisable(true);
updateRemainingCharsLabel();
}
/**
* Scrolls to the bottom of the {@code messageList}.
*
* @since Envoy Client v0.1-beta
*/
private void scrollToMessageListEnd() { messageList.scrollTo(messageList.getItems().size() - 1); }
/**
* Updates the {@code infoLabel}.
*
* @param text the text to use
* @param infoLabelID the id the the {@code infoLabel} should have so that it
* can be styled accordingly in CSS
* @since Envoy Client v0.1-beta
*/
private void updateInfoLabel(String text, String infoLabelID) {
infoLabel.setText(text);
infoLabel.setId(infoLabelID);
infoLabel.setVisible(true);
}
// Context menu actions
@FXML
private void copyMessage() {
try {
Toolkit.getDefaultToolkit()
.getSystemClipboard()
.setContents(new StringSelection(messageList.getSelectionModel().getSelectedItem().getText()), null);
} catch (final NullPointerException e) {}
}
@FXML
private void deleteMessage() { try {} catch (final NullPointerException e) {} }
@FXML
private void forwardMessage() { try {} catch (final NullPointerException e) {} }
@FXML
private void quoteMessage() { try {} catch (final NullPointerException e) {} }
@FXML
private void deleteContact() { try {} catch (final NullPointerException e) {} }
@FXML
private void copyAndPostMessage() {
final var messageText = messageTextArea.getText();
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(messageText), null);
postMessage();
messageTextArea.setText(messageText);
updateRemainingCharsLabel();
postButton.setDisable(messageText.isBlank());
}
@FXML
private void loadMessageInfoScene() { try {} catch (final NullPointerException e) {} }
}