diff --git a/client/src/main/java/envoy/client/ui/ListViewRefresh.java b/client/src/main/java/envoy/client/ui/ListViewRefresh.java
new file mode 100644
index 0000000..962b18c
--- /dev/null
+++ b/client/src/main/java/envoy/client/ui/ListViewRefresh.java
@@ -0,0 +1,36 @@
+package envoy.client.ui;
+
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+
+/**
+ * This is a utility class that provides access to a refreshing mechanism for
+ * elements that were added without notifying the underlying {@link ListView}.
+ *
+ * Project: envoy-client
+ * File: ListViewRefresh.java
+ * Created: 16.07.2020
+ *
+ * @author Leon Hofmeister
+ * @since Envoy Client v0.1-beta
+ */
+public final class ListViewRefresh {
+
+ private ListViewRefresh() {}
+
+ /**
+ * Deeply refreshes a {@code listview}, meaning it recomputes every single of
+ * its {@link ListCell}s.
+ *
+ * While it does work, it is not the most efficient algorithm possible.
+ *
+ * @param toRefresh the listView to refresh
+ * @param the type of its {@code listcells}
+ * @since Envoy Client v0.1-beta
+ */
+ public static void deepRefresh(ListView toRefresh) {
+ final var items = toRefresh.getItems();
+ toRefresh.setItems(null);
+ toRefresh.setItems(items);
+ }
+}
diff --git a/client/src/main/java/envoy/client/ui/controller/ChatScene.java b/client/src/main/java/envoy/client/ui/controller/ChatScene.java
index 920d93f..83e64fe 100644
--- a/client/src/main/java/envoy/client/ui/controller/ChatScene.java
+++ b/client/src/main/java/envoy/client/ui/controller/ChatScene.java
@@ -31,9 +31,7 @@ import envoy.client.data.audio.AudioRecorder;
import envoy.client.event.MessageCreationEvent;
import envoy.client.net.Client;
import envoy.client.net.WriteProxy;
-import envoy.client.ui.IconUtil;
-import envoy.client.ui.Restorable;
-import envoy.client.ui.SceneContext;
+import envoy.client.ui.*;
import envoy.client.ui.listcell.ChatControl;
import envoy.client.ui.listcell.ListCellFactory;
import envoy.client.ui.listcell.MessageControl;
@@ -144,7 +142,7 @@ public final class ChatScene implements Restorable {
} catch (final IOException e1) {
logger.log(Level.WARNING, "Could not read current chat: ", e1);
}
- Platform.runLater(() -> { messageList.refresh(); scrollToMessageListEnd(); });
+ Platform.runLater(() -> { ListViewRefresh.deepRefresh(messageList); scrollToMessageListEnd(); });
} else chat.incrementUnreadAmount();
// Moving chat with most recent unreadMessages to the top
Platform.runLater(() -> {
@@ -158,8 +156,9 @@ public final class ChatScene implements Restorable {
// Listen to message status changes
eventBus.register(MessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(message -> {
message.setStatus(e.get());
- // Update UI if in current chat
- if (currentChat != null && message.getSenderID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh);
+ // Update UI if in current chat and the current user was the sender of the
+ // message
+ if (currentChat != null && message.getSenderID() == client.getSender().getID()) Platform.runLater(messageList::refresh);
}));
eventBus.register(GroupMessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(groupMessage -> {
@@ -176,7 +175,7 @@ public final class ChatScene implements Restorable {
.filter(c -> c.getRecipient().getID() == e.getID())
.findAny()
.map(Chat::getRecipient)
- .ifPresent(u -> { ((User) u).setStatus(e.get()); Platform.runLater(chatList::refresh); }));
+ .ifPresent(u -> { ((User) u).setStatus(e.get()); Platform.runLater(() -> ListViewRefresh.deepRefresh(chatList)); }));
// Listen to contacts changes
eventBus.register(ContactOperation.class, e -> {
@@ -184,7 +183,7 @@ public final class ChatScene implements Restorable {
switch (e.getOperationType()) {
case ADD:
localDB.getUsers().put(contact.getName(), contact);
- Chat chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact);
+ final Chat chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact);
Platform.runLater(() -> chatList.getItems().add(chat));
break;
case REMOVE:
@@ -356,6 +355,7 @@ public final class ChatScene implements Restorable {
try {
final var fileBytes = Files.readAllBytes(file.toPath());
pendingAttachment = new Attachment(fileBytes, type);
+ checkPostConditions(false);
// Setting the preview image as image of the attachmentView
if (type == AttachmentType.PICTURE)
attachmentView.setImage(new Image(new ByteArrayInputStream(fileBytes), DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE, true, true));
@@ -414,9 +414,9 @@ public final class ChatScene implements Restorable {
|| !settings.isEnterToSend() && e.getCode() == KeyCode.ENTER && e.isControlDown());
}
- private void checkPostConditions(boolean sendKeyPressed) {
+ private void checkPostConditions(boolean postMessage) {
if (!postingPermanentlyDisabled) {
- if (!postButton.isDisabled() && sendKeyPressed) postMessage();
+ if (!postButton.isDisabled() && postMessage) postMessage();
postButton.setDisable(messageTextArea.getText().isBlank() && pendingAttachment == null || currentChat == null);
} else {
final var noMoreMessaging = "Go online to send messages";
@@ -499,7 +499,7 @@ public final class ChatScene implements Restorable {
localDB.getChats().remove(currentChat);
localDB.getChats().add(0, currentChat);
});
- messageList.refresh();
+ ListViewRefresh.deepRefresh(messageList);
scrollToMessageListEnd();
// Request a new ID generator if all IDs were used
diff --git a/client/src/main/java/envoy/client/ui/controller/ContactSearchScene.java b/client/src/main/java/envoy/client/ui/controller/ContactSearchScene.java
index 5b5c448..e21c646 100644
--- a/client/src/main/java/envoy/client/ui/controller/ContactSearchScene.java
+++ b/client/src/main/java/envoy/client/ui/controller/ContactSearchScene.java
@@ -118,7 +118,8 @@ public class ContactSearchScene {
final var event = new ContactOperation(user, ElementOperation.ADD);
// Sends the event to the server
eventBus.dispatch(new SendEvent(event));
- // Updates the UI
+ // Removes the chosen user and updates the UI
+ userList.getItems().remove(user);
eventBus.dispatch(event);
logger.log(Level.INFO, "Added user " + user);
}));
diff --git a/client/src/main/java/envoy/client/ui/controller/GroupCreationScene.java b/client/src/main/java/envoy/client/ui/controller/GroupCreationScene.java
index e6ca29a..71c7dd4 100644
--- a/client/src/main/java/envoy/client/ui/controller/GroupCreationScene.java
+++ b/client/src/main/java/envoy/client/ui/controller/GroupCreationScene.java
@@ -16,6 +16,8 @@ import envoy.client.ui.ClearableTextField;
import envoy.client.ui.SceneContext;
import envoy.client.ui.listcell.ContactControl;
import envoy.client.ui.listcell.ListCellFactory;
+import envoy.data.Contact;
+import envoy.data.Group;
import envoy.data.User;
import envoy.event.EventBus;
import envoy.event.GroupCreation;
@@ -50,6 +52,8 @@ public class GroupCreationScene {
private SceneContext sceneContext;
+ private LocalDB localDB;
+
private static final EventBus eventBus = EventBus.getInstance();
@FXML
@@ -66,7 +70,8 @@ public class GroupCreationScene {
* @since Envoy Client v0.1-beta
*/
public void initializeData(SceneContext sceneContext, LocalDB localDB) {
- this.sceneContext = sceneContext;
+ this.sceneContext = sceneContext;
+ this.localDB = localDB;
Platform.runLater(() -> userList.getItems()
.addAll(localDB.getChats()
.stream()
@@ -109,14 +114,47 @@ public class GroupCreationScene {
if (!Bounds.isValidContactName(name)) {
new Alert(AlertType.ERROR, "The entered group name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait();
groupNameField.getTextField().clear();
+ } else if (groupNameAlreadyPresent(name)) {
+ final var alert = new Alert(AlertType.WARNING, "You already have a group with that name.", ButtonType.OK, ButtonType.CANCEL);
+ alert.setTitle("Create Group?");
+ alert.setHeaderText("Proceed?");
+ alert.showAndWait().filter(btn -> btn == ButtonType.OK).ifPresent(btn -> createGroup(name));
} else {
- eventBus.dispatch(new SendEvent(
- new GroupCreation(name, userList.getSelectionModel().getSelectedItems().stream().map(User::getID).collect(Collectors.toSet()))));
new Alert(AlertType.INFORMATION, String.format("Group '%s' successfully created.", name)).showAndWait();
- sceneContext.pop();
+ createGroup(name);
}
}
+ /**
+ * Creates a new group with the given name and all selected members.
+ * Additionally pops the scene automatically.
+ *
+ * @param name the chosen group name
+ * @since Envoy Client v0.1-beta
+ */
+ private void createGroup(String name) {
+ eventBus.dispatch(new SendEvent(
+ new GroupCreation(name, userList.getSelectionModel().getSelectedItems().stream().map(User::getID).collect(Collectors.toSet()))));
+ sceneContext.pop();
+ }
+
+ /**
+ * Returns true if the proposed group name is already present in the users
+ * {@code LocalDB}.
+ *
+ * @param newName the chosen group name
+ * @return true if this name is already present
+ * @since Envoy Client v0.1-beta
+ */
+ public boolean groupNameAlreadyPresent(String newName) {
+ return localDB.getChats()
+ .stream()
+ .map(Chat::getRecipient)
+ .filter(Group.class::isInstance)
+ .map(Contact::getName)
+ .anyMatch(newName::equals);
+ }
+
@FXML
private void backButtonClicked() { sceneContext.pop(); }
}