Merge pull request #36 from informatik-ag-ngl/f/message_notification

Implemented event system and status tray icon
This commit is contained in:
Kai S. K. Engelbart 2019-12-07 09:54:24 +01:00 committed by GitHub
commit 254de4fd8b
12 changed files with 714 additions and 327 deletions

View File

@ -1,8 +1,107 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.APILeak=warning
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
org.eclipse.jdt.core.compiler.problem.deadCode=warning
org.eclipse.jdt.core.compiler.problem.deprecation=warning
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
org.eclipse.jdt.core.compiler.problem.nullReference=warning
org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedImport=warning
org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.8

View File

@ -1,305 +1,300 @@
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.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 static final Logger logger = Logger.getLogger(LocalDB.class.getSimpleName());
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
*/
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()));
localDB = new File(localDBDir, sender.getID() + ".db");
if (localDB.exists()) loadFromLocalDB();
}
/**
* 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() {
try {
localDB.getParentFile().mkdirs();
localDB.createNewFile();
} catch (IOException e) {
e.printStackTrace();
logger.warning("unable to save the messages");
}
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) {
out.writeObject(chats);
} catch (IOException ex) {
ex.printStackTrace();
logger.warning("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 recipient The recipient of the message
* @return prepared {@link Message} object
* @since Envoy v0.1-alpha
*/
public Message createMessage(String textContent, User recipient) {
Message.Metadata metaData = objectFactory.createMessageMetadata();
metaData.setSender(sender.getID());
metaData.setRecipient(recipient.getID());
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();
logger.info(String.format("Filled sync with %d messages.", 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
logger.info("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()) {
logger.info("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()) {
logger.info("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());
logger.info("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());
logger.info(getChats().get(k).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.
*
* @param 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));
}
}
/**
* 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
* @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) {
// addMessageToSync(localDB.getChats().get(i).getModel().get(j));
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 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; }
}
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()));
localDB = new File(localDBDir, sender.getID() + ".db");
if (localDB.exists()) loadFromLocalDB();
}
/**
* 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() {
try {
localDB.getParentFile().mkdirs();
localDB.createNewFile();
} catch (IOException e) {
e.printStackTrace();
logger.warning("unable to save the messages");
}
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) {
out.writeObject(chats);
} catch (IOException ex) {
ex.printStackTrace();
logger.warning("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 recipient The recipient of the message
* @return prepared {@link Message} object
* @since Envoy v0.1-alpha
*/
public Message createMessage(String textContent, User recipient) {
Message.Metadata metaData = objectFactory.createMessageMetadata();
metaData.setSender(sender.getID());
metaData.setRecipient(recipient.getID());
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 userId the ID of the user that is synchronized by this client
* @return {@link Sync} object filled with the current changes
*/
public Sync fillSync(long userId) {
addWaitingMessagesToSync();
sync.getMessages().addAll(readMessages.getMessages());
readMessages.getMessages().clear();
logger.info(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
*/
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.
*
* @param 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
* @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

@ -38,7 +38,7 @@ 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
@ -125,13 +125,11 @@ public class ChatWindow extends JFrame {
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER && ((SettingsScreen.enterToSend && e.getModifiersEx() == 0)
|| (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) {
if (e.getKeyCode() == KeyEvent.VK_ENTER
&& ((SettingsScreen.enterToSend && e.getModifiersEx() == 0) || (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) {
postMessage(messageList);
}
}
});
// Checks for changed Message
@ -219,11 +217,7 @@ public class ChatWindow extends JFrame {
final User user = selectedUserList.getSelectedValue();
client.setRecipient(user);
currentChat = localDB.getChats()
.stream()
.filter(chat -> chat.getRecipient().getID() == user.getID())
.findFirst()
.get();
currentChat = localDB.getChats().stream().filter(chat -> chat.getRecipient().getID() == user.getID()).findFirst().get();
// Set all unread messages in the chat to read
readCurrentChat();
@ -262,10 +256,8 @@ public class ChatWindow extends JFrame {
private void postMessage(JList<Message> messageList) {
if (!client.hasRecipient()) {
JOptionPane.showMessageDialog(this,
"Please select a recipient!",
"Cannot send message",
JOptionPane.INFORMATION_MESSAGE);
JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE);
return;
}
if (!messageEnterTextArea.getText().isEmpty()) try {
@ -290,7 +282,7 @@ public class ChatWindow extends JFrame {
/**
* Initializes the elements of the user list by downloading them from the
* server.
*
*
* @since Envoy v0.1-alpha
*/
private void loadUsersAndChats() {
@ -311,7 +303,7 @@ public class ChatWindow extends JFrame {
/**
* 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
@ -321,8 +313,7 @@ public class ChatWindow extends JFrame {
new Thread(() -> {
// Synchronize
localDB.applySync(
client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
localDB.applySync(client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
// Process unread messages
localDB.addUnreadMessagesToLocalDB();
@ -332,8 +323,7 @@ public class ChatWindow extends JFrame {
readCurrentChat();
// Update UI
SwingUtilities
.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); });
SwingUtilities.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); });
}).start();
}).start();
}

View File

@ -72,8 +72,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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB