diff --git a/src/main/java/envoy/client/data/LocalDB.java b/src/main/java/envoy/client/data/LocalDB.java index a9b0665..60bf7c0 100644 --- a/src/main/java/envoy/client/data/LocalDB.java +++ b/src/main/java/envoy/client/data/LocalDB.java @@ -148,12 +148,22 @@ public abstract class LocalDB { * Searches for a message by ID. * * @param id the ID of the message to search for - * @return the message with the corresponding ID, or {@code null} if no message - * has been found + * @return an optional containing the message * @since Envoy Client v0.1-beta */ - public Message getMessage(long id) { - return chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny().orElse(null); + public Optional getMessage(long id) { + return chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny(); + } + + /** + * Searches for a chat by recipient ID. + * + * @param recipientID the ID of the chat's recipient + * @return an optional containing the chat + * @since Envoy Client v0.1-beta + */ + public Optional getChat(long recipientID) { + return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny(); } /** diff --git a/src/main/java/envoy/client/net/Client.java b/src/main/java/envoy/client/net/Client.java index fcf9dd0..7aaf28b 100644 --- a/src/main/java/envoy/client/net/Client.java +++ b/src/main/java/envoy/client/net/Client.java @@ -91,7 +91,7 @@ public class Client implements Closeable { SerializationUtils.writeBytesWithLength(credentials, socket.getOutputStream()); // Wait for a maximum of five seconds to acquire the sender object - long start = System.currentTimeMillis(); + final long start = System.currentTimeMillis(); while (sender == null) { // Quit immediately after handshake rejection @@ -141,7 +141,7 @@ public class Client implements Closeable { receiver.registerProcessor(MessageStatusChangeEvent.class, new MessageStatusChangeEventProcessor()); // Process user status changes - receiver.registerProcessor(UserStatusChangeEvent.class, new UserStatusChangeProcessor(localDB)); + receiver.registerProcessor(UserStatusChangeEvent.class, eventBus::dispatch); // Process message ID generation receiver.registerProcessor(IDGenerator.class, localDB::setIDGenerator); @@ -162,7 +162,7 @@ public class Client implements Closeable { eventBus.register(SendEvent.class, evt -> { try { sendEvent(evt.get()); - } catch (IOException e) { + } catch (final IOException e) { e.printStackTrace(); } }); @@ -221,7 +221,7 @@ public class Client implements Closeable { */ public Map getUsers() { checkOnline(); - Map users = new HashMap<>(); + final Map users = new HashMap<>(); contacts.forEach(u -> users.put(u.getName(), u)); users.put(sender.getName(), sender); return users; diff --git a/src/main/java/envoy/client/net/UserStatusChangeProcessor.java b/src/main/java/envoy/client/net/UserStatusChangeProcessor.java deleted file mode 100644 index 9120662..0000000 --- a/src/main/java/envoy/client/net/UserStatusChangeProcessor.java +++ /dev/null @@ -1,33 +0,0 @@ -package envoy.client.net; - -import java.util.function.Consumer; - -import envoy.client.data.LocalDB; -import envoy.data.User; -import envoy.event.EventBus; -import envoy.event.UserStatusChangeEvent; - -/** - * Project: envoy-client
- * File: UserStatusChangeProcessor.java
- * Created: 2 Feb 2020
- * - * @author Leon Hofmeister - * @since Envoy Client v0.3-alpha - */ -public class UserStatusChangeProcessor implements Consumer { - - private final LocalDB localDB; - - /** - * @param localDB the local database in which status updates will by applied - * @since Envoy Client v0.3-alpha - */ - public UserStatusChangeProcessor(LocalDB localDB) { this.localDB = localDB; } - - @Override - public void accept(UserStatusChangeEvent evt) { - localDB.getUsers().values().stream().filter(u -> u.getID() == evt.getID()).map(User.class::cast).findFirst().get().setStatus(evt.get()); - EventBus.getInstance().dispatch(evt); - } -} diff --git a/src/main/java/envoy/client/net/WriteProxy.java b/src/main/java/envoy/client/net/WriteProxy.java index 6e192f8..97848df 100644 --- a/src/main/java/envoy/client/net/WriteProxy.java +++ b/src/main/java/envoy/client/net/WriteProxy.java @@ -49,7 +49,7 @@ public class WriteProxy { client.sendMessage(msg); // Update message state to SENT in localDB - localDB.getMessage(msg.getID()).nextStatus(); + localDB.getMessage(msg.getID()).ifPresent(Message::nextStatus); } catch (IOException e) { logger.log(Level.SEVERE, "Could not send cached message", e); } diff --git a/src/main/java/envoy/client/ui/ContactListCell.java b/src/main/java/envoy/client/ui/ContactListCell.java index eb6e3c1..5cb04ca 100644 --- a/src/main/java/envoy/client/ui/ContactListCell.java +++ b/src/main/java/envoy/client/ui/ContactListCell.java @@ -3,8 +3,10 @@ package envoy.client.ui; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; import envoy.data.Contact; +import envoy.data.Group; import envoy.data.User; /** @@ -20,15 +22,42 @@ public class ContactListCell extends ListCell { /** * Displays the name of a contact. If the contact is a user, their online status * is displayed as well. - * + * * @since Envoy Client v0.1-beta */ @Override protected void updateItem(Contact contact, boolean empty) { super.updateItem(contact, empty); - if (!empty && contact != null) { - final var name = new Label(contact.getName()); - setGraphic(contact instanceof User ? new VBox(name, new Label(((User) contact).getStatus().toString())) : new VBox(name)); + if (empty || contact == null) { + setText(null); + setGraphic(null); + } else { + // the infoLabel displays specific contact info, i.e. status of a user or amount + // of members in a group + Label infoLabel = null; + if (contact instanceof User) { + // user specific info + infoLabel = new Label(((User) contact).getStatus().toString()); + Color textColor = null; + switch (((User) contact).getStatus()) { + case ONLINE: + textColor = Color.LIMEGREEN; + break; + case AWAY: + textColor = Color.ORANGERED; + break; + case BUSY: + textColor = Color.RED; + break; + case OFFLINE: + textColor = Color.GRAY; + break; + } + infoLabel.setTextFill(textColor); + } else + // group specific infos + infoLabel = new Label(String.valueOf(((Group) contact).getContacts().size()) + " members"); + setGraphic(new VBox(new Label(contact.getName()), infoLabel)); } } } diff --git a/src/main/java/envoy/client/ui/MessageListCell.java b/src/main/java/envoy/client/ui/MessageListCell.java index 60a2ada..7b7a479 100644 --- a/src/main/java/envoy/client/ui/MessageListCell.java +++ b/src/main/java/envoy/client/ui/MessageListCell.java @@ -45,10 +45,15 @@ public class MessageListCell extends ListCell { @Override protected void updateItem(Message message, boolean empty) { super.updateItem(message, empty); - setGraphic(!empty && message != null ? new HBox( + if(empty || message == null) { + setText(null); + setGraphic(null); + } else { + setGraphic(new HBox( new VBox( new Label(dateFormat.format(message.getCreationDate())), new Label(message.getText())), - new Label("", new ImageView(statusImages.get(message.getStatus())))) : null); + new Label("", new ImageView(statusImages.get(message.getStatus()))))); + } } } diff --git a/src/main/java/envoy/client/ui/SceneContext.java b/src/main/java/envoy/client/ui/SceneContext.java index fb2f0b2..ea3b4b2 100644 --- a/src/main/java/envoy/client/ui/SceneContext.java +++ b/src/main/java/envoy/client/ui/SceneContext.java @@ -115,6 +115,7 @@ public final class SceneContext { sceneStack.push(scene); stage.setScene(scene); applyCSS(); + stage.sizeToScene(); stage.show(); } catch (IOException e) { throw new RuntimeException(e); @@ -131,6 +132,7 @@ public final class SceneContext { if (!sceneStack.isEmpty()) { stage.setScene(sceneStack.peek()); applyCSS(); + stage.sizeToScene(); } stage.show(); } diff --git a/src/main/java/envoy/client/ui/controller/ChatScene.java b/src/main/java/envoy/client/ui/controller/ChatScene.java index 7bdae86..8f7b487 100644 --- a/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -9,6 +9,7 @@ import javafx.application.Platform; import javafx.collections.FXCollections; import javafx.fxml.FXML; import javafx.scene.control.*; +import javafx.scene.control.Alert.AlertType; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.paint.Color; @@ -22,9 +23,7 @@ import envoy.client.net.WriteProxy; import envoy.client.ui.ContactListCell; import envoy.client.ui.MessageListCell; import envoy.client.ui.SceneContext; -import envoy.data.Contact; -import envoy.data.Message; -import envoy.data.MessageBuilder; +import envoy.data.*; import envoy.event.EventBus; import envoy.event.MessageStatusChangeEvent; import envoy.event.UserStatusChangeEvent; @@ -74,6 +73,11 @@ public final class ChatScene { 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() { @@ -83,24 +87,36 @@ public final class ChatScene { // Listen to received messages eventBus.register(MessageCreationEvent.class, e -> { - final var message = e.get(); - final var chat = localDB.getChats().stream().filter(c -> c.getRecipient().getID() == message.getSenderID()).findAny().get(); + final var message = e.get(); + localDB.getChat(message.getSenderID()).ifPresent(chat -> { + chat.getMessages().add(message); - // Update UI if in current chat - if (chat == currentChat) Platform.runLater(() -> messageList.getItems().add(message)); + // Update UI if in current chat + if (chat == currentChat) + Platform.runLater(messageList::refresh); + }); }); // Listen to message status changes - eventBus.register(MessageStatusChangeEvent.class, e -> { - final var message = localDB.getMessage(e.getID()); + eventBus.register(MessageStatusChangeEvent.class, e -> + localDB.getMessage(e.getID()).ifPresent(message -> { message.setStatus(e.get()); // Update UI if in current chat if (currentChat != null && message.getSenderID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh); - }); + })); // Listen to user status changes - eventBus.register(UserStatusChangeEvent.class, e -> Platform.runLater(userList::refresh)); + eventBus.register(UserStatusChangeEvent.class, 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(ContactOperationEvent.class, e -> { @@ -121,6 +137,8 @@ public final class ChatScene { } /** + * 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 @@ -137,6 +155,11 @@ public final class ChatScene { userList.setItems(FXCollections.observableList(localDB.getChats().stream().map(Chat::getRecipient).collect(Collectors.toList()))); } + /** + * 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(); @@ -146,10 +169,8 @@ public final class ChatScene { // LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes // Load the chat or create a new one and add it to the LocalDB - currentChat = localDB.getChats() - .stream() - .filter(c -> c.getRecipient().getID() == user.getID()) - .findAny() + currentChat = localDB + .getChat(user.getID()) .orElseGet(() -> { final var chat = new Chat(user); localDB.getChats().add(chat); return chat; }); messageList.setItems(FXCollections.observableList(currentChat.getMessages())); @@ -161,32 +182,35 @@ public final class ChatScene { messageTextArea.setDisable(currentChat == null); } - @FXML - private void postButtonClicked() { postMessage(); } - + /** + * 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.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.getController().initializeData(sceneContext, localDB); } + /** + * Actions to perform when the text was updated in the messageTextArea. + * + * @since Envoy Client v0.1-beta + */ @FXML - private void messageTextUpdated(KeyEvent e) { - // TODO: Letting go of ctrl automatically posts a message. Needs to be fixed - // soon. - - // Automatic sending of messages via (ctrl +) enter - if (!postButton.isDisabled() && settings.isEnterToSend() && e.getCode() == KeyCode.ENTER - || !settings.isEnterToSend() && e.getCode() == KeyCode.CONTROL) - postMessage(); - else postButton.setDisable(messageTextArea.getText().isBlank() || currentChat == null); - + 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)); @@ -201,33 +225,34 @@ public final class ChatScene { remainingChars.setTextFill(Color.rgb(currentLength, remainingLength, 0, 1)); } + /** + * Actions to perform when a key has been entered. + * + * @param e the Keys that have been entered + * @since Envoy Client v0.1-beta + */ + @FXML + private void checkKeyCombination(KeyEvent e) { + // Automatic sending of messages via (ctrl +) enter + 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); + } + /** * Sends a new message to the server based on the text entered in the * messageTextArea. * * @since Envoy Client v0.1-beta */ + @FXML private void postMessage() { - - // Create and send message - sendMessage(new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator()) - .setText(messageTextArea.getText().strip()) - .build()); - - // Clear text field and disable post button - messageTextArea.setText(""); - postButton.setDisable(true); - } - - /** - * Sends a message to the server and appends it to the current chat. If all - * message IDs have been used, a new ID generator is requested. - * - * @param message the message to send - * @since Envoy Client v0.1-beta - */ - private void sendMessage(Message message) { try { + // Create and send message + final var message = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator()) + .setText(messageTextArea.getText().strip()) + .build(); // Send message writeProxy.writeMessage(message); @@ -240,6 +265,11 @@ public final class ChatScene { } catch (final IOException e) { logger.log(Level.SEVERE, "Error sending message", e); + new Alert(AlertType.ERROR, "An error occured while sending the message!").showAndWait(); } + + // Clear text field and disable post button + messageTextArea.setText(""); + postButton.setDisable(true); } } diff --git a/src/main/java/envoy/client/ui/controller/LoginScene.java b/src/main/java/envoy/client/ui/controller/LoginScene.java index 4f13149..5f6c5aa 100644 --- a/src/main/java/envoy/client/ui/controller/LoginScene.java +++ b/src/main/java/envoy/client/ui/controller/LoginScene.java @@ -28,7 +28,7 @@ import envoy.util.EnvoyLog; * Project: envoy-client
* File: LoginDialog.java
* Created: 03.04.2020
- * + * * @author Kai S. K. Engelbart * @since Envoy Client v0.1-beta */ @@ -72,7 +72,7 @@ public final class LoginScene { /** * Loads the login dialog using the FXML file {@code LoginDialog.fxml}. - * + * * @param client the client used to perform the handshake * @param localDB the local database used for offline login * @param receivedMessageCache the cache storing messages received during @@ -104,9 +104,7 @@ public final class LoginScene { if (registerCheckBox.isSelected() && !passwordField.getText().equals(repeatPasswordField.getText())) { clearPasswordFields(); new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one").showAndWait(); - } else { - performHandshake(new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), registerCheckBox.isSelected())); - } + } else performHandshake(new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), registerCheckBox.isSelected())); } @FXML @@ -120,7 +118,6 @@ public final class LoginScene { // Make repeat password field and label visible / invisible repeatPasswordField.setVisible(registerCheckBox.isSelected()); repeatPasswordLabel.setVisible(registerCheckBox.isSelected()); - clearPasswordFields(); } @@ -148,12 +145,12 @@ public final class LoginScene { try { // Try entering offline mode localDB.loadUsers(); - User clientUser = (User) localDB.getUsers().get(credentials.getIdentifier()); + final User clientUser = (User) localDB.getUsers().get(credentials.getIdentifier()); if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown"); client.setSender(clientUser); new Alert(AlertType.WARNING, "A connection to the server could not be established. Starting in offline mode.").showAndWait(); loadChatScene(); - } catch (Exception e) { + } catch (final Exception e) { new Alert(AlertType.ERROR, "Client error: " + e).showAndWait(); System.exit(1); } diff --git a/src/main/java/envoy/client/ui/controller/package-info.java b/src/main/java/envoy/client/ui/controller/package-info.java index 51d18e8..7989b8f 100644 --- a/src/main/java/envoy/client/ui/controller/package-info.java +++ b/src/main/java/envoy/client/ui/controller/package-info.java @@ -4,8 +4,8 @@ * Project: envoy-client
* File: package-info.java
* Created: 08.06.2020
- * + * * @author Kai S. K. Engelbart * @since Envoy Client v0.1-beta */ -package envoy.client.ui.controller; \ No newline at end of file +package envoy.client.ui.controller; diff --git a/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java b/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java index 12cbaf5..e6d5761 100644 --- a/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java +++ b/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java @@ -8,13 +8,14 @@ import javafx.scene.layout.VBox; import envoy.client.data.Settings; import envoy.client.data.SettingsItem; import envoy.client.event.ThemeChangeEvent; +import envoy.data.User.UserStatus; import envoy.event.EventBus; /** * Project: envoy-client
* File: GeneralSettingsPane.java
* Created: 18.04.2020
- * + * * @author Kai S. K. Engelbart * @since Envoy Client v0.1-beta */ @@ -27,7 +28,7 @@ public class GeneralSettingsPane extends SettingsPane { */ public GeneralSettingsPane() { super("General"); - var vbox = new VBox(); + final var vbox = new VBox(); // TODO: Support other value types List.of("onCloseMode", "enterToSend") @@ -36,7 +37,7 @@ public class GeneralSettingsPane extends SettingsPane { .map(i -> new SettingsCheckbox((SettingsItem) i)) .forEach(vbox.getChildren()::add); - var combobox = new ComboBox(); + final var combobox = new ComboBox(); combobox.getItems().add("dark"); combobox.getItems().add("light"); combobox.setValue(settings.getCurrentTheme()); @@ -44,6 +45,13 @@ public class GeneralSettingsPane extends SettingsPane { e -> { settings.setCurrentTheme(combobox.getValue()); EventBus.getInstance().dispatch(new ThemeChangeEvent(combobox.getValue())); }); vbox.getChildren().add(combobox); + final var statusComboBox = new ComboBox(); + statusComboBox.getItems().setAll(UserStatus.values()); + statusComboBox.setValue(UserStatus.ONLINE); + // TODO add action when value is changed + statusComboBox.setOnAction(e -> {}); + vbox.getChildren().add(statusComboBox); + getChildren().add(vbox); } } diff --git a/src/main/resources/css/dark.css b/src/main/resources/css/dark.css index 4a899b2..e10a301 100644 --- a/src/main/resources/css/dark.css +++ b/src/main/resources/css/dark.css @@ -1,4 +1,4 @@ .button{ -fx-background-color: rgb(105,0,153); -fx-text-fill: white; -} \ No newline at end of file +} diff --git a/src/main/resources/css/light.css b/src/main/resources/css/light.css index a529c0f..34a1f74 100644 --- a/src/main/resources/css/light.css +++ b/src/main/resources/css/light.css @@ -1,3 +1,3 @@ .button{ -fx-background-color: snow; -} \ No newline at end of file +} diff --git a/src/main/resources/fxml/ChatScene.fxml b/src/main/resources/fxml/ChatScene.fxml index a81cd00..aab77a6 100644 --- a/src/main/resources/fxml/ChatScene.fxml +++ b/src/main/resources/fxml/ChatScene.fxml @@ -26,8 +26,8 @@