Merge pull request #174 from informatik-ag-ngl/f/groupMessages
Group Messages
This commit is contained in:
		@@ -24,10 +24,10 @@ import envoy.event.MessageStatusChange;
 | 
				
			|||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 * @since Envoy Client v0.1-alpha
 | 
					 * @since Envoy Client v0.1-alpha
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public final class Chat implements Serializable {
 | 
					public class Chat implements Serializable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final Contact		recipient;
 | 
						protected final Contact			recipient;
 | 
				
			||||||
	private final List<Message>	messages	= new ArrayList<>();
 | 
						protected final List<Message>	messages	= new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final long serialVersionUID = 1L;
 | 
						private static final long serialVersionUID = 1L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,7 +39,9 @@ public final 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) { this.recipient = recipient; }
 | 
						public Chat(Contact 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()); }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										54
									
								
								src/main/java/envoy/client/data/GroupChat.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/main/java/envoy/client/data/GroupChat.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					package envoy.client.data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.time.LocalDateTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.client.net.WriteProxy;
 | 
				
			||||||
 | 
					import envoy.data.Contact;
 | 
				
			||||||
 | 
					import envoy.data.GroupMessage;
 | 
				
			||||||
 | 
					import envoy.data.Message.MessageStatus;
 | 
				
			||||||
 | 
					import envoy.data.User;
 | 
				
			||||||
 | 
					import envoy.event.GroupMessageStatusChange;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Represents a chat between a user and a group
 | 
				
			||||||
 | 
					 * as a list of messages.
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * Project: <strong>envoy-client</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>GroupChat.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>05.07.2020</strong><br>
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @author Maximilian Käfer
 | 
				
			||||||
 | 
					 * @since Envoy Client v0.1-beta
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class GroupChat extends Chat {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final User sender;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final long serialVersionUID = 1L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param sender    the user sending the messages
 | 
				
			||||||
 | 
						 * @param recipient the group whose members receive the messages
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public GroupChat(User sender, Contact recipient) {
 | 
				
			||||||
 | 
							super(recipient);
 | 
				
			||||||
 | 
							this.sender = sender;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void read(WriteProxy writeProxy) throws IOException {
 | 
				
			||||||
 | 
							for (int i = messages.size() - 1; i >= 0; --i) {
 | 
				
			||||||
 | 
								final GroupMessage gmsg = (GroupMessage) messages.get(i);
 | 
				
			||||||
 | 
								if (gmsg.getSenderID() != sender.getID()) {
 | 
				
			||||||
 | 
									if (gmsg.getMemberStatuses().get(sender.getID()) == MessageStatus.READ) break;
 | 
				
			||||||
 | 
									else {
 | 
				
			||||||
 | 
										gmsg.getMemberStatuses().replace(sender.getID(), MessageStatus.READ);
 | 
				
			||||||
 | 
										writeProxy
 | 
				
			||||||
 | 
											.writeMessageStatusChange(new GroupMessageStatusChange(gmsg.getID(), MessageStatus.READ, LocalDateTime.now(), sender.getID()));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -82,7 +82,11 @@ public abstract class LocalDB {
 | 
				
			|||||||
				getChat(contact.getID()).ifPresent(chat -> { ((User) chat.getRecipient()).setStatus(((User) contact).getStatus()); });
 | 
									getChat(contact.getID()).ifPresent(chat -> { ((User) chat.getRecipient()).setStatus(((User) contact).getStatus()); });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Create missing chats
 | 
							// Create missing chats
 | 
				
			||||||
		user.getContacts().stream().filter(u -> !u.equals(user) && getChat(u.getID()).isEmpty()).map(Chat::new).forEach(chats::add);
 | 
							user.getContacts()
 | 
				
			||||||
 | 
								.stream()
 | 
				
			||||||
 | 
								.filter(c -> !c.equals(user) && getChat(c.getID()).isEmpty())
 | 
				
			||||||
 | 
								.map(c -> c instanceof User ? new Chat(c) : new GroupChat(user, c))
 | 
				
			||||||
 | 
								.forEach(chats::add);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -53,23 +53,34 @@ public class Client implements Closeable {
 | 
				
			|||||||
	 * will block for up to 5 seconds. If the handshake does exceed this time limit,
 | 
						 * will block for up to 5 seconds. If the handshake does exceed this time limit,
 | 
				
			||||||
	 * an exception is thrown.
 | 
						 * an exception is thrown.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param credentials                      the login credentials of the user
 | 
						 * @param credentials                           the login credentials of the
 | 
				
			||||||
	 * @param receivedMessageCache             a message cache containing all unread
 | 
						 *                                              user
 | 
				
			||||||
	 *                                         messages
 | 
						 * @param receivedMessageCache                  a message cache containing all
 | 
				
			||||||
	 *                                         from the server that can be relayed
 | 
						 *                                              unread messages from the server
 | 
				
			||||||
	 *                                         after
 | 
						 *                                              that can be relayed after
 | 
				
			||||||
	 *                                              initialization
 | 
						 *                                              initialization
 | 
				
			||||||
	 * @param receivedMessageStatusChangeCache an event cache containing all
 | 
						 * @param receivedGroupMessageCache             a groupMessage cache containing
 | 
				
			||||||
	 *                                         received messageStatusChangeEvents
 | 
						 *                                              all unread groupMessages from
 | 
				
			||||||
	 *                                         from the server that can be relayed
 | 
						 *                                              the server that can be relayed
 | 
				
			||||||
	 *                                              after initialization
 | 
						 *                                              after initialization
 | 
				
			||||||
 | 
						 * @param receivedMessageStatusChangeCache      an event cache containing all
 | 
				
			||||||
 | 
						 *                                              received
 | 
				
			||||||
 | 
						 *                                              messageStatusChangeEvents from
 | 
				
			||||||
 | 
						 *                                              the server that can be relayed
 | 
				
			||||||
 | 
						 *                                              after initialization
 | 
				
			||||||
 | 
						 * @param receivedGroupMessageStatusChangeCache an event cache containing all
 | 
				
			||||||
 | 
						 *                                              received
 | 
				
			||||||
 | 
						 *                                              groupMessageStatusChangeEvents
 | 
				
			||||||
 | 
						 *                                              from the server that can be
 | 
				
			||||||
 | 
						 *                                              relayed after initialization
 | 
				
			||||||
	 * @throws TimeoutException     if the server could not be reached
 | 
						 * @throws TimeoutException     if the server could not be reached
 | 
				
			||||||
	 * @throws IOException          if the login credentials could not be written
 | 
						 * @throws IOException          if the login credentials could not be written
 | 
				
			||||||
	 * @throws InterruptedException if the current thread is interrupted while
 | 
						 * @throws InterruptedException if the current thread is interrupted while
 | 
				
			||||||
	 *                              waiting for the handshake response
 | 
						 *                              waiting for the handshake response
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void performHandshake(LoginCredentials credentials, Cache<Message> receivedMessageCache,
 | 
						public void performHandshake(LoginCredentials credentials, Cache<Message> receivedMessageCache, Cache<GroupMessage> receivedGroupMessageCache,
 | 
				
			||||||
			Cache<MessageStatusChange> receivedMessageStatusChangeCache) throws TimeoutException, IOException, InterruptedException {
 | 
								Cache<MessageStatusChange> receivedMessageStatusChangeCache, Cache<GroupMessageStatusChange> receivedGroupMessageStatusChangeCache)
 | 
				
			||||||
 | 
								throws TimeoutException, IOException, InterruptedException {
 | 
				
			||||||
		if (online) throw new IllegalStateException("Handshake has already been performed successfully");
 | 
							if (online) throw new IllegalStateException("Handshake has already been performed successfully");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Establish TCP connection
 | 
							// Establish TCP connection
 | 
				
			||||||
@@ -83,7 +94,9 @@ public class Client implements Closeable {
 | 
				
			|||||||
		// Register user creation processor, contact list processor and message cache
 | 
							// Register user creation processor, contact list processor and message cache
 | 
				
			||||||
		receiver.registerProcessor(User.class, sender -> this.sender = sender);
 | 
							receiver.registerProcessor(User.class, sender -> this.sender = sender);
 | 
				
			||||||
		receiver.registerProcessor(Message.class, receivedMessageCache);
 | 
							receiver.registerProcessor(Message.class, receivedMessageCache);
 | 
				
			||||||
 | 
							receiver.registerProcessor(GroupMessage.class, receivedGroupMessageCache);
 | 
				
			||||||
		receiver.registerProcessor(MessageStatusChange.class, receivedMessageStatusChangeCache);
 | 
							receiver.registerProcessor(MessageStatusChange.class, receivedMessageStatusChangeCache);
 | 
				
			||||||
 | 
							receiver.registerProcessor(GroupMessageStatusChange.class, receivedGroupMessageStatusChangeCache);
 | 
				
			||||||
		receiver.registerProcessor(HandshakeRejection.class, evt -> { rejected = true; eventBus.dispatch(evt); });
 | 
							receiver.registerProcessor(HandshakeRejection.class, evt -> { rejected = true; eventBus.dispatch(evt); });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		rejected = false;
 | 
							rejected = false;
 | 
				
			||||||
@@ -122,38 +135,65 @@ public class Client implements Closeable {
 | 
				
			|||||||
	 * Initializes the {@link Receiver} used to process data sent from the server to
 | 
						 * Initializes the {@link Receiver} used to process data sent from the server to
 | 
				
			||||||
	 * this client.
 | 
						 * this client.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param localDB                          the local database used to persist
 | 
						 * @param localDB                               the local database used to
 | 
				
			||||||
 | 
						 *                                              persist
 | 
				
			||||||
	 *                                              the current
 | 
						 *                                              the current
 | 
				
			||||||
	 *                                              {@link IDGenerator}
 | 
						 *                                              {@link IDGenerator}
 | 
				
			||||||
	 * @param receivedMessageCache             a message cache containing all unread
 | 
						 * @param receivedMessageCache                  a message cache containing all
 | 
				
			||||||
 | 
						 *                                              unread
 | 
				
			||||||
	 *                                              messages
 | 
						 *                                              messages
 | 
				
			||||||
	 *                                         from the server that can be relayed
 | 
						 *                                              from the server that can be
 | 
				
			||||||
 | 
						 *                                              relayed
 | 
				
			||||||
 | 
						 *                                              after
 | 
				
			||||||
 | 
						 *                                              initialization
 | 
				
			||||||
 | 
						 * @param receivedGroupMessageCache             a groupMessage cache containing
 | 
				
			||||||
 | 
						 *                                              all
 | 
				
			||||||
 | 
						 *                                              unread
 | 
				
			||||||
 | 
						 *                                              groupMessages
 | 
				
			||||||
 | 
						 *                                              from the server that can be
 | 
				
			||||||
 | 
						 *                                              relayed
 | 
				
			||||||
	 *                                              after
 | 
						 *                                              after
 | 
				
			||||||
	 *                                              initialization
 | 
						 *                                              initialization
 | 
				
			||||||
	 * @param receivedMessageStatusChangeCache      an event cache containing all
 | 
						 * @param receivedMessageStatusChangeCache      an event cache containing all
 | 
				
			||||||
	 *                                         received messageStatusChangeEvents
 | 
						 *                                              received
 | 
				
			||||||
	 *                                         from the server that can be relayed
 | 
						 *                                              messageStatusChangeEvents
 | 
				
			||||||
 | 
						 *                                              from the server that can be
 | 
				
			||||||
 | 
						 *                                              relayed
 | 
				
			||||||
	 *                                              after initialization
 | 
						 *                                              after initialization
 | 
				
			||||||
 | 
						 * @param receivedGroupMessageStatusChangeCache an event cache containing all
 | 
				
			||||||
 | 
						 *                                              received
 | 
				
			||||||
 | 
						 *                                              groupMessageStatusChangeEvents
 | 
				
			||||||
 | 
						 *                                              from the server that can be
 | 
				
			||||||
 | 
						 *                                              relayed after initialization
 | 
				
			||||||
	 * @throws IOException if no {@link IDGenerator} is present and none could be
 | 
						 * @throws IOException if no {@link IDGenerator} is present and none could be
 | 
				
			||||||
	 *                     requested from the server
 | 
						 *                     requested from the server
 | 
				
			||||||
	 * @since Envoy Client v0.2-alpha
 | 
						 * @since Envoy Client v0.2-alpha
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void initReceiver(LocalDB localDB, Cache<Message> receivedMessageCache, Cache<MessageStatusChange> receivedMessageStatusChangeCache)
 | 
						public void initReceiver(LocalDB localDB, Cache<Message> receivedMessageCache, Cache<GroupMessage> receivedGroupMessageCache,
 | 
				
			||||||
 | 
								Cache<MessageStatusChange> receivedMessageStatusChangeCache, Cache<GroupMessageStatusChange> receivedGroupMessageStatusChangeCache)
 | 
				
			||||||
			throws IOException {
 | 
								throws IOException {
 | 
				
			||||||
		checkOnline();
 | 
							checkOnline();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Process incoming messages
 | 
							// Process incoming messages
 | 
				
			||||||
		final ReceivedMessageProcessor			receivedMessageProcessor			= new ReceivedMessageProcessor();
 | 
							final ReceivedMessageProcessor			receivedMessageProcessor			= new ReceivedMessageProcessor();
 | 
				
			||||||
		final MessageStatusChangeProcessor	messageStatusChangeEventProcessor	= new MessageStatusChangeProcessor();
 | 
							final ReceivedGroupMessageProcessor		receivedGroupMessageProcessor		= new ReceivedGroupMessageProcessor();
 | 
				
			||||||
 | 
							final MessageStatusChangeProcessor		messageStatusChangeProcessor		= new MessageStatusChangeProcessor();
 | 
				
			||||||
 | 
							final GroupMessageStatusChangeProcessor	groupMessageStatusChangeProcessor	= new GroupMessageStatusChangeProcessor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							receiver.registerProcessor(GroupMessage.class, receivedGroupMessageProcessor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		receiver.registerProcessor(Message.class, receivedMessageProcessor);
 | 
							receiver.registerProcessor(Message.class, receivedMessageProcessor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Relay cached unread messages
 | 
							receiver.registerProcessor(MessageStatusChange.class, messageStatusChangeProcessor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							receiver.registerProcessor(GroupMessageStatusChange.class, groupMessageStatusChangeProcessor);
 | 
				
			||||||
 | 
							// Relay cached unread messages and unread groupMessages
 | 
				
			||||||
		receivedMessageCache.setProcessor(receivedMessageProcessor);
 | 
							receivedMessageCache.setProcessor(receivedMessageProcessor);
 | 
				
			||||||
 | 
							receivedGroupMessageCache.setProcessor(receivedGroupMessageProcessor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Process message status changes
 | 
							// Process message status changes
 | 
				
			||||||
		receiver.registerProcessor(MessageStatusChange.class, messageStatusChangeEventProcessor);
 | 
							receivedMessageStatusChangeCache.setProcessor(messageStatusChangeProcessor);
 | 
				
			||||||
		receivedMessageStatusChangeCache.setProcessor(messageStatusChangeEventProcessor);
 | 
							receivedGroupMessageStatusChangeCache.setProcessor(groupMessageStatusChangeProcessor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Process user status changes
 | 
							// Process user status changes
 | 
				
			||||||
		receiver.registerProcessor(UserStatusChange.class, eventBus::dispatch);
 | 
							receiver.registerProcessor(UserStatusChange.class, eventBus::dispatch);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					package envoy.client.net;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
 | 
					import java.util.logging.Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.data.Message.MessageStatus;
 | 
				
			||||||
 | 
					import envoy.event.EventBus;
 | 
				
			||||||
 | 
					import envoy.event.GroupMessageStatusChange;
 | 
				
			||||||
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>envoy-client</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>GroupMessageStatusChangePocessor.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>03.07.2020</strong><br>
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @author Maximilian Käfer
 | 
				
			||||||
 | 
					 * @since Envoy Client v0.1-beta
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class GroupMessageStatusChangeProcessor implements Consumer<GroupMessageStatusChange> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final Logger logger = EnvoyLog.getLogger(GroupMessageStatusChangeProcessor.class);
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void accept(GroupMessageStatusChange evt) {
 | 
				
			||||||
 | 
							if (evt.get().ordinal() < MessageStatus.RECEIVED.ordinal()) logger.warning("Received invalid group message status change " + evt);
 | 
				
			||||||
 | 
							else EventBus.getInstance().dispatch(evt);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					package envoy.client.net;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
 | 
					import java.util.logging.Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import envoy.client.event.MessageCreationEvent;
 | 
				
			||||||
 | 
					import envoy.data.GroupMessage;
 | 
				
			||||||
 | 
					import envoy.data.Message.MessageStatus;
 | 
				
			||||||
 | 
					import envoy.event.EventBus;
 | 
				
			||||||
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>envoy-client</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>ReceivedGroupMessageProcessor.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>13.06.2020</strong><br>
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @author Maximilian Käfer
 | 
				
			||||||
 | 
					 * @since Envoy Client v0.1-beta
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class ReceivedGroupMessageProcessor implements Consumer<GroupMessage> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final Logger logger = EnvoyLog.getLogger(ReceivedGroupMessageProcessor.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void accept(GroupMessage groupMessage) {
 | 
				
			||||||
 | 
							if (groupMessage.getStatus() == MessageStatus.WAITING || groupMessage.getStatus() == MessageStatus.READ)
 | 
				
			||||||
 | 
								logger.warning("The groupMessage has the unexpected status " + groupMessage.getStatus());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Dispatch event
 | 
				
			||||||
 | 
							EventBus.getInstance().dispatch(new MessageCreationEvent(groupMessage));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -78,8 +78,10 @@ public class Receiver extends Thread {
 | 
				
			|||||||
					// Get appropriate processor
 | 
										// Get appropriate processor
 | 
				
			||||||
					@SuppressWarnings("rawtypes")
 | 
										@SuppressWarnings("rawtypes")
 | 
				
			||||||
					final Consumer processor = processors.get(obj.getClass());
 | 
										final Consumer processor = processors.get(obj.getClass());
 | 
				
			||||||
					if (processor == null) logger.log(Level.WARNING,
 | 
										if (processor == null)
 | 
				
			||||||
							String.format("The received object has the class %s for which no processor is defined.", obj.getClass()));
 | 
											logger.log(Level.WARNING, String.format(
 | 
				
			||||||
 | 
															"The received object has the %s for which no processor is defined.",
 | 
				
			||||||
 | 
															obj.getClass()));
 | 
				
			||||||
					else processor.accept(obj);
 | 
										else processor.accept(obj);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			} catch (final SocketException e) {
 | 
								} catch (final SocketException e) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,9 @@ import envoy.client.data.*;
 | 
				
			|||||||
import envoy.client.net.Client;
 | 
					import envoy.client.net.Client;
 | 
				
			||||||
import envoy.client.ui.SceneContext.SceneInfo;
 | 
					import envoy.client.ui.SceneContext.SceneInfo;
 | 
				
			||||||
import envoy.client.ui.controller.LoginScene;
 | 
					import envoy.client.ui.controller.LoginScene;
 | 
				
			||||||
 | 
					import envoy.data.GroupMessage;
 | 
				
			||||||
import envoy.data.Message;
 | 
					import envoy.data.Message;
 | 
				
			||||||
 | 
					import envoy.event.GroupMessageStatusChange;
 | 
				
			||||||
import envoy.event.MessageStatusChange;
 | 
					import envoy.event.MessageStatusChange;
 | 
				
			||||||
import envoy.exception.EnvoyException;
 | 
					import envoy.exception.EnvoyException;
 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
@@ -43,7 +45,9 @@ public final class Startup extends Application {
 | 
				
			|||||||
	private LocalDB						localDB;
 | 
						private LocalDB						localDB;
 | 
				
			||||||
	private Client						client;
 | 
						private Client						client;
 | 
				
			||||||
	private Cache<Message>				messageCache;
 | 
						private Cache<Message>				messageCache;
 | 
				
			||||||
 | 
						private Cache<GroupMessage>			groupMessageCache;
 | 
				
			||||||
	private Cache<MessageStatusChange>	messageStatusCache;
 | 
						private Cache<MessageStatusChange>	messageStatusCache;
 | 
				
			||||||
 | 
						private Cache<GroupMessageStatusChange>	groupMessageStatusCache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final ClientConfig	config	= ClientConfig.getInstance();
 | 
						private static final ClientConfig	config	= ClientConfig.getInstance();
 | 
				
			||||||
	private static final Logger			logger	= EnvoyLog.getLogger(Startup.class);
 | 
						private static final Logger			logger	= EnvoyLog.getLogger(Startup.class);
 | 
				
			||||||
@@ -99,14 +103,17 @@ public final class Startup extends Application {
 | 
				
			|||||||
		// Initialize client and unread message cache
 | 
							// Initialize client and unread message cache
 | 
				
			||||||
		client				= new Client();
 | 
							client				= new Client();
 | 
				
			||||||
		messageCache		= new Cache<>();
 | 
							messageCache		= new Cache<>();
 | 
				
			||||||
 | 
							groupMessageCache	= new Cache<>();
 | 
				
			||||||
		messageStatusCache	= new Cache<>();
 | 
							messageStatusCache	= new Cache<>();
 | 
				
			||||||
 | 
							groupMessageStatusCache	= new Cache<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		stage.setTitle("Envoy");
 | 
							stage.setTitle("Envoy");
 | 
				
			||||||
		stage.getIcons().add(IconUtil.loadIcon("envoy_logo"));
 | 
							stage.getIcons().add(IconUtil.loadIcon("envoy_logo"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		final var sceneContext = new SceneContext(stage);
 | 
							final var sceneContext = new SceneContext(stage);
 | 
				
			||||||
		sceneContext.load(SceneInfo.LOGIN_SCENE);
 | 
							sceneContext.load(SceneInfo.LOGIN_SCENE);
 | 
				
			||||||
		sceneContext.<LoginScene>getController().initializeData(client, localDB, messageCache, messageStatusCache, sceneContext);
 | 
							sceneContext.<LoginScene>getController()
 | 
				
			||||||
 | 
								.initializeData(client, localDB, messageCache, groupMessageCache, messageStatusCache, groupMessageStatusCache, sceneContext);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,9 +17,7 @@ import javafx.scene.input.KeyCode;
 | 
				
			|||||||
import javafx.scene.input.KeyEvent;
 | 
					import javafx.scene.input.KeyEvent;
 | 
				
			||||||
import javafx.scene.paint.Color;
 | 
					import javafx.scene.paint.Color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Chat;
 | 
					import envoy.client.data.*;
 | 
				
			||||||
import envoy.client.data.LocalDB;
 | 
					 | 
				
			||||||
import envoy.client.data.Settings;
 | 
					 | 
				
			||||||
import envoy.client.data.audio.AudioRecorder;
 | 
					import envoy.client.data.audio.AudioRecorder;
 | 
				
			||||||
import envoy.client.event.MessageCreationEvent;
 | 
					import envoy.client.event.MessageCreationEvent;
 | 
				
			||||||
import envoy.client.net.Client;
 | 
					import envoy.client.net.Client;
 | 
				
			||||||
@@ -32,9 +30,7 @@ import envoy.client.ui.listcell.MessageControl;
 | 
				
			|||||||
import envoy.client.ui.listcell.MessageListCellFactory;
 | 
					import envoy.client.ui.listcell.MessageListCellFactory;
 | 
				
			||||||
import envoy.data.*;
 | 
					import envoy.data.*;
 | 
				
			||||||
import envoy.data.Attachment.AttachmentType;
 | 
					import envoy.data.Attachment.AttachmentType;
 | 
				
			||||||
import envoy.event.EventBus;
 | 
					import envoy.event.*;
 | 
				
			||||||
import envoy.event.MessageStatusChange;
 | 
					 | 
				
			||||||
import envoy.event.UserStatusChange;
 | 
					 | 
				
			||||||
import envoy.event.contact.ContactOperation;
 | 
					import envoy.event.contact.ContactOperation;
 | 
				
			||||||
import envoy.exception.EnvoyException;
 | 
					import envoy.exception.EnvoyException;
 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
@@ -116,9 +112,8 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
		// Listen to received messages
 | 
							// Listen to received messages
 | 
				
			||||||
		eventBus.register(MessageCreationEvent.class, e -> {
 | 
							eventBus.register(MessageCreationEvent.class, e -> {
 | 
				
			||||||
			final var message = e.get();
 | 
								final var message = e.get();
 | 
				
			||||||
			localDB.getChat(message.getSenderID()).ifPresent(chat -> {
 | 
								localDB.getChat(message instanceof GroupMessage ? message.getRecipientID() : message.getSenderID()).ifPresent(chat -> {
 | 
				
			||||||
				chat.insert(message);
 | 
									chat.insert(message);
 | 
				
			||||||
 | 
					 | 
				
			||||||
				if (chat.equals(currentChat)) {
 | 
									if (chat.equals(currentChat)) {
 | 
				
			||||||
					try {
 | 
										try {
 | 
				
			||||||
						currentChat.read(writeProxy);
 | 
											currentChat.read(writeProxy);
 | 
				
			||||||
@@ -133,11 +128,17 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
		// Listen to message status changes
 | 
							// Listen to message status changes
 | 
				
			||||||
		eventBus.register(MessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(message -> {
 | 
							eventBus.register(MessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(message -> {
 | 
				
			||||||
			message.setStatus(e.get());
 | 
								message.setStatus(e.get());
 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Update UI if in current chat
 | 
								// Update UI if in current chat
 | 
				
			||||||
			if (currentChat != null && message.getSenderID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh);
 | 
								if (currentChat != null && message.getSenderID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh);
 | 
				
			||||||
		}));
 | 
							}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							eventBus.register(GroupMessageStatusChange.class, e -> localDB.getMessage(e.getID()).ifPresent(groupMessage -> {
 | 
				
			||||||
 | 
								((GroupMessage) groupMessage).getMemberStatuses().replace(e.getMemberID(), e.get());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Update UI if in current chat
 | 
				
			||||||
 | 
								if (currentChat != null && groupMessage.getRecipientID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh);
 | 
				
			||||||
 | 
							}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Listen to user status changes
 | 
							// Listen to user status changes
 | 
				
			||||||
		eventBus.register(UserStatusChange.class,
 | 
							eventBus.register(UserStatusChange.class,
 | 
				
			||||||
				e -> userList.getItems()
 | 
									e -> userList.getItems()
 | 
				
			||||||
@@ -152,7 +153,7 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
			switch (e.getOperationType()) {
 | 
								switch (e.getOperationType()) {
 | 
				
			||||||
				case ADD:
 | 
									case ADD:
 | 
				
			||||||
					localDB.getUsers().put(contact.getName(), contact);
 | 
										localDB.getUsers().put(contact.getName(), contact);
 | 
				
			||||||
					localDB.getChats().add(new Chat(contact));
 | 
										localDB.getChats().add(contact instanceof User ? new Chat(contact) : new GroupChat(client.getSender(), contact));
 | 
				
			||||||
					Platform.runLater(() -> userList.getItems().add(contact));
 | 
										Platform.runLater(() -> userList.getItems().add(contact));
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				case REMOVE:
 | 
									case REMOVE:
 | 
				
			||||||
@@ -354,8 +355,8 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Sends a new message to the server based on the text entered in the
 | 
						 * Sends a new {@link Message} or {@link GroupMessage} to the server based on
 | 
				
			||||||
	 * messageTextArea.
 | 
						 * the text entered in the {@code messageTextArea} and the given attachment.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -371,15 +372,18 @@ public final class ChatScene implements Restorable {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		final var text = messageTextArea.getText().strip();
 | 
							final var text = messageTextArea.getText().strip();
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			// Create and send message
 | 
								// 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())
 | 
				
			||||||
				.setText(text);
 | 
									.setText(text);
 | 
				
			||||||
 | 
					      // Setting an attachment, if present
 | 
				
			||||||
      if (pendingAttachment != null) {
 | 
					      if (pendingAttachment != null) {
 | 
				
			||||||
				builder.setAttachment(pendingAttachment);
 | 
									builder.setAttachment(pendingAttachment);
 | 
				
			||||||
				pendingAttachment = null;
 | 
									pendingAttachment = null;
 | 
				
			||||||
				attachmentView.setVisible(false);
 | 
									attachmentView.setVisible(false);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			final var message = builder.build();
 | 
					      // Building the final message
 | 
				
			||||||
 | 
								final var	message	= currentChat.getRecipient() instanceof Group ? builder.buildGroupMessage((Group) currentChat.getRecipient())
 | 
				
			||||||
 | 
										: builder.build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Send message
 | 
								// Send message
 | 
				
			||||||
			writeProxy.writeMessage(message);
 | 
								writeProxy.writeMessage(message);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,13 +16,9 @@ import envoy.client.net.Client;
 | 
				
			|||||||
import envoy.client.ui.ClearableTextField;
 | 
					import envoy.client.ui.ClearableTextField;
 | 
				
			||||||
import envoy.client.ui.SceneContext;
 | 
					import envoy.client.ui.SceneContext;
 | 
				
			||||||
import envoy.client.ui.Startup;
 | 
					import envoy.client.ui.Startup;
 | 
				
			||||||
import envoy.data.LoginCredentials;
 | 
					import envoy.data.*;
 | 
				
			||||||
import envoy.data.Message;
 | 
					 | 
				
			||||||
import envoy.data.User;
 | 
					 | 
				
			||||||
import envoy.data.User.UserStatus;
 | 
					import envoy.data.User.UserStatus;
 | 
				
			||||||
import envoy.event.EventBus;
 | 
					import envoy.event.*;
 | 
				
			||||||
import envoy.event.HandshakeRejection;
 | 
					 | 
				
			||||||
import envoy.event.MessageStatusChange;
 | 
					 | 
				
			||||||
import envoy.exception.EnvoyException;
 | 
					import envoy.exception.EnvoyException;
 | 
				
			||||||
import envoy.util.Bounds;
 | 
					import envoy.util.Bounds;
 | 
				
			||||||
import envoy.util.EnvoyLog;
 | 
					import envoy.util.EnvoyLog;
 | 
				
			||||||
@@ -59,7 +55,9 @@ public final class LoginScene {
 | 
				
			|||||||
	private Client						client;
 | 
						private Client						client;
 | 
				
			||||||
	private LocalDB						localDB;
 | 
						private LocalDB						localDB;
 | 
				
			||||||
	private Cache<Message>				receivedMessageCache;
 | 
						private Cache<Message>				receivedMessageCache;
 | 
				
			||||||
 | 
						private Cache<GroupMessage>			receivedGroupMessageCache;
 | 
				
			||||||
	private Cache<MessageStatusChange>	receivedMessageStatusChangeCache;
 | 
						private Cache<MessageStatusChange>	receivedMessageStatusChangeCache;
 | 
				
			||||||
 | 
						private Cache<GroupMessageStatusChange>	receivedGroupMessageStatusChangeCache;
 | 
				
			||||||
	private SceneContext				sceneContext;
 | 
						private SceneContext				sceneContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final Logger			logger		= EnvoyLog.getLogger(LoginScene.class);
 | 
						private static final Logger			logger		= EnvoyLog.getLogger(LoginScene.class);
 | 
				
			||||||
@@ -79,25 +77,38 @@ public final class LoginScene {
 | 
				
			|||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param client                                the client used to perform the
 | 
						 * @param client                                the client used to perform the
 | 
				
			||||||
	 *                                              handshake
 | 
						 *                                              handshake
 | 
				
			||||||
	 * @param localDB                          the local database used for offline
 | 
						 * @param localDB                               the local database used for
 | 
				
			||||||
 | 
						 *                                              offline
 | 
				
			||||||
	 *                                              login
 | 
						 *                                              login
 | 
				
			||||||
	 * @param receivedMessageCache             the cache storing messages received
 | 
						 * @param receivedMessageCache                  the cache storing messages
 | 
				
			||||||
 | 
						 *                                              received
 | 
				
			||||||
	 *                                              during
 | 
						 *                                              during
 | 
				
			||||||
	 *                                              the handshake
 | 
						 *                                              the handshake
 | 
				
			||||||
 | 
						 * @param receivedGroupMessageCache             the cache storing groupMessages
 | 
				
			||||||
 | 
						 *                                              received during the handshake
 | 
				
			||||||
	 * @param receivedMessageStatusChangeCache      the cache storing
 | 
						 * @param receivedMessageStatusChangeCache      the cache storing
 | 
				
			||||||
	 *                                         messageStatusChangeEvents received
 | 
						 *                                              messageStatusChangeEvents
 | 
				
			||||||
 | 
						 *                                              received
 | 
				
			||||||
	 *                                              during handshake
 | 
						 *                                              during handshake
 | 
				
			||||||
	 * @param sceneContext                     the scene context used to initialize
 | 
						 * @param receivedGroupMessageStatusChangeCache the cache storing
 | 
				
			||||||
 | 
						 *                                              groupMessageStatusChangeEvents
 | 
				
			||||||
 | 
						 *                                              received
 | 
				
			||||||
 | 
						 *                                              during handshake
 | 
				
			||||||
 | 
						 * @param sceneContext                          the scene context used to
 | 
				
			||||||
 | 
						 *                                              initialize
 | 
				
			||||||
	 *                                              the chat
 | 
						 *                                              the chat
 | 
				
			||||||
	 *                                              scene
 | 
						 *                                              scene
 | 
				
			||||||
	 * @since Envoy Client v0.1-beta
 | 
						 * @since Envoy Client v0.1-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void initializeData(Client client, LocalDB localDB, Cache<Message> receivedMessageCache,
 | 
						public void initializeData(Client client, LocalDB localDB, Cache<Message> receivedMessageCache, Cache<GroupMessage> receivedGroupMessageCache,
 | 
				
			||||||
			Cache<MessageStatusChange> receivedMessageStatusChangeCache, SceneContext sceneContext) {
 | 
								Cache<MessageStatusChange> receivedMessageStatusChangeCache, Cache<GroupMessageStatusChange> receivedGroupMessageStatusChangeCache,
 | 
				
			||||||
 | 
								SceneContext sceneContext) {
 | 
				
			||||||
		this.client								= client;
 | 
							this.client								= client;
 | 
				
			||||||
		this.localDB							= localDB;
 | 
							this.localDB							= localDB;
 | 
				
			||||||
		this.receivedMessageCache				= receivedMessageCache;
 | 
							this.receivedMessageCache				= receivedMessageCache;
 | 
				
			||||||
 | 
							this.receivedGroupMessageCache			= receivedGroupMessageCache;
 | 
				
			||||||
		this.receivedMessageStatusChangeCache	= receivedMessageStatusChangeCache;
 | 
							this.receivedMessageStatusChangeCache	= receivedMessageStatusChangeCache;
 | 
				
			||||||
 | 
							this.receivedGroupMessageStatusChangeCache	= receivedGroupMessageStatusChangeCache;
 | 
				
			||||||
		this.sceneContext						= sceneContext;
 | 
							this.sceneContext						= sceneContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Prepare handshake
 | 
							// Prepare handshake
 | 
				
			||||||
@@ -146,9 +157,17 @@ public final class LoginScene {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private void performHandshake(LoginCredentials credentials) {
 | 
						private void performHandshake(LoginCredentials credentials) {
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			client.performHandshake(credentials, receivedMessageCache, receivedMessageStatusChangeCache);
 | 
								client.performHandshake(credentials,
 | 
				
			||||||
 | 
										receivedMessageCache,
 | 
				
			||||||
 | 
										receivedGroupMessageCache,
 | 
				
			||||||
 | 
										receivedMessageStatusChangeCache,
 | 
				
			||||||
 | 
										receivedGroupMessageStatusChangeCache);
 | 
				
			||||||
			if (client.isOnline()) {
 | 
								if (client.isOnline()) {
 | 
				
			||||||
				client.initReceiver(localDB, receivedMessageCache, receivedMessageStatusChangeCache);
 | 
									client.initReceiver(localDB,
 | 
				
			||||||
 | 
											receivedMessageCache,
 | 
				
			||||||
 | 
											receivedGroupMessageCache,
 | 
				
			||||||
 | 
											receivedMessageStatusChangeCache,
 | 
				
			||||||
 | 
											receivedGroupMessageStatusChangeCache);
 | 
				
			||||||
				loadChatScene();
 | 
									loadChatScene();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} catch (IOException | InterruptedException | TimeoutException e) {
 | 
							} catch (IOException | InterruptedException | TimeoutException e) {
 | 
				
			||||||
@@ -212,6 +231,8 @@ public final class LoginScene {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Relay unread messages from cache
 | 
							// Relay unread messages from cache
 | 
				
			||||||
		if (receivedMessageCache != null && client.isOnline()) receivedMessageCache.relay();
 | 
							if (receivedMessageCache != null && client.isOnline()) receivedMessageCache.relay();
 | 
				
			||||||
 | 
							if (receivedGroupMessageCache != null && client.isOnline()) receivedGroupMessageCache.relay();
 | 
				
			||||||
		if (receivedMessageStatusChangeCache != null && client.isOnline()) receivedMessageStatusChangeCache.relay();
 | 
							if (receivedMessageStatusChangeCache != null && client.isOnline()) receivedMessageStatusChangeCache.relay();
 | 
				
			||||||
 | 
							if (receivedGroupMessageStatusChangeCache != null && client.isOnline()) receivedGroupMessageStatusChangeCache.relay();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,12 +57,14 @@ public class MessageControl extends VBox {
 | 
				
			|||||||
		textLabel.setWrapText(true);
 | 
							textLabel.setWrapText(true);
 | 
				
			||||||
		getChildren().add(textLabel);
 | 
							getChildren().add(textLabel);
 | 
				
			||||||
		// Setting the message status icon and background color
 | 
							// Setting the message status icon and background color
 | 
				
			||||||
		if (message.getRecipientID() != client.getID()) {
 | 
							if (message.getSenderID() == client.getID()) {
 | 
				
			||||||
			final var statusIcon = new ImageView(statusImages.get(message.getStatus()));
 | 
								final var statusIcon = new ImageView(statusImages.get(message.getStatus()));
 | 
				
			||||||
			statusIcon.setPreserveRatio(true);
 | 
								statusIcon.setPreserveRatio(true);
 | 
				
			||||||
			getChildren().add(statusIcon);
 | 
								getChildren().add(statusIcon);
 | 
				
			||||||
			getStyleClass().add("own-message");
 | 
								getStyleClass().add("own-message");
 | 
				
			||||||
		} else getStyleClass().add("received-message");
 | 
							} else {
 | 
				
			||||||
 | 
								getStyleClass().add("received-message");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		// Adjusting height and weight of the cell to the corresponding ListView
 | 
							// Adjusting height and weight of the cell to the corresponding ListView
 | 
				
			||||||
		paddingProperty().setValue(new Insets(5, 20, 5, 20));
 | 
							paddingProperty().setValue(new Insets(5, 20, 5, 20));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user