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:
		@@ -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,9 +48,7 @@ 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()); }
 | 
				
			||||||
@@ -65,7 +70,7 @@ public class Chat implements Serializable {
 | 
				
			|||||||
	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);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -122,7 +127,7 @@ public class Chat implements Serializable {
 | 
				
			|||||||
	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(); }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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();
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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");
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										45
									
								
								common/src/main/java/envoy/event/IsTyping.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								common/src/main/java/envoy/event/IsTyping.java
									
									
									
									
									
										Normal 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; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user