Merge branch 'develop' into f/themes

This commit is contained in:
delvh
2019-12-07 14:50:20 +01:00
committed by GitHub
23 changed files with 1388 additions and 878 deletions

View File

@ -1,5 +1,7 @@
package envoy.client;
import java.util.logging.Logger;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
@ -28,18 +30,20 @@ public class Client {
private Config config;
private User sender, recipient;
private static final Logger logger = Logger.getLogger(Client.class.getSimpleName());
public Client(Config config, String username) {
this.config = config;
sender = getUser(username);
System.out.println("ID: " + sender.getID());
logger.info("ID: " + sender.getID());
}
private <T, R> R post(String uri, T body, Class<R> responseBodyClass) {
javax.ws.rs.client.Client client = ClientBuilder.newClient();
WebTarget target = client.target(uri);
Response response = target.request().post(Entity.entity(body, "application/xml"));
R responseBody = response.readEntity(responseBodyClass);
javax.ws.rs.client.Client client = ClientBuilder.newClient();
WebTarget target = client.target(uri);
Response response = target.request().post(Entity.entity(body, "application/xml"));
R responseBody = response.readEntity(responseBodyClass);
response.close();
client.close();
@ -92,7 +96,7 @@ public class Client {
if (returnSenderSync.getUsers().size() == 1) {
returnSender = returnSenderSync.getUsers().get(0);
} else {
System.out.println("ERROR exiting...");
logger.warning("ERROR exiting...");
}
return returnSender;
@ -129,7 +133,7 @@ public class Client {
* Updating UserStatus of all users in LocalDB. (Server sends all users with
* their updated UserStatus to the client.) <br>
*
* @param userId the client who sends the sync
* @param userId the id of the {@link Client} who sends the {@link Sync}
* @param sync the sync object (yet to be converted from java class to sync.xml)
* @return a returnSync.xml file
* @since Envoy v0.1-alpha
@ -166,16 +170,16 @@ public class Client {
public User getRecipient() { return recipient; }
/**
* Sets the recipient.
* Sets the recipient.
*
* @param recipient - the recipient to set
* @since Envoy v0.1-alpha
*/
public void setRecipient(User recipient) { this.recipient = recipient; }
/**
/**
* @return true, if a recipient is selected
* @since Envoy v0.1-alpha
*/
public boolean hasRecipient() { return recipient != null; }
}

View File

@ -32,7 +32,7 @@ public class Config {
*
* @param properties a {@link Properties} object containing information about
* the server and port, as well as the path to the local
* database
* database and the synchronization timeout
* @since Envoy v0.1-alpha
*/
public void load(Properties properties) {
@ -65,8 +65,6 @@ public class Config {
localDB = new File(args[++i]);
break;
}
if (localDB == null) localDB = new File(".\\localDB");
if (syncTimeout == 0) syncTimeout = 1000;
}
/**
@ -116,7 +114,7 @@ public class Config {
/**
* Changes the default local database.
* Exclusively intended for development purposes.
*
*
* @param localDB the file containing the local database
* @since Envoy v0.1-alpha
**/

View File

@ -1,313 +1,295 @@
package envoy.client;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import envoy.exception.EnvoyException;
import envoy.schema.Message;
import envoy.schema.Message.Metadata.MessageState;
import envoy.schema.ObjectFactory;
import envoy.schema.Sync;
import envoy.schema.User;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>LocalDB.java</strong><br>
* Created: <strong>27.10.2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha
*/
public class LocalDB {
private File localDB;
private User sender;
private List<Chat> chats = new ArrayList<>();
private ObjectFactory objectFactory = new ObjectFactory();
private DatatypeFactory datatypeFactory;
private Sync unreadMessagesSync = objectFactory.createSync();
private Sync sync = objectFactory.createSync();
private Sync readMessages = objectFactory.createSync();
/**
* Constructs an empty local database.
*
* @param sender the user that is logged in with this client
* @since Envoy v0.1-alpha
*/
public LocalDB(User sender) {
this.sender = sender;
try {
datatypeFactory = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException e) {
e.printStackTrace();
}
}
/**
* Initializes the local database and fills it with values
* if the user has already sent or received messages.
*
* @param localDBDir the directory where we wish to save/load the database from.
* @throws EnvoyException if the directory selected is not an actual directory.
* @since Envoy v0.1-alpha
*/
package envoy.client;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import envoy.client.event.EventBus;
import envoy.client.event.MessageCreationEvent;
import envoy.exception.EnvoyException;
import envoy.schema.Message;
import envoy.schema.Message.Metadata.MessageState;
import envoy.schema.ObjectFactory;
import envoy.schema.Sync;
import envoy.schema.User;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>LocalDB.java</strong><br>
* Created: <strong>27.10.2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha
*/
public class LocalDB {
private File localDB;
private User sender;
private List<Chat> chats = new ArrayList<>();
private ObjectFactory objectFactory = new ObjectFactory();
private DatatypeFactory datatypeFactory;
private Sync unreadMessagesSync = objectFactory.createSync();
private Sync sync = objectFactory.createSync();
private Sync readMessages = objectFactory.createSync();
private static final Logger logger = Logger.getLogger(LocalDB.class.getSimpleName());
/**
* Constructs an empty local database.
*
* @param sender the user that is logged in with this client
* @since Envoy v0.1-alpha
*/
public LocalDB(User sender) {
this.sender = sender;
try {
datatypeFactory = DatatypeFactory.newInstance();
} catch (DatatypeConfigurationException e) {
e.printStackTrace();
}
}
/**
* Initializes the local database and fills it with values
* if the user has already sent or received messages.
*
* @param localDBDir the directory where we wish to save/load the database from.
* @throws EnvoyException if the directory selected is not an actual directory.
* @since Envoy v0.1-alpha
*/
public void initializeDBFile(File localDBDir) throws EnvoyException {
if (localDBDir.exists() && !localDBDir.isDirectory()) throw new EnvoyException(
String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath()));
if (localDBDir.exists() && !localDBDir.isDirectory())
throw new EnvoyException(String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath()));
localDB = new File(localDBDir, sender.getID() + ".db");
if (localDB.exists()) loadFromLocalDB();
}
/**
* Saves the database into a file for future use.<br>
* It is theoretically possible to fail due to unknown causes.<br>
* In such a case, every message sent/ received during that session will be
* lost.
*
* @throws IOException gets thrown, if saving failed for some reason
* @since Envoy v0.1-alpha
*/
public void saveToLocalDB() throws IOException{
try {
localDB.getParentFile().mkdirs();
localDB.createNewFile();
} catch (IOException e) {
e.printStackTrace();
System.err.println("unable to save the messages");
}
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) {
out.writeObject(chats);
} catch (IOException ex) {
ex.printStackTrace();
System.err.println("unable to save the messages");
}
}
/**
* Loads all chats saved by Envoy for the client user.
*
* @throws EnvoyException if something fails while loading.
* @since Envoy v0.1-alpha
*/
@SuppressWarnings("unchecked")
private void loadFromLocalDB() throws EnvoyException {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(localDB))) {
Object obj = in.readObject();
if (obj instanceof ArrayList<?>) chats = (ArrayList<Chat>) obj;
} catch (ClassNotFoundException | IOException e) {
throw new EnvoyException(e);
}
}
/**
* Creates a {@link Message} object serializable to XML.
*
* @param textContent The content (text) of the message
* @param recipientID self explanatory
* @return prepared {@link Message} object
*/
public Message createMessage(String textContent, long recipientID) {
Message.Metadata metaData = objectFactory.createMessageMetadata();
metaData.setSender(sender.getID());
metaData.setRecipient(recipientID);
metaData.setState(MessageState.WAITING);
metaData.setDate(datatypeFactory.newXMLGregorianCalendar(Instant.now().toString()));
Message.Content content = objectFactory.createMessageContent();
content.setType("text");
content.setText(textContent);
Message message = objectFactory.createMessage();
message.setMetadata(metaData);
message.getContent().add(content);
return message;
}
public Sync fillSync(long userId) {
addWaitingMessagesToSync();
sync.getMessages().addAll(readMessages.getMessages());
readMessages.getMessages().clear();
System.out.println(sync.getMessages().size());
return sync;
}
public void applySync(Sync returnSync) {
for (int i = 0; i < returnSync.getMessages().size(); i++) {
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0
&& returnSync.getMessages().get(i).getMetadata().getState() == MessageState.SENT) {
// Update Local Messages with State WAITING (add Message ID and change State to
// SENT)
for (int j = 0; j < sync.getMessages().size(); j++) {
if (j == i) {
sync.getMessages().get(j).getMetadata().setMessageId(returnSync.getMessages().get(j).getMetadata().getMessageId());
sync.getMessages().get(j).getMetadata().setState(returnSync.getMessages().get(j).getMetadata().getState());
}
}
}
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 && returnSync.getMessages().get(i).getMetadata().getSender() != 0
&& returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) {
// these are the unread Messages from the server
unreadMessagesSync.getMessages().add(returnSync.getMessages().get(i));
}
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 && returnSync.getMessages().get(i).getMetadata().getSender() == 0
&& returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) {
// Update Messages in localDB to state RECEIVED
for (int j = 0; j < getChats().size(); j++) {
if (getChats().get(j).getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) {
for (int k = 0; k < getChats().get(j).getModel().getSize(); k++) {
if (getChats().get(j).getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages()
.get(i)
.getMetadata()
.getMessageId()) {
// Update Message in LocalDB
getChats().get(j).getModel().get(k).getMetadata().setState(returnSync.getMessages().get(j).getMetadata().getState());
}
}
}
}
}
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0
&& returnSync.getMessages().get(i).getMetadata().getState() == MessageState.READ) {
// Update local Messages to state READ
System.out.println("Message with ID: " + returnSync.getMessages().get(i).getMetadata().getMessageId()
+ "was initialized to be set to READ in localDB.");
for (int j = 0; j < getChats().size(); j++) {
if (getChats().get(j).getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) {
System.out.println("Chat with: " + getChats().get(j).getRecipient().getID() + "was selected.");
for (int k = 0; k < getChats().get(j).getModel().getSize(); k++) {
if (getChats().get(j).getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages()
.get(i)
.getMetadata()
.getMessageId()) {
System.out.println(
"Message with ID: " + getChats().get(j).getModel().get(k).getMetadata().getMessageId() + "was selected.");
getChats().get(j).getModel().get(k).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
System.out
.println("Message State is now: " + getChats().get(j).getModel().get(k).getMetadata().getState().toString());
}
}
}
}
}
}
// Updating UserStatus of all users in LocalDB
for (int j = 0; j < returnSync.getUsers().size(); j++) {
for (int k = 0; k < getChats().size(); k++) {
if (getChats().get(k).getRecipient().getID() == returnSync.getUsers().get(j).getID()) {
getChats().get(k).getRecipient().setStatus(returnSync.getUsers().get(j).getStatus());
System.out.println(getChats().get(k).getRecipient().getStatus().toString());
}
}
}
sync.getMessages().clear();
sync.getUsers().clear();
}
if (localDB.exists()) loadFromLocalDB();
}
/**
* Adds a message to the "sync" Sync object.
*
* @param message the message to send
* @since Envoy v0.1-alpha
*/
public void addMessageToSync(Message message) { sync.getMessages().add(message); }
* Saves the database into a file for future use.
*
* @throws IOException if something went wrong during saving
* @since Envoy v0.1-alpha
*/
public void saveToLocalDB() throws IOException {
localDB.getParentFile().mkdirs();
localDB.createNewFile();
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) {
out.writeObject(chats);
} catch (IOException ex) {
throw ex;
}
}
/**
* Adds the unread messages returned from the server in the latest sync to the
* right chats in the LocalDB.
*
* @since Envoy v0.1-alpha
*/
public void addUnreadMessagesToLocalDB() {
Sync unreadMessages = unreadMessagesSync;
for (int i = 0; i < unreadMessages.getMessages().size(); i++)
for (int j = 0; j < getChats().size(); j++)
if (getChats().get(j).getRecipient().getID() == unreadMessages.getMessages().get(i).getMetadata().getSender()) {
getChats().get(j).appendMessage(unreadMessages.getMessages().get(i));
}
}
* Loads all chats saved by Envoy for the client user.
*
* @throws EnvoyException if something fails while loading.
* @since Envoy v0.1-alpha
*/
@SuppressWarnings("unchecked")
private void loadFromLocalDB() throws EnvoyException {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(localDB))) {
Object obj = in.readObject();
if (obj instanceof ArrayList<?>) chats = (ArrayList<Chat>) obj;
} catch (ClassNotFoundException | IOException e) {
throw new EnvoyException(e);
}
}
/**
* Changes all messages with state {@code RECEIVED} of a specific chat to state
* {@code READ}.
* <br>
* Adds these messages to the {@code readMessages} {@link Sync} object.
*
* @param currentChat the chat that was just opened
* @since Envoy v0.1-alpha
*/
public void setMessagesToRead(Chat currentChat) {
for (int i = currentChat.getModel().size() - 1; i >= 0; --i)
if (currentChat.getModel().get(i).getMetadata().getRecipient() != currentChat.getRecipient().getID())
if (currentChat.getModel().get(i).getMetadata().getState() == MessageState.RECEIVED) {
currentChat.getModel().get(i).getMetadata().setState(MessageState.READ);
readMessages.getMessages().add(currentChat.getModel().get(i));
} else break;
}
/**
* Adds all messages with state {@code WAITING} from the {@link LocalDB} to the
* {@link Sync} object.
* Creates a {@link Message} object serializable to XML.
*
* @param textContent The content (text) of the message
* @param recipientID The recipient of the message
* @return prepared {@link Message} object
* @since Envoy v0.1-alpha
*/
public Message createMessage(String textContent, long recipientID) {
Message.Metadata metaData = objectFactory.createMessageMetadata();
metaData.setSender(sender.getID());
metaData.setRecipient(recipientID);
metaData.setState(MessageState.WAITING);
metaData.setDate(datatypeFactory.newXMLGregorianCalendar(Instant.now().toString()));
Message.Content content = objectFactory.createMessageContent();
content.setType("text");
content.setText(textContent);
Message message = objectFactory.createMessage();
message.setMetadata(metaData);
message.getContent().add(content);
return message;
}
/**
* Creates a {@link Sync} object filled with the changes that occurred to the
* local database since the last synchronization.
*
* @param message the message that is not yet received by the other user
* @param currentChat the chat that is currently open
* @since Envoy v0.1-alpha
* @param userId the ID of the user that is synchronized by this client
* @return {@link Sync} object filled with the current changes
* @since Envoy v0.1-alpha
*/
public void addWaitingMessageToLocalDB(Message message, Chat currentChat) { currentChat.appendMessage(message); }
/**
* Adds all messages with State WAITING from the {@link LocalDB} to the Sync.
*
* @since Envoy v0.1-alpha
public Sync fillSync(long userId) {
addWaitingMessagesToSync();
sync.getMessages().addAll(readMessages.getMessages());
readMessages.getMessages().clear();
logger.finest(String.format("Filled sync with %d messages.", sync.getMessages().size()));
return sync;
}
/**
* Applies the changes carried by a {@link Sync} object to the local database
*
* @param returnSync the {@link Sync} object to apply
* @since Envoy v0.1-alpha
*/
private void addWaitingMessagesToSync() {
for (Chat chat : getChats())
for (int i = 0; i < chat.getModel().size(); i++)
if (chat.getModel().get(i).getMetadata().getState() == MessageState.WAITING) {
System.out.println("Got Waiting Message");
sync.getMessages().add(chat.getModel().get(i));
}
}
/**
* Clears the {@code unreadMessagesSync} {@link Sync} object.
*
* @since Envoy v0.1-alpha
*/
public void clearUnreadMessagesSync() { unreadMessagesSync.getMessages().clear(); }
/**
* @return all saves {@link Chat} objects that list the client user as the
* client
* @since Envoy v0.1-alpha
**/
public List<Chat> getChats() { return chats; }
/**
* @return the {@link User} who initialized the local database
* @since Envoy v0.1-alpha
*/
public User getUser() { return sender; }
}
public void applySync(Sync returnSync) {
for (int i = 0; i < returnSync.getMessages().size(); i++) {
// The message has an ID
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0) {
// Messages are processes differently corresponding to their state
switch (returnSync.getMessages().get(i).getMetadata().getState()) {
case SENT:
// Update previously waiting and now sent messages that were assigned an ID by
// the server
sync.getMessages().get(i).getMetadata().setMessageId(returnSync.getMessages().get(i).getMetadata().getMessageId());
sync.getMessages().get(i).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
break;
case RECEIVED:
if (returnSync.getMessages().get(i).getMetadata().getSender() != 0) {
// these are the unread Messages from the server
unreadMessagesSync.getMessages().add(returnSync.getMessages().get(i));
// Create and dispatch message creation event
EventBus.getInstance().dispatch(new MessageCreationEvent(returnSync.getMessages().get(i)));
} else {
// Update Messages in localDB to state RECEIVED
for (Chat chat : getChats())
if (chat.getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient())
for (int j = 0; j < chat.getModel().getSize(); j++)
if (chat.getModel().get(j).getMetadata().getMessageId() == returnSync.getMessages()
.get(i)
.getMetadata()
.getMessageId())
chat.getModel().get(j).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
}
break;
case READ:
// Update local Messages to state READ
logger.info("Message with ID: " + returnSync.getMessages().get(i).getMetadata().getMessageId()
+ "was initialized to be set to READ in localDB.");
for (Chat chat : getChats())
if (chat.getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) {
logger.info("Chat with: " + chat.getRecipient().getID() + "was selected.");
for (int k = 0; k < chat.getModel().getSize(); k++)
if (chat.getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages()
.get(i)
.getMetadata()
.getMessageId()) {
logger.info("Message with ID: " + chat.getModel().get(k).getMetadata().getMessageId() + "was selected.");
chat.getModel().get(k).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
logger.info("Message State is now: " + chat.getModel().get(k).getMetadata().getState());
}
}
break;
}
}
}
// Updating UserStatus of all users in LocalDB
for (User user : returnSync.getUsers())
for (Chat chat : getChats())
if (user.getID() == chat.getRecipient().getID()) {
chat.getRecipient().setStatus(user.getStatus());
logger.info(chat.getRecipient().getStatus().toString());
}
sync.getMessages().clear();
sync.getUsers().clear();
}
/**
* Adds the unread messages returned from the server in the latest sync to the
* right chats in the LocalDB.
*
* @since Envoy v0.1-alpha
*/
public void addUnreadMessagesToLocalDB() {
for (Message message : unreadMessagesSync.getMessages())
for (Chat chat : getChats())
if (message.getMetadata().getSender() == chat.getRecipient().getID()) {
chat.appendMessage(message);
break;
}
}
/**
* Changes all messages with state {@code RECEIVED} of a specific chat to state
* {@code READ}.
* <br>
* Adds these messages to the {@code readMessages} {@link Sync} object.
*
* @param currentChat the {@link Chat} that was just opened
* @since Envoy v0.1-alpha
*/
public void setMessagesToRead(Chat currentChat) {
for (int i = currentChat.getModel().size() - 1; i >= 0; --i)
if (currentChat.getModel().get(i).getMetadata().getRecipient() != currentChat.getRecipient().getID())
if (currentChat.getModel().get(i).getMetadata().getState() == MessageState.RECEIVED) {
currentChat.getModel().get(i).getMetadata().setState(MessageState.READ);
readMessages.getMessages().add(currentChat.getModel().get(i));
} else break;
}
/**
* Adds all messages with state {@code WAITING} from the {@link LocalDB} to the
* {@link Sync} object.
*
* @since Envoy v0.1-alpha
*/
private void addWaitingMessagesToSync() {
for (Chat chat : getChats())
for (int i = 0; i < chat.getModel().size(); i++)
if (chat.getModel().get(i).getMetadata().getState() == MessageState.WAITING) {
logger.info("Got Waiting Message");
sync.getMessages().add(chat.getModel().get(i));
}
}
/**
* Clears the {@code unreadMessagesSync} {@link Sync} object.
*
* @since Envoy v0.1-alpha
*/
public void clearUnreadMessagesSync() { unreadMessagesSync.getMessages().clear(); }
/**
* @return all saved {@link Chat} objects that list the client user as the
* sender
* @since Envoy v0.1-alpha
**/
public List<Chat> getChats() { return chats; }
/**
* @return the {@link User} who initialized the local database
* @since Envoy v0.1-alpha
*/
public User getUser() { return sender; }
}

View File

@ -0,0 +1,17 @@
package envoy.client.event;
/**
* Project: <strong>envoy-clientChess</strong><br>
* File: <strong>Event.javaEvent.java</strong><br>
* Created: <strong>04.12.2019</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy v0.2-alpha
*/
public interface Event<T> {
/**
* @return the data associated with this event
*/
T get();
}

View File

@ -0,0 +1,71 @@
package envoy.client.event;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This class handles events by allowing {@link EventHandler} object to register
* themselves and then be notified about certain events dispatched by the event
* bus.<br>
* <br>
* The event bus is a singleton and can be used across the entire application to
* guarantee the propagation of events.
* Project: <strong>envoy-client</strong><br>
* File: <strong>EventBus.java</strong><br>
* Created: <strong>04.12.2019</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy v0.2-alpha
*/
public class EventBus {
/**
* Contains all {@link EventHandler} instances registered at this
* {@link EventBus}.
*/
private List<EventHandler> handlers = new ArrayList<>();
/**
* The singleton instance of this {@link EventBus} that is used across the
* entire application.
*/
private static EventBus eventBus = new EventBus();
/**
* This constructor is not accessible from outside this class because a
* singleton instance of it is provided by the {@link EventBus#getInstance()}
* method.
*/
private EventBus() {}
/**
* @return the singleton instance of the {@link EventBus}
* @since Envoy v0.2-alpha
*/
public static EventBus getInstance() { return eventBus; }
/**
* Registers a list of {@link EventHandler} objects to be notified when a
* {@link Event} is dispatched that they are subscribed to.
*
* @param handlers the {@link EventHandler} objects to register
* @since Envoy v0.2-alpha
*/
public void register(EventHandler... handlers) { this.handlers.addAll(Arrays.asList(handlers)); }
/**
* Dispatches a {@link Event} to every {@link EventHandler} subscribed to it.
*
* @param event the {@link Event} to dispatch
* @since Envoy v0.2-alpha
*/
public void dispatch(Event<?> event) { handlers.stream().filter(h -> h.supports().contains(event.getClass())).forEach(h -> h.handle(event)); }
/**
* @return a list of all {@link EventHandler} instances currently registered at
* this {@link EventBus}
* @since Envoy v0.2-alpha
*/
public List<EventHandler> getHandlers() { return handlers; }
}

View File

@ -0,0 +1,25 @@
package envoy.client.event;
import java.util.Set;
/**
* Project: <strong>envoy-clientChess</strong><br>
* File: <strong>EventHandler.javaEvent.java</strong><br>
* Created: <strong>04.12.2019</strong><br>
*
* @author Kai S. K. Engelbart
*/
public interface EventHandler {
/**
* Consumes an event dispatched by the event bus.
*
* @param event The event dispatched by the event bus, only of supported type
*/
void handle(Event<?> event);
/**
* @return A set of classes this class is supposed to handle in events
*/
Set<Class<? extends Event<?>>> supports();
}

View File

@ -0,0 +1,18 @@
package envoy.client.event;
import envoy.schema.Message;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageCreationEvent.java</strong><br>
* Created: <strong>4 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart
*/
public class MessageCreationEvent extends MessageEvent {
/**
* @param message the {@link Message} that has been created
*/
public MessageCreationEvent(Message message) { super(message); }
}

View File

@ -0,0 +1,20 @@
package envoy.client.event;
import envoy.schema.Message;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageCreationEvent.java</strong><br>
* Created: <strong>4 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart
*/
public class MessageEvent implements Event<Message> {
protected final Message message;
public MessageEvent(Message message) { this.message = message; }
@Override
public Message get() { return message; }
}

View File

@ -0,0 +1,18 @@
package envoy.client.event;
import envoy.schema.Message;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>MessageModificationEvent.java</strong><br>
* Created: <strong>4 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart
*/
public class MessageModificationEvent extends MessageEvent {
/**
* @param message the {@link Message} that has been modified
*/
public MessageModificationEvent(Message message) { super(message); }
}

View File

@ -1,381 +1,373 @@
package envoy.client.ui;
import java.awt.ComponentOrientation;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import envoy.client.Chat;
import envoy.client.Client;
import envoy.client.Config;
import envoy.client.LocalDB;
import envoy.client.Settings;
import envoy.schema.Message;
import envoy.schema.Sync;
import envoy.schema.User;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>ChatWindow.java</strong><br>
* Created: <strong>28 Sep 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @author Leon Hofmeister
* @since Envoy v0.1-alpha
*/
public class ChatWindow extends JFrame {
private static final long serialVersionUID = 6865098428255463649L;
// user specific objects
private Client client;
private LocalDB localDB;
// GUI components
private JPanel contentPane = new JPanel();
private JTextArea messageEnterTextArea = new JTextArea();
private JList<User> userList = new JList<>();
private Chat currentChat;
private JList<Message> messageList = new JList<>();
private JScrollPane scrollPane = new JScrollPane();
private JTextPane textPane = new JTextPane();
// private JCheckBox jCbChangeMode;
private JButton postButton = new JButton("Post");
private JButton settingsButton = new JButton("Settings");
private static int space = 4;
public ChatWindow(Client client, LocalDB localDB) {
this.client = client;
this.localDB = localDB;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 600, 800);
setTitle("Envoy");
setLocationRelativeTo(null);
// Save chats when window closes
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
try {
localDB.saveToLocalDB();
Settings.getInstance().save();
} catch (IOException e1) {
e1.printStackTrace();
System.err.println("Could not save localDB");
}
}
});
contentPane.setBorder(new EmptyBorder(space, space, space, space));
setContentPane(contentPane);
GridBagLayout gbl_contentPane = new GridBagLayout();
gbl_contentPane.columnWidths = new int[] { 1, 1, 1 };
gbl_contentPane.rowHeights = new int[] { 1, 1, 1 };
gbl_contentPane.columnWeights = new double[] { 0.3, 1.0, 0.1 };
gbl_contentPane.rowWeights = new double[] { 0.05, 1.0, 0.07 };
contentPane.setLayout(gbl_contentPane);
messageList.setCellRenderer(new MessageListRenderer());
messageList.setFocusTraversalKeysEnabled(false);
messageList.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
DefaultListModel<Message> messageListModel = new DefaultListModel<>();
messageList.setModel(messageListModel);
messageList.setFont(new Font("Arial", Font.PLAIN, 17));
messageList.setFixedCellHeight(60);
messageList.setBorder(new EmptyBorder(space, space, space, space));
scrollPane.setViewportView(messageList);
scrollPane.setBorder(null);
GridBagConstraints gbc_scrollPane = new GridBagConstraints();
gbc_scrollPane.fill = GridBagConstraints.BOTH;
gbc_scrollPane.gridwidth = 2;
gbc_scrollPane.gridx = 1;
gbc_scrollPane.gridy = 1;
gbc_scrollPane.insets = new Insets(space, space, space, space);
contentPane.add(scrollPane, gbc_scrollPane);
// Message enter field
messageEnterTextArea.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER
&& ((Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0)
|| (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) {
postMessage(messageList);
}
}
});
// Checks for changed Message
messageEnterTextArea.setWrapStyleWord(true);
messageEnterTextArea.setLineWrap(true);
messageEnterTextArea.setBorder(null);
messageEnterTextArea.setFont(new Font("Arial", Font.PLAIN, 17));
messageEnterTextArea.setBorder(new EmptyBorder(space, space, space, space));
GridBagConstraints gbc_messageEnterTextfield = new GridBagConstraints();
gbc_messageEnterTextfield.fill = GridBagConstraints.BOTH;
gbc_messageEnterTextfield.gridx = 1;
gbc_messageEnterTextfield.gridy = 2;
gbc_messageEnterTextfield.insets = new Insets(space, space, space, space);
contentPane.add(messageEnterTextArea, gbc_messageEnterTextfield);
// Post Button
postButton.setBorderPainted(false);
GridBagConstraints gbc_moveSelectionPostButton = new GridBagConstraints();
gbc_moveSelectionPostButton.fill = GridBagConstraints.BOTH;
gbc_moveSelectionPostButton.gridx = 2;
gbc_moveSelectionPostButton.gridy = 2;
gbc_moveSelectionPostButton.insets = new Insets(space, space, space, space);
postButton.addActionListener((evt) -> { postMessage(messageList); });
contentPane.add(postButton, gbc_moveSelectionPostButton);
// Settings Button
settingsButton.setBorderPainted(false);
GridBagConstraints gbc_moveSelectionSettingsButton = new GridBagConstraints();
gbc_moveSelectionSettingsButton.fill = GridBagConstraints.BOTH;
gbc_moveSelectionSettingsButton.gridx = 2;
gbc_moveSelectionSettingsButton.gridy = 0;
gbc_moveSelectionSettingsButton.insets = new Insets(space, space, space, space);
settingsButton.addActionListener((evt) -> {
try {
SettingsScreen.open();
changeChatWindowColors(Settings.getInstance().getCurrentTheme());
} catch (Exception e) {
System.err.println("An error occured while opening the settings screen: " + e);
e.printStackTrace();
}
});
contentPane.add(settingsButton, gbc_moveSelectionSettingsButton);
// Partner name display
textPane.setFont(new Font("Arial", Font.PLAIN, 20));
textPane.setEditable(false);
GridBagConstraints gbc_partnerName = new GridBagConstraints();
gbc_partnerName.fill = GridBagConstraints.HORIZONTAL;
gbc_partnerName.gridx = 1;
gbc_partnerName.gridy = 0;
gbc_partnerName.insets = new Insets(space, space, space, space);
contentPane.add(textPane, gbc_partnerName);
userList.setCellRenderer(new UserListRenderer());
userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
userList.addListSelectionListener((listSelectionEvent) -> {
if (!listSelectionEvent.getValueIsAdjusting()) {
@SuppressWarnings("unchecked")
final JList<User> selectedUserList = (JList<User>) listSelectionEvent.getSource();
final User user = selectedUserList.getSelectedValue();
client.setRecipient(user);
currentChat = localDB.getChats()
.stream()
.filter(chat -> chat.getRecipient().getID() == user.getID())
.findFirst()
.get();
// Set all unread messages in the chat to read
readCurrentChat();
client.setRecipient(user);
textPane.setText(currentChat.getRecipient().getName());
messageList.setModel(currentChat.getModel());
contentPane.revalidate();
}
});
userList.setFont(new Font("Arial", Font.PLAIN, 17));
userList.setBorder(new EmptyBorder(space, space, space, space));
GridBagConstraints gbc_userList = new GridBagConstraints();
gbc_userList.fill = GridBagConstraints.VERTICAL;
gbc_userList.gridx = 0;
gbc_userList.gridy = 1;
gbc_userList.anchor = GridBagConstraints.PAGE_START;
gbc_userList.insets = new Insets(space, space, space, space);
changeChatWindowColors(Settings.getInstance().getCurrentTheme());
contentPane.add(userList, gbc_userList);
contentPane.revalidate();
loadUsersAndChats();
startSyncThread(Config.getInstance().getSyncTimeout());
contentPane.revalidate();
}
/**
* Used to immediately reload the ChatWindow when settings were changed.
*
* @since Envoy v0.1-alpha
*/
public void changeChatWindowColors(String key) {
Theme theme = Settings.getInstance().getThemes().get(key);
// contentPane
contentPane.setBackground(theme.getBackgroundColor());
contentPane.setForeground(theme.getUserNameColor());
// messageList
messageList.setSelectionForeground(theme.getUserNameColor());
messageList.setSelectionBackground(theme.getSelectionColor());
messageList.setForeground(theme.getMessageColorChat());
messageList.setBackground(theme.getCellColor());
// scrollPane
scrollPane.setForeground(theme.getBackgroundColor());
scrollPane.setBackground(theme.getCellColor());
// messageEnterTextArea
messageEnterTextArea.setCaretColor(theme.getTypingMessageColor());
messageEnterTextArea.setForeground(theme.getTypingMessageColor());
messageEnterTextArea.setBackground(theme.getCellColor());
// postButton
postButton.setForeground(theme.getInteractableForegroundColor());
postButton.setBackground(theme.getInteractableBackgroundColor());
// settingsButton
settingsButton.setForeground(theme.getInteractableForegroundColor());
settingsButton.setBackground(theme.getInteractableBackgroundColor());
// textPane
textPane.setBackground(theme.getBackgroundColor());
textPane.setForeground(theme.getUserNameColor());
// userList
userList.setSelectionForeground(theme.getUserNameColor());
userList.setSelectionBackground(theme.getSelectionColor());
userList.setForeground(theme.getUserNameColor());
userList.setBackground(theme.getCellColor());
}
private void postMessage(JList<Message> messageList) {
if (!client.hasRecipient()) {
JOptionPane.showMessageDialog(this,
"Please select a recipient!",
"Cannot send message",
JOptionPane.INFORMATION_MESSAGE);
}
if (!messageEnterTextArea.getText().isEmpty()) try {
// Create and send message object
final Message message = localDB.createMessage(messageEnterTextArea.getText(),
currentChat.getRecipient().getID());
localDB.addWaitingMessageToLocalDB(message, currentChat);
messageList.setModel(currentChat.getModel());
// Clear text field
messageEnterTextArea.setText("");
contentPane.revalidate();
} catch (Exception e) {
JOptionPane.showMessageDialog(this,
"An exception occured while sending a message. See the log for more details.",
"Exception occured",
JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
}
}
/**
* Initializes the elements of the user list by downloading them from the
* server.
*
* @since Envoy v0.1-alpha
*/
private void loadUsersAndChats() {
new Thread(() -> {
Sync users = client.getUsersListXml();
DefaultListModel<User> userListModel = new DefaultListModel<>();
users.getUsers().forEach(user -> {
userListModel.addElement(user);
// Check if user exists in local DB
if (localDB.getChats().stream().filter(c -> c.getRecipient().getID() == user.getID()).count() == 0)
localDB.getChats().add(new Chat(user));
});
SwingUtilities.invokeLater(() -> userList.setModel(userListModel));
}).start();
}
/**
* Updates the data model and the UI repeatedly after a certain amount of
* time.
*
* @param timeout the amount of time that passes between two requests sent to
* the server
* @since Envoy v0.1-alpha
*/
private void startSyncThread(int timeout) {
new Timer(timeout, (evt) -> {
new Thread(() -> {
// Synchronize
localDB.applySync(
client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
// Process unread messages
localDB.addUnreadMessagesToLocalDB();
localDB.clearUnreadMessagesSync();
// Mark unread messages as read when they are in the current chat
readCurrentChat();
// Update UI
SwingUtilities
.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); });
}).start();
}).start();
}
private void updateUserStates() {
for (int i = 0; i < userList.getModel().getSize(); i++)
for (int j = 0; j < localDB.getChats().size(); j++)
if (userList.getModel().getElementAt(i).getID() == localDB.getChats().get(j).getRecipient().getID())
userList.getModel().getElementAt(i).setStatus(localDB.getChats().get(j).getRecipient().getStatus());
}
/**
* Marks messages in the current chat as {@code READ}.
*/
private void readCurrentChat() { if (currentChat != null) { localDB.setMessagesToRead(currentChat); } }
}
package envoy.client.ui;
import java.awt.Color;
import java.awt.ComponentOrientation;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextPane;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import envoy.client.Chat;
import envoy.client.Client;
import envoy.client.Config;
import envoy.client.LocalDB;
import envoy.client.Settings;
import envoy.schema.Message;
import envoy.schema.Sync;
import envoy.schema.User;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>ChatWindow.java</strong><br>
* Created: <strong>28 Sep 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @author Leon Hofmeister
* @since Envoy v0.1-alpha
*/
public class ChatWindow extends JFrame {
private static final long serialVersionUID = 6865098428255463649L;
// user specific objects
private Client client;
private LocalDB localDB;
// GUI components
private JPanel contentPane = new JPanel();
private JTextArea messageEnterTextArea = new JTextArea();
private JList<User> userList = new JList<>();
private Chat currentChat;
private JList<Message> messageList = new JList<>();
private JScrollPane scrollPane = new JScrollPane();
private JTextPane textPane = new JTextPane();
// private JCheckBox jCbChangeMode;
private JButton postButton = new JButton("Post");
private JButton settingsButton = new JButton("Settings");
private static int space = 4;
private static final Logger logger = Logger.getLogger(ChatWindow.class.getSimpleName());
public ChatWindow(Client client, LocalDB localDB) {
this.client = client;
this.localDB = localDB;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 600, 800);
setTitle("Envoy");
setLocationRelativeTo(null);
setIconImage(Toolkit.getDefaultToolkit().createImage(getClass().getClassLoader().getResource("envoy_logo.png")));
// Save chats when window closes
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent evt) {
try {
localDB.saveToLocalDB();
Settings.getInstance().save();
} catch (IOException e1) {
e1.printStackTrace();
logger.log(Level.WARNING, "Unable to save the messages", e1);
}
}
});
contentPane.setBorder(new EmptyBorder(space, space, space, space));
setContentPane(contentPane);
GridBagLayout gbl_contentPane = new GridBagLayout();
gbl_contentPane.columnWidths = new int[] { 1, 1, 1 };
gbl_contentPane.rowHeights = new int[] { 1, 1, 1 };
gbl_contentPane.columnWeights = new double[] { 0.3, 1.0, 0.1 };
gbl_contentPane.rowWeights = new double[] { 0.05, 1.0, 0.07 };
contentPane.setLayout(gbl_contentPane);
messageList.setCellRenderer(new MessageListRenderer());
messageList.setFocusTraversalKeysEnabled(false);
messageList.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
DefaultListModel<Message> messageListModel = new DefaultListModel<>();
messageList.setModel(messageListModel);
messageList.setFont(new Font("Arial", Font.PLAIN, 17));
messageList.setFixedCellHeight(60);
messageList.setBorder(new EmptyBorder(space, space, space, space));
scrollPane.setViewportView(messageList);
scrollPane.setBorder(null);
GridBagConstraints gbc_scrollPane = new GridBagConstraints();
gbc_scrollPane.fill = GridBagConstraints.BOTH;
gbc_scrollPane.gridwidth = 2;
gbc_scrollPane.gridx = 1;
gbc_scrollPane.gridy = 1;
gbc_scrollPane.insets = new Insets(space, space, space, space);
contentPane.add(scrollPane, gbc_scrollPane);
// Message enter field
messageEnterTextArea.addKeyListener(new KeyAdapter() {
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER
&& ((Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0)
|| (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) {
postMessage(messageList);
}
});
// Checks for changed Message
messageEnterTextArea.setWrapStyleWord(true);
messageEnterTextArea.setLineWrap(true);
messageEnterTextArea.setBorder(null);
messageEnterTextArea.setFont(new Font("Arial", Font.PLAIN, 17));
messageEnterTextArea.setBorder(new EmptyBorder(space, space, space, space));
GridBagConstraints gbc_messageEnterTextfield = new GridBagConstraints();
gbc_messageEnterTextfield.fill = GridBagConstraints.BOTH;
gbc_messageEnterTextfield.gridx = 1;
gbc_messageEnterTextfield.gridy = 2;
gbc_messageEnterTextfield.insets = new Insets(space, space, space, space);
contentPane.add(messageEnterTextArea, gbc_messageEnterTextfield);
// Post Button
postButton.setBorderPainted(false);
GridBagConstraints gbc_moveSelectionPostButton = new GridBagConstraints();
gbc_moveSelectionPostButton.fill = GridBagConstraints.BOTH;
gbc_moveSelectionPostButton.gridx = 2;
gbc_moveSelectionPostButton.gridy = 2;
gbc_moveSelectionPostButton.insets = new Insets(space, space, space, space);
postButton.addActionListener((evt) -> { postMessage(messageList); });
contentPane.add(postButton, gbc_moveSelectionPostButton);
// Settings Button
settingsButton.setBorderPainted(false);
GridBagConstraints gbc_moveSelectionSettingsButton = new GridBagConstraints();
gbc_moveSelectionSettingsButton.fill = GridBagConstraints.BOTH;
gbc_moveSelectionSettingsButton.gridx = 2;
gbc_moveSelectionSettingsButton.gridy = 0;
gbc_moveSelectionSettingsButton.insets = new Insets(space, space, space, space);
settingsButton.addActionListener((evt) -> {
try {
SettingsScreen.open();
changeChatWindowColors(Settings.getInstance().getCurrentTheme());
} catch (Exception e) {
SettingsScreen.open();
logger.log(Level.WARNING, "An error occured while opening the settings screen", e);
e.printStackTrace();
}
});
contentPane.add(settingsButton, gbc_moveSelectionSettingsButton);
// Partner name display
textPane.setFont(new Font("Arial", Font.PLAIN, 20));
textPane.setEditable(false);
GridBagConstraints gbc_partnerName = new GridBagConstraints();
gbc_partnerName.fill = GridBagConstraints.HORIZONTAL;
gbc_partnerName.gridx = 1;
gbc_partnerName.gridy = 0;
gbc_partnerName.insets = new Insets(space, space, space, space);
contentPane.add(textPane, gbc_partnerName);
userList.setCellRenderer(new UserListRenderer());
userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
userList.addListSelectionListener((listSelectionEvent) -> {
if (!listSelectionEvent.getValueIsAdjusting()) {
@SuppressWarnings("unchecked")
final JList<User> selectedUserList = (JList<User>) listSelectionEvent.getSource();
final User user = selectedUserList.getSelectedValue();
client.setRecipient(user);
currentChat = localDB.getChats().stream().filter(chat -> chat.getRecipient().getID() == user.getID()).findFirst().get();
// Set all unread messages in the chat to read
readCurrentChat();
client.setRecipient(user);
textPane.setText(currentChat.getRecipient().getName());
messageList.setModel(currentChat.getModel());
contentPane.revalidate();
}
});
userList.setFont(new Font("Arial", Font.PLAIN, 17));
userList.setBorder(new EmptyBorder(space, space, space, space));
GridBagConstraints gbc_userList = new GridBagConstraints();
gbc_userList.fill = GridBagConstraints.VERTICAL;
gbc_userList.gridx = 0;
gbc_userList.gridy = 1;
gbc_userList.anchor = GridBagConstraints.PAGE_START;
gbc_userList.insets = new Insets(space, space, space, space);
changeChatWindowColors(Settings.getInstance().getCurrentTheme());
contentPane.add(userList, gbc_userList);
contentPane.revalidate();
loadUsersAndChats();
startSyncThread(Config.getInstance().getSyncTimeout());
contentPane.revalidate();
}
/**
* Used to immediately reload the ChatWindow when settings were changed.
*
* @since Envoy v0.1-alpha
*/
public void changeChatWindowColors(String key) {
Theme theme = Settings.getInstance().getThemes().get(key);
// contentPane
contentPane.setBackground(theme.getBackgroundColor());
contentPane.setForeground(theme.getUserNameColor());
// messageList
messageList.setSelectionForeground(theme.getUserNameColor());
messageList.setSelectionBackground(theme.getSelectionColor());
messageList.setForeground(theme.getMessageColorChat());
messageList.setBackground(theme.getCellColor());
// scrollPane
scrollPane.setForeground(theme.getBackgroundColor());
scrollPane.setBackground(theme.getCellColor());
// messageEnterTextArea
messageEnterTextArea.setCaretColor(theme.getTypingMessageColor());
messageEnterTextArea.setForeground(theme.getTypingMessageColor());
messageEnterTextArea.setBackground(theme.getCellColor());
// postButton
postButton.setForeground(theme.getInteractableForegroundColor());
postButton.setBackground(theme.getInteractableBackgroundColor());
// settingsButton
settingsButton.setForeground(theme.getInteractableForegroundColor());
settingsButton.setBackground(theme.getInteractableBackgroundColor());
// textPane
textPane.setBackground(theme.getBackgroundColor());
textPane.setForeground(theme.getUserNameColor());
// userList
userList.setSelectionForeground(theme.getUserNameColor());
userList.setSelectionBackground(theme.getSelectionColor());
userList.setForeground(theme.getUserNameColor());
userList.setBackground(theme.getCellColor());
}
private void postMessage(JList<Message> messageList) {
if (!client.hasRecipient()) {
JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE);
return;
}
if (!messageEnterTextArea.getText().isEmpty()) try {
// Create and send message object
final Message message = localDB.createMessage(messageEnterTextArea.getText(), currentChat.getRecipient());
currentChat.appendMessage(message);
messageList.setModel(currentChat.getModel());
// Clear text field
messageEnterTextArea.setText("");
contentPane.revalidate();
} catch (Exception e) {
JOptionPane.showMessageDialog(this,
"An exception occured while sending a message. See the log for more details.",
"Exception occured",
JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
}
}
/**
* Initializes the elements of the user list by downloading them from the
* server.
*
* @since Envoy v0.1-alpha
*/
private void loadUsersAndChats() {
new Thread(() -> {
Sync users = client.getUsersListXml();
DefaultListModel<User> userListModel = new DefaultListModel<>();
users.getUsers().forEach(user -> {
userListModel.addElement(user);
// Check if user exists in local DB
if (localDB.getChats().stream().filter(c -> c.getRecipient().getID() == user.getID()).count() == 0)
localDB.getChats().add(new Chat(user));
});
SwingUtilities.invokeLater(() -> userList.setModel(userListModel));
}).start();
}
/**
* Updates the data model and the UI repeatedly after a certain amount of
* time.
*
* @param timeout the amount of time that passes between two requests sent to
* the server
* @since Envoy v0.1-alpha
*/
private void startSyncThread(int timeout) {
new Timer(timeout, (evt) -> {
new Thread(() -> {
// Synchronize
localDB.applySync(client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
// Process unread messages
localDB.addUnreadMessagesToLocalDB();
localDB.clearUnreadMessagesSync();
// Mark unread messages as read when they are in the current chat
readCurrentChat();
// Update UI
SwingUtilities.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); });
}).start();
}).start();
}
private void updateUserStates() {
for (int i = 0; i < userList.getModel().getSize(); i++)
for (int j = 0; j < localDB.getChats().size(); j++)
if (userList.getModel().getElementAt(i).getID() == localDB.getChats().get(j).getRecipient().getID())
userList.getModel().getElementAt(i).setStatus(localDB.getChats().get(j).getRecipient().getStatus());
}
/**
* Marks messages in the current chat as {@code READ}.
*/
private void readCurrentChat() { if (currentChat != null) { localDB.setMessagesToRead(currentChat); } }
}

View File

@ -1,78 +1,76 @@
package envoy.client.ui;
import java.awt.Color;
import java.awt.Component;
import java.text.SimpleDateFormat;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import envoy.client.Settings;
import envoy.schema.Message;
/**
* Defines how a message is displayed.<br>
* <br>
*
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListRenderer.java</strong><br>
* Created: <strong>19 Oct 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha
*/
public class MessageListRenderer extends JLabel implements ListCellRenderer<Message> {
private static final long serialVersionUID = 5164417379767181198L;
@Override
public Component getListCellRendererComponent(JList<? extends Message> list, Message value, int index,
boolean isSelected, boolean cellHasFocus) {
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setOpaque(true);
final String text = value.getContent().get(0).getText();
final String state = value.getMetadata().getState().toString();
final String date = value.getMetadata().getDate() == null ? ""
: new SimpleDateFormat("dd.MM.yyyy HH:mm ")
.format(value.getMetadata().getDate().toGregorianCalendar().getTime());
// Getting the MessageColor in the Chat of the current theme
String textColor = null;
textColor = toHex(
Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getMessageColorChat());
// Getting the DateColor in the Chat of the current theme
String dateColor = null;
dateColor = toHex(
Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getDateColorChat());
setText(String.format(
"<html><p style=\"color:%s\"><b><small>%s</b></small><br><p style=\"color:%s\">%s :%s</html>",
dateColor,
date,
textColor,
text,
state));
return this;
}
public String toHex(Color c) {
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
String hex = String.format("#%02x%02x%02x", r, g, b);
return hex;
}
package envoy.client.ui;
import java.awt.Color;
import java.awt.Component;
import java.text.SimpleDateFormat;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import envoy.client.Settings;
import envoy.schema.Message;
/**
* Defines how a message is displayed.<br>
* <br>
*
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListRenderer.java</strong><br>
* Created: <strong>19 Oct 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha
*/
public class MessageListRenderer extends JLabel implements ListCellRenderer<Message> {
private static final long serialVersionUID = 5164417379767181198L;
@Override
public Component getListCellRendererComponent(JList<? extends Message> list, Message value, int index, boolean isSelected, boolean cellHasFocus) {
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setOpaque(true);
final String text = value.getContent().get(0).getText();
final String state = value.getMetadata().getState().toString();
final String date = value.getMetadata().getDate() == null ? ""
: new SimpleDateFormat("dd.MM.yyyy HH:mm ")
.format(value.getMetadata().getDate().toGregorianCalendar().getTime());
// Getting the MessageColor in the Chat of the current theme
String textColor = null;
textColor = toHex(
Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getMessageColorChat());
// Getting the DateColor in the Chat of the current theme
String dateColor = null;
dateColor = toHex(
Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getDateColorChat());
setText(String.format(
"<html><p style=\"color:%s\"><b><small>%s</b></small><br><p style=\"color:%s\">%s :%s</html>",
dateColor,
date,
textColor,
text,
state));
return this;
}
public String toHex(Color c) {
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
String hex = String.format("#%02x%02x%02x", r, g, b);
return hex;
}
}

View File

@ -509,4 +509,5 @@ public class SettingsScreen extends JDialog {
colorsPanel.add(panel);
}
}

View File

@ -3,6 +3,8 @@ package envoy.client.ui;
import java.awt.EventQueue;
import java.io.IOException;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;
@ -26,29 +28,35 @@ import envoy.exception.EnvoyException;
*/
public class Startup {
private static final Logger logger = Logger.getLogger(Startup.class.getSimpleName());
public static void main(String[] args) {
logger.setLevel(Level.ALL);
Config config = Config.getInstance();
if (args.length > 0) {
config.load(args);
} else {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
Properties configProperties = new Properties();
configProperties.load(loader.getResourceAsStream("client.properties"));
config.load(configProperties);
} catch (IOException e) {
e.printStackTrace();
}
// Load the configuration from client.properties first
ClassLoader loader = Thread.currentThread().getContextClassLoader();
try {
Properties configProperties = new Properties();
configProperties.load(loader.getResourceAsStream("client.properties"));
config.load(configProperties);
} catch (IOException e) {
e.printStackTrace();
}
// Override configuration values with command line arguments
if (args.length > 0) config.load(args);
if (!config.isInitialized()) {
System.err.println("Server or port are not defined. Exiting...");
logger.severe("Server or port are not defined. Exiting...");
JOptionPane.showMessageDialog(null, "Error loading configuration values.", "Configuration error", JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
String userName = JOptionPane.showInputDialog("Please enter your username");
if (userName == null || userName.isEmpty()) {
System.err.println("User name is not set or empty. Exiting...");
logger.severe("User name is not set or empty. Exiting...");
System.exit(1);
}
Client client = new Client(config, userName);
@ -66,8 +74,9 @@ public class Startup {
EventQueue.invokeLater(() -> {
try {
ChatWindow frame = new ChatWindow(client, localDB);
frame.setVisible(true);
ChatWindow chatWindow = new ChatWindow(client, localDB);
new StatusTrayIcon(chatWindow).show();
chatWindow.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}

View File

@ -0,0 +1,133 @@
package envoy.client.ui;
import java.awt.AWTException;
import java.awt.Image;
import java.awt.MenuItem;
import java.awt.PopupMenu;
import java.awt.SystemTray;
import java.awt.Toolkit;
import java.awt.TrayIcon;
import java.awt.TrayIcon.MessageType;
import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.HashSet;
import java.util.Set;
import envoy.client.event.Event;
import envoy.client.event.EventBus;
import envoy.client.event.EventHandler;
import envoy.client.event.MessageCreationEvent;
import envoy.exception.EnvoyException;
import envoy.schema.Message;
/**
* Project: <strong>envoy-client</strong><br>
* File: <strong>StatusTrayIcon.java</strong><br>
* Created: <strong>3 Dec 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @since Envoy v0.2-alpha
*/
public class StatusTrayIcon implements EventHandler {
/**
* The {@link TrayIcon} provided by the System Tray API for controlling the
* system tray. This includes displaying the icon, but also creating
* notifications when new messages are received.
*/
private TrayIcon trayIcon;
/**
* A received {@link Message} is only displayed as a system tray notification if
* this variable is set to {@code true}.
*/
private boolean displayMessages = false;
/**
* Creates a {@link StatusTrayIcon} with the Envoy logo, a tool tip and a pop-up
* menu.
*
* @param focusTarget the {@link Window} which focus determines if message
* notifications are displayed
* @throws EnvoyException if the currently used OS does not support the System
* Tray API
* @since Envoy v0.2-alpha
*/
public StatusTrayIcon(Window focusTarget) throws EnvoyException {
if (!SystemTray.isSupported()) throw new EnvoyException("The Envoy tray icon is not supported.");
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Image img = Toolkit.getDefaultToolkit().createImage(loader.getResource("envoy_logo.png"));
trayIcon = new TrayIcon(img, "Envoy Client");
trayIcon.setImageAutoSize(true);
trayIcon.setToolTip("You are notified if you have unread messages.");
PopupMenu popup = new PopupMenu();
MenuItem exitMenuItem = new MenuItem("Exit");
exitMenuItem.addActionListener((evt) -> System.exit(0));
popup.add(exitMenuItem);
trayIcon.setPopupMenu(popup);
// Only display messages if the chat window is not focused
focusTarget.addWindowFocusListener(new WindowAdapter() {
@Override
public void windowGainedFocus(WindowEvent e) {
displayMessages = false;
}
@Override
public void windowLostFocus(WindowEvent e) {
displayMessages = true;
}
});
// Start processing message events
EventBus.getInstance().register(this);
}
/**
* Makes this {@link StatusTrayIcon} appear in the system tray.
*
* @throws EnvoyException if the status icon could not be attaches to the system
* tray for system-internal reasons
* @since Envoy v0.2-alpha
*/
public void show() throws EnvoyException {
try {
SystemTray.getSystemTray().add(trayIcon);
} catch (AWTException e) {
throw new EnvoyException("Could not attach Envoy tray icon to system tray.", e);
}
}
/**
* Notifies the user of a message by displaying a pop-up every time a new
* message is received.
*
* @since Envoy v0.2-alpha
*/
@Override
public void handle(Event<?> event) {
System.out.println("Message received. Displaying message: " + displayMessages);
if (displayMessages)
trayIcon.displayMessage("New message received", ((MessageCreationEvent) event).get().getContent().get(0).getText(), MessageType.INFO);
}
/**
* The {@link StatusTrayIcon} only reacts to {@link MessageCreationEvent}
* instances which signify newly received messages.
*
* @return A set with the single element {@code MessageCreationEvent.class}
* @since Envoy v0.2-alpha
*/
@Override
public Set<Class<? extends Event<?>>> supports() {
Set<Class<? extends Event<?>>> supportedEvents = new HashSet<>();
supportedEvents.add(MessageCreationEvent.class);
return supportedEvents;
}
}

View File

@ -1,84 +1,77 @@
package envoy.client.ui;
import java.awt.Color;
import java.awt.Component;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import envoy.client.Settings;
import envoy.schema.User;
import envoy.schema.User.UserStatus;
/**
* Defines how the {@code UserList} is displayed.
*
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListRenderer.java</strong><br>
* Created: <strong>12 Oct 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha
*/
public class UserListRenderer extends JLabel implements ListCellRenderer<User> {
private static final long serialVersionUID = 5164417379767181198L;
@SuppressWarnings("incomplete-switch")
@Override
public Component getListCellRendererComponent(JList<? extends User> list, User value, int index, boolean isSelected,
boolean cellHasFocus) {
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
// Enable background rendering
setOpaque(true);
final String name = value.getName();
final UserStatus status = value.getStatus();
// Getting the UserNameColor of the current theme
String textColor = null;
textColor = toHex(
Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getUserNameColor());
switch (status) {
case ONLINE:
setText(String.format(
"<html><p style=\"color:#03fc20\"><b><small>%s</b></small><br><p style=\"color:%s\">%s</html>",
status,
textColor,
name));
break;
case OFFLINE:
setText(String.format(
"<html><p style=\"color:#fc0303\"><b><small>%s</b></small><br><p style=\"color:%s\">%s</html>",
status,
textColor,
name));
break;
}
return this;
}
public String toHex(Color c) {
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
String hex = String.format("#%02x%02x%02x", r, g, b);
return hex;
}
package envoy.client.ui;
import java.awt.Color;
import java.awt.Component;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.ListCellRenderer;
import envoy.client.Settings;
import envoy.schema.User;
import envoy.schema.User.UserStatus;
/**
* Defines how the {@code UserList} is displayed.
*
* Project: <strong>envoy-client</strong><br>
* File: <strong>UserListRenderer.java</strong><br>
* Created: <strong>12 Oct 2019</strong><br>
*
* @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer
* @since Envoy v0.1-alpha
*/
public class UserListRenderer extends JLabel implements ListCellRenderer<User> {
private static final long serialVersionUID = 5164417379767181198L;
@SuppressWarnings("incomplete-switch")
@Override
public Component getListCellRendererComponent(JList<? extends User> list, User value, int index, boolean isSelected, boolean cellHasFocus) {
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
} else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
// Enable background rendering
setOpaque(true);
final String name = value.getName();
final UserStatus status = value.getStatus();
// Getting the UserNameColor of the current theme
String textColor = null;
textColor = toHex(
Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getUserNameColor());
switch (status) {
case ONLINE:
setText(String.format(
"<html><p style=\"color:#03fc20\"><b><small>%s</b></small><br><p style=\"color:%s\">%s</html>",
status,
textColor,
name));
break;
case OFFLINE:
setText(String.format(
"<html><p style=\"color:#fc0303\"><b><small>%s</b></small><br><p style=\"color:%s\">%s</html>",
status,
textColor,
name));
break;
}
return this;
}
public String toHex(Color c) {
int r = c.getRed();
int g = c.getGreen();
int b = c.getBlue();
String hex = String.format("#%02x%02x%02x", r, g, b);
return hex;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB