Merge remote-tracking branch 'origin/develop' into
enhanced-shortcut-mechanism Conflicts: client/src/main/java/envoy/client/ui/SceneContext.java
This commit is contained in:
commit
31e27ae2e0
@ -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,8 +314,9 @@ 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.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
if (currentChat != null)
|
||||||
else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
|
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)
|
@Event(eventType = Logout.class, priority = 200)
|
||||||
@ -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
|
||||||
@ -12,7 +12,8 @@ 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,14 +22,15 @@
|
|||||||
<?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">
|
||||||
<fx:define>
|
<fx:define>
|
||||||
<Screen fx:factory="getPrimary" fx:id="screen" />
|
<Screen fx:factory="getPrimary" fx:id="screen" />
|
||||||
</fx:define>
|
</fx:define>
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints hgrow="NEVER"
|
<ColumnConstraints hgrow="NEVER"
|
||||||
@ -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,42 +46,40 @@ 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
|
||||||
if (connectionManager.isOnline(user.getID())) {
|
if (connectionManager.isOnline(user.getID())) {
|
||||||
logger.warning(user + " is already online!");
|
logger.warning(user + " is already online!");
|
||||||
writeProxy.write(socketID, new HandshakeRejection(INTERNAL_ERROR));
|
writeProxy.write(socketID, new HandshakeRejection(INTERNAL_ERROR));
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authenticate with password or token
|
|
||||||
if (credentials.usesToken()) {
|
|
||||||
|
|
||||||
// Check the token
|
|
||||||
if (user.getAuthToken() == null || user.getAuthTokenExpiration().isBefore(Instant.now())
|
|
||||||
|| !user.getAuthToken().equals(credentials.getPassword())) {
|
|
||||||
logger.info(user + " tried to use an invalid token.");
|
|
||||||
writeProxy.write(socketID, new HandshakeRejection(INVALID_TOKEN));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Check the password hash
|
|
||||||
if (!PasswordUtil.validate(credentials.getPassword(), user.getPasswordHash())) {
|
|
||||||
logger.info(user + " has entered the wrong password.");
|
|
||||||
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (NoResultException e) {
|
|
||||||
logger.info("The requested user does not exist.");
|
|
||||||
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
// Authenticate with password or token
|
||||||
|
if (credentials.usesToken()) {
|
||||||
|
|
||||||
|
// Check the token
|
||||||
|
if (user.getAuthToken() == null || user.getAuthTokenExpiration().isBefore(Instant.now())
|
||||||
|
|| !user.getAuthToken().equals(credentials.getPassword())) {
|
||||||
|
logger.info(user + " tried to use an invalid token.");
|
||||||
|
writeProxy.write(socketID, new HandshakeRejection(INVALID_TOKEN));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
|
||||||
|
// Check the password hash
|
||||||
|
if (!PasswordUtil.validate(credentials.getPassword(), user.getPasswordHash())) {
|
||||||
|
logger.info(user + " has entered the wrong password.");
|
||||||
|
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (final NoResultException e) {
|
||||||
|
logger.info("The requested user does not exist.");
|
||||||
|
writeProxy.write(socketID, new HandshakeRejection(WRONG_PASSWORD_OR_USER));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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