Refactoring #55

Merged
kske merged 7 commits from refactoring into develop 2020-09-27 12:06:39 +02:00
140 changed files with 484 additions and 1524 deletions

View File

@ -9,16 +9,17 @@ import envoy.client.ui.Startup;
* <p> * <p>
* To allow Maven shading, the main method has to be separated from the * To allow Maven shading, the main method has to be separated from the
* {@link Startup} class which extends {@link Application}. * {@link Startup} class which extends {@link Application}.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Main.java</strong><br>
* Created: <strong>05.07.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public final class Main { public final class Main {
/**
* A funny debug switch put in by {@code delvh} to enable easy debugging.
*
* @since Envoy Client v0.2-beta
*/
private static final boolean debug = false; private static final boolean debug = false;
/** /**

View File

@ -1,20 +1,14 @@
package envoy.client.data; package envoy.client.data;
import java.io.Serializable; import java.io.Serializable;
import java.util.LinkedList; import java.util.*;
import java.util.Queue;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.*;
import java.util.logging.Logger;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
/** /**
* Stores elements in a queue to process them later. * Stores elements in a queue to process them later.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Cache.java</strong><br>
* Created: <strong>6 Feb 2020</strong><br>
* *
* @param <T> the type of cached elements * @param <T> the type of cached elements
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart

View File

@ -1,16 +1,11 @@
package envoy.client.data; package envoy.client.data;
import java.io.Serializable; import java.io.Serializable;
import java.util.HashMap; import java.util.*;
import java.util.Map;
/** /**
* Stores a heterogeneous map of {@link Cache} objects with different type * Stores a heterogeneous map of {@link Cache} objects with different type
* parameters. * parameters.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>CacheMap.java</strong><br>
* Created: <strong>09.07.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta

View File

@ -1,25 +1,18 @@
package envoy.client.data; package envoy.client.data;
import java.io.IOException; import java.io.*;
import java.io.Serializable; import java.util.*;
import java.util.ArrayList;
import java.util.List; import javafx.collections.*;
import java.util.Objects;
import envoy.client.net.WriteProxy; import envoy.client.net.WriteProxy;
import envoy.data.Contact; import envoy.data.*;
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;
/** /**
* Represents a chat between two {@link User}s * Represents a chat between two {@link User}s
* as a list of {@link Message} objects. * as a list of {@link Message} objects.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Chat.java</strong><br>
* Created: <strong>19 Oct 2019</strong><br>
* *
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @author Leon Hofmeister * @author Leon Hofmeister
@ -28,8 +21,9 @@ import envoy.event.MessageStatusChange;
*/ */
public class Chat implements Serializable { public class Chat implements Serializable {
protected final Contact recipient; protected final Contact recipient;
protected final List<Message> messages = new ArrayList<>();
protected transient ObservableList<Message> messages = FXCollections.observableArrayList();
protected int unreadAmount; protected int unreadAmount;
@ -38,7 +32,7 @@ public class Chat implements Serializable {
*/ */
protected transient long lastWritingEvent; protected transient long lastWritingEvent;
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 2L;
/** /**
* Provides the list of messages that the recipient receives. * Provides the list of messages that the recipient receives.
@ -50,8 +44,18 @@ public class Chat implements Serializable {
*/ */
public Chat(Contact recipient) { this.recipient = recipient; } public Chat(Contact recipient) { this.recipient = recipient; }
private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException {
stream.defaultReadObject();
messages = FXCollections.observableList((List<Message>) stream.readObject());
}
private void writeObject(ObjectOutputStream stream) throws IOException {
stream.defaultWriteObject();
stream.writeObject(new ArrayList<>(messages));
}
@Override @Override
public String toString() { return String.format("Chat[recipient=%s,messages=%d]", recipient, messages.size()); } public String toString() { return String.format("%s[recipient=%s,messages=%d]", getClass().getSimpleName(), recipient, messages.size()); }
/** /**
* Generates a hash code based on the recipient. * Generates a hash code based on the recipient.
@ -81,11 +85,9 @@ public class Chat implements Serializable {
* *
* @param writeProxy the write proxy instance used to notify the server about * @param writeProxy the write proxy instance used to notify the server about
* the message status changes * the message status changes
* @throws IOException if a {@link MessageStatusChange} could not be
* delivered to the server
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void read(WriteProxy writeProxy) throws IOException { public void read(WriteProxy writeProxy) {
for (int i = messages.size() - 1; i >= 0; --i) { for (int i = messages.size() - 1; i >= 0; --i) {
final Message m = messages.get(i); final Message m = messages.get(i);
if (m.getSenderID() == recipient.getID()) if (m.getStatus() == MessageStatus.READ) break; if (m.getSenderID() == recipient.getID()) if (m.getStatus() == MessageStatus.READ) break;
@ -136,7 +138,7 @@ public class Chat implements Serializable {
* @return all messages in the current chat * @return all messages in the current chat
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public List<Message> getMessages() { return messages; } public ObservableList<Message> getMessages() { return messages; }
/** /**
* @return the recipient of a message * @return the recipient of a message

View File

@ -7,10 +7,6 @@ import envoy.data.Config;
/** /**
* Implements a configuration specific to the Envoy Client with default values * Implements a configuration specific to the Envoy Client with default values
* and convenience methods. * and convenience methods.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ClientConfig.java</strong><br>
* Created: <strong>01.03.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta

View File

@ -2,16 +2,11 @@ package envoy.client.data;
import javafx.stage.Stage; import javafx.stage.Stage;
import envoy.client.net.Client; import envoy.client.net.*;
import envoy.client.net.WriteProxy;
import envoy.client.ui.SceneContext; import envoy.client.ui.SceneContext;
/** /**
* Provides access to commonly used objects. * Provides access to commonly used objects.
* <p>
* Project: <strong>client</strong><br>
* File: <strong>Context.java</strong><br>
* Created: <strong>01.09.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta

View File

@ -1,22 +1,15 @@
package envoy.client.data; package envoy.client.data;
import java.io.IOException;
import java.time.Instant; import java.time.Instant;
import envoy.client.net.WriteProxy; import envoy.client.net.WriteProxy;
import envoy.data.Contact; import envoy.data.*;
import envoy.data.GroupMessage;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
import envoy.data.User;
import envoy.event.GroupMessageStatusChange; import envoy.event.GroupMessageStatusChange;
/** /**
* Represents a chat between a user and a group * Represents a chat between a user and a group
* as a list of messages. * 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&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
@ -38,7 +31,7 @@ public final class GroupChat extends Chat {
} }
@Override @Override
public void read(WriteProxy writeProxy) throws IOException { public void read(WriteProxy writeProxy) {
for (int i = messages.size() - 1; i >= 0; --i) { for (int i = messages.size() - 1; i >= 0; --i) {
final GroupMessage gmsg = (GroupMessage) messages.get(i); final GroupMessage gmsg = (GroupMessage) messages.get(i);
if (gmsg.getSenderID() != sender.getID()) if (gmsg.getMemberStatuses().get(sender.getID()) == MessageStatus.READ) break; if (gmsg.getSenderID() != sender.getID()) if (gmsg.getMemberStatuses().get(sender.getID()) == MessageStatus.READ) break;

View File

@ -5,10 +5,13 @@ import java.nio.channels.*;
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.time.Instant; import java.time.Instant;
import java.util.*; import java.util.*;
import java.util.logging.Level; import java.util.logging.*;
import javafx.collections.*;
import envoy.client.event.EnvoyCloseEvent; import envoy.client.event.EnvoyCloseEvent;
import envoy.data.*; import envoy.data.*;
import envoy.data.Message.MessageStatus;
import envoy.event.*; import envoy.event.*;
import envoy.exception.EnvoyException; import envoy.exception.EnvoyException;
import envoy.util.*; import envoy.util.*;
@ -22,10 +25,6 @@ import dev.kske.eventbus.EventListener;
* For message ID generation a {@link IDGenerator} is stored as well. * For message ID generation a {@link IDGenerator} is stored as well.
* <p> * <p>
* The managed objects are stored inside a folder in the local file system. * The managed objects are stored inside a folder in the local file system.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>LocalDB.java</strong><br>
* Created: <strong>3 Feb 2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
@ -33,12 +32,12 @@ import dev.kske.eventbus.EventListener;
public final class LocalDB implements EventListener { public final class LocalDB implements EventListener {
// Data // Data
private User user; private User user;
private Map<String, User> users = Collections.synchronizedMap(new HashMap<>()); private Map<String, User> users = Collections.synchronizedMap(new HashMap<>());
private List<Chat> chats = Collections.synchronizedList(new ArrayList<>()); private ObservableList<Chat> chats = FXCollections.observableArrayList();
private IDGenerator idGenerator; private IDGenerator idGenerator;
private CacheMap cacheMap = new CacheMap(); private CacheMap cacheMap = new CacheMap();
private String authToken; private String authToken;
// State management // State management
private Instant lastSync = Instant.EPOCH; private Instant lastSync = Instant.EPOCH;
@ -49,6 +48,8 @@ public final class LocalDB implements EventListener {
private final File dbDir, idGeneratorFile, lastLoginFile, usersFile; private final File dbDir, idGeneratorFile, lastLoginFile, usersFile;
private static final Logger logger = EnvoyLog.getLogger(LocalDB.class);
/** /**
* Constructs an empty local database. * Constructs an empty local database.
* *
@ -130,7 +131,7 @@ public final class LocalDB implements EventListener {
if (user == null) throw new IllegalStateException("Client user is null, cannot initialize user storage"); if (user == null) throw new IllegalStateException("Client user is null, cannot initialize user storage");
userFile = new File(dbDir, user.getID() + ".db"); userFile = new File(dbDir, user.getID() + ".db");
try (var in = new ObjectInputStream(new FileInputStream(userFile))) { try (var in = new ObjectInputStream(new FileInputStream(userFile))) {
chats = (List<Chat>) in.readObject(); chats = FXCollections.observableList((List<Chat>) in.readObject());
cacheMap = (CacheMap) in.readObject(); cacheMap = (CacheMap) in.readObject();
lastSync = (Instant) in.readObject(); lastSync = (Instant) in.readObject();
} finally { } finally {
@ -190,8 +191,8 @@ public final class LocalDB implements EventListener {
SerializationUtils.write(usersFile, users); SerializationUtils.write(usersFile, users);
// Save user data and last sync time stamp // Save user data and last sync time stamp
if (user != null) if (user != null) SerializationUtils
SerializationUtils.write(userFile, chats, cacheMap, Context.getInstance().getClient().isOnline() ? Instant.now() : lastSync); .write(userFile, new ArrayList<>(chats), cacheMap, Context.getInstance().getClient().isOnline() ? Instant.now() : lastSync);
// Save last login information // Save last login information
if (authToken != null) SerializationUtils.write(lastLoginFile, user, authToken); if (authToken != null) SerializationUtils.write(lastLoginFile, user, authToken);
@ -203,6 +204,37 @@ public final class LocalDB implements EventListener {
} }
} }
@Event(priority = 150)
private void onMessage(Message msg) { if (msg.getStatus() == MessageStatus.SENT) msg.nextStatus(); }
@Event(priority = 150)
private void onGroupMessage(GroupMessage msg) {
// TODO: Cancel event once EventBus is updated
if (msg.getStatus() == MessageStatus.WAITING || msg.getStatus() == MessageStatus.READ)
logger.warning("The groupMessage has the unexpected status " + msg.getStatus());
}
@Event(priority = 150)
private void onMessageStatusChange(MessageStatusChange evt) { getMessage(evt.getID()).ifPresent(msg -> msg.setStatus(evt.get())); }
@Event(priority = 150)
private void onGroupMessageStatusChange(GroupMessageStatusChange evt) {
this.<GroupMessage>getMessage(evt.getID()).ifPresent(msg -> msg.getMemberStatuses().replace(evt.getMemberID(), evt.get()));
}
@Event(priority = 150)
private void onUserStatusChange(UserStatusChange evt) {
this.getChat(evt.getID()).map(Chat::getRecipient).map(User.class::cast).ifPresent(u -> u.setStatus(evt.get()));
}
@Event(priority = 150)
private void onGroupResize(GroupResize evt) { getChat(evt.getGroupID()).map(Chat::getRecipient).map(Group.class::cast).ifPresent(evt::apply); }
@Event(priority = 150)
private void onNameChange(NameChange evt) {
chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == evt.getID()).findAny().ifPresent(c -> c.setName(evt.get()));
}
/** /**
* Stores a new authentication token. * Stores a new authentication token.
* *
@ -219,17 +251,32 @@ public final class LocalDB implements EventListener {
*/ */
public Map<String, User> getUsers() { return users; } public Map<String, User> getUsers() { return users; }
/**
* Searches for a message by ID.
*
* @param id the ID of the message to search for
* @return an optional containing the message
* @since Envoy Client v0.1-beta
*/
public <T extends Message> Optional<T> getMessage(long id) {
return (Optional<T>) chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny();
}
/**
* Searches for a chat by recipient ID.
*
* @param recipientID the ID of the chat's recipient
* @return an optional containing the chat
* @since Envoy Client v0.1-beta
*/
public Optional<Chat> getChat(long recipientID) { return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny(); }
/** /**
* @return all saved {@link Chat} objects that list the client user as the * @return all saved {@link Chat} objects that list the client user as the
* sender * sender
* @since Envoy Client v0.1-alpha * @since Envoy Client v0.1-alpha
**/ **/
public List<Chat> getChats() { return chats; } public ObservableList<Chat> getChats() { return chats; }
/**
* @param chats the chats to set
*/
public void setChats(List<Chat> chats) { this.chats = chats; }
/** /**
* @return the {@link User} who initialized the local database * @return the {@link User} who initialized the local database
@ -253,6 +300,7 @@ public final class LocalDB implements EventListener {
* @param idGenerator the message ID generator to set * @param idGenerator the message ID generator to set
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
@Event(priority = 150)
public void setIDGenerator(IDGenerator idGenerator) { this.idGenerator = idGenerator; } public void setIDGenerator(IDGenerator idGenerator) { this.idGenerator = idGenerator; }
/** /**
@ -278,59 +326,4 @@ public final class LocalDB implements EventListener {
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public String getAuthToken() { return authToken; } public String getAuthToken() { return authToken; }
/**
* Searches for a message by ID.
*
* @param id the ID of the message to search for
* @return an optional containing the message
* @since Envoy Client v0.1-beta
*/
public Optional<Message> getMessage(long id) {
return chats.stream().map(Chat::getMessages).flatMap(List::stream).filter(m -> m.getID() == id).findAny();
}
/**
* Searches for a chat by recipient ID.
*
* @param recipientID the ID of the chat's recipient
* @return an optional containing the chat
* @since Envoy Client v0.1-beta
*/
public Optional<Chat> getChat(long recipientID) { return chats.stream().filter(c -> c.getRecipient().getID() == recipientID).findAny(); }
/**
* Performs a contact name change if the corresponding contact is present.
*
* @param event the {@link NameChange} to process
* @since Envoy Client v0.1-beta
*/
public void replaceContactName(NameChange event) {
chats.stream().map(Chat::getRecipient).filter(c -> c.getID() == event.getID()).findAny().ifPresent(c -> c.setName(event.get()));
}
/**
* Performs a group resize operation if the corresponding group is present.
*
* @param event the {@link GroupResize} to process
* @since Envoy Client v0.1-beta
*/
public void updateGroup(GroupResize event) {
chats.stream()
.map(Chat::getRecipient)
.filter(Group.class::isInstance)
.filter(g -> g.getID() == event.getGroupID() && g.getID() != user.getID())
.map(Group.class::cast)
.findAny()
.ifPresent(group -> {
switch (event.getOperation()) {
case ADD:
group.getContacts().add(event.get());
break;
case REMOVE:
group.getContacts().remove(event.get());
break;
}
});
}
} }

View File

@ -15,10 +15,6 @@ import dev.kske.eventbus.EventListener;
* Manages all application settings, which are different objects that can be * Manages all application settings, which are different objects that can be
* changed during runtime and serialized them by using either the file system or * changed during runtime and serialized them by using either the file system or
* the {@link Preferences} API. * the {@link Preferences} API.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Settings.java</strong><br>
* Created: <strong>11 Nov 2019</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer

View File

@ -8,10 +8,6 @@ import javax.swing.JComponent;
/** /**
* Encapsulates a persistent value that is directly or indirectly mutable by the * Encapsulates a persistent value that is directly or indirectly mutable by the
* user. * user.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsItem.java</strong><br>
* Created: <strong>23.12.2019</strong><br>
* *
* @param <T> the type of this {@link SettingsItem}'s value * @param <T> the type of this {@link SettingsItem}'s value
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart

View File

@ -6,10 +6,6 @@ import envoy.exception.EnvoyException;
/** /**
* Plays back audio from a byte array. * Plays back audio from a byte array.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>AudioPlayer.java</strong><br>
* Created: <strong>05.07.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta

View File

@ -1,8 +1,7 @@
package envoy.client.data.audio; package envoy.client.data.audio;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.*;
import java.nio.file.Path;
import javax.sound.sampled.*; import javax.sound.sampled.*;
@ -10,10 +9,6 @@ import envoy.exception.EnvoyException;
/** /**
* Records audio and exports it as a byte array. * Records audio and exports it as a byte array.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>AudioRecorder.java</strong><br>
* Created: <strong>02.07.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta

View File

@ -1,9 +1,5 @@
/** /**
* Contains classes related to recording and playing back audio clips. * Contains classes related to recording and playing back audio clips.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>05.07.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta

View File

@ -5,10 +5,6 @@ import java.util.function.Supplier;
/** /**
* This interface defines an action that should be performed when a system * This interface defines an action that should be performed when a system
* command gets called. * command gets called.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>OnCall.java</strong><br>
* Created: <strong>23.07.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta

View File

@ -1,10 +1,7 @@
package envoy.client.data.commands; package envoy.client.data.commands;
import java.util.ArrayList; import java.util.*;
import java.util.List; import java.util.function.*;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
/** /**
* This class is the base class of all {@code SystemCommands} and contains an * This class is the base class of all {@code SystemCommands} and contains an
@ -16,10 +13,6 @@ import java.util.function.Supplier;
* function. This approach has one limitation:<br> * function. This approach has one limitation:<br>
* <b>Order matters!</b> Changing the order of arguments will likely result in * <b>Order matters!</b> Changing the order of arguments will likely result in
* unexpected behavior. * unexpected behavior.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SystemCommand.java</strong><br>
* Created: <strong>16.07.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta

View File

@ -1,15 +1,10 @@
package envoy.client.data.commands; package envoy.client.data.commands;
import java.util.ArrayList; import java.util.*;
import java.util.List;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
* This class acts as a builder for {@link SystemCommand}s. * This class acts as a builder for {@link SystemCommand}s.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SystemCommandBuilder.java</strong><br>
* Created: <strong>23.07.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta

View File

@ -10,10 +10,6 @@ import envoy.util.EnvoyLog;
/** /**
* This class stores all {@link SystemCommand}s used. * This class stores all {@link SystemCommand}s used.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SystemCommandsMap.java</strong><br>
* Created: <strong>17.07.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta

View File

@ -1,10 +1,6 @@
/** /**
* This package contains all classes that can be used as system commands.<br> * This package contains all classes that can be used as system commands.<br>
* Every system command can be called using a specific syntax:"/&lt;command&gt;" * Every system command can be called using a specific syntax:"/&lt;command&gt;"
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>16.07.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta

View File

@ -3,12 +3,8 @@ package envoy.client.event;
import envoy.event.Event.Valueless; import envoy.event.Event.Valueless;
/** /**
* This event serves the purpose to trigger the tab change to tab 0 in * This event serves the purpose of triggering the tab change to tab 0 in
* {@link envoy.client.ui.controller.ChatScene}. * {@link envoy.client.ui.controller.ChatScene}.
* <p>
* Project: <strong>client</strong><br>
* File: <strong>BackEvent.java</strong><br>
* Created: <strong>23.08.2020</strong><br>
* *
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta

View File

@ -3,9 +3,9 @@ package envoy.client.event;
import envoy.event.Event.Valueless; import envoy.event.Event.Valueless;
/** /**
* This event will be sent once Envoy is <strong>really</strong> closed. * This event notifies various Envoy components of the application being about
* Its purpose is to forcefully stop other threads peacefully so that the VM can * to shut down. This allows the graceful closing of connections, persisting
* shutdown too. * local data etc.
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta

View File

@ -1,22 +0,0 @@
package envoy.client.event;
import envoy.event.Event;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>SendEvent.java</strong><br>
* Created: <strong>11.02.2020</strong><br>
*
* @author: Maximilian K&aumlfer
* @since Envoy Client v0.3-alpha
*/
public final class SendEvent extends Event<Event<?>> {
private static final long serialVersionUID = 0L;
/**
* @param value the event to send to the server
*/
public SendEvent(Event<?> value) { super(value); }
}

View File

@ -3,9 +3,7 @@ package envoy.client.event;
import envoy.event.Event; import envoy.event.Event;
/** /**
* Project: <strong>envoy-client</strong><br> * Notifies UI components of a theme change.
* File: <strong>ThemeChangeEvent.java</strong><br>
* Created: <strong>15 Dec 2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.2-alpha * @since Envoy Client v0.2-alpha

View File

@ -6,22 +6,17 @@ import java.util.concurrent.TimeoutException;
import java.util.logging.*; import java.util.logging.*;
import envoy.client.data.*; import envoy.client.data.*;
import envoy.client.event.*; import envoy.client.event.EnvoyCloseEvent;
import envoy.data.*; import envoy.data.*;
import envoy.event.*; import envoy.event.*;
import envoy.event.Event;
import envoy.event.contact.*;
import envoy.util.*; import envoy.util.*;
import dev.kske.eventbus.*; import dev.kske.eventbus.*;
import dev.kske.eventbus.Event;
/** /**
* Establishes a connection to the server, performs a handshake and delivers * Establishes a connection to the server, performs a handshake and delivers
* certain objects to the server. * certain objects to the server.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Client.java</strong><br>
* Created: <strong>28 Sep 2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
@ -79,8 +74,6 @@ public final class Client implements EventListener, Closeable {
// authentication token // authentication token
receiver.registerProcessor(User.class, sender -> this.sender = sender); receiver.registerProcessor(User.class, sender -> this.sender = sender);
receiver.registerProcessors(cacheMap.getMap()); receiver.registerProcessors(cacheMap.getMap());
receiver.registerProcessor(HandshakeRejection.class, evt -> { rejected = true; eventBus.dispatch(evt); });
receiver.registerProcessor(NewAuthToken.class, eventBus::dispatch);
rejected = false; rejected = false;
@ -128,113 +121,64 @@ public final class Client implements EventListener, Closeable {
// Remove all processors as they are only used during the handshake // Remove all processors as they are only used during the handshake
receiver.removeAllProcessors(); receiver.removeAllProcessors();
// Process incoming messages
final var receivedMessageProcessor = new ReceivedMessageProcessor();
final var receivedGroupMessageProcessor = new ReceivedGroupMessageProcessor();
final var messageStatusChangeProcessor = new MessageStatusChangeProcessor();
final var groupMessageStatusChangeProcessor = new GroupMessageStatusChangeProcessor();
receiver.registerProcessor(GroupMessage.class, receivedGroupMessageProcessor);
receiver.registerProcessor(Message.class, receivedMessageProcessor);
receiver.registerProcessor(MessageStatusChange.class, messageStatusChangeProcessor);
receiver.registerProcessor(GroupMessageStatusChange.class, groupMessageStatusChangeProcessor);
// Relay cached messages and message status changes // Relay cached messages and message status changes
cacheMap.get(Message.class).setProcessor(receivedMessageProcessor); cacheMap.get(Message.class).setProcessor(eventBus::dispatch);
cacheMap.get(GroupMessage.class).setProcessor(receivedGroupMessageProcessor); cacheMap.get(GroupMessage.class).setProcessor(eventBus::dispatch);
cacheMap.get(MessageStatusChange.class).setProcessor(messageStatusChangeProcessor); cacheMap.get(MessageStatusChange.class).setProcessor(eventBus::dispatch);
cacheMap.get(GroupMessageStatusChange.class).setProcessor(groupMessageStatusChangeProcessor); cacheMap.get(GroupMessageStatusChange.class).setProcessor(eventBus::dispatch);
// Process user status changes
receiver.registerProcessor(UserStatusChange.class, eventBus::dispatch);
// Process message ID generation
receiver.registerProcessor(IDGenerator.class, localDB::setIDGenerator);
// Process name changes
receiver.registerProcessor(NameChange.class, evt -> { localDB.replaceContactName(evt); eventBus.dispatch(evt); });
// Process contact searches
receiver.registerProcessor(UserSearchResult.class, eventBus::dispatch);
// Process contact operations
receiver.registerProcessor(ContactOperation.class, eventBus::dispatch);
// Process group size changes
receiver.registerProcessor(GroupResize.class, evt -> { localDB.updateGroup(evt); eventBus.dispatch(evt); });
// Process IsTyping events
receiver.registerProcessor(IsTyping.class, eventBus::dispatch);
// Process PasswordChangeResults
receiver.registerProcessor(PasswordChangeResult.class, eventBus::dispatch);
// Process ProfilePicChanges
receiver.registerProcessor(ProfilePicChange.class, eventBus::dispatch);
// Process requests to not send any more attachments as they will not be shown
// to other users
receiver.registerProcessor(NoAttachments.class, eventBus::dispatch);
// Process group creation results - they might have been disabled on the server
receiver.registerProcessor(GroupCreationResult.class, eventBus::dispatch);
// Request a generator if none is present or the existing one is consumed // Request a generator if none is present or the existing one is consumed
if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext()) requestIdGenerator(); if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext()) requestIDGenerator();
// Relay caches // Relay caches
cacheMap.getMap().values().forEach(Cache::relay); cacheMap.getMap().values().forEach(Cache::relay);
} }
/**
* Sends an object to the server.
*
* @param obj the object to send
* @throws IllegalStateException if the client is not online
* @throws RuntimeException if the object serialization failed
* @since Envoy Client v0.2-beta
*/
public void send(Serializable obj) throws IllegalStateException, RuntimeException {
checkOnline();
logger.log(Level.FINE, "Sending " + obj);
try {
SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/** /**
* Sends a message to the server. The message's status will be incremented once * Sends a message to the server. The message's status will be incremented once
* it was delivered successfully. * it was delivered successfully.
* *
* @param message the message to send * @param message the message to send
* @throws IOException if the message does not reach the server
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void sendMessage(Message message) throws IOException { public void sendMessage(Message message) {
writeObject(message); send(message);
message.nextStatus(); message.nextStatus();
} }
/**
* Sends an event to the server.
*
* @param evt the event to send
* @throws IOException if the event did not reach the server
*/
public void sendEvent(Event<?> evt) throws IOException { if (online) writeObject(evt); }
/** /**
* Requests a new {@link IDGenerator} from the server. * Requests a new {@link IDGenerator} from the server.
* *
* @throws IOException if the request does not reach the server
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void requestIdGenerator() throws IOException { public void requestIDGenerator() {
logger.log(Level.INFO, "Requesting new id generator..."); logger.log(Level.INFO, "Requesting new id generator...");
writeObject(new IDGeneratorRequest()); send(new IDGeneratorRequest());
} }
/** @Event(eventType = HandshakeRejection.class, priority = 1000)
* Sends the value of a send event to the server. private void onHandshakeRejection() { rejected = true; }
*
* @param evt the send event to extract the value from
* @since Envoy Client v0.2-beta
*/
@dev.kske.eventbus.Event
private void onSendEvent(SendEvent evt) {
try {
sendEvent(evt.get());
} catch (final IOException e) {
logger.log(Level.WARNING, "An error occurred when trying to send " + evt, e);
}
}
@Override @Override
@dev.kske.eventbus.Event(eventType = EnvoyCloseEvent.class, priority = 800) @Event(eventType = EnvoyCloseEvent.class, priority = 800)
public void close() { public void close() {
if (online) { if (online) {
logger.log(Level.INFO, "Closing connection..."); logger.log(Level.INFO, "Closing connection...");
@ -244,13 +188,13 @@ public final class Client implements EventListener, Closeable {
} }
} }
private void writeObject(Object obj) throws IOException { /**
checkOnline(); * Ensured that the client is online.
logger.log(Level.FINE, "Sending " + obj); *
SerializationUtils.writeBytesWithLength(obj, socket.getOutputStream()); * @throws IllegalStateException if the client is not online
} * @since Envoy Client v0.3-alpha
*/
private void checkOnline() { if (!online) throw new IllegalStateException("Client is not online"); } private void checkOnline() throws IllegalStateException { if (!online) throw new IllegalStateException("Client is not online"); }
/** /**
* @return the {@link User} as which this client is logged in * @return the {@link User} as which this client is logged in

View File

@ -1,29 +0,0 @@
package envoy.client.net;
import java.util.function.Consumer;
import java.util.logging.Logger;
import envoy.data.Message.MessageStatus;
import envoy.event.GroupMessageStatusChange;
import envoy.util.EnvoyLog;
import dev.kske.eventbus.EventBus;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>GroupMessageStatusChangePocessor.java</strong><br>
* Created: <strong>03.07.2020</strong><br>
*
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
public final 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);
}
}

View File

@ -1,36 +0,0 @@
package envoy.client.net;
import java.util.function.Consumer;
import java.util.logging.Logger;
import envoy.data.Message.MessageStatus;
import envoy.event.MessageStatusChange;
import envoy.util.EnvoyLog;
import dev.kske.eventbus.EventBus;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageStatusChangeProcessor.java</strong><br>
* Created: <strong>4 Feb 2020</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
public final class MessageStatusChangeProcessor implements Consumer<MessageStatusChange> {
private static final Logger logger = EnvoyLog.getLogger(MessageStatusChangeProcessor.class);
/**
* Dispatches a {@link MessageStatusChange} if the status is
* {@code RECEIVED} or {@code READ}.
*
* @param evt the status change event
* @since Envoy Client v0.3-alpha
*/
@Override
public void accept(MessageStatusChange evt) {
if (evt.get().ordinal() < MessageStatus.RECEIVED.ordinal()) logger.warning("Received invalid message status change " + evt);
else EventBus.getInstance().dispatch(evt);
}
}

View File

@ -1,33 +0,0 @@
package envoy.client.net;
import java.util.function.Consumer;
import java.util.logging.Logger;
import envoy.data.GroupMessage;
import envoy.data.Message.MessageStatus;
import envoy.util.EnvoyLog;
import dev.kske.eventbus.EventBus;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>ReceivedGroupMessageProcessor.java</strong><br>
* Created: <strong>13.06.2020</strong><br>
*
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta
*/
public final 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(groupMessage);
}
}

View File

@ -1,28 +0,0 @@
package envoy.client.net;
import java.util.function.Consumer;
import envoy.data.Message;
import envoy.data.Message.MessageStatus;
import dev.kske.eventbus.EventBus;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>ReceivedMessageProcessor.java</strong><br>
* Created: <strong>31.12.2019</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha
*/
public final class ReceivedMessageProcessor implements Consumer<Message> {
@Override
public void accept(Message message) {
// Update status to RECEIVED
if (message.getStatus() == MessageStatus.SENT) message.nextStatus();
// Dispatch message
EventBus.getInstance().dispatch(message);
}
}

View File

@ -8,13 +8,11 @@ import java.util.logging.*;
import envoy.util.*; import envoy.util.*;
import dev.kske.eventbus.*;
/** /**
* Receives objects from the server and passes them to processor objects based * Receives objects from the server and passes them to processor objects based
* on their class. * on their class.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Receiver.java</strong><br>
* Created: <strong>30.12.2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
@ -26,7 +24,8 @@ public final class Receiver extends Thread {
private final InputStream in; private final InputStream in;
private final Map<Class<?>, Consumer<?>> processors = new HashMap<>(); private final Map<Class<?>, Consumer<?>> processors = new HashMap<>();
private static final Logger logger = EnvoyLog.getLogger(Receiver.class); private static final EventBus eventBus = EventBus.getInstance();
private static final Logger logger = EnvoyLog.getLogger(Receiver.class);
/** /**
* Creates an instance of {@link Receiver}. * Creates an instance of {@link Receiver}.
@ -81,9 +80,14 @@ public final 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, String.format("The received object has the %s for which no processor is defined.", obj.getClass())); // Dispatch to the processor if present
else processor.accept(obj); if (processor != null) processor.accept(obj);
// Dispatch to the event bus if the object is an event without a processor
else if (obj instanceof IEvent) eventBus.dispatch((IEvent) obj);
// Notify if no processor could be located
else logger.log(Level.WARNING,
String.format("The received object has the %s for which no processor is defined.", obj.getClass()));
} }
} catch (final SocketException | EOFException e) { } catch (final SocketException | EOFException e) {
// Connection probably closed by client. // Connection probably closed by client.

View File

@ -1,11 +1,8 @@
package envoy.client.net; package envoy.client.net;
import java.io.IOException; import java.util.logging.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import envoy.client.data.Cache; import envoy.client.data.*;
import envoy.client.data.LocalDB;
import envoy.data.Message; import envoy.data.Message;
import envoy.event.MessageStatusChange; import envoy.event.MessageStatusChange;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
@ -14,10 +11,6 @@ import envoy.util.EnvoyLog;
* Implements methods to send {@link Message}s and * Implements methods to send {@link Message}s and
* {@link MessageStatusChange}s to the server or cache them inside a * {@link MessageStatusChange}s to the server or cache them inside a
* {@link LocalDB} depending on the online status. * {@link LocalDB} depending on the online status.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>WriteProxy.java</strong><br>
* Created: <strong>6 Feb 2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
@ -44,20 +37,12 @@ public final class WriteProxy {
// Initialize cache processors for messages and message status change events // Initialize cache processors for messages and message status change events
localDB.getCacheMap().get(Message.class).setProcessor(msg -> { localDB.getCacheMap().get(Message.class).setProcessor(msg -> {
try { logger.log(Level.FINER, "Sending cached " + msg);
logger.log(Level.FINER, "Sending cached " + msg); client.sendMessage(msg);
client.sendMessage(msg);
} catch (final IOException e) {
logger.log(Level.SEVERE, "Could not send cached message: ", e);
}
}); });
localDB.getCacheMap().get(MessageStatusChange.class).setProcessor(evt -> { localDB.getCacheMap().get(MessageStatusChange.class).setProcessor(evt -> {
logger.log(Level.FINER, "Sending cached " + evt); logger.log(Level.FINER, "Sending cached " + evt);
try { client.send(evt);
client.sendEvent(evt);
} catch (final IOException e) {
logger.log(Level.SEVERE, "Could not send cached message status change event: ", e);
}
}); });
} }
@ -74,10 +59,9 @@ public final class WriteProxy {
* inside the local database. * inside the local database.
* *
* @param message the message to send * @param message the message to send
* @throws IOException if the message could not be sent
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void writeMessage(Message message) throws IOException { public void writeMessage(Message message) {
if (client.isOnline()) client.sendMessage(message); if (client.isOnline()) client.sendMessage(message);
else localDB.getCacheMap().getApplicable(Message.class).accept(message); else localDB.getCacheMap().getApplicable(Message.class).accept(message);
} }
@ -87,11 +71,10 @@ public final class WriteProxy {
* event is cached inside the local database. * event is cached inside the local database.
* *
* @param evt the event to send * @param evt the event to send
* @throws IOException if the event could not be sent
* @since Envoy Client v0.3-alpha * @since Envoy Client v0.3-alpha
*/ */
public void writeMessageStatusChange(MessageStatusChange evt) throws IOException { public void writeMessageStatusChange(MessageStatusChange evt) {
if (client.isOnline()) client.sendEvent(evt); if (client.isOnline()) client.send(evt);
else localDB.getCacheMap().getApplicable(MessageStatusChange.class).accept(evt); else localDB.getCacheMap().getApplicable(MessageStatusChange.class).accept(evt);
} }
} }

View File

@ -1,169 +0,0 @@
package envoy.client.ui;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.StringProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Background;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.GridPane;
/**
* This class offers a text field that is automatically equipped with a clear
* button.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ClearableTextField.java</strong><br>
* Created: <strong>25.06.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public final class ClearableTextField extends GridPane {
private final TextField textField;
private final Button clearButton;
/**
* Constructs a new {@code ClearableTextField} with no initial text and icon
* size 16.
*
* @since Envoy Client v0.1-beta
*/
public ClearableTextField() { this("", 16); }
/**
* Constructs a new {@code ClearableTextField} with initial text and a
* predetermined icon size.
*
* @param text the text that should be displayed by default
* @param size the size of the icon
* @since Envoy Client v0.1-beta
*/
public ClearableTextField(String text, int size) {
// initializing the textField and the button
textField = new TextField(text);
clearButton = new Button("", new ImageView(IconUtil.loadIconThemeSensitive("clear_button", size)));
clearButton.setOnAction(e -> textField.clear());
clearButton.setFocusTraversable(false);
clearButton.getStyleClass().clear();
clearButton.setBackground(Background.EMPTY);
// Adding the two elements to the GridPane
add(textField, 0, 0, 2, 1);
add(clearButton, 1, 0, 1, 1);
// Setting the percent - widths of the two columns.
// Used to locate the button on the right.
final var columnConstraints = new ColumnConstraints();
columnConstraints.setPercentWidth(90);
getColumnConstraints().add(columnConstraints);
final var columnConstraints2 = new ColumnConstraints();
columnConstraints2.setPercentWidth(10);
getColumnConstraints().add(columnConstraints2);
}
/**
* @return the underlying {@code textField}
* @since Envoy Client v0.1-beta
*/
public TextField getTextField() { return textField; }
/**
* This method offers the freedom to perform custom actions when the
* {@code clearButton} has been pressed.
* <p>
* The default is
* <b><code> e -> {clearableTextField.getTextField().clear();}</code></b>
*
* @param onClearButtonAction the action that should be performed
* @since Envoy Client v0.1-beta
*/
public void setClearButtonListener(EventHandler<ActionEvent> onClearButtonAction) { clearButton.setOnAction(onClearButtonAction); }
/**
* @return the current property of the prompt text
* @see javafx.scene.control.TextInputControl#promptTextProperty()
* @since Envoy Client v0.1-beta
*/
public StringProperty promptTextProperty() { return textField.promptTextProperty(); }
/**
* @return the current prompt text
* @see javafx.scene.control.TextInputControl#getPromptText()
* @since Envoy Client v0.1-beta
*/
public String getPromptText() { return textField.getPromptText(); }
/**
* @param value the prompt text to display
* @see javafx.scene.control.TextInputControl#setPromptText(java.lang.String)
* @since Envoy Client v0.1-beta
*/
public void setPromptText(String value) { textField.setPromptText(value); }
/**
* @return the current property of the tooltip
* @see javafx.scene.control.Control#tooltipProperty()
* @since Envoy Client v0.1-beta
*/
public ObjectProperty<Tooltip> tooltipProperty() { return textField.tooltipProperty(); }
/**
* @param value the new tooltip
* @see javafx.scene.control.Control#setTooltip(javafx.scene.control.Tooltip)
* @since Envoy Client v0.1-beta
*/
public void setTooltip(Tooltip value) { textField.setTooltip(value); }
/**
* @return the current tooltip
* @see javafx.scene.control.Control#getTooltip()
* @since Envoy Client v0.1-beta
*/
public Tooltip getTooltip() { return textField.getTooltip(); }
/**
* @return the current property of the context menu
* @see javafx.scene.control.Control#contextMenuProperty()
* @since Envoy Client v0.1-beta
*/
public ObjectProperty<ContextMenu> contextMenuProperty() { return textField.contextMenuProperty(); }
/**
* @param value the new context menu
* @see javafx.scene.control.Control#setContextMenu(javafx.scene.control.ContextMenu)
* @since Envoy Client v0.1-beta
*/
public void setContextMenu(ContextMenu value) { textField.setContextMenu(value); }
/**
* @return the current context menu
* @see javafx.scene.control.Control#getContextMenu()
* @since Envoy Client v0.1-beta
*/
public ContextMenu getContextMenu() { return textField.getContextMenu(); }
/**
* @param value whether this ClearableTextField should be editable
* @see javafx.scene.control.TextInputControl#setEditable(boolean)
* @since Envoy Client v0.1-beta
*/
public void setEditable(boolean value) { textField.setEditable(value); }
/**
* @return the current property whether this ClearableTextField is editable
* @see javafx.scene.control.TextInputControl#editableProperty()
* @since Envoy Client v0.1-beta
*/
public BooleanProperty editableProperty() { return textField.editableProperty(); }
/**
* @return whether this {@code ClearableTextField} is editable
* @see javafx.scene.control.TextInputControl#isEditable()
* @since Envoy Client v0.1-beta
*/
public boolean isEditable() { return textField.isEditable(); }
}

View File

@ -1,36 +0,0 @@
package envoy.client.ui;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
/**
* This is a utility class that provides access to a refreshing mechanism for
* elements that were added without notifying the underlying {@link ListView}.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ListViewRefresh.java</strong><br>
* Created: <strong>16.07.2020</strong><br>
*
* @author Leon Hofmeister
* @since Envoy Client v0.1-beta
*/
public final class ListViewRefresh {
private ListViewRefresh() {}
/**
* Deeply refreshes a {@code listview}, meaning it recomputes every single of
* its {@link ListCell}s.
* <p>
* While it does work, it is <b>not the most efficient algorithm</b> possible.
*
* @param toRefresh the listView to refresh
* @param <T> the type of its {@code listcells}
* @since Envoy Client v0.1-beta
*/
public static <T> void deepRefresh(ListView<T> toRefresh) {
final var items = toRefresh.getItems();
toRefresh.setItems(null);
toRefresh.setItems(items);
}
}

View File

@ -3,10 +3,6 @@ package envoy.client.ui;
/** /**
* This interface defines an action that should be performed when a scene gets * This interface defines an action that should be performed when a scene gets
* restored from the scene stack in {@link SceneContext}. * restored from the scene stack in {@link SceneContext}.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Restorable.java</strong><br>
* Created: <strong>03.07.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta

View File

@ -23,10 +23,6 @@ import dev.kske.eventbus.*;
* <p> * <p>
* When a scene is loaded, the style sheet for the current theme is applied to * When a scene is loaded, the style sheet for the current theme is applied to
* it. * it.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>SceneContext.java</strong><br>
* Created: <strong>06.06.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta

View File

@ -15,6 +15,7 @@ import envoy.client.event.EnvoyCloseEvent;
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.client.util.IconUtil;
import envoy.data.*; import envoy.data.*;
import envoy.data.User.UserStatus; import envoy.data.User.UserStatus;
import envoy.event.*; import envoy.event.*;
@ -25,10 +26,6 @@ import dev.kske.eventbus.EventBus;
/** /**
* Handles application startup and shutdown. * Handles application startup and shutdown.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Startup.java</strong><br>
* Created: <strong>26.03.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer

View File

@ -6,16 +6,13 @@ import java.awt.TrayIcon.MessageType;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.stage.Stage; import javafx.stage.Stage;
import envoy.client.util.IconUtil;
import envoy.data.Message; import envoy.data.Message;
import dev.kske.eventbus.*; import dev.kske.eventbus.*;
import dev.kske.eventbus.Event; import dev.kske.eventbus.Event;
/** /**
* Project: <strong>envoy-client</strong><br>
* File: <strong>StatusTrayIcon.java</strong><br>
* Created: <strong>3 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.2-alpha * @since Envoy Client v0.2-alpha
*/ */
@ -32,7 +29,7 @@ public final class StatusTrayIcon implements EventListener {
* A received {@link Message} is only displayed as a system tray notification if * A received {@link Message} is only displayed as a system tray notification if
* this variable is set to {@code true}. * this variable is set to {@code true}.
*/ */
private boolean displayMessages = false; private boolean displayMessages;
/** /**
* @return {@code true} if the status tray icon is supported on this platform * @return {@code true} if the status tray icon is supported on this platform
@ -90,10 +87,10 @@ public final class StatusTrayIcon implements EventListener {
public void hide() { SystemTray.getSystemTray().remove(trayIcon); } public void hide() { SystemTray.getSystemTray().remove(trayIcon); }
@Event @Event
private void onMessage(Message evt) { private void onMessage(Message message) {
if (displayMessages) trayIcon.displayMessage( if (displayMessages) trayIcon.displayMessage(
evt.hasAttachment() ? "New " + evt.getAttachment().getType().toString().toLowerCase() + " message received" : "New message received", message.hasAttachment() ? "New " + message.getAttachment().getType().toString().toLowerCase() + " message received" : "New message received",
evt.getText(), message.getText(),
MessageType.INFO); MessageType.INFO);
} }
} }

View File

@ -1,11 +1,9 @@
package envoy.client.ui; package envoy.client.ui.control;
import java.util.logging.Level; import java.util.logging.*;
import java.util.logging.Logger;
import javafx.scene.control.Alert; import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import envoy.client.data.audio.AudioPlayer; import envoy.client.data.audio.AudioPlayer;
@ -14,10 +12,6 @@ import envoy.util.EnvoyLog;
/** /**
* Enables the play back of audio clips through a button. * Enables the play back of audio clips through a button.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>AudioControl.java</strong><br>
* Created: <strong>05.07.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta

View File

@ -1,23 +1,17 @@
package envoy.client.ui.listcell; package envoy.client.ui.control;
import javafx.geometry.Insets; import javafx.geometry.*;
import javafx.geometry.Pos;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.image.ImageView; import javafx.scene.image.*;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
import envoy.client.data.Chat; import envoy.client.data.*;
import envoy.client.ui.IconUtil; import envoy.client.util.IconUtil;
import envoy.data.Group;
/** /**
* Displays a chat using a contact control for the recipient and a label for the * Displays a chat using a contact control for the recipient and a label for the
* unread message count. * unread message count.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContactControl.java</strong><br>
* Created: <strong>01.07.2020</strong><br>
* *
* @see ContactControl * @see ContactControl
* @author Leon Hofmeister * @author Leon Hofmeister
@ -25,6 +19,9 @@ import envoy.data.Group;
*/ */
public final class ChatControl extends HBox { public final class ChatControl extends HBox {
private static final Image userIcon = IconUtil.loadIconThemeSensitive("user_icon", 32),
groupIcon = IconUtil.loadIconThemeSensitive("group_icon", 32);
/** /**
* @param chat the chat to display * @param chat the chat to display
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
@ -32,10 +29,9 @@ public final class ChatControl extends HBox {
public ChatControl(Chat chat) { public ChatControl(Chat chat) {
setAlignment(Pos.CENTER_LEFT); setAlignment(Pos.CENTER_LEFT);
setPadding(new Insets(0, 0, 3, 0)); setPadding(new Insets(0, 0, 3, 0));
// profile pic
ImageView contactProfilePic; // Profile picture
if (chat.getRecipient() instanceof Group) contactProfilePic = new ImageView(IconUtil.loadIconThemeSensitive("group_icon", 32)); ImageView contactProfilePic = new ImageView(chat instanceof GroupChat ? groupIcon : userIcon);
else contactProfilePic = new ImageView(IconUtil.loadIconThemeSensitive("user_icon", 32));
final var clip = new Rectangle(); final var clip = new Rectangle();
clip.setWidth(32); clip.setWidth(32);
clip.setHeight(32); clip.setHeight(32);
@ -43,14 +39,17 @@ public final class ChatControl extends HBox {
clip.setArcWidth(32); clip.setArcWidth(32);
contactProfilePic.setClip(clip); contactProfilePic.setClip(clip);
getChildren().add(contactProfilePic); getChildren().add(contactProfilePic);
// spacing
// Spacing
final var leftSpacing = new Region(); final var leftSpacing = new Region();
leftSpacing.setPrefSize(8, 0); leftSpacing.setPrefSize(8, 0);
leftSpacing.setMinSize(8, 0); leftSpacing.setMinSize(8, 0);
leftSpacing.setMaxSize(8, 0); leftSpacing.setMaxSize(8, 0);
getChildren().add(leftSpacing); getChildren().add(leftSpacing);
// Contact control // Contact control
getChildren().add(new ContactControl(chat.getRecipient())); getChildren().add(new ContactControl(chat.getRecipient()));
// Unread messages // Unread messages
if (chat.getUnreadAmount() != 0) { if (chat.getUnreadAmount() != 0) {
final var spacing = new Region(); final var spacing = new Region();
@ -58,12 +57,12 @@ public final class ChatControl extends HBox {
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);
final var vBox2 = new VBox(); final var vbox = new VBox();
vBox2.setAlignment(Pos.CENTER_RIGHT); vbox.setAlignment(Pos.CENTER_RIGHT);
unreadMessagesLabel.setAlignment(Pos.CENTER); unreadMessagesLabel.setAlignment(Pos.CENTER);
unreadMessagesLabel.getStyleClass().add("unread-messages-amount"); unreadMessagesLabel.getStyleClass().add("unread-messages-amount");
vBox2.getChildren().add(unreadMessagesLabel); vbox.getChildren().add(unreadMessagesLabel);
getChildren().add(vBox2); getChildren().add(vbox);
} }
getStyleClass().add("list-element"); getStyleClass().add("list-element");
} }

View File

@ -1,19 +1,14 @@
package envoy.client.ui.listcell; package envoy.client.ui.control;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import envoy.data.Contact; import envoy.data.*;
import envoy.data.User;
/** /**
* Displays information about a contact in two rows. The first row contains the * Displays information about a contact in two rows. The first row contains the
* name. The second row contains the online status (user) or the member count * name. The second row contains the online status (user) or the member count
* (group). * (group).
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContactControl.java</strong><br>
* Created: <strong>13.07.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta

View File

@ -1,4 +1,4 @@
package envoy.client.ui.listcell; package envoy.client.ui.control;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.StringSelection;
@ -6,37 +6,23 @@ import java.io.*;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.Map; import java.util.Map;
import java.util.logging.Level; import java.util.logging.*;
import java.util.logging.Logger;
import javafx.geometry.Insets; import javafx.geometry.*;
import javafx.geometry.Pos; import javafx.scene.control.*;
import javafx.scene.control.ContextMenu; import javafx.scene.image.*;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import envoy.client.data.Context; import envoy.client.data.*;
import envoy.client.data.LocalDB; import envoy.client.ui.*;
import envoy.client.data.Settings; import envoy.client.util.IconUtil;
import envoy.client.ui.AudioControl; import envoy.data.*;
import envoy.client.ui.IconUtil;
import envoy.client.ui.SceneContext;
import envoy.data.GroupMessage;
import envoy.data.Message;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
import envoy.data.User;
import envoy.util.EnvoyLog; import envoy.util.EnvoyLog;
/** /**
* This class formats a single {@link Message} into a UI component. * This class transforms a single {@link Message} into a UI component.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageControl.java</strong><br>
* Created: <strong>01.07.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
@ -95,6 +81,7 @@ public final class MessageControl extends Label {
contextMenu.getItems().addAll(copyMenuItem, deleteMenuItem, forwardMenuItem, quoteMenuItem, infoMenuItem); contextMenu.getItems().addAll(copyMenuItem, deleteMenuItem, forwardMenuItem, quoteMenuItem, infoMenuItem);
// Handling message attachment display // Handling message attachment display
// TODO: Add missing attachment types
if (message.hasAttachment()) { if (message.hasAttachment()) {
switch (message.getAttachment().getType()) { switch (message.getAttachment().getType()) {
case PICTURE: case PICTURE:

View File

@ -1,15 +1,10 @@
package envoy.client.ui.custom; package envoy.client.ui.control;
import javafx.scene.image.Image; import javafx.scene.image.*;
import javafx.scene.image.ImageView;
import javafx.scene.shape.Rectangle; import javafx.scene.shape.Rectangle;
/** /**
* Provides a set of convenience constructors for images that are displayed as profile pictures. * Provides a set of convenience constructors for images that are displayed as profile pictures.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ProfilePicImageView.java</strong><br>
* Created: <strong>30.07.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta

View File

@ -1,4 +1,4 @@
package envoy.client.ui.custom; package envoy.client.ui.control;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -21,10 +21,6 @@ import javafx.scene.input.Clipboard;
* <li>clear</li> * <li>clear</li>
* <li>Select all</li> * <li>Select all</li>
* </ul> * </ul>
* <p>
* Project: <strong>client</strong><br>
* File: <strong>TextInputContextMenu.java</strong><br>
* Created: <strong>20.09.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta

View File

@ -0,0 +1,9 @@
/**
* Defines custom UI controls.
*
* @author Kai S. K. Engelbart
* @author Leon Hofmeister
* @author Maximilian K&auml;fer
* @since Envoy Client v0.2-beta
*/
package envoy.client.ui.control;

View File

@ -11,7 +11,7 @@ import java.util.logging.*;
import javafx.animation.RotateTransition; import javafx.animation.RotateTransition;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.collections.*; import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList; import javafx.collections.transformation.FilteredList;
import javafx.fxml.*; import javafx.fxml.*;
import javafx.scene.control.*; import javafx.scene.control.*;
@ -30,9 +30,9 @@ import envoy.client.data.commands.*;
import envoy.client.event.*; import envoy.client.event.*;
import envoy.client.net.*; import envoy.client.net.*;
import envoy.client.ui.*; import envoy.client.ui.*;
import envoy.client.ui.custom.TextInputContextMenu; import envoy.client.ui.control.*;
import envoy.client.ui.listcell.*; import envoy.client.ui.listcell.*;
import envoy.client.util.ReflectionUtil; import envoy.client.util.*;
import envoy.data.*; import envoy.data.*;
import envoy.data.Attachment.AttachmentType; import envoy.data.Attachment.AttachmentType;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
@ -45,9 +45,7 @@ import dev.kske.eventbus.*;
import dev.kske.eventbus.Event; import dev.kske.eventbus.Event;
/** /**
* Project: <strong>envoy-client</strong><br> * Controller for the chat scene.
* File: <strong>ChatSceneController.java</strong><br>
* Created: <strong>26.03.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
@ -169,7 +167,8 @@ public final class ChatScene implements EventListener, Restorable {
messageList.setCellFactory(MessageListCell::new); messageList.setCellFactory(MessageListCell::new);
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new)); chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
// JavaFX provides an internal way of populating the context menu of a textarea. // JavaFX provides an internal way of populating the context menu of a text
// area.
// We, however, need additional functionality. // We, however, need additional functionality.
messageTextArea.setContextMenu(new TextInputContextMenu(messageTextArea, e -> checkKeyCombination(null))); messageTextArea.setContextMenu(new TextInputContextMenu(messageTextArea, e -> checkKeyCombination(null)));
@ -188,7 +187,7 @@ public final class ChatScene implements EventListener, Restorable {
clip.setArcWidth(43); clip.setArcWidth(43);
clientProfilePic.setClip(clip); clientProfilePic.setClip(clip);
chatList.setItems(chats = new FilteredList<>(FXCollections.observableList(localDB.getChats()))); chatList.setItems(chats = new FilteredList<>(localDB.getChats()));
contactLabel.setText(localDB.getUser().getName()); contactLabel.setText(localDB.getUser().getName());
initializeSystemCommandsMap(); initializeSystemCommandsMap();
@ -203,8 +202,8 @@ public final class ChatScene implements EventListener, Restorable {
Tooltip.uninstall(contactSpecificOnlineOperations, onlyIfOnlineTooltip); Tooltip.uninstall(contactSpecificOnlineOperations, onlyIfOnlineTooltip);
contactSearchTab.setContent(new FXMLLoader().load(getClass().getResourceAsStream("/fxml/ContactSearchTab.fxml"))); contactSearchTab.setContent(new FXMLLoader().load(getClass().getResourceAsStream("/fxml/ContactSearchTab.fxml")));
groupCreationTab.setContent(new FXMLLoader().load(getClass().getResourceAsStream("/fxml/GroupCreationTab.fxml"))); groupCreationTab.setContent(new FXMLLoader().load(getClass().getResourceAsStream("/fxml/GroupCreationTab.fxml")));
} catch (final IOException e2) { } catch (final IOException e) {
logger.log(Level.SEVERE, "An error occurred when attempting to load tabs: ", e2); logger.log(Level.SEVERE, "An error occurred when attempting to load tabs: ", e);
} }
else { else {
Tooltip.install(contactSpecificOnlineOperations, onlyIfOnlineTooltip); Tooltip.install(contactSpecificOnlineOperations, onlyIfOnlineTooltip);
@ -230,12 +229,8 @@ public final class ChatScene implements EventListener, Restorable {
// Read current chat or increment unread amount // Read current chat or increment unread amount
if (chat.equals(currentChat)) { if (chat.equals(currentChat)) {
try { currentChat.read(writeProxy);
currentChat.read(writeProxy); Platform.runLater(this::scrollToMessageListEnd);
} catch (final IOException e) {
logger.log(Level.WARNING, "Could not read current chat: ", e);
}
Platform.runLater(() -> { ListViewRefresh.deepRefresh(messageList); scrollToMessageListEnd(); });
} else if (!ownMessage && message.getStatus() != MessageStatus.READ) chat.incrementUnreadAmount(); } else if (!ownMessage && message.getStatus() != MessageStatus.READ) chat.incrementUnreadAmount();
// Move chat with most recent unread messages to the top // Move chat with most recent unread messages to the top
@ -250,33 +245,16 @@ public final class ChatScene implements EventListener, Restorable {
@Event @Event
private void onMessageStatusChange(MessageStatusChange evt) { private void onMessageStatusChange(MessageStatusChange evt) {
localDB.getMessage(evt.getID()).ifPresent(message -> {
message.setStatus(evt.get()); // Update UI if in current chat and the current user was the sender of the
// Update UI if in current chat and the current user was the sender of the // message
// message if (currentChat != null) localDB.getMessage(evt.getID())
if (currentChat != null && message.getSenderID() == client.getSender().getID()) Platform.runLater(messageList::refresh); .filter(msg -> msg.getSenderID() == client.getSender().getID())
}); .ifPresent(msg -> Platform.runLater(messageList::refresh));
kske marked this conversation as resolved
Review

Did you test this? Is the normal refresh enough?

Did you test this? Is the normal refresh enough?
Review

Yes, it is, as the refresh method refreshed all objects inside the list that might have changed.

Yes, it is, as the `refresh` method refreshed all objects inside the list that might have changed.
} }
@Event @Event(eventType = UserStatusChange.class)
private void onGroupMessageStatusChange(GroupMessageStatusChange evt) { private void onUserStatusChange() { Platform.runLater(chatList::refresh); }
localDB.getMessage(evt.getID()).ifPresent(groupMessage -> {
((GroupMessage) groupMessage).getMemberStatuses().replace(evt.getMemberID(), evt.get());
// Update UI if in current chat
if (currentChat != null && groupMessage.getRecipientID() == currentChat.getRecipient().getID()) Platform.runLater(messageList::refresh);
});
}
@Event
private void onUserStatusChange(UserStatusChange evt) {
chats.getSource()
.stream()
.filter(c -> c.getRecipient().getID() == evt.getID())
.findAny()
.map(Chat::getRecipient)
.ifPresent(u -> { ((User) u).setStatus(evt.get()); Platform.runLater(() -> ListViewRefresh.deepRefresh(chatList)); });
}
@Event @Event
private void onContactOperation(ContactOperation operation) { private void onContactOperation(ContactOperation operation) {
@ -320,6 +298,7 @@ public final class ChatScene implements EventListener, Restorable {
clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43)); clientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
chatList.setCellFactory(new ListCellFactory<>(ChatControl::new)); chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
messageList.setCellFactory(MessageListCell::new); messageList.setCellFactory(MessageListCell::new);
// TODO: cache image
if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43)); if (currentChat.getRecipient() instanceof User) recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("user_icon", 43));
else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43)); else recipientProfilePic.setImage(IconUtil.loadIconThemeSensitive("group_icon", 43));
} }
@ -360,18 +339,14 @@ public final class ChatScene implements EventListener, Restorable {
// Load the chat // Load the chat
currentChat = localDB.getChat(user.getID()).get(); currentChat = localDB.getChat(user.getID()).get();
messageList.setItems(FXCollections.observableList(currentChat.getMessages())); messageList.setItems(currentChat.getMessages());
final var scrollIndex = messageList.getItems().size() - currentChat.getUnreadAmount(); final var scrollIndex = messageList.getItems().size() - currentChat.getUnreadAmount();
messageList.scrollTo(scrollIndex); messageList.scrollTo(scrollIndex);
logger.log(Level.FINEST, "Loading chat with " + user + " at index " + scrollIndex); logger.log(Level.FINEST, "Loading chat with " + user + " at index " + scrollIndex);
deleteContactMenuItem.setText("Delete " + user.getName()); deleteContactMenuItem.setText("Delete " + user.getName());
// Read the current chat // Read the current chat
try { currentChat.read(writeProxy);
currentChat.read(writeProxy);
} catch (final IOException e) {
logger.log(Level.WARNING, "Could not read current chat.", e);
}
// Discard the pending attachment // Discard the pending attachment
if (recorder.isRecording()) { if (recorder.isRecording()) {
@ -555,8 +530,8 @@ public final class ChatScene implements EventListener, Restorable {
// Sending an IsTyping event if none has been sent for // Sending an IsTyping event if none has been sent for
// IsTyping#millisecondsActive // IsTyping#millisecondsActive
if (currentChat.getLastWritingEvent() + IsTyping.millisecondsActive <= System.currentTimeMillis()) { if (client.isOnline() && currentChat.getLastWritingEvent() + IsTyping.millisecondsActive <= System.currentTimeMillis()) {
eventBus.dispatch(new SendEvent(new IsTyping(getChatID(), currentChat.getRecipient().getID()))); client.send(new IsTyping(getChatID(), currentChat.getRecipient().getID()));
currentChat.lastWritingEventWasNow(); currentChat.lastWritingEventWasNow();
} }
@ -665,7 +640,7 @@ public final class ChatScene implements EventListener, Restorable {
return; return;
} }
final var text = messageTextArea.getText().strip(); final var text = messageTextArea.getText().strip();
if (!messageTextAreaCommands.executeIfAnyPresent(text)) try { if (!messageTextAreaCommands.executeIfAnyPresent(text)) {
// 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())
.setText(text); .setText(text);
@ -692,15 +667,10 @@ public final class ChatScene implements EventListener, Restorable {
localDB.getChats().remove(currentChat); localDB.getChats().remove(currentChat);
localDB.getChats().add(0, currentChat); localDB.getChats().add(0, currentChat);
}); });
ListViewRefresh.deepRefresh(messageList);
scrollToMessageListEnd(); scrollToMessageListEnd();
// Request a new ID generator if all IDs were used // Request a new ID generator if all IDs were used
if (!localDB.getIDGenerator().hasNext() && client.isOnline()) client.requestIdGenerator(); if (!localDB.getIDGenerator().hasNext() && client.isOnline()) client.requestIDGenerator();
} catch (final IOException e) {
logger.log(Level.SEVERE, "Error while sending message: ", e);
new Alert(AlertType.ERROR, "An error occured while sending the message!").showAndWait();
} }
// Clear text field and disable post button // Clear text field and disable post button

View File

@ -7,8 +7,11 @@ import javafx.fxml.FXML;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.control.Alert.AlertType; import javafx.scene.control.Alert.AlertType;
import envoy.client.event.*; import envoy.client.data.Context;
import envoy.client.ui.listcell.*; import envoy.client.event.BackEvent;
import envoy.client.net.Client;
import envoy.client.ui.control.ContactControl;
import envoy.client.ui.listcell.ListCellFactory;
import envoy.data.User; import envoy.data.User;
import envoy.event.ElementOperation; import envoy.event.ElementOperation;
import envoy.event.contact.*; import envoy.event.contact.*;
@ -25,10 +28,6 @@ import dev.kske.eventbus.*;
* <p> * <p>
* To create a group, a button is available that loads the * To create a group, a button is available that loads the
* {@link GroupCreationTab}. * {@link GroupCreationTab}.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ContactSearchScene.java</strong><br>
* Created: <strong>07.06.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
@ -46,6 +45,7 @@ public class ContactSearchTab implements EventListener {
private final Alert alert = new Alert(AlertType.CONFIRMATION); private final Alert alert = new Alert(AlertType.CONFIRMATION);
private static final Client client = Context.getInstance().getClient();
private static final EventBus eventBus = EventBus.getInstance(); private static final EventBus eventBus = EventBus.getInstance();
private static final Logger logger = EnvoyLog.getLogger(ChatScene.class); private static final Logger logger = EnvoyLog.getLogger(ChatScene.class);
@ -77,7 +77,7 @@ public class ContactSearchTab implements EventListener {
@FXML @FXML
private void sendRequest() { private void sendRequest() {
final var text = searchBar.getText().strip(); final var text = searchBar.getText().strip();
if (!text.isBlank()) eventBus.dispatch(new SendEvent(new UserSearchRequest(text))); if (!text.isBlank()) client.send(new UserSearchRequest(text));
else userList.getItems().clear(); else userList.getItems().clear();
} }
@ -106,7 +106,7 @@ public class ContactSearchTab implements EventListener {
currentlySelectedUser = user; currentlySelectedUser = user;
final var event = new ContactOperation(currentlySelectedUser, ElementOperation.ADD); final var event = new ContactOperation(currentlySelectedUser, ElementOperation.ADD);
// Sends the event to the server // Sends the event to the server
eventBus.dispatch(new SendEvent(event)); client.send(event);
// Removes the chosen user and updates the UI // Removes the chosen user and updates the UI
userList.getItems().remove(currentlySelectedUser); userList.getItems().remove(currentlySelectedUser);
eventBus.dispatch(event); eventBus.dispatch(event);

View File

@ -10,8 +10,9 @@ import javafx.scene.control.*;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import envoy.client.data.*; import envoy.client.data.*;
import envoy.client.event.*; import envoy.client.event.BackEvent;
import envoy.client.ui.listcell.*; import envoy.client.ui.control.ContactControl;
import envoy.client.ui.listcell.ListCellFactory;
import envoy.data.*; import envoy.data.*;
import envoy.event.GroupCreation; import envoy.event.GroupCreation;
import envoy.event.contact.ContactOperation; import envoy.event.contact.ContactOperation;
@ -27,10 +28,6 @@ import dev.kske.eventbus.*;
* When the group creation button is pressed, a {@link GroupCreation} is sent to * When the group creation button is pressed, a {@link GroupCreation} is sent to
* the server. This controller enforces a valid group name and a non-empty * the server. This controller enforces a valid group name and a non-empty
* member list (excluding the client user). * member list (excluding the client user).
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>GroupCreationScene.java</strong><br>
* Created: <strong>07.06.2020</strong><br>
* *
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
@ -137,8 +134,9 @@ public class GroupCreationTab implements EventListener {
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
private void createGroup(String name) { private void createGroup(String name) {
eventBus.dispatch(new SendEvent( Context.getInstance()
new GroupCreation(name, userList.getSelectionModel().getSelectedItems().stream().map(User::getID).collect(Collectors.toSet())))); .getClient()
.send(new GroupCreation(name, userList.getSelectionModel().getSelectedItems().stream().map(User::getID).collect(Collectors.toSet())));
} }
/** /**
@ -150,12 +148,7 @@ public class GroupCreationTab implements EventListener {
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public boolean groupNameAlreadyPresent(String newName) { public boolean groupNameAlreadyPresent(String newName) {
return localDB.getChats() return localDB.getChats().stream().map(Chat::getRecipient).filter(Group.class::isInstance).map(Contact::getName).anyMatch(newName::equals);
.stream()
.map(Chat::getRecipient)
.filter(Group.class::isInstance)
.map(Contact::getName)
.anyMatch(newName::equals);
} }
@FXML @FXML
@ -211,7 +204,7 @@ public class GroupCreationTab implements EventListener {
userList.getItems().add((User) operation.get()); userList.getItems().add((User) operation.get());
break; break;
case REMOVE: case REMOVE:
userList.getItems().removeIf(u -> u.equals(operation.get())); userList.getItems().removeIf(operation.get()::equals);
break; break;
} }
}); });

View File

@ -12,6 +12,7 @@ import javafx.scene.image.ImageView;
import envoy.client.data.ClientConfig; import envoy.client.data.ClientConfig;
import envoy.client.ui.*; import envoy.client.ui.*;
import envoy.client.util.IconUtil;
import envoy.data.LoginCredentials; import envoy.data.LoginCredentials;
import envoy.event.HandshakeRejection; import envoy.event.HandshakeRejection;
import envoy.util.*; import envoy.util.*;
@ -19,9 +20,7 @@ import envoy.util.*;
import dev.kske.eventbus.*; import dev.kske.eventbus.*;
/** /**
* Project: <strong>envoy-client</strong><br> * Controller for the login scene.
* File: <strong>LoginDialog.java</strong><br>
* Created: <strong>03.04.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
@ -101,20 +100,21 @@ public final class LoginScene implements EventListener {
@FXML @FXML
private void registerSwitchPressed() { private void registerSwitchPressed() {
// Update button text and register switch
if (!registration) { if (!registration) {
// case if the current mode is login
loginButton.setText("Register"); loginButton.setText("Register");
loginButton.setPadding(new Insets(2, 116, 2, 116)); loginButton.setPadding(new Insets(2, 116, 2, 116));
registerTextLabel.setText("Already an account?"); registerTextLabel.setText("Already an account?");
registerSwitch.setText("Login"); registerSwitch.setText("Login");
} else { } else {
// case if the current mode is registration
loginButton.setText("Login"); loginButton.setText("Login");
loginButton.setPadding(new Insets(2, 125, 2, 125)); loginButton.setPadding(new Insets(2, 125, 2, 125));
registerTextLabel.setText("No account yet?"); registerTextLabel.setText("No account yet?");
registerSwitch.setText("Register"); registerSwitch.setText("Register");
} }
registration = !registration; registration = !registration;
// Make repeat password field and label visible / invisible // Make repeat password field and label visible / invisible
repeatPasswordField.setVisible(registration); repeatPasswordField.setVisible(registration);
offlineModeButton.setDisable(registration); offlineModeButton.setDisable(registration);

View File

@ -1,20 +1,14 @@
package envoy.client.ui.controller; package envoy.client.ui.controller;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.scene.control.Label; import javafx.scene.control.*;
import javafx.scene.control.ListView;
import javafx.scene.control.TitledPane;
import envoy.client.data.Context; import envoy.client.data.Context;
import envoy.client.net.Client; import envoy.client.ui.listcell.ListCellFactory;
import envoy.client.ui.SceneContext;
import envoy.client.ui.listcell.AbstractListCell;
import envoy.client.ui.settings.*; import envoy.client.ui.settings.*;
/** /**
* Project: <strong>envoy-client</strong><br> * Controller for the settings scene.
* File: <strong>SettingsSceneController.java</strong><br>
* Created: <strong>10.04.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
@ -27,20 +21,10 @@ public final class SettingsScene {
@FXML @FXML
private TitledPane titledPane; private TitledPane titledPane;
private final Client client = Context.getInstance().getClient();
private final SceneContext sceneContext = Context.getInstance().getSceneContext();
@FXML @FXML
private void initialize() { private void initialize() {
settingsList.setCellFactory(listView -> new AbstractListCell<>(listView) { settingsList.setCellFactory(new ListCellFactory<>(pane -> new Label(pane.getTitle())));
settingsList.getItems().addAll(new GeneralSettingsPane(), new UserSettingsPane(), new DownloadSettingsPane(), new BugReportPane());
@Override
protected Label renderItem(SettingsPane item) { return new Label(item.getTitle()); }
});
settingsList.getItems().add(new GeneralSettingsPane());
settingsList.getItems().add(new UserSettingsPane(sceneContext, client.getSender(), client.isOnline()));
settingsList.getItems().add(new DownloadSettingsPane(sceneContext));
settingsList.getItems().add(new BugReportPane(client.getSender(), client.isOnline()));
} }
@FXML @FXML
@ -53,5 +37,5 @@ public final class SettingsScene {
} }
@FXML @FXML
private void backButtonClicked() { sceneContext.pop(); } private void backButtonClicked() { Context.getInstance().getSceneContext().pop(); }
} }

View File

@ -2,10 +2,6 @@ package envoy.client.ui.controller;
/** /**
* Provides options to select different tabs. * Provides options to select different tabs.
* <p>
* Project: <strong>client</strong><br>
* File: <strong>Tabs.java</strong><br>
* Created: <strong>30.8.2020</strong><br>
* *
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta

View File

@ -1,11 +1,9 @@
/** /**
* Contains JavaFX scene controllers. * Contains JavaFX scene controllers.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>08.06.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Leon Hofmeister
* @author Maximilian K&auml;fer
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
package envoy.client.ui.controller; package envoy.client.ui.controller;

View File

@ -1,14 +0,0 @@
/**
* This package stores custom components for use in JavaFX.
* These components are also expected to be used via FXML.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>30.07.2020</strong><br>
*
* @author Leon Hofmeister
* @author Maximilian K&auml;fer
* @author Kai S. K. Engelbart
* @since Envoy Client v0.2-beta
*/
package envoy.client.ui.custom;

View File

@ -1,17 +1,10 @@
package envoy.client.ui.listcell; package envoy.client.ui.listcell;
import javafx.scene.Cursor; import javafx.scene.*;
import javafx.scene.Node; import javafx.scene.control.*;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
/** /**
* Provides a convenience frame for list cell creation. * Provides a convenience frame for list cell creation.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>AbstractListCell.java</strong><br>
* Created: <strong>18.07.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @param <T> the type of element displayed by the list cell * @param <T> the type of element displayed by the list cell

View File

@ -7,10 +7,6 @@ import javafx.scene.control.ListView;
/** /**
* A generic list cell rendering an item using a provided render function. * A generic list cell rendering an item using a provided render function.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>GenericListCell.java</strong><br>
* Created: <strong>18.07.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @param <T> the type of element displayed by the list cell * @param <T> the type of element displayed by the list cell

View File

@ -3,17 +3,12 @@ package envoy.client.ui.listcell;
import java.util.function.Function; import java.util.function.Function;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.control.ListCell; import javafx.scene.control.*;
import javafx.scene.control.ListView;
import javafx.util.Callback; import javafx.util.Callback;
/** /**
* Provides a creation mechanism for generic list cells given a list view and a * Provides a creation mechanism for generic list cells given a list view and a
* conversion function. * conversion function.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>ListCellFactory.java</strong><br>
* Created: <strong>13.07.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @param <T> the type of object to display * @param <T> the type of object to display

View File

@ -1,17 +1,13 @@
package envoy.client.ui.listcell; package envoy.client.ui.listcell;
import javafx.geometry.Insets; import javafx.geometry.*;
import javafx.geometry.Pos;
import javafx.scene.control.ListView; import javafx.scene.control.ListView;
import envoy.client.ui.control.MessageControl;
import envoy.data.Message; import envoy.data.Message;
/** /**
* A list cell containing messages represented as message controls. * A list cell containing messages represented as message controls.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageListCell.java</strong><br>
* Created: <strong>18.07.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta

View File

@ -1,12 +1,9 @@
/** /**
* This package contains custom list cells that are used to display certain * This package contains custom list cells that are used to display certain
* things. * things.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>30.06.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
package envoy.client.ui.listcell; package envoy.client.ui.listcell;

View File

@ -4,25 +4,17 @@ import javafx.event.EventHandler;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.input.InputEvent; import javafx.scene.input.InputEvent;
import envoy.client.event.SendEvent;
import envoy.client.util.IssueUtil; import envoy.client.util.IssueUtil;
import envoy.data.User;
import envoy.event.IssueProposal; import envoy.event.IssueProposal;
import dev.kske.eventbus.EventBus;
/** /**
* This class offers the option for users to submit a bug report. Only the title * This class offers the option for users to submit a bug report. Only the title
* of a bug is needed to be sent. * of a bug is needed to be sent.
* <p>
* Project: <strong>client</strong><br>
* File: <strong>BugReportPane.java</strong><br>
* Created: <strong>Aug 4, 2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public final class BugReportPane extends OnlyIfOnlineSettingsPane { public final class BugReportPane extends OnlineOnlySettingsPane {
private final Label titleLabel = new Label("Suggest a title for the bug:"); private final Label titleLabel = new Label("Suggest a title for the bug:");
private final TextField titleTextField = new TextField(); private final TextField titleTextField = new TextField();
@ -36,12 +28,10 @@ public final class BugReportPane extends OnlyIfOnlineSettingsPane {
/** /**
* Creates a new {@code BugReportPane}. * Creates a new {@code BugReportPane}.
* *
* @param user the user whose details to use
* @param online whether this user is currently online
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public BugReportPane(User user, boolean online) { public BugReportPane() {
super("Report a bug", online); super("Report a bug");
setSpacing(10); setSpacing(10);
setToolTipText("A bug can only be reported while being online"); setToolTipText("A bug can only be reported while being online");
@ -68,12 +58,8 @@ public final class BugReportPane extends OnlyIfOnlineSettingsPane {
// Displaying the submitReportButton // Displaying the submitReportButton
submitReportButton.setDisable(true); submitReportButton.setDisable(true);
submitReportButton.setOnAction(e -> { submitReportButton.setOnAction(e -> client.send(new IssueProposal(titleTextField.getText(), IssueUtil
EventBus.getInstance() .sanitizeIssueDescription(errorDetailArea.getText(), showUsernameInBugReport.isSelected() ? client.getSender().getName() : null), true)));
.dispatch(new SendEvent(new IssueProposal(titleTextField.getText(),
IssueUtil.sanitizeIssueDescription(errorDetailArea.getText(), showUsernameInBugReport.isSelected() ? user.getName() : null),
true)));
});
getChildren().add(submitReportButton); getChildren().add(submitReportButton);
} }
} }

View File

@ -5,14 +5,10 @@ import javafx.scene.control.*;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.stage.DirectoryChooser; import javafx.stage.DirectoryChooser;
import envoy.client.ui.SceneContext; import envoy.client.data.Context;
/** /**
* Displays options for downloading {@link envoy.data.Attachment}s. * Displays options for downloading {@link envoy.data.Attachment}s.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>DownloadSettingsPane.java</strong><br>
* Created: <strong>27.07.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
@ -22,15 +18,14 @@ public final class DownloadSettingsPane extends SettingsPane {
/** /**
* Constructs a new {@code DownloadSettingsPane}. * Constructs a new {@code DownloadSettingsPane}.
* *
* @param sceneContext the {@code SceneContext} used to block input to the
* {@link javafx.stage.Stage} used in Envoy
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public DownloadSettingsPane(SceneContext sceneContext) { public DownloadSettingsPane() {
super("Download"); super("Download");
setSpacing(15); setSpacing(15);
setPadding(new Insets(15)); setPadding(new Insets(15));
// checkbox to disable asking
// Checkbox to disable asking
final var checkBox = new CheckBox(settings.getItems().get("autoSaveDownloads").getUserFriendlyName()); final var checkBox = new CheckBox(settings.getItems().get("autoSaveDownloads").getUserFriendlyName());
checkBox.setSelected(settings.isDownloadSavedWithoutAsking()); checkBox.setSelected(settings.isDownloadSavedWithoutAsking());
checkBox.setTooltip(new Tooltip("Determines whether a \"Select save location\" - dialogue will be shown when saving attachments.")); checkBox.setTooltip(new Tooltip("Determines whether a \"Select save location\" - dialogue will be shown when saving attachments."));
@ -52,7 +47,7 @@ public final class DownloadSettingsPane extends SettingsPane {
final var directoryChooser = new DirectoryChooser(); final var directoryChooser = new DirectoryChooser();
directoryChooser.setTitle("Select the directory where attachments should be saved to"); directoryChooser.setTitle("Select the directory where attachments should be saved to");
directoryChooser.setInitialDirectory(settings.getDownloadLocation()); directoryChooser.setInitialDirectory(settings.getDownloadLocation());
final var selectedDirectory = directoryChooser.showDialog(sceneContext.getStage()); final var selectedDirectory = directoryChooser.showDialog(Context.getInstance().getSceneContext().getStage());
if (selectedDirectory != null) { if (selectedDirectory != null) {
currentPath.setText(selectedDirectory.getAbsolutePath()); currentPath.setText(selectedDirectory.getAbsolutePath());

View File

@ -9,10 +9,6 @@ import envoy.data.User.UserStatus;
import dev.kske.eventbus.EventBus; import dev.kske.eventbus.EventBus;
/** /**
* Project: <strong>envoy-client</strong><br>
* File: <strong>GeneralSettingsPane.java</strong><br>
* Created: <strong>18.04.2020</strong><br>
*
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */

View File

@ -1,40 +1,39 @@
package envoy.client.ui.settings; package envoy.client.ui.settings;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.scene.control.Label; import javafx.scene.control.*;
import javafx.scene.control.Tooltip; import javafx.scene.layout.*;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import envoy.client.data.Context;
import envoy.client.net.Client;
/** /**
* Inheriting from this class signifies that options should only be available if * Inheriting from this class signifies that options should only be available if
* the {@link envoy.data.User} is currently online. If the user is currently * the {@link envoy.data.User} is currently online. If the user is currently
* offline, all {@link javafx.scene.Node} variables will be disabled and a * offline, all {@link javafx.scene.Node} variables will be disabled and a
* {@link Tooltip} will be displayed for the whole node. * {@link Tooltip} will be displayed for the whole node.
* <p>
* Project: <strong>client</strong><br>
* File: <strong>OnlyIfOnlineSettingsPane.java</strong><br>
* Created: <strong>04.08.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public abstract class OnlyIfOnlineSettingsPane extends SettingsPane { public abstract class OnlineOnlySettingsPane extends SettingsPane {
protected final Client client = Context.getInstance().getClient();
private final Tooltip beOnlineReminder = new Tooltip("You need to be online to modify your account."); private final Tooltip beOnlineReminder = new Tooltip("You need to be online to modify your account.");
/** /**
* @param title * @param title the title of this pane
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
protected OnlyIfOnlineSettingsPane(String title, boolean online) { protected OnlineOnlySettingsPane(String title) {
super(title); super(title);
setDisable(!online); setDisable(!client.isOnline());
if (!online) { if (!client.isOnline()) {
final var infoLabel = new Label("You shall not pass!\n(... Unless you would happen to be online)"); final var infoLabel = new Label("You shall not pass!\n(... Unless you would happen to be online)");
infoLabel.setId("info-label-warning"); infoLabel.setId("info-label-warning");
infoLabel.setWrapText(true); infoLabel.setWrapText(true);
@ -45,5 +44,11 @@ public abstract class OnlyIfOnlineSettingsPane extends SettingsPane {
} else Tooltip.uninstall(this, beOnlineReminder); } else Tooltip.uninstall(this, beOnlineReminder);
} }
/**
* Sets the text of the tooltip displayed for this pane.
*
* @param text the text to display
* @since Envoy Client v0.2-beta
*/
protected void setToolTipText(String text) { beOnlineReminder.setText(text); } protected void setToolTipText(String text) { beOnlineReminder.setText(text); }
} }

View File

@ -6,10 +6,6 @@ import javafx.scene.control.CheckBox;
import envoy.client.data.SettingsItem; import envoy.client.data.SettingsItem;
/** /**
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsToggleButton.java</strong><br>
* Created: <strong>18.04.2020</strong><br>
*
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */

View File

@ -5,10 +5,6 @@ import javafx.scene.layout.VBox;
import envoy.client.data.Settings; import envoy.client.data.Settings;
/** /**
* Project: <strong>envoy-client</strong><br>
* File: <strong>SettingsPane.java</strong><br>
* Created: <strong>18.04.2020</strong><br>
*
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */

View File

@ -14,24 +14,19 @@ import javafx.scene.input.InputEvent;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import envoy.client.event.SendEvent; import envoy.client.data.Context;
import envoy.client.ui.*; import envoy.client.ui.control.ProfilePicImageView;
import envoy.client.ui.custom.ProfilePicImageView; import envoy.client.util.IconUtil;
import envoy.data.User;
import envoy.event.*; import envoy.event.*;
import envoy.util.*; import envoy.util.*;
import dev.kske.eventbus.EventBus; import dev.kske.eventbus.EventBus;
/** /**
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserSettingsPane.java</strong><br>
* Created: <strong>31.07.2020</strong><br>
*
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public final class UserSettingsPane extends OnlyIfOnlineSettingsPane { public final class UserSettingsPane extends OnlineOnlySettingsPane {
private boolean profilePicChanged, usernameChanged, validPassword; private boolean profilePicChanged, usernameChanged, validPassword;
private byte[] currentImageBytes; private byte[] currentImageBytes;
@ -50,13 +45,10 @@ public final class UserSettingsPane extends OnlyIfOnlineSettingsPane {
/** /**
* Creates a new {@code UserSettingsPane}. * Creates a new {@code UserSettingsPane}.
* *
* @param sceneContext the {@code SceneContext} to block input to Envoy
* @param user the user who wants to customize his profile
* @param online whether this user is currently online
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public UserSettingsPane(SceneContext sceneContext, User user, boolean online) { public UserSettingsPane() {
super("User", online); super("User");
setSpacing(10); setSpacing(10);
// Display of profile pic change mechanism // Display of profile pic change mechanism
@ -67,18 +59,19 @@ public final class UserSettingsPane extends OnlyIfOnlineSettingsPane {
profilePic.setFitWidth(60); profilePic.setFitWidth(60);
profilePic.setFitHeight(60); profilePic.setFitHeight(60);
profilePic.setOnMouseClicked(e -> { profilePic.setOnMouseClicked(e -> {
if (!online) return; if (!client.isOnline()) return;
final var pictureChooser = new FileChooser(); final var pictureChooser = new FileChooser();
pictureChooser.setTitle("Select a new profile pic"); pictureChooser.setTitle("Select a new profile pic");
pictureChooser.setInitialDirectory(new File(System.getProperty("user.home"))); pictureChooser.setInitialDirectory(new File(System.getProperty("user.home")));
pictureChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif")); pictureChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"));
final var file = pictureChooser.showOpenDialog(sceneContext.getStage()); final var file = pictureChooser.showOpenDialog(Context.getInstance().getSceneContext().getStage());
if (file != null) { if (file != null) {
// Check max file size // Check max file size
// TODO: Move to config
if (file.length() > 5E6) { if (file.length() > 5E6) {
new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 5MB!").showAndWait(); new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 5MB!").showAndWait();
return; return;
@ -96,7 +89,7 @@ public final class UserSettingsPane extends OnlyIfOnlineSettingsPane {
hbox.getChildren().add(profilePic); hbox.getChildren().add(profilePic);
// Displaying the username change mechanism // Displaying the username change mechanism
final var username = user.getName(); final var username = client.getSender().getName();
newUsername = username; newUsername = username;
usernameTextField.setText(username); usernameTextField.setText(username);
final EventHandler<? super InputEvent> textChanged = e -> { final EventHandler<? super InputEvent> textChanged = e -> {
@ -133,7 +126,7 @@ public final class UserSettingsPane extends OnlyIfOnlineSettingsPane {
} }
// Displaying the save button // Displaying the save button
saveButton.setOnAction(e -> save(user.getID(), currentPasswordField.getText())); saveButton.setOnAction(e -> save(client.getSender().getID(), currentPasswordField.getText()));
saveButton.setAlignment(Pos.BOTTOM_RIGHT); saveButton.setAlignment(Pos.BOTTOM_RIGHT);
getChildren().add(saveButton); getChildren().add(saveButton);
} }
@ -150,7 +143,7 @@ public final class UserSettingsPane extends OnlyIfOnlineSettingsPane {
if (profilePicChanged) { if (profilePicChanged) {
final var profilePicChangeEvent = new ProfilePicChange(currentImageBytes, userID); final var profilePicChangeEvent = new ProfilePicChange(currentImageBytes, userID);
eventBus.dispatch(profilePicChangeEvent); eventBus.dispatch(profilePicChangeEvent);
eventBus.dispatch(new SendEvent(profilePicChangeEvent)); client.send(profilePicChangeEvent);
logger.log(Level.INFO, "The user just changed his profile pic."); logger.log(Level.INFO, "The user just changed his profile pic.");
} }
@ -158,8 +151,8 @@ public final class UserSettingsPane extends OnlyIfOnlineSettingsPane {
final var validContactName = Bounds.isValidContactName(newUsername); final var validContactName = Bounds.isValidContactName(newUsername);
if (usernameChanged && validContactName) { if (usernameChanged && validContactName) {
final var nameChangeEvent = new NameChange(userID, newUsername); final var nameChangeEvent = new NameChange(userID, newUsername);
eventBus.dispatch(new SendEvent(nameChangeEvent));
eventBus.dispatch(nameChangeEvent); eventBus.dispatch(nameChangeEvent);
client.send(nameChangeEvent);
logger.log(Level.INFO, "The user just changed his name to " + newUsername + "."); logger.log(Level.INFO, "The user just changed his name to " + newUsername + ".");
} else if (!validContactName) { } else if (!validContactName) {
final var alert = new Alert(AlertType.ERROR); final var alert = new Alert(AlertType.ERROR);
@ -172,14 +165,13 @@ public final class UserSettingsPane extends OnlyIfOnlineSettingsPane {
// The password was changed // The password was changed
if (validPassword) { if (validPassword) {
eventBus.dispatch(new SendEvent(new PasswordChangeRequest(newPassword, oldPassword, userID))); client.send(new PasswordChangeRequest(newPassword, oldPassword, userID));
logger.log(Level.INFO, "The user just tried to change his password!"); logger.log(Level.INFO, "The user just tried to change his password!");
} else if (!(validPassword || newPassword.isBlank())) { } else if (!(validPassword || newPassword.isBlank())) {
final var alert = new Alert(AlertType.ERROR); final var alert = new Alert(AlertType.ERROR);
alert.setTitle("Unequal Password"); alert.setTitle("Unequal Password");
alert.setContentText("Repeated password is unequal to the chosen new password"); alert.setContentText("Repeated password is unequal to the chosen new password");
alert.showAndWait(); alert.showAndWait();
return;
} }
} }
} }

View File

@ -1,10 +1,6 @@
/** /**
* This package contains classes used for representing the settings * This package contains classes used for representing the settings
* visually. * visually.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>19 Apr 2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart

View File

@ -1,9 +1,8 @@
package envoy.client.ui; package envoy.client.util;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.IOException; import java.io.IOException;
import java.util.EnumMap; import java.util.*;
import java.util.EnumSet;
import java.util.logging.Level; import java.util.logging.Level;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@ -16,10 +15,6 @@ import envoy.util.EnvoyLog;
/** /**
* Provides static utility methods for loading icons from the resource * Provides static utility methods for loading icons from the resource
* folder. * folder.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>IconUtil.java</strong><br>
* Created: <strong>16.03.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
@ -35,15 +30,7 @@ public final class IconUtil {
* @return the loaded image * @return the loaded image
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public static Image load(String path) { public static Image load(String path) { return new Image(IconUtil.class.getResource(path).toExternalForm()); }
Image image = null;
try {
image = new Image(IconUtil.class.getResource(path).toExternalForm());
} catch (final NullPointerException e) {
EnvoyLog.getLogger(IconUtil.class).log(Level.WARNING, String.format("Could not load image at path %s: ", path), e);
}
return image;
}
/** /**
* Loads an image from the resource folder and scales it to the given size. * Loads an image from the resource folder and scales it to the given size.
@ -54,13 +41,7 @@ public final class IconUtil {
* @since Envoy Client v0.1-beta * @since Envoy Client v0.1-beta
*/ */
public static Image load(String path, int size) { public static Image load(String path, int size) {
Image image = null; return new Image(IconUtil.class.getResource(path).toExternalForm(), size, size, true, true);
try {
image = new Image(IconUtil.class.getResource(path).toExternalForm(), size, size, true, true);
} catch (final NullPointerException e) {
EnvoyLog.getLogger(IconUtil.class).log(Level.WARNING, String.format("Could not load image at path %s: ", path), e);
}
return image;
} }
/** /**

View File

@ -2,39 +2,34 @@ package envoy.client.util;
/** /**
* Provides methods to handle outgoing issues. * Provides methods to handle outgoing issues.
* <p>
* Project: <strong>client</strong><br>
* File: <strong>IssueUtil.java</strong><br>
* Created: <strong>20.08.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Kai S. K. Engelbart
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public final class IssueUtil { public final class IssueUtil {
/**
*
* @since Envoy Client v0.2-beta
*/
private IssueUtil() {} private IssueUtil() {}
/** /**
* Performs actions to ensure the description of an issue will be displayed as * Normalizes line breaks and appends the user name to the issue description if
* intended by the user. * requested.
* *
* @param rawDescription the description to sanitize * @param description the description to sanitize
* @param username the user who submitted the issue. Should be * @param username the user who submitted the issue. Should be
* {@code null} if he does not want to be named. * {@code null} if he does not want to be named.
* @return the sanitized description * @return the sanitized description
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
public static String sanitizeIssueDescription(String rawDescription, String username) { public static String sanitizeIssueDescription(String description, String username) {
// Appending the submitter name, if this option was enabled
rawDescription += username != null // Trim and replace line breaks by <br> tags
? (rawDescription.endsWith("\n") || rawDescription.endsWith("<br>") ? "" : "<br>") + String.format("Submitted by user %s.", username) description = description.trim().replaceAll(System.getProperty("line.separator"), "<br>");
: "";
// Markdown does not support "normal" line breaks. It uses "<br>" // Append user name if requested
rawDescription = rawDescription.replaceAll(System.getProperty("line.separator", "\r?\n"), "<br>"); if (username != null)
return rawDescription; description += String.format("<br>Submitted by user %s.", username);
return description;
} }
} }

View File

@ -1,17 +1,11 @@
package envoy.client.util; package envoy.client.util;
import java.util.Arrays; import java.util.*;
import java.util.List; import java.util.stream.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.scene.Node; import javafx.scene.Node;
/** /**
* Project: <strong>envoy-client</strong><br>
* File: <strong>ReflectionUtil.java</strong><br>
* Created: <strong>02.08.2020</strong><br>
*
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta
*/ */
@ -20,8 +14,9 @@ public final class ReflectionUtil {
private ReflectionUtil() {} private ReflectionUtil() {}
/** /**
* Gets all declared variables of the given instance that have the specified * Gets all declared variable values of the given instance that have the
* class<br> * specified class.
* <p>
* (i.e. can get all {@code JComponents} (Swing) or {@code Nodes} (JavaFX) in a * (i.e. can get all {@code JComponents} (Swing) or {@code Nodes} (JavaFX) in a
* GUI class). * GUI class).
* <p> * <p>
@ -41,13 +36,11 @@ public final class ReflectionUtil {
return Arrays.stream(instance.getClass().getDeclaredFields()).filter(field -> typeToReturn.isAssignableFrom(field.getType())).map(field -> { return Arrays.stream(instance.getClass().getDeclaredFields()).filter(field -> typeToReturn.isAssignableFrom(field.getType())).map(field -> {
try { try {
field.setAccessible(true); field.setAccessible(true);
final var value = field.get(instance); return typeToReturn.cast(field.get(instance));
return value;
} catch (IllegalArgumentException | IllegalAccessException e) { } catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}).map(typeToReturn::cast);// field -> });
// typeToReturn.isAssignableFrom(field.getClass())).map(typeToReturn::cast);
} }
/** /**

View File

@ -1,9 +1,5 @@
/** /**
* This package contains utility classes for use in envoy-client. * This package contains utility classes for use in envoy-client.
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>package-info.java</strong><br>
* Created: <strong>02.08.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Client v0.2-beta * @since Envoy Client v0.2-beta

View File

@ -20,7 +20,7 @@ module envoy.client {
opens envoy.client.ui to javafx.graphics, javafx.fxml, dev.kske.eventbus; opens envoy.client.ui to javafx.graphics, javafx.fxml, dev.kske.eventbus;
opens envoy.client.ui.controller to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus; opens envoy.client.ui.controller to javafx.graphics, javafx.fxml, envoy.client.util, dev.kske.eventbus;
opens envoy.client.ui.custom to javafx.graphics, javafx.fxml; opens envoy.client.ui.control to javafx.graphics, javafx.fxml;
opens envoy.client.ui.settings to envoy.client.util; opens envoy.client.ui.settings to envoy.client.util;
opens envoy.client.net to dev.kske.eventbus; opens envoy.client.net to dev.kske.eventbus;
opens envoy.client.data to dev.kske.eventbus; opens envoy.client.data to dev.kske.eventbus;

View File

@ -5,10 +5,6 @@ import java.io.Serializable;
/** /**
* This interface should be used for any type supposed to be a {@link Message} * This interface should be used for any type supposed to be a {@link Message}
* attachment (i.e. images or sound). * attachment (i.e. images or sound).
* <p>
* Project: <strong>envoy-common</strong><br>
* File: <strong>Attachment.java</strong><br>
* Created: <strong>30 Dec 2019</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart

View File

@ -1,7 +1,6 @@
package envoy.data; package envoy.data;
import java.io.File; import java.io.*;
import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.function.Function; import java.util.function.Function;
import java.util.logging.Level; import java.util.logging.Level;
@ -19,10 +18,6 @@ import envoy.util.EnvoyLog;
* default value or over command line argument. Developers that fail to provide * default value or over command line argument. Developers that fail to provide
* default values will be greeted with an error message the next time they try * default values will be greeted with an error message the next time they try
* to start Envoy... * to start Envoy...
* <p>
* Project: <strong>envoy-client</strong><br>
* File: <strong>Config.java</strong><br>
* Created: <strong>12 Oct 2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Common v0.1-beta * @since Envoy Common v0.1-beta
@ -104,6 +99,7 @@ public class Config {
public void loadAll(Class<?> declaringClass, String propertiesFilePath, String[] args) { public void loadAll(Class<?> declaringClass, String propertiesFilePath, String[] args) {
if (modificationDisabled) if (modificationDisabled)
throw new IllegalStateException("Cannot change config after isInitialized has been called"); throw new IllegalStateException("Cannot change config after isInitialized has been called");
// Load the defaults from the given .properties file first // Load the defaults from the given .properties file first
final var properties = new Properties(); final var properties = new Properties();
try { try {
@ -120,6 +116,7 @@ public class Config {
// Check if all configuration values have been initialized // Check if all configuration values have been initialized
isInitialized(); isInitialized();
// Disable further editing of the config // Disable further editing of the config
modificationDisabled = true; modificationDisabled = true;
} }
@ -130,10 +127,9 @@ public class Config {
* @since Envoy Common v0.1-beta * @since Envoy Common v0.1-beta
*/ */
private void isInitialized() { private void isInitialized() {
if (items.values().stream().map(ConfigItem::get).anyMatch(Objects::isNull)) String uninitialized = items.values().stream().filter(c -> c.get() == null).map(ConfigItem::getCommandLong).collect(Collectors.joining(", "));
throw new IllegalStateException("config item(s) has/ have not been initialized:" if(!uninitialized.isEmpty())
+ items.values().stream().filter(configItem -> configItem.get() == null) throw new IllegalStateException("Config items uninitialized: " + uninitialized);
.map(ConfigItem::getCommandLong).collect(Collectors.toSet()));
} }
/** /**

View File

@ -7,10 +7,6 @@ import java.util.function.Function;
* line arguments and its default value. * line arguments and its default value.
* <p> * <p>
* All {@code ConfigItem}s are automatically mandatory. * All {@code ConfigItem}s are automatically mandatory.
* <p>
* Project: <strong>envoy-clientChess</strong><br>
* File: <strong>ConfigItem.javaEvent.java</strong><br>
* Created: <strong>21.12.2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @param <T> the type of the config item's value * @param <T> the type of the config item's value

View File

@ -1,16 +1,11 @@
package envoy.data; package envoy.data;
import java.io.Serializable; import java.io.Serializable;
import java.util.Objects; import java.util.*;
import java.util.Set;
/** /**
* This class is the superclass for both {@link User} and {@link Group}.<br> * This class is the superclass for both {@link User} and {@link Group}.<br>
* It provides an id and a name for each user and group.<br> * It provides an id and a name for each user and group.<br>
* <br>
* Project: <strong>envoy-common</strong><br>
* File: <strong>Contact.java</strong><br>
* Created: <strong>24 Mar 2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy v0.1-beta * @since Envoy v0.1-beta

View File

@ -1,15 +1,9 @@
package envoy.data; package envoy.data;
import java.io.ObjectInputStream; import java.io.*;
import java.io.ObjectOutputStream; import java.util.*;
import java.util.HashSet;
import java.util.Set;
/** /**
* Project: <strong>envoy-common</strong><br>
* File: <strong>Group.java</strong><br>
* Created: <strong>24 Mar 2020</strong><br>
*
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.1-beta * @since Envoy Common v0.1-beta
*/ */

View File

@ -1,14 +1,9 @@
package envoy.data; package envoy.data;
import java.time.Instant; import java.time.Instant;
import java.util.Collections; import java.util.*;
import java.util.Map;
/** /**
* Project: <strong>envoy-common</strong><br>
* File: <strong>GroupMessage.java</strong><br>
* Created: <strong>26.03.2020</strong><br>
*
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Common v0.1-beta * @since Envoy Common v0.1-beta
*/ */

View File

@ -2,17 +2,15 @@ package envoy.data;
import java.io.Serializable; import java.io.Serializable;
import dev.kske.eventbus.IEvent;
/** /**
* Generates increasing IDs between two numbers.<br> * Generates increasing IDs between two numbers.
* <br>
* Project: <strong>envoy-common</strong><br>
* File: <strong>IDGenerator.java</strong><br>
* Created: <strong>31.12.2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Common v0.2-alpha * @since Envoy Common v0.2-alpha
*/ */
public final class IDGenerator implements Serializable { public final class IDGenerator implements IEvent, Serializable {
private final long end; private final long end;
private long current; private long current;

View File

@ -9,10 +9,6 @@ import java.time.Instant;
* <p> * <p>
* If the authentication is performed with a token, the token is stored instead * If the authentication is performed with a token, the token is stored instead
* of the password. * of the password.
* <p>
* Project: <strong>envoy-common</strong><br>
* File: <strong>LoginCredentials.java</strong><br>
* Created: <strong>29.12.2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Common v0.2-alpha * @since Envoy Common v0.2-alpha

View File

@ -9,10 +9,6 @@ import dev.kske.eventbus.IEvent;
* Represents a unique message with a unique, numeric ID. Further metadata * Represents a unique message with a unique, numeric ID. Further metadata
* includes the sender and recipient {@link User}s, as well as the creation * includes the sender and recipient {@link User}s, as well as the creation
* date and the current {@link MessageStatus}.<br> * date and the current {@link MessageStatus}.<br>
* <br>
* Project: <strong>envoy-common</strong><br>
* File: <strong>Message.java</strong><br>
* Created: <strong>28.12.2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Leon Hofmeister * @author Leon Hofmeister
@ -28,23 +24,22 @@ public class Message implements Serializable, IEvent {
public enum MessageStatus { public enum MessageStatus {
/** /**
* is selected, if a message was sent but not received by the server yet. * The message has not yet been sent to the server
*/ */
WAITING, WAITING,
/** /**
* is selected, if a sent message was received by the server. * The message has been sent to the server.
*/ */
SENT, SENT,
/** /**
* is selected, if a message was delivered from the server to the recipient, but * The message has been received by its recipient.
* has not been read yet.
*/ */
RECEIVED, RECEIVED,
/** /**
* is selected, if a recipient opened the corresponding chat of said message. * The message has been read by its recipient.
*/ */
READ READ
} }

View File

@ -7,10 +7,6 @@ import envoy.data.Message.MessageStatus;
/** /**
* Provides a method of constructing the {@link Message} class.<br> * Provides a method of constructing the {@link Message} class.<br>
* <br>
* Project: <strong>envoy-common</strong><br>
* File: <strong>MessageBuilder.java</strong><br>
* Created: <strong>31.12.2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Common v0.2-alpha * @since Envoy Common v0.2-alpha

View File

@ -1,17 +1,11 @@
package envoy.data; package envoy.data;
import java.io.ObjectInputStream; import java.io.*;
import java.io.ObjectOutputStream; import java.util.*;
import java.util.HashSet;
import java.util.Set;
/** /**
* Represents a unique user with a unique, numeric ID, a name and a current * Represents a unique user with a unique, numeric ID, a name and a current
* {@link UserStatus}.<br> * {@link UserStatus}.<br>
* <br>
* Project: <strong>envoy-common</strong><br>
* File: <strong>User.java</strong><br>
* Created: <strong>28.12.2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Common v0.2-alpha * @since Envoy Common v0.2-alpha
@ -32,7 +26,7 @@ public final class User extends Contact {
* *
* @since Envoy Common v0.2-alpha * @since Envoy Common v0.2-alpha
*/ */
public static enum UserStatus { public enum UserStatus {
/** /**
* select this, if a user is online and can be interacted with * select this, if a user is online and can be interacted with

View File

@ -1,6 +1,6 @@
/** /**
* This package contains all data objects that are used both by Envoy Client and * This package contains all data objects that are used both by Envoy Client and
* by Envoy Server Standalone. * by Envoy Server.
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer

View File

@ -1,13 +1,10 @@
package envoy.event; package envoy.event;
/** /**
* This enum declares all modification possibilities for a given container.<br> * This enum declares all modification possibilities for a given container.
* <p>
* These can be: {@link ElementOperation#ADD} or * These can be: {@link ElementOperation#ADD} or
* {@link ElementOperation#REMOVE}.<br> * {@link ElementOperation#REMOVE}.
* <br>
* Project: <strong>envoy-common</strong><br>
* File: <strong>ElementOperation.java</strong><br>
* Created: <strong>25 Mar 2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.1-beta * @since Envoy Common v0.1-beta

View File

@ -8,10 +8,6 @@ import dev.kske.eventbus.IEvent;
* This class serves as a convenience base class for all events. It implements * This class serves as a convenience base class for all events. It implements
* the {@link IEvent} interface and provides a generic value. For events without * the {@link IEvent} interface and provides a generic value. For events without
* a value there also is {@link envoy.event.Event.Valueless}. * a value there also is {@link envoy.event.Event.Valueless}.
* <p>
* Project: <strong>envoy-common</strong><br>
* File: <strong>Event.java</strong><br>
* Created: <strong>04.12.2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @param <T> the type of the Event * @param <T> the type of the Event
@ -34,11 +30,7 @@ public abstract class Event<T> implements IEvent, Serializable {
public String toString() { return String.format("%s[value=%s]", this.getClass().getSimpleName(), value); } public String toString() { return String.format("%s[value=%s]", this.getClass().getSimpleName(), value); }
/** /**
* Serves as a super class for events that do not carry a value.<br> * Serves as a super class for events that do not carry a value.
* <br>
* Project: <strong>envoy-common</strong><br>
* File: <strong>Event.java</strong><br>
* Created: <strong>11 Feb 2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Common v0.2-alpha * @since Envoy Common v0.2-alpha

View File

@ -1,16 +1,11 @@
package envoy.event; package envoy.event;
import java.util.HashSet; import java.util.*;
import java.util.Set;
import envoy.data.User; import envoy.data.User;
/** /**
* This event creates a group with the given name.<br> * This event creates a group with the given name.
* <br>
* Project: <strong>envoy-common</strong><br>
* File: <strong>GroupCreation.java</strong><br>
* Created: <strong>25 Mar 2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.1-beta * @since Envoy Common v0.1-beta
@ -30,7 +25,7 @@ public final class GroupCreation extends Event<String> {
*/ */
public GroupCreation(String value, Set<Long> initialMemberIDs) { public GroupCreation(String value, Set<Long> initialMemberIDs) {
super(value); super(value);
this.initialMemberIDs = (initialMemberIDs != null) ? initialMemberIDs : new HashSet<>(); this.initialMemberIDs = initialMemberIDs != null ? initialMemberIDs : new HashSet<>();
} }
/** /**

View File

@ -3,10 +3,6 @@ package envoy.event;
/** /**
* Used to communicate with a client that his request to create a group might * 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. * have been rejected as it might be disabled on his current server.
* <p>
* Project: <strong>common</strong><br>
* File: <strong>GroupCreationResult.java</strong><br>
* Created: <strong>22.08.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.2-beta * @since Envoy Common v0.2-beta

View File

@ -6,10 +6,6 @@ import envoy.data.GroupMessage;
import envoy.data.Message.MessageStatus; import envoy.data.Message.MessageStatus;
/** /**
* Project: <strong>envoy-common</strong><br>
* File: <strong>GroupMessageStatusChange.java</strong><br>
* Created: <strong>18.04.2020</strong><br>
*
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Common v0.1-beta * @since Envoy Common v0.1-beta
*/ */

View File

@ -1,18 +1,15 @@
package envoy.event; package envoy.event;
import envoy.data.Contact; import static envoy.event.ElementOperation.*;
import envoy.data.Group;
import envoy.data.User; import envoy.data.*;
/** /**
* This event is used to communicate changes in the group size between client * This event is used to communicate changes in the group size between client
* and server.<br> * and server.
* <p>
* Possible actions are adding or removing certain {@link User}s to or from a * Possible actions are adding or removing certain {@link User}s to or from a
* certain {@link Group}. * certain {@link Group}.
* <br>
* Project: <strong>envoy-common</strong><br>
* File: <strong>GroupResize.java</strong><br>
* Created: <strong>25 Mar 2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.1-beta * @since Envoy Common v0.1-beta
@ -36,13 +33,13 @@ public final class GroupResize extends Event<User> {
*/ */
public GroupResize(User user, Group group, ElementOperation operation) { public GroupResize(User user, Group group, ElementOperation operation) {
super(user); super(user);
if (group.getContacts().contains(user)) {
if (operation.equals(ElementOperation.ADD)) throw new IllegalArgumentException(
"Cannot add " + user + " to group " + group.getID() + " because he is already a member of this group");
} else if (operation.equals(ElementOperation.REMOVE))
throw new IllegalArgumentException("Cannot remove " + user + " from group " + group.getID() + " because he is no part of this group");
groupID = group.getID();
this.operation = operation; this.operation = operation;
if (group.getContacts().contains(user)) {
if (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));
groupID = group.getID();
} }
/** /**
@ -58,8 +55,22 @@ public final class GroupResize extends Event<User> {
public ElementOperation getOperation() { return operation; } public ElementOperation getOperation() { return operation; }
/** /**
* {@inheritDoc} * Applies the operation to a group.
*
* @param group the group to resize
* @since Envoy Common v0.2-beta
*/ */
public void apply(Group group) {
switch (operation) {
case ADD:
group.getContacts().add(value);
break;
case REMOVE:
group.getContacts().remove(value);
break;
}
}
@Override @Override
public String toString() { return String.format("GroupResize[userid=%d,groupid=%d,operation=%s]", get(), groupID, operation); } public String toString() { return String.format("GroupResize[userid=%d,groupid=%d,operation=%s]", get(), groupID, operation); }
} }

View File

@ -3,10 +3,6 @@ package envoy.event;
/** /**
* Signifies to the client that the handshake failed for the attached * Signifies to the client that the handshake failed for the attached
* reason. * reason.
* <p>
* Project: <strong>envoy-common</strong><br>
* File: <strong>HandshakeRejection.java</strong><br>
* Created: <strong>28 Jan 2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Common v0.3-alpha * @since Envoy Common v0.3-alpha

View File

@ -2,11 +2,7 @@ package envoy.event;
/** /**
* Signifies to the server that the client needs a new * Signifies to the server that the client needs a new
* {@link envoy.data.IDGenerator} instance.<br> * {@link envoy.data.IDGenerator} instance.
* <br>
* Project: <strong>envoy-common</strong><br>
* File: <strong>IDGeneratorRequest.java</strong><br>
* Created: <strong>28 Jan 2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Common v0.3-alpha * @since Envoy Common v0.3-alpha

View File

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

View File

@ -2,11 +2,7 @@ package envoy.event;
/** /**
* This class allows envoy users to send an issue proposal to the server who, if * This class allows envoy users to send an issue proposal to the server who, if
* not disabled by its admin, will forward it directly to gitea. * not disabled by its administrator, will forward it directly to Gitea.
* <p>
* Project: <strong>common</strong><br>
* File: <strong>IssueProposal.java</strong><br>
* Created: <strong>05.08.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.2-beta * @since Envoy Common v0.2-beta

View File

@ -5,10 +5,6 @@ import java.time.Instant;
import envoy.data.Message; import envoy.data.Message;
/** /**
* Project: <strong>envoy-common</strong><br>
* File: <strong>MessageStatusChange.java</strong><br>
* Created: <strong>6 Jan 2020</strong><br>
*
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Common v0.2-alpha * @since Envoy Common v0.2-alpha
*/ */

View File

@ -3,14 +3,11 @@ package envoy.event;
import envoy.data.Contact; import envoy.data.Contact;
/** /**
* This event informs<br> * This event informs
* <p>
* a) the server of the name change of a user or a group. * a) the server of the name change of a user or a group.
* b) another user of this users name change. * b) another user of this users name change.
* *
* Project: <strong>envoy-common</strong><br>
* File: <strong>NameChange.java</strong><br>
* Created: <strong>25 Mar 2020</strong><br>
*
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.1-beta * @since Envoy Common v0.1-beta
*/ */

View File

@ -2,10 +2,6 @@ package envoy.event;
/** /**
* This event can be used to transmit a new authentication token to a client. * This event can be used to transmit a new authentication token to a client.
* <p>
* Project: <strong>envoy-common</strong><br>
* File: <strong>NewAuthToken.java</strong><br>
* Created: <strong>19.09.2020</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @since Envoy Common v0.2-beta * @since Envoy Common v0.2-beta

View File

@ -3,10 +3,6 @@ package envoy.event;
/** /**
* This event is used so that the server can tell the client that attachments * This event is used so that the server can tell the client that attachments
* will be filtered out. * will be filtered out.
* <p>
* Project: <strong>common</strong><br>
* File: <strong>NoAttachments.java</strong><br>
* Created: <strong>22.08.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.2-beta * @since Envoy Common v0.2-beta

View File

@ -3,10 +3,6 @@ package envoy.event;
import envoy.data.Contact; import envoy.data.Contact;
/** /**
* Project: <strong>envoy-common</strong><br>
* File: <strong>PasswordChangeRequest.java</strong><br>
* Created: <strong>31.07.2020</strong><br>
*
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.2-beta * @since Envoy Common v0.2-beta
*/ */

View File

@ -3,10 +3,6 @@ package envoy.event;
/** /**
* This class acts as a notice to the user whether his * This class acts as a notice to the user whether his
* {@link envoy.event.PasswordChangeRequest} was successful. * {@link envoy.event.PasswordChangeRequest} was successful.
* <p>
* Project: <strong>envoy-common</strong><br>
* File: <strong>PasswordChangeResult.java</strong><br>
* Created: <strong>01.08.2020</strong><br>
* *
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.2-beta * @since Envoy Common v0.2-beta

View File

@ -1,10 +1,6 @@
package envoy.event; package envoy.event;
/** /**
* Project: <strong>envoy-common</strong><br>
* File: <strong>ProfilePicChange.java</strong><br>
* Created: <strong>31.07.2020</strong><br>
*
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.2-beta * @since Envoy Common v0.2-beta
*/ */

View File

@ -4,10 +4,6 @@ import envoy.data.User;
import envoy.data.User.UserStatus; import envoy.data.User.UserStatus;
/** /**
* Project: <strong>envoy-common</strong><br>
* File: <strong>UserStatusChange.java</strong><br>
* Created: <strong>1 Feb 2020</strong><br>
*
* @author Leon Hofmeister * @author Leon Hofmeister
* @since Envoy Common v0.2-alpha * @since Envoy Common v0.2-alpha
*/ */

View File

@ -1,15 +1,10 @@
package envoy.event.contact; package envoy.event.contact;
import envoy.data.Contact; import envoy.data.Contact;
import envoy.event.ElementOperation; import envoy.event.*;
import envoy.event.Event;
/** /**
* Signifies the modification of a contact list.<br> * Signifies the modification of a contact list.
* <br>
* Project: <strong>envoy-common</strong><br>
* File: <strong>ContactOperation.java</strong><br>
* Created: <strong>05.02.2020</strong><br>
* *
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Common v0.2-alpha * @since Envoy Common v0.2-alpha

View File

@ -4,10 +4,6 @@ import envoy.event.Event;
/** /**
* Requests a user search from the server. * Requests a user search from the server.
* <p>
* Project: <strong>envoy-common</strong><br>
* File: <strong>UserSearchRequest.java</strong><br>
* Created: <strong>05.02.2020</strong><br>
* *
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @since Envoy Common v0.2-alpha * @since Envoy Common v0.2-alpha

Some files were not shown because too many files have changed in this diff Show More