diff --git a/client/src/main/java/envoy/client/net/Client.java b/client/src/main/java/envoy/client/net/Client.java index afa423a..a01468f 100644 --- a/client/src/main/java/envoy/client/net/Client.java +++ b/client/src/main/java/envoy/client/net/Client.java @@ -12,7 +12,7 @@ import envoy.client.event.SendEvent; import envoy.data.*; import envoy.event.*; import envoy.event.contact.ContactOperation; -import envoy.event.contact.ContactSearchResult; +import envoy.event.contact.UserSearchResult; import envoy.util.EnvoyLog; import envoy.util.SerializationUtils; @@ -147,7 +147,7 @@ public class Client implements Closeable { receiver.registerProcessor(NameChange.class, evt -> { localDB.replaceContactName(evt); eventBus.dispatch(evt); }); // Process contact searches - receiver.registerProcessor(ContactSearchResult.class, eventBus::dispatch); + receiver.registerProcessor(UserSearchResult.class, eventBus::dispatch); // Process contact operations receiver.registerProcessor(ContactOperation.class, eventBus::dispatch); 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 fd0e5c8..92f9e83 100644 --- a/client/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/client/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -34,7 +34,7 @@ import envoy.client.net.WriteProxy; 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.ChatListCellFactory; import envoy.client.ui.listcell.MessageControl; import envoy.client.ui.listcell.MessageListCellFactory; import envoy.data.*; @@ -125,7 +125,7 @@ public final class ChatScene implements Restorable { // Initialize message and user rendering messageList.setCellFactory(MessageListCellFactory::new); - chatList.setCellFactory(ContactListCellFactory::new); + chatList.setCellFactory(ChatListCellFactory::new); settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE))); voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE))); @@ -151,8 +151,6 @@ public final class ChatScene implements Restorable { chatList.getItems().remove(chat); chatList.getItems().add(0, chat); if (chat.equals(currentChat)) chatList.getSelectionModel().select(0); - localDB.getChats().remove(chat); - localDB.getChats().add(0, chat); }); }); }); @@ -187,13 +185,11 @@ public final class ChatScene implements Restorable { case ADD: localDB.getUsers().put(contact.getName(), contact); Chat chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact); - localDB.getChats().add(chat); Platform.runLater(() -> chatList.getItems().add(chat)); break; case REMOVE: localDB.getUsers().remove(contact.getName()); - localDB.getChats().removeIf(c -> c.getRecipient().getID() == contact.getID()); - Platform.runLater(() -> chatList.getItems().removeIf(c -> c.getRecipient().getID() == contact.getID())); + Platform.runLater(() -> chatList.getItems().removeIf(c -> c.getRecipient().equals(contact))); break; } }); @@ -233,6 +229,8 @@ public final class ChatScene implements Restorable { */ @FXML private void chatListClicked() { + if (chatList.getSelectionModel().isEmpty()) return; + final Contact user = chatList.getSelectionModel().getSelectedItem().getRecipient(); if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) { 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 a86d79d..b3bf823 100644 --- a/client/src/main/java/envoy/client/ui/controller/ContactSearchScene.java +++ b/client/src/main/java/envoy/client/ui/controller/ContactSearchScene.java @@ -2,7 +2,6 @@ package envoy.client.ui.controller; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; import javafx.application.Platform; import javafx.fxml.FXML; @@ -11,22 +10,31 @@ import javafx.scene.control.Alert.AlertType; import javafx.scene.control.ButtonType; import javafx.scene.control.ListView; -import envoy.client.data.Chat; import envoy.client.data.LocalDB; import envoy.client.event.SendEvent; import envoy.client.ui.ClearableTextField; import envoy.client.ui.SceneContext; import envoy.client.ui.listcell.ContactListCellFactory; +import envoy.data.User; import envoy.event.ElementOperation; import envoy.event.EventBus; import envoy.event.contact.ContactOperation; -import envoy.event.contact.ContactSearchRequest; -import envoy.event.contact.ContactSearchResult; +import envoy.event.contact.UserSearchRequest; +import envoy.event.contact.UserSearchResult; import envoy.util.EnvoyLog; /** + * Provides a search bar in which a user name (substring) can be entered. The + * users with a matching name are then displayed inside a list view. A + * {@link UserSearchRequest} is sent on every keystroke. + *

+ * The actual search algorithm is implemented on the server. + *

+ * To create a group, a button is available that loads the + * {@link GroupCreationScene}. + *

* Project: envoy-client
- * File: ContactSearchSceneController.java
+ * File: ContactSearchScene.java
* Created: 07.06.2020
* * @author Leon Hofmeister @@ -38,7 +46,7 @@ public class ContactSearchScene { private ClearableTextField searchBar; @FXML - private ListView chatList; + private ListView userList; private SceneContext sceneContext; @@ -59,12 +67,12 @@ public class ContactSearchScene { @FXML private void initialize() { - chatList.setCellFactory(ContactListCellFactory::new); - searchBar.setClearButtonListener(e -> { searchBar.getTextField().clear(); chatList.getItems().clear(); }); - eventBus.register(ContactSearchResult.class, + userList.setCellFactory(new ContactListCellFactory<>()); + searchBar.setClearButtonListener(e -> { searchBar.getTextField().clear(); userList.getItems().clear(); }); + eventBus.register(UserSearchResult.class, response -> Platform.runLater(() -> { - chatList.getItems().clear(); - chatList.getItems().addAll(response.get().stream().map(Chat::new).collect(Collectors.toList())); + userList.getItems().clear(); + userList.getItems().addAll(response.get()); })); } @@ -76,8 +84,8 @@ public class ContactSearchScene { @FXML private void sendRequest() { final var text = searchBar.getTextField().getText().strip(); - if (!text.isBlank()) eventBus.dispatch(new SendEvent(new ContactSearchRequest(text))); - else chatList.getItems().clear(); + if (!text.isBlank()) eventBus.dispatch(new SendEvent(new UserSearchRequest(text))); + else userList.getItems().clear(); } /** @@ -89,7 +97,7 @@ public class ContactSearchScene { @FXML private void clear() { searchBar.getTextField().setText(null); - chatList.getItems().clear(); + userList.getItems().clear(); } /** @@ -100,21 +108,21 @@ public class ContactSearchScene { */ @FXML private void chatListClicked() { - final var chat = chatList.getSelectionModel().getSelectedItem(); - if (chat != null) { + final var user = userList.getSelectionModel().getSelectedItem(); + if (user != null) { final var alert = new Alert(AlertType.CONFIRMATION); alert.setTitle("Add Contact to Contact List"); - alert.setHeaderText("Add the user " + chat.getRecipient().getName() + " to your contact list?"); + alert.setHeaderText("Add the user " + user.getName() + " to your contact list?"); // 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(chat.getRecipient(), ElementOperation.ADD); + final var event = new ContactOperation(user, 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 " + chat.getRecipient()); + 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 e7bbf93..3bf4265 100644 --- a/client/src/main/java/envoy/client/ui/controller/GroupCreationScene.java +++ b/client/src/main/java/envoy/client/ui/controller/GroupCreationScene.java @@ -1,5 +1,7 @@ package envoy.client.ui.controller; +import static java.util.function.Predicate.not; + import java.util.stream.Collectors; import javafx.application.Platform; @@ -13,14 +15,22 @@ import envoy.client.event.SendEvent; import envoy.client.ui.ClearableTextField; import envoy.client.ui.SceneContext; import envoy.client.ui.listcell.ContactListCellFactory; -import envoy.data.Group; +import envoy.data.User; import envoy.event.EventBus; import envoy.event.GroupCreation; import envoy.util.Bounds; /** + * Provides a group creation interface. A group name can be entered in the text + * field at the top. Available users (local chat recipients) are displayed + * inside a list and can be selected (multiple selection available). + *

+ * When the group creation button is pressed, a {@link GroupCreation} is sent to + * the server. This controller enforces a valid group name and a non-empty + * member list (excluding the client user). + *

* Project: envoy-client
- * File: ContactSearchSceneController.java
+ * File: GroupCreationScene.java
* Created: 07.06.2020
* * @author Maximilian Käfer @@ -35,7 +45,7 @@ public class GroupCreationScene { private ClearableTextField groupNameField; @FXML - private ListView chatList; + private ListView userList; private SceneContext sceneContext; @@ -43,8 +53,8 @@ public class GroupCreationScene { @FXML private void initialize() { - chatList.setCellFactory(ContactListCellFactory::new); - chatList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + userList.setCellFactory(new ContactListCellFactory<>()); + userList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); groupNameField.setClearButtonListener(e -> { groupNameField.getTextField().clear(); createButton.setDisable(true); }); } @@ -56,11 +66,13 @@ public class GroupCreationScene { */ public void initializeData(SceneContext sceneContext, LocalDB localDB) { this.sceneContext = sceneContext; - Platform.runLater(() -> chatList.getItems() + Platform.runLater(() -> userList.getItems() .addAll(localDB.getChats() .stream() - .filter(c -> !(c.getRecipient() instanceof Group)) - .filter(c -> c.getRecipient().getID() != localDB.getUser().getID()) + .map(Chat::getRecipient) + .filter(User.class::isInstance) + .filter(not(localDB.getUser()::equals)) + .map(User.class::cast) .collect(Collectors.toList()))); } @@ -71,12 +83,12 @@ public class GroupCreationScene { */ @FXML private void chatListClicked() { - createButton.setDisable(chatList.getSelectionModel().isEmpty() || groupNameField.getTextField().getText().isBlank()); + createButton.setDisable(userList.getSelectionModel().isEmpty() || groupNameField.getTextField().getText().isBlank()); } /** * Checks, whether the {@code createButton} can be enabled because text is - * present in the textfield. + * present in the text field. * * @since Envoy Client v0.1-beta */ @@ -97,8 +109,8 @@ public class GroupCreationScene { new Alert(AlertType.ERROR, "The entered group name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait(); groupNameField.getTextField().clear(); } else { - eventBus.dispatch(new SendEvent(new GroupCreation(name, - chatList.getSelectionModel().getSelectedItems().stream().map(c -> c.getRecipient().getID()).collect(Collectors.toSet())))); + 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(); } diff --git a/client/src/main/java/envoy/client/ui/listcell/ChatListCellFactory.java b/client/src/main/java/envoy/client/ui/listcell/ChatListCellFactory.java new file mode 100644 index 0000000..01b09ae --- /dev/null +++ b/client/src/main/java/envoy/client/ui/listcell/ChatListCellFactory.java @@ -0,0 +1,44 @@ +package envoy.client.ui.listcell; + +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; + +import envoy.client.data.Chat; + +/** + * Project: envoy-client
+ * File: ChatListCellFactory.java
+ * Created: 28.03.2020
+ * + * @author Kai S. K. Engelbart + * @since Envoy Client v0.1-beta + */ +public class ChatListCellFactory extends ListCell { + + private final ListView listView; + + /** + * @param listView the list view inside which this cell is contained + * @since Envoy Client v0.1-beta + */ + public ChatListCellFactory(ListView 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(Chat chat, boolean empty) { + super.updateItem(chat, empty); + if (empty || chat.getRecipient() == null) { + setText(null); + setGraphic(null); + } else { + final var control = new ChatControl(chat); + prefWidthProperty().bind(listView.widthProperty().subtract(40)); + setGraphic(control); + } + } +} diff --git a/client/src/main/java/envoy/client/ui/listcell/ContactControl.java b/client/src/main/java/envoy/client/ui/listcell/ContactControl.java index c573e69..c8f9107 100644 --- a/client/src/main/java/envoy/client/ui/listcell/ContactControl.java +++ b/client/src/main/java/envoy/client/ui/listcell/ContactControl.java @@ -28,7 +28,6 @@ public class ContactControl extends VBox { // Name label final var nameLabel = new Label(contact.getName()); - nameLabel.setWrapText(true); getChildren().add(nameLabel); // Online status (user) or member count (group) diff --git a/client/src/main/java/envoy/client/ui/listcell/ContactListCellFactory.java b/client/src/main/java/envoy/client/ui/listcell/ContactListCellFactory.java index 3d34aa9..5e00c1a 100644 --- a/client/src/main/java/envoy/client/ui/listcell/ContactListCellFactory.java +++ b/client/src/main/java/envoy/client/ui/listcell/ContactListCellFactory.java @@ -2,43 +2,51 @@ package envoy.client.ui.listcell; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; +import javafx.util.Callback; -import envoy.client.data.Chat; +import envoy.data.Contact; /** * Project: envoy-client
- * File: UserListCell.java
- * Created: 28.03.2020
+ * File: ContactListCellFactory.java
+ * Created: 13.07.2020
* * @author Kai S. K. Engelbart + * @param the type of contact to display * @since Envoy Client v0.1-beta */ -public class ContactListCellFactory extends ListCell { - - private final ListView listView; +public class ContactListCellFactory implements Callback, ListCell> { /** - * @param listView the list view inside which this cell is contained - * @since Envoy Client v0.1-beta - */ - public ContactListCellFactory(ListView listView) { this.listView = listView; } - - /** - * Displays the name of a contact. If the contact is a user, their online status - * is displayed as well. + * Wraps a the {@link ContactControl} inside a list cell. * + * @param the type of contact to display * @since Envoy Client v0.1-beta */ - @Override - protected void updateItem(Chat chat, boolean empty) { - super.updateItem(chat, empty); - if (empty || chat.getRecipient() == null) { - setText(null); - setGraphic(null); - } else { - final var control = new ChatControl(chat); - prefWidthProperty().bind(listView.widthProperty().subtract(40)); - setGraphic(control); + public static final class ContactListCell extends ListCell { + + private final ListView listView; + + /** + * @param listView the list view containing this list cell + * @since Envoy Client v0.1-beta + */ + public ContactListCell(ListView listView) { this.listView = listView; } + + @Override + protected void updateItem(T 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); + } } } + + @Override + public ListCell call(ListView listView) { return new ContactListCell<>(listView); } } diff --git a/client/src/main/resources/fxml/ContactSearchScene.fxml b/client/src/main/resources/fxml/ContactSearchScene.fxml index a88430c..a77dfd6 100644 --- a/client/src/main/resources/fxml/ContactSearchScene.fxml +++ b/client/src/main/resources/fxml/ContactSearchScene.fxml @@ -8,19 +8,12 @@ - + - - + + @@ -29,14 +22,10 @@ - + - - + @@ -56,8 +43,7 @@ - diff --git a/client/src/main/resources/fxml/GroupCreationScene.fxml b/client/src/main/resources/fxml/GroupCreationScene.fxml index 0085b03..897955b 100644 --- a/client/src/main/resources/fxml/GroupCreationScene.fxml +++ b/client/src/main/resources/fxml/GroupCreationScene.fxml @@ -11,18 +11,12 @@ - + - + @@ -30,9 +24,7 @@ - + @@ -45,9 +37,7 @@ - + @@ -57,16 +47,12 @@ - -