From 2e17caea4d832072d732699d0db6bc4389cd5b11 Mon Sep 17 00:00:00 2001 From: kske Date: Sun, 18 Oct 2020 16:45:36 +0200 Subject: [PATCH] Keep track of total unread messages and display them in the status tray --- .../src/main/java/envoy/client/data/Chat.java | 85 +++++++++++++------ .../java/envoy/client/ui/StatusTrayIcon.java | 50 ++++++++--- 2 files changed, 96 insertions(+), 39 deletions(-) diff --git a/client/src/main/java/envoy/client/data/Chat.java b/client/src/main/java/envoy/client/data/Chat.java index 80b2a97..0683d2d 100644 --- a/client/src/main/java/envoy/client/data/Chat.java +++ b/client/src/main/java/envoy/client/data/Chat.java @@ -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 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) 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; - else { - m.setStatus(MessageStatus.READ); - writeProxy.writeMessageStatusChange(new MessageStatusChange(m)); - } + 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(); + } } diff --git a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java index aac73db..7fac539 100644 --- a/client/src/main/java/envoy/client/ui/StatusTrayIcon.java +++ b/client/src/main/java/envoy/client/ui/StatusTrayIcon.java @@ -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 *
    *
  • Changing the user status
  • *
  • Logging out
  • @@ -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();