Skip to content

Commit

Permalink
feat(view): add unread message indicator to channels
Browse files Browse the repository at this point in the history
  • Loading branch information
Silthus committed Nov 13, 2021
1 parent f32b8b7 commit bf720d4
Show file tree
Hide file tree
Showing 14 changed files with 129 additions and 27 deletions.
2 changes: 2 additions & 0 deletions src/main/java/net/silthus/chat/ChatTarget.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,7 @@ default Message sendMessage(String message) {

void unsubscribe(@NonNull Conversation conversation);

Collection<Message> getUnreadMessages(Conversation conversation);

void setActiveConversation(Conversation conversation);
}
2 changes: 2 additions & 0 deletions src/main/java/net/silthus/chat/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ public static class View {
public static final TextColor INFO_COLOR = NamedTextColor.GRAY;
public static final TextColor COMMAND = NamedTextColor.AQUA;
public static final TextColor ACTIVE_COLOR = NamedTextColor.GREEN;
public static final TextColor UNREAD_COLOR = NamedTextColor.WHITE;
public static final TextColor UNREAD_COUNT_COLOR = NamedTextColor.RED;
public static final TextColor INACTIVE_COLOR = NamedTextColor.GRAY;
public static final TextDecoration ACTIVE_DECORATION = TextDecoration.UNDERLINED;

Expand Down
4 changes: 1 addition & 3 deletions src/main/java/net/silthus/chat/conversations/Channel.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ public Message sendMessage(String message) {
}

@Override
public void sendMessage(Message message) {
if (alreadyProcessed(message)) return;

protected void processMessage(Message message) {
getScopedTargets(message).forEach(target -> target.sendMessage(message));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ public DirectConversation(UUID id, String name, Component displayName, Collectio
}

@Override
public void sendMessage(Message message) {
if (alreadyProcessed(message)) return;

protected void processMessage(Message message) {
getTargets().stream()
.filter(target -> !target.getConversations().contains(this))
.forEach(target -> target.setActiveConversation(this));
Expand Down
30 changes: 23 additions & 7 deletions src/main/java/net/silthus/chat/identities/AbstractChatTarget.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public abstract class AbstractChatTarget extends AbstractIdentity implements Cha

private final Stack<Message> receivedMessages = new Stack<>();
private final Set<Conversation> conversations = new HashSet<>();
private final Map<Conversation, Set<Message>> unreadMessages = new HashMap<>();
@Getter
private Conversation activeConversation;

Expand All @@ -44,6 +45,16 @@ protected AbstractChatTarget(String name) {
super(name);
}

@Override
public void sendMessage(Message message) {
if (getReceivedMessages().contains(message)) return;
addReceivedMessage(message);

processMessage(message);
}

protected abstract void processMessage(Message message);

@Override
public Message getLastReceivedMessage() {
if (receivedMessages.isEmpty()) return null;
Expand All @@ -55,14 +66,24 @@ public Collection<Message> getReceivedMessages() {
return List.copyOf(receivedMessages);
}

@Override
public Collection<Message> getUnreadMessages(Conversation conversation) {
return List.copyOf(unreadMessages.getOrDefault(conversation, new HashSet<>()));
}

protected void addReceivedMessage(Message lastMessage) {
this.receivedMessages.push(lastMessage);
if (lastMessage.getConversation() != null && !lastMessage.getConversation().equals(getActiveConversation())) {
unreadMessages.computeIfAbsent(lastMessage.getConversation(), conversation -> new HashSet<>()).add(lastMessage);
}
}

public void setActiveConversation(Conversation conversation) {
this.activeConversation = conversation;
if (conversation != null)
if (conversation != null) {
subscribe(conversation);
unreadMessages.remove(conversation);
}
}

public Collection<Conversation> getConversations() {
Expand All @@ -76,14 +97,9 @@ public void subscribe(@NonNull Conversation conversation) {

public void unsubscribe(@NonNull Conversation conversation) {
conversation.removeTarget(this);
unreadMessages.remove(conversation);
conversations.removeIf(existingConversation -> existingConversation.equals(conversation));
if (conversation.equals(activeConversation))
setActiveConversation(getConversations().stream().findFirst().orElse(null));
}

protected boolean alreadyProcessed(Message message) {
if (getReceivedMessages().contains(message)) return true;
addReceivedMessage(message);
return false;
}
}
4 changes: 1 addition & 3 deletions src/main/java/net/silthus/chat/identities/Chatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,7 @@ void onPlayerChat(AsyncChatEvent event) {
}

@Override
public void sendMessage(Message message) {
if (alreadyProcessed(message)) return;

protected void processMessage(Message message) {
getPlayer().ifPresentOrElse(
view::sendTo,
() -> SChat.instance().getBungeecord().sendMessage(message)
Expand Down
4 changes: 1 addition & 3 deletions src/main/java/net/silthus/chat/identities/Console.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,7 @@ private Console(ConsoleConfig config) {
}

@Override
public void sendMessage(Message message) {
if (alreadyProcessed(message)) return;

protected void processMessage(Message message) {
Bukkit.getConsoleSender().sendMessage(message.formatted());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ public NilChatIdentity() {
}

@Override
public void sendMessage(Message message) {
addReceivedMessage(message);
protected void processMessage(Message message) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ public BungeeCord(SChat plugin) {
}

@Override
public void sendMessage(Message message) {
if (alreadyProcessed(message)) return;

protected void processMessage(Message message) {
sendPluginMessage(forwardToAllServers(SEND_MESSAGE), json(new MessageDto(message)));
}

Expand Down
10 changes: 10 additions & 0 deletions src/main/java/net/silthus/chat/renderer/FontInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,16 @@ public enum FontInfo {
NUM_8('8', 5),
NUM_9('9', 5),
NUM_0('0', 5),
SMALL_NUM_1('\u2081', 4), // ₁
SMALL_NUM_2('\u2082', 4), // ₂
SMALL_NUM_3('\u2083', 4), // ₃
SMALL_NUM_4('\u2084', 4), // ₄
SMALL_NUM_5('\u2085', 4), // ₅
SMALL_NUM_6('\u2086', 4), // ₆
SMALL_NUM_7('\u2087', 4), // ₇
SMALL_NUM_8('\u2088', 4), // ₈
SMALL_NUM_9('\u2089', 4), // ₉
SMALL_NUM_0('\u2080', 4), // ₀
EXCLAMATION_POINT('!', 1),
AT_SYMBOL('@', 6),
NUM_SIGN('#', 5),
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/net/silthus/chat/renderer/TabbedMessageRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static net.kyori.adventure.text.Component.*;
Expand Down Expand Up @@ -115,11 +116,35 @@ private Component conversationName(View view, Conversation conversation, boolean
.clickEvent(clickEvent(ClickEvent.Action.RUN_COMMAND, JOIN_CONVERSATION.apply(conversation)));
if (isActive)
conversationName = conversationName.color(ACTIVE_COLOR).decorate(ACTIVE_DECORATION);
else if (view.unreadMessageCount(conversation) > 0)
conversationName = conversationName.color(UNREAD_COLOR).append(smallNumber(view.unreadMessageCount(conversation)).color(UNREAD_COUNT_COLOR));
else
conversationName = conversationName.color(INACTIVE_COLOR);
return conversationName;
}

private final Map<Integer, Character> numberMap = Map.of(
0, '₀',
1, '₁',
2, '₂',
3, '₃',
4, '₄',
5, '₅',
6, '₆',
7, '₇',
8, '₈',
9, '₉'
);

private Component smallNumber(int number) {
StringBuilder str = new StringBuilder();
while (number > 0) {
str.insert(0, numberMap.get(number % 10));
number = number / 10;
}
return Component.text(str.toString());
}

private TextReplacementConfig playerName(Chatter chatter) {
return TextReplacementConfig.builder()
.match("<player_name>").replacement(chatter.getDisplayName()).build();
Expand Down
10 changes: 7 additions & 3 deletions src/main/java/net/silthus/chat/renderer/View.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,15 @@
import net.silthus.chat.conversations.DirectConversation;
import net.silthus.chat.identities.Chatter;

import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.*;
import java.util.stream.Stream;

@Data
@Accessors(fluent = true)
public class View {

private final Chatter chatter;
private final Map<UUID, Set<Message>> unreadMessages = new HashMap<>();
private MessageRenderer renderer = MessageRenderer.TABBED;

public View(@NonNull Chatter chatter, @NonNull MessageRenderer renderer) {
Expand Down Expand Up @@ -80,6 +79,11 @@ public Optional<Conversation> activeConversation() {
return Optional.ofNullable(chatter().getActiveConversation());
}

public int unreadMessageCount(Conversation conversation) {
if (conversation == null) return 0;
return chatter().getUnreadMessages(conversation).size();
}

private Stream<Message> getSystemMessages() {
if (isDirectConversation())
return Stream.empty();
Expand Down
41 changes: 41 additions & 0 deletions src/test/java/net/silthus/chat/identities/ChatterTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,47 @@ void view_containsTheLastMessagesThePlayerSaw() {
assertThat(view.messages()).contains(message);
}

@Nested
class UnreadMessages {

private Channel test;
private Channel foo;

@BeforeEach
void setUp() {
test = createChannel("test");
foo = createChannel("foo");
chatter.setActiveConversation(test);
chatter.subscribe(foo);
}

@Test
void containsUnreadMessages() {
final Message message = foo.sendMessage("hi");

assertThat(chatter.getUnreadMessages(foo)).hasSize(1).contains(message);
assertThat(chatter.getUnreadMessages(test)).isEmpty();
}

@Test
void clearsUnreadMessages_onConversationActive() {
final Message message = foo.sendMessage("hi");

assertThat(chatter.getUnreadMessages(foo)).hasSize(1).contains(message);
chatter.setActiveConversation(foo);
assertThat(chatter.getUnreadMessages(foo)).isEmpty();
}

@Test
void unsubscribe_removesUnreadMessages() {

final Message message = foo.sendMessage("hi");
assertThat(chatter.getUnreadMessages(foo)).hasSize(1).contains(message);
chatter.unsubscribe(foo);
assertThat(chatter.getUnreadMessages(foo)).isEmpty();
}
}

@Nested
@DisplayName("direct messaging")
class DirectMessages {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,19 @@ void supports_channelName_placeholders() {
assertThat(text).contains(toText(chatter.getDisplayName()));
}

@Test
void renders_unreadMessageCountNearChannelName() {
Channel test = createChannel("test");
Channel foo = createChannel("foo");
chatter.setActiveConversation(test);
chatter.subscribe(foo);

foo.sendMessage("test");

final String text = toText(view.conversationTabs(new View(chatter)));
assertThat(text).contains("foo&c\u2081");
}

private void addChannels() {
chatter.subscribe(ChatTarget.channel("test"));
chatter.subscribe(ChatTarget.channel("foobar"));
Expand Down

0 comments on commit bf720d4

Please sign in to comment.