import SwiftUI struct MessageRow: View { let message: Message let senderName: String let isSelf: Bool let replyToName: String? let canDMSender: Bool let onReply: () -> Void let onReact: (String) -> Void let onDMSender: () -> Void static let reactionEmojis = ["👋", "👍", "❤️", "😂", "😮", "😢", "🙏"] private static let timeFmt: DateFormatter = { let f = DateFormatter() f.dateFormat = "HH:mm" return f }() var body: some View { HStack { if isSelf { Spacer(minLength: 40) } VStack(alignment: isSelf ? .trailing : .leading, spacing: 2) { header bubble .contextMenu { menuContent } } if !isSelf { Spacer(minLength: 40) } } } private var header: some View { HStack(spacing: 6) { if !isSelf { Text(senderName) .font(.caption.weight(.semibold)) .foregroundStyle(.tint) } Text(Self.timeFmt.string(from: message.timestamp)) .font(.caption2) .foregroundStyle(.secondary) if isSelf { Text(senderName) .font(.caption.weight(.semibold)) .foregroundStyle(.tint) } } } private var bubble: some View { HStack(alignment: .bottom, spacing: 4) { VStack(alignment: .leading, spacing: 3) { if let name = replyToName { HStack(spacing: 3) { Image(systemName: "arrowshape.turn.up.left.fill") .font(.caption2) Text(name).font(.caption.weight(.semibold)) } .foregroundStyle(isSelf ? Color.white.opacity(0.85) : Color.secondary) } Text(message.text ?? "") .font(.body) .foregroundStyle(isSelf ? .white : .primary) } .padding(.horizontal, 12) .padding(.vertical, 8) .background( RoundedRectangle(cornerRadius: 14, style: .continuous) .fill(isSelf ? AnyShapeStyle(Color.accentColor) : AnyShapeStyle(Color(.systemGray5))) ) if isSelf, message.delivery != .none { Image(systemName: message.delivery.icon) .font(.caption2) .foregroundStyle(deliveryColor) } } } @ViewBuilder private var menuContent: some View { Menu("React", systemImage: "face.smiling") { ForEach(Self.reactionEmojis, id: \.self) { e in Button { onReact(e) } label: { Text(e) } } } Button { onReply() } label: { Label("Reply", systemImage: "arrowshape.turn.up.left") } if canDMSender { Button { onDMSender() } label: { Label("Message \(senderName)", systemImage: "bubble.left.and.bubble.right") } } } private var deliveryColor: Color { switch message.delivery { case .confirmed: return .green case .failed: return .red case .pending: return .gray case .none: return .clear } } }