Merge pull request #30 from informatik-ag-ngl/f/is_typing_event

Added IsTyping event on common, server and partially on client side.

Additionally fixed small NullPointerException in ContactSearchScene
This commit is contained in:
delvh 2020-07-25 17:17:29 +02:00 committed by GitHub
commit 1cdad2df0b
9 changed files with 139 additions and 25 deletions

View File

@ -7,8 +7,10 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import envoy.client.net.WriteProxy; import envoy.client.net.WriteProxy;
import envoy.data.*; import envoy.data.Contact;
import envoy.data.Message;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
import envoy.data.User;
import envoy.event.MessageStatusChange; import envoy.event.MessageStatusChange;
/** /**
@ -31,6 +33,11 @@ public class Chat implements Serializable {
protected int unreadAmount; protected int unreadAmount;
/**
* Stores the last time an {@link envoy.event.IsTyping} event has been sent.
*/
protected transient long lastWritingEvent;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/** /**
@ -41,16 +48,14 @@ public class Chat implements Serializable {
* @param recipient the user who receives the messages * @param recipient the user who receives the messages
* @since Envoy Client v0.1-alpha * @since Envoy Client v0.1-alpha
*/ */
public Chat(Contact recipient) { public Chat(Contact recipient) { this.recipient = recipient; }
this.recipient = recipient;
}
@Override @Override
public String toString() { return String.format("Chat[recipient=%s,messages=%d]", recipient, messages.size()); } public String toString() { return String.format("Chat[recipient=%s,messages=%d]", recipient, messages.size()); }
/** /**
* Generates a hash code based on the recipient. * Generates a hash code based on the recipient.
* *
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
@Override @Override
@ -58,14 +63,14 @@ public class Chat implements Serializable {
/** /**
* Tests equality to another object based on the recipient. * Tests equality to another object based on the recipient.
* *
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (this == obj) return true; if (this == obj) return true;
if (!(obj instanceof Chat)) return false; if (!(obj instanceof Chat)) return false;
Chat other = (Chat) obj; final Chat other = (Chat) obj;
return Objects.equals(recipient, other.recipient); return Objects.equals(recipient, other.recipient);
} }
@ -101,7 +106,7 @@ public class Chat implements Serializable {
/** /**
* Inserts a message at the correct place according to its creation date. * Inserts a message at the correct place according to its creation date.
* *
* @param message the message to insert * @param message the message to insert
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
@ -116,13 +121,13 @@ public class Chat implements Serializable {
/** /**
* Increments the amount of unread messages. * Increments the amount of unread messages.
* *
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public void incrementUnreadAmount() { unreadAmount++; } public void incrementUnreadAmount() { unreadAmount++; }
/** /**
* @return the amount of unread mesages in this chat * @return the amount of unread messages in this chat
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public int getUnreadAmount() { return unreadAmount; } public int getUnreadAmount() { return unreadAmount; }
@ -140,14 +145,16 @@ public class Chat implements Serializable {
public Contact getRecipient() { return recipient; } public Contact getRecipient() { return recipient; }
/** /**
* @return whether this {@link Chat} points at a {@link User} * @return the last known time a {@link envoy.event.IsTyping} event has been
* @since Envoy Client v0.1-beta * sent
* @since Envoy Client v0.2-beta
*/ */
public boolean isUserChat() { return recipient instanceof User; } public long getLastWritingEvent() { return lastWritingEvent; }
/** /**
* @return whether this {@link Chat} points at a {@link Group} * Sets the {@code lastWritingEvent} to {@code System#currentTimeMillis()}.
* @since Envoy Client v0.1-beta *
* @since Envoy Client v0.2-beta
*/ */
public boolean isGroupChat() { return recipient instanceof Group; } public void lastWritingEventWasNow() { lastWritingEvent = System.currentTimeMillis(); }
} }

View File

@ -155,6 +155,9 @@ public class Client implements Closeable {
// Process group size changes // Process group size changes
receiver.registerProcessor(GroupResize.class, evt -> { localDB.updateGroup(evt); eventBus.dispatch(evt); }); receiver.registerProcessor(GroupResize.class, evt -> { localDB.updateGroup(evt); eventBus.dispatch(evt); });
// Process IsTyping events
receiver.registerProcessor(IsTyping.class, eventBus::dispatch);
// Send event // Send event
eventBus.register(SendEvent.class, evt -> { eventBus.register(SendEvent.class, evt -> {
try { try {

View File

@ -33,6 +33,7 @@ import envoy.client.data.audio.AudioRecorder;
import envoy.client.data.commands.SystemCommandBuilder; import envoy.client.data.commands.SystemCommandBuilder;
import envoy.client.data.commands.SystemCommandsMap; import envoy.client.data.commands.SystemCommandsMap;
import envoy.client.event.MessageCreationEvent; import envoy.client.event.MessageCreationEvent;
import envoy.client.event.SendEvent;
import envoy.client.net.Client; import envoy.client.net.Client;
import envoy.client.net.WriteProxy; import envoy.client.net.WriteProxy;
import envoy.client.ui.*; import envoy.client.ui.*;
@ -444,10 +445,28 @@ public final class ChatScene implements Restorable {
private void checkKeyCombination(KeyEvent e) { private void checkKeyCombination(KeyEvent e) {
// Checks whether the text is too long // Checks whether the text is too long
messageTextUpdated(); messageTextUpdated();
// Sending an IsTyping event if none has been sent for
// IsTyping#millisecondsActive
if (client.isOnline() && currentChat.getLastWritingEvent() + IsTyping.millisecondsActive <= System.currentTimeMillis()) {
eventBus.dispatch(new SendEvent(new IsTyping(getChatID(), currentChat.getRecipient().getID())));
currentChat.lastWritingEventWasNow();
}
// Automatic sending of messages via (ctrl +) enter // Automatic sending of messages via (ctrl +) enter
checkPostConditions(e); checkPostConditions(e);
} }
/**
* Returns the id that should be used to send things to the server:
* the id of 'our' {@link User} if the recipient of that object is another User,
* else the id of the {@link Group} 'our' user is sending to.
*
* @return an id that can be sent to the server
* @since Envoy Client v0.2-beta
*/
private long getChatID() {
return currentChat.getRecipient() instanceof User ? client.getSender().getID() : currentChat.getRecipient().getID();
}
/** /**
* @param e the keys that have been pressed * @param e the keys that have been pressed
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
@ -515,7 +534,7 @@ public final class ChatScene implements Restorable {
updateInfoLabel("You need to go online to send more messages", "infoLabel-error"); updateInfoLabel("You need to go online to send more messages", "infoLabel-error");
return; return;
} }
final var text = messageTextArea.getText().strip(); final var text = messageTextArea.getText().strip();
if (!messageTextAreaCommands.executeIfAnyPresent(text)) try { if (!messageTextAreaCommands.executeIfAnyPresent(text)) try {
// Creating the message and its metadata // Creating the message and its metadata
final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator()) final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())

View File

@ -60,8 +60,10 @@ public class ContactSearchScene {
private final Consumer<ContactOperation> handler = e -> { private final Consumer<ContactOperation> handler = e -> {
final var contact = e.get(); final var contact = e.get();
if (e.getOperationType() == ElementOperation.ADD) Platform if (e.getOperationType() == ElementOperation.ADD) Platform.runLater(() -> {
.runLater(() -> { userList.getItems().remove(contact); if (currentlySelectedUser.equals(contact) && alert.isShowing()) alert.close(); }); userList.getItems().remove(contact);
if (currentlySelectedUser != null && currentlySelectedUser.equals(contact) && alert.isShowing()) alert.close();
});
}; };
private static final EventBus eventBus = EventBus.getInstance(); private static final EventBus eventBus = EventBus.getInstance();

View File

@ -138,7 +138,7 @@ public final class LoginScene {
localDB.setUser(localDB.getUsers().get(identifier)); localDB.setUser(localDB.getUsers().get(identifier));
localDB.initializeUserStorage(); localDB.initializeUserStorage();
localDB.loadUserData(); localDB.loadUserData();
} catch (Exception e) { } catch (final Exception e) {
// User storage empty, wrong user name etc. -> default lastSync // User storage empty, wrong user name etc. -> default lastSync
} }
return localDB.getLastSync(); return localDB.getLastSync();
@ -161,7 +161,7 @@ public final class LoginScene {
try { try {
// Try entering offline mode // Try entering offline mode
localDB.loadUsers(); localDB.loadUsers();
final User clientUser = (User) localDB.getUsers().get(credentials.getIdentifier()); final User clientUser = localDB.getUsers().get(credentials.getIdentifier());
if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown"); if (clientUser == null) throw new EnvoyException("Could not enter offline mode: user name unknown");
client.setSender(clientUser); client.setSender(clientUser);
loadChatScene(); loadChatScene();
@ -223,7 +223,7 @@ public final class LoginScene {
// Initialize status tray icon // Initialize status tray icon
final var trayIcon = new StatusTrayIcon(sceneContext.getStage()); final var trayIcon = new StatusTrayIcon(sceneContext.getStage());
settings.getItems().get("hideOnClose").setChangeHandler(c -> { settings.getItems().get("hideOnClose").setChangeHandler(c -> {
if (((Boolean) c)) trayIcon.show(); if ((Boolean) c) trayIcon.show();
else trayIcon.hide(); else trayIcon.hide();
}); });
} }

View File

@ -31,12 +31,12 @@ public class ChatControl extends HBox {
// Unread messages // Unread messages
if (chat.getUnreadAmount() != 0) { if (chat.getUnreadAmount() != 0) {
Region spacing = new Region(); final var spacing = new Region();
setHgrow(spacing, Priority.ALWAYS); setHgrow(spacing, Priority.ALWAYS);
getChildren().add(spacing); getChildren().add(spacing);
final var unreadMessagesLabel = new Label(Integer.toString(chat.getUnreadAmount())); final var unreadMessagesLabel = new Label(Integer.toString(chat.getUnreadAmount()));
unreadMessagesLabel.setMinSize(15, 15); unreadMessagesLabel.setMinSize(15, 15);
var vBox2 = new VBox(); final var vBox2 = new VBox();
vBox2.setAlignment(Pos.CENTER_RIGHT); vBox2.setAlignment(Pos.CENTER_RIGHT);
unreadMessagesLabel.setAlignment(Pos.CENTER); unreadMessagesLabel.setAlignment(Pos.CENTER);
unreadMessagesLabel.getStyleClass().add("unreadMessagesAmount"); unreadMessagesLabel.getStyleClass().add("unreadMessagesAmount");

View File

@ -0,0 +1,45 @@
package envoy.event;
/**
* This event should be sent when a user is currently typing something in a
* chat.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>IsTyping.java</strong><br>
* Created: <strong>24.07.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.2-beta
*/
public class IsTyping extends Event<Long> {
private final long destinationID;
private static final long serialVersionUID = 1L;
/**
* The number of milliseconds that this event will be active.<br>
* Currently set to 3.5 seconds.
*
* @since Envoy Common v0.2-beta
*/
public static final int millisecondsActive = 3500;
/**
* Creates a new {@code IsTyping} event with originator and recipient.
*
* @param sourceID the id of the originator
* @param destinationID the id of the contact the user wrote to
* @since Envoy Common v0.2-beta
*/
public IsTyping(Long sourceID, long destinationID) {
super(sourceID);
this.destinationID = destinationID;
}
/**
* @return the id of the contact in whose chat the user typed something
* @since Envoy Common v0.2-beta
*/
public long getDestinationID() { return destinationID; }
}

View File

@ -69,7 +69,8 @@ public class Startup {
new UserStatusChangeProcessor(), new UserStatusChangeProcessor(),
new IDGeneratorRequestProcessor(), new IDGeneratorRequestProcessor(),
new UserSearchProcessor(), new UserSearchProcessor(),
new ContactOperationProcessor()))); new ContactOperationProcessor(),
new IsTypingProcessor())));
// Initialize the current message ID // Initialize the current message ID
final PersistenceManager persistenceManager = PersistenceManager.getInstance(); final PersistenceManager persistenceManager = PersistenceManager.getInstance();

View File

@ -0,0 +1,37 @@
package envoy.server.processors;
import java.io.IOException;
import envoy.event.IsTyping;
import envoy.server.data.PersistenceManager;
import envoy.server.data.User;
import envoy.server.net.ConnectionManager;
import envoy.server.net.ObjectWriteProxy;
/**
* This processor handles incoming {@link IsTyping} events.
* <p>
* Project: <strong>envoy-server-standalone</strong><br>
* File: <strong>IsTypingProcessor.java</strong><br>
* Created: <strong>24.07.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Server v0.2-beta
*/
public class IsTypingProcessor implements ObjectProcessor<IsTyping> {
private static final ConnectionManager connectionManager = ConnectionManager.getInstance();
private static final PersistenceManager persistenceManager = PersistenceManager.getInstance();
@Override
public Class<IsTyping> getInputClass() { return IsTyping.class; }
@Override
public void process(IsTyping event, long socketID, ObjectWriteProxy writeProxy) throws IOException {
final var contact = persistenceManager.getContactByID(event.get());
if (contact instanceof User) {
final var destinationID = event.getDestinationID();
if (connectionManager.isOnline(destinationID)) writeProxy.write(connectionManager.getSocketID(destinationID), event);
} else writeProxy.writeToOnlineContacts(contact.getContacts(), event);
}
}