diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..2b978b3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: CyB3RC0nN0R, delvh, DieGurke, derharry333 + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..fbdebf6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: CyB3RC0nN0R, delvh, DieGurke + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.project b/.project index 08d7599..49a90bb 100644 --- a/.project +++ b/.project @@ -34,7 +34,5 @@ org.eclipse.jdt.core.javanature org.eclipse.m2e.core.maven2Nature - org.jboss.tools.jst.web.kb.kbnature - org.jboss.tools.cdi.core.cdinature 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/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..9d4ae77 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at kske@outlook.de. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/src/main/java/envoy/client/Client.java b/src/main/java/envoy/client/Client.java index 6533d47..8f96739 100644 --- a/src/main/java/envoy/client/Client.java +++ b/src/main/java/envoy/client/Client.java @@ -1,5 +1,7 @@ package envoy.client; +import java.util.logging.Logger; + import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.WebTarget; @@ -28,18 +30,20 @@ public class Client { private Config config; private User sender, recipient; + private static final Logger logger = Logger.getLogger(Client.class.getSimpleName()); + public Client(Config config, String username) { this.config = config; sender = getUser(username); - System.out.println("ID: " + sender.getID()); + + logger.info("ID: " + sender.getID()); } private R post(String uri, T body, Class responseBodyClass) { - javax.ws.rs.client.Client client = ClientBuilder.newClient(); - WebTarget target = client.target(uri); - - Response response = target.request().post(Entity.entity(body, "application/xml")); - R responseBody = response.readEntity(responseBodyClass); + javax.ws.rs.client.Client client = ClientBuilder.newClient(); + WebTarget target = client.target(uri); + Response response = target.request().post(Entity.entity(body, "application/xml")); + R responseBody = response.readEntity(responseBodyClass); response.close(); client.close(); @@ -92,7 +96,7 @@ public class Client { if (returnSenderSync.getUsers().size() == 1) { returnSender = returnSenderSync.getUsers().get(0); } else { - System.out.println("ERROR exiting..."); + logger.warning("ERROR exiting..."); } return returnSender; @@ -129,7 +133,7 @@ public class Client { * Updating UserStatus of all users in LocalDB. (Server sends all users with * their updated UserStatus to the client.)
* - * @param userId the client who sends the sync + * @param userId the id of the {@link Client} who sends the {@link Sync} * @param sync the sync object (yet to be converted from java class to sync.xml) * @return a returnSync.xml file * @since Envoy v0.1-alpha @@ -166,16 +170,16 @@ public class Client { public User getRecipient() { return recipient; } /** - * Sets the recipient. + * Sets the recipient. + * * @param recipient - the recipient to set * @since Envoy v0.1-alpha */ public void setRecipient(User recipient) { this.recipient = recipient; } - /** + /** * @return true, if a recipient is selected * @since Envoy v0.1-alpha */ public boolean hasRecipient() { return recipient != null; } } - diff --git a/src/main/java/envoy/client/Config.java b/src/main/java/envoy/client/Config.java index fd83299..8cea817 100644 --- a/src/main/java/envoy/client/Config.java +++ b/src/main/java/envoy/client/Config.java @@ -32,7 +32,7 @@ public class Config { * * @param properties a {@link Properties} object containing information about * the server and port, as well as the path to the local - * database + * database and the synchronization timeout * @since Envoy v0.1-alpha */ public void load(Properties properties) { @@ -65,8 +65,6 @@ public class Config { localDB = new File(args[++i]); break; } - if (localDB == null) localDB = new File(".\\localDB"); - if (syncTimeout == 0) syncTimeout = 1000; } /** @@ -116,7 +114,7 @@ public class Config { /** * Changes the default local database. * Exclusively intended for development purposes. - * + * * @param localDB the file containing the local database * @since Envoy v0.1-alpha **/ diff --git a/src/main/java/envoy/client/LocalDB.java b/src/main/java/envoy/client/LocalDB.java index 9f683a8..f061b28 100644 --- a/src/main/java/envoy/client/LocalDB.java +++ b/src/main/java/envoy/client/LocalDB.java @@ -1,313 +1,295 @@ -package envoy.client; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; - -import javax.xml.datatype.DatatypeConfigurationException; -import javax.xml.datatype.DatatypeFactory; - -import envoy.exception.EnvoyException; -import envoy.schema.Message; -import envoy.schema.Message.Metadata.MessageState; -import envoy.schema.ObjectFactory; -import envoy.schema.Sync; -import envoy.schema.User; - -/** - * Project: envoy-client
- * File: LocalDB.java
- * Created: 27.10.2019
- * - * @author Kai S. K. Engelbart - * @author Maximilian Käfer - * @since Envoy v0.1-alpha - */ -public class LocalDB { - - private File localDB; - private User sender; - private List chats = new ArrayList<>(); - private ObjectFactory objectFactory = new ObjectFactory(); - private DatatypeFactory datatypeFactory; - - private Sync unreadMessagesSync = objectFactory.createSync(); - private Sync sync = objectFactory.createSync(); - private Sync readMessages = objectFactory.createSync(); - - /** - * Constructs an empty local database. - * - * @param sender the user that is logged in with this client - * @since Envoy v0.1-alpha - */ - - public LocalDB(User sender) { - this.sender = sender; - try { - datatypeFactory = DatatypeFactory.newInstance(); - } catch (DatatypeConfigurationException e) { - e.printStackTrace(); - } - } - - /** - * Initializes the local database and fills it with values - * if the user has already sent or received messages. - * - * @param localDBDir the directory where we wish to save/load the database from. - * @throws EnvoyException if the directory selected is not an actual directory. - * @since Envoy v0.1-alpha - */ +package envoy.client; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import javax.xml.datatype.DatatypeConfigurationException; +import javax.xml.datatype.DatatypeFactory; + +import envoy.client.event.EventBus; +import envoy.client.event.MessageCreationEvent; +import envoy.exception.EnvoyException; +import envoy.schema.Message; +import envoy.schema.Message.Metadata.MessageState; +import envoy.schema.ObjectFactory; +import envoy.schema.Sync; +import envoy.schema.User; + +/** + * Project: envoy-client
+ * File: LocalDB.java
+ * Created: 27.10.2019
+ * + * @author Kai S. K. Engelbart + * @author Maximilian Käfer + * @since Envoy v0.1-alpha + */ +public class LocalDB { + + private File localDB; + private User sender; + private List chats = new ArrayList<>(); + private ObjectFactory objectFactory = new ObjectFactory(); + private DatatypeFactory datatypeFactory; + + private Sync unreadMessagesSync = objectFactory.createSync(); + private Sync sync = objectFactory.createSync(); + private Sync readMessages = objectFactory.createSync(); + + private static final Logger logger = Logger.getLogger(LocalDB.class.getSimpleName()); + + /** + * Constructs an empty local database. + * + * @param sender the user that is logged in with this client + * @since Envoy v0.1-alpha + */ + public LocalDB(User sender) { + this.sender = sender; + try { + datatypeFactory = DatatypeFactory.newInstance(); + } catch (DatatypeConfigurationException e) { + e.printStackTrace(); + } + } + + /** + * Initializes the local database and fills it with values + * if the user has already sent or received messages. + * + * @param localDBDir the directory where we wish to save/load the database from. + * @throws EnvoyException if the directory selected is not an actual directory. + * @since Envoy v0.1-alpha + */ public void initializeDBFile(File localDBDir) throws EnvoyException { - if (localDBDir.exists() && !localDBDir.isDirectory()) throw new EnvoyException( - String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath())); + if (localDBDir.exists() && !localDBDir.isDirectory()) + throw new EnvoyException(String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath())); localDB = new File(localDBDir, sender.getID() + ".db"); - if (localDB.exists()) loadFromLocalDB(); - } - - /** - * Saves the database into a file for future use.
- * It is theoretically possible to fail due to unknown causes.
- * In such a case, every message sent/ received during that session will be - * lost. - * - * @throws IOException gets thrown, if saving failed for some reason - * @since Envoy v0.1-alpha - */ - public void saveToLocalDB() throws IOException{ - try { - localDB.getParentFile().mkdirs(); - localDB.createNewFile(); - } catch (IOException e) { - e.printStackTrace(); - System.err.println("unable to save the messages"); - } - try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) { - out.writeObject(chats); - } catch (IOException ex) { - ex.printStackTrace(); - System.err.println("unable to save the messages"); - } - } - - /** - * Loads all chats saved by Envoy for the client user. - * - * @throws EnvoyException if something fails while loading. - * @since Envoy v0.1-alpha - */ - @SuppressWarnings("unchecked") - private void loadFromLocalDB() throws EnvoyException { - try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(localDB))) { - Object obj = in.readObject(); - if (obj instanceof ArrayList) chats = (ArrayList) obj; - } catch (ClassNotFoundException | IOException e) { - throw new EnvoyException(e); - } - } - - /** - * Creates a {@link Message} object serializable to XML. - * - * @param textContent The content (text) of the message - * @param recipientID self explanatory - * @return prepared {@link Message} object - */ - public Message createMessage(String textContent, long recipientID) { - Message.Metadata metaData = objectFactory.createMessageMetadata(); - metaData.setSender(sender.getID()); - metaData.setRecipient(recipientID); - metaData.setState(MessageState.WAITING); - metaData.setDate(datatypeFactory.newXMLGregorianCalendar(Instant.now().toString())); - - Message.Content content = objectFactory.createMessageContent(); - content.setType("text"); - content.setText(textContent); - - Message message = objectFactory.createMessage(); - message.setMetadata(metaData); - message.getContent().add(content); - - return message; - } - - public Sync fillSync(long userId) { - addWaitingMessagesToSync(); - - sync.getMessages().addAll(readMessages.getMessages()); - readMessages.getMessages().clear(); - - System.out.println(sync.getMessages().size()); - return sync; - } - - public void applySync(Sync returnSync) { - for (int i = 0; i < returnSync.getMessages().size(); i++) { - if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 - && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.SENT) { - // Update Local Messages with State WAITING (add Message ID and change State to - // SENT) - for (int j = 0; j < sync.getMessages().size(); j++) { - if (j == i) { - sync.getMessages().get(j).getMetadata().setMessageId(returnSync.getMessages().get(j).getMetadata().getMessageId()); - sync.getMessages().get(j).getMetadata().setState(returnSync.getMessages().get(j).getMetadata().getState()); - } - } - } - if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 && returnSync.getMessages().get(i).getMetadata().getSender() != 0 - && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) { - // these are the unread Messages from the server - unreadMessagesSync.getMessages().add(returnSync.getMessages().get(i)); - } - - if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 && returnSync.getMessages().get(i).getMetadata().getSender() == 0 - && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) { - // Update Messages in localDB to state RECEIVED - for (int j = 0; j < getChats().size(); j++) { - if (getChats().get(j).getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) { - for (int k = 0; k < getChats().get(j).getModel().getSize(); k++) { - if (getChats().get(j).getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages() - .get(i) - .getMetadata() - .getMessageId()) { - // Update Message in LocalDB - getChats().get(j).getModel().get(k).getMetadata().setState(returnSync.getMessages().get(j).getMetadata().getState()); - } - } - } - } - - } - - if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 - && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.READ) { - // Update local Messages to state READ - System.out.println("Message with ID: " + returnSync.getMessages().get(i).getMetadata().getMessageId() - + "was initialized to be set to READ in localDB."); - for (int j = 0; j < getChats().size(); j++) { - if (getChats().get(j).getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) { - System.out.println("Chat with: " + getChats().get(j).getRecipient().getID() + "was selected."); - for (int k = 0; k < getChats().get(j).getModel().getSize(); k++) { - if (getChats().get(j).getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages() - .get(i) - .getMetadata() - .getMessageId()) { - System.out.println( - "Message with ID: " + getChats().get(j).getModel().get(k).getMetadata().getMessageId() + "was selected."); - getChats().get(j).getModel().get(k).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState()); - System.out - .println("Message State is now: " + getChats().get(j).getModel().get(k).getMetadata().getState().toString()); - } - } - } - } - } - } - - // Updating UserStatus of all users in LocalDB - for (int j = 0; j < returnSync.getUsers().size(); j++) { - for (int k = 0; k < getChats().size(); k++) { - if (getChats().get(k).getRecipient().getID() == returnSync.getUsers().get(j).getID()) { - - getChats().get(k).getRecipient().setStatus(returnSync.getUsers().get(j).getStatus()); - System.out.println(getChats().get(k).getRecipient().getStatus().toString()); - } - } - } - - sync.getMessages().clear(); - sync.getUsers().clear(); - } - + if (localDB.exists()) loadFromLocalDB(); + } + /** - * Adds a message to the "sync" Sync object. - * - * @param message the message to send - * @since Envoy v0.1-alpha - */ - public void addMessageToSync(Message message) { sync.getMessages().add(message); } - + * Saves the database into a file for future use. + * + * @throws IOException if something went wrong during saving + * @since Envoy v0.1-alpha + */ + public void saveToLocalDB() throws IOException { + localDB.getParentFile().mkdirs(); + localDB.createNewFile(); + try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) { + out.writeObject(chats); + } catch (IOException ex) { + throw ex; + } + } + /** - * Adds the unread messages returned from the server in the latest sync to the - * right chats in the LocalDB. - * - * @since Envoy v0.1-alpha - */ - public void addUnreadMessagesToLocalDB() { - Sync unreadMessages = unreadMessagesSync; - for (int i = 0; i < unreadMessages.getMessages().size(); i++) - for (int j = 0; j < getChats().size(); j++) - if (getChats().get(j).getRecipient().getID() == unreadMessages.getMessages().get(i).getMetadata().getSender()) { - getChats().get(j).appendMessage(unreadMessages.getMessages().get(i)); - } - } - + * Loads all chats saved by Envoy for the client user. + * + * @throws EnvoyException if something fails while loading. + * @since Envoy v0.1-alpha + */ + @SuppressWarnings("unchecked") + private void loadFromLocalDB() throws EnvoyException { + try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(localDB))) { + Object obj = in.readObject(); + if (obj instanceof ArrayList) chats = (ArrayList) obj; + } catch (ClassNotFoundException | IOException e) { + throw new EnvoyException(e); + } + } + /** - * Changes all messages with state {@code RECEIVED} of a specific chat to state - * {@code READ}. - *
- * Adds these messages to the {@code readMessages} {@link Sync} object. - * - * @param currentChat the chat that was just opened - * @since Envoy v0.1-alpha - */ - public void setMessagesToRead(Chat currentChat) { - for (int i = currentChat.getModel().size() - 1; i >= 0; --i) - if (currentChat.getModel().get(i).getMetadata().getRecipient() != currentChat.getRecipient().getID()) - if (currentChat.getModel().get(i).getMetadata().getState() == MessageState.RECEIVED) { - currentChat.getModel().get(i).getMetadata().setState(MessageState.READ); - readMessages.getMessages().add(currentChat.getModel().get(i)); - } else break; - } - - /** - * Adds all messages with state {@code WAITING} from the {@link LocalDB} to the - * {@link Sync} object. + * Creates a {@link Message} object serializable to XML. + * + * @param textContent The content (text) of the message + * @param recipientID The recipient of the message + * @return prepared {@link Message} object + * @since Envoy v0.1-alpha + */ + public Message createMessage(String textContent, long recipientID) { + Message.Metadata metaData = objectFactory.createMessageMetadata(); + metaData.setSender(sender.getID()); + metaData.setRecipient(recipientID); + metaData.setState(MessageState.WAITING); + metaData.setDate(datatypeFactory.newXMLGregorianCalendar(Instant.now().toString())); + + Message.Content content = objectFactory.createMessageContent(); + content.setType("text"); + content.setText(textContent); + + Message message = objectFactory.createMessage(); + message.setMetadata(metaData); + message.getContent().add(content); + + return message; + } + + /** + * Creates a {@link Sync} object filled with the changes that occurred to the + * local database since the last synchronization. * - * @param message the message that is not yet received by the other user - * @param currentChat the chat that is currently open - * @since Envoy v0.1-alpha + * @param userId the ID of the user that is synchronized by this client + * @return {@link Sync} object filled with the current changes + * @since Envoy v0.1-alpha */ - public void addWaitingMessageToLocalDB(Message message, Chat currentChat) { currentChat.appendMessage(message); } - - /** - * Adds all messages with State WAITING from the {@link LocalDB} to the Sync. - * - * @since Envoy v0.1-alpha + public Sync fillSync(long userId) { + addWaitingMessagesToSync(); + + sync.getMessages().addAll(readMessages.getMessages()); + readMessages.getMessages().clear(); + + logger.finest(String.format("Filled sync with %d messages.", sync.getMessages().size())); + return sync; + } + + /** + * Applies the changes carried by a {@link Sync} object to the local database + * + * @param returnSync the {@link Sync} object to apply + * @since Envoy v0.1-alpha */ - private void addWaitingMessagesToSync() { - for (Chat chat : getChats()) - for (int i = 0; i < chat.getModel().size(); i++) - if (chat.getModel().get(i).getMetadata().getState() == MessageState.WAITING) { - System.out.println("Got Waiting Message"); - sync.getMessages().add(chat.getModel().get(i)); - } - } - - /** - * Clears the {@code unreadMessagesSync} {@link Sync} object. - * - * @since Envoy v0.1-alpha - */ - public void clearUnreadMessagesSync() { unreadMessagesSync.getMessages().clear(); } - - /** - * @return all saves {@link Chat} objects that list the client user as the - * client - * @since Envoy v0.1-alpha - **/ - public List getChats() { return chats; } - - /** - * @return the {@link User} who initialized the local database - * @since Envoy v0.1-alpha - */ - public User getUser() { return sender; } -} + public void applySync(Sync returnSync) { + for (int i = 0; i < returnSync.getMessages().size(); i++) { + + // The message has an ID + if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0) { + + // Messages are processes differently corresponding to their state + switch (returnSync.getMessages().get(i).getMetadata().getState()) { + case SENT: + // Update previously waiting and now sent messages that were assigned an ID by + // the server + sync.getMessages().get(i).getMetadata().setMessageId(returnSync.getMessages().get(i).getMetadata().getMessageId()); + sync.getMessages().get(i).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState()); + break; + case RECEIVED: + if (returnSync.getMessages().get(i).getMetadata().getSender() != 0) { + // these are the unread Messages from the server + unreadMessagesSync.getMessages().add(returnSync.getMessages().get(i)); + + // Create and dispatch message creation event + EventBus.getInstance().dispatch(new MessageCreationEvent(returnSync.getMessages().get(i))); + } else { + // Update Messages in localDB to state RECEIVED + for (Chat chat : getChats()) + if (chat.getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) + for (int j = 0; j < chat.getModel().getSize(); j++) + if (chat.getModel().get(j).getMetadata().getMessageId() == returnSync.getMessages() + .get(i) + .getMetadata() + .getMessageId()) + chat.getModel().get(j).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState()); + } + break; + case READ: + // Update local Messages to state READ + logger.info("Message with ID: " + returnSync.getMessages().get(i).getMetadata().getMessageId() + + "was initialized to be set to READ in localDB."); + for (Chat chat : getChats()) + if (chat.getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) { + logger.info("Chat with: " + chat.getRecipient().getID() + "was selected."); + for (int k = 0; k < chat.getModel().getSize(); k++) + if (chat.getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages() + .get(i) + .getMetadata() + .getMessageId()) { + logger.info("Message with ID: " + chat.getModel().get(k).getMetadata().getMessageId() + "was selected."); + chat.getModel().get(k).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState()); + logger.info("Message State is now: " + chat.getModel().get(k).getMetadata().getState()); + } + } + break; + } + } + } + + // Updating UserStatus of all users in LocalDB + for (User user : returnSync.getUsers()) + for (Chat chat : getChats()) + if (user.getID() == chat.getRecipient().getID()) { + chat.getRecipient().setStatus(user.getStatus()); + logger.info(chat.getRecipient().getStatus().toString()); + } + + sync.getMessages().clear(); + sync.getUsers().clear(); + } + + /** + * Adds the unread messages returned from the server in the latest sync to the + * right chats in the LocalDB. + * + * @since Envoy v0.1-alpha + */ + public void addUnreadMessagesToLocalDB() { + for (Message message : unreadMessagesSync.getMessages()) + for (Chat chat : getChats()) + if (message.getMetadata().getSender() == chat.getRecipient().getID()) { + chat.appendMessage(message); + break; + } + } + + /** + * Changes all messages with state {@code RECEIVED} of a specific chat to state + * {@code READ}. + *
+ * Adds these messages to the {@code readMessages} {@link Sync} object. + * + * @param currentChat the {@link Chat} that was just opened + * @since Envoy v0.1-alpha + */ + public void setMessagesToRead(Chat currentChat) { + for (int i = currentChat.getModel().size() - 1; i >= 0; --i) + if (currentChat.getModel().get(i).getMetadata().getRecipient() != currentChat.getRecipient().getID()) + if (currentChat.getModel().get(i).getMetadata().getState() == MessageState.RECEIVED) { + currentChat.getModel().get(i).getMetadata().setState(MessageState.READ); + readMessages.getMessages().add(currentChat.getModel().get(i)); + } else break; + } + + /** + * Adds all messages with state {@code WAITING} from the {@link LocalDB} to the + * {@link Sync} object. + * + * @since Envoy v0.1-alpha + */ + private void addWaitingMessagesToSync() { + for (Chat chat : getChats()) + for (int i = 0; i < chat.getModel().size(); i++) + if (chat.getModel().get(i).getMetadata().getState() == MessageState.WAITING) { + logger.info("Got Waiting Message"); + sync.getMessages().add(chat.getModel().get(i)); + } + } + + /** + * Clears the {@code unreadMessagesSync} {@link Sync} object. + * + * @since Envoy v0.1-alpha + */ + public void clearUnreadMessagesSync() { unreadMessagesSync.getMessages().clear(); } + + /** + * @return all saved {@link Chat} objects that list the client user as the + * sender + * @since Envoy v0.1-alpha + **/ + public List getChats() { return chats; } + + /** + * @return the {@link User} who initialized the local database + * @since Envoy v0.1-alpha + */ + public User getUser() { return sender; } +} 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 d074711..fccbe06 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -1,381 +1,373 @@ -package envoy.client.ui; - -import java.awt.ComponentOrientation; -import java.awt.Font; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.IOException; - -import javax.swing.DefaultListModel; -import javax.swing.JButton; -import javax.swing.JFrame; -import javax.swing.JList; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; -import javax.swing.JTextPane; -import javax.swing.ListSelectionModel; -import javax.swing.SwingUtilities; -import javax.swing.Timer; -import javax.swing.border.EmptyBorder; - -import envoy.client.Chat; -import envoy.client.Client; -import envoy.client.Config; -import envoy.client.LocalDB; -import envoy.client.Settings; -import envoy.schema.Message; -import envoy.schema.Sync; -import envoy.schema.User; - -/** - * Project: envoy-client
- * File: ChatWindow.java
- * Created: 28 Sep 2019
- * - * @author Kai S. K. Engelbart - * @author Maximilian Käfer - * @author Leon Hofmeister - * @since Envoy v0.1-alpha - */ -public class ChatWindow extends JFrame { - - private static final long serialVersionUID = 6865098428255463649L; - - // user specific objects - private Client client; - private LocalDB localDB; - // GUI components - private JPanel contentPane = new JPanel(); - private JTextArea messageEnterTextArea = new JTextArea(); - private JList userList = new JList<>(); - private Chat currentChat; - private JList messageList = new JList<>(); - private JScrollPane scrollPane = new JScrollPane(); - private JTextPane textPane = new JTextPane(); - // private JCheckBox jCbChangeMode; - private JButton postButton = new JButton("Post"); - private JButton settingsButton = new JButton("Settings"); - - private static int space = 4; - - public ChatWindow(Client client, LocalDB localDB) { - this.client = client; - this.localDB = localDB; - - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setBounds(100, 100, 600, 800); - setTitle("Envoy"); - setLocationRelativeTo(null); - - // Save chats when window closes - addWindowListener(new WindowAdapter() { - - @Override - public void windowClosing(WindowEvent e) { - try { - localDB.saveToLocalDB(); - Settings.getInstance().save(); - } catch (IOException e1) { - e1.printStackTrace(); - System.err.println("Could not save localDB"); - } - } - }); - - contentPane.setBorder(new EmptyBorder(space, space, space, space)); - setContentPane(contentPane); - GridBagLayout gbl_contentPane = new GridBagLayout(); - gbl_contentPane.columnWidths = new int[] { 1, 1, 1 }; - gbl_contentPane.rowHeights = new int[] { 1, 1, 1 }; - gbl_contentPane.columnWeights = new double[] { 0.3, 1.0, 0.1 }; - gbl_contentPane.rowWeights = new double[] { 0.05, 1.0, 0.07 }; - contentPane.setLayout(gbl_contentPane); - - messageList.setCellRenderer(new MessageListRenderer()); - messageList.setFocusTraversalKeysEnabled(false); - messageList.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT); - - DefaultListModel messageListModel = new DefaultListModel<>(); - messageList.setModel(messageListModel); - messageList.setFont(new Font("Arial", Font.PLAIN, 17)); - messageList.setFixedCellHeight(60); - messageList.setBorder(new EmptyBorder(space, space, space, space)); - - scrollPane.setViewportView(messageList); - scrollPane.setBorder(null); - - GridBagConstraints gbc_scrollPane = new GridBagConstraints(); - gbc_scrollPane.fill = GridBagConstraints.BOTH; - gbc_scrollPane.gridwidth = 2; - gbc_scrollPane.gridx = 1; - gbc_scrollPane.gridy = 1; - - gbc_scrollPane.insets = new Insets(space, space, space, space); - contentPane.add(scrollPane, gbc_scrollPane); - - // Message enter field - messageEnterTextArea.addKeyListener(new KeyAdapter() { - - @Override - public void keyReleased(KeyEvent e) { - - if (e.getKeyCode() == KeyEvent.VK_ENTER - && ((Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0) - || (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) { - - postMessage(messageList); - } - - } - }); - // Checks for changed Message - messageEnterTextArea.setWrapStyleWord(true); - messageEnterTextArea.setLineWrap(true); - messageEnterTextArea.setBorder(null); - messageEnterTextArea.setFont(new Font("Arial", Font.PLAIN, 17)); - messageEnterTextArea.setBorder(new EmptyBorder(space, space, space, space)); - - GridBagConstraints gbc_messageEnterTextfield = new GridBagConstraints(); - gbc_messageEnterTextfield.fill = GridBagConstraints.BOTH; - gbc_messageEnterTextfield.gridx = 1; - gbc_messageEnterTextfield.gridy = 2; - - gbc_messageEnterTextfield.insets = new Insets(space, space, space, space); - - contentPane.add(messageEnterTextArea, gbc_messageEnterTextfield); - - // Post Button - postButton.setBorderPainted(false); - GridBagConstraints gbc_moveSelectionPostButton = new GridBagConstraints(); - - gbc_moveSelectionPostButton.fill = GridBagConstraints.BOTH; - gbc_moveSelectionPostButton.gridx = 2; - gbc_moveSelectionPostButton.gridy = 2; - - gbc_moveSelectionPostButton.insets = new Insets(space, space, space, space); - - postButton.addActionListener((evt) -> { postMessage(messageList); }); - contentPane.add(postButton, gbc_moveSelectionPostButton); - - // Settings Button - settingsButton.setBorderPainted(false); - - GridBagConstraints gbc_moveSelectionSettingsButton = new GridBagConstraints(); - - gbc_moveSelectionSettingsButton.fill = GridBagConstraints.BOTH; - gbc_moveSelectionSettingsButton.gridx = 2; - gbc_moveSelectionSettingsButton.gridy = 0; - - gbc_moveSelectionSettingsButton.insets = new Insets(space, space, space, space); - - settingsButton.addActionListener((evt) -> { - try { - SettingsScreen.open(); - - changeChatWindowColors(Settings.getInstance().getCurrentTheme()); - - } catch (Exception e) { - System.err.println("An error occured while opening the settings screen: " + e); - e.printStackTrace(); - } - }); - contentPane.add(settingsButton, gbc_moveSelectionSettingsButton); - - // Partner name display - textPane.setFont(new Font("Arial", Font.PLAIN, 20)); - textPane.setEditable(false); - - GridBagConstraints gbc_partnerName = new GridBagConstraints(); - gbc_partnerName.fill = GridBagConstraints.HORIZONTAL; - gbc_partnerName.gridx = 1; - gbc_partnerName.gridy = 0; - - gbc_partnerName.insets = new Insets(space, space, space, space); - contentPane.add(textPane, gbc_partnerName); - - userList.setCellRenderer(new UserListRenderer()); - userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - userList.addListSelectionListener((listSelectionEvent) -> { - if (!listSelectionEvent.getValueIsAdjusting()) { - @SuppressWarnings("unchecked") - final JList selectedUserList = (JList) listSelectionEvent.getSource(); - final User user = selectedUserList.getSelectedValue(); - client.setRecipient(user); - - currentChat = localDB.getChats() - .stream() - .filter(chat -> chat.getRecipient().getID() == user.getID()) - .findFirst() - .get(); - - // Set all unread messages in the chat to read - readCurrentChat(); - - client.setRecipient(user); - textPane.setText(currentChat.getRecipient().getName()); - - messageList.setModel(currentChat.getModel()); - contentPane.revalidate(); - } - }); - - userList.setFont(new Font("Arial", Font.PLAIN, 17)); - userList.setBorder(new EmptyBorder(space, space, space, space)); - - GridBagConstraints gbc_userList = new GridBagConstraints(); - gbc_userList.fill = GridBagConstraints.VERTICAL; - gbc_userList.gridx = 0; - gbc_userList.gridy = 1; - gbc_userList.anchor = GridBagConstraints.PAGE_START; - gbc_userList.insets = new Insets(space, space, space, space); - - changeChatWindowColors(Settings.getInstance().getCurrentTheme()); - - contentPane.add(userList, gbc_userList); - contentPane.revalidate(); - - loadUsersAndChats(); - startSyncThread(Config.getInstance().getSyncTimeout()); - - contentPane.revalidate(); - } - - - /** - * Used to immediately reload the ChatWindow when settings were changed. - * - * @since Envoy v0.1-alpha - */ - public void changeChatWindowColors(String key) { - Theme theme = Settings.getInstance().getThemes().get(key); - - // contentPane - contentPane.setBackground(theme.getBackgroundColor()); - contentPane.setForeground(theme.getUserNameColor()); - // messageList - messageList.setSelectionForeground(theme.getUserNameColor()); - messageList.setSelectionBackground(theme.getSelectionColor()); - messageList.setForeground(theme.getMessageColorChat()); - messageList.setBackground(theme.getCellColor()); - // scrollPane - scrollPane.setForeground(theme.getBackgroundColor()); - scrollPane.setBackground(theme.getCellColor()); - // messageEnterTextArea - messageEnterTextArea.setCaretColor(theme.getTypingMessageColor()); - messageEnterTextArea.setForeground(theme.getTypingMessageColor()); - messageEnterTextArea.setBackground(theme.getCellColor()); - // postButton - postButton.setForeground(theme.getInteractableForegroundColor()); - postButton.setBackground(theme.getInteractableBackgroundColor()); - // settingsButton - settingsButton.setForeground(theme.getInteractableForegroundColor()); - settingsButton.setBackground(theme.getInteractableBackgroundColor()); - // textPane - textPane.setBackground(theme.getBackgroundColor()); - textPane.setForeground(theme.getUserNameColor()); - // userList - userList.setSelectionForeground(theme.getUserNameColor()); - userList.setSelectionBackground(theme.getSelectionColor()); - userList.setForeground(theme.getUserNameColor()); - userList.setBackground(theme.getCellColor()); - - } - - private void postMessage(JList messageList) { - if (!client.hasRecipient()) { - JOptionPane.showMessageDialog(this, - "Please select a recipient!", - "Cannot send message", - JOptionPane.INFORMATION_MESSAGE); - } - - if (!messageEnterTextArea.getText().isEmpty()) try { - - // Create and send message object - final Message message = localDB.createMessage(messageEnterTextArea.getText(), - currentChat.getRecipient().getID()); - localDB.addWaitingMessageToLocalDB(message, currentChat); - messageList.setModel(currentChat.getModel()); - - // Clear text field - messageEnterTextArea.setText(""); - contentPane.revalidate(); - } catch (Exception e) { - JOptionPane.showMessageDialog(this, - "An exception occured while sending a message. See the log for more details.", - "Exception occured", - JOptionPane.ERROR_MESSAGE); - e.printStackTrace(); - } - } - - /** - * Initializes the elements of the user list by downloading them from the - * server. - * - * @since Envoy v0.1-alpha - */ - private void loadUsersAndChats() { - new Thread(() -> { - Sync users = client.getUsersListXml(); - DefaultListModel userListModel = new DefaultListModel<>(); - users.getUsers().forEach(user -> { - userListModel.addElement(user); - - // Check if user exists in local DB - if (localDB.getChats().stream().filter(c -> c.getRecipient().getID() == user.getID()).count() == 0) - localDB.getChats().add(new Chat(user)); - }); - SwingUtilities.invokeLater(() -> userList.setModel(userListModel)); - }).start(); - } - - /** - * Updates the data model and the UI repeatedly after a certain amount of - * time. - * - * @param timeout the amount of time that passes between two requests sent to - * the server - * @since Envoy v0.1-alpha - */ - private void startSyncThread(int timeout) { - new Timer(timeout, (evt) -> { - new Thread(() -> { - - // Synchronize - localDB.applySync( - client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID()))); - - // Process unread messages - localDB.addUnreadMessagesToLocalDB(); - localDB.clearUnreadMessagesSync(); - - // Mark unread messages as read when they are in the current chat - readCurrentChat(); - - // Update UI - SwingUtilities - .invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); }); - }).start(); - }).start(); - } - - private void updateUserStates() { - for (int i = 0; i < userList.getModel().getSize(); i++) - for (int j = 0; j < localDB.getChats().size(); j++) - if (userList.getModel().getElementAt(i).getID() == localDB.getChats().get(j).getRecipient().getID()) - userList.getModel().getElementAt(i).setStatus(localDB.getChats().get(j).getRecipient().getStatus()); - } - - /** - * Marks messages in the current chat as {@code READ}. - */ - private void readCurrentChat() { if (currentChat != null) { localDB.setMessagesToRead(currentChat); } } -} +package envoy.client.ui; + +import java.awt.Color; +import java.awt.ComponentOrientation; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Toolkit; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextPane; +import javax.swing.ListSelectionModel; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.border.EmptyBorder; + +import envoy.client.Chat; +import envoy.client.Client; +import envoy.client.Config; +import envoy.client.LocalDB; +import envoy.client.Settings; +import envoy.schema.Message; +import envoy.schema.Sync; +import envoy.schema.User; + +/** + * Project: envoy-client
+ * File: ChatWindow.java
+ * Created: 28 Sep 2019
+ * + * @author Kai S. K. Engelbart + * @author Maximilian Käfer + * @author Leon Hofmeister + * @since Envoy v0.1-alpha + */ +public class ChatWindow extends JFrame { + + private static final long serialVersionUID = 6865098428255463649L; + + // user specific objects + private Client client; + private LocalDB localDB; + // GUI components + private JPanel contentPane = new JPanel(); + private JTextArea messageEnterTextArea = new JTextArea(); + private JList userList = new JList<>(); + private Chat currentChat; + private JList messageList = new JList<>(); + private JScrollPane scrollPane = new JScrollPane(); + private JTextPane textPane = new JTextPane(); + // private JCheckBox jCbChangeMode; + private JButton postButton = new JButton("Post"); + private JButton settingsButton = new JButton("Settings"); + + private static int space = 4; + + private static final Logger logger = Logger.getLogger(ChatWindow.class.getSimpleName()); + + public ChatWindow(Client client, LocalDB localDB) { + this.client = client; + this.localDB = localDB; + + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setBounds(100, 100, 600, 800); + setTitle("Envoy"); + setLocationRelativeTo(null); + setIconImage(Toolkit.getDefaultToolkit().createImage(getClass().getClassLoader().getResource("envoy_logo.png"))); + + // Save chats when window closes + addWindowListener(new WindowAdapter() { + + @Override + public void windowClosing(WindowEvent evt) { + try { + localDB.saveToLocalDB(); + Settings.getInstance().save(); + } catch (IOException e1) { + e1.printStackTrace(); + logger.log(Level.WARNING, "Unable to save the messages", e1); + } + } + }); + + contentPane.setBorder(new EmptyBorder(space, space, space, space)); + setContentPane(contentPane); + GridBagLayout gbl_contentPane = new GridBagLayout(); + gbl_contentPane.columnWidths = new int[] { 1, 1, 1 }; + gbl_contentPane.rowHeights = new int[] { 1, 1, 1 }; + gbl_contentPane.columnWeights = new double[] { 0.3, 1.0, 0.1 }; + gbl_contentPane.rowWeights = new double[] { 0.05, 1.0, 0.07 }; + contentPane.setLayout(gbl_contentPane); + + messageList.setCellRenderer(new MessageListRenderer()); + messageList.setFocusTraversalKeysEnabled(false); + messageList.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT); + + DefaultListModel messageListModel = new DefaultListModel<>(); + messageList.setModel(messageListModel); + messageList.setFont(new Font("Arial", Font.PLAIN, 17)); + messageList.setFixedCellHeight(60); + messageList.setBorder(new EmptyBorder(space, space, space, space)); + + scrollPane.setViewportView(messageList); + scrollPane.setBorder(null); + + GridBagConstraints gbc_scrollPane = new GridBagConstraints(); + gbc_scrollPane.fill = GridBagConstraints.BOTH; + gbc_scrollPane.gridwidth = 2; + gbc_scrollPane.gridx = 1; + gbc_scrollPane.gridy = 1; + + gbc_scrollPane.insets = new Insets(space, space, space, space); + contentPane.add(scrollPane, gbc_scrollPane); + + // Message enter field + messageEnterTextArea.addKeyListener(new KeyAdapter() { + + @Override + public void keyReleased(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER + && ((Settings.getInstance().isEnterToSend() && e.getModifiersEx() == 0) + || (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) { + postMessage(messageList); + } + }); + // Checks for changed Message + messageEnterTextArea.setWrapStyleWord(true); + messageEnterTextArea.setLineWrap(true); + messageEnterTextArea.setBorder(null); + messageEnterTextArea.setFont(new Font("Arial", Font.PLAIN, 17)); + messageEnterTextArea.setBorder(new EmptyBorder(space, space, space, space)); + + GridBagConstraints gbc_messageEnterTextfield = new GridBagConstraints(); + gbc_messageEnterTextfield.fill = GridBagConstraints.BOTH; + gbc_messageEnterTextfield.gridx = 1; + gbc_messageEnterTextfield.gridy = 2; + + gbc_messageEnterTextfield.insets = new Insets(space, space, space, space); + + contentPane.add(messageEnterTextArea, gbc_messageEnterTextfield); + + // Post Button + postButton.setBorderPainted(false); + GridBagConstraints gbc_moveSelectionPostButton = new GridBagConstraints(); + + gbc_moveSelectionPostButton.fill = GridBagConstraints.BOTH; + gbc_moveSelectionPostButton.gridx = 2; + gbc_moveSelectionPostButton.gridy = 2; + + gbc_moveSelectionPostButton.insets = new Insets(space, space, space, space); + + postButton.addActionListener((evt) -> { postMessage(messageList); }); + contentPane.add(postButton, gbc_moveSelectionPostButton); + + // Settings Button + settingsButton.setBorderPainted(false); + + GridBagConstraints gbc_moveSelectionSettingsButton = new GridBagConstraints(); + + gbc_moveSelectionSettingsButton.fill = GridBagConstraints.BOTH; + gbc_moveSelectionSettingsButton.gridx = 2; + gbc_moveSelectionSettingsButton.gridy = 0; + + gbc_moveSelectionSettingsButton.insets = new Insets(space, space, space, space); + + settingsButton.addActionListener((evt) -> { + try { + SettingsScreen.open(); + changeChatWindowColors(Settings.getInstance().getCurrentTheme()); + } catch (Exception e) { + SettingsScreen.open(); + logger.log(Level.WARNING, "An error occured while opening the settings screen", e); + e.printStackTrace(); + } + }); + contentPane.add(settingsButton, gbc_moveSelectionSettingsButton); + + // Partner name display + textPane.setFont(new Font("Arial", Font.PLAIN, 20)); + textPane.setEditable(false); + + GridBagConstraints gbc_partnerName = new GridBagConstraints(); + gbc_partnerName.fill = GridBagConstraints.HORIZONTAL; + gbc_partnerName.gridx = 1; + gbc_partnerName.gridy = 0; + + gbc_partnerName.insets = new Insets(space, space, space, space); + contentPane.add(textPane, gbc_partnerName); + + userList.setCellRenderer(new UserListRenderer()); + userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + userList.addListSelectionListener((listSelectionEvent) -> { + if (!listSelectionEvent.getValueIsAdjusting()) { + @SuppressWarnings("unchecked") + final JList selectedUserList = (JList) listSelectionEvent.getSource(); + final User user = selectedUserList.getSelectedValue(); + client.setRecipient(user); + + currentChat = localDB.getChats().stream().filter(chat -> chat.getRecipient().getID() == user.getID()).findFirst().get(); + + // Set all unread messages in the chat to read + readCurrentChat(); + + client.setRecipient(user); + textPane.setText(currentChat.getRecipient().getName()); + + messageList.setModel(currentChat.getModel()); + contentPane.revalidate(); + } + }); + + userList.setFont(new Font("Arial", Font.PLAIN, 17)); + userList.setBorder(new EmptyBorder(space, space, space, space)); + + GridBagConstraints gbc_userList = new GridBagConstraints(); + gbc_userList.fill = GridBagConstraints.VERTICAL; + gbc_userList.gridx = 0; + gbc_userList.gridy = 1; + gbc_userList.anchor = GridBagConstraints.PAGE_START; + gbc_userList.insets = new Insets(space, space, space, space); + + changeChatWindowColors(Settings.getInstance().getCurrentTheme()); + + contentPane.add(userList, gbc_userList); + contentPane.revalidate(); + + loadUsersAndChats(); + startSyncThread(Config.getInstance().getSyncTimeout()); + + contentPane.revalidate(); + } + + /** + * Used to immediately reload the ChatWindow when settings were changed. + * + * @since Envoy v0.1-alpha + */ + public void changeChatWindowColors(String key) { + Theme theme = Settings.getInstance().getThemes().get(key); + + // contentPane + contentPane.setBackground(theme.getBackgroundColor()); + contentPane.setForeground(theme.getUserNameColor()); + // messageList + messageList.setSelectionForeground(theme.getUserNameColor()); + messageList.setSelectionBackground(theme.getSelectionColor()); + messageList.setForeground(theme.getMessageColorChat()); + messageList.setBackground(theme.getCellColor()); + // scrollPane + scrollPane.setForeground(theme.getBackgroundColor()); + scrollPane.setBackground(theme.getCellColor()); + // messageEnterTextArea + messageEnterTextArea.setCaretColor(theme.getTypingMessageColor()); + messageEnterTextArea.setForeground(theme.getTypingMessageColor()); + messageEnterTextArea.setBackground(theme.getCellColor()); + // postButton + postButton.setForeground(theme.getInteractableForegroundColor()); + postButton.setBackground(theme.getInteractableBackgroundColor()); + // settingsButton + settingsButton.setForeground(theme.getInteractableForegroundColor()); + settingsButton.setBackground(theme.getInteractableBackgroundColor()); + // textPane + textPane.setBackground(theme.getBackgroundColor()); + textPane.setForeground(theme.getUserNameColor()); + // userList + userList.setSelectionForeground(theme.getUserNameColor()); + userList.setSelectionBackground(theme.getSelectionColor()); + userList.setForeground(theme.getUserNameColor()); + userList.setBackground(theme.getCellColor()); + + } + + private void postMessage(JList messageList) { + if (!client.hasRecipient()) { + JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE); + return; + } + + if (!messageEnterTextArea.getText().isEmpty()) try { + + // Create and send message object + final Message message = localDB.createMessage(messageEnterTextArea.getText(), currentChat.getRecipient()); + currentChat.appendMessage(message); + messageList.setModel(currentChat.getModel()); + + // Clear text field + messageEnterTextArea.setText(""); + contentPane.revalidate(); + } catch (Exception e) { + JOptionPane.showMessageDialog(this, + "An exception occured while sending a message. See the log for more details.", + "Exception occured", + JOptionPane.ERROR_MESSAGE); + e.printStackTrace(); + } + } + + /** + * Initializes the elements of the user list by downloading them from the + * server. + * + * @since Envoy v0.1-alpha + */ + private void loadUsersAndChats() { + new Thread(() -> { + Sync users = client.getUsersListXml(); + DefaultListModel userListModel = new DefaultListModel<>(); + users.getUsers().forEach(user -> { + userListModel.addElement(user); + + // Check if user exists in local DB + if (localDB.getChats().stream().filter(c -> c.getRecipient().getID() == user.getID()).count() == 0) + localDB.getChats().add(new Chat(user)); + }); + SwingUtilities.invokeLater(() -> userList.setModel(userListModel)); + }).start(); + } + + /** + * Updates the data model and the UI repeatedly after a certain amount of + * time. + * + * @param timeout the amount of time that passes between two requests sent to + * the server + * @since Envoy v0.1-alpha + */ + private void startSyncThread(int timeout) { + new Timer(timeout, (evt) -> { + new Thread(() -> { + + // Synchronize + localDB.applySync(client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID()))); + + // Process unread messages + localDB.addUnreadMessagesToLocalDB(); + localDB.clearUnreadMessagesSync(); + + // Mark unread messages as read when they are in the current chat + readCurrentChat(); + + // Update UI + SwingUtilities.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); }); + }).start(); + }).start(); + } + + private void updateUserStates() { + for (int i = 0; i < userList.getModel().getSize(); i++) + for (int j = 0; j < localDB.getChats().size(); j++) + if (userList.getModel().getElementAt(i).getID() == localDB.getChats().get(j).getRecipient().getID()) + userList.getModel().getElementAt(i).setStatus(localDB.getChats().get(j).getRecipient().getStatus()); + } + + /** + * Marks messages in the current chat as {@code READ}. + */ + private void readCurrentChat() { if (currentChat != null) { localDB.setMessagesToRead(currentChat); } } +} diff --git a/src/main/java/envoy/client/ui/MessageListRenderer.java b/src/main/java/envoy/client/ui/MessageListRenderer.java index ec2aaab..2e6b65f 100644 --- a/src/main/java/envoy/client/ui/MessageListRenderer.java +++ b/src/main/java/envoy/client/ui/MessageListRenderer.java @@ -1,78 +1,76 @@ -package envoy.client.ui; - -import java.awt.Color; -import java.awt.Component; -import java.text.SimpleDateFormat; - -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.ListCellRenderer; - -import envoy.client.Settings; -import envoy.schema.Message; - -/** - * Defines how a message is displayed.
- *
- * - * Project: envoy-client
- * File: UserListRenderer.java
- * Created: 19 Oct 2019
- * - * @author Kai S. K. Engelbart - * @author Maximilian Käfer - * @since Envoy v0.1-alpha - */ -public class MessageListRenderer extends JLabel implements ListCellRenderer { - - private static final long serialVersionUID = 5164417379767181198L; - - @Override - public Component getListCellRendererComponent(JList list, Message value, int index, - boolean isSelected, boolean cellHasFocus) { - if (isSelected) { - setBackground(list.getSelectionBackground()); - setForeground(list.getSelectionForeground()); - } else { - setBackground(list.getBackground()); - setForeground(list.getForeground()); - } - - setOpaque(true); - - final String text = value.getContent().get(0).getText(); - final String state = value.getMetadata().getState().toString(); - final String date = value.getMetadata().getDate() == null ? "" - : new SimpleDateFormat("dd.MM.yyyy HH:mm ") - .format(value.getMetadata().getDate().toGregorianCalendar().getTime()); - - // Getting the MessageColor in the Chat of the current theme - String textColor = null; - - textColor = toHex( - Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getMessageColorChat()); - - // Getting the DateColor in the Chat of the current theme - String dateColor = null; - dateColor = toHex( - Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getDateColorChat()); - - setText(String.format( - "

%s

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

%s

%s :%s", + dateColor, + date, + textColor, + text, + state)); + return this; + } + + public String toHex(Color c) { + int r = c.getRed(); + int g = c.getGreen(); + int b = c.getBlue(); + String hex = String.format("#%02x%02x%02x", r, g, b); + return hex; + } } \ No newline at end of file diff --git a/src/main/java/envoy/client/ui/SettingsScreen.java b/src/main/java/envoy/client/ui/SettingsScreen.java index 6d5c94d..d2b2db3 100644 --- a/src/main/java/envoy/client/ui/SettingsScreen.java +++ b/src/main/java/envoy/client/ui/SettingsScreen.java @@ -509,4 +509,5 @@ public class SettingsScreen extends JDialog { colorsPanel.add(panel); } + } diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index f940191..25af819 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/ui/Startup.java @@ -3,6 +3,8 @@ package envoy.client.ui; import java.awt.EventQueue; import java.io.IOException; import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.swing.JOptionPane; @@ -26,29 +28,35 @@ import envoy.exception.EnvoyException; */ public class Startup { + private static final Logger logger = Logger.getLogger(Startup.class.getSimpleName()); + public static void main(String[] args) { + logger.setLevel(Level.ALL); + Config config = Config.getInstance(); - if (args.length > 0) { - config.load(args); - } else { - ClassLoader loader = Thread.currentThread().getContextClassLoader(); - try { - Properties configProperties = new Properties(); - configProperties.load(loader.getResourceAsStream("client.properties")); - config.load(configProperties); - } catch (IOException e) { - e.printStackTrace(); - } + + // Load the configuration from client.properties first + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + try { + Properties configProperties = new Properties(); + configProperties.load(loader.getResourceAsStream("client.properties")); + config.load(configProperties); + } catch (IOException e) { + e.printStackTrace(); } + // Override configuration values with command line arguments + if (args.length > 0) config.load(args); + if (!config.isInitialized()) { - System.err.println("Server or port are not defined. Exiting..."); + logger.severe("Server or port are not defined. Exiting..."); + JOptionPane.showMessageDialog(null, "Error loading configuration values.", "Configuration error", JOptionPane.ERROR_MESSAGE); System.exit(1); } String userName = JOptionPane.showInputDialog("Please enter your username"); if (userName == null || userName.isEmpty()) { - System.err.println("User name is not set or empty. Exiting..."); + logger.severe("User name is not set or empty. Exiting..."); System.exit(1); } Client client = new Client(config, userName); @@ -66,8 +74,9 @@ public class Startup { EventQueue.invokeLater(() -> { try { - ChatWindow frame = new ChatWindow(client, localDB); - frame.setVisible(true); + ChatWindow chatWindow = new ChatWindow(client, localDB); + new StatusTrayIcon(chatWindow).show(); + chatWindow.setVisible(true); } catch (Exception e) { e.printStackTrace(); } 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/java/envoy/client/ui/UserListRenderer.java b/src/main/java/envoy/client/ui/UserListRenderer.java index ef40e3c..3258afa 100644 --- a/src/main/java/envoy/client/ui/UserListRenderer.java +++ b/src/main/java/envoy/client/ui/UserListRenderer.java @@ -1,84 +1,77 @@ -package envoy.client.ui; - -import java.awt.Color; -import java.awt.Component; - -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.ListCellRenderer; - -import envoy.client.Settings; -import envoy.schema.User; -import envoy.schema.User.UserStatus; - -/** - * Defines how the {@code UserList} is displayed. - * - * Project: envoy-client
- * File: UserListRenderer.java
- * Created: 12 Oct 2019
- * - * @author Kai S. K. Engelbart - * @author Maximilian Käfer - * @since Envoy v0.1-alpha - */ -public class UserListRenderer extends JLabel implements ListCellRenderer { - - private static final long serialVersionUID = 5164417379767181198L; - - @SuppressWarnings("incomplete-switch") - @Override - public Component getListCellRendererComponent(JList list, User value, int index, boolean isSelected, - boolean cellHasFocus) { - if (isSelected) { - setBackground(list.getSelectionBackground()); - setForeground(list.getSelectionForeground()); - } else { - setBackground(list.getBackground()); - setForeground(list.getForeground()); - } - - // Enable background rendering - setOpaque(true); - - - final String name = value.getName(); - final UserStatus status = value.getStatus(); - - // Getting the UserNameColor of the current theme - String textColor = null; - - textColor = toHex( - Settings.getInstance().getThemes().get(Settings.getInstance().getCurrentTheme()).getUserNameColor()); - - switch (status) { - case ONLINE: - setText(String.format( - "

%s

%s", - status, - textColor, - name)); - break; - - case OFFLINE: - setText(String.format( - "

%s

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

%s

%s", + status, + textColor, + name)); + break; + case OFFLINE: + setText(String.format( + "

%s

%s", + status, + textColor, + name)); + break; + } + return this; + } + + public String toHex(Color c) { + int r = c.getRed(); + int g = c.getGreen(); + int b = c.getBlue(); + String hex = String.format("#%02x%02x%02x", r, g, b); + return hex; + } } \ No newline at end of file diff --git a/src/main/resources/envoy_logo.png b/src/main/resources/envoy_logo.png new file mode 100644 index 0000000..1606bd5 Binary files /dev/null and b/src/main/resources/envoy_logo.png differ diff --git a/src/main/resources/envoy_logo_alpha.png b/src/main/resources/envoy_logo_alpha.png new file mode 100644 index 0000000..f1040c5 Binary files /dev/null and b/src/main/resources/envoy_logo_alpha.png differ diff --git a/src/main/resources/envoy_logo_old.png b/src/main/resources/envoy_logo_old.png new file mode 100644 index 0000000..35ef7d9 Binary files /dev/null and b/src/main/resources/envoy_logo_old.png differ