Add Enhanced Keyboard Shortcut Mechanism #91
@@ -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<String, User>} of all users stored locally with their
 | 
						 * @return a {@code Map<String, User>} of all users stored locally with their
 | 
				
			||||||
	 *         user names as keys
 | 
						 *         user names as keys
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										21
									
								
								client/src/main/java/envoy/client/event/OwnStatusChange.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								client/src/main/java/envoy/client/event/OwnStatusChange.java
									
									
									
									
									
										Normal file
									
								
							@@ -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<UserStatus> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -27,8 +27,6 @@ public final class AlertHelper {
 | 
				
			|||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static void confirmAction(Alert alert, Runnable action) {
 | 
						public static void confirmAction(Alert alert, Runnable action) {
 | 
				
			||||||
		alert.setHeight(225);
 | 
					 | 
				
			||||||
		alert.setWidth(400);
 | 
					 | 
				
			||||||
		alert.setHeaderText("");
 | 
							alert.setHeaderText("");
 | 
				
			||||||
		if (Settings.getInstance().isAskForConfirmation()) alert.showAndWait().filter(ButtonType.OK::equals).ifPresent(bu -> action.run());
 | 
							if (Settings.getInstance().isAskForConfirmation()) alert.showAndWait().filter(ButtonType.OK::equals).ifPresent(bu -> action.run());
 | 
				
			||||||
		else action.run();
 | 
							else action.run();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,8 @@
 | 
				
			|||||||
package envoy.client.helper;
 | 
					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.data.*;
 | 
				
			||||||
import envoy.client.event.*;
 | 
					import envoy.client.event.EnvoyCloseEvent;
 | 
				
			||||||
import envoy.client.ui.SceneContext.SceneInfo;
 | 
					 | 
				
			||||||
import envoy.client.ui.StatusTrayIcon;
 | 
					import envoy.client.ui.StatusTrayIcon;
 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.eventbus.EventBus;
 | 
					import dev.kske.eventbus.EventBus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,30 +22,21 @@ public final class ShutdownHelper {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static void exit() {
 | 
						public static void exit() { exit(false); }
 | 
				
			||||||
		if (Settings.getInstance().isHideOnClose() && StatusTrayIcon.isSupported()) Context.getInstance().getStage().setIconified(true);
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * 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 {
 | 
							else {
 | 
				
			||||||
			EventBus.getInstance().dispatch(new EnvoyCloseEvent());
 | 
								EventBus.getInstance().dispatch(new EnvoyCloseEvent());
 | 
				
			||||||
			System.exit(0);
 | 
								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);
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -117,9 +117,13 @@ public final class Startup extends Application {
 | 
				
			|||||||
		cacheMap.put(GroupMessage.class, new Cache<GroupMessage>());
 | 
							cacheMap.put(GroupMessage.class, new Cache<GroupMessage>());
 | 
				
			||||||
		cacheMap.put(MessageStatusChange.class, new Cache<MessageStatusChange>());
 | 
							cacheMap.put(MessageStatusChange.class, new Cache<MessageStatusChange>());
 | 
				
			||||||
		cacheMap.put(GroupMessageStatusChange.class, new Cache<GroupMessageStatusChange>());
 | 
							cacheMap.put(GroupMessageStatusChange.class, new Cache<GroupMessageStatusChange>());
 | 
				
			||||||
 | 
							final var originalStatus = localDB.getUser().getStatus();
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			client.performHandshake(credentials, cacheMap);
 | 
								client.performHandshake(credentials, cacheMap);
 | 
				
			||||||
			if (client.isOnline()) {
 | 
								if (client.isOnline()) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Restore the original status as the server automatically returns status ONLINE
 | 
				
			||||||
 | 
									client.getSender().setStatus(originalStatus);
 | 
				
			||||||
				loadChatScene();
 | 
									loadChatScene();
 | 
				
			||||||
				client.initReceiver(localDB, cacheMap);
 | 
									client.initReceiver(localDB, cacheMap);
 | 
				
			||||||
				return true;
 | 
									return true;
 | 
				
			||||||
@@ -174,7 +178,8 @@ public final class Startup extends Application {
 | 
				
			|||||||
	private static void loadChatScene() {
 | 
						private static void loadChatScene() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Set client user in local database
 | 
							// Set client user in local database
 | 
				
			||||||
		localDB.setUser(client.getSender());
 | 
							final var user = client.getSender();
 | 
				
			||||||
 | 
							localDB.setUser(user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Initialize chats in local database
 | 
							// Initialize chats in local database
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
@@ -188,8 +193,13 @@ public final class Startup extends Application {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		context.initWriteProxy();
 | 
							context.initWriteProxy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (client.isOnline()) context.getWriteProxy().flushCache();
 | 
							if (client.isOnline()) {
 | 
				
			||||||
		else
 | 
								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
 | 
								// Set all contacts to offline mode
 | 
				
			||||||
			localDB.getChats()
 | 
								localDB.getChats()
 | 
				
			||||||
				.stream()
 | 
									.stream()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,9 +6,11 @@ import java.awt.TrayIcon.MessageType;
 | 
				
			|||||||
import javafx.application.Platform;
 | 
					import javafx.application.Platform;
 | 
				
			||||||
import javafx.stage.Stage;
 | 
					import javafx.stage.Stage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.client.event.OwnStatusChange;
 | 
				
			||||||
import envoy.client.helper.ShutdownHelper;
 | 
					import envoy.client.helper.ShutdownHelper;
 | 
				
			||||||
import envoy.client.util.IconUtil;
 | 
					import envoy.client.util.*;
 | 
				
			||||||
import envoy.data.Message;
 | 
					import envoy.data.Message;
 | 
				
			||||||
 | 
					import envoy.data.User.UserStatus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.eventbus.*;
 | 
					import dev.kske.eventbus.*;
 | 
				
			||||||
import dev.kske.eventbus.Event;
 | 
					import dev.kske.eventbus.Event;
 | 
				
			||||||
@@ -51,16 +53,32 @@ public final class StatusTrayIcon implements EventListener {
 | 
				
			|||||||
		trayIcon.setImageAutoSize(true);
 | 
							trayIcon.setImageAutoSize(true);
 | 
				
			||||||
		trayIcon.setToolTip("You are notified if you have unread messages.");
 | 
							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");
 | 
							// Adding the exit menu item
 | 
				
			||||||
		exitMenuItem.addActionListener(evt -> ShutdownHelper.exit());
 | 
							final var exitMenuItem = new MenuItem("Exit");
 | 
				
			||||||
 | 
							exitMenuItem.addActionListener(evt -> ShutdownHelper.exit(true));
 | 
				
			||||||
		popup.add(exitMenuItem);
 | 
							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);
 | 
							trayIcon.setPopupMenu(popup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Only display messages if the stage is not focused
 | 
							// Only display messages if the stage is not focused and the current user status
 | 
				
			||||||
		stage.focusedProperty().addListener((ov, onHidden, onShown) -> displayMessages = !ov.getValue());
 | 
							// 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
 | 
							// Show the window if the user clicks on the icon
 | 
				
			||||||
		trayIcon.addActionListener(evt -> Platform.runLater(() -> { stage.setIconified(false); stage.toFront(); stage.requestFocus(); }));
 | 
							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); }
 | 
						public void hide() { SystemTray.getSystemTray().remove(trayIcon); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Event
 | 
				
			||||||
 | 
						private void onOwnStatusChange(OwnStatusChange statusChange) { displayMessages = !statusChange.get().equals(UserStatus.BUSY); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event
 | 
						@Event
 | 
				
			||||||
	private void onMessage(Message message) {
 | 
						private void onMessage(Message message) {
 | 
				
			||||||
		if (displayMessages) trayIcon.displayMessage(
 | 
							if (displayMessages) trayIcon
 | 
				
			||||||
				message.hasAttachment() ? "New " + message.getAttachment().getType().toString().toLowerCase() + " message received" : "New message received",
 | 
								.displayMessage(message.hasAttachment() ? "New " + message.getAttachment().getType().toString().toLowerCase() + " message received"
 | 
				
			||||||
				message.getText(),
 | 
										: "New message received", message.getText(), MessageType.INFO);
 | 
				
			||||||
				MessageType.INFO);
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,8 @@ import java.util.Random;
 | 
				
			|||||||
import java.util.function.*;
 | 
					import java.util.function.*;
 | 
				
			||||||
import java.util.logging.Level;
 | 
					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 javafx.scene.control.skin.VirtualFlow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Context;
 | 
					import envoy.client.data.Context;
 | 
				
			||||||
@@ -12,8 +13,9 @@ import envoy.client.data.commands.*;
 | 
				
			|||||||
import envoy.client.helper.ShutdownHelper;
 | 
					import envoy.client.helper.ShutdownHelper;
 | 
				
			||||||
import envoy.client.ui.SceneContext.SceneInfo;
 | 
					import envoy.client.ui.SceneContext.SceneInfo;
 | 
				
			||||||
import envoy.client.ui.controller.ChatScene;
 | 
					import envoy.client.ui.controller.ChatScene;
 | 
				
			||||||
import envoy.client.util.MessageUtil;
 | 
					import envoy.client.util.*;
 | 
				
			||||||
import envoy.data.Message;
 | 
					import envoy.data.Message;
 | 
				
			||||||
 | 
					import envoy.data.User.UserStatus;
 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -52,7 +54,7 @@ public final class ChatSceneCommands {
 | 
				
			|||||||
			.build("dabr");
 | 
								.build("dabr");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Logout initialization
 | 
							// 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
 | 
							// Exit initialization
 | 
				
			||||||
		builder.setAction(text -> ShutdownHelper.exit()).setNumberOfArguments(0).setDescription("Exits the program.").build("exit", false);
 | 
							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")
 | 
								.setDescription("Opens the settings screen")
 | 
				
			||||||
			.buildNoArg("settings");
 | 
								.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
 | 
							// Selection of a new message initialization
 | 
				
			||||||
		messageDependantAction("s",
 | 
							messageDependantAction("s",
 | 
				
			||||||
				m -> { messageList.getSelectionModel().clearSelection(); messageList.getSelectionModel().select(m); },
 | 
									m -> { messageList.getSelectionModel().clearSelection(); messageList.getSelectionModel().select(m); },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,9 +2,8 @@ package envoy.client.ui.control;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import javafx.geometry.*;
 | 
					import javafx.geometry.*;
 | 
				
			||||||
import javafx.scene.control.Label;
 | 
					import javafx.scene.control.Label;
 | 
				
			||||||
import javafx.scene.image.*;
 | 
					import javafx.scene.image.Image;
 | 
				
			||||||
import javafx.scene.layout.*;
 | 
					import javafx.scene.layout.*;
 | 
				
			||||||
import javafx.scene.shape.Rectangle;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.*;
 | 
					import envoy.client.data.*;
 | 
				
			||||||
import envoy.client.util.IconUtil;
 | 
					import envoy.client.util.IconUtil;
 | 
				
			||||||
@@ -23,6 +22,8 @@ public final class ChatControl extends HBox {
 | 
				
			|||||||
			groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32);
 | 
								groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
 | 
						 * Creates a new {@code ChatControl}.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
	 * @param chat the chat to display
 | 
						 * @param chat the chat to display
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -31,13 +32,7 @@ public final class ChatControl extends HBox {
 | 
				
			|||||||
		setPadding(new Insets(0, 0, 3, 0));
 | 
							setPadding(new Insets(0, 0, 3, 0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Profile picture
 | 
							// Profile picture
 | 
				
			||||||
		ImageView	contactProfilePic	= new ImageView(chat instanceof GroupChat ? groupIcon : userIcon);
 | 
							final var contactProfilePic = new ProfilePicImageView(chat instanceof GroupChat ? groupIcon : userIcon, 32);
 | 
				
			||||||
		final var clip = new Rectangle();
 | 
					 | 
				
			||||||
		clip.setWidth(32);
 | 
					 | 
				
			||||||
		clip.setHeight(32);
 | 
					 | 
				
			||||||
		clip.setArcHeight(32);
 | 
					 | 
				
			||||||
		clip.setArcWidth(32);
 | 
					 | 
				
			||||||
		contactProfilePic.setClip(clip);
 | 
					 | 
				
			||||||
		getChildren().add(contactProfilePic);
 | 
							getChildren().add(contactProfilePic);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Spacing
 | 
							// Spacing
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,25 +15,37 @@ import envoy.data.*;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public final class ContactControl extends VBox {
 | 
					public final class ContactControl extends VBox {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Contact contact;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param contact the contact to display
 | 
						 * @param contact the contact to display
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public ContactControl(Contact contact) {
 | 
						public ContactControl(Contact contact) {
 | 
				
			||||||
 | 
							this.contact = contact;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Name label
 | 
							// Name label
 | 
				
			||||||
		final var nameLabel = new Label(contact.getName());
 | 
							final var nameLabel = new Label(contact.getName());
 | 
				
			||||||
		getChildren().add(nameLabel);
 | 
							getChildren().add(nameLabel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Online status (user) or member count (group)
 | 
							// Online status (user) or member count (group)
 | 
				
			||||||
		if (contact instanceof User) {
 | 
							getChildren().add(contact instanceof User ? new UserStatusLabel((User) contact) : new GroupSizeLabel((Group) contact));
 | 
				
			||||||
			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"));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		getStyleClass().add("list-element");
 | 
							getStyleClass().add("list-element");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Replaces the info label of this {@code ContactControl} with an updated
 | 
				
			||||||
 | 
						 * version.
 | 
				
			||||||
 | 
						 * <p>
 | 
				
			||||||
 | 
						 * 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));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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"); }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -13,6 +13,7 @@ import javafx.application.Platform;
 | 
				
			|||||||
import javafx.collections.ObservableList;
 | 
					import javafx.collections.ObservableList;
 | 
				
			||||||
import javafx.collections.transformation.FilteredList;
 | 
					import javafx.collections.transformation.FilteredList;
 | 
				
			||||||
import javafx.fxml.*;
 | 
					import javafx.fxml.*;
 | 
				
			||||||
 | 
					import javafx.geometry.Pos;
 | 
				
			||||||
import javafx.scene.control.*;
 | 
					import javafx.scene.control.*;
 | 
				
			||||||
import javafx.scene.control.Alert.AlertType;
 | 
					import javafx.scene.control.Alert.AlertType;
 | 
				
			||||||
import javafx.scene.image.*;
 | 
					import javafx.scene.image.*;
 | 
				
			||||||
@@ -29,7 +30,7 @@ import envoy.client.event.*;
 | 
				
			|||||||
import envoy.client.net.*;
 | 
					import envoy.client.net.*;
 | 
				
			||||||
import envoy.client.ui.*;
 | 
					import envoy.client.ui.*;
 | 
				
			||||||
import envoy.client.ui.chatscene.*;
 | 
					import envoy.client.ui.chatscene.*;
 | 
				
			||||||
import envoy.client.ui.control.ChatControl;
 | 
					import envoy.client.ui.control.*;
 | 
				
			||||||
import envoy.client.ui.listcell.*;
 | 
					import envoy.client.ui.listcell.*;
 | 
				
			||||||
import envoy.client.util.*;
 | 
					import envoy.client.util.*;
 | 
				
			||||||
import envoy.data.*;
 | 
					import envoy.data.*;
 | 
				
			||||||
@@ -51,12 +52,6 @@ import dev.kske.eventbus.Event;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public final class ChatScene implements EventListener, Restorable {
 | 
					public final class ChatScene implements EventListener, Restorable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
					 | 
				
			||||||
	private GridPane scene;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@FXML
 | 
					 | 
				
			||||||
	private Label contactLabel;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private ListView<Message> messageList;
 | 
						private ListView<Message> messageList;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -84,33 +79,33 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private Button newContactButton;
 | 
						private Button newContactButton;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
					 | 
				
			||||||
	private TextArea messageTextArea;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private Label remainingChars;
 | 
						private Label remainingChars;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private Label infoLabel;
 | 
						private Label infoLabel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
					 | 
				
			||||||
	private MenuItem deleteContactMenuItem;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@FXML
 | 
					 | 
				
			||||||
	private ImageView attachmentView;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private Label topBarContactLabel;
 | 
						private Label topBarContactLabel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private Label topBarStatusLabel;
 | 
						private Label topBarStatusLabel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@FXML
 | 
				
			||||||
 | 
						private MenuItem deleteContactMenuItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@FXML
 | 
				
			||||||
 | 
						private ImageView attachmentView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private ImageView clientProfilePic;
 | 
						private ImageView clientProfilePic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private ImageView recipientProfilePic;
 | 
						private ImageView recipientProfilePic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@FXML
 | 
				
			||||||
 | 
						private TextArea messageTextArea;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private TextArea contactSearch;
 | 
						private TextArea contactSearch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -129,6 +124,12 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private HBox contactSpecificOnlineOperations;
 | 
						private HBox contactSpecificOnlineOperations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@FXML
 | 
				
			||||||
 | 
						private HBox ownContactControl;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@FXML
 | 
				
			||||||
 | 
						private Region spaceBetweenUserAndSettingsButton;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private Chat				currentChat;
 | 
						private Chat				currentChat;
 | 
				
			||||||
	private FilteredList<Chat>	chats;
 | 
						private FilteredList<Chat>	chats;
 | 
				
			||||||
	private boolean				recording;
 | 
						private boolean				recording;
 | 
				
			||||||
@@ -188,10 +189,15 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
		clientProfilePic.setClip(clip);
 | 
							clientProfilePic.setClip(clip);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		chatList.setItems(chats = new FilteredList<>(localDB.getChats()));
 | 
							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(() -> {
 | 
							Platform.runLater(() -> {
 | 
				
			||||||
			final var online = client.isOnline();
 | 
								final var online = client.isOnline();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// no check will be performed in case it has already been disabled - a negative
 | 
								// no check will be performed in case it has already been disabled - a negative
 | 
				
			||||||
			// GroupCreationResult might have been returned
 | 
								// GroupCreationResult might have been returned
 | 
				
			||||||
			if (!newGroupButton.isDisabled()) newGroupButton.setDisable(!online);
 | 
								if (!newGroupButton.isDisabled()) newGroupButton.setDisable(!online);
 | 
				
			||||||
@@ -251,8 +257,19 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
			.ifPresent(msg -> Platform.runLater(messageList::refresh));
 | 
								.ifPresent(msg -> Platform.runLater(messageList::refresh));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event(eventType = UserStatusChange.class)
 | 
						@Event
 | 
				
			||||||
	private void onUserStatusChange() { Platform.runLater(chatList::refresh); }
 | 
						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
 | 
						@Event
 | 
				
			||||||
	private void onContactOperation(ContactOperation operation) {
 | 
						private void onContactOperation(ContactOperation operation) {
 | 
				
			||||||
@@ -297,6 +314,7 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
		chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
 | 
							chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
 | 
				
			||||||
		messageList.setCellFactory(MessageListCell::new);
 | 
							messageList.setCellFactory(MessageListCell::new);
 | 
				
			||||||
		// TODO: cache image
 | 
							// TODO: cache image
 | 
				
			||||||
 | 
							if (currentChat != null)
 | 
				
			||||||
			if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
 | 
								if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
 | 
				
			||||||
			else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
 | 
								else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -359,6 +377,7 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
				recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
 | 
									recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				topBarStatusLabel.setText(currentChat.getRecipient().getContacts().size() + " members");
 | 
									topBarStatusLabel.setText(currentChat.getRecipient().getContacts().size() + " members");
 | 
				
			||||||
 | 
									topBarStatusLabel.getStyleClass().clear();
 | 
				
			||||||
				recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
 | 
									recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			final var clip = new Rectangle();
 | 
								final var clip = new Rectangle();
 | 
				
			||||||
@@ -699,6 +718,21 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
		attachmentView.setVisible(visible);
 | 
							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
 | 
						// Context menu actions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,6 @@ import javafx.scene.control.*;
 | 
				
			|||||||
import javafx.scene.layout.HBox;
 | 
					import javafx.scene.layout.HBox;
 | 
				
			||||||
import javafx.stage.DirectoryChooser;
 | 
					import javafx.stage.DirectoryChooser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Context;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Displays options for downloading {@link envoy.data.Attachment}s.
 | 
					 * Displays options for downloading {@link envoy.data.Attachment}s.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
@@ -47,7 +45,7 @@ public final class DownloadSettingsPane extends SettingsPane {
 | 
				
			|||||||
			final var directoryChooser = new DirectoryChooser();
 | 
								final var directoryChooser = new DirectoryChooser();
 | 
				
			||||||
			directoryChooser.setTitle("Select the directory where attachments should be saved to");
 | 
								directoryChooser.setTitle("Select the directory where attachments should be saved to");
 | 
				
			||||||
			directoryChooser.setInitialDirectory(settings.getDownloadLocation());
 | 
								directoryChooser.setInitialDirectory(settings.getDownloadLocation());
 | 
				
			||||||
			final var selectedDirectory = directoryChooser.showDialog(Context.getInstance().getSceneContext().getStage());
 | 
								final var selectedDirectory = directoryChooser.showDialog(context.getSceneContext().getStage());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (selectedDirectory != null) {
 | 
								if (selectedDirectory != null) {
 | 
				
			||||||
				currentPath.setText(selectedDirectory.getAbsolutePath());
 | 
									currentPath.setText(selectedDirectory.getAbsolutePath());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,8 +4,8 @@ import javafx.scene.control.*;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.SettingsItem;
 | 
					import envoy.client.data.SettingsItem;
 | 
				
			||||||
import envoy.client.event.ThemeChangeEvent;
 | 
					import envoy.client.event.ThemeChangeEvent;
 | 
				
			||||||
import envoy.client.helper.ShutdownHelper;
 | 
					 | 
				
			||||||
import envoy.client.ui.StatusTrayIcon;
 | 
					import envoy.client.ui.StatusTrayIcon;
 | 
				
			||||||
 | 
					import envoy.client.util.UserUtil;
 | 
				
			||||||
import envoy.data.User.UserStatus;
 | 
					import envoy.data.User.UserStatus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.eventbus.EventBus;
 | 
					import dev.kske.eventbus.EventBus;
 | 
				
			||||||
@@ -57,14 +57,13 @@ public final class GeneralSettingsPane extends SettingsPane {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		final var statusComboBox = new ComboBox<UserStatus>();
 | 
							final var statusComboBox = new ComboBox<UserStatus>();
 | 
				
			||||||
		statusComboBox.getItems().setAll(UserStatus.values());
 | 
							statusComboBox.getItems().setAll(UserStatus.values());
 | 
				
			||||||
		statusComboBox.setValue(UserStatus.ONLINE);
 | 
							statusComboBox.setValue(context.getLocalDB().getUser().getStatus());
 | 
				
			||||||
		statusComboBox.setTooltip(new Tooltip("Change your current status"));
 | 
							statusComboBox.setTooltip(new Tooltip("Change your current status"));
 | 
				
			||||||
		// TODO add action when value is changed
 | 
							statusComboBox.setOnAction(e -> UserUtil.changeStatus(statusComboBox.getValue()));
 | 
				
			||||||
		statusComboBox.setOnAction(e -> {});
 | 
					 | 
				
			||||||
		getChildren().add(statusComboBox);
 | 
							getChildren().add(statusComboBox);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		final var logoutButton = new Button("Logout");
 | 
							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");
 | 
							final var logoutTooltip = new Tooltip("Brings you back to the login screen and removes \"remember me\" status from this account");
 | 
				
			||||||
		logoutTooltip.setWrapText(true);
 | 
							logoutTooltip.setWrapText(true);
 | 
				
			||||||
		logoutButton.setTooltip(logoutTooltip);
 | 
							logoutButton.setTooltip(logoutTooltip);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,6 @@ import javafx.scene.control.*;
 | 
				
			|||||||
import javafx.scene.layout.*;
 | 
					import javafx.scene.layout.*;
 | 
				
			||||||
import javafx.scene.paint.Color;
 | 
					import javafx.scene.paint.Color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Context;
 | 
					 | 
				
			||||||
import envoy.client.net.Client;
 | 
					import envoy.client.net.Client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -20,7 +19,7 @@ import envoy.client.net.Client;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public abstract class OnlineOnlySettingsPane extends SettingsPane {
 | 
					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.");
 | 
						private final Tooltip beOnlineReminder = new Tooltip("You need to be online to modify your account.");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@ package envoy.client.ui.settings;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import javafx.scene.layout.VBox;
 | 
					import javafx.scene.layout.VBox;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Settings;
 | 
					import envoy.client.data.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
@@ -13,6 +13,7 @@ public abstract class SettingsPane extends VBox {
 | 
				
			|||||||
	protected String title;
 | 
						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; }
 | 
						protected SettingsPane(String title) { this.title = title; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,6 @@ import javafx.scene.input.InputEvent;
 | 
				
			|||||||
import javafx.scene.layout.HBox;
 | 
					import javafx.scene.layout.HBox;
 | 
				
			||||||
import javafx.stage.FileChooser;
 | 
					import javafx.stage.FileChooser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Context;
 | 
					 | 
				
			||||||
import envoy.client.ui.control.ProfilePicImageView;
 | 
					import envoy.client.ui.control.ProfilePicImageView;
 | 
				
			||||||
import envoy.client.util.IconUtil;
 | 
					import envoy.client.util.IconUtil;
 | 
				
			||||||
import envoy.event.*;
 | 
					import envoy.event.*;
 | 
				
			||||||
@@ -66,7 +65,7 @@ public final class UserSettingsPane extends OnlineOnlySettingsPane {
 | 
				
			|||||||
			pictureChooser.setInitialDirectory(new File(System.getProperty("user.home")));
 | 
								pictureChooser.setInitialDirectory(new File(System.getProperty("user.home")));
 | 
				
			||||||
			pictureChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"));
 | 
								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) {
 | 
								if (file != null) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										64
									
								
								client/src/main/java/envoy/client/util/UserUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								client/src/main/java/envoy/client/util/UserUtil.java
									
									
									
									
									
										Normal file
									
								
							@@ -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));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -6,7 +6,7 @@
 | 
				
			|||||||
	-fx-background-radius: 15.0px;
 | 
						-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;
 | 
					    -fx-background-color: #454c4f;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,7 +18,7 @@
 | 
				
			|||||||
	-fx-background-color: lightgray;
 | 
						-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;
 | 
						-fx-background-color: #222222;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,9 +22,10 @@
 | 
				
			|||||||
<?import javafx.scene.text.Font?>
 | 
					<?import javafx.scene.text.Font?>
 | 
				
			||||||
<?import javafx.stage.Screen?>
 | 
					<?import javafx.stage.Screen?>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<GridPane fx:id="scene" maxHeight="-Infinity"
 | 
					<GridPane maxHeight="-Infinity" maxWidth="-Infinity"
 | 
				
			||||||
	maxWidth="-Infinity" minHeight="400.0" minWidth="500.0"
 | 
						minHeight="400.0" minWidth="500.0"
 | 
				
			||||||
	prefHeight="${screen.visualBounds.height}" prefWidth="${screen.visualBounds.width}"
 | 
						prefHeight="${screen.visualBounds.height}"
 | 
				
			||||||
 | 
						prefWidth="${screen.visualBounds.width}"
 | 
				
			||||||
	xmlns="http://javafx.com/javafx/11.0.1"
 | 
						xmlns="http://javafx.com/javafx/11.0.1"
 | 
				
			||||||
	xmlns:fx="http://javafx.com/fxml/1"
 | 
						xmlns:fx="http://javafx.com/fxml/1"
 | 
				
			||||||
	fx:controller="envoy.client.ui.controller.ChatScene">
 | 
						fx:controller="envoy.client.ui.controller.ChatScene">
 | 
				
			||||||
@@ -160,44 +161,25 @@
 | 
				
			|||||||
					fitHeight="43.0" fitWidth="43.0" pickOnBounds="true"
 | 
										fitHeight="43.0" fitWidth="43.0" pickOnBounds="true"
 | 
				
			||||||
					preserveRatio="true">
 | 
										preserveRatio="true">
 | 
				
			||||||
					<HBox.margin>
 | 
										<HBox.margin>
 | 
				
			||||||
						<Insets left="15.0" top="5.0" />
 | 
											<Insets left="15.0" top="5.0" right="10.0" />
 | 
				
			||||||
					</HBox.margin>
 | 
										</HBox.margin>
 | 
				
			||||||
				</ImageView>
 | 
									</ImageView>
 | 
				
			||||||
				<Label id="transparent-background" fx:id="contactLabel"
 | 
									<HBox id="transparent-background" fx:id="ownContactControl">
 | 
				
			||||||
					prefHeight="27.0" prefWidth="134.0">
 | 
					 | 
				
			||||||
					<padding>
 | 
					 | 
				
			||||||
						<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
 | 
					 | 
				
			||||||
					</padding>
 | 
					 | 
				
			||||||
					<font>
 | 
					 | 
				
			||||||
						<Font size="18.0" />
 | 
					 | 
				
			||||||
					</font>
 | 
					 | 
				
			||||||
					<HBox.margin>
 | 
					 | 
				
			||||||
						<Insets left="10.0" top="5.0" />
 | 
					 | 
				
			||||||
					</HBox.margin>
 | 
					 | 
				
			||||||
				</Label>
 | 
					 | 
				
			||||||
				<Region id="transparent-background" prefHeight="77.0"
 | 
					 | 
				
			||||||
					prefWidth="115.0" />
 | 
					 | 
				
			||||||
				<VBox id="transparent-background" alignment="CENTER_RIGHT"
 | 
					 | 
				
			||||||
					prefHeight="200.0" prefWidth="100.0" spacing="5.0">
 | 
					 | 
				
			||||||
					<children>
 | 
										<children>
 | 
				
			||||||
						<Button fx:id="settingsButton" mnemonicParsing="true"
 | 
											<Region id="transparent-background" prefWidth="120"
 | 
				
			||||||
 | 
												fx:id="spaceBetweenUserAndSettingsButton" />
 | 
				
			||||||
 | 
											<Button fx:id="settingsButton" mnemonicParsing="false"
 | 
				
			||||||
							onAction="#settingsButtonClicked" prefHeight="30.0"
 | 
												onAction="#settingsButtonClicked" prefHeight="30.0"
 | 
				
			||||||
							prefWidth="30.0" text="">
 | 
												prefWidth="30.0" text="" alignment="CENTER">
 | 
				
			||||||
							<padding>
 | 
												<padding>
 | 
				
			||||||
								<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
 | 
													<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
 | 
				
			||||||
							</padding>
 | 
												</padding>
 | 
				
			||||||
							<VBox.margin>
 | 
												<HBox.margin>
 | 
				
			||||||
								<Insets />
 | 
													<Insets bottom="35.0" left="5.0" top="35.0"/>
 | 
				
			||||||
							</VBox.margin>
 | 
												</HBox.margin>
 | 
				
			||||||
						</Button>
 | 
											</Button>
 | 
				
			||||||
					</children>
 | 
										</children>
 | 
				
			||||||
					<HBox.margin>
 | 
									</HBox>
 | 
				
			||||||
						<Insets right="10.0" />
 | 
					 | 
				
			||||||
					</HBox.margin>
 | 
					 | 
				
			||||||
					<opaqueInsets>
 | 
					 | 
				
			||||||
						<Insets />
 | 
					 | 
				
			||||||
					</opaqueInsets>
 | 
					 | 
				
			||||||
				</VBox>
 | 
					 | 
				
			||||||
			</children>
 | 
								</children>
 | 
				
			||||||
			<GridPane.margin>
 | 
								<GridPane.margin>
 | 
				
			||||||
				<Insets bottom="1.0" right="1.0" />
 | 
									<Insets bottom="1.0" right="1.0" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,14 +22,14 @@ public final class ConnectionManager implements ISocketIdListener {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Server Standalone v0.1-alpha
 | 
						 * @since Envoy Server Standalone v0.1-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private Set<Long> pendingSockets = new HashSet<>();
 | 
						private final Set<Long> pendingSockets = new HashSet<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Contains all socket IDs that have acquired a user ID as keys to these IDs.
 | 
						 * Contains all socket IDs that have acquired a user ID as keys to these IDs.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Server Standalone v0.1-alpha
 | 
						 * @since Envoy Server Standalone v0.1-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private Map<Long, Long> sockets = new HashMap<>();
 | 
						private final Map<Long, Long> sockets = new HashMap<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static ConnectionManager connectionManager = new ConnectionManager();
 | 
						private static ConnectionManager connectionManager = new ConnectionManager();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -44,11 +44,11 @@ public final class ConnectionManager implements ISocketIdListener {
 | 
				
			|||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void socketCancelled(long socketID) {
 | 
						public void socketCancelled(long socketID) {
 | 
				
			||||||
		if (!pendingSockets.remove(socketID)) {
 | 
							if (!pendingSockets.remove(socketID)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Notify contacts of this users offline-going
 | 
								// Notify contacts of this users offline-going
 | 
				
			||||||
			envoy.server.data.User user = PersistenceManager.getInstance().getUserByID(getUserIDBySocketID(socketID));
 | 
								final envoy.server.data.User user = PersistenceManager.getInstance().getUserByID(getUserIDBySocketID(socketID));
 | 
				
			||||||
			user.setStatus(UserStatus.OFFLINE);
 | 
					 | 
				
			||||||
			user.setLastSeen(Instant.now());
 | 
								user.setLastSeen(Instant.now());
 | 
				
			||||||
			UserStatusChangeProcessor.updateUserStatus(user);
 | 
								UserStatusChangeProcessor.updateUserStatus(user, UserStatus.OFFLINE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Remove the socket
 | 
								// Remove the socket
 | 
				
			||||||
			sockets.entrySet().removeIf(e -> e.getValue() == socketID);
 | 
								sockets.entrySet().removeIf(e -> e.getValue() == socketID);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -46,8 +46,7 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Acquire a user object (or reject the handshake if that's impossible)
 | 
							// Acquire a user object (or reject the handshake if that's impossible)
 | 
				
			||||||
		User user = null;
 | 
							User user = null;
 | 
				
			||||||
		if (!credentials.isRegistration()) {
 | 
							if (!credentials.isRegistration()) try {
 | 
				
			||||||
			try {
 | 
					 | 
				
			||||||
			user = persistenceManager.getUserByName(credentials.getIdentifier());
 | 
								user = persistenceManager.getUserByName(credentials.getIdentifier());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Check if the user is already online
 | 
								// Check if the user is already online
 | 
				
			||||||
@@ -67,7 +66,7 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
 | 
				
			|||||||
					writeProxy.write(socketID, new HandshakeRejection(INVALID_TOKEN));
 | 
										writeProxy.write(socketID, new HandshakeRejection(INVALID_TOKEN));
 | 
				
			||||||
					return;
 | 
										return;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				} else {
 | 
								} else
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Check the password hash
 | 
									// Check the password hash
 | 
				
			||||||
				if (!PasswordUtil.validate(credentials.getPassword(), user.getPasswordHash())) {
 | 
									if (!PasswordUtil.validate(credentials.getPassword(), user.getPasswordHash())) {
 | 
				
			||||||
@@ -75,13 +74,12 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
 | 
				
			|||||||
					writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
 | 
										writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
 | 
				
			||||||
					return;
 | 
										return;
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				}
 | 
							} catch (final NoResultException e) {
 | 
				
			||||||
			} catch (NoResultException e) {
 | 
					 | 
				
			||||||
			logger.info("The requested user does not exist.");
 | 
								logger.info("The requested user does not exist.");
 | 
				
			||||||
			writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
 | 
								writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		} else {
 | 
							else {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Validate user name
 | 
								// Validate user name
 | 
				
			||||||
			if (!Bounds.isValidContactName(credentials.getIdentifier())) {
 | 
								if (!Bounds.isValidContactName(credentials.getIdentifier())) {
 | 
				
			||||||
@@ -98,7 +96,7 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
 | 
				
			|||||||
				logger.info("The requested user already exists.");
 | 
									logger.info("The requested user already exists.");
 | 
				
			||||||
				writeProxy.write(socketID, new HandshakeRejection(USERNAME_TAKEN));
 | 
									writeProxy.write(socketID, new HandshakeRejection(USERNAME_TAKEN));
 | 
				
			||||||
				return;
 | 
									return;
 | 
				
			||||||
			} catch (NoResultException e) {
 | 
								} catch (final NoResultException e) {
 | 
				
			||||||
				// Creation of a new user
 | 
									// Creation of a new user
 | 
				
			||||||
				user = new User();
 | 
									user = new User();
 | 
				
			||||||
				user.setName(credentials.getIdentifier());
 | 
									user.setName(credentials.getIdentifier());
 | 
				
			||||||
@@ -115,18 +113,17 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
 | 
				
			|||||||
		connectionManager.registerUser(user.getID(), socketID);
 | 
							connectionManager.registerUser(user.getID(), socketID);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Change status and notify contacts about it
 | 
							// Change status and notify contacts about it
 | 
				
			||||||
		user.setStatus(ONLINE);
 | 
							UserStatusChangeProcessor.updateUserStatus(user, ONLINE);
 | 
				
			||||||
		UserStatusChangeProcessor.updateUserStatus(user);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Process token request
 | 
							// Process token request
 | 
				
			||||||
		if (credentials.requestToken()) {
 | 
							if (credentials.requestToken()) {
 | 
				
			||||||
			String token;
 | 
								String token;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (user.getAuthToken() != null && user.getAuthTokenExpiration().isAfter(Instant.now())) {
 | 
								if (user.getAuthToken() != null && user.getAuthTokenExpiration().isAfter(Instant.now()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Reuse existing token and delay expiration date
 | 
									// Reuse existing token and delay expiration date
 | 
				
			||||||
				token = user.getAuthToken();
 | 
									token = user.getAuthToken();
 | 
				
			||||||
			} else {
 | 
								else {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Generate new token
 | 
									// Generate new token
 | 
				
			||||||
				token = AuthTokenGenerator.nextToken();
 | 
									token = AuthTokenGenerator.nextToken();
 | 
				
			||||||
@@ -141,13 +138,13 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
 | 
				
			|||||||
		pendingMessages.removeIf(GroupMessage.class::isInstance);
 | 
							pendingMessages.removeIf(GroupMessage.class::isInstance);
 | 
				
			||||||
		logger.fine("Sending " + pendingMessages.size() + " pending messages to " + user + "...");
 | 
							logger.fine("Sending " + pendingMessages.size() + " pending messages to " + user + "...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (var msg : pendingMessages) {
 | 
							for (final var msg : pendingMessages) {
 | 
				
			||||||
			final var msgCommon = msg.toCommon();
 | 
								final var msgCommon = msg.toCommon();
 | 
				
			||||||
			if (msg.getCreationDate().isAfter(credentials.getLastSync())) {
 | 
								if (msg.getCreationDate().isAfter(credentials.getLastSync()))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Sync without side effects
 | 
									// Sync without side effects
 | 
				
			||||||
				writeProxy.write(socketID, msgCommon);
 | 
									writeProxy.write(socketID, msgCommon);
 | 
				
			||||||
			} else if (msg.getStatus() == SENT) {
 | 
								else if (msg.getStatus() == SENT) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Send the message
 | 
									// Send the message
 | 
				
			||||||
				writeProxy.write(socketID, msgCommon);
 | 
									writeProxy.write(socketID, msgCommon);
 | 
				
			||||||
@@ -162,10 +159,10 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
 | 
				
			|||||||
			} else writeProxy.write(socketID, new MessageStatusChange(msgCommon));
 | 
								} else writeProxy.write(socketID, new MessageStatusChange(msgCommon));
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		List<GroupMessage> pendingGroupMessages = PersistenceManager.getInstance().getPendingGroupMessages(user, credentials.getLastSync());
 | 
							final List<GroupMessage> pendingGroupMessages = PersistenceManager.getInstance().getPendingGroupMessages(user, credentials.getLastSync());
 | 
				
			||||||
		logger.fine("Sending " + pendingGroupMessages.size() + " pending group messages to " + user + "...");
 | 
							logger.fine("Sending " + pendingGroupMessages.size() + " pending group messages to " + user + "...");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (var gmsg : pendingGroupMessages) {
 | 
							for (final var gmsg : pendingGroupMessages) {
 | 
				
			||||||
			final var gmsgCommon = gmsg.toCommon();
 | 
								final var gmsgCommon = gmsg.toCommon();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Deliver the message to the user if he hasn't received it yet
 | 
								// Deliver the message to the user if he hasn't received it yet
 | 
				
			||||||
@@ -189,20 +186,18 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					PersistenceManager.getInstance().updateMessage(gmsg);
 | 
										PersistenceManager.getInstance().updateMessage(gmsg);
 | 
				
			||||||
				} else {
 | 
									} else
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					// Just send the message without updating if it was received in the past
 | 
										// Just send the message without updating if it was received in the past
 | 
				
			||||||
					writeProxy.write(socketID, gmsgCommon);
 | 
										writeProxy.write(socketID, gmsgCommon);
 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Sending group message status changes
 | 
									// Sending group message status changes
 | 
				
			||||||
				if (gmsg.getStatus() == SENT && gmsg.getLastStatusChangeDate().isAfter(gmsg.getCreationDate())
 | 
									if (gmsg.getStatus() == SENT && gmsg.getLastStatusChangeDate().isAfter(gmsg.getCreationDate())
 | 
				
			||||||
						|| gmsg.getStatus() == RECEIVED && gmsg.getLastStatusChangeDate().isAfter(gmsg.getReceivedDate())) {
 | 
											|| gmsg.getStatus() == RECEIVED && gmsg.getLastStatusChangeDate().isAfter(gmsg.getReceivedDate()))
 | 
				
			||||||
					gmsg.getMemberMessageStatus()
 | 
										gmsg.getMemberMessageStatus()
 | 
				
			||||||
						.forEach((memberID, memberStatus) -> writeProxy.write(socketID,
 | 
											.forEach((memberID, memberStatus) -> writeProxy.write(socketID,
 | 
				
			||||||
								new GroupMessageStatusChange(gmsg.getID(), memberStatus, gmsg.getLastStatusChangeDate(), memberID)));
 | 
													new GroupMessageStatusChange(gmsg.getID(), memberStatus, gmsg.getLastStatusChangeDate(), memberID)));
 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Deliver just a status change instead of the whole message
 | 
									// Deliver just a status change instead of the whole message
 | 
				
			||||||
				if (gmsg.getStatus() == RECEIVED && user.getLastSeen().isBefore(gmsg.getReceivedDate())
 | 
									if (gmsg.getStatus() == RECEIVED && user.getLastSeen().isBefore(gmsg.getReceivedDate())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,18 +28,20 @@ public final class UserStatusChangeProcessor implements ObjectProcessor<UserStat
 | 
				
			|||||||
			logger.warning("Received an unnecessary UserStatusChange");
 | 
								logger.warning("Received an unnecessary UserStatusChange");
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		updateUserStatus(input);
 | 
							updateUserStatus(persistenceManager.getUserByID(input.getID()), input.get());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Sets the {@link UserStatus} for a given user. Both offline contacts and
 | 
						 * Sets the {@link UserStatus} for a given user. Both offline contacts and
 | 
				
			||||||
	 * currently online contacts are notified.
 | 
						 * currently online contacts are notified.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param user the {@link UserStatusChange} that signals the change
 | 
						 * @param user      the user whose status has changed
 | 
				
			||||||
 | 
						 * @param newStatus the new status of that user
 | 
				
			||||||
	 * @since Envoy Server Standalone v0.1-alpha
 | 
						 * @since Envoy Server Standalone v0.1-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static void updateUserStatus(User user) {
 | 
						public static void updateUserStatus(User user, UserStatus newStatus) {
 | 
				
			||||||
 | 
							user.setStatus(newStatus);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Handling for newly logged in clients
 | 
							// Handling for newly logged in clients
 | 
				
			||||||
		persistenceManager.updateContact(user);
 | 
							persistenceManager.updateContact(user);
 | 
				
			||||||
@@ -48,12 +50,6 @@ public final class UserStatusChangeProcessor implements ObjectProcessor<UserStat
 | 
				
			|||||||
		writeProxy.writeToOnlineContacts(user.getContacts(), new UserStatusChange(user.getID(), user.getStatus()));
 | 
							writeProxy.writeToOnlineContacts(user.getContacts(), new UserStatusChange(user.getID(), user.getStatus()));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * @param evt the {@link UserStatusChange}
 | 
					 | 
				
			||||||
	 * @since Envoy Server Standalone v0.1-alpha
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	public static void updateUserStatus(UserStatusChange evt) { updateUserStatus(persistenceManager.getUserByID(evt.getID())); }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * This method is only called by the LoginCredentialProcessor because every
 | 
						 * This method is only called by the LoginCredentialProcessor because every
 | 
				
			||||||
	 * user needs to login (open a socket) before changing his status.
 | 
						 * user needs to login (open a socket) before changing his status.
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user