diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index cb635b1..262bd4f 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -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 diff --git a/src/main/java/envoy/client/LocalDB.java b/src/main/java/envoy/client/LocalDB.java index ba5e3a3..d952bef 100644 --- a/src/main/java/envoy/client/LocalDB.java +++ b/src/main/java/envoy/client/LocalDB.java @@ -14,6 +14,8 @@ 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; @@ -38,12 +40,12 @@ public class LocalDB { 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(); + private static final Logger logger = Logger.getLogger(LocalDB.class.getSimpleName()); + /** * Constructs an empty local database. * @@ -108,7 +110,7 @@ public class LocalDB { /** * 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 @@ -132,6 +134,14 @@ public class LocalDB { 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 + * @since Envoy v0.1-alpha + */ public Sync fillSync(long userId) { addWaitingMessagesToSync(); @@ -142,78 +152,74 @@ public class LocalDB { 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 + */ 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()); - } + // 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()); } - } - } - - } - - if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 - && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.READ) { - // Update local Messages to state READ - logger.finest("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.fine("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 - .finest("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.finest("Message State is now: " + getChats().get(j).getModel().get(k).getMetadata().getState().toString()); + 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 (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()); + 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(); @@ -222,15 +228,15 @@ public class LocalDB { /** * 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)); + for (Message message : unreadMessagesSync.getMessages()) + for (Chat chat : getChats()) + if (message.getMetadata().getSender() == chat.getRecipient().getID()) { + chat.appendMessage(message); + break; } } @@ -239,7 +245,7 @@ public class LocalDB { * {@code READ}. *
* Adds these messages to the {@code readMessages} {@link Sync} object. - * + * * @param currentChat the {@link Chat} that was just opened * @since Envoy v0.1-alpha */ @@ -255,14 +261,13 @@ public class LocalDB { /** * 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)); } @@ -270,14 +275,14 @@ public class LocalDB { /** * 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 + * @return all saved {@link Chat} objects that list the client user as the + * sender * @since Envoy v0.1-alpha **/ public List getChats() { return chats; } diff --git a/src/main/java/envoy/client/event/Event.java b/src/main/java/envoy/client/event/Event.java new file mode 100644 index 0000000..9db2477 --- /dev/null +++ b/src/main/java/envoy/client/event/Event.java @@ -0,0 +1,17 @@ +package envoy.client.event; + +/** + * Project: envoy-clientChess
+ * File: Event.javaEvent.java
+ * Created: 04.12.2019
+ * + * @author Kai S. K. Engelbart + * @since Envoy v0.2-alpha + */ +public interface Event { + + /** + * @return the data associated with this event + */ + T get(); +} diff --git a/src/main/java/envoy/client/event/EventBus.java b/src/main/java/envoy/client/event/EventBus.java new file mode 100644 index 0000000..f6da3f5 --- /dev/null +++ b/src/main/java/envoy/client/event/EventBus.java @@ -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.
+ *
+ * The event bus is a singleton and can be used across the entire application to + * guarantee the propagation of events. + * Project: envoy-client
+ * File: EventBus.java
+ * Created: 04.12.2019
+ * + * @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 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 getHandlers() { return handlers; } +} diff --git a/src/main/java/envoy/client/event/EventHandler.java b/src/main/java/envoy/client/event/EventHandler.java new file mode 100644 index 0000000..a6e5b81 --- /dev/null +++ b/src/main/java/envoy/client/event/EventHandler.java @@ -0,0 +1,25 @@ +package envoy.client.event; + +import java.util.Set; + +/** + * Project: envoy-clientChess
+ * File: EventHandler.javaEvent.java
+ * Created: 04.12.2019
+ * + * @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>> supports(); +} diff --git a/src/main/java/envoy/client/event/MessageCreationEvent.java b/src/main/java/envoy/client/event/MessageCreationEvent.java new file mode 100644 index 0000000..28f1b58 --- /dev/null +++ b/src/main/java/envoy/client/event/MessageCreationEvent.java @@ -0,0 +1,18 @@ +package envoy.client.event; + +import envoy.schema.Message; + +/** + * Project: envoy-client
+ * File: MessageCreationEvent.java
+ * Created: 4 Dec 2019
+ * + * @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); } +} diff --git a/src/main/java/envoy/client/event/MessageEvent.java b/src/main/java/envoy/client/event/MessageEvent.java new file mode 100644 index 0000000..014a7bb --- /dev/null +++ b/src/main/java/envoy/client/event/MessageEvent.java @@ -0,0 +1,20 @@ +package envoy.client.event; + +import envoy.schema.Message; + +/** + * Project: envoy-client
+ * File: MessageCreationEvent.java
+ * Created: 4 Dec 2019
+ * + * @author Kai S. K. Engelbart + */ +public class MessageEvent implements Event { + + protected final Message message; + + public MessageEvent(Message message) { this.message = message; } + + @Override + public Message get() { return message; } +} diff --git a/src/main/java/envoy/client/event/MessageModificationEvent.java b/src/main/java/envoy/client/event/MessageModificationEvent.java new file mode 100644 index 0000000..0b83ef0 --- /dev/null +++ b/src/main/java/envoy/client/event/MessageModificationEvent.java @@ -0,0 +1,18 @@ +package envoy.client.event; + +import envoy.schema.Message; + +/** + * Project: envoy-client
+ * File: MessageModificationEvent.java
+ * Created: 4 Dec 2019
+ * + * @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); } +} diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index e36a3f0..b43bb65 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -40,7 +40,7 @@ import envoy.schema.User; * Project: envoy-client
* File: ChatWindow.java
* Created: 28 Sep 2019
- * + * * @author Kai S. K. Engelbart * @author Maximilian Käfer * @author Leon Hofmeister @@ -59,7 +59,7 @@ public class ChatWindow extends JFrame { private Chat currentChat; private JTextArea messageEnterTextArea; - + private static final Logger logger = Logger.getLogger(ChatWindow.class.getSimpleName()); public ChatWindow(Client client, LocalDB localDB) { @@ -134,13 +134,9 @@ 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))) { - + && ((SettingsScreen.enterToSend && e.getModifiersEx() == 0) || (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) postMessage(messageList); - } - } }); // Checks for changed Message @@ -268,6 +264,7 @@ public class ChatWindow extends JFrame { private void postMessage(JList messageList) { if (!client.hasRecipient()) { JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE); + return; } if (!messageEnterTextArea.getText().isEmpty()) try { @@ -292,7 +289,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() { @@ -313,7 +310,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 diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index 5b8674f..f672f94 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/ui/Startup.java @@ -71,8 +71,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(); } diff --git a/src/main/java/envoy/client/ui/StatusTrayIcon.java b/src/main/java/envoy/client/ui/StatusTrayIcon.java new file mode 100644 index 0000000..01cc8d7 --- /dev/null +++ b/src/main/java/envoy/client/ui/StatusTrayIcon.java @@ -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: envoy-client
+ * File: StatusTrayIcon.java
+ * Created: 3 Dec 2019
+ * + * @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>> supports() { + Set>> supportedEvents = new HashSet<>(); + supportedEvents.add(MessageCreationEvent.class); + return supportedEvents; + } +} diff --git a/src/main/resources/envoy_logo.png b/src/main/resources/envoy_logo.png new file mode 100644 index 0000000..35ef7d9 Binary files /dev/null and b/src/main/resources/envoy_logo.png differ