Merge branch 'develop' into f/groupMessages

This commit is contained in:
Kai S. K. Engelbart 2020-07-05 11:15:00 +02:00
commit a3f3a9dcbb
No known key found for this signature in database
GPG Key ID: 0A48559CA32CB48F
28 changed files with 908 additions and 481 deletions

View File

@ -5,6 +5,7 @@ labels: bug
assignees: CyB3RC0nN0R, delvh, DieGurke
reviewers: CyB3RC0nN0R, delvh
projects: Envoy
milestone: Envoy v0.3-alpha
milestone: Envoy v0.1-beta
---
Fixes #{issue}

View File

@ -1,9 +1,10 @@
---
name: Feature integration
title: Added feature
labels: enhancement
labels: feature
assignees: CyB3RC0nN0R, delvh, DieGurke
reviewers: CyB3RC0nN0R, delvh
projects: Envoy
milestone: Envoy v0.3-alpha
milestone: Envoy v0.1-beta
---

View File

@ -5,5 +5,6 @@ labels: documentation
assignees: CyB3RC0nN0R, delvh
reviewers: CyB3RC0nN0R, delvh
projects: Envoy
milestone: Envoy v0.3-alpha
milestone: Envoy v0.1-beta
---

View File

@ -4,14 +4,25 @@ on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Stage build artifacts
run: mkdir staging && cp target/*.jar staging
- uses: actions/upload-artifact@v1
with:
name: envoy-client-artifacts
path: staging

View File

@ -114,6 +114,21 @@ public final class Chat implements Serializable {
*/
public boolean isUnread() { return !messages.isEmpty() && messages.get(messages.size() - 1).getStatus() != MessageStatus.READ; }
/**
* Inserts a message at the correct place according to its creation date.
*
* @param message the message to insert
* @since Envoy Client v0.1-beta
*/
public void insert(Message message) {
for (int i = messages.size() - 1; i >= 0; --i)
if (message.getCreationDate().isAfter(messages.get(i).getCreationDate())) {
messages.add(i + 1, message);
return;
}
messages.add(0, message);
}
/**
* @return all messages in the current chat
* @since Envoy Client v0.1-beta

View File

@ -0,0 +1,174 @@
package envoy.client.ui;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Background;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
import envoy.client.data.Settings;
/**
* This class offers a text field that is automatically equipped with a clear
* button.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ClearableTextField.java</strong><br>
* Created: <strong>25.06.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class ClearableTextField extends GridPane {
private final TextField textField;
private final Button clearButton;
/**
* Constructs a new {@code ClearableTextField} with no initial text and icon
* size 16.
*
* @since Envoy Client v0.1-beta
*/
public ClearableTextField() { this("", 16); }
/**
* Constructs a new {@code ClearableTextField} with initial text and a
* predetermined icon size.
*
* @param text the text that should be displayed by default
* @param size the size of the icon
* @since Envoy Client v0.1-beta
*/
public ClearableTextField(String text, int size) {
// initializing the textField and the button
textField = new TextField(text);
clearButton = new Button("",
new ImageView(IconUtil.load(
Settings.getInstance().getCurrentTheme().equals("dark") ? "/icons/clear_button_white.png" : "/icons/clear_button_black.png",
size)));
clearButton.setOnAction(e -> textField.clear());
clearButton.setFocusTraversable(false);
clearButton.getStyleClass().clear();
clearButton.setBackground(Background.EMPTY);
// Adding the two elements to the GridPane
add(textField, 0, 0, 2, 1);
add(clearButton, 1, 0, 1, 1);
// Setting the percent - widths of the two columns.
// Used to locate the button on the right.
final var columnConstraints = new ColumnConstraints();
columnConstraints.setPercentWidth(90);
getColumnConstraints().add(columnConstraints);
final var columnConstraints2 = new ColumnConstraints();
columnConstraints2.setPercentWidth(10);
getColumnConstraints().add(columnConstraints2);
}
/**
* @return the underlying {@code textField}
* @since Envoy Client v0.1-beta
*/
public TextField getTextField() { return textField; }
/**
* This method offers the freedom to perform custom actions when the
* {@code clearButton} has been pressed.
* <p>
* The default is
* <b><code> e -> {clearableTextField.getTextField().clear();}</code></b>
*
* @param onClearButtonAction the action that should be performed
* @since Envoy Client v0.1-beta
*/
public void setClearButtonListener(EventHandler<ActionEvent> onClearButtonAction) { clearButton.setOnAction(onClearButtonAction); }
/**
* @return the current property of the prompt text
* @see javafx.scene.control.TextInputControl#promptTextProperty()
* @since Envoy Client v0.1-beta
*/
public final StringProperty promptTextProperty() { return textField.promptTextProperty(); }
/**
* @return the current prompt text
* @see javafx.scene.control.TextInputControl#getPromptText()
* @since Envoy Client v0.1-beta
*/
public final String getPromptText() { return textField.getPromptText(); }
/**
* @param value the prompt text to display
* @see javafx.scene.control.TextInputControl#setPromptText(java.lang.String)
* @since Envoy Client v0.1-beta
*/
public final void setPromptText(String value) { textField.setPromptText(value); }
/**
* @return the current property of the tooltip
* @see javafx.scene.control.Control#tooltipProperty()
* @since Envoy Client v0.1-beta
*/
public final ObjectProperty<Tooltip> tooltipProperty() { return textField.tooltipProperty(); }
/**
* @param value the new tooltip
* @see javafx.scene.control.Control#setTooltip(javafx.scene.control.Tooltip)
* @since Envoy Client v0.1-beta
*/
public final void setTooltip(Tooltip value) { textField.setTooltip(value); }
/**
* @return the current tooltip
* @see javafx.scene.control.Control#getTooltip()
* @since Envoy Client v0.1-beta
*/
public final Tooltip getTooltip() { return textField.getTooltip(); }
/**
* @return the current property of the context menu
* @see javafx.scene.control.Control#contextMenuProperty()
* @since Envoy Client v0.1-beta
*/
public final ObjectProperty<ContextMenu> contextMenuProperty() { return textField.contextMenuProperty(); }
/**
* @param value the new context menu
* @see javafx.scene.control.Control#setContextMenu(javafx.scene.control.ContextMenu)
* @since Envoy Client v0.1-beta
*/
public final void setContextMenu(ContextMenu value) { textField.setContextMenu(value); }
/**
* @return the current context menu
* @see javafx.scene.control.Control#getContextMenu()
* @since Envoy Client v0.1-beta
*/
public final ContextMenu getContextMenu() { return textField.getContextMenu(); }
/**
* @param value whether this ClearableTextField should be editable
* @see javafx.scene.control.TextInputControl#setEditable(boolean)
* @since Envoy Client v0.1-beta
*/
public final void setEditable(boolean value) { textField.setEditable(value); }
/**
* @return the current property whether this ClearableTextField is editable
* @see javafx.scene.control.TextInputControl#editableProperty()
* @since Envoy Client v0.1-beta
*/
public final BooleanProperty editableProperty() { return textField.editableProperty(); }
/**
* @return whether this {@code ClearableTextField} is editable
* @see javafx.scene.control.TextInputControl#isEditable()
* @since Envoy Client v0.1-beta
*/
public final boolean isEditable() { return textField.isEditable(); }
}

View File

@ -1,49 +0,0 @@
package envoy.client.ui;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.layout.VBox;
import envoy.data.Contact;
import envoy.data.Group;
import envoy.data.User;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListCell.java</strong><br>
* Created: <strong>28.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class ContactListCell extends ListCell<Contact> {
/**
* 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) {
setText(null);
setGraphic(null);
} else {
// Container with contact name
final var vbox = new VBox(new Label(contact.getName()));
if (contact instanceof User) {
// Online status
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 {
// Member count
vbox.getChildren().add(new Label(((Group) contact).getContacts().size() + " members"));
}
setGraphic(vbox);
}
}
}

View File

@ -1,60 +0,0 @@
package envoy.client.ui;
import java.time.format.DateTimeFormatter;
import java.util.Map;
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.VBox;
import envoy.data.Message;
import envoy.data.Message.MessageStatus;
import envoy.data.User;
/**
* Displays a single message inside the message list.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageListCell.java</strong><br>
* Created: <strong>28.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class MessageListCell extends ListCell<Message> {
private static User client;
private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
private static final Map<MessageStatus, Image> statusImages = IconUtil.loadByEnum(MessageStatus.class, 16);
/**
* Displays the text, the data of creation and the status of a message.
*
* @since Envoy v0.1-beta
*/
@Override
protected void updateItem(Message message, boolean empty) {
super.updateItem(message, empty);
if (empty || message == null) {
setText(null);
setGraphic(null);
} else {
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

@ -0,0 +1,25 @@
package envoy.client.ui;
/**
* This interface defines an action that should be performed when a scene gets
* restored from the scene stack in {@link SceneContext}.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Restorable.java</strong><br>
* Created: <strong>03.07.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
@FunctionalInterface
public interface Restorable {
/**
* This method is getting called when a scene gets restored.<br>
* Hence, it can contain anything that should be done when the underlying scene
* gets restored.
*
* @since Envoy Client v0.1-beta
*/
void onRestore();
}

View File

@ -40,21 +40,21 @@ public final class SceneContext {
public enum SceneInfo {
/**
* The main scene in which chats are displayed.
* The main scene in which the chat screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
CHAT_SCENE("/fxml/ChatScene.fxml"),
/**
* The scene in which settings are displayed.
* The scene in which the settings screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
SETTINGS_SCENE("/fxml/SettingsScene.fxml"),
/**
* The scene in which the contact search is displayed.
* The scene in which the contact search screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
@ -72,7 +72,14 @@ public final class SceneContext {
*
* @since Envoy Client v0.1-beta
*/
LOGIN_SCENE("/fxml/LoginScene.fxml");
LOGIN_SCENE("/fxml/LoginScene.fxml"),
/**
* The scene in which the info screen is displayed.
*
* @since Envoy Client v0.1-beta
*/
MESSAGE_INFO_SCENE("/fxml/MessageInfoScene.fxml");
/**
* The path to the FXML resource.
@ -83,8 +90,9 @@ public final class SceneContext {
}
private final Stage stage;
private final FXMLLoader loader = new FXMLLoader();
private final Stack<Scene> sceneStack = new Stack<>();
private final FXMLLoader loader = new FXMLLoader();
private final Stack<Scene> sceneStack = new Stack<>();
private final Stack<Object> controllerStack = new Stack<>();
private static final Settings settings = Settings.getInstance();
@ -113,6 +121,7 @@ public final class SceneContext {
try {
final var rootNode = (Parent) loader.load(getClass().getResourceAsStream(sceneInfo.path));
final var scene = new Scene(rootNode);
controllerStack.push(loader.getController());
sceneStack.push(scene);
stage.setScene(scene);
@ -132,10 +141,16 @@ public final class SceneContext {
*/
public void pop() {
sceneStack.pop();
controllerStack.pop();
if (!sceneStack.isEmpty()) {
stage.setScene(sceneStack.peek());
final var newScene = sceneStack.peek();
stage.setScene(newScene);
applyCSS();
stage.sizeToScene();
// If the controller implements the Restorable interface,
// the actions to perform on restoration will be executed here
final var controller = controllerStack.peek();
if (controller instanceof Restorable) ((Restorable) controller).onRestore();
}
stage.show();
}
@ -154,7 +169,7 @@ public final class SceneContext {
* @return the controller used by the current scene
* @since Envoy Client v0.1-beta
*/
public <T> T getController() { return loader.getController(); }
public <T> T getController() { return (T) controllerStack.peek(); }
/**
* @return the stage in which the scenes are displayed

View File

@ -16,7 +16,6 @@ import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import envoy.client.data.Chat;
import envoy.client.data.LocalDB;
@ -24,7 +23,12 @@ import envoy.client.data.Settings;
import envoy.client.event.MessageCreationEvent;
import envoy.client.net.Client;
import envoy.client.net.WriteProxy;
import envoy.client.ui.*;
import envoy.client.ui.IconUtil;
import envoy.client.ui.Restorable;
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.*;
import envoy.event.contact.ContactOperation;
@ -38,7 +42,7 @@ import envoy.util.EnvoyLog;
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public final class ChatScene {
public final class ChatScene implements Restorable {
@FXML
private Label contactLabel;
@ -90,26 +94,16 @@ public final class ChatScene {
private void initialize() {
// Initialize message and user rendering
messageList.setCellFactory(listView -> new MessageListCell());
userList.setCellFactory(listView -> new ContactListCell());
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 -> {
final var message = e.get();
if (message.getClass().equals(Message.class)) {
localDB.getChat(message.getSenderID()).ifPresent(chat -> {
chat.getMessages().add(message);
// Update UI if in current chat
if (chat == currentChat)
Platform.runLater(messageList::refresh);
});
} else {
localDB.getChat(message.getRecipientID()).ifPresent(chat -> {
chat.getMessages().add(message);
localDB.getChat(message instanceof GroupMessage ? message.getSenderID() : message.getRecipientID()).ifPresent(chat -> {
chat.insert(message);
if (chat.equals(currentChat)) {
try {
currentChat.read(writeProxy);
@ -118,8 +112,7 @@ public final class ChatScene {
}
Platform.runLater(() -> { messageList.refresh(); scrollToMessageListEnd(); });
}
});
}
});
});
// Listen to message status changes
@ -128,7 +121,7 @@ public final class ChatScene {
// Update UI if in current chat
if (currentChat != null && message.getSenderID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh);
}));
eventBus.register(GroupMessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(groupMessage -> {
((GroupMessage) groupMessage).getMemberStatuses().replace(e.getMemberID(), e.get());
@ -180,10 +173,13 @@ public final class ChatScene {
userList.setItems(FXCollections.observableList(localDB.getChats().stream().map(Chat::getRecipient).collect(Collectors.toList())));
contactLabel.setText(localDB.getUser().getName());
MessageListCell.setUser(localDB.getUser());
if (!client.isOnline()) updateInfoLabel("You are offline", Color.YELLOW);
MessageControl.setUser(localDB.getUser());
if (!client.isOnline()) updateInfoLabel("You are offline", "infoLabel-info");
}
@Override
public void onRestore() { updateRemainingCharsLabel(); }
/**
* Actions to perform when the list of contacts has been clicked.
*
@ -193,7 +189,6 @@ public final class ChatScene {
private void userListClicked() {
final Contact user = userList.getSelectionModel().getSelectedItem();
if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) {
logger.log(Level.FINEST, "Loading chat with " + user);
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
@ -201,6 +196,9 @@ public final class ChatScene {
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
@ -271,7 +269,7 @@ public final class ChatScene {
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, Color.RED);
updateInfoLabel(noMoreMessaging, "infoLabel-error");
}
}
@ -317,35 +315,24 @@ public final class ChatScene {
postButton.setDisable(true);
messageTextArea.setDisable(true);
messageTextArea.clear();
updateInfoLabel("You need to go online to send more messages", Color.RED);
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 {
if (currentChat.getRecipient().getClass().equals(Group.class)) {
// Create and send groupMessage
final var groupMessage = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
.setText(messageTextArea.getText().strip())
.buildGroupMessage((Group) currentChat.getRecipient());
// Create and send message
final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
.setText(text);
final var message = currentChat.getRecipient() instanceof Group ? builder.buildGroupMessage((Group) currentChat.getRecipient())
: builder.build();
// Send groupMessage
writeProxy.writeMessage(groupMessage);
// Send message
writeProxy.writeMessage(message);
// Add message to LocalDB and update UI
messageList.getItems().add(groupMessage);
} else {
// 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);
// Add message to LocalDB and update UI
messageList.getItems().add(message);
}
// Add message to LocalDB and update UI
currentChat.insert(message);
messageList.refresh();
scrollToMessageListEnd();
// Request a new ID generator if all IDs were used
@ -372,13 +359,14 @@ public final class ChatScene {
/**
* Updates the {@code infoLabel}.
*
* @param text the text to use
* @param textfill the color in which to display information
* @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, Paint textfill) {
private void updateInfoLabel(String text, String infoLabelID) {
infoLabel.setText(text);
infoLabel.setTextFill(textfill);
infoLabel.setId(infoLabelID);
infoLabel.setVisible(true);
}
@ -414,4 +402,7 @@ public final class ChatScene {
updateRemainingCharsLabel();
postButton.setDisable(messageText.isBlank());
}
@FXML
private void loadMessageInfoScene() { try {} catch (final NullPointerException e) {} }
}

View File

@ -5,13 +5,16 @@ import java.util.logging.Logger;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ListView;
import envoy.client.data.LocalDB;
import envoy.client.event.SendEvent;
import envoy.client.ui.ContactListCell;
import envoy.client.ui.ClearableTextField;
import envoy.client.ui.SceneContext;
import envoy.client.ui.listcell.ContactListCellFactory;
import envoy.data.Contact;
import envoy.event.ElementOperation;
import envoy.event.EventBus;
@ -31,19 +34,7 @@ import envoy.util.EnvoyLog;
public class ContactSearchScene {
@FXML
private Button backButton;
@FXML
private Button clearButton;
@FXML
private Button searchButton;
@FXML
private Button newGroupButton;
@FXML
private TextField searchBar;
private ClearableTextField searchBar;
@FXML
private ListView<Contact> contactList;
@ -67,7 +58,8 @@ public class ContactSearchScene {
@FXML
private void initialize() {
contactList.setCellFactory(e -> new ContactListCell());
contactList.setCellFactory(ContactListCellFactory::new);
searchBar.setClearButtonListener(e -> { searchBar.getTextField().clear(); contactList.getItems().clear(); });
eventBus.register(ContactSearchResult.class,
response -> Platform.runLater(() -> { contactList.getItems().clear(); contactList.getItems().addAll(response.get()); }));
}
@ -78,20 +70,12 @@ public class ContactSearchScene {
* @since Envoy Client v0.1-beta
*/
@FXML
private void checkClearButton() {
final var containsContent = searchBar.getText().strip().isEmpty();
clearButton.setDisable(containsContent);
searchButton.setDisable(containsContent);
private void sendRequest() {
final var text = searchBar.getTextField().getText().strip();
if (!text.isBlank()) eventBus.dispatch(new SendEvent(new ContactSearchRequest(text)));
else contactList.getItems().clear();
}
/**
* Sends a {@link ContactSearchRequest} to the server.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void suggestContacts() { eventBus.dispatch(new SendEvent(new ContactSearchRequest(searchBar.getText()))); }
/**
* Clears the text in the search bar and the items shown in the list.
* Additionally disables both clear and search button.
@ -100,10 +84,8 @@ public class ContactSearchScene {
*/
@FXML
private void clear() {
searchBar.setText(null);
searchBar.getTextField().setText(null);
contactList.getItems().clear();
clearButton.setDisable(true);
searchButton.setDisable(true);
}
/**
@ -119,14 +101,17 @@ public class ContactSearchScene {
final var alert = new Alert(AlertType.CONFIRMATION);
alert.setTitle("Add Contact to Contact List");
alert.setHeaderText("Add the user " + contact.getName() + " to your contact list?");
alert.showAndWait().filter(btn -> btn == ButtonType.OK).ifPresent(btn -> {
// Normally, this would be total BS (we are already on the FX Thread), however
// it could be proven that the creation of this dialog wrapped in
// Platform.runLater is less error-prone than without it
Platform.runLater(() -> alert.showAndWait().filter(btn -> btn == ButtonType.OK).ifPresent(btn -> {
final var event = new ContactOperation(contact, ElementOperation.ADD);
// Sends the event to the server
eventBus.dispatch(new SendEvent(event));
// Updates the UI
eventBus.dispatch(event);
logger.log(Level.INFO, "Added contact " + contact);
});
}));
}
}

View File

@ -9,8 +9,9 @@ import javafx.scene.control.Alert.AlertType;
import envoy.client.data.LocalDB;
import envoy.client.event.SendEvent;
import envoy.client.ui.ContactListCell;
import envoy.client.ui.ClearableTextField;
import envoy.client.ui.SceneContext;
import envoy.client.ui.listcell.ContactListCellFactory;
import envoy.data.Contact;
import envoy.event.EventBus;
import envoy.event.GroupCreation;
@ -30,7 +31,7 @@ public class GroupCreationScene {
private Button createButton;
@FXML
private TextField groupNameField;
private ClearableTextField groupNameField;
@FXML
private ListView<Contact> contactList;
@ -41,8 +42,9 @@ public class GroupCreationScene {
@FXML
private void initialize() {
contactList.setCellFactory(e -> new ContactListCell());
contactList.setCellFactory(ContactListCellFactory::new);
contactList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
groupNameField.setClearButtonListener(e -> { groupNameField.getTextField().clear(); createButton.setDisable(true); });
}
/**
@ -59,11 +61,22 @@ public class GroupCreationScene {
/**
* Enables the {@code createButton} if at least one contact is selected.
*
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void contactListClicked() { createButton.setDisable(contactList.getSelectionModel().isEmpty()); }
private void contactListClicked() {
createButton.setDisable(contactList.getSelectionModel().isEmpty() || groupNameField.getTextField().getText().isBlank());
}
/**
* Checks, whether the {@code createButton} can be enabled because text is
* present in the textfield.
*
* @since Envoy Client v0.1-beta
*/
@FXML
private void textUpdated() { createButton.setDisable(groupNameField.getTextField().getText().isBlank()); }
/**
* Sends a {@link GroupCreation} to the server and closes this scene.
@ -74,10 +87,10 @@ public class GroupCreationScene {
*/
@FXML
private void createButtonClicked() {
final var name = groupNameField.getText();
final var name = groupNameField.getTextField().getText();
if (!Bounds.isValidContactName(name)) {
new Alert(AlertType.ERROR, "The entered group name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait();
groupNameField.clear();
groupNameField.getTextField().clear();
} else {
eventBus.dispatch(new SendEvent(new GroupCreation(name,
contactList.getSelectionModel().getSelectedItems().stream().map(Contact::getID).collect(Collectors.toSet()))));

View File

@ -13,6 +13,7 @@ import javafx.scene.control.Alert.AlertType;
import envoy.client.data.*;
import envoy.client.net.Client;
import envoy.client.ui.ClearableTextField;
import envoy.client.ui.SceneContext;
import envoy.client.ui.Startup;
import envoy.data.*;
@ -34,7 +35,7 @@ import envoy.util.EnvoyLog;
public final class LoginScene {
@FXML
private TextField userTextField;
private ClearableTextField userTextField;
@FXML
private PasswordField passwordField;
@ -119,17 +120,17 @@ public final class LoginScene {
if (registerCheckBox.isSelected() && !passwordField.getText().equals(repeatPasswordField.getText())) {
new Alert(AlertType.ERROR, "The entered password is unequal to the repeated one").showAndWait();
repeatPasswordField.clear();
} else if (!Bounds.isValidContactName(userTextField.getText())) {
} else if (!Bounds.isValidContactName(userTextField.getTextField().getText())) {
new Alert(AlertType.ERROR, "The entered user name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait();
userTextField.clear();
} else
performHandshake(
new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), registerCheckBox.isSelected(), Startup.VERSION));
userTextField.getTextField().clear();
} else performHandshake(new LoginCredentials(userTextField.getTextField().getText(), passwordField.getText().toCharArray(),
registerCheckBox.isSelected(), Startup.VERSION));
}
@FXML
private void offlineModeButtonPressed() {
attemptOfflineMode(new LoginCredentials(userTextField.getText(), passwordField.getText().toCharArray(), false, Startup.VERSION));
attemptOfflineMode(
new LoginCredentials(userTextField.getTextField().getText(), passwordField.getText().toCharArray(), false, Startup.VERSION));
}
@FXML
@ -162,8 +163,7 @@ public final class LoginScene {
loadChatScene();
}
} catch (IOException | InterruptedException | TimeoutException e) {
logger.log(Level.WARNING, "Could not connect to server: ", e);
logger.log(Level.FINER, "Attempting offline mode...");
logger.log(Level.INFO, "Could not connect to server. Entering offline mode...");
attemptOfflineMode(credentials);
}
}

View File

@ -0,0 +1,40 @@
package envoy.client.ui.listcell;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import envoy.data.Contact;
import envoy.data.Group;
import envoy.data.User;
/**
* This class formats a single {@link Contact} into a UI component.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContactControl.java</strong><br>
* Created: <strong>01.07.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class ContactControl extends VBox {
/**
* @param contact the contact that should be formatted
* @since Envoy Client v0.1-beta
*/
public ContactControl(Contact contact) {
// Container with contact name
final var nameLabel = new Label(contact.getName());
nameLabel.setWrapText(true);
getChildren().add(nameLabel);
if (contact instanceof User) {
// Online status
final var user = (User) contact;
final var statusLabel = new Label(user.getStatus().toString());
statusLabel.getStyleClass().add(user.getStatus().toString().toLowerCase());
getChildren().add(statusLabel);
} else // Member count
getChildren().add(new Label(((Group) contact).getContacts().size() + " members"));
}
}

View File

@ -0,0 +1,44 @@
package envoy.client.ui.listcell;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import envoy.data.Contact;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListCell.java</strong><br>
* Created: <strong>28.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class ContactListCellFactory extends ListCell<Contact> {
private final ListView<Contact> listView;
/**
* @param listView the list view inside which this cell is contained
* @since Envoy Client v0.1-beta
*/
public ContactListCellFactory(ListView<Contact> listView) { this.listView = listView; }
/**
* 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) {
setText(null);
setGraphic(null);
} else {
final var control = new ContactControl(contact);
prefWidthProperty().bind(listView.widthProperty().subtract(40));
setGraphic(control);
}
}
}

View File

@ -0,0 +1,60 @@
package envoy.client.ui.listcell;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import envoy.client.ui.IconUtil;
import envoy.data.Message;
import envoy.data.Message.MessageStatus;
import envoy.data.User;
/**
* This class formats a single {@link Message} into a UI component.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageControl.java</strong><br>
* Created: <strong>01.07.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public class MessageControl extends VBox {
private static User client;
private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
private static final Map<MessageStatus, Image> statusImages = IconUtil.loadByEnum(MessageStatus.class, 16);
/**
*
* @param message the message that should be formatted
* @since Envoy Client v0.1-beta
*/
public MessageControl(Message message) {
// Creating the underlying VBox, the dateLabel and the textLabel
super(new Label(dateFormat.format(message.getCreationDate())));
final var textLabel = new Label(message.getText());
textLabel.setWrapText(true);
getChildren().add(textLabel);
// Setting the message status icon and background color
if (message.getRecipientID() != client.getID()) {
final var statusIcon = new ImageView(statusImages.get(message.getStatus()));
statusIcon.setPreserveRatio(true);
getChildren().add(statusIcon);
getStyleClass().add("own-message");
} else getStyleClass().add("received-message");
// Adjusting height and weight of the cell to the corresponding ListView
paddingProperty().setValue(new Insets(5, 20, 5, 20));
}
/**
* @param client the user who has logged in
* @since Envoy Client v0.1-beta
*/
public static void setUser(User client) { MessageControl.client = client; }
}

View File

@ -0,0 +1,52 @@
package envoy.client.ui.listcell;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.Tooltip;
import javafx.stage.PopupWindow.AnchorLocation;
import envoy.data.Message;
/**
* Displays a single message inside the message list.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageListCellFactory.java</strong><br>
* Created: <strong>28.03.2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta
*/
public class MessageListCellFactory extends ListCell<Message> {
private final ListView<Message> listView;
/**
* @param listView the list view inside which this cell is contained
* @since Envoy Client v0.1-beta
*/
public MessageListCellFactory(ListView<Message> listView) { this.listView = listView; }
/**
* Displays the text, the data of creation and the status of a message.
*
* @since Envoy v0.1-beta
*/
@Override
protected void updateItem(Message message, boolean empty) {
super.updateItem(message, empty);
if (empty || message == null) {
setText(null);
setGraphic(null);
} else {
final var control = new MessageControl(message);
control.prefWidthProperty().bind(listView.widthProperty().subtract(40));
// Creating the Tooltip to deselect a message
final var tooltip = new Tooltip("You can select a message by clicking on it \nand deselect it by pressing \"ctrl\" and clicking on it");
tooltip.setWrapText(true);
tooltip.setAnchorLocation(AnchorLocation.WINDOW_TOP_LEFT);
setTooltip(tooltip);
setGraphic(control);
}
}
}

View File

@ -0,0 +1,12 @@
/**
* This package contains custom list cells that are used to display certain
* things.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>30.06.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
package envoy.client.ui.listcell;

Binary file not shown.

View File

@ -3,7 +3,7 @@
}
.context-menu, .context-menu > * {
-fx-background-radius: 15px;
-fx-background-radius: 15.0px;
/*TODO: solution below does not work */
-fx-background-color: transparent;
}
@ -58,3 +58,19 @@
-fx-text-fill: #00FF00;
-fx-background-color: transparent;
}
#infoLabel-success {
-fx-text-fill: #00FF00;
}
#infoLabel-info {
-fx-text-fill: yellow;
}
#infoLabel-warning {
-fx-text-fill: orange;
}
#infoLabel-error {
-fx-text-fill: red;
}

View File

@ -12,9 +12,9 @@
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<GridPane maxHeight="-Infinity" maxWidth="-Infinity"
<GridPane hgap="5.0" maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="400.0" minWidth="350.0" prefHeight="400.0" prefWidth="600.0"
xmlns="http://javafx.com/javafx/11.0.1"
vgap="2.0" xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="envoy.client.ui.controller.ChatScene">
<columnConstraints>
@ -47,7 +47,7 @@
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" />
<Insets bottom="10.0" left="10.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
@ -64,7 +64,7 @@
<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" />
<Insets left="10.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
@ -75,17 +75,18 @@
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" />
<Insets bottom="10.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="465.0" GridPane.columnIndex="1" GridPane.columnSpan="2"
GridPane.rowIndex="1" GridPane.rowSpan="2">
prefWidth="465.0" GridPane.columnIndex="1"
GridPane.columnSpan="2147483647" GridPane.rowIndex="1"
GridPane.rowSpan="2">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="10.0" top="5.0" />
<Insets left="5.0" right="10.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
@ -101,6 +102,8 @@
onAction="#forwardMessage" text="Forward" />
<MenuItem mnemonicParsing="false"
onAction="#quoteMessage" text="Quote" />
<MenuItem mnemonicParsing="false"
onAction="#loadMessageInfoScene" text="Info" />
</items>
</ContextMenu>
</contextMenu>
@ -111,11 +114,8 @@
GridPane.halignment="CENTER" GridPane.rowIndex="4"
GridPane.valignment="BOTTOM">
<GridPane.margin>
<Insets bottom="10.0" left="5.0" right="10.0" />
<Insets bottom="10.0" right="10.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"
@ -130,6 +130,9 @@
</items>
</ContextMenu>
</contextMenu>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Button>
<TextArea fx:id="messageTextArea" disable="true"
onInputMethodTextChanged="#messageTextUpdated"
@ -137,7 +140,7 @@
prefHeight="200.0" prefWidth="200.0" wrapText="true"
GridPane.columnIndex="1" GridPane.rowIndex="4">
<GridPane.margin>
<Insets bottom="10.0" left="5.0" right="5.0" top="5.0" />
<Insets bottom="10.0" left="5.0" top="3.0" />
</GridPane.margin>
<opaqueInsets>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
@ -163,7 +166,7 @@
<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" />
<Insets bottom="5.0" top="5.0" />
</padding>
<opaqueInsets>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
@ -175,8 +178,8 @@
</tooltip>
</Label>
<Label fx:id="infoLabel" text="Something happened"
textFill="#faa007" visible="false" GridPane.columnIndex="1"
GridPane.rowIndex="1">
wrapText="true" textFill="#faa007" visible="false"
GridPane.columnIndex="1" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>

View File

@ -1,77 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import envoy.client.ui.ClearableTextField?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" 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.ContactSearchScene">
<children>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
<children>
<TextField fx:id="searchBar" onInputMethodTextChanged="#checkClearButton" onKeyTyped="#checkClearButton" prefColumnCount="22" promptText="Enter username to search for">
<HBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<tooltip>
<Tooltip text="Enter a name. If an account by that name exists, it will be displayed below." wrapText="true" />
</tooltip>
</TextField>
<Button fx:id="clearButton" disable="true" mnemonicParsing="true" onAction="#clear" prefHeight="26.0" prefWidth="110.0" text="Clea_r">
<tooltip>
<Tooltip autoHide="true" text="Clears the text to the left and the elements below" wrapText="true" />
</tooltip>
<HBox.margin>
<Insets bottom="5.0" left="5.0" right="10.0" top="5.0" />
</HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Button>
<Button fx:id="searchButton" defaultButton="true" disable="true" mnemonicParsing="true" onAction="#suggestContacts" prefHeight="26.0" prefWidth="124.0" text="_Search" textOverrun="LEADING_WORD_ELLIPSIS">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<HBox.margin>
<Insets bottom="5.0" right="20.0" top="5.0" />
</HBox.margin>
<tooltip>
<Tooltip autoHide="true" text="Search for accounts with the name entered to the left" wrapText="true" />
</tooltip>
</Button>
<Button mnemonicParsing="false" onAction="#newGroupButtonClicked" prefHeight="26.0" prefWidth="139.0" text="New Group">
<HBox.margin>
<Insets bottom="5.0" left="20.0" right="5.0" top="5.0" />
</HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Button>
</children>
</HBox>
<ListView fx:id="contactList" onMouseClicked="#contactListClicked" prefHeight="314.0" prefWidth="600.0">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<VBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</VBox.margin></ListView>
<Button fx:id="backButton" cancelButton="true" mnemonicParsing="true" onAction="#backButtonClicked" text="_Back">
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</VBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<tooltip>
<Tooltip autoHide="true" text="Takes you back to the screen where you can chat with others" wrapText="true" />
</tooltip>
</Button>
</children>
<VBox maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity" 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.ContactSearchScene">
<children>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
<children>
<ClearableTextField fx:id="searchBar"
prefWidth="310.0">
<textField onInputMethodTextChanged="#sendRequest"
onKeyTyped="#sendRequest" prefColumnCount="22"
promptText="Enter username to search for">
</textField>
<HBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="15.0" />
</HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<tooltip>
<Tooltip
text="Enter a name. If an account by that name exists, it will be displayed below."
wrapText="true" />
</tooltip>
</ClearableTextField>
<Button mnemonicParsing="false"
onAction="#newGroupButtonClicked" prefHeight="26.0"
prefWidth="139.0" text="New Group">
<HBox.margin>
<Insets bottom="5.0" left="30.0" right="5.0" top="5.0" />
</HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Button>
</children>
</HBox>
<ListView fx:id="contactList"
onMouseClicked="#contactListClicked" prefHeight="314.0"
prefWidth="600.0">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<VBox.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</VBox.margin>
</ListView>
<Button cancelButton="true" mnemonicParsing="true"
onAction="#backButtonClicked" text="_Back">
<VBox.margin>
<Insets bottom="10.0" left="10.0" />
</VBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<tooltip>
<Tooltip autoHide="true"
text="Takes you back to the screen where you can chat with others"
wrapText="true" />
</tooltip>
</Button>
</children>
</VBox>

View File

@ -1,67 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import envoy.client.ui.ClearableTextField?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ListView?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Tooltip?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" 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.GroupCreationScene">
<children>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
<children>
<TextField fx:id="groupNameField" prefColumnCount="22" promptText="Enter Group Name">
<HBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<tooltip>
<Tooltip text="Enter a name. If an account by that name exists, it will be displayed below." wrapText="true" />
</tooltip>
</TextField>
</children>
</HBox>
<Label text="Choose Members:">
<font>
<Font size="16.0" />
</font>
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</VBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Label>
<ListView fx:id="contactList" onMouseClicked="#contactListClicked" prefHeight="314.0" prefWidth="600.0">
<VBox.margin>
<Insets bottom="5.0" left="10.0" right="10.0" top="5.0" />
</VBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></ListView>
<Button fx:id="createButton" defaultButton="true" disable="true" mnemonicParsing="false" onAction="#createButtonClicked" text="Create">
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</VBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></Button>
<Button cancelButton="true" mnemonicParsing="true" onAction="#backButtonClicked" text="_Back">
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</VBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<tooltip>
<Tooltip autoHide="true" text="Takes you back to the screen where you can chat with others" wrapText="true" />
</tooltip>
</Button>
</children>
<VBox maxHeight="-Infinity" maxWidth="-Infinity"
minHeight="-Infinity" minWidth="-Infinity" 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.GroupCreationScene">
<children>
<HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0">
<children>
<ClearableTextField fx:id="groupNameField">
<textField prefColumnCount="22"
promptText="Enter Group Name"
onInputMethodTextChanged="#textUpdated" onKeyTyped="#textUpdated" />
<HBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<tooltip>
<Tooltip
text="Enter something. A group with this name will be created."
wrapText="true" />
</tooltip>
</ClearableTextField>
</children>
</HBox>
<Label text="Choose Members:">
<font>
<Font size="16.0" />
</font>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Label>
<ListView fx:id="contactList"
onMouseClicked="#contactListClicked" prefHeight="314.0"
prefWidth="600.0">
<VBox.margin>
<Insets bottom="5.0" left="10.0" right="10.0" top="5.0" />
</VBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</ListView>
<BorderPane prefHeight="50.0">
<left>
<Button cancelButton="true" mnemonicParsing="true"
onAction="#backButtonClicked" text="_Back"
BorderPane.alignment="CENTER">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<tooltip>
<Tooltip autoHide="true"
text="Takes you back to the screen where you can chat with others"
wrapText="true" />
</tooltip>
<BorderPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</BorderPane.margin>
</Button>
</left>
<right>
<Button fx:id="createButton" alignment="CENTER_RIGHT"
defaultButton="true" disable="true" mnemonicParsing="false"
onAction="#createButtonClicked" text="Create"
BorderPane.alignment="CENTER">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<BorderPane.margin>
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
</BorderPane.margin>
</Button>
</right>
</BorderPane>
</children>
</VBox>

View File

@ -1,104 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import envoy.client.ui.ClearableTextField?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<VBox prefHeight="206.0" prefWidth="440.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="envoy.client.ui.controller.LoginScene">
<children>
<Label text="User Login">
<font>
<Font size="26.0" />
</font>
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</VBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<VBox prefHeight="206.0" prefWidth="440.0"
xmlns="http://javafx.com/javafx/11.0.1"
xmlns:fx="http://javafx.com/fxml/1"
fx:controller="envoy.client.ui.controller.LoginScene">
<children>
<Label text="User Login">
<font>
<Font size="26.0" />
</font>
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</VBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Label>
<GridPane hgap="5.0" vgap="10.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES"
minWidth="10.0" percentWidth="40.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES"
minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0"
vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0"
vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0"
vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="User Name:">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
</Label>
<GridPane>
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="40.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="User Name:">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding></Label>
<Label text="Password" GridPane.rowIndex="1">
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin></Label>
<Label fx:id="repeatPasswordLabel" text="Repeat Password:" visible="false" GridPane.rowIndex="2">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding></Label>
<TextField fx:id="userTextField" GridPane.columnIndex="1">
<GridPane.margin>
<Insets bottom="10.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></TextField>
<PasswordField fx:id="passwordField" GridPane.columnIndex="1" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="5.0" right="5.0" top="10.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></PasswordField>
<PasswordField fx:id="repeatPasswordField" visible="false" GridPane.columnIndex="1" GridPane.rowIndex="2">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="10.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></PasswordField>
</children>
</GridPane>
<CheckBox fx:id="registerCheckBox" mnemonicParsing="true" onAction="#registerCheckboxChanged" prefHeight="17.0" prefWidth="181.0" text="_Register">
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
<VBox.margin>
<Insets bottom="3.0" left="3.0" right="3.0" top="3.0" />
</VBox.margin></CheckBox>
<Label fx:id="connectionLabel" />
<ButtonBar prefHeight="40.0" prefWidth="200.0">
<buttons>
<Button mnemonicParsing="false" onAction="#abortLogin" text="Close">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</Button>
<Button mnemonicParsing="false" onAction="#offlineModeButtonPressed" text="Offline mode" />
<Button defaultButton="true" mnemonicParsing="false" onAction="#loginButtonPressed" text="Login" />
</buttons>
</ButtonBar>
</children>
<Label text="Password:" GridPane.rowIndex="1">
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
</Label>
<Label fx:id="repeatPasswordLabel" text="Repeat Password:"
visible="false" GridPane.rowIndex="2">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
<padding>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</padding>
</Label>
<ClearableTextField fx:id="userTextField"
GridPane.columnIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="5.0" right="5.0" top="5.0" />
</GridPane.margin>
</ClearableTextField>
<PasswordField fx:id="passwordField"
GridPane.columnIndex="1" GridPane.rowIndex="1">
<GridPane.margin>
<Insets bottom="10.0" left="5.0" right="5.0" top="10.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</PasswordField>
<PasswordField fx:id="repeatPasswordField"
visible="false" GridPane.columnIndex="1" GridPane.rowIndex="2">
<GridPane.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="10.0" />
</GridPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</PasswordField>
</children>
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</VBox.margin>
</GridPane>
<CheckBox fx:id="registerCheckBox" mnemonicParsing="true"
onAction="#registerCheckboxChanged" prefHeight="17.0"
prefWidth="181.0" text="_Register">
<VBox.margin>
<Insets left="5.0" right="3.0" />
</VBox.margin>
</CheckBox>
<Label fx:id="connectionLabel">
<VBox.margin>
<Insets left="5.0" />
</VBox.margin>
</Label>
<BorderPane prefWidth="200.0">
<left>
<Button cancelButton="true" mnemonicParsing="false"
onAction="#abortLogin" text="Close" BorderPane.alignment="CENTER">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<BorderPane.margin>
<Insets />
</BorderPane.margin>
</Button>
</left>
<center>
<Button mnemonicParsing="false"
onAction="#offlineModeButtonPressed" text="Offline mode"
BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets />
</BorderPane.margin>
</Button>
</center>
<right>
<Button defaultButton="true" mnemonicParsing="false"
onAction="#loginButtonPressed" text="Login"
BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets />
</BorderPane.margin>
</Button>
</right>
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" />
</VBox.margin>
</BorderPane>
</children>
</VBox>

View File

@ -7,39 +7,50 @@
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="TOP_RIGHT" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" 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.SettingsScene">
<VBox alignment="TOP_RIGHT" maxHeight="-Infinity"
maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
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.SettingsScene">
<children>
<HBox prefHeight="389.0" prefWidth="600.0">
<children>
<ListView fx:id="settingsList" onMouseClicked="#settingsListClicked" prefHeight="200.0" prefWidth="200.0">
<opaqueInsets>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</opaqueInsets>
<HBox.margin>
<Insets bottom="10.0" left="10.0" right="5.0" top="10.0" />
</HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></ListView>
<TitledPane fx:id="titledPane" collapsible="false" prefHeight="325.0" prefWidth="300.0">
<HBox.margin>
<Insets bottom="10.0" left="5.0" right="10.0" top="10.0" />
</HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></TitledPane>
</children>
<children>
<ListView fx:id="settingsList"
onMouseClicked="#settingsListClicked" prefHeight="200.0"
prefWidth="200.0">
<opaqueInsets>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</opaqueInsets>
<HBox.margin>
<Insets bottom="10.0" left="10.0" right="5.0" top="10.0" />
</HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</ListView>
<TitledPane fx:id="titledPane" collapsible="false"
prefHeight="325.0" prefWidth="300.0">
<HBox.margin>
<Insets bottom="10.0" left="5.0" right="10.0" top="10.0" />
</HBox.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
</TitledPane>
</children>
</HBox>
<Button defaultButton="true" mnemonicParsing="true" onMouseClicked="#backButtonClicked" text="_Back">
<opaqueInsets>
<Insets />
</opaqueInsets>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</VBox.margin>
</Button>
<Button defaultButton="true" mnemonicParsing="true"
onMouseClicked="#backButtonClicked" text="_Back">
<opaqueInsets>
<Insets />
</opaqueInsets>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding>
<VBox.margin>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</VBox.margin>
</Button>
</children>
</VBox>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB