Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 73 additions & 29 deletions Relay/Views/Message/MessageReactionBadges.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct MessageReactionBadges: View {

/// The overlay's offset from the bubble's corner.
private static let cornerOffsetX: CGFloat = 8
private static let cornerOffsetY: CGFloat = -11
private static let cornerOffsetY: CGFloat = -16

@State private var isExpanded = false

Expand All @@ -54,8 +54,8 @@ struct MessageReactionBadges: View {
expandedContent
.transition(expandTransition)
} else {
expandButton
.background(Circle().fill(.ultraThickMaterial))
collapsedPreview
.background(Capsule().fill(.ultraThickMaterial))
.transition(expandTransition)
}
}
Expand All @@ -81,7 +81,7 @@ struct MessageReactionBadges: View {
private var expandedRow: some View {
HStack(spacing: Self.expandedSpacing) {
if isOutgoing {
expandButton
collapseButton
}

ForEach(reactions) { reaction in
Expand All @@ -94,19 +94,17 @@ struct MessageReactionBadges: View {
}

if !isOutgoing {
expandButton
collapseButton
}
}
.padding(.vertical, 4)
.padding(.horizontal, 6)
}

/// The button that expands the control (filled smiley on a material circle)
/// and collapses it (plain xmark). Leading for outgoing messages, trailing
/// for incoming ones.
private var expandButton: some View {
/// The button that collapses an expanded row.
private var collapseButton: some View {
Button(action: { isExpanded.toggle() }) {
Image(systemName: isExpanded ? "xmark" : "face.smiling")
Image(systemName: "xmark")
.font(.system(size: 13))
.foregroundStyle(.primary)
.frame(width: Self.badgeSize, height: Self.badgeSize)
Expand All @@ -120,6 +118,30 @@ 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 {
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.
Expand All @@ -131,6 +153,7 @@ private struct ReactionBadge: View {
let reaction: TimelineMessage.ReactionGroup
let coloredBubbles: Bool
let onToggle: () -> Void
@State private var isHovering = false

private static let size: CGFloat = 22

Expand All @@ -142,27 +165,49 @@ 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) {
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(.tertiary)
.italic()
}
}
.buttonStyle(.plain)
}
}

Expand Down Expand Up @@ -247,4 +292,3 @@ private let sampleReactions: [TimelineMessage.ReactionGroup] = [
}
.padding(40)
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line too. Should this be deleted?

Loading