Contact Deletion #97
@ -27,6 +27,8 @@ public class Chat implements Serializable {
|
||||
|
||||
protected int unreadAmount;
|
||||
|
||||
private boolean disabled;
|
||||
kske marked this conversation as resolved
|
||||
|
||||
/**
|
||||
* Stores the last time an {@link envoy.event.IsTyping} event has been sent.
|
||||
*/
|
||||
@ -168,4 +170,22 @@ public class Chat implements Serializable {
|
||||
* @since Envoy Client v0.2-beta
|
||||
*/
|
||||
public void lastWritingEventWasNow() { lastWritingEvent = System.currentTimeMillis(); }
|
||||
|
||||
/**
|
||||
* Determines whether messages can be sent in this chat. Should be true i.e. for
|
||||
* chats whose recipient deleted this client as a contact.
|
||||
*
|
||||
* @return whether this {@code Chat} has been disabled
|
||||
* @since Envoy Client v0.3-beta
|
||||
*/
|
||||
public boolean isDisabled() { return disabled; }
|
||||
|
||||
/**
|
||||
* Determines whether messages can be sent in this chat. Should be true i.e. for
|
||||
* chats whose recipient deleted this client as a contact.
|
||||
*
|
||||
* @param disabled the disabled to set
|
||||
* @since Envoy Client v0.3-beta
|
||||
*/
|
||||
delvh marked this conversation as resolved
kske
commented
Doesn't sound very conclusive to me. Doesn't sound very conclusive to me.
|
||||
public void setDisabled(boolean disabled) { this.disabled = disabled; }
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ public final class GroupChat extends Chat {
|
||||
* @param recipient the group whose members receive the messages
|
||||
* @since Envoy Client v0.1-beta
|
||||
*/
|
||||
public GroupChat(User sender, Contact recipient) {
|
||||
public GroupChat(User sender, Group recipient) {
|
||||
super(recipient);
|
||||
this.sender = sender;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import envoy.client.event.*;
|
||||
import envoy.data.*;
|
||||
import envoy.data.Message.MessageStatus;
|
||||
import envoy.event.*;
|
||||
import envoy.event.contact.*;
|
||||
import envoy.exception.EnvoyException;
|
||||
import envoy.util.*;
|
||||
|
||||
@ -39,6 +40,7 @@ public final class LocalDB implements EventListener {
|
||||
private IDGenerator idGenerator;
|
||||
private CacheMap cacheMap = new CacheMap();
|
||||
private String authToken;
|
||||
private Set<? extends Contact> originalContacts = Set.of();
|
||||
|
||||
// Auto save timer
|
||||
private Timer autoSaver;
|
||||
@ -163,7 +165,7 @@ public final class LocalDB implements EventListener {
|
||||
user.getContacts()
|
||||
.stream()
|
||||
.filter(c -> !c.equals(user) && getChat(c.getID()).isEmpty())
|
||||
.map(c -> c instanceof User ? new Chat(c) : new GroupChat(user, c))
|
||||
.map(c -> c instanceof User ? new Chat(c) : new GroupChat(user, (Group) c))
|
||||
.forEach(chats::add);
|
||||
}
|
||||
|
||||
@ -197,7 +199,7 @@ public final class LocalDB implements EventListener {
|
||||
*/
|
||||
@Event(eventType = EnvoyCloseEvent.class, priority = 1000)
|
||||
private synchronized void save() {
|
||||
EnvoyLog.getLogger(LocalDB.class).log(Level.INFO, "Saving local database...");
|
||||
EnvoyLog.getLogger(LocalDB.class).log(Level.FINER, "Saving local database...");
|
||||
|
||||
// Save users
|
||||
try {
|
||||
@ -240,6 +242,31 @@ public final class LocalDB implements EventListener {
|
||||
getChat(evt.getID()).map(Chat::getRecipient).map(User.class::cast).ifPresent(u -> u.setStatus(evt.get()));
|
||||
}
|
||||
|
||||
@Event(priority = 200)
|
||||
private void onUserOperation(UserOperation operation) {
|
||||
final var eventUser = operation.get();
|
||||
switch (operation.getOperationType()) {
|
||||
case ADD:
|
||||
getUsers().put(eventUser.getName(), eventUser);
|
||||
Platform.runLater(() -> chats.add(0, new Chat(eventUser)));
|
||||
break;
|
||||
case REMOVE:
|
||||
getChat(eventUser.getID()).ifPresent(chat -> chat.setDisabled(true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Event
|
||||
private void onGroupCreationResult(GroupCreationResult evt) {
|
||||
final var newGroup = evt.get();
|
||||
|
||||
// The group creation was not successful
|
||||
if (newGroup == null) return;
|
||||
|
||||
// The group was successfully created
|
||||
else Platform.runLater(() -> chats.add(new GroupChat(user, newGroup)));
|
||||
}
|
||||
|
||||
@Event(priority = 150)
|
||||
private void onGroupResize(GroupResize evt) { getChat(evt.getGroupID()).map(Chat::getRecipient).map(Group.class::cast).ifPresent(evt::apply); }
|
||||
|
||||
@ -296,6 +323,9 @@ public final class LocalDB implements EventListener {
|
||||
@Event(priority = 500)
|
||||
private void onOwnStatusChange(OwnStatusChange statusChange) { user.setStatus(statusChange.get()); }
|
||||
|
||||
@Event(eventType = ContactsChangedSinceLastLogin.class, priority = 500)
|
||||
private void onContactsChangedSinceLastLogin() { if (user != null) originalContacts = user.getContacts(); }
|
||||
|
||||
/**
|
||||
* @return a {@code Map<String, User>} of all users stored locally with their
|
||||
* user names as keys
|
||||
@ -323,6 +353,25 @@ public final class LocalDB implements EventListener {
|
||||
*/
|
||||
public Optional<Chat> getChat(long recipientID) { return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny(); }
|
||||
|
||||
/**
|
||||
* Sets the given user as base of this {@code LocalDB}.
|
||||
* Additionally compares his contacts with the ones returned by the server, in
|
||||
* case they might have been deleted.
|
||||
*
|
||||
* @param user the user to set
|
||||
* @since Envoy Client v0.2-alpha
|
||||
*/
|
||||
public void setUserAndMergeContacts(User user) {
|
||||
this.user = user;
|
||||
if (originalContacts.isEmpty()) return;
|
||||
else {
|
||||
|
||||
// mark all chats of deleted contacts
|
||||
originalContacts.removeAll(user.getContacts());
|
||||
originalContacts.forEach(contact -> getChat(contact.getID()).ifPresent(chat -> chat.setDisabled(true)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return all saved {@link Chat} objects that list the client user as the
|
||||
* sender
|
||||
|
@ -9,6 +9,7 @@ import envoy.client.data.*;
|
||||
import envoy.client.event.EnvoyCloseEvent;
|
||||
import envoy.data.*;
|
||||
import envoy.event.*;
|
||||
import envoy.event.contact.ContactsChangedSinceLastLogin;
|
||||
import envoy.util.*;
|
||||
|
||||
import dev.kske.eventbus.*;
|
||||
@ -29,6 +30,7 @@ public final class Client implements EventListener, Closeable {
|
||||
private Socket socket;
|
||||
private Receiver receiver;
|
||||
private boolean online;
|
||||
private boolean isHandShake;
|
||||
|
||||
// Asynchronously initialized during handshake
|
||||
private volatile User sender;
|
||||
@ -61,6 +63,7 @@ public final class Client implements EventListener, Closeable {
|
||||
*/
|
||||
public void performHandshake(LoginCredentials credentials, CacheMap cacheMap) throws TimeoutException, IOException, InterruptedException {
|
||||
if (online) throw new IllegalStateException("Handshake has already been performed successfully");
|
||||
isHandShake = true;
|
||||
|
||||
// Establish TCP connection
|
||||
logger.log(Level.FINER, String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort()));
|
||||
@ -92,14 +95,19 @@ public final class Client implements EventListener, Closeable {
|
||||
if (rejected) {
|
||||
socket.close();
|
||||
receiver.removeAllProcessors();
|
||||
isHandShake = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() - start > 5000) throw new TimeoutException("Did not log in after 5 seconds");
|
||||
if (System.currentTimeMillis() - start > 5000) {
|
||||
isHandShake = false;
|
||||
throw new TimeoutException("Did not log in after 5 seconds");
|
||||
}
|
||||
Thread.sleep(500);
|
||||
}
|
||||
|
||||
online = true;
|
||||
isHandShake = false;
|
||||
logger.log(Level.INFO, "Handshake completed.");
|
||||
}
|
||||
|
||||
@ -146,7 +154,7 @@ public final class Client implements EventListener, Closeable {
|
||||
logger.log(Level.FINE, "Sending " + obj);
|
||||
try {
|
||||
SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
|
||||
} catch (IOException e) {
|
||||
} catch (final IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
@ -174,7 +182,20 @@ public final class Client implements EventListener, Closeable {
|
||||
}
|
||||
|
||||
@Event(eventType = HandshakeRejection.class, priority = 1000)
|
||||
private void onHandshakeRejection() { rejected = true; }
|
||||
private void onHandshakeRejection() {
|
||||
rejected = true;
|
||||
isHandShake = false;
|
||||
}
|
||||
|
||||
@Event(eventType = ContactsChangedSinceLastLogin.class, priority = 1000)
|
||||
private void onContactsChangedSinceLastLogin() {
|
||||
if (!isHandShake) {
|
||||
logger.log(Level.WARNING, "Received a request to check contacts while not in the handshake");
|
||||
|
||||
// TODO: consume event once event consumption is implemented
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Event(eventType = EnvoyCloseEvent.class, priority = 800)
|
||||
|
@ -179,7 +179,7 @@ public final class Startup extends Application {
|
||||
|
||||
// Set client user in local database
|
||||
final var user = client.getSender();
|
||||
localDB.setUser(user);
|
||||
localDB.setUserAndMergeContacts(user);
|
||||
|
||||
// Initialize chats in local database
|
||||
try {
|
||||
|
@ -59,6 +59,8 @@ public final class ChatControl extends HBox {
|
||||
vbox.getChildren().add(unreadMessagesLabel);
|
||||
getChildren().add(vbox);
|
||||
}
|
||||
getStyleClass().add("list-element");
|
||||
|
||||
// Set background depending on whether it is disabled or not
|
||||
getStyleClass().add(chat.isDisabled() ? "disabled-chat" : "list-element");
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ import envoy.data.*;
|
||||
import envoy.data.Attachment.AttachmentType;
|
||||
import envoy.data.Message.MessageStatus;
|
||||
import envoy.event.*;
|
||||
import envoy.event.contact.ContactOperation;
|
||||
import envoy.event.contact.UserOperation;
|
||||
import envoy.exception.EnvoyException;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
@ -270,18 +270,22 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
}
|
||||
|
||||
@Event
|
||||
private void onContactOperation(ContactOperation operation) {
|
||||
final var contact = operation.get();
|
||||
switch (operation.getOperationType()) {
|
||||
case ADD:
|
||||
if (contact instanceof User) localDB.getUsers().put(contact.getName(), (User) contact);
|
||||
final var chat = contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact);
|
||||
Platform.runLater(() -> ((ObservableList<Chat>) chats.getSource()).add(0, chat));
|
||||
break;
|
||||
case REMOVE:
|
||||
Platform.runLater(() -> chats.getSource().removeIf(c -> c.getRecipient().equals(contact)));
|
||||
break;
|
||||
private void onUserOperation(UserOperation operation) {
|
||||
// All ADD dependant logic resides in LocalDB
|
||||
if (operation.getOperationType().equals(ElementOperation.REMOVE))
|
||||
if (currentChat != null && currentChat.getRecipient().equals(operation.get())) Platform.runLater(this::resetState);
|
||||
}
|
||||
delvh marked this conversation as resolved
kske
commented
Why not use Why not use `Optional#ifPresent` here?
|
||||
|
||||
@Event
|
||||
private void onGroupResize(GroupResize resize) {
|
||||
final var chatFound = localDB.getChat(resize.getGroupID());
|
||||
Platform.runLater(() -> {
|
||||
chatList.refresh();
|
||||
|
||||
// Update the top-bar status label if all conditions apply
|
||||
if (currentChat != null && currentChat.getRecipient().equals(chatFound.get().getRecipient()))
|
||||
chatFound.ifPresent(chat -> topBarStatusLabel.setText(chat.getRecipient().getContacts().size() + " members"));
|
||||
});
|
||||
}
|
||||
|
||||
@Event(eventType = NoAttachments.class)
|
||||
@ -297,8 +301,8 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
});
|
||||
}
|
||||
|
||||
@Event
|
||||
private void onGroupCreationResult(GroupCreationResult result) { Platform.runLater(() -> newGroupButton.setDisable(!result.get())); }
|
||||
@Event(priority = 150)
|
||||
private void onGroupCreationResult(GroupCreationResult result) { Platform.runLater(() -> newGroupButton.setDisable(result.get() == null)); }
|
||||
|
||||
@Event(eventType = ThemeChangeEvent.class)
|
||||
private void onThemeChange() {
|
||||
@ -311,7 +315,6 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
||||
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
|
||||
messageList.setCellFactory(MessageListCell::new);
|
||||
// TODO: cache image
|
||||
if (currentChat != null)
|
||||
if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
||||
else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
|
||||
@ -330,9 +333,11 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
*/
|
||||
@FXML
|
||||
delvh marked this conversation as resolved
kske
commented
`currentChat != null` is unnecessary here.
delvh
commented
Let's hope I' ll stay unable to reproduce what I once have produced... Let's hope I' ll stay unable to reproduce what I once have produced...
|
||||
private void chatListClicked() {
|
||||
if (chatList.getSelectionModel().isEmpty()) return;
|
||||
if (currentChat != null && chatList.getSelectionModel().isEmpty()) return;
|
||||
final var chat = chatList.getSelectionModel().getSelectedItem();
|
||||
if (chat == null) return;
|
||||
|
||||
final var user = chatList.getSelectionModel().getSelectedItem().getRecipient();
|
||||
final var user = chat.getRecipient();
|
||||
if (user != null && (currentChat == null || !user.equals(currentChat.getRecipient()))) {
|
||||
|
||||
// LEON: JFC <===> JAVA FRIED CHICKEN <=/=> Java Foundation Classes
|
||||
@ -362,17 +367,24 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
remainingChars
|
||||
.setText(String.format("remaining chars: %d/%d", MAX_MESSAGE_LENGTH - messageTextArea.getText().length(), MAX_MESSAGE_LENGTH));
|
||||
}
|
||||
messageTextArea.setDisable(currentChat == null || postingPermanentlyDisabled);
|
||||
voiceButton.setDisable(!recorder.isSupported());
|
||||
attachmentButton.setDisable(false);
|
||||
|
||||
// Enable or disable the necessary UI controls
|
||||
final var chatEditable = currentChat == null || currentChat.isDisabled();
|
||||
messageTextArea.setDisable(chatEditable || postingPermanentlyDisabled);
|
||||
voiceButton.setDisable(!recorder.isSupported() || chatEditable);
|
||||
attachmentButton.setDisable(chatEditable);
|
||||
chatList.refresh();
|
||||
|
||||
// Design the top bar
|
||||
if (currentChat != null) {
|
||||
topBarContactLabel.setText(currentChat.getRecipient().getName());
|
||||
topBarContactLabel.setVisible(true);
|
||||
if (currentChat.getRecipient() instanceof User) {
|
||||
final var status = ((User) currentChat.getRecipient()).getStatus().toString();
|
||||
topBarStatusLabel.setText(status);
|
||||
topBarStatusLabel.getStyleClass().clear();
|
||||
topBarStatusLabel.getStyleClass().add(status.toLowerCase());
|
||||
topBarStatusLabel.setVisible(true);
|
||||
recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
|
||||
} else {
|
||||
topBarStatusLabel.setText(currentChat.getRecipient().getContacts().size() + " members");
|
||||
@ -385,8 +397,11 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
clip.setArcHeight(43);
|
||||
clip.setArcWidth(43);
|
||||
recipientProfilePic.setClip(clip);
|
||||
|
||||
messageList.getStyleClass().clear();
|
||||
messageSearchButton.setVisible(true);
|
||||
|
||||
// Change the background of the message list if the chat is disabled
|
||||
if (currentChat.isDisabled()) messageList.getStyleClass().add("disabled-chat");
|
||||
}
|
||||
}
|
||||
|
||||
@ -664,9 +679,9 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
Platform.runLater(() -> {
|
||||
chats.getSource().remove(currentChat);
|
||||
((ObservableList<Chat>) chats.getSource()).add(0, currentChat);
|
||||
chatList.getSelectionModel().select(0);
|
||||
localDB.getChats().remove(currentChat);
|
||||
localDB.getChats().add(0, currentChat);
|
||||
chatList.getSelectionModel().select(0);
|
||||
});
|
||||
scrollToMessageListEnd();
|
||||
|
||||
@ -711,7 +726,8 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
* @since Envoy Client v0.1-beta
|
||||
*/
|
||||
private void updateAttachmentView(boolean visible) {
|
||||
if (!attachmentView.getImage().equals(DEFAULT_ATTACHMENT_VIEW_IMAGE)) attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
|
||||
if (!(attachmentView.getImage() == null || attachmentView.getImage().equals(DEFAULT_ATTACHMENT_VIEW_IMAGE)))
|
||||
attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
|
||||
attachmentView.setVisible(visible);
|
||||
}
|
||||
|
||||
@ -734,7 +750,62 @@ public final class ChatScene implements EventListener, Restorable {
|
||||
// Context menu actions
|
||||
|
||||
@FXML
|
||||
private void deleteContact() { try {} catch (final NullPointerException e) {} }
|
||||
private void blockOrDeleteContact() {
|
||||
final var selectedChat = chatList.getSelectionModel().getSelectedItem();
|
||||
|
||||
if (selectedChat == null) return;
|
||||
// If a contact has already been blocked deletes this chat else only blocks him
|
||||
if (selectedChat.isDisabled()) UserUtil.deleteContact(selectedChat.getRecipient());
|
||||
else UserUtil.blockContact(selectedChat.getRecipient());
|
||||
}
|
||||
|
||||
/**
|
||||
* Redesigns the UI when the {@link Chat} of the given contact has been marked
|
||||
* as disabled.
|
||||
*
|
||||
* @param recipient the contact whose chat got disabled
|
||||
* @param refreshChatList whether to refresh the chatList
|
||||
* @since Envoy Client v0.3-beta
|
||||
*/
|
||||
public void disableChat(Contact recipient, boolean refreshChatList) {
|
||||
if (refreshChatList) chatList.refresh();
|
||||
if (currentChat != null && currentChat.getRecipient().equals(recipient)) {
|
||||
messageTextArea.setDisable(true);
|
||||
voiceButton.setDisable(true);
|
||||
attachmentButton.setDisable(true);
|
||||
pendingAttachment = null;
|
||||
messageList.getStyleClass().clear();
|
||||
messageList.getStyleClass().add("disabled-chat");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets every component back to its inital state before a chat was selected.
|
||||
*
|
||||
* @since Envoy Client v0.3-beta
|
||||
*/
|
||||
public void resetState() {
|
||||
currentChat = null;
|
||||
chatList.getSelectionModel().clearSelection();
|
||||
messageList.getItems().clear();
|
||||
messageTextArea.setDisable(true);
|
||||
attachmentView.setImage(null);
|
||||
topBarContactLabel.setVisible(false);
|
||||
topBarStatusLabel.setVisible(false);
|
||||
messageSearchButton.setVisible(false);
|
||||
messageTextArea.clear();
|
||||
messageTextArea.setDisable(true);
|
||||
attachmentButton.setDisable(true);
|
||||
voiceButton.setDisable(true);
|
||||
remainingChars.setVisible(false);
|
||||
pendingAttachment = null;
|
||||
recipientProfilePic.setImage(null);
|
||||
|
||||
// It is a valid case that cancel can cause a NullPointerException
|
||||
try {
|
||||
recorder.cancel();
|
||||
} catch (final NullPointerException e) {}
|
||||
}
|
||||
|
||||
@FXML
|
||||
private void copyAndPostMessage() {
|
||||
|
@ -63,7 +63,7 @@ public class ContactSearchTab implements EventListener {
|
||||
}
|
||||
|
||||
@Event
|
||||
private void onContactOperation(ContactOperation operation) {
|
||||
private void onUserOperation(UserOperation operation) {
|
||||
final var contact = operation.get();
|
||||
if (operation.getOperationType() == ElementOperation.ADD) Platform.runLater(() -> {
|
||||
userList.getItems().remove(contact);
|
||||
@ -96,7 +96,7 @@ public class ContactSearchTab implements EventListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an {@link ContactOperation} for the selected user to the
|
||||
* Sends an {@link UserOperation} for the selected user to the
|
||||
* server.
|
||||
*
|
||||
* @since Envoy Client v0.1-beta
|
||||
@ -114,7 +114,7 @@ public class ContactSearchTab implements EventListener {
|
||||
private void addAsContact() {
|
||||
|
||||
// Sends the event to the server
|
||||
final var event = new ContactOperation(currentlySelectedUser, ElementOperation.ADD);
|
||||
final var event = new UserOperation(currentlySelectedUser, ElementOperation.ADD);
|
||||
client.send(event);
|
||||
|
||||
// Removes the chosen user and updates the UI
|
||||
|
@ -16,7 +16,7 @@ import envoy.client.ui.control.*;
|
||||
import envoy.client.ui.listcell.ListCellFactory;
|
||||
import envoy.data.*;
|
||||
import envoy.event.GroupCreation;
|
||||
import envoy.event.contact.ContactOperation;
|
||||
import envoy.event.contact.UserOperation;
|
||||
import envoy.util.Bounds;
|
||||
|
||||
import dev.kske.eventbus.*;
|
||||
@ -234,8 +234,8 @@ public class GroupCreationTab implements EventListener {
|
||||
}
|
||||
|
||||
@Event
|
||||
private void onContactOperation(ContactOperation operation) {
|
||||
if (operation.get() instanceof User) Platform.runLater(() -> {
|
||||
private void onUserOperation(UserOperation operation) {
|
||||
Platform.runLater(() -> {
|
||||
switch (operation.getOperationType()) {
|
||||
case ADD:
|
||||
userList.getItems().add((User) operation.get());
|
||||
|
@ -5,12 +5,15 @@ import java.util.logging.Level;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Alert.AlertType;
|
||||
|
||||
import envoy.client.data.Context;
|
||||
import envoy.client.data.*;
|
||||
import envoy.client.event.*;
|
||||
import envoy.client.helper.*;
|
||||
import envoy.client.ui.SceneContext.SceneInfo;
|
||||
import envoy.client.ui.controller.ChatScene;
|
||||
import envoy.data.*;
|
||||
import envoy.data.User.UserStatus;
|
||||
import envoy.event.UserStatusChange;
|
||||
import envoy.event.*;
|
||||
import envoy.event.contact.UserOperation;
|
||||
import envoy.util.EnvoyLog;
|
||||
|
||||
import dev.kske.eventbus.EventBus;
|
||||
@ -23,6 +26,8 @@ import dev.kske.eventbus.EventBus;
|
||||
*/
|
||||
public final class UserUtil {
|
||||
|
||||
private static final Context context = Context.getInstance();
|
||||
|
||||
private UserUtil() {}
|
||||
|
||||
/**
|
||||
@ -40,7 +45,7 @@ public final class UserUtil {
|
||||
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);
|
||||
context.getSceneContext().load(SceneInfo.LOGIN_SCENE);
|
||||
});
|
||||
}
|
||||
|
||||
@ -54,11 +59,56 @@ public final class UserUtil {
|
||||
public static void changeStatus(UserStatus newStatus) {
|
||||
|
||||
// Sending the already active status is a valid action
|
||||
if (newStatus.equals(Context.getInstance().getLocalDB().getUser().getStatus())) return;
|
||||
if (newStatus.equals(context.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));
|
||||
if (context.getClient().isOnline()) context.getClient().send(new UserStatusChange(context.getLocalDB().getUser().getID(), newStatus));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given contact. Should not be confused with the method that is
|
||||
* called when the server reports that a contact has been deleted while the user
|
||||
* was offline.
|
||||
*
|
||||
* @param block the contact that should be removed
|
||||
* @since Envoy Client v0.3-beta
|
||||
* @see LocalDB#setUserAndMergeContacts(envoy.data.User)
|
||||
*/
|
||||
public static void blockContact(Contact block) {
|
||||
if (!context.getClient().isOnline() || block == null) return;
|
||||
else {
|
||||
final var alert = new Alert(AlertType.CONFIRMATION);
|
||||
alert.setContentText("Are you sure you want to block " + block.getName() + "?");
|
||||
AlertHelper.confirmAction(alert, () -> {
|
||||
context.getClient()
|
||||
.send(block instanceof User ? new UserOperation((User) block, ElementOperation.REMOVE)
|
||||
: new GroupResize(context.getLocalDB().getUser(), (Group) block, ElementOperation.REMOVE));
|
||||
context.getLocalDB().getChat(block.getID()).ifPresent(chat -> chat.setDisabled(true));
|
||||
final var controller = context.getSceneContext().getController();
|
||||
if (controller instanceof ChatScene) ((ChatScene) controller).disableChat(block, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given contact with all his messages entirely.
|
||||
*
|
||||
* @param delete the contact to delete
|
||||
delvh marked this conversation as resolved
kske
commented
Use an event here instead. This would also simplify the interaction with the local database. Use an event here instead. This would also simplify the interaction with the local database.
|
||||
* @since Envoy Client v0.3-beta
|
||||
*/
|
||||
public static void deleteContact(Contact delete) {
|
||||
if (!context.getClient().isOnline() || delete == null) return;
|
||||
else {
|
||||
final var alert = new Alert(AlertType.CONFIRMATION);
|
||||
alert.setContentText("Are you sure you want to delete " + delete.getName()
|
||||
+ " entirely? All messages with this contact will be deleted. This action cannot be undone.");
|
||||
AlertHelper.confirmAction(alert, () -> {
|
||||
context.getLocalDB().getUsers().remove(delete.getName());
|
||||
context.getLocalDB().getChats().removeIf(chat -> chat.getRecipient().equals(delete));
|
||||
if (context.getSceneContext().getController() instanceof ChatScene)
|
||||
((ChatScene) context.getSceneContext().getController()).resetState();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,19 +140,24 @@
|
||||
.tab-pane {
|
||||
-fx-tab-max-height: 0.0 ;
|
||||
}
|
||||
|
||||
.tab-pane .tab-header-area {
|
||||
visibility: hidden ;
|
||||
-fx-padding: -20.0 0.0 0.0 0.0;
|
||||
}
|
||||
|
||||
.disabled-chat {
|
||||
-fx-background-color: #F04000;
|
||||
}
|
||||
|
||||
#quick-select-list .scroll-bar:horizontal{
|
||||
-fx-pref-height: 0;
|
||||
-fx-max-height: 0;
|
||||
-fx-min-height: 0;
|
||||
-fx-pref-height: 0.0;
|
||||
-fx-max-height: 0.0;
|
||||
-fx-min-height: 0.0;
|
||||
}
|
||||
|
||||
#quick-select-list .scroll-bar:vertical{
|
||||
-fx-pref-width: 0;
|
||||
-fx-max-width: 0;
|
||||
-fx-min-width: 0;
|
||||
-fx-pref-width: 0.0;
|
||||
-fx-max-width: 0.0;
|
||||
-fx-min-width: 0.0;
|
||||
}
|
||||
|
@ -130,7 +130,7 @@
|
||||
<ContextMenu anchorLocation="CONTENT_TOP_LEFT">
|
||||
<items>
|
||||
<MenuItem fx:id="deleteContactMenuItem"
|
||||
mnemonicParsing="false" onAction="#deleteContact"
|
||||
mnemonicParsing="false" onAction="#blockOrDeleteContact"
|
||||
text="Delete" />
|
||||
</items>
|
||||
</ContextMenu>
|
||||
|
@ -17,14 +17,14 @@ public final class GroupCreation extends Event<String> {
|
||||
private static final long serialVersionUID = 0L;
|
||||
|
||||
/**
|
||||
* @param value the name of this group at creation time
|
||||
* @param name the name of this group at creation time
|
||||
* @param initialMemberIDs the IDs of all {@link User}s that should be group
|
||||
* members from the beginning on (excluding the creator
|
||||
* of this group)
|
||||
* @since Envoy Common v0.1-beta
|
||||
*/
|
||||
public GroupCreation(String value, Set<Long> initialMemberIDs) {
|
||||
super(value);
|
||||
public GroupCreation(String name, Set<Long> initialMemberIDs) {
|
||||
super(name);
|
||||
this.initialMemberIDs = initialMemberIDs != null ? initialMemberIDs : new HashSet<>();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
package envoy.event;
|
||||
|
||||
import envoy.data.Group;
|
||||
|
||||
/**
|
||||
* Used to communicate with a client that his request to create a group might
|
||||
* have been rejected as it might be disabled on his current server.
|
||||
@ -7,15 +9,23 @@ package envoy.event;
|
||||
* @author Leon Hofmeister
|
||||
* @since Envoy Common v0.2-beta
|
||||
*/
|
||||
public class GroupCreationResult extends Event<Boolean> {
|
||||
public class GroupCreationResult extends Event<Group> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Creates a new {@code GroupCreationResult}.
|
||||
* Creates a new {@code GroupCreationResult} that implies the failure of this
|
||||
* {@link GroupCreationResult}.
|
||||
*
|
||||
* @param success whether the GroupCreation sent before was successful
|
||||
* @since Envoy Common v0.2-beta
|
||||
*/
|
||||
public GroupCreationResult(boolean success) { super(success); }
|
||||
public GroupCreationResult() { super(null); }
|
||||
|
||||
/**
|
||||
* Creates a new {@code GroupCreationResult}.
|
||||
*
|
||||
* @param resultGroup the group the server created
|
||||
* @since Envoy Common v0.2-beta
|
||||
*/
|
||||
public GroupCreationResult(Group resultGroup) { super(resultGroup); }
|
||||
}
|
||||
|
@ -34,11 +34,10 @@ public final class GroupResize extends Event<User> {
|
||||
public GroupResize(User user, Group group, ElementOperation operation) {
|
||||
super(user);
|
||||
this.operation = operation;
|
||||
if (group.getContacts().contains(user)) {
|
||||
if (operation.equals(ADD))
|
||||
final var contained = group.getContacts().contains(user);
|
||||
if (contained && operation.equals(ADD))
|
||||
throw new IllegalArgumentException(String.format("Cannot add %s to %s!", user, group));
|
||||
} else if (operation.equals(REMOVE))
|
||||
throw new IllegalArgumentException(String.format("Cannot remove %s from %s!", user, group));
|
||||
else if (operation.equals(REMOVE) && !contained) throw new IllegalArgumentException(String.format("Cannot remove %s from %s!", user, group));
|
||||
groupID = group.getID();
|
||||
}
|
||||
|
||||
@ -72,5 +71,5 @@ public final class GroupResize extends Event<User> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return String.format("GroupResize[userid=%d,groupid=%d,operation=%s]", get(), groupID, operation); }
|
||||
public String toString() { return String.format("GroupResize[user=%s,groupid=%d,operation=%s]", get(), groupID, operation); }
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import envoy.event.Event.Valueless;
|
||||
* @author Leon Hofmeister
|
||||
* @since Envoy Common v0.3-beta
|
||||
*/
|
||||
public class ContactDeletionSinceLastLogin extends Valueless {
|
||||
public class ContactsChangedSinceLastLogin extends Valueless {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
@ -1,35 +1,38 @@
|
||||
package envoy.event.contact;
|
||||
|
||||
import envoy.data.Contact;
|
||||
import envoy.data.User;
|
||||
import envoy.event.*;
|
||||
|
||||
/**
|
||||
* Signifies the modification of a contact list.
|
||||
*
|
||||
* @author Maximilian Käfer
|
||||
* @since Envoy Common v0.2-alpha
|
||||
* @since Envoy Common v0.3-beta
|
||||
*/
|
||||
public final class ContactOperation extends Event<Contact> {
|
||||
public final class UserOperation extends Event<User> {
|
||||
|
||||
private final ElementOperation operationType;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Initializes a {@link ContactOperation}.
|
||||
* Initializes a {@link UserOperation}.
|
||||
*
|
||||
* @param contact the user on which the operation is performed
|
||||
* @param operationType the type of operation to perform
|
||||
* @since Envoy Common v0.2-alpha
|
||||
* @since Envoy Common v0.3-beta
|
||||
*/
|
||||
public ContactOperation(Contact contact, ElementOperation operationType) {
|
||||
public UserOperation(User contact, ElementOperation operationType) {
|
||||
super(contact);
|
||||
this.operationType = operationType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the type of operation to perform
|
||||
* @since Envoy Common v0.2-alpha
|
||||
* @since Envoy Common v0.3-beta
|
||||
*/
|
||||
public ElementOperation getOperationType() { return operationType; }
|
||||
|
||||
@Override
|
||||
public String toString() { return String.format("%s[contact=%s, operation=%s]", UserOperation.class.getSimpleName(), value, operationType); }
|
||||
}
|
@ -4,7 +4,7 @@ import java.time.Instant;
|
||||
import java.util.logging.*;
|
||||
|
||||
import envoy.event.ElementOperation;
|
||||
import envoy.event.contact.ContactOperation;
|
||||
import envoy.event.contact.UserOperation;
|
||||
import envoy.server.data.*;
|
||||
import envoy.server.net.*;
|
||||
import envoy.util.EnvoyLog;
|
||||
@ -13,14 +13,14 @@ import envoy.util.EnvoyLog;
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Envoy Server Standalone v0.1-alpha
|
||||
*/
|
||||
public final class ContactOperationProcessor implements ObjectProcessor<ContactOperation> {
|
||||
public final class ContactOperationProcessor implements ObjectProcessor<UserOperation> {
|
||||
|
||||
private static final ConnectionManager connectionManager = ConnectionManager.getInstance();
|
||||
private static final Logger logger = EnvoyLog.getLogger(ContactOperationProcessor.class);
|
||||
private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
|
||||
|
||||
@Override
|
||||
public void process(ContactOperation evt, long socketId, ObjectWriteProxy writeProxy) {
|
||||
public void process(UserOperation evt, long socketId, ObjectWriteProxy writeProxy) {
|
||||
final long userID = ConnectionManager.getInstance().getUserIDBySocketID(socketId);
|
||||
final long contactID = evt.get().getID();
|
||||
final var sender = persistenceManager.getUserByID(userID);
|
||||
@ -31,7 +31,7 @@ public final class ContactOperationProcessor implements ObjectProcessor<ContactO
|
||||
|
||||
// Notify the contact if online
|
||||
if (connectionManager.isOnline(contactID))
|
||||
writeProxy.write(connectionManager.getSocketID(contactID), new ContactOperation(sender.toCommon(), ElementOperation.ADD));
|
||||
writeProxy.write(connectionManager.getSocketID(contactID), new UserOperation(sender.toCommon(), ElementOperation.ADD));
|
||||
break;
|
||||
case REMOVE:
|
||||
|
||||
@ -49,7 +49,7 @@ public final class ContactOperationProcessor implements ObjectProcessor<ContactO
|
||||
|
||||
// Notify the removed contact if online
|
||||
if (connectionManager.isOnline(contactID))
|
||||
writeProxy.write(connectionManager.getSocketID(contactID), new ContactOperation(sender.toCommon(), ElementOperation.REMOVE));
|
||||
writeProxy.write(connectionManager.getSocketID(contactID), new UserOperation(sender.toCommon(), ElementOperation.REMOVE));
|
||||
} else {
|
||||
|
||||
// The sender wants to be removed from a Group
|
||||
@ -60,7 +60,7 @@ public final class ContactOperationProcessor implements ObjectProcessor<ContactO
|
||||
else {
|
||||
|
||||
// Informing the other members
|
||||
writeProxy.writeToOnlineContacts(group.getContacts(), new ContactOperation(sender.toCommon(), ElementOperation.REMOVE));
|
||||
writeProxy.writeToOnlineContacts(group.getContacts(), new UserOperation(sender.toCommon(), ElementOperation.REMOVE));
|
||||
group.getContacts().forEach(c -> ((User) c).setLatestContactDeletion(Instant.now()));
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import static envoy.server.Startup.config;
|
||||
import java.util.HashSet;
|
||||
|
||||
import envoy.event.*;
|
||||
import envoy.event.contact.ContactOperation;
|
||||
import envoy.server.data.*;
|
||||
import envoy.server.net.*;
|
||||
|
||||
@ -21,8 +20,10 @@ public final class GroupCreationProcessor implements ObjectProcessor<GroupCreati
|
||||
@Override
|
||||
public void process(GroupCreation groupCreation, long socketID, ObjectWriteProxy writeProxy) {
|
||||
// Don't allow the creation of groups if manually disabled
|
||||
writeProxy.write(socketID, new GroupCreationResult(config.isGroupSupportEnabled()));
|
||||
if (!config.isGroupSupportEnabled()) return;
|
||||
if (!config.isGroupSupportEnabled()) {
|
||||
writeProxy.write(socketID, new GroupCreationResult());
|
||||
return;
|
||||
}
|
||||
final envoy.server.data.Group group = new envoy.server.data.Group();
|
||||
group.setName(groupCreation.get());
|
||||
group.setContacts(new HashSet<>());
|
||||
@ -31,11 +32,13 @@ public final class GroupCreationProcessor implements ObjectProcessor<GroupCreati
|
||||
group.getContacts().forEach(c -> c.getContacts().add(group));
|
||||
group.getContacts().add(persistenceManager.getUserByID(connectionManager.getUserIDBySocketID(socketID)));
|
||||
persistenceManager.addContact(group);
|
||||
final var resultGroup = group.toCommon();
|
||||
group.getContacts()
|
||||
.stream()
|
||||
.map(Contact::getID)
|
||||
.filter(connectionManager::isOnline)
|
||||
.map(connectionManager::getSocketID)
|
||||
.forEach(memberSocketID -> writeProxy.write(memberSocketID, new ContactOperation(group.toCommon(), ElementOperation.ADD)));
|
||||
.forEach(memberSocketID -> writeProxy.write(memberSocketID, new GroupCreationResult(resultGroup)));
|
||||
writeProxy.write(socketID, new GroupCreationResult(resultGroup));
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import javax.persistence.NoResultException;
|
||||
|
||||
import envoy.data.LoginCredentials;
|
||||
import envoy.event.*;
|
||||
import envoy.event.contact.ContactDeletionSinceLastLogin;
|
||||
import envoy.event.contact.ContactsChangedSinceLastLogin;
|
||||
import envoy.server.data.*;
|
||||
import envoy.server.net.*;
|
||||
import envoy.server.util.*;
|
||||
@ -207,11 +207,10 @@ public final class LoginCredentialProcessor implements ObjectProcessor<LoginCred
|
||||
writeProxy.write(socketID, new MessageStatusChange(gmsgCommon));
|
||||
}
|
||||
}
|
||||
// Notify the user if a contact deletion has happened since he last logged in
|
||||
if (user.getLatestContactDeletion().isAfter(user.getLastSeen())) writeProxy.write(socketID, new ContactsChangedSinceLastLogin());
|
||||
|
||||
// Complete the handshake
|
||||
writeProxy.write(socketID, user.toCommon());
|
||||
|
||||
// Notify the user if a contact deletion has happened since he last logged in
|
||||
if (user.getLatestContactDeletion().isAfter(user.getLastSeen())) writeProxy.write(socketID, new ContactDeletionSinceLastLogin());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user
Why is this
private
even though we permit subclassing here?Because we don't need to access this in subclasses. Subclasses themselves should never disable themselves.
There is a public setter though. The chat can literally be disabled from anywhere.
According to Meyer's open-closed principle (the O in SOLID), software entities should open for extension, while closed for modification. To me, this implies that subclasses should be able to access this value without the use of a getter.
The problem I have with this is that in this case it is literally intended that they are modified from outside and that they do not modify themselves.