diff --git a/client/.classpath b/client/.classpath index 234db15..99d4cc0 100644 --- a/client/.classpath +++ b/client/.classpath @@ -13,13 +13,14 @@ + - + diff --git a/client/.settings/org.eclipse.jdt.core.prefs b/client/.settings/org.eclipse.jdt.core.prefs index 65c71af..acfcecc 100644 --- a/client/.settings/org.eclipse.jdt.core.prefs +++ b/client/.settings/org.eclipse.jdt.core.prefs @@ -18,6 +18,7 @@ org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.doc.comment.support=enabled org.eclipse.jdt.core.compiler.problem.APILeak=warning +org.eclipse.jdt.core.compiler.problem.annotatedTypeArgumentToUnannotated=info org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.autoboxing=ignore diff --git a/client/src/main/java/envoy/client/ui/SceneContext.java b/client/src/main/java/envoy/client/ui/SceneContext.java index a027395..a7ab403 100644 --- a/client/src/main/java/envoy/client/ui/SceneContext.java +++ b/client/src/main/java/envoy/client/ui/SceneContext.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.Stack; import java.util.logging.Level; +import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; @@ -125,8 +126,12 @@ public final class SceneContext { sceneStack.push(scene); stage.setScene(scene); - applyCSS(); + // The LoginScene is the only scene not intended to be resized + // As strange as it seems, this is needed as otherwise the LoginScene won't be + // displayed on some OS (...Debian...) stage.sizeToScene(); + Platform.runLater(() -> stage.setResizable(sceneInfo != SceneInfo.LOGIN_SCENE)); + applyCSS(); stage.show(); } catch (final IOException e) { EnvoyLog.getLogger(SceneContext.class).log(Level.SEVERE, String.format("Could not load scene for %s: ", sceneInfo), e); 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 ba8346a..173784f 100644 --- a/client/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/client/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -10,17 +10,16 @@ import java.io.IOException; import java.nio.file.Files; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import java.util.Arrays; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; import javafx.animation.RotateTransition; import javafx.application.Platform; import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; import javafx.fxml.FXML; -import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; import javafx.scene.image.Image; @@ -29,6 +28,7 @@ import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.GridPane; import javafx.scene.paint.Color; +import javafx.scene.shape.Rectangle; import javafx.stage.FileChooser; import javafx.util.Duration; @@ -41,9 +41,8 @@ import envoy.client.event.SendEvent; import envoy.client.net.Client; import envoy.client.net.WriteProxy; import envoy.client.ui.*; -import envoy.client.ui.listcell.ChatControl; -import envoy.client.ui.listcell.ListCellFactory; -import envoy.client.ui.listcell.MessageControl; +import envoy.client.ui.listcell.*; +import envoy.client.util.ReflectionUtil; import envoy.data.*; import envoy.data.Attachment.AttachmentType; import envoy.event.*; @@ -103,6 +102,24 @@ public final class ChatScene implements Restorable { @FXML private ImageView attachmentView; + @FXML + private Label topBarContactLabel; + + @FXML + private Label topBarStatusLabel; + + @FXML + private Button messageSearchButton; + + @FXML + private ImageView clientProfilePic; + + @FXML + private ImageView recipientProfilePic; + + @FXML + private TextArea contactSearch; + private LocalDB localDB; private Client client; private WriteProxy writeProxy; @@ -124,6 +141,8 @@ public final class ChatScene implements Restorable { private static final int MAX_MESSAGE_LENGTH = 255; private static final int DEFAULT_ICON_SIZE = 16; + private FilteredList chats; + /** * Initializes the appearance of certain visual components. * @@ -131,9 +150,8 @@ public final class ChatScene implements Restorable { */ @FXML private void initialize() { - // Initialize message and user rendering - messageList.setCellFactory(new ListCellFactory<>(MessageControl::new)); + messageList.setCellFactory(MessageListCell::new); chatList.setCellFactory(new ListCellFactory<>(ChatControl::new)); settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE))); @@ -141,6 +159,14 @@ public final class ChatScene implements Restorable { attachmentButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE))); attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE); rotateButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("rotate", (int) (DEFAULT_ICON_SIZE * 1.5)))); + messageSearchButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("search", DEFAULT_ICON_SIZE))); + clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43)); + final Rectangle clip = new Rectangle(); + clip.setWidth(43); + clip.setHeight(43); + clip.setArcHeight(43); + clip.setArcWidth(43); + clientProfilePic.setClip(clip); // Listen to received messages eventBus.register(MessageCreationEvent.class, e -> { @@ -165,8 +191,9 @@ public final class ChatScene implements Restorable { // Move chat with most recent unread messages to the top Platform.runLater(() -> { - chatList.getItems().remove(chat); - chatList.getItems().add(0, chat); + chats.getSource().remove(chat); + ((ObservableList) chats.getSource()).add(0, chat); + if (chat.equals(currentChat)) chatList.getSelectionModel().select(0); }); }); @@ -189,7 +216,7 @@ public final class ChatScene implements Restorable { // Listen to user status changes eventBus.register(UserStatusChange.class, - e -> chatList.getItems() + e -> chats.getSource() .stream() .filter(c -> c.getRecipient().getID() == e.getID()) .findAny() @@ -203,10 +230,10 @@ public final class ChatScene implements Restorable { case ADD: if (contact instanceof User) localDB.getUsers().put(contact.getName(), (User) contact); final var chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact); - Platform.runLater(() -> chatList.getItems().add(chat)); + Platform.runLater(() -> ((ObservableList) chats.getSource()).add(0, chat)); break; case REMOVE: - Platform.runLater(() -> chatList.getItems().removeIf(c -> c.getRecipient().equals(contact))); + Platform.runLater(() -> chats.getSource().removeIf(c -> c.getRecipient().equals(contact))); break; } }); @@ -244,10 +271,12 @@ public final class ChatScene implements Restorable { this.client = client; this.writeProxy = writeProxy; - MessageControl.setUser(localDB.getUser()); - MessageControl.setSceneContext(sceneContext); - chatList.setItems(FXCollections.observableList(localDB.getChats())); + chats = new FilteredList<>(FXCollections.observableList(localDB.getChats())); + chatList.setItems(chats); contactLabel.setText(localDB.getUser().getName()); + MessageControl.setLocalDB(localDB); + MessageControl.setSceneContext(sceneContext); + if (!client.isOnline()) updateInfoLabel("You are offline", "infoLabel-info"); recorder = new AudioRecorder(); @@ -275,7 +304,7 @@ public final class ChatScene implements Restorable { currentChat = localDB.getChat(user.getID()).get(); messageList.setItems(FXCollections.observableList(currentChat.getMessages())); - final var scrollIndex = messageList.getItems().size() - currentChat.getUnreadAmount() - 1; + final var scrollIndex = messageList.getItems().size() - currentChat.getUnreadAmount(); messageList.scrollTo(scrollIndex); logger.log(Level.FINEST, "Loading chat with " + user + " at index " + scrollIndex); deleteContactMenuItem.setText("Delete " + user.getName()); @@ -303,6 +332,27 @@ public final class ChatScene implements Restorable { voiceButton.setDisable(!recorder.isSupported()); attachmentButton.setDisable(false); chatList.refresh(); + + if (currentChat != null) { + topBarContactLabel.setText(currentChat.getRecipient().getName()); + if (currentChat.getRecipient() instanceof User) { + final String status = ((User) currentChat.getRecipient()).getStatus().toString(); + topBarStatusLabel.setText(status); + topBarStatusLabel.getStyleClass().add(status.toLowerCase()); + recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43)); + } else { + topBarStatusLabel.setText(currentChat.getRecipient().getContacts().size() + " members"); + recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43)); + } + final Rectangle clip = new Rectangle(); + clip.setWidth(43); + clip.setHeight(43); + clip.setArcHeight(43); + clip.setArcWidth(43); + recipientProfilePic.setClip(clip); + + messageSearchButton.setVisible(true); + } } /** @@ -313,7 +363,7 @@ public final class ChatScene implements Restorable { @FXML private void settingsButtonClicked() { sceneContext.load(SceneContext.SceneInfo.SETTINGS_SCENE); - sceneContext.getController().initializeData(sceneContext, localDB.getUser()); + sceneContext.getController().initializeData(sceneContext, client); } /** @@ -430,16 +480,9 @@ public final class ChatScene implements Restorable { rotations = Math.max(rotations, 1); animationTime = Math.min(animationTime, 150); animationTime = Math.max(animationTime, 0.25); - + // contains all Node objects in ChatScene - final var rotatableNodes = Arrays.stream(ChatScene.class.getDeclaredFields()).map(field -> { - try { - return field.get(this); - } catch (IllegalArgumentException | IllegalAccessException e1) { - // In this case, this option can never be executed - return null; - } - }).filter(Node.class::isInstance).map(Node.class::cast).collect(Collectors.toList()); + final var rotatableNodes = ReflectionUtil.getAllDeclaredNodeVariables(this); for (final var node : rotatableNodes) { // Sets the animation duration to {animationTime} final var rotateTransition = new RotateTransition(Duration.seconds(animationTime), node); @@ -575,8 +618,8 @@ public final class ChatScene implements Restorable { currentChat.insert(message); // Moving currentChat to the top Platform.runLater(() -> { - chatList.getItems().remove(currentChat); - chatList.getItems().add(0, currentChat); + chats.getSource().remove(currentChat); + ((ObservableList) chats.getSource()).add(0, currentChat); chatList.getSelectionModel().select(0); localDB.getChats().remove(currentChat); localDB.getChats().add(0, currentChat); @@ -651,4 +694,10 @@ public final class ChatScene implements Restorable { if (attachmentView.getImage() != null) attachmentView.setVisible(true); pendingAttachment = messageAttachment; } + + @FXML + private void searchContacts() { + chats.setPredicate(contactSearch.getText().isBlank() ? c -> true + : c -> c.getRecipient().getName().toLowerCase().contains(contactSearch.getText().toLowerCase())); + } } diff --git a/client/src/main/java/envoy/client/ui/controller/LoginScene.java b/client/src/main/java/envoy/client/ui/controller/LoginScene.java index 2b03a5b..d3f1956 100644 --- a/client/src/main/java/envoy/client/ui/controller/LoginScene.java +++ b/client/src/main/java/envoy/client/ui/controller/LoginScene.java @@ -9,8 +9,10 @@ import java.util.logging.Logger; import javafx.application.Platform; import javafx.fxml.FXML; +import javafx.geometry.Insets; import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; +import javafx.scene.image.ImageView; import envoy.client.data.*; import envoy.client.net.Client; @@ -37,7 +39,7 @@ import envoy.util.EnvoyLog; public final class LoginScene { @FXML - private ClearableTextField userTextField; + private TextField userTextField; @FXML private PasswordField passwordField; @@ -46,19 +48,30 @@ public final class LoginScene { private PasswordField repeatPasswordField; @FXML - private Label repeatPasswordLabel; - - @FXML - private CheckBox registerCheckBox; + private Button registerSwitch; @FXML private Label connectionLabel; + @FXML + private Button loginButton; + + @FXML + private Button offlineModeButton; + + @FXML + private Label registerTextLabel; + + @FXML + private ImageView logo; + private Client client; private LocalDB localDB; private CacheMap cacheMap; private SceneContext sceneContext; + private boolean registration = false; + private static final Logger logger = EnvoyLog.getLogger(LoginScene.class); private static final EventBus eventBus = EventBus.getInstance(); private static final ClientConfig config = ClientConfig.getInstance(); @@ -70,6 +83,8 @@ public final class LoginScene { // Show an alert after an unsuccessful handshake eventBus.register(HandshakeRejection.class, e -> Platform.runLater(() -> { new Alert(AlertType.ERROR, e.get()).showAndWait(); })); + + logo.setImage(IconUtil.loadIcon("envoy_logo")); } /** @@ -102,28 +117,41 @@ public final class LoginScene { private void loginButtonPressed() { // Prevent registration with unequal passwords - if (registerCheckBox.isSelected() && !passwordField.getText().equals(repeatPasswordField.getText())) { + if (registration && !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.getTextField().getText())) { + } else if (!Bounds.isValidContactName(userTextField.getText())) { new Alert(AlertType.ERROR, "The entered user name is not valid (" + Bounds.CONTACT_NAME_PATTERN + ")").showAndWait(); - userTextField.getTextField().clear(); - } else performHandshake(new LoginCredentials(userTextField.getTextField().getText(), passwordField.getText(), registerCheckBox.isSelected(), - Startup.VERSION, loadLastSync(userTextField.getTextField().getText()))); + userTextField.clear(); + } else performHandshake(new LoginCredentials(userTextField.getText(), passwordField.getText(), registration, + Startup.VERSION, loadLastSync(userTextField.getText()))); } @FXML private void offlineModeButtonPressed() { - attemptOfflineMode( - new LoginCredentials(userTextField.getTextField().getText(), passwordField.getText(), false, Startup.VERSION, localDB.getLastSync())); + attemptOfflineMode(new LoginCredentials(userTextField.getText(), passwordField.getText(), false, Startup.VERSION, + loadLastSync(userTextField.getText()))); } @FXML - private void registerCheckboxChanged() { - + private void registerSwitchPressed() { + if (!registration) { + // case if the current mode is login + loginButton.setText("Register"); + loginButton.setPadding(new Insets(2, 116, 2, 116)); + registerTextLabel.setText("Already an account?"); + registerSwitch.setText("Login"); + } else { + // case if the current mode is registration + loginButton.setText("Login"); + loginButton.setPadding(new Insets(2, 125, 2, 125)); + registerTextLabel.setText("No account yet?"); + registerSwitch.setText("Register"); + } + registration = !registration; // Make repeat password field and label visible / invisible - repeatPasswordField.setVisible(registerCheckBox.isSelected()); - repeatPasswordLabel.setVisible(registerCheckBox.isSelected()); + repeatPasswordField.setVisible(registration); + offlineModeButton.setDisable(registration); } @FXML @@ -206,9 +234,10 @@ public final class LoginScene { // Load ChatScene sceneContext.pop(); sceneContext.getStage().setMinHeight(400); - sceneContext.getStage().setMinWidth(350); + sceneContext.getStage().setMinWidth(843); sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE); sceneContext.getController().initializeData(sceneContext, localDB, client, writeProxy); + sceneContext.getStage().centerOnScreen(); if (StatusTrayIcon.isSupported()) { diff --git a/client/src/main/java/envoy/client/ui/controller/SettingsScene.java b/client/src/main/java/envoy/client/ui/controller/SettingsScene.java index 7af8659..5656588 100644 --- a/client/src/main/java/envoy/client/ui/controller/SettingsScene.java +++ b/client/src/main/java/envoy/client/ui/controller/SettingsScene.java @@ -3,9 +3,9 @@ package envoy.client.ui.controller; import javafx.fxml.FXML; import javafx.scene.control.*; +import envoy.client.net.Client; import envoy.client.ui.SceneContext; import envoy.client.ui.settings.*; -import envoy.data.User; /** * Project: envoy-client
@@ -27,13 +27,14 @@ public class SettingsScene { /** * @param sceneContext enables the user to return to the chat scene - * @param client the user who uses Envoy + * @param client the {@code Client} used to get the current user and to + * check if this user is online * @since Envoy Client v0.1-beta */ - public void initializeData(SceneContext sceneContext, User client) { + public void initializeData(SceneContext sceneContext, Client client) { this.sceneContext = sceneContext; settingsList.getItems().add(new GeneralSettingsPane()); - settingsList.getItems().add(new UserSettingsPane(sceneContext, client)); + settingsList.getItems().add(new UserSettingsPane(sceneContext, client.getSender(), client.isOnline())); settingsList.getItems().add(new DownloadSettingsPane(sceneContext)); } diff --git a/client/src/main/java/envoy/client/ui/custom/ProfilePicImageView.java b/client/src/main/java/envoy/client/ui/custom/ProfilePicImageView.java new file mode 100644 index 0000000..4708974 --- /dev/null +++ b/client/src/main/java/envoy/client/ui/custom/ProfilePicImageView.java @@ -0,0 +1,61 @@ +package envoy.client.ui.custom; + +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.shape.Rectangle; + +/** + * Provides a set of convenience constructors for images that are displayed as profile pictures. + *

+ * Project: envoy-client
+ * File: ProfilePicImageView.java
+ * Created: 30.07.2020
+ * + * @author Leon Hofmeister + * @since Envoy Client v0.2-beta + */ +public final class ProfilePicImageView extends ImageView { + + /** + * Creates a new {@code ProfilePicImageView} without a default image. + * + * @since Envoy Client v0.2-beta + */ + public ProfilePicImageView() { this(null); } + + /** + * Creates a new {@code ProfilePicImageView}. + * + * @param image the image to display + * @since Envoy Client v0.2-beta + */ + public ProfilePicImageView(Image image) { this(image, 40); } + + /** + * Creates a new {@code ProfilePicImageView}. + * + * @param image the image to display + * @param sizeAndRounding the size and rounding for a circular + * {@code ProfilePicImageView} + * @since Envoy Client v0.2-beta + */ + public ProfilePicImageView(Image image, double sizeAndRounding) { this(image, sizeAndRounding, sizeAndRounding); } + + /** + * Creates a new {@code ProfilePicImageView}. + * + * @param image the image to display + * @param size the size of this {@code ProfilePicImageView} + * @param rounding how rounded this {@code ProfilePicImageView} should be + * @since Envoy Client v0.2-beta + */ + public ProfilePicImageView(Image image, double size, double rounding) { + super(image); + final var clip = new Rectangle(); + clip.setWidth(size); + clip.setHeight(size); + clip.setArcHeight(rounding); + clip.setArcWidth(rounding); + setClip(clip); + } +} diff --git a/client/src/main/java/envoy/client/ui/custom/package-info.java b/client/src/main/java/envoy/client/ui/custom/package-info.java new file mode 100644 index 0000000..97a8f58 --- /dev/null +++ b/client/src/main/java/envoy/client/ui/custom/package-info.java @@ -0,0 +1,14 @@ +/** + * This package stores custom components for use in JavaFX. + * These components are also expected to be used via FXML. + *

+ * Project: envoy-client
+ * File: package-info.java
+ * Created: 30.07.2020
+ * + * @author Leon Hofmeister + * @author Maximilian Käfer + * @author Kai S. K. Engelbart + * @since Envoy Client v0.2-beta + */ +package envoy.client.ui.custom; diff --git a/client/src/main/java/envoy/client/ui/listcell/AbstractListCell.java b/client/src/main/java/envoy/client/ui/listcell/AbstractListCell.java new file mode 100644 index 0000000..0bbe20b --- /dev/null +++ b/client/src/main/java/envoy/client/ui/listcell/AbstractListCell.java @@ -0,0 +1,50 @@ +package envoy.client.ui.listcell; + +import javafx.scene.Cursor; +import javafx.scene.Node; +import javafx.scene.control.ContentDisplay; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; + +/** + * Provides a convenience frame for list cell creation. + *

+ * Project: envoy-client
+ * File: AbstractListCell.java
+ * Created: 18.07.2020
+ * + * @author Kai S. K. Engelbart + * @param the type of element displayed by the list cell + * @param the type of node as which the list element will be displayed + * @since Envoy Client v0.1-beta + */ +public abstract class AbstractListCell extends ListCell { + + protected ListView listView; + + /** + * @param listView the list view inside of which the cell will be displayed + * @since Envoy Client v0.1-beta + */ + public AbstractListCell(ListView listView) { + this.listView = listView; + setContentDisplay(ContentDisplay.GRAPHIC_ONLY); + getStyleClass().add("listElement"); + } + + @Override + protected final void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + setGraphic(empty || item == null ? null : renderItem(item)); + if (!empty) setCursor(Cursor.HAND); + } + + /** + * Converts a list item to a node. This can have side effects on the list cell. + * + * @param item the item to render + * @return a node representing the item + * @since Envoy Client v0.1-beta + */ + protected abstract U renderItem(T item); +} diff --git a/client/src/main/java/envoy/client/ui/listcell/ChatControl.java b/client/src/main/java/envoy/client/ui/listcell/ChatControl.java index e19cd54..8e2a698 100644 --- a/client/src/main/java/envoy/client/ui/listcell/ChatControl.java +++ b/client/src/main/java/envoy/client/ui/listcell/ChatControl.java @@ -1,10 +1,15 @@ package envoy.client.ui.listcell; +import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Label; +import javafx.scene.image.ImageView; import javafx.scene.layout.*; +import javafx.scene.shape.Rectangle; import envoy.client.data.Chat; +import envoy.client.ui.IconUtil; +import envoy.data.Group; /** * Displays a chat using a contact control for the recipient and a label for the @@ -25,10 +30,27 @@ public class ChatControl extends HBox { * @since Envoy Client v0.1-beta */ public ChatControl(Chat chat) { - + setAlignment(Pos.CENTER_LEFT); + setPadding(new Insets(0, 0, 3, 0)); + // profile pic + ImageView contactProfilePic; + if (chat.getRecipient() instanceof Group) contactProfilePic = new ImageView(IconUtil.loadIconThemeSensitive("group_icon", 32)); + else contactProfilePic = new ImageView(IconUtil.loadIconThemeSensitive("user_icon", 32)); + Rectangle clip = new Rectangle(); + clip.setWidth(32); + clip.setHeight(32); + clip.setArcHeight(32); + clip.setArcWidth(32); + contactProfilePic.setClip(clip); + getChildren().add(contactProfilePic); + // spacing + Region leftSpacing = new Region(); + leftSpacing.setPrefSize(8, 0); + leftSpacing.setMinSize(8, 0); + leftSpacing.setMaxSize(8, 0); + getChildren().add(leftSpacing); // Contact control getChildren().add(new ContactControl(chat.getRecipient())); - // Unread messages if (chat.getUnreadAmount() != 0) { final var spacing = new Region(); @@ -43,5 +65,6 @@ public class ChatControl extends HBox { vBox2.getChildren().add(unreadMessagesLabel); getChildren().add(vBox2); } + getStyleClass().add("listElement"); } } 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 c8f9107..eb4a483 100644 --- a/client/src/main/java/envoy/client/ui/listcell/ContactControl.java +++ b/client/src/main/java/envoy/client/ui/listcell/ContactControl.java @@ -39,5 +39,6 @@ public class ContactControl extends VBox { } else { getChildren().add(new Label(contact.getContacts().size() + " members")); } + getStyleClass().add("listElement"); } } diff --git a/client/src/main/java/envoy/client/ui/listcell/GenericListCell.java b/client/src/main/java/envoy/client/ui/listcell/GenericListCell.java new file mode 100644 index 0000000..6b981a7 --- /dev/null +++ b/client/src/main/java/envoy/client/ui/listcell/GenericListCell.java @@ -0,0 +1,36 @@ +package envoy.client.ui.listcell; + +import java.util.function.Function; + +import javafx.scene.Node; +import javafx.scene.control.ListView; + +/** + * A generic list cell rendering an item using a provided render function. + *

+ * Project: envoy-client
+ * File: GenericListCell.java
+ * Created: 18.07.2020
+ * + * @author Kai S. K. Engelbart + * @param the type of element displayed by the list cell + * @param the type of node as which the list element will be displayed + * @since Envoy Client v0.2-beta + */ +public final class GenericListCell extends AbstractListCell { + + private final Function renderer; + + /** + * @param listView the list view inside of which the cell will be displayed + * @param renderer a function converting a list item to a node + * @since Envoy Client v0.1-beta + */ + public GenericListCell(ListView listView, Function renderer) { + super(listView); + this.renderer = renderer; + } + + @Override + protected U renderItem(T item) { return renderer.apply(item); } +} diff --git a/client/src/main/java/envoy/client/ui/listcell/ListCellFactory.java b/client/src/main/java/envoy/client/ui/listcell/ListCellFactory.java index 86270ce..018a0ba 100644 --- a/client/src/main/java/envoy/client/ui/listcell/ListCellFactory.java +++ b/client/src/main/java/envoy/client/ui/listcell/ListCellFactory.java @@ -17,38 +17,19 @@ import javafx.util.Callback; * * @author Kai S. K. Engelbart * @param the type of object to display + * @param the type of node displayed * @since Envoy Client v0.1-beta */ -public final class ListCellFactory implements Callback, ListCell> { +public final class ListCellFactory implements Callback, ListCell> { - private final class GenericListCell extends ListCell { - - private ListView listView; - - private GenericListCell(ListView listView) { this.listView = listView; } - - @Override - protected void updateItem(T item, boolean empty) { - super.updateItem(item, empty); - if (empty || item == null) { - setText(null); - setGraphic(null); - } else { - final var control = converter.apply(item); - prefWidthProperty().bind(listView.widthProperty().subtract(40)); - setGraphic(control); - } - } - } - - private final Function converter; + private final Function renderer; /** - * @param converter a function converting the type to display into a node + * @param renderer a function converting the type to display into a node * @since Envoy Client v0.1-beta */ - public ListCellFactory(Function converter) { this.converter = converter; } + public ListCellFactory(Function renderer) { this.renderer = renderer; } @Override - public ListCell call(ListView listView) { return new GenericListCell(listView); } + public ListCell call(ListView listView) { return new GenericListCell<>(listView, renderer); } } diff --git a/client/src/main/java/envoy/client/ui/listcell/MessageControl.java b/client/src/main/java/envoy/client/ui/listcell/MessageControl.java index eec8612..9b66c6c 100644 --- a/client/src/main/java/envoy/client/ui/listcell/MessageControl.java +++ b/client/src/main/java/envoy/client/ui/listcell/MessageControl.java @@ -10,18 +10,22 @@ import java.util.logging.Level; import java.util.logging.Logger; import javafx.geometry.Insets; +import javafx.geometry.Pos; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; import javafx.scene.control.MenuItem; import javafx.scene.image.Image; import javafx.scene.image.ImageView; -import javafx.scene.layout.VBox; +import javafx.scene.layout.*; import javafx.stage.FileChooser; +import envoy.client.data.LocalDB; import envoy.client.data.Settings; import envoy.client.ui.AudioControl; import envoy.client.ui.IconUtil; import envoy.client.ui.SceneContext; + +import envoy.data.GroupMessage; import envoy.data.Message; import envoy.data.Message.MessageStatus; import envoy.data.User; @@ -35,11 +39,14 @@ import envoy.util.EnvoyLog; * Created: 01.07.2020
* * @author Leon Hofmeister + * @author Maximilian Käfer * @since Envoy Client v0.1-beta */ public class MessageControl extends Label { - private static User client; + private boolean ownMessage; + + private static LocalDB localDB; private static SceneContext sceneContext; @@ -56,15 +63,31 @@ public class MessageControl extends Label { */ public MessageControl(Message message) { // Creating the underlying VBox and the dateLabel - final var vbox = new VBox(new Label(dateFormat.format(message.getCreationDate()))); + final var hbox = new HBox(); + if (message.getSenderID() != localDB.getUser().getID() && message instanceof GroupMessage) { + // Displaying the name of the sender in a group + final var label = new Label(); + label.getStyleClass().add("groupMemberNames"); + label.setText(localDB.getUsers() + .values() + .stream() + .filter(c -> c.getID() == message.getSenderID()) + .findFirst() + .map(User::getName) + .orElse("Unknown User")); + label.setPadding(new Insets(0, 5, 0, 0)); + hbox.getChildren().add(label); + } + hbox.getChildren().add(new Label(dateFormat.format(message.getCreationDate()))); + final var vbox = new VBox(hbox); // Creating the actions for the MenuItems - final ContextMenu contextMenu = new ContextMenu(); - final MenuItem copyMenuItem = new MenuItem("Copy"); - final MenuItem deleteMenuItem = new MenuItem("Delete"); - final MenuItem forwardMenuItem = new MenuItem("Forward"); - final MenuItem quoteMenuItem = new MenuItem("Quote"); - final MenuItem infoMenuItem = new MenuItem("Info"); + final var contextMenu = new ContextMenu(); + final var copyMenuItem = new MenuItem("Copy"); + final var deleteMenuItem = new MenuItem("Delete"); + final var forwardMenuItem = new MenuItem("Forward"); + final var quoteMenuItem = new MenuItem("Quote"); + final var infoMenuItem = new MenuItem("Info"); copyMenuItem.setOnAction(e -> copyMessage(message)); deleteMenuItem.setOnAction(e -> deleteMessage(message)); forwardMenuItem.setOnAction(e -> forwardMessage(message)); @@ -93,15 +116,27 @@ public class MessageControl extends Label { } // Creating the textLabel final var textLabel = new Label(message.getText()); + textLabel.setMaxWidth(430); textLabel.setWrapText(true); - vbox.getChildren().add(textLabel); + final var hBoxBottom = new HBox(); + hBoxBottom.getChildren().add(textLabel); // Setting the message status icon and background color - if (message.getSenderID() == client.getID()) { + if (message.getSenderID() == localDB.getUser().getID()) { final var statusIcon = new ImageView(statusImages.get(message.getStatus())); statusIcon.setPreserveRatio(true); - vbox.getChildren().add(statusIcon); + final var space = new Region(); + HBox.setHgrow(space, Priority.ALWAYS); + hBoxBottom.getChildren().add(space); + hBoxBottom.getChildren().add(statusIcon); + hBoxBottom.setAlignment(Pos.BOTTOM_RIGHT); getStyleClass().add("own-message"); - } else getStyleClass().add("received-message"); + ownMessage = true; + hbox.setAlignment(Pos.CENTER_RIGHT); + } else { + getStyleClass().add("received-message"); + ownMessage = false; + } + vbox.getChildren().add(hBoxBottom); // Adjusting height and weight of the cell to the corresponding ListView paddingProperty().setValue(new Insets(5, 20, 5, 20)); setContextMenu(contextMenu); @@ -144,10 +179,17 @@ public class MessageControl extends Label { } /** - * @param client the user who has logged in + * @param localDB the localDB used by the current user + * @since Envoy Client v0.2-beta + */ + public static void setLocalDB(LocalDB localDB) { MessageControl.localDB = localDB; } + + /** + * @return whether the message stored by this {@code MessageControl} has been + * sent by this user of Envoy * @since Envoy Client v0.1-beta */ - public static void setUser(User client) { MessageControl.client = client; } + public boolean isOwnMessage() { return ownMessage; } /** * @param sceneContext the scene context storing the stage used in Envoy diff --git a/client/src/main/java/envoy/client/ui/listcell/MessageListCell.java b/client/src/main/java/envoy/client/ui/listcell/MessageListCell.java new file mode 100644 index 0000000..80cf215 --- /dev/null +++ b/client/src/main/java/envoy/client/ui/listcell/MessageListCell.java @@ -0,0 +1,41 @@ +package envoy.client.ui.listcell; + +import javafx.geometry.Insets; +import javafx.geometry.Pos; +import javafx.scene.control.ListView; + +import envoy.data.Message; + +/** + * A list cell containing messages represented as message controls. + *

+ * Project: envoy-client
+ * File: MessageListCell.java
+ * Created: 18.07.2020
+ * + * @author Kai S. K. Engelbart + * @since Envoy Client v0.1-beta + */ +public final class MessageListCell extends AbstractListCell { + + /** + * @param listView the list view inside of which the cell will be displayed + * @since Envoy Client v0.1-beta + */ + public MessageListCell(ListView listView) { super(listView); } + + @Override + protected MessageControl renderItem(Message message) { + final var control = new MessageControl(message); + listView.widthProperty().addListener((observable, oldValue, newValue) -> adjustPadding(newValue.intValue(), control.isOwnMessage())); + adjustPadding((int) listView.getWidth(), control.isOwnMessage()); + if (control.isOwnMessage()) setAlignment(Pos.CENTER_RIGHT); + else setAlignment(Pos.CENTER_LEFT); + return control; + } + + private void adjustPadding(int listWidth, boolean ownMessage) { + int padding = 10 + Math.max((listWidth - 1000) / 2, 0); + setPadding(ownMessage ? new Insets(0, padding, 6, 0) : new Insets(0, 0, 6, padding)); + } +} diff --git a/client/src/main/java/envoy/client/ui/settings/DownloadSettingsPane.java b/client/src/main/java/envoy/client/ui/settings/DownloadSettingsPane.java index 90e06f0..cce597e 100644 --- a/client/src/main/java/envoy/client/ui/settings/DownloadSettingsPane.java +++ b/client/src/main/java/envoy/client/ui/settings/DownloadSettingsPane.java @@ -1,9 +1,7 @@ package envoy.client.ui.settings; import javafx.geometry.Insets; -import javafx.scene.control.Button; -import javafx.scene.control.CheckBox; -import javafx.scene.control.Label; +import javafx.scene.control.*; import javafx.scene.layout.HBox; import javafx.stage.DirectoryChooser; @@ -30,18 +28,22 @@ public class DownloadSettingsPane extends SettingsPane { */ public DownloadSettingsPane(SceneContext sceneContext) { super("Download"); - vbox.setSpacing(15); - vbox.setPadding(new Insets(15)); + setSpacing(15); + setPadding(new Insets(15)); // checkbox to disable asking final var checkBox = new CheckBox(settings.getItems().get("autoSaveDownloads").getUserFriendlyName()); checkBox.setSelected(settings.isDownloadSavedWithoutAsking()); + checkBox.setTooltip(new Tooltip("Determines whether a \"Select save location\" - dialogue will be shown when saving attachments.")); checkBox.setOnAction(e -> settings.setDownloadSavedWithoutAsking(checkBox.isSelected())); - vbox.getChildren().add(checkBox); + getChildren().add(checkBox); // Displaying the default path to save to - vbox.getChildren().add(new Label(settings.getItems().get("downloadLocation").getDescription() + ":")); - final var hbox = new HBox(20); - final var currentPath = new Label(settings.getDownloadLocation().getAbsolutePath()); + final var pathLabel = new Label(settings.getItems().get("downloadLocation").getDescription() + ":"); + pathLabel.setWrapText(true); + getChildren().add(pathLabel); + final var hbox = new HBox(20); + Tooltip.install(hbox, new Tooltip("Determines the location where attachments will be saved to.")); + final var currentPath = new Label(settings.getDownloadLocation().getAbsolutePath()); hbox.getChildren().add(currentPath); // Setting the default path @@ -58,6 +60,6 @@ public class DownloadSettingsPane extends SettingsPane { } }); hbox.getChildren().add(button); - vbox.getChildren().add(hbox); + getChildren().add(hbox); } } diff --git a/client/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java b/client/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java index 20189f4..36755a9 100644 --- a/client/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java +++ b/client/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java @@ -1,8 +1,7 @@ package envoy.client.ui.settings; -import java.util.List; - import javafx.scene.control.ComboBox; +import javafx.scene.control.Tooltip; import envoy.client.data.SettingsItem; import envoy.client.event.ThemeChangeEvent; @@ -24,27 +23,36 @@ public class GeneralSettingsPane extends SettingsPane { */ public GeneralSettingsPane() { super("General"); + setSpacing(10); // TODO: Support other value types - List.of("hideOnClose", "enterToSend") - .stream() - .map(settings.getItems()::get) - .map(i -> new SettingsCheckbox((SettingsItem) i)) - .forEach(vbox.getChildren()::add); + final var settingsItems = settings.getItems(); + final var hideOnCloseCheckbox = new SettingsCheckbox((SettingsItem) settingsItems.get("hideOnClose")); + hideOnCloseCheckbox.setTooltip(new Tooltip("If selected, Envoy will still be present in the task bar when closed.")); + getChildren().add(hideOnCloseCheckbox); + + final var enterToSendCheckbox = new SettingsCheckbox((SettingsItem) settingsItems.get("enterToSend")); + final var enterToSendTooltip = new Tooltip( + "If selected, messages can be sent pressing \"Enter\". They can always be sent by pressing \"Ctrl\" + \"Enter\""); + enterToSendTooltip.setWrapText(true); + enterToSendCheckbox.setTooltip(enterToSendTooltip); + getChildren().add(enterToSendCheckbox); final var combobox = new ComboBox(); combobox.getItems().add("dark"); combobox.getItems().add("light"); + combobox.setTooltip(new Tooltip("Determines the current theme Envoy will be displayed in.")); combobox.setValue(settings.getCurrentTheme()); combobox.setOnAction( e -> { settings.setCurrentTheme(combobox.getValue()); EventBus.getInstance().dispatch(new ThemeChangeEvent(combobox.getValue())); }); - vbox.getChildren().add(combobox); + getChildren().add(combobox); final var statusComboBox = new ComboBox(); statusComboBox.getItems().setAll(UserStatus.values()); statusComboBox.setValue(UserStatus.ONLINE); + statusComboBox.setTooltip(new Tooltip("Change your current status")); // TODO add action when value is changed statusComboBox.setOnAction(e -> {}); - vbox.getChildren().add(statusComboBox); + getChildren().add(statusComboBox); } } diff --git a/client/src/main/java/envoy/client/ui/settings/SettingsPane.java b/client/src/main/java/envoy/client/ui/settings/SettingsPane.java index e7b884d..be63f26 100644 --- a/client/src/main/java/envoy/client/ui/settings/SettingsPane.java +++ b/client/src/main/java/envoy/client/ui/settings/SettingsPane.java @@ -1,6 +1,5 @@ package envoy.client.ui.settings; -import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import envoy.client.data.Settings; @@ -13,17 +12,13 @@ import envoy.client.data.Settings; * @author Kai S. K. Engelbart * @since Envoy Client v0.1-beta */ -public abstract class SettingsPane extends Pane { +public abstract class SettingsPane extends VBox { - protected String title; - protected final VBox vbox = new VBox(); + protected String title; protected static final Settings settings = Settings.getInstance(); - protected SettingsPane(String title) { - this.title = title; - getChildren().add(vbox); - } + protected SettingsPane(String title) { this.title = title; } /** * @return the title of this settings pane diff --git a/client/src/main/java/envoy/client/ui/settings/UserSettingsPane.java b/client/src/main/java/envoy/client/ui/settings/UserSettingsPane.java index cb33fd4..cad265b 100644 --- a/client/src/main/java/envoy/client/ui/settings/UserSettingsPane.java +++ b/client/src/main/java/envoy/client/ui/settings/UserSettingsPane.java @@ -4,8 +4,8 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.nio.file.Files; -import java.util.Arrays; import java.util.logging.Level; +import java.util.logging.Logger; import javafx.event.EventHandler; import javafx.geometry.Pos; @@ -21,6 +21,8 @@ import javafx.stage.FileChooser; import envoy.client.event.SendEvent; import envoy.client.ui.IconUtil; import envoy.client.ui.SceneContext; +import envoy.client.ui.custom.ProfilePicImageView; +import envoy.client.util.ReflectionUtil; import envoy.data.User; import envoy.event.*; import envoy.util.Bounds; @@ -37,30 +39,45 @@ import envoy.util.EnvoyLog; public class UserSettingsPane extends SettingsPane { private boolean profilePicChanged, usernameChanged, validPassword; - private byte[] currentImageBytes, originalImageBytes; + private byte[] currentImageBytes; private String newUsername, newPassword = ""; + private final ImageView profilePic = new ProfilePicImageView(null, 60); + private final TextField usernameTextField = new TextField(); + private final PasswordField currentPasswordField = new PasswordField(); + private final PasswordField newPasswordField = new PasswordField(); + private final PasswordField repeatNewPasswordField = new PasswordField(); + private final Button saveButton = new Button("Save"); + + private final Tooltip beOnlineReminder = new Tooltip("You need to be online to modify your acount."); + + private static final EventBus eventBus = EventBus.getInstance(); + private static final Logger logger = EnvoyLog.getLogger(UserSettingsPane.class); + /** * Creates a new {@code UserSettingsPane}. * * @param sceneContext the {@code SceneContext} to block input to Envoy * @param user the user who wants to customize his profile + * @param online whether this user is currently online * @since Envoy Client v0.2-beta */ - public UserSettingsPane(SceneContext sceneContext, User user) { + public UserSettingsPane(SceneContext sceneContext, User user, boolean online) { super("User"); + setSpacing(10); // Display of profile pic change mechanism final var hbox = new HBox(); // TODO: display current profile pic - final var profilePic = new ImageView(IconUtil.loadIcon("envoy_logo", 50)); + profilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon")); profilePic.setCursor(Cursor.HAND); - profilePic.setFitWidth(50); - profilePic.setFitHeight(50); + profilePic.setFitWidth(60); + profilePic.setFitHeight(60); profilePic.setOnMouseClicked(e -> { + if (!online) return; final var pictureChooser = new FileChooser(); - pictureChooser.setTitle("Select a new picture"); + pictureChooser.setTitle("Select a new profile pic"); pictureChooser.setInitialDirectory(new File(System.getProperty("user.home"))); pictureChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif")); @@ -88,30 +105,27 @@ public class UserSettingsPane extends SettingsPane { // Displaying the username change mechanism final var username = user.getName(); newUsername = username; - final var usernameTextField = new TextField(username); - final EventHandler textChanged = e -> { + usernameTextField.setText(username); + final EventHandler textChanged = e -> { newUsername = usernameTextField.getText(); usernameChanged = newUsername != username; }; usernameTextField.setOnInputMethodTextChanged(textChanged); usernameTextField.setOnKeyTyped(textChanged); hbox.getChildren().add(usernameTextField); - vbox.getChildren().add(hbox); + getChildren().add(hbox); // "Displaying" the password change mechanism final HBox[] passwordHBoxes = { new HBox(), new HBox(), new HBox() }; final Label[] passwordLabels = { new Label("Enter current password:"), new Label("Enter new password:"), new Label("Repeat new password:") }; - final var currentPasswordField = new PasswordField(); - final var newPasswordField = new PasswordField(); - final var repeatNewPasswordField = new PasswordField(); - final PasswordField[] passwordFields = { currentPasswordField, newPasswordField, repeatNewPasswordField }; - final EventHandler passwordEntered = e -> { - newPassword = newPasswordField.getText(); - validPassword = newPassword.equals(repeatNewPasswordField.getText()) - && !newPasswordField.getText().isBlank(); - }; + final PasswordField[] passwordFields = { currentPasswordField, newPasswordField, repeatNewPasswordField }; + final EventHandler passwordEntered = e -> { + newPassword = newPasswordField.getText(); + validPassword = newPassword.equals(repeatNewPasswordField.getText()) + && !newPasswordField.getText().isBlank(); + }; newPasswordField.setOnInputMethodTextChanged(passwordEntered); newPasswordField.setOnKeyTyped(passwordEntered); repeatNewPasswordField.setOnInputMethodTextChanged(passwordEntered); @@ -119,17 +133,27 @@ public class UserSettingsPane extends SettingsPane { for (int i = 0; i < passwordHBoxes.length; i++) { final var hBox2 = passwordHBoxes[i]; + passwordLabels[i].setWrapText(true); hBox2.getChildren().add(passwordLabels[i]); hBox2.getChildren().add(passwordFields[i]); - vbox.getChildren().add(hBox2); + getChildren().add(hBox2); } // Displaying the save button - final var saveButton = new Button("Save"); saveButton.setOnAction(e -> save(user.getID(), currentPasswordField.getText())); saveButton.setAlignment(Pos.BOTTOM_RIGHT); - vbox.getChildren().add(saveButton); + getChildren().add(saveButton); + final var offline = !online; + ReflectionUtil.getAllDeclaredNodeVariables(this).forEach(node -> node.setDisable(offline)); + if (offline) { + final var infoLabel = new Label("You shall not pass!\n(... Unless you would happen to be online)"); + infoLabel.setId("infoLabel-warning"); + infoLabel.setWrapText(true); + getChildren().add(infoLabel); + + Tooltip.install(this, beOnlineReminder); + } else Tooltip.uninstall(this, beOnlineReminder); } /** @@ -139,11 +163,9 @@ public class UserSettingsPane extends SettingsPane { * @since Envoy Client v0.2-beta */ private void save(long userID, String oldPassword) { - final var eventBus = EventBus.getInstance(); - final var logger = EnvoyLog.getLogger(UserSettingsPane.class); // The profile pic was changed - if (profilePicChanged && !Arrays.equals(currentImageBytes, originalImageBytes)) { + if (profilePicChanged) { final var profilePicChangeEvent = new ProfilePicChange(currentImageBytes, userID); eventBus.dispatch(profilePicChangeEvent); eventBus.dispatch(new SendEvent(profilePicChangeEvent)); diff --git a/client/src/main/java/envoy/client/util/ReflectionUtil.java b/client/src/main/java/envoy/client/util/ReflectionUtil.java new file mode 100644 index 0000000..132f5da --- /dev/null +++ b/client/src/main/java/envoy/client/util/ReflectionUtil.java @@ -0,0 +1,88 @@ +package envoy.client.util; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javafx.scene.Node; + +/** + * Project: envoy-client
+ * File: ReflectionUtil.java
+ * Created: 02.08.2020
+ * + * @author Leon Hofmeister + * @since Envoy Client v0.2-beta + */ +public class ReflectionUtil { + + private ReflectionUtil() {} + + /** + * Gets all declared variables of the given instance that have the specified + * class
+ * (i.e. can get all {@code JComponents} (Swing) or {@code Nodes} (JavaFX) in a + * GUI class). + *

+ * Important: If you are using a module, you first need to declare
+ * "opens {your_package} to envoy.client.util;" in your module-info.java
. + * + * @param the type of the object + * @param the type to return + * @param instance the instance of a given class whose values are to be + * evaluated + * @param typeToReturn the type of variable to return + * @return all variables in the given instance that have the requested type + * @throws RuntimeException if an exception occurs + * @since Envoy Client v0.2-beta + */ + public static Stream getAllDeclaredVariablesOfTypeAsStream(T instance, Class typeToReturn) { + return Arrays.stream(instance.getClass().getDeclaredFields()).filter(field -> typeToReturn.isAssignableFrom(field.getType())).map(field -> { + try { + field.setAccessible(true); + final var value = field.get(instance); + field.setAccessible(false); + return value; + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); + } + }).map(typeToReturn::cast);// field -> + // typeToReturn.isAssignableFrom(field.getClass())).map(typeToReturn::cast); + } + + /** + * Gets all declared variables of the given instance that are children of + * {@code Node}. + *

+ * Important: If you are using a module, you first need to declare
+ * "opens {your_package} to envoy.client.util;" in your module-info.java
. + * + * @param the type of the instance + * @param instance the instance of a given class whose values are to be + * evaluated + * @return all variables of the given object that have the requested type as + * {@code Stream} + * @since Envoy Client v0.2-beta + */ + public static Stream getAllDeclaredNodeVariablesAsStream(T instance) { + return getAllDeclaredVariablesOfTypeAsStream(instance, Node.class); + } + + /** + * Gets all declared variables of the given instance that are children of + * {@code Node}
+ *

+ * Important: If you are using a module, you first need to declare
+ * "opens {your_package} to envoy.client.util;" in your module-info.java
. + * + * @param the type of the instance + * @param instance the instance of a given class whose values are to be + * evaluated + * @return all variables of the given object that have the requested type + * @since Envoy Client v0.2-beta + */ + public static List getAllDeclaredNodeVariables(T instance) { + return getAllDeclaredNodeVariablesAsStream(instance).collect(Collectors.toList()); + } +} diff --git a/client/src/main/java/envoy/client/util/package-info.java b/client/src/main/java/envoy/client/util/package-info.java new file mode 100644 index 0000000..c6b342e --- /dev/null +++ b/client/src/main/java/envoy/client/util/package-info.java @@ -0,0 +1,11 @@ +/** + * This package contains utility classes for use in envoy-client. + *

+ * Project: envoy-client
+ * File: package-info.java
+ * Created: 02.08.2020
+ * + * @author Leon Hofmeister + * @since Envoy Client v0.2-beta + */ +package envoy.client.util; diff --git a/client/src/main/java/module-info.java b/client/src/main/java/module-info.java index 1f0d0b1..778f673 100644 --- a/client/src/main/java/module-info.java +++ b/client/src/main/java/module-info.java @@ -19,5 +19,7 @@ module envoy { requires javafx.graphics; opens envoy.client.ui to javafx.graphics, javafx.fxml; - opens envoy.client.ui.controller to javafx.graphics, javafx.fxml; + opens envoy.client.ui.controller to javafx.graphics, javafx.fxml, envoy.client.util; + opens envoy.client.ui.custom to javafx.graphics, javafx.fxml; + opens envoy.client.ui.settings to envoy.client.util; } diff --git a/client/src/main/resources/css/base.css b/client/src/main/resources/css/base.css index 78fff9e..4c289bb 100644 --- a/client/src/main/resources/css/base.css +++ b/client/src/main/resources/css/base.css @@ -1,5 +1,5 @@ .button, .list-cell, .progress-bar * { - -fx-background-radius: 5.0em; + -fx-background-radius: 0.3em; } .context-menu, .context-menu > * { @@ -8,6 +8,28 @@ -fx-background-color: transparent; } +#textEnterContainer, #contact-search-enter-container { + -fx-background-radius: 5.0em; +} + +#roundButton { + -fx-background-radius: 5.0em; +} + +.text-area { + -fx-background-color: transparent; +} + +.text-area .scroll-pane { + -fx-background-color: transparent; +} +.text-area .scroll-pane .viewport{ + -fx-background-color: transparent; +} +.text-area .scroll-pane .content{ + -fx-background-color: transparent; +} + .menu-item { -fx-background-radius: 15.0px; } @@ -48,13 +70,13 @@ .received-message { -fx-alignment: center-left; - -fx-background-radius: 4.0em; + -fx-background-radius: 1.3em; -fx-text-alignment: right; } .own-message { -fx-alignment: center-right; - -fx-background-radius: 4.0em; + -fx-background-radius: 1.3em; -fx-text-alignment: left; } @@ -65,6 +87,21 @@ -fx-text-alignment: center; } +#loginButton { + -fx-background-radius: 1.0em; +} + +#registerSwitch { + -fx-background-color: transparent; + -fx-text-fill: orange; +} + +#loginInputField { + -fx-background-color: transparent; + -fx-border: solid; + -fx-border-width: 0.0 0.0 1.0 0.0; +} + #remainingCharsLabel { -fx-text-fill: #00FF00; -fx-background-color: transparent; @@ -85,3 +122,15 @@ #infoLabel-error { -fx-text-fill: red; } + +#transparentBackground { + -fx-background-color: transparent; +} + +#profilePic { + -fx-radius: 1.0em; +} + +.listElement { + -fx-background-color: transparent; +} diff --git a/client/src/main/resources/css/dark.css b/client/src/main/resources/css/dark.css index eaad3a9..ca98909 100644 --- a/client/src/main/resources/css/dark.css +++ b/client/src/main/resources/css/dark.css @@ -18,8 +18,8 @@ -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, .context-menu, .menu-item { - -fx-background-color: dimgray; +#messageList, .text-field, .password-field, .tooltip, .pane, .pane .content, .vbox, .titled-pane > .title, .titled-pane > *.content, .context-menu, .menu-item { + -fx-background-color: #222222; } .list-cell:selected, .list-cell:selected > *, .menu-item:hover { @@ -37,3 +37,48 @@ .alert.information.dialog-pane, .alert.warning.dialog-pane, .alert.error.dialog-pane { -fx-background-color: black; } + +#loginInputField { + -fx-border-color: white; +} + +#loginBackground { + -fx-background-color: rgb(25, 25, 25); +} + +#chatList, #topBar, #search-panel { + -fx-background-color: #303030; +} + +#textEnterContainer { + -fx-background-color: #363636; +} + +#contact-search-enter-container { + -fx-background-color: #202020; +} + +#underline { + -fx-border: solid; + -fx-border-width: 0.0 0.0 1.0 0.0; + -fx-border-color: #202020; +} + +.groupMemberNames { + -fx-text-fill: rgb(105.0,0.0,153.0); + -fx-font-weight: bold; +} + +.scroll-bar:vertical, .scroll-bar:vertical .track, .scroll-bar:vertical .increment-button , .scroll-bar:vertical .decrement-button { + -fx-background-color: transparent; +} + +.scroll-bar:vertical .increment-arrow, .scroll-bar:vertical .decrement-arrow { + -fx-background-color: transparent; +} + +.scroll-bar:vertical .thumb { + -fx-background-color: #707070; + -fx-background-insets : 4.0, 0.0, 0.0; + -fx-background-radius : 2.0em; +} diff --git a/client/src/main/resources/css/light.css b/client/src/main/resources/css/light.css index 066de55..8486dcb 100644 --- a/client/src/main/resources/css/light.css +++ b/client/src/main/resources/css/light.css @@ -14,3 +14,7 @@ .own-message { -fx-background-color: lightgreen; } + +#loginInputField { + -fx-border-color: black; +} diff --git a/client/src/main/resources/fxml/ChatScene.fxml b/client/src/main/resources/fxml/ChatScene.fxml index 11c4c3e..5bf8cd1 100644 --- a/client/src/main/resources/fxml/ChatScene.fxml +++ b/client/src/main/resources/fxml/ChatScene.fxml @@ -3,7 +3,6 @@ - @@ -13,192 +12,217 @@ + + + + - + - - - + + - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -206,5 +230,33 @@ + + + + + + + + + + + + + + + + + + + diff --git a/client/src/main/resources/fxml/LoginScene.fxml b/client/src/main/resources/fxml/LoginScene.fxml index 1e915a2..9a6d7d9 100644 --- a/client/src/main/resources/fxml/LoginScene.fxml +++ b/client/src/main/resources/fxml/LoginScene.fxml @@ -1,94 +1,76 @@ - - - + + + + - + -

- -
- - - - - - - diff --git a/client/src/main/resources/icons/dark/group_icon.png b/client/src/main/resources/icons/dark/group_icon.png new file mode 100644 index 0000000..5b1660e Binary files /dev/null and b/client/src/main/resources/icons/dark/group_icon.png differ diff --git a/client/src/main/resources/icons/dark/user_icon.png b/client/src/main/resources/icons/dark/user_icon.png new file mode 100644 index 0000000..6d7b8b0 Binary files /dev/null and b/client/src/main/resources/icons/dark/user_icon.png differ diff --git a/client/src/main/resources/icons/light/group_icon.png b/client/src/main/resources/icons/light/group_icon.png new file mode 100644 index 0000000..5b1660e Binary files /dev/null and b/client/src/main/resources/icons/light/group_icon.png differ diff --git a/client/src/main/resources/icons/light/user_icon.png b/client/src/main/resources/icons/light/user_icon.png new file mode 100644 index 0000000..6d7b8b0 Binary files /dev/null and b/client/src/main/resources/icons/light/user_icon.png differ diff --git a/common/src/main/java/envoy/event/PasswordChangeRequest.java b/common/src/main/java/envoy/event/PasswordChangeRequest.java index 3029df8..dd088df 100644 --- a/common/src/main/java/envoy/event/PasswordChangeRequest.java +++ b/common/src/main/java/envoy/event/PasswordChangeRequest.java @@ -12,9 +12,8 @@ import envoy.data.Contact; */ public class PasswordChangeRequest extends Event { - private final long id; - - private final String oldPassword; + private final long id; + private final String oldPassword; private static final long serialVersionUID = 0L; diff --git a/common/src/main/java/envoy/event/ProfilePicChange.java b/common/src/main/java/envoy/event/ProfilePicChange.java index 52c5941..c06c545 100644 --- a/common/src/main/java/envoy/event/ProfilePicChange.java +++ b/common/src/main/java/envoy/event/ProfilePicChange.java @@ -25,9 +25,8 @@ public class ProfilePicChange extends Event { } /** - * @return the id + * @return the ID of the user changing his profile pic * @since Envoy Common v0.2-beta */ public long getId() { return id; } - } diff --git a/common/src/main/java/envoy/util/Bounds.java b/common/src/main/java/envoy/util/Bounds.java index 315e047..7540b88 100644 --- a/common/src/main/java/envoy/util/Bounds.java +++ b/common/src/main/java/envoy/util/Bounds.java @@ -23,6 +23,8 @@ public class Bounds { */ public static final Pattern CONTACT_NAME_PATTERN = Pattern.compile("^\\w[a-zA-Z0-9-]{2,15}$"); + // KAI: Trust of Chain - das berühmte Konzept aus der Kryptographie + /** * @param contactName the contact name to validate * @return {@code true} if the given contact name is valid