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 771a635888
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 714 additions and 327 deletions

View File

@ -1,8 +1,107 @@
eclipse.preferences.version=1 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.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=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.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.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.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.release=disabled
org.eclipse.jdt.core.compiler.source=1.8 org.eclipse.jdt.core.compiler.source=1.8

View File

@ -1,305 +1,300 @@
package envoy.client; package envoy.client;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.DatatypeFactory;
import envoy.exception.EnvoyException; import envoy.client.event.EventBus;
import envoy.schema.Message; import envoy.client.event.MessageCreationEvent;
import envoy.schema.Message.Metadata.MessageState; import envoy.exception.EnvoyException;
import envoy.schema.ObjectFactory; import envoy.schema.Message;
import envoy.schema.Sync; import envoy.schema.Message.Metadata.MessageState;
import envoy.schema.User; 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> * Project: <strong>envoy-client</strong><br>
* * File: <strong>LocalDB.java</strong><br>
* @author Kai S. K. Engelbart * Created: <strong>27.10.2019</strong><br>
* @author Maximilian K&auml;fer *
* @since Envoy v0.1-alpha * @author Kai S. K. Engelbart
*/ * @author Maximilian K&auml;fer
public class LocalDB { * @since Envoy v0.1-alpha
*/
private File localDB; public class LocalDB {
private User sender;
private List<Chat> chats = new ArrayList<>(); private File localDB;
private ObjectFactory objectFactory = new ObjectFactory(); private User sender;
private DatatypeFactory datatypeFactory; private List<Chat> chats = new ArrayList<>();
private ObjectFactory objectFactory = new ObjectFactory();
private static final Logger logger = Logger.getLogger(LocalDB.class.getSimpleName()); private DatatypeFactory datatypeFactory;
private Sync unreadMessagesSync = objectFactory.createSync(); private Sync unreadMessagesSync = objectFactory.createSync();
private Sync sync = objectFactory.createSync(); private Sync sync = objectFactory.createSync();
private Sync readMessages = 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 * Constructs an empty local database.
* @since Envoy v0.1-alpha *
*/ * @param sender the user that is logged in with this client
public LocalDB(User sender) { * @since Envoy v0.1-alpha
this.sender = sender; */
try { public LocalDB(User sender) {
datatypeFactory = DatatypeFactory.newInstance(); this.sender = sender;
} catch (DatatypeConfigurationException e) { try {
e.printStackTrace(); 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. /**
* * Initializes the local database and fills it with values
* @param localDBDir the directory where we wish to save/load the database from. * if the user has already sent or received messages.
* @throws EnvoyException if the directory selected is not an actual directory. *
* @since Envoy v0.1-alpha * @param localDBDir the directory where we wish to save/load the database from.
*/ * @throws EnvoyException if the directory selected is not an actual directory.
public void initializeDBFile(File localDBDir) throws EnvoyException { * @since Envoy v0.1-alpha
if (localDBDir.exists() && !localDBDir.isDirectory()) */
throw new EnvoyException(String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath())); public void initializeDBFile(File localDBDir) throws EnvoyException {
localDB = new File(localDBDir, sender.getID() + ".db"); if (localDBDir.exists() && !localDBDir.isDirectory())
if (localDB.exists()) loadFromLocalDB(); 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 * Saves the database into a file for future use.
* @since Envoy v0.1-alpha *
*/ * @throws IOException if something went wrong during saving
public void saveToLocalDB() { * @since Envoy v0.1-alpha
try { */
localDB.getParentFile().mkdirs(); public void saveToLocalDB() {
localDB.createNewFile(); try {
} catch (IOException e) { localDB.getParentFile().mkdirs();
e.printStackTrace(); localDB.createNewFile();
logger.warning("unable to save the messages"); } catch (IOException e) {
} e.printStackTrace();
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) { logger.warning("unable to save the messages");
out.writeObject(chats); }
} catch (IOException ex) { try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) {
ex.printStackTrace(); out.writeObject(chats);
logger.warning("unable to save the messages"); } 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. * Loads all chats saved by Envoy for the client user.
* @since Envoy v0.1-alpha *
*/ * @throws EnvoyException if something fails while loading.
@SuppressWarnings("unchecked") * @since Envoy v0.1-alpha
private void loadFromLocalDB() throws EnvoyException { */
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(localDB))) { @SuppressWarnings("unchecked")
Object obj = in.readObject(); private void loadFromLocalDB() throws EnvoyException {
if (obj instanceof ArrayList<?>) chats = (ArrayList<Chat>) obj; try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(localDB))) {
} catch (ClassNotFoundException | IOException e) { Object obj = in.readObject();
throw new EnvoyException(e); 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 * Creates a {@link Message} object serializable to XML.
* @param recipient The recipient of the message *
* @return prepared {@link Message} object * @param textContent The content (text) of the message
* @since Envoy v0.1-alpha * @param recipient The recipient of the message
*/ * @return prepared {@link Message} object
public Message createMessage(String textContent, User recipient) { * @since Envoy v0.1-alpha
Message.Metadata metaData = objectFactory.createMessageMetadata(); */
metaData.setSender(sender.getID()); public Message createMessage(String textContent, User recipient) {
metaData.setRecipient(recipient.getID()); Message.Metadata metaData = objectFactory.createMessageMetadata();
metaData.setState(MessageState.WAITING); metaData.setSender(sender.getID());
metaData.setDate(datatypeFactory.newXMLGregorianCalendar(Instant.now().toString())); metaData.setRecipient(recipient.getID());
metaData.setState(MessageState.WAITING);
Message.Content content = objectFactory.createMessageContent(); metaData.setDate(datatypeFactory.newXMLGregorianCalendar(Instant.now().toString()));
content.setType("text");
content.setText(textContent); Message.Content content = objectFactory.createMessageContent();
content.setType("text");
Message message = objectFactory.createMessage(); content.setText(textContent);
message.setMetadata(metaData);
message.getContent().add(content); Message message = objectFactory.createMessage();
message.setMetadata(metaData);
return message; message.getContent().add(content);
}
return message;
public Sync fillSync(long userId) { }
addWaitingMessagesToSync();
/**
sync.getMessages().addAll(readMessages.getMessages()); * Creates a {@link Sync} object filled with the changes that occurred to the
readMessages.getMessages().clear(); * local database since the last synchronization.
*
logger.info(String.format("Filled sync with %d messages.", sync.getMessages().size())); * @param userId the ID of the user that is synchronized by this client
return sync; * @return {@link Sync} object filled with the current changes
} */
public Sync fillSync(long userId) {
public void applySync(Sync returnSync) { addWaitingMessagesToSync();
for (int i = 0; i < returnSync.getMessages().size(); i++) {
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 sync.getMessages().addAll(readMessages.getMessages());
&& returnSync.getMessages().get(i).getMetadata().getState() == MessageState.SENT) { readMessages.getMessages().clear();
// Update Local Messages with State WAITING (add Message ID and change State to
// SENT) logger.info(String.format("Filled sync with %d messages.", sync.getMessages().size()));
for (int j = 0; j < sync.getMessages().size(); j++) { return sync;
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()); /**
} * Applies the changes carried by a {@link Sync} object to the local database
} *
} * @param returnSync the {@link Sync} object to apply
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 && returnSync.getMessages().get(i).getMetadata().getSender() != 0 */
&& returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) { public void applySync(Sync returnSync) {
// these are the unread Messages from the server for (int i = 0; i < returnSync.getMessages().size(); i++) {
unreadMessagesSync.getMessages().add(returnSync.getMessages().get(i));
} // The message has an ID
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0) {
if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 && returnSync.getMessages().get(i).getMetadata().getSender() == 0
&& returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) { // Messages are processes differently corresponding to their state
// Update Messages in localDB to state RECEIVED switch (returnSync.getMessages().get(i).getMetadata().getState()) {
for (int j = 0; j < getChats().size(); j++) { case SENT:
if (getChats().get(j).getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) { // Update previously waiting and now sent messages that were assigned an ID by
for (int k = 0; k < getChats().get(j).getModel().getSize(); k++) { // the server
if (getChats().get(j).getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages() sync.getMessages().get(i).getMetadata().setMessageId(returnSync.getMessages().get(i).getMetadata().getMessageId());
.get(i) sync.getMessages().get(i).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
.getMetadata() break;
.getMessageId()) { case RECEIVED:
// Update Message in LocalDB if (returnSync.getMessages().get(i).getMetadata().getSender() != 0) {
getChats().get(j).getModel().get(k).getMetadata().setState(returnSync.getMessages().get(j).getMetadata().getState()); // 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 (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 if (chat.getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient())
&& returnSync.getMessages().get(i).getMetadata().getState() == MessageState.READ) { for (int j = 0; j < chat.getModel().getSize(); j++)
// Update local Messages to state READ if (chat.getModel().get(j).getMetadata().getMessageId() == returnSync.getMessages()
logger.info("Message with ID: " + returnSync.getMessages().get(i).getMetadata().getMessageId() .get(i)
+ "was initialized to be set to READ in localDB."); .getMetadata()
for (int j = 0; j < getChats().size(); j++) { .getMessageId())
if (getChats().get(j) chat.getModel().get(j).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
.getRecipient() }
.getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) { break;
logger.info("Chat with: " + getChats().get(j).getRecipient().getID() + "was selected."); case READ:
for (int k = 0; k < getChats().get(j).getModel().getSize(); k++) { // Update local Messages to state READ
if (getChats().get(j).getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages() logger.info("Message with ID: " + returnSync.getMessages().get(i).getMetadata().getMessageId()
.get(i) + "was initialized to be set to READ in localDB.");
.getMetadata() for (Chat chat : getChats())
.getMessageId()) { if (chat.getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) {
logger.info("Message with ID: " logger.info("Chat with: " + chat.getRecipient().getID() + "was selected.");
+ getChats().get(j).getModel().get(k).getMetadata().getMessageId() for (int k = 0; k < chat.getModel().getSize(); k++)
+ "was selected."); if (chat.getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages()
getChats().get(j) .get(i)
.getModel() .getMetadata()
.get(k) .getMessageId()) {
.getMetadata() logger.info("Message with ID: " + chat.getModel().get(k).getMetadata().getMessageId() + "was selected.");
.setState(returnSync.getMessages().get(i).getMetadata().getState()); chat.getModel().get(k).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
logger.info("Message State is now: " logger.info("Message State is now: " + chat.getModel().get(k).getMetadata().getState());
+ getChats().get(j).getModel().get(k).getMetadata().getState().toString()); }
} }
} break;
} }
} }
} }
}
// Updating UserStatus of all users in LocalDB
// Updating UserStatus of all users in LocalDB for (User user : returnSync.getUsers())
for (int j = 0; j < returnSync.getUsers().size(); j++) { for (Chat chat : getChats())
for (int k = 0; k < getChats().size(); k++) { if (user.getID() == chat.getRecipient().getID()) {
if (getChats().get(k).getRecipient().getID() == returnSync.getUsers().get(j).getID()) { chat.getRecipient().setStatus(user.getStatus());
logger.info(chat.getRecipient().getStatus().toString());
getChats().get(k).getRecipient().setStatus(returnSync.getUsers().get(j).getStatus()); }
logger.info(getChats().get(k).getRecipient().getStatus().toString());
} sync.getMessages().clear();
} sync.getUsers().clear();
} }
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
* Adds the unread messages returned from the server in the latest sync to the * @since Envoy v0.1-alpha
* right chats in the LocalDB. */
* public void addUnreadMessagesToLocalDB() {
* @param localDB for (Message message : unreadMessagesSync.getMessages())
* @since Envoy v0.1-alpha for (Chat chat : getChats())
*/ if (message.getMetadata().getSender() == chat.getRecipient().getID()) {
public void addUnreadMessagesToLocalDB() { chat.appendMessage(message);
Sync unreadMessages = unreadMessagesSync; break;
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.
* Changes all messages with state {@code RECEIVED} of a specific chat to state *
* {@code READ}. * @param currentChat
* <br> * @since Envoy v0.1-alpha
* Adds these messages to the {@code readMessages} {@link Sync} object. */
* public void setMessagesToRead(Chat currentChat) {
* @param currentChat for (int i = currentChat.getModel().size() - 1; i >= 0; --i)
* @since Envoy v0.1-alpha if (currentChat.getModel().get(i).getMetadata().getRecipient() != currentChat.getRecipient().getID())
*/ if (currentChat.getModel().get(i).getMetadata().getState() == MessageState.RECEIVED) {
public void setMessagesToRead(Chat currentChat) { currentChat.getModel().get(i).getMetadata().setState(MessageState.READ);
for (int i = currentChat.getModel().size() - 1; i >= 0; --i) readMessages.getMessages().add(currentChat.getModel().get(i));
if (currentChat.getModel().get(i).getMetadata().getRecipient() != currentChat.getRecipient().getID()) } else break;
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
* Adds all messages with state {@code WAITING} from the {@link LocalDB} to the */
* {@link Sync} object. private void addWaitingMessagesToSync() {
* for (Chat chat : getChats())
* @since Envoy v0.1-alpha for (int i = 0; i < chat.getModel().size(); i++)
*/ if (chat.getModel().get(i).getMetadata().getState() == MessageState.WAITING) {
private void addWaitingMessagesToSync() { logger.info("Got Waiting Message");
for (Chat chat : getChats()) sync.getMessages().add(chat.getModel().get(i));
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(); }
* Clears the {@code unreadMessagesSync} {@link Sync} object.
* /**
* @since Envoy v0.1-alpha * @return all saved {@link Chat} objects that list the client user as the
*/ * sender
public void clearUnreadMessagesSync() { unreadMessagesSync.getMessages().clear(); } * @since Envoy v0.1-alpha
**/
/** public List<Chat> getChats() { return chats; }
* @return all saves {@link Chat} objects that list the client user as the
* client /**
* @since Envoy v0.1-alpha * @return the {@link User} who initialized the local database
**/ * @since Envoy v0.1-alpha
public List<Chat> getChats() { return chats; } */
public User getUser() { return sender; }
/** }
* @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> * Project: <strong>envoy-client</strong><br>
* File: <strong>ChatWindow.java</strong><br> * File: <strong>ChatWindow.java</strong><br>
* Created: <strong>28 Sep 2019</strong><br> * Created: <strong>28 Sep 2019</strong><br>
* *
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
* @author Maximilian K&auml;fer * @author Maximilian K&auml;fer
* @author Leon Hofmeister * @author Leon Hofmeister
@ -125,13 +125,11 @@ public class ChatWindow extends JFrame {
@Override @Override
public void keyReleased(KeyEvent e) { public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER
if (e.getKeyCode() == KeyEvent.VK_ENTER && ((SettingsScreen.enterToSend && e.getModifiersEx() == 0) && ((SettingsScreen.enterToSend && e.getModifiersEx() == 0) || (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) {
|| (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) {
postMessage(messageList); postMessage(messageList);
} }
} }
}); });
// Checks for changed Message // Checks for changed Message
@ -219,11 +217,7 @@ public class ChatWindow extends JFrame {
final User user = selectedUserList.getSelectedValue(); final User user = selectedUserList.getSelectedValue();
client.setRecipient(user); client.setRecipient(user);
currentChat = localDB.getChats() currentChat = localDB.getChats().stream().filter(chat -> chat.getRecipient().getID() == user.getID()).findFirst().get();
.stream()
.filter(chat -> chat.getRecipient().getID() == user.getID())
.findFirst()
.get();
// Set all unread messages in the chat to read // Set all unread messages in the chat to read
readCurrentChat(); readCurrentChat();
@ -262,10 +256,8 @@ public class ChatWindow extends JFrame {
private void postMessage(JList<Message> messageList) { private void postMessage(JList<Message> messageList) {
if (!client.hasRecipient()) { if (!client.hasRecipient()) {
JOptionPane.showMessageDialog(this, JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE);
"Please select a recipient!", return;
"Cannot send message",
JOptionPane.INFORMATION_MESSAGE);
} }
if (!messageEnterTextArea.getText().isEmpty()) try { 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 * Initializes the elements of the user list by downloading them from the
* server. * server.
* *
* @since Envoy v0.1-alpha * @since Envoy v0.1-alpha
*/ */
private void loadUsersAndChats() { 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 * Updates the data model and the UI repeatedly after a certain amount of
* time. * time.
* *
* @param timeout the amount of time that passes between two requests sent to * @param timeout the amount of time that passes between two requests sent to
* the server * the server
* @since Envoy v0.1-alpha * @since Envoy v0.1-alpha
@ -321,8 +313,7 @@ public class ChatWindow extends JFrame {
new Thread(() -> { new Thread(() -> {
// Synchronize // Synchronize
localDB.applySync( localDB.applySync(client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
// Process unread messages // Process unread messages
localDB.addUnreadMessagesToLocalDB(); localDB.addUnreadMessagesToLocalDB();
@ -332,8 +323,7 @@ public class ChatWindow extends JFrame {
readCurrentChat(); readCurrentChat();
// Update UI // Update UI
SwingUtilities SwingUtilities.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); });
.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); });
}).start(); }).start();
}).start(); }).start();
} }

View File

@ -72,8 +72,9 @@ public class Startup {
EventQueue.invokeLater(() -> { EventQueue.invokeLater(() -> {
try { try {
ChatWindow frame = new ChatWindow(client, localDB); ChatWindow chatWindow = new ChatWindow(client, localDB);
frame.setVisible(true); new StatusTrayIcon(chatWindow).show();
chatWindow.setVisible(true);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); 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