From 1def37c5c93f447d38f2f0047db9c2e68675e72a Mon Sep 17 00:00:00 2001 From: ln-001 <160061265+ln-001@users.noreply.github.com> Date: Sun, 30 Aug 2026 10:13:32 +0200 Subject: [PATCH 1/4] Show up to 3 of te actual reaction emojis stacked together on the message. If more than 3 are present, show a buton with the number of hidden emojis. Clicking on that will reveal them. --- .../Views/Message/MessageReactionBadges.swift | 39 ++++++++++++++++++- Relay/Views/Message/MessageView.swift | 1 + 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Relay/Views/Message/MessageReactionBadges.swift b/Relay/Views/Message/MessageReactionBadges.swift index c59a048..7bc837d 100644 --- a/Relay/Views/Message/MessageReactionBadges.swift +++ b/Relay/Views/Message/MessageReactionBadges.swift @@ -54,8 +54,8 @@ struct MessageReactionBadges: View { expandedContent .transition(expandTransition) } else { - expandButton - .background(Circle().fill(.ultraThickMaterial)) + collapsedPreview + .background(Capsule().fill(.ultraThickMaterial)) .transition(expandTransition) } } @@ -120,6 +120,36 @@ struct MessageReactionBadges: View { .scale(scale: 0.5, anchor: isOutgoing ? .topLeading : .topTrailing) .combined(with: .opacity) } + + /// Collapsed state: shows up to 3 of the actual reaction emojis stacked together + private var collapsedPreview: some View { + Group { + if reactions.isEmpty { + expandButton + } else { + Button(action: { isExpanded.toggle() }) { + HStack(spacing: 2) { + ForEach(reactions.prefix(3)) { reaction in + Text(reaction.key) + .font(.system(size: 12)) + } + if reactions.count > 3 { + Text("+\(reactions.count - 3)") + .font(.system(size: 8, weight: .bold)) + .foregroundStyle(.white) + .padding(.horizontal, 3) + .padding(.vertical, 1) + .background(.secondary, in: Capsule()) + } + } + .padding(.horizontal, 8) + .frame(height: Self.badgeSize) + .contentShape(Capsule()) + } + .buttonStyle(.plain) + } + } + } } /// A single reaction badge: emoji in a small circle with optional count and border. @@ -129,6 +159,7 @@ struct MessageReactionBadges: View { /// their own color. Otherwise a neutral gray is used. private struct ReactionBadge: View { let reaction: TimelineMessage.ReactionGroup + let author: TimlineMessage.ReactionGroup let coloredBubbles: Bool let onToggle: () -> Void @@ -164,6 +195,8 @@ private struct ReactionBadge: View { } .buttonStyle(.plain) } + + } // MARK: - Previews @@ -248,3 +281,5 @@ private let sampleReactions: [TimelineMessage.ReactionGroup] = [ .padding(40) } + + diff --git a/Relay/Views/Message/MessageView.swift b/Relay/Views/Message/MessageView.swift index e8ce54c..50c6ee2 100644 --- a/Relay/Views/Message/MessageView.swift +++ b/Relay/Views/Message/MessageView.swift @@ -149,6 +149,7 @@ struct MessageView: View { .overlay(alignment: message.isOutgoing ? .topLeading : .topTrailing) { if !message.reactions.isEmpty { MessageReactionBadges( + reactions: message.reactions, isOutgoing: message.isOutgoing, coloredBubbles: coloredBubbles, From 939d625f77f43148946d15c26e51710ebd899932 Mon Sep 17 00:00:00 2001 From: ln-001 <160061265+ln-001@users.noreply.github.com> Date: Sun, 30 Aug 2026 14:03:34 +0200 Subject: [PATCH 2/4] Show the authors of each reaction up to (maxShown), if there are more than maxShown then the popover will show a "and X more" message at the end. --- .../Views/Message/MessageReactionBadges.swift | 69 +++++++++++++------ 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/Relay/Views/Message/MessageReactionBadges.swift b/Relay/Views/Message/MessageReactionBadges.swift index 7bc837d..f985b17 100644 --- a/Relay/Views/Message/MessageReactionBadges.swift +++ b/Relay/Views/Message/MessageReactionBadges.swift @@ -159,9 +159,10 @@ struct MessageReactionBadges: View { /// their own color. Otherwise a neutral gray is used. private struct ReactionBadge: View { let reaction: TimelineMessage.ReactionGroup - let author: TimlineMessage.ReactionGroup let coloredBubbles: Bool let onToggle: () -> Void + @State private var isHovering = false + @State private var showSheet = false private static let size: CGFloat = 22 @@ -173,32 +174,60 @@ private struct ReactionBadge: View { } var body: some View { - Button(action: onToggle) { - ZStack { - Circle() - .fill(reaction.highlightedByCurrentUser ? fillColor : .clear) - .frame(width: Self.size, height: Self.size) - - Text(reaction.key) - .font(.system(size: 12)) + HStack(spacing: 4) { + Button(action: onToggle) { + Text(reaction.key) + .font(.system(size: 13)) + } + .buttonStyle(.plain) + Text("\(reaction.count)") + .font(.system(size: 11, weight: .medium)) + .foregroundStyle(reaction.highlightedByCurrentUser ? .white : .secondary) + .onHover{ + hovering in isHovering = hovering + } + .popover(isPresented: $isHovering, arrowEdge: .top) { + ReactionAuthors(reaction: reaction) + .padding(8) + } + } + .padding(.horizontal, 6) + .padding(.vertical, 3) + .background( + Capsule().fill(reaction.highlightedByCurrentUser ? fillColor : .secondary.opacity(0.15)) + ) } - .overlay(alignment: .topTrailing) { - if reaction.count > 1 { - Text("\(reaction.count)") - .font(.system(size: 8, weight: .bold)) - .foregroundStyle(.white) - .padding(.horizontal, 3) - .padding(.vertical, 1) - .background(.secondary, in: Capsule()) +} + +/// Shows the authors of each reaction up to maxShown. +private struct ReactionAuthors: View { + let reaction: TimelineMessage.ReactionGroup + let maxShown = 5 + var body: some View { + VStack(spacing: 2) { + if reaction.senderIDs.count <= maxShown { + ForEach(reaction.senderIDs, id: \.self) { senderID in + Text(senderID) + .font(.caption) + .foregroundStyle(.secondary) + } + } else { + ForEach(reaction.senderIDs.prefix(maxShown), id: \.self) { senderID in + Text(senderID) + .font(.caption) + .foregroundStyle(.secondary) } + Text("and \(reaction.senderIDs.count - maxShown) more") + .font(.caption) + .foregroundStyle(.tertiary) + .italic() } } - .buttonStyle(.plain) } - - } + + // MARK: - Previews private let sampleReactions: [TimelineMessage.ReactionGroup] = [ From de2914ba59561e402811b05206c6d55b5932f477 Mon Sep 17 00:00:00 2001 From: ln-001 <160061265+ln-001@users.noreply.github.com> Date: Mon, 31 Aug 2026 09:47:03 +0200 Subject: [PATCH 3/4] PR fixes --- .../Views/Message/MessageReactionBadges.swift | 26 ++++++------------- 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/Relay/Views/Message/MessageReactionBadges.swift b/Relay/Views/Message/MessageReactionBadges.swift index f985b17..02f45b8 100644 --- a/Relay/Views/Message/MessageReactionBadges.swift +++ b/Relay/Views/Message/MessageReactionBadges.swift @@ -162,7 +162,6 @@ private struct ReactionBadge: View { let coloredBubbles: Bool let onToggle: () -> Void @State private var isHovering = false - @State private var showSheet = false private static let size: CGFloat = 22 @@ -205,22 +204,16 @@ private struct ReactionAuthors: View { let maxShown = 5 var body: some View { VStack(spacing: 2) { - if reaction.senderIDs.count <= maxShown { - ForEach(reaction.senderIDs, id: \.self) { senderID in - Text(senderID) - .font(.caption) - .foregroundStyle(.secondary) - } - } else { - ForEach(reaction.senderIDs.prefix(maxShown), id: \.self) { senderID in - Text(senderID) + ForEach(reaction.senderIDs.prefix(maxShown), id:\.self){ + senderId in Text(senderId) + .font(.caption) + .foregroundStyle(.secondary) + if reaction.senderIDs.count > maxShown{ + Text("and \(reaction.senderIDs.count - maxShown) more") .font(.caption) - .foregroundStyle(.secondary) + .foregroundStyle(.tertiary) + .italic() } - Text("and \(reaction.senderIDs.count - maxShown) more") - .font(.caption) - .foregroundStyle(.tertiary) - .italic() } } } @@ -309,6 +302,3 @@ private let sampleReactions: [TimelineMessage.ReactionGroup] = [ } .padding(40) } - - - From 7d171943649457081ef080149ce8ad0b329027eb Mon Sep 17 00:00:00 2001 From: ln-001 <160061265+ln-001@users.noreply.github.com> Date: Mon, 31 Aug 2026 23:47:43 +0200 Subject: [PATCH 4/4] For each loop error fixed. --- Relay/Views/Message/MessageReactionBadges.swift | 6 ++---- Relay/Views/Message/MessageView.swift | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Relay/Views/Message/MessageReactionBadges.swift b/Relay/Views/Message/MessageReactionBadges.swift index 02f45b8..bf4f339 100644 --- a/Relay/Views/Message/MessageReactionBadges.swift +++ b/Relay/Views/Message/MessageReactionBadges.swift @@ -208,19 +208,17 @@ private struct ReactionAuthors: View { senderId in Text(senderId) .font(.caption) .foregroundStyle(.secondary) - if reaction.senderIDs.count > maxShown{ + } + if reaction.senderIDs.count > maxShown{ Text("and \(reaction.senderIDs.count - maxShown) more") .font(.caption) .foregroundStyle(.tertiary) .italic() - } } } } } - - // MARK: - Previews private let sampleReactions: [TimelineMessage.ReactionGroup] = [ diff --git a/Relay/Views/Message/MessageView.swift b/Relay/Views/Message/MessageView.swift index 50c6ee2..e8ce54c 100644 --- a/Relay/Views/Message/MessageView.swift +++ b/Relay/Views/Message/MessageView.swift @@ -149,7 +149,6 @@ struct MessageView: View { .overlay(alignment: message.isOutgoing ? .topLeading : .topTrailing) { if !message.reactions.isEmpty { MessageReactionBadges( - reactions: message.reactions, isOutgoing: message.isOutgoing, coloredBubbles: coloredBubbles,