diff --git a/client/src/main/java/envoy/client/data/LocalDB.java b/client/src/main/java/envoy/client/data/LocalDB.java index 7ab7a98..13db9df 100644 --- a/client/src/main/java/envoy/client/data/LocalDB.java +++ b/client/src/main/java/envoy/client/data/LocalDB.java @@ -293,6 +293,9 @@ public final class LocalDB implements EventListener { }); } + @Event(priority = 500) + private void onOwnStatusChange(OwnStatusChange statusChange) { user.setStatus(statusChange.get()); } + /** * @return a {@code Map} of all users stored locally with their * user names as keys diff --git a/client/src/main/java/envoy/client/event/OwnStatusChange.java b/client/src/main/java/envoy/client/event/OwnStatusChange.java new file mode 100644 index 0000000..8962d6f --- /dev/null +++ b/client/src/main/java/envoy/client/event/OwnStatusChange.java @@ -0,0 +1,21 @@ +package envoy.client.event; + +import envoy.data.User.UserStatus; +import envoy.event.Event; + +/** + * Signifies a manual status change of the client user. + * + * @author Leon Hofmeister + * @since Envoy Client v0.3-beta + */ +public class OwnStatusChange extends Event { + + private static final long serialVersionUID = 1L; + + /** + * @param value the new user status of the client user + * @since Envoy Client v0.3-beta + */ + public OwnStatusChange(UserStatus value) { super(value); } +} diff --git a/client/src/main/java/envoy/client/helper/AlertHelper.java b/client/src/main/java/envoy/client/helper/AlertHelper.java index 4c6e880..6b053f7 100644 --- a/client/src/main/java/envoy/client/helper/AlertHelper.java +++ b/client/src/main/java/envoy/client/helper/AlertHelper.java @@ -27,8 +27,6 @@ public final class AlertHelper { * @since Envoy Client v0.2-beta */ public static void confirmAction(Alert alert, Runnable action) { - alert.setHeight(225); - alert.setWidth(400); alert.setHeaderText(""); if (Settings.getInstance().isAskForConfirmation()) alert.showAndWait().filter(ButtonType.OK::equals).ifPresent(bu -> action.run()); else action.run(); diff --git a/client/src/main/java/envoy/client/helper/ShutdownHelper.java b/client/src/main/java/envoy/client/helper/ShutdownHelper.java index 18164b8..0371f50 100644 --- a/client/src/main/java/envoy/client/helper/ShutdownHelper.java +++ b/client/src/main/java/envoy/client/helper/ShutdownHelper.java @@ -1,15 +1,8 @@ package envoy.client.helper; -import java.util.logging.Level; - -import javafx.scene.control.Alert; -import javafx.scene.control.Alert.AlertType; - import envoy.client.data.*; -import envoy.client.event.*; -import envoy.client.ui.SceneContext.SceneInfo; +import envoy.client.event.EnvoyCloseEvent; import envoy.client.ui.StatusTrayIcon; -import envoy.util.EnvoyLog; import dev.kske.eventbus.EventBus; @@ -29,30 +22,21 @@ public final class ShutdownHelper { * * @since Envoy Client v0.2-beta */ - public static void exit() { - if (Settings.getInstance().isHideOnClose() && StatusTrayIcon.isSupported()) Context.getInstance().getStage().setIconified(true); + public static void exit() { exit(false); } + + /** + * Exits Envoy immediately if {@code force = true}, + * else it can exit or minimize Envoy, depending on the current state of + * {@link Settings#isHideOnClose()} and {@link StatusTrayIcon#isSupported()}. + * + * @param force whether to close in any case. + * @since Envoy Client v0.2-beta + */ + public static void exit(boolean force) { + if (!force && Settings.getInstance().isHideOnClose() && StatusTrayIcon.isSupported()) Context.getInstance().getStage().setIconified(true); else { EventBus.getInstance().dispatch(new EnvoyCloseEvent()); System.exit(0); } } - - /** - * Logs the current user out and reopens - * {@link envoy.client.ui.controller.LoginScene}. - * - * @since Envoy Client v0.2-beta - */ - public static void logout() { - final var alert = new Alert(AlertType.CONFIRMATION); - alert.setTitle("Logout?"); - alert.setContentText("Are you sure you want to log out?"); - - AlertHelper.confirmAction(alert, () -> { - EnvoyLog.getLogger(ShutdownHelper.class).log(Level.INFO, "A logout was requested"); - EventBus.getInstance().dispatch(new EnvoyCloseEvent()); - EventBus.getInstance().dispatch(new Logout()); - Context.getInstance().getSceneContext().load(SceneInfo.LOGIN_SCENE); - }); - } } diff --git a/client/src/main/java/envoy/client/ui/SceneContext.java b/client/src/main/java/envoy/client/ui/SceneContext.java index e458407..970c737 100644 --- a/client/src/main/java/envoy/client/ui/SceneContext.java +++ b/client/src/main/java/envoy/client/ui/SceneContext.java @@ -13,6 +13,8 @@ import javafx.stage.Stage; import envoy.client.data.Settings; import envoy.client.event.*; import envoy.client.helper.ShutdownHelper; +import envoy.client.util.UserUtil; +import envoy.data.User.UserStatus; import envoy.util.EnvoyLog; import dev.kske.eventbus.*; @@ -126,9 +128,22 @@ public final class SceneContext implements EventListener { // Add the option to exit Linux-like with "Control" + "Q" or "Alt" + "F4" accelerators.put(new KeyCodeCombination(KeyCode.Q, KeyCombination.CONTROL_DOWN), ShutdownHelper::exit); - // Add the option to logout using "Control"+"Shift"+"L" if not in login scene - if (sceneInfo != SceneInfo.LOGIN_SCENE) - accelerators.put(new KeyCodeCombination(KeyCode.L, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN), ShutdownHelper::logout); + if (sceneInfo != SceneInfo.LOGIN_SCENE) { + + // Add the option to logout using "Control"+"Shift"+"L" + accelerators.put(new KeyCodeCombination(KeyCode.L, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN), UserUtil::logout); + + // Add the option to change status using "Control" + "Shift" + + // (o)F(fline)/ A(way)/ B(usy)/(o)N(line) + accelerators.put(new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN), + () -> UserUtil.changeStatus(UserStatus.OFFLINE)); + accelerators.put(new KeyCodeCombination(KeyCode.A, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN), + () -> UserUtil.changeStatus(UserStatus.AWAY)); + accelerators.put(new KeyCodeCombination(KeyCode.B, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN), + () -> UserUtil.changeStatus(UserStatus.BUSY)); + accelerators.put(new KeyCodeCombination(KeyCode.N, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN), + () -> UserUtil.changeStatus(UserStatus.ONLINE)); + } // Add the option to open the settings scene with "Control"+"S", if being in // chat scene diff --git a/client/src/main/java/envoy/client/ui/Startup.java b/client/src/main/java/envoy/client/ui/Startup.java index 811ba61..96af931 100644 --- a/client/src/main/java/envoy/client/ui/Startup.java +++ b/client/src/main/java/envoy/client/ui/Startup.java @@ -113,9 +113,13 @@ public final class Startup extends Application { cacheMap.put(GroupMessage.class, new Cache()); cacheMap.put(MessageStatusChange.class, new Cache()); cacheMap.put(GroupMessageStatusChange.class, new Cache()); + final var originalStatus = localDB.getUser().getStatus(); try { client.performHandshake(credentials, cacheMap); if (client.isOnline()) { + + // Restore the original status as the server automatically returns status ONLINE + client.getSender().setStatus(originalStatus); loadChatScene(); client.initReceiver(localDB, cacheMap); return true; @@ -170,7 +174,8 @@ public final class Startup extends Application { private static void loadChatScene() { // Set client user in local database - localDB.setUser(client.getSender()); + final var user = client.getSender(); + localDB.setUser(user); // Initialize chats in local database try { @@ -184,8 +189,13 @@ public final class Startup extends Application { context.initWriteProxy(); - if (client.isOnline()) context.getWriteProxy().flushCache(); - else + if (client.isOnline()) { + context.getWriteProxy().flushCache(); + + // Inform the server that this user has a different user status than expected + if (!user.getStatus().equals(UserStatus.ONLINE)) client.send(new UserStatusChange(user)); + } else + // Set all contacts to offline mode localDB.getChats() .stream() diff --git a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java index 7fbbe40..cf0fcb0 100644 --- a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java +++ b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java @@ -6,9 +6,11 @@ import java.awt.TrayIcon.MessageType; import javafx.application.Platform; import javafx.stage.Stage; +import envoy.client.event.OwnStatusChange; import envoy.client.helper.ShutdownHelper; -import envoy.client.util.IconUtil; +import envoy.client.util.*; import envoy.data.Message; +import envoy.data.User.UserStatus; import dev.kske.eventbus.*; import dev.kske.eventbus.Event; @@ -51,16 +53,32 @@ public final class StatusTrayIcon implements EventListener { trayIcon.setImageAutoSize(true); trayIcon.setToolTip("You are notified if you have unread messages."); - final PopupMenu popup = new PopupMenu(); + final var popup = new PopupMenu(); - final MenuItem exitMenuItem = new MenuItem("Exit"); - exitMenuItem.addActionListener(evt -> ShutdownHelper.exit()); + // Adding the exit menu item + final var exitMenuItem = new MenuItem("Exit"); + exitMenuItem.addActionListener(evt -> ShutdownHelper.exit(true)); popup.add(exitMenuItem); + // Adding the logout menu item + final var logoutMenuItem = new MenuItem("Logout"); + logoutMenuItem.addActionListener(evt -> { hide(); Platform.runLater(UserUtil::logout); }); + popup.add(logoutMenuItem); + + // Adding the status change items + final var statusSubMenu = new Menu("Change status"); + for (final var status : UserStatus.values()) { + final var statusMenuItem = new MenuItem(status.toString().toLowerCase()); + statusMenuItem.addActionListener(evt -> Platform.runLater(() -> UserUtil.changeStatus(status))); + statusSubMenu.add(statusMenuItem); + } + popup.add(statusSubMenu); + trayIcon.setPopupMenu(popup); - // Only display messages if the stage is not focused - stage.focusedProperty().addListener((ov, onHidden, onShown) -> displayMessages = !ov.getValue()); + // Only display messages if the stage is not focused and the current user status + // is not BUSY (if BUSY, displayMessages will be false) + stage.focusedProperty().addListener((ov, wasFocused, isFocused) -> displayMessages = !displayMessages && wasFocused ? false : !isFocused); // Show the window if the user clicks on the icon trayIcon.addActionListener(evt -> Platform.runLater(() -> { stage.setIconified(false); stage.toFront(); stage.requestFocus(); })); @@ -87,11 +105,13 @@ public final class StatusTrayIcon implements EventListener { */ public void hide() { SystemTray.getSystemTray().remove(trayIcon); } + @Event + private void onOwnStatusChange(OwnStatusChange statusChange) { displayMessages = !statusChange.get().equals(UserStatus.BUSY); } + @Event private void onMessage(Message message) { - if (displayMessages) trayIcon.displayMessage( - message.hasAttachment() ? "New " + message.getAttachment().getType().toString().toLowerCase() + " message received" : "New message received", - message.getText(), - MessageType.INFO); + if (displayMessages) trayIcon + .displayMessage(message.hasAttachment() ? "New " + message.getAttachment().getType().toString().toLowerCase() + " message received" + : "New message received", message.getText(), MessageType.INFO); } } diff --git a/client/src/main/java/envoy/client/ui/chatscene/ChatSceneCommands.java b/client/src/main/java/envoy/client/ui/chatscene/ChatSceneCommands.java index e2fd619..475d210 100644 --- a/client/src/main/java/envoy/client/ui/chatscene/ChatSceneCommands.java +++ b/client/src/main/java/envoy/client/ui/chatscene/ChatSceneCommands.java @@ -4,7 +4,8 @@ import java.util.Random; import java.util.function.*; import java.util.logging.Level; -import javafx.scene.control.ListView; +import javafx.scene.control.*; +import javafx.scene.control.Alert.AlertType; import javafx.scene.control.skin.VirtualFlow; import envoy.client.data.Context; @@ -12,8 +13,9 @@ import envoy.client.data.commands.*; import envoy.client.helper.ShutdownHelper; import envoy.client.ui.SceneContext.SceneInfo; import envoy.client.ui.controller.ChatScene; -import envoy.client.util.MessageUtil; +import envoy.client.util.*; import envoy.data.Message; +import envoy.data.User.UserStatus; import envoy.util.EnvoyLog; /** @@ -52,7 +54,7 @@ public final class ChatSceneCommands { .build("dabr"); // Logout initialization - builder.setAction(text -> ShutdownHelper.logout()).setDescription("Logs you out.").buildNoArg("logout"); + builder.setAction(text -> UserUtil.logout()).setDescription("Logs you out.").buildNoArg("logout"); // Exit initialization builder.setAction(text -> ShutdownHelper.exit()).setNumberOfArguments(0).setDescription("Exits the program.").build("exit", false); @@ -63,6 +65,17 @@ public final class ChatSceneCommands { .setDescription("Opens the settings screen") .buildNoArg("settings"); + // Status change initialization + builder.setAction(text -> { + try { + UserUtil.changeStatus(Enum.valueOf(UserStatus.class, text.get(0).toUpperCase())); + } catch (final IllegalArgumentException e) { + final var alert = new Alert(AlertType.ERROR); + alert.setContentText("Please provide an existing status"); + alert.showAndWait(); + } + }).setDescription("Changes your status to the given status.").setNumberOfArguments(1).setDefaults("").build("status"); + // Selection of a new message initialization messageDependantAction("s", m -> { messageList.getSelectionModel().clearSelection(); messageList.getSelectionModel().select(m); }, diff --git a/client/src/main/java/envoy/client/ui/control/ChatControl.java b/client/src/main/java/envoy/client/ui/control/ChatControl.java index eca5cb5..353bdaf 100644 --- a/client/src/main/java/envoy/client/ui/control/ChatControl.java +++ b/client/src/main/java/envoy/client/ui/control/ChatControl.java @@ -2,9 +2,8 @@ package envoy.client.ui.control; import javafx.geometry.*; import javafx.scene.control.Label; -import javafx.scene.image.*; +import javafx.scene.image.Image; import javafx.scene.layout.*; -import javafx.scene.shape.Rectangle; import envoy.client.data.*; import envoy.client.util.IconUtil; @@ -23,6 +22,8 @@ public final class ChatControl extends HBox { groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32); /** + * Creates a new {@code ChatControl}. + * * @param chat the chat to display * @since Envoy Client v0.1-beta */ @@ -31,13 +32,7 @@ public final class ChatControl extends HBox { setPadding(new Insets(0, 0, 3, 0)); // Profile picture - ImageView contactProfilePic = new ImageView(chat instanceof GroupChat ? groupIcon : userIcon); - final var clip = new Rectangle(); - clip.setWidth(32); - clip.setHeight(32); - clip.setArcHeight(32); - clip.setArcWidth(32); - contactProfilePic.setClip(clip); + final var contactProfilePic = new ProfilePicImageView(chat instanceof GroupChat ? groupIcon : userIcon, 32); getChildren().add(contactProfilePic); // Spacing diff --git a/client/src/main/java/envoy/client/ui/control/ContactControl.java b/client/src/main/java/envoy/client/ui/control/ContactControl.java index 8a85739..93b33d1 100644 --- a/client/src/main/java/envoy/client/ui/control/ContactControl.java +++ b/client/src/main/java/envoy/client/ui/control/ContactControl.java @@ -15,25 +15,37 @@ import envoy.data.*; */ public final class ContactControl extends VBox { + private final Contact contact; + /** * @param contact the contact to display * @since Envoy Client v0.2-beta */ public ContactControl(Contact contact) { + this.contact = contact; // Name label final var nameLabel = new Label(contact.getName()); getChildren().add(nameLabel); // Online status (user) or member count (group) - if (contact instanceof User) { - final var status = ((User) contact).getStatus().toString(); - final var statusLabel = new Label(status); - statusLabel.getStyleClass().add(status.toLowerCase()); - getChildren().add(statusLabel); - } else { - getChildren().add(new Label(contact.getContacts().size() + " members")); - } + getChildren().add(contact instanceof User ? new UserStatusLabel((User) contact) : new GroupSizeLabel((Group) contact)); + getStyleClass().add("list-element"); } + + /** + * Replaces the info label of this {@code ContactControl} with an updated + * version. + *

+ * This method should be called when the status of the underlying user or the + * size of the underlying group has changed. + * + * @since Envoy Client v0.3-beta + * @apiNote will produce buggy results if contact control gets updated so that + * the info label is no longer on index 1. + */ + public void replaceInfoLabel() { + getChildren().set(1, contact instanceof User ? new UserStatusLabel((User) contact) : new GroupSizeLabel((Group) contact)); + } } diff --git a/client/src/main/java/envoy/client/ui/control/GroupSizeLabel.java b/client/src/main/java/envoy/client/ui/control/GroupSizeLabel.java new file mode 100644 index 0000000..8413744 --- /dev/null +++ b/client/src/main/java/envoy/client/ui/control/GroupSizeLabel.java @@ -0,0 +1,20 @@ +package envoy.client.ui.control; + +import javafx.scene.control.Label; + +import envoy.data.Group; + +/** + * Displays the amount of members in a {@link Group}. + * + * @author Leon Hofmeister + * @since Envoy Client v0.3-beta + */ +public final class GroupSizeLabel extends Label { + + /** + * @param recipient the group whose members to show + * @since Envoy Client v0.3-beta + */ + public GroupSizeLabel(Group recipient) { super(recipient.getContacts().size() + " members"); } +} diff --git a/client/src/main/java/envoy/client/ui/control/UserStatusLabel.java b/client/src/main/java/envoy/client/ui/control/UserStatusLabel.java new file mode 100644 index 0000000..4f942cb --- /dev/null +++ b/client/src/main/java/envoy/client/ui/control/UserStatusLabel.java @@ -0,0 +1,23 @@ +package envoy.client.ui.control; + +import javafx.scene.control.Label; + +import envoy.data.User; + +/** + * Displays the status of a {@link User}. + * + * @author Leon Hofmeister + * @since Envoy Client v0.3-beta + */ +public final class UserStatusLabel extends Label { + + /** + * @param user the user whose status to display + * @since Envoy Client v0.3-beta + */ + public UserStatusLabel(User user) { + super(user.getStatus().toString()); + getStyleClass().add(user.getStatus().toString().toLowerCase()); + } +} 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 5a55646..da4323e 100644 --- a/client/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/client/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -13,6 +13,7 @@ import javafx.application.Platform; import javafx.collections.ObservableList; import javafx.collections.transformation.FilteredList; import javafx.fxml.*; +import javafx.geometry.Pos; import javafx.scene.control.*; import javafx.scene.control.Alert.AlertType; import javafx.scene.image.*; @@ -29,7 +30,7 @@ import envoy.client.event.*; import envoy.client.net.*; import envoy.client.ui.*; import envoy.client.ui.chatscene.*; -import envoy.client.ui.control.ChatControl; +import envoy.client.ui.control.*; import envoy.client.ui.listcell.*; import envoy.client.util.*; import envoy.data.*; @@ -51,12 +52,6 @@ import dev.kske.eventbus.Event; */ public final class ChatScene implements EventListener, Restorable { - @FXML - private GridPane scene; - - @FXML - private Label contactLabel; - @FXML private ListView messageList; @@ -84,33 +79,33 @@ public final class ChatScene implements EventListener, Restorable { @FXML private Button newContactButton; - @FXML - private TextArea messageTextArea; - @FXML private Label remainingChars; @FXML private Label infoLabel; - @FXML - private MenuItem deleteContactMenuItem; - - @FXML - private ImageView attachmentView; - @FXML private Label topBarContactLabel; @FXML private Label topBarStatusLabel; + @FXML + private MenuItem deleteContactMenuItem; + + @FXML + private ImageView attachmentView; + @FXML private ImageView clientProfilePic; @FXML private ImageView recipientProfilePic; + @FXML + private TextArea messageTextArea; + @FXML private TextArea contactSearch; @@ -129,6 +124,12 @@ public final class ChatScene implements EventListener, Restorable { @FXML private HBox contactSpecificOnlineOperations; + @FXML + private HBox ownContactControl; + + @FXML + private Region spaceBetweenUserAndSettingsButton; + private Chat currentChat; private FilteredList chats; private boolean recording; @@ -188,10 +189,15 @@ public final class ChatScene implements EventListener, Restorable { clientProfilePic.setClip(clip); chatList.setItems(chats = new FilteredList<>(localDB.getChats())); - contactLabel.setText(localDB.getUser().getName()); + + // Set the design of the box in the upper-left corner + settingsButton.setAlignment(Pos.BOTTOM_RIGHT); + HBox.setHgrow(spaceBetweenUserAndSettingsButton, Priority.ALWAYS); + generateOwnStatusControl(); Platform.runLater(() -> { final var online = client.isOnline(); + // no check will be performed in case it has already been disabled - a negative // GroupCreationResult might have been returned if (!newGroupButton.isDisabled()) newGroupButton.setDisable(!online); @@ -251,8 +257,19 @@ public final class ChatScene implements EventListener, Restorable { .ifPresent(msg -> Platform.runLater(messageList::refresh)); } - @Event(eventType = UserStatusChange.class) - private void onUserStatusChange() { Platform.runLater(chatList::refresh); } + @Event + private void onUserStatusChange(UserStatusChange statusChange) { + Platform.runLater(() -> { + chatList.refresh(); + + // Replacing the display in the top bar + if (currentChat != null && currentChat.getRecipient().getID() == statusChange.getID()) { + topBarStatusLabel.getStyleClass().clear(); + topBarStatusLabel.setText(statusChange.get().toString()); + topBarStatusLabel.getStyleClass().add(statusChange.get().toString().toLowerCase()); + } + }); + } @Event private void onContactOperation(ContactOperation operation) { @@ -297,8 +314,9 @@ public final class ChatScene implements EventListener, Restorable { chatList.setCellFactory(new ListCellFactory<>(ChatControl::new)); messageList.setCellFactory(MessageListCell::new); // TODO: cache image - if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43)); - else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43)); + if (currentChat != null) + if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43)); + else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43)); } @Event(eventType = Logout.class, priority = 200) @@ -359,6 +377,7 @@ public final class ChatScene implements EventListener, Restorable { recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43)); } else { topBarStatusLabel.setText(currentChat.getRecipient().getContacts().size() + " members"); + topBarStatusLabel.getStyleClass().clear(); recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43)); } final var clip = new Rectangle(); @@ -699,6 +718,21 @@ public final class ChatScene implements EventListener, Restorable { attachmentView.setVisible(visible); } + @Event(eventType = OwnStatusChange.class, priority = 50) + private void generateOwnStatusControl() { + + // Update the own user status if present + if (ownContactControl.getChildren().get(0) instanceof ContactControl) + ((ContactControl) ownContactControl.getChildren().get(0)).replaceInfoLabel(); + else { + + // Else prepend it to the HBox children + final var ownUserControl = new ContactControl(localDB.getUser()); + ownUserControl.setAlignment(Pos.CENTER_LEFT); + ownContactControl.getChildren().add(0, ownUserControl); + } + } + // Context menu actions @FXML 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 4bcb1d8..ae2f04f 100644 --- a/client/src/main/java/envoy/client/ui/settings/DownloadSettingsPane.java +++ b/client/src/main/java/envoy/client/ui/settings/DownloadSettingsPane.java @@ -5,8 +5,6 @@ import javafx.scene.control.*; import javafx.scene.layout.HBox; import javafx.stage.DirectoryChooser; -import envoy.client.data.Context; - /** * Displays options for downloading {@link envoy.data.Attachment}s. * @@ -47,7 +45,7 @@ public final class DownloadSettingsPane extends SettingsPane { final var directoryChooser = new DirectoryChooser(); directoryChooser.setTitle("Select the directory where attachments should be saved to"); directoryChooser.setInitialDirectory(settings.getDownloadLocation()); - final var selectedDirectory = directoryChooser.showDialog(Context.getInstance().getSceneContext().getStage()); + final var selectedDirectory = directoryChooser.showDialog(context.getSceneContext().getStage()); if (selectedDirectory != null) { currentPath.setText(selectedDirectory.getAbsolutePath()); 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 2e3786c..bed9af9 100644 --- a/client/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java +++ b/client/src/main/java/envoy/client/ui/settings/GeneralSettingsPane.java @@ -4,8 +4,8 @@ import javafx.scene.control.*; import envoy.client.data.SettingsItem; import envoy.client.event.ThemeChangeEvent; -import envoy.client.helper.ShutdownHelper; import envoy.client.ui.StatusTrayIcon; +import envoy.client.util.UserUtil; import envoy.data.User.UserStatus; import dev.kske.eventbus.EventBus; @@ -57,14 +57,13 @@ public final class GeneralSettingsPane extends SettingsPane { final var statusComboBox = new ComboBox(); statusComboBox.getItems().setAll(UserStatus.values()); - statusComboBox.setValue(UserStatus.ONLINE); + statusComboBox.setValue(context.getLocalDB().getUser().getStatus()); statusComboBox.setTooltip(new Tooltip("Change your current status")); - // TODO add action when value is changed - statusComboBox.setOnAction(e -> {}); + statusComboBox.setOnAction(e -> UserUtil.changeStatus(statusComboBox.getValue())); getChildren().add(statusComboBox); final var logoutButton = new Button("Logout"); - logoutButton.setOnAction(e -> ShutdownHelper.logout()); + logoutButton.setOnAction(e -> UserUtil.logout()); final var logoutTooltip = new Tooltip("Brings you back to the login screen and removes \"remember me\" status from this account"); logoutTooltip.setWrapText(true); logoutButton.setTooltip(logoutTooltip); diff --git a/client/src/main/java/envoy/client/ui/settings/OnlineOnlySettingsPane.java b/client/src/main/java/envoy/client/ui/settings/OnlineOnlySettingsPane.java index 592b31b..5ba18b4 100644 --- a/client/src/main/java/envoy/client/ui/settings/OnlineOnlySettingsPane.java +++ b/client/src/main/java/envoy/client/ui/settings/OnlineOnlySettingsPane.java @@ -5,7 +5,6 @@ import javafx.scene.control.*; import javafx.scene.layout.*; import javafx.scene.paint.Color; -import envoy.client.data.Context; import envoy.client.net.Client; /** @@ -20,7 +19,7 @@ import envoy.client.net.Client; */ public abstract class OnlineOnlySettingsPane extends SettingsPane { - protected final Client client = Context.getInstance().getClient(); + protected final Client client = context.getClient(); private final Tooltip beOnlineReminder = new Tooltip("You need to be online to modify your account."); 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 5dc9796..ed80f20 100644 --- a/client/src/main/java/envoy/client/ui/settings/SettingsPane.java +++ b/client/src/main/java/envoy/client/ui/settings/SettingsPane.java @@ -2,7 +2,7 @@ package envoy.client.ui.settings; import javafx.scene.layout.VBox; -import envoy.client.data.Settings; +import envoy.client.data.*; /** * @author Kai S. K. Engelbart @@ -12,7 +12,8 @@ public abstract class SettingsPane extends VBox { protected String title; - protected static final Settings settings = Settings.getInstance(); + protected static final Settings settings = Settings.getInstance(); + protected static final Context context = Context.getInstance(); protected SettingsPane(String title) { this.title = title; } 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 5c6e730..fdf751e 100644 --- a/client/src/main/java/envoy/client/ui/settings/UserSettingsPane.java +++ b/client/src/main/java/envoy/client/ui/settings/UserSettingsPane.java @@ -14,7 +14,6 @@ import javafx.scene.input.InputEvent; import javafx.scene.layout.HBox; import javafx.stage.FileChooser; -import envoy.client.data.Context; import envoy.client.ui.control.ProfilePicImageView; import envoy.client.util.IconUtil; import envoy.event.*; @@ -66,7 +65,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane { pictureChooser.setInitialDirectory(new File(System.getProperty("user.home"))); pictureChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif")); - final var file = pictureChooser.showOpenDialog(Context.getInstance().getSceneContext().getStage()); + final var file = pictureChooser.showOpenDialog(context.getSceneContext().getStage()); if (file != null) { diff --git a/client/src/main/java/envoy/client/util/UserUtil.java b/client/src/main/java/envoy/client/util/UserUtil.java new file mode 100644 index 0000000..4f1b37b --- /dev/null +++ b/client/src/main/java/envoy/client/util/UserUtil.java @@ -0,0 +1,64 @@ +package envoy.client.util; + +import java.util.logging.Level; + +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; + +import envoy.client.data.Context; +import envoy.client.event.*; +import envoy.client.helper.*; +import envoy.client.ui.SceneContext.SceneInfo; +import envoy.data.User.UserStatus; +import envoy.event.UserStatusChange; +import envoy.util.EnvoyLog; + +import dev.kske.eventbus.EventBus; + +/** + * Contains methods that change something about the currently logged in user. + * + * @author Leon Hofmeister + * @since Envoy Client v0.3-beta + */ +public final class UserUtil { + + private UserUtil() {} + + /** + * Logs the current user out and reopens + * {@link envoy.client.ui.controller.LoginScene}. + * + * @since Envoy Client v0.2-beta + */ + public static void logout() { + final var alert = new Alert(AlertType.CONFIRMATION); + alert.setTitle("Logout?"); + alert.setContentText("Are you sure you want to log out?"); + + AlertHelper.confirmAction(alert, () -> { + EnvoyLog.getLogger(ShutdownHelper.class).log(Level.INFO, "A logout was requested"); + EventBus.getInstance().dispatch(new EnvoyCloseEvent()); + EventBus.getInstance().dispatch(new Logout()); + Context.getInstance().getSceneContext().load(SceneInfo.LOGIN_SCENE); + }); + } + + /** + * Notifies the application that the status of the currently logged in user has + * changed. + * + * @param newStatus the new status + * @since Envoy Client v0.3-beta + */ + public static void changeStatus(UserStatus newStatus) { + + // Sending the already active status is a valid action + if (newStatus.equals(Context.getInstance().getLocalDB().getUser().getStatus())) return; + else { + EventBus.getInstance().dispatch(new OwnStatusChange(newStatus)); + if (Context.getInstance().getClient().isOnline()) + Context.getInstance().getClient().send(new UserStatusChange(Context.getInstance().getLocalDB().getUser().getID(), newStatus)); + } + } +} diff --git a/client/src/main/resources/css/base.css b/client/src/main/resources/css/base.css index be559b7..28d276c 100644 --- a/client/src/main/resources/css/base.css +++ b/client/src/main/resources/css/base.css @@ -6,7 +6,7 @@ -fx-background-radius: 15.0px; } -.list-cell:selected, .menu-item:hover { +.list-cell:selected, .menu-item:hover, .combo-box-popup .list-view .list-cell:selected { -fx-background-color: #454c4f; } diff --git a/client/src/main/resources/css/dark.css b/client/src/main/resources/css/dark.css index 0ed2997..8b28f80 100644 --- a/client/src/main/resources/css/dark.css +++ b/client/src/main/resources/css/dark.css @@ -18,7 +18,7 @@ -fx-background-color: lightgray; } -#message-list, .text-field, .password-field, .tooltip, .pane, .pane .content, .vbox, .titled-pane > .title, .titled-pane > *.content, .context-menu, .menu-item, #quick-select-list { +#message-list, .text-field, .password-field, .tooltip, .pane, .pane .content, .vbox, .titled-pane > .title, .titled-pane > *.content, .context-menu, .menu-item, .combo-box-popup .list-view .list-cell, #quick-select-list { -fx-background-color: #222222; } diff --git a/client/src/main/resources/fxml/ChatScene.fxml b/client/src/main/resources/fxml/ChatScene.fxml index f557974..ec6db63 100644 --- a/client/src/main/resources/fxml/ChatScene.fxml +++ b/client/src/main/resources/fxml/ChatScene.fxml @@ -22,14 +22,15 @@ - - + - + - - - + - - - - - - - - + diff --git a/server/src/main/java/envoy/server/net/ConnectionManager.java b/server/src/main/java/envoy/server/net/ConnectionManager.java index 2dc7d2d..581a10f 100755 --- a/server/src/main/java/envoy/server/net/ConnectionManager.java +++ b/server/src/main/java/envoy/server/net/ConnectionManager.java @@ -22,14 +22,14 @@ public final class ConnectionManager implements ISocketIdListener { * * @since Envoy Server Standalone v0.1-alpha */ - private Set pendingSockets = new HashSet<>(); + private final Set pendingSockets = new HashSet<>(); /** * Contains all socket IDs that have acquired a user ID as keys to these IDs. * * @since Envoy Server Standalone v0.1-alpha */ - private Map sockets = new HashMap<>(); + private final Map sockets = new HashMap<>(); private static ConnectionManager connectionManager = new ConnectionManager(); @@ -44,11 +44,11 @@ public final class ConnectionManager implements ISocketIdListener { @Override public void socketCancelled(long socketID) { if (!pendingSockets.remove(socketID)) { + // Notify contacts of this users offline-going - envoy.server.data.User user = PersistenceManager.getInstance().getUserByID(getUserIDBySocketID(socketID)); - user.setStatus(UserStatus.OFFLINE); + final envoy.server.data.User user = PersistenceManager.getInstance().getUserByID(getUserIDBySocketID(socketID)); user.setLastSeen(Instant.now()); - UserStatusChangeProcessor.updateUserStatus(user); + UserStatusChangeProcessor.updateUserStatus(user, UserStatus.OFFLINE); // Remove the socket sockets.entrySet().removeIf(e -> e.getValue() == socketID); diff --git a/server/src/main/java/envoy/server/processors/LoginCredentialProcessor.java b/server/src/main/java/envoy/server/processors/LoginCredentialProcessor.java index fdd83bf..3e91a7f 100755 --- a/server/src/main/java/envoy/server/processors/LoginCredentialProcessor.java +++ b/server/src/main/java/envoy/server/processors/LoginCredentialProcessor.java @@ -46,42 +46,40 @@ public final class LoginCredentialProcessor implements ObjectProcessor pendingGroupMessages = PersistenceManager.getInstance().getPendingGroupMessages(user, credentials.getLastSync()); + final List pendingGroupMessages = PersistenceManager.getInstance().getPendingGroupMessages(user, credentials.getLastSync()); logger.fine("Sending " + pendingGroupMessages.size() + " pending group messages to " + user + "..."); - for (var gmsg : pendingGroupMessages) { + for (final var gmsg : pendingGroupMessages) { final var gmsgCommon = gmsg.toCommon(); // Deliver the message to the user if he hasn't received it yet @@ -189,20 +186,18 @@ public final class LoginCredentialProcessor implements ObjectProcessor writeProxy.write(socketID, new GroupMessageStatusChange(gmsg.getID(), memberStatus, gmsg.getLastStatusChangeDate(), memberID))); - } // Deliver just a status change instead of the whole message if (gmsg.getStatus() == RECEIVED && user.getLastSeen().isBefore(gmsg.getReceivedDate()) diff --git a/server/src/main/java/envoy/server/processors/UserStatusChangeProcessor.java b/server/src/main/java/envoy/server/processors/UserStatusChangeProcessor.java index 9350af0..c551513 100755 --- a/server/src/main/java/envoy/server/processors/UserStatusChangeProcessor.java +++ b/server/src/main/java/envoy/server/processors/UserStatusChangeProcessor.java @@ -28,18 +28,20 @@ public final class UserStatusChangeProcessor implements ObjectProcessor