diff --git a/src/main/java/envoy/client/ui/ContactListCell.java b/src/main/java/envoy/client/ui/ContactListCell.java index e52197d..283a89a 100644 --- a/src/main/java/envoy/client/ui/ContactListCell.java +++ b/src/main/java/envoy/client/ui/ContactListCell.java @@ -35,11 +35,11 @@ public class ContactListCell extends ListCell { final var vbox = new VBox(new Label(contact.getName())); if (contact instanceof User) { // Online status - final var user = (User) contact; + final var user = (User) contact; final var statusLabel = new Label(user.getStatus().toString()); statusLabel.getStyleClass().add(user.getStatus().toString().toLowerCase()); vbox.getChildren().add(statusLabel); - } else { + } else { // Member count vbox.getChildren().add(new Label(((Group) contact).getContacts().size() + " members")); } diff --git a/src/main/java/envoy/client/ui/MessageListCell.java b/src/main/java/envoy/client/ui/MessageListCell.java index d287e0f..22aa188 100644 --- a/src/main/java/envoy/client/ui/MessageListCell.java +++ b/src/main/java/envoy/client/ui/MessageListCell.java @@ -5,15 +5,16 @@ import java.time.format.DateTimeFormatter; import java.util.Map; import java.util.logging.Level; +import javafx.geometry.Insets; import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import envoy.data.Message; import envoy.data.Message.MessageStatus; +import envoy.data.User; import envoy.util.EnvoyLog; /** @@ -30,10 +31,11 @@ public class MessageListCell extends ListCell { private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"); private static Map statusImages; + private static User client; static { try { - statusImages = IconUtil.loadByEnum(MessageStatus.class, 32); + statusImages = IconUtil.loadByEnum(MessageStatus.class, 16); } catch (final IOException e) { e.printStackTrace(); EnvoyLog.getLogger(MessageListCell.class).log(Level.WARNING, "could not load status icons: ", e); @@ -52,11 +54,19 @@ public class MessageListCell extends ListCell { 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()))))); + final var cell = new VBox(new Label(dateFormat.format(message.getCreationDate())), new Label(message.getText())); + if (message.getRecipientID() == client.getID()) { + cell.getChildren().add(new Label("", new ImageView(statusImages.get(message.getStatus())))); + cell.getStyleClass().add("own-message"); + } else cell.getStyleClass().add("received-message"); + cell.paddingProperty().setValue(new Insets(5, 20, 5, 20)); + setGraphic(cell); } } + + /** + * @param client the user who chats with another person + * @since Envoy Client v0.1-beta + */ + public static void setUser(User client) { MessageListCell.client = client; } } diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index 0812129..f57b22e 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/ui/Startup.java @@ -35,7 +35,7 @@ public final class Startup extends Application { /** * The version of this client. Used to verify compatibility with the server. - * + * * @since Envoy Client v0.1-beta */ public static final String VERSION = "0.1-beta"; diff --git a/src/main/java/envoy/client/ui/controller/ChatScene.java b/src/main/java/envoy/client/ui/controller/ChatScene.java index ff93351..3f6188f 100644 --- a/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -1,5 +1,7 @@ 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; @@ -10,6 +12,7 @@ import javafx.collections.FXCollections; import javafx.fxml.FXML; import javafx.scene.control.*; 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; @@ -20,9 +23,7 @@ import envoy.client.data.Settings; import envoy.client.event.MessageCreationEvent; import envoy.client.net.Client; import envoy.client.net.WriteProxy; -import envoy.client.ui.ContactListCell; -import envoy.client.ui.MessageListCell; -import envoy.client.ui.SceneContext; +import envoy.client.ui.*; import envoy.data.*; import envoy.event.EventBus; import envoy.event.MessageStatusChange; @@ -61,6 +62,9 @@ public final class ChatScene { @FXML private Label remainingChars; + @FXML + private MenuItem deleteContactMenuItem; + private LocalDB localDB; private Client client; private WriteProxy writeProxy; @@ -85,6 +89,12 @@ public final class ChatScene { messageList.setCellFactory(listView -> new MessageListCell()); userList.setCellFactory(listView -> new ContactListCell()); + try { + settingsButton.setGraphic(new ImageView(IconUtil.load("/icons/settings.png", 16))); + } catch (final IOException e2) { + logger.log(Level.WARNING, "Could not load settings icon", e2); + } + // Listen to received messages eventBus.register(MessageCreationEvent.class, e -> { final var message = e.get(); @@ -97,7 +107,7 @@ public final class ChatScene { } catch (final IOException e1) { logger.log(Level.WARNING, "Could not read current chat: ", e1); } - Platform.runLater(messageList::refresh); + Platform.runLater(() -> { messageList.refresh(); scrollToMessageListEnd(); }); } }); }); @@ -153,6 +163,8 @@ public final class ChatScene { this.writeProxy = writeProxy; userList.setItems(FXCollections.observableList(localDB.getChats().stream().map(Chat::getRecipient).collect(Collectors.toList()))); + contactLabel.setText(localDB.getUser().getName()); + MessageListCell.setUser(localDB.getUser()); } /** @@ -165,7 +177,6 @@ public final class ChatScene { final Contact user = userList.getSelectionModel().getSelectedItem(); if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) { logger.log(Level.FINEST, "Loading chat with " + user); - contactLabel.setText(user.getName()); // LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes @@ -173,6 +184,7 @@ public final class ChatScene { currentChat = localDB.getChat(user.getID()).get(); messageList.setItems(FXCollections.observableList(currentChat.getMessages())); + deleteContactMenuItem.setText("Delete " + user.getName()); // Read the current chat try { @@ -287,6 +299,7 @@ public final class ChatScene { // Add message to LocalDB and update UI messageList.getItems().add(message); + scrollToMessageListEnd(); // Request a new ID generator if all IDs were used if (!localDB.getIDGenerator().hasNext() && client.isOnline()) client.requestIdGenerator(); @@ -301,4 +314,44 @@ public final class ChatScene { 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); } + + // 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()); + } } diff --git a/src/main/resources/css/base.css b/src/main/resources/css/base.css index 4711344..f9f1ce2 100644 --- a/src/main/resources/css/base.css +++ b/src/main/resources/css/base.css @@ -1,5 +1,15 @@ -.button { - -fx-background-radius: 5em; +.button, .list-cell { + -fx-background-radius: 5.0em; +} + +.context-menu, .context-menu > * { + -fx-background-radius: 15px; + /*TODO: solution below does not work */ + -fx-background-color: transparent; +} + +.menu-item { + -fx-background-radius: 15.0px; } .button:hover { @@ -11,10 +21,9 @@ -fx-background-color: transparent; } -#remainingCharsLabel { - -fx-text-fill: #00FF00; - -fx-opacity: 1; +.scroll-bar:horizontal, .scroll-bar:horizontal *, .scroll-bar:horizontal > *{ -fx-background-color: transparent; + -fx-text-fill: transparent; } .online { @@ -32,3 +41,20 @@ .offline { -fx-text-fill: gray; } + +.received-message { + -fx-alignment: center-left; + -fx-background-radius: 4.0em; + -fx-text-alignment: right; +} + +.own-message { + -fx-alignment: center-right; + -fx-background-radius: 4.0em; + -fx-text-alignment: left; +} + +#remainingCharsLabel { + -fx-text-fill: #00FF00; + -fx-background-color: transparent; +} diff --git a/src/main/resources/css/dark.css b/src/main/resources/css/dark.css index 60707a4..967732b 100644 --- a/src/main/resources/css/dark.css +++ b/src/main/resources/css/dark.css @@ -18,12 +18,20 @@ -fx-background-color: lightgray; } -.list-view, .list-cell, .text-area .content, .text-field, .password-field, .tooltip, .pane, .pane .content, .vbox, .titled-pane > .title, .titled-pane > *.content { +.list-view, .list-cell, .text-area .content, .text-field, .password-field, .tooltip, .pane, .pane .content, .vbox, .titled-pane > .title, .titled-pane > *.content, .context-menu, .menu-item { -fx-background-color: dimgray; } -.list-cell:selected { - -fx-background-color:rgb(105.0,0.0,153.0) ; +.list-cell:selected, .list-cell:selected > *, .menu-item:hover { + -fx-background-color: rgb(105.0,0.0,153.0); +} + +.received-message { + -fx-background-color: gray; +} + +.own-message { + -fx-background-color: #8fa88f; } .alert.information.dialog-pane, .alert.warning.dialog-pane, .alert.error.dialog-pane { diff --git a/src/main/resources/css/light.css b/src/main/resources/css/light.css index bb57c4f..066de55 100644 --- a/src/main/resources/css/light.css +++ b/src/main/resources/css/light.css @@ -1,3 +1,16 @@ -.button, .list-cell:selected{ +.button{ -fx-background-color: orangered; } + +.list-cell:selected, .list-cell:selected > * { + -fx-background-color: orangered; + -fx-text-fill: black; +} + +.received-message, .menu-item { + -fx-background-color: lightgray; +} + +.own-message { + -fx-background-color: lightgreen; +} diff --git a/src/main/resources/fxml/ChatScene.fxml b/src/main/resources/fxml/ChatScene.fxml index 9d95fba..0a65449 100644 --- a/src/main/resources/fxml/ChatScene.fxml +++ b/src/main/resources/fxml/ChatScene.fxml @@ -2,93 +2,177 @@ + + - + - - - + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +