Display Current User Status and Unread Message Amount in Status Tray Icon #103

Merged
kske merged 8 commits from f/enhanced-status-tray-icon into develop 2020-10-23 17:19:46 +02:00
2 changed files with 96 additions and 39 deletions
Showing only changes of commit 2e17caea4d - Show all commits

View File

@ -3,16 +3,17 @@ package envoy.client.data;
import java.io.*;
import java.util.*;
import javafx.beans.property.*;
import javafx.collections.*;
import envoy.client.net.WriteProxy;
import envoy.data.*;
import envoy.data.Message.MessageStatus;
import envoy.event.MessageStatusChange;
import envoy.client.net.WriteProxy;
/**
* Represents a chat between two {@link User}s
* as a list of {@link Message} objects.
* Represents a chat between two {@link User}s as a list of {@link Message} objects.
*
* @author Maximilian Käfer
* @author Leon Hofmeister
@ -25,13 +26,14 @@ public class Chat implements Serializable {
protected transient ObservableList<Message> messages = FXCollections.observableArrayList();
protected int unreadAmount;
/**
* Stores the last time an {@link envoy.event.IsTyping} event has been sent.
*/
protected transient long lastWritingEvent;
protected int unreadAmount;
protected static IntegerProperty totalUnreadAmount = new SimpleIntegerProperty(0);
private static final long serialVersionUID = 2L;
/**
@ -42,11 +44,14 @@ public class Chat implements Serializable {
* @param recipient the user who receives the messages
* @since Envoy Client v0.1-alpha
*/
public Chat(Contact recipient) { this.recipient = recipient; }
public Chat(Contact recipient) {
this.recipient = recipient;
}
private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException {
stream.defaultReadObject();
messages = FXCollections.observableList((List<Message>) stream.readObject());
totalUnreadAmount.set(totalUnreadAmount.get() + unreadAmount);
}
private void writeObject(ObjectOutputStream stream) throws IOException {
@ -55,7 +60,10 @@ public class Chat implements Serializable {
}
@Override
public String toString() { return String.format("%s[recipient=%s,messages=%d]", getClass().getSimpleName(), recipient, messages.size()); }
public String toString() {
return String.format("%s[recipient=%s,messages=%d]", getClass().getSimpleName(), recipient,
messages.size());
}
/**
* Generates a hash code based on the recipient.
@ -63,7 +71,9 @@ public class Chat implements Serializable {
* @since Envoy Client v0.1-beta
*/
@Override
public int hashCode() { return Objects.hash(recipient); }
public int hashCode() {
return Objects.hash(recipient);
}
/**
* Tests equality to another object based on the recipient.
@ -72,39 +82,46 @@ public class Chat implements Serializable {
*/
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Chat)) return false;
if (this == obj)
return true;
if (!(obj instanceof Chat))
return false;
final var other = (Chat) obj;
return Objects.equals(recipient, other.recipient);
}
/**
* Sets the status of all chat messages received from the recipient to
* {@code READ} starting from the bottom and stopping once a read message is
* found.
* Sets the status of all chat messages received from the recipient to {@code READ} starting
* from the bottom and stopping once a read message is found.
*
* @param writeProxy the write proxy instance used to notify the server about
* the message status changes
* @param writeProxy the write proxy instance used to notify the server about the message status
* changes
* @since Envoy Client v0.3-alpha
*/
public void read(WriteProxy writeProxy) {
for (int i = messages.size() - 1; i >= 0; --i) {
final var m = messages.get(i);
if (m.getSenderID() == recipient.getID()) if (m.getStatus() == MessageStatus.READ) break;
if (m.getSenderID() == recipient.getID())
if (m.getStatus() == MessageStatus.READ)
break;
else {
m.setStatus(MessageStatus.READ);
writeProxy.writeMessageStatusChange(new MessageStatusChange(m));
}
}
totalUnreadAmount.set(totalUnreadAmount.get() - unreadAmount);
unreadAmount = 0;
}
/**
* @return {@code true} if the newest message received in the chat doesn't have
* the status {@code READ}
* @return {@code true} if the newest message received in the chat doesn't have the status
* {@code READ}
* @since Envoy Client v0.3-alpha
*/
public boolean isUnread() { return !messages.isEmpty() && messages.get(messages.size() - 1).getStatus() != MessageStatus.READ; }
public boolean isUnread() {
return !messages.isEmpty()
&& messages.get(messages.size() - 1).getStatus() != MessageStatus.READ;
}
/**
* Inserts a message at the correct place according to its creation date.
@ -128,14 +145,25 @@ public class Chat implements Serializable {
* @return whether the message has been found and removed
* @since Envoy Client v0.3-beta
*/
public boolean remove(long messageID) { return messages.removeIf(m -> m.getID() == messageID); }
public boolean remove(long messageID) {
return messages.removeIf(m -> m.getID() == messageID);
}
/**
* @return an integer property storing the total amount of unread messages
* @since Envoy Client v0.3-beta
*/
public static IntegerProperty getTotalUnreadAmount() { return totalUnreadAmount; }
/**
* Increments the amount of unread messages.
*
* @since Envoy Client v0.1-beta
*/
public void incrementUnreadAmount() { ++unreadAmount; }
public void incrementUnreadAmount() {
++unreadAmount;
totalUnreadAmount.set(totalUnreadAmount.get() + 1);
}
/**
* @return the amount of unread messages in this chat
@ -156,8 +184,7 @@ public class Chat implements Serializable {
public Contact getRecipient() { return recipient; }
/**
* @return the last known time a {@link envoy.event.IsTyping} event has been
* sent
* @return the last known time a {@link envoy.event.IsTyping} event has been sent
* @since Envoy Client v0.2-beta
*/
public long getLastWritingEvent() { return lastWritingEvent; }
@ -167,5 +194,7 @@ public class Chat implements Serializable {
*
* @since Envoy Client v0.2-beta
*/
public void lastWritingEventWasNow() { lastWritingEvent = System.currentTimeMillis(); }
public void lastWritingEventWasNow() {
lastWritingEvent = System.currentTimeMillis();
}
}

View File

@ -1,5 +1,7 @@
package envoy.client.ui;
import static java.awt.Image.SCALE_SMOOTH;
import java.awt.*;
import java.awt.TrayIcon.MessageType;
import java.awt.image.BufferedImage;
@ -13,13 +15,13 @@ import dev.kske.eventbus.Event;
import envoy.data.Message;
import envoy.data.User.UserStatus;
import envoy.client.data.Context;
import envoy.client.data.*;
import envoy.client.event.OwnStatusChange;
import envoy.client.helper.ShutdownHelper;
import envoy.client.util.*;
/**
* A tray icon with the Envoy logo, a "Envoy" tool tip and a pop-up menu with menu items for
* A tray icon with the Envoy logo, an "Envoy" tool tip and a pop-up menu with menu items for
* <ul>
* <li>Changing the user status</li>
* <li>Logging out</li>
@ -43,17 +45,16 @@ public final class StatusTrayIcon implements EventListener {
*/
private boolean displayMessageNotification;
/**
* The size of the tray icon's image.
*/
private final Dimension size;
/**
* The Envoy logo on which the current user status and unread message count will be drawn to
* compose the tray icon.
*/
private final Image logo = IconUtil.loadAWTCompatible("/icons/envoy_logo.png")
.getScaledInstance(size, size, BufferedImage.SCALE_SMOOTH);
/**
* The size of the tray icon, as defined by the system tray.
*/
private static final int size = (int) SystemTray.getSystemTray().getTrayIconSize().getWidth();
private final Image logo;
/**
* @return {@code true} if the status tray icon is supported on this platform
@ -68,6 +69,10 @@ public final class StatusTrayIcon implements EventListener {
* @since Envoy Client v0.2-beta
*/
public StatusTrayIcon(Stage stage) {
size = SystemTray.getSystemTray().getTrayIconSize();
logo = IconUtil.loadAWTCompatible("/icons/envoy_logo.png").getScaledInstance(size.width,
size.height, SCALE_SMOOTH);
final var popup = new PopupMenu();
// Adding the exit menu item
@ -102,6 +107,9 @@ public final class StatusTrayIcon implements EventListener {
.addListener((ov, wasFocused, isFocused) -> displayMessageNotification =
!displayMessageNotification && wasFocused ? false : !isFocused);
// Listen to changes in the total unread message amount
Chat.getTotalUnreadAmount().addListener((ov, oldValue, newValue) -> updateImage());
// Show the window if the user clicks on the icon
trayIcon.addActionListener(evt -> Platform.runLater(() -> {
stage.setIconified(false);
@ -150,6 +158,17 @@ public final class StatusTrayIcon implements EventListener {
: "New message received", message.getText(), MessageType.INFO);
}
/**
* Updates the tray icon's image by first releasing the resources held by the current image and
* then setting a new one generated by the {@link StatusTrayIcon#createImage()} method.
*
* @since Envoy Client v0.3-beta
*/
private void updateImage() {
trayIcon.getImage().flush();
trayIcon.setImage(createImage());
}
/**
* Composes an icon that displays the current user status and the amount of unread messages, if
* any are present.
@ -159,7 +178,7 @@ public final class StatusTrayIcon implements EventListener {
private BufferedImage createImage() {
// Create a new image with the dimensions of the logo
var img = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
var img = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
// Obtain the draw graphics of the image and copy the logo
var g = img.createGraphics();
@ -179,7 +198,16 @@ public final class StatusTrayIcon implements EventListener {
case OFFLINE:
g.setColor(Color.GRAY);
}
g.fillOval(size / 2, size / 2, size / 2, size / 2);
g.fillOval(size.width / 2, size.height / 2, size.width / 2, size.height / 2);
// Draw total amount of unread messages, if any are present
if (Chat.getTotalUnreadAmount().get() > 0) {
g.setColor(Color.RED);
g.fillOval(size.width / 2, 0, size.width / 2, size.height / 2);
g.setColor(Color.BLACK);
g.drawString(String.valueOf(Chat.getTotalUnreadAmount().get()), size.width / 2,
size.height / 2);
}
// Finish drawing
g.dispose();