Display Current User Status and Unread Message Amount in Status Tray Icon #103
@ -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;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
Reference in New Issue
Block a user