Merge branch 'develop' into f/themes

This commit is contained in:
delvh 2019-12-07 14:50:20 +01:00 committed by GitHub
commit 6679685b3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 1388 additions and 878 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -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.

View File

@ -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.

View File

@ -34,7 +34,5 @@
<natures> <natures>
<nature>org.eclipse.jdt.core.javanature</nature> <nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature> <nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.jboss.tools.jst.web.kb.kbnature</nature>
<nature>org.jboss.tools.cdi.core.cdinature</nature>
</natures> </natures>
</projectDescription> </projectDescription>

View File

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

76
CODE_OF_CONDUCT.md Normal file
View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB