Merge pull request #154 from informatik-ag-ngl/f/enhanced_UI

added:
    - alignment and coloring of messages according to sender
    - context menu for userList messageList and postButton (partially without function currently) -> implemented copying as well as "CopyAndSend" capability
    - hid horizontal scrollbars (they are still present, but cannot be seen)
    - displayed settings icon in ChatScene
This commit is contained in:
delvh 2020-06-26 20:20:37 +02:00 committed by GitHub
commit 961527e663
8 changed files with 293 additions and 99 deletions

View File

@ -35,11 +35,11 @@ public class ContactListCell extends ListCell<Contact> {
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"));
}

View File

@ -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<Message> {
private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
private static Map<MessageStatus, Image> 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<Message> {
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; }
}

View File

@ -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";

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -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 {

View File

@ -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;
}

View File

@ -2,93 +2,177 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ContextMenu?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.MenuItem?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<GridPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="400.0" minWidth="350.0" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="envoy.client.ui.controller.ChatScene">
<GridPane maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="400.0" minWidth="350.0" prefHeight="400.0" prefWidth="600.0"
xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="envoy.client.ui.controller.ChatScene">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="10.0" percentWidth="20.0" prefWidth="161.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="10.0" percentWidth="65.0" prefWidth="357.0" />
<ColumnConstraints hgrow="SOMETIMES" maxWidth="1.7976931348623157E308" minWidth="6.285736083984375" percentWidth="15.0" prefWidth="48.000030517578125" />
<ColumnConstraints hgrow="SOMETIMES"
maxWidth="1.7976931348623157E308" minWidth="10.0" percentWidth="20.0"
prefWidth="161.0" />
<ColumnConstraints hgrow="SOMETIMES"
maxWidth="1.7976931348623157E308" minWidth="10.0" percentWidth="65.0"
prefWidth="357.0" />
<ColumnConstraints hgrow="SOMETIMES"
maxWidth="1.7976931348623157E308" minWidth="6.285736083984375"
percentWidth="15.0" prefWidth="48.000030517578125" />
</columnConstraints>
<rowConstraints>
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" percentHeight="10.0" prefHeight="70.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" percentHeight="70.0" prefHeight="326.2857404436384" vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" percentHeight="5.0" prefHeight="50.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308" minHeight="10.0" percentHeight="15.0" prefHeight="100.0" vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308"
minHeight="10.0" percentHeight="10.0" prefHeight="70.0"
vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308"
minHeight="10.0" percentHeight="70.0" prefHeight="326.2857404436384"
vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308"
minHeight="10.0" percentHeight="5.0" prefHeight="50.0"
vgrow="SOMETIMES" />
<RowConstraints maxHeight="1.7976931348623157E308"
minHeight="10.0" percentHeight="15.0" prefHeight="100.0"
vgrow="SOMETIMES" />
</rowConstraints>
<children>
<ListView fx:id="userList" onMouseClicked="#userListClicked" prefHeight="211.0" prefWidth="300.0" GridPane.rowIndex="1" GridPane.rowSpan="2147483647">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="5.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></ListView>
<Label fx:id="contactLabel" prefHeight="16.0" prefWidth="250.0" text="Select a contact to chat with" GridPane.columnSpan="2">
<GridPane.margin>
<Insets bottom="5.0" left="10.0" right="5.0" top="10.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></Label>
<Button fx:id="settingsButton" mnemonicParsing="true" onAction="#settingsButtonClicked" text="_Settings" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.valignment="CENTER">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></Button>
<ListView fx:id="messageList" prefHeight="257.0" prefWidth="155.0" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1" GridPane.rowSpan="2">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="10.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></ListView>
<Button fx:id="postButton" defaultButton="true" disable="true" mnemonicParsing="true" onAction="#postMessage" prefHeight="10.0" prefWidth="65.0" text="_Post" GridPane.columnIndex="2" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="CENTER">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<tooltip>
<Tooltip anchorLocation="WINDOW_TOP_LEFT" autoHide="true" maxWidth="350.0" text="Click this button to send the message. If it is disabled, you first have to select a contact to send it to. A message may automatically be sent when you press (Ctrl + ) Enter, according to your preferences. Additionally sends a message when pressing &quot;Alt&quot; + &quot;P&quot;." wrapText="true" />
</tooltip></Button>
<TextArea fx:id="messageTextArea" disable="true" onInputMethodTextChanged="#messageTextUpdated" onKeyPressed="#checkPostConditions" onKeyTyped="#checkKeyCombination" prefHeight="200.0" prefWidth="200.0" wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="3">
<GridPane.margin>
<Insets bottom="10.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
<opaqueInsets>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</opaqueInsets></TextArea>
<Button mnemonicParsing="true" onAction="#addContactButtonClicked" text="_Add Contacts" GridPane.halignment="CENTER" GridPane.rowIndex="3" GridPane.valignment="CENTER">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
</Button>
<Label id="remainingCharsLabel" fx:id="remainingChars" ellipsisString="" maxHeight="30.0" maxWidth="180.0" prefHeight="30.0" prefWidth="180.0" text="remaining chars: 0/x" textFill="LIME" textOverrun="LEADING_WORD_ELLIPSIS" visible="false" GridPane.columnIndex="1" GridPane.rowIndex="2">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<opaqueInsets>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</opaqueInsets>
<tooltip>
<Tooltip text="Shows how many chars you can still enter in this message" wrapText="true" />
</tooltip>
</Label>
<ListView fx:id="userList" onMouseClicked="#userListClicked"
prefHeight="211.0" prefWidth="300.0" GridPane.rowIndex="1"
GridPane.rowSpan="2147483647">
<GridPane.margin>
<Insets bottom="10.0" left="10.0" right="5.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<contextMenu>
<ContextMenu anchorLocation="CONTENT_TOP_LEFT">
<items>
<MenuItem fx:id="deleteContactMenuItem"
mnemonicParsing="false" text="Delete" onAction="#deleteContact" />
</items>
</ContextMenu>
</contextMenu>
</ListView>
<Label fx:id="contactLabel" prefHeight="16.0" prefWidth="250.0"
text="Select a contact to chat with" GridPane.columnSpan="2">
<GridPane.margin>
<Insets left="15.0" right="5.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Label>
<Button fx:id="settingsButton" mnemonicParsing="true"
onAction="#settingsButtonClicked" text="_Settings"
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.halignment="RIGHT"
GridPane.valignment="CENTER">
<GridPane.margin>
<Insets bottom="10.0" left="5.0" right="10.0" top="10.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Button>
<ListView fx:id="messageList" prefHeight="257.0"
prefWidth="155.0" GridPane.columnIndex="1" GridPane.columnSpan="2"
GridPane.rowIndex="1" GridPane.rowSpan="2">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="10.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<contextMenu>
<ContextMenu anchorLocation="CONTENT_TOP_LEFT">
<items>
<MenuItem mnemonicParsing="false" text="Copy"
onAction="#copyMessage" />
<MenuItem mnemonicParsing="false" text="Delete"
onAction="#deleteMessage" />
<MenuItem mnemonicParsing="false" text="Forward"
onAction="#forwardMessage" />
<MenuItem mnemonicParsing="false" text="Quote"
onAction="#quoteMessage" />
</items>
</ContextMenu>
</contextMenu>
</ListView>
<Button fx:id="postButton" defaultButton="true" disable="true"
mnemonicParsing="true" onAction="#postMessage" prefHeight="10.0"
prefWidth="65.0" text="_Post" GridPane.columnIndex="2"
GridPane.halignment="CENTER" GridPane.rowIndex="3"
GridPane.valignment="CENTER">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0"/>
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<tooltip>
<Tooltip anchorLocation="WINDOW_TOP_LEFT" autoHide="true"
maxWidth="350.0"
text="Click this button to send the message. If it is disabled, you first have to select a contact to send it to. A message may automatically be sent when you press (Ctrl + ) Enter, according to your preferences. Additionally sends a message when pressing &quot;Alt&quot; + &quot;P&quot;."
wrapText="true" />
</tooltip>
<contextMenu>
<ContextMenu anchorLocation="CONTENT_TOP_LEFT">
<items>
<MenuItem mnemonicParsing="false" text="Copy and Send"
onAction="#copyAndPostMessage" />
</items>
</ContextMenu>
</contextMenu>
</Button>
<TextArea fx:id="messageTextArea" disable="true"
onInputMethodTextChanged="#messageTextUpdated"
onKeyPressed="#checkPostConditions" onKeyTyped="#checkKeyCombination"
prefHeight="200.0" prefWidth="200.0" wrapText="true"
GridPane.columnIndex="1" GridPane.rowIndex="3">
<GridPane.margin>
<Insets bottom="10.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
<opaqueInsets>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</opaqueInsets>
</TextArea>
<Button mnemonicParsing="true"
onAction="#addContactButtonClicked" text="_Add Contacts"
GridPane.halignment="CENTER" GridPane.rowIndex="3"
GridPane.valignment="CENTER">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
</Button>
<Label id="remainingCharsLabel" fx:id="remainingChars"
ellipsisString="" maxHeight="30.0" maxWidth="180.0" prefHeight="30.0"
prefWidth="180.0" text="remaining chars: 0/x" textFill="LIME"
textOverrun="LEADING_WORD_ELLIPSIS" visible="false"
GridPane.columnIndex="1" GridPane.rowIndex="2">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<opaqueInsets>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</opaqueInsets>
<tooltip>
<Tooltip
text="Shows how many chars you can still enter in this message"
wrapText="true" />
</tooltip>
</Label>
</children>
</GridPane>