Make timeline reactions and reaction senders identifiable

This commit is contained in:
Stefan Ceriu
2024-01-03 17:41:25 +02:00
committed by Stefan Ceriu
parent 9bb177bff5
commit 2a33ee1f63
7 changed files with 24 additions and 21 deletions
@@ -26,7 +26,7 @@ extension AggregatedReaction {
private static func mockReaction(key: String, senderIDs: [String]) -> AggregatedReaction { private static func mockReaction(key: String, senderIDs: [String]) -> AggregatedReaction {
let senders = senderIDs let senders = senderIDs
.map { id in .map { id in
ReactionSender(senderID: id, timestamp: Date(timeIntervalSinceReferenceDate: 0)) ReactionSender(id: id, timestamp: Date(timeIntervalSinceReferenceDate: 0))
} }
return AggregatedReaction(accountOwnerID: alice, key: key, senders: senders) return AggregatedReaction(accountOwnerID: alice, key: key, senders: senders)
} }
@@ -37,11 +37,10 @@ struct ReactionsSummaryView: View {
ScrollView(.horizontal, showsIndicators: false) { ScrollView(.horizontal, showsIndicators: false) {
ScrollViewReader { scrollView in ScrollViewReader { scrollView in
HStack(spacing: 8) { HStack(spacing: 8) {
ForEach(reactions, id: \.self) { reaction in ForEach(reactions) { reaction in
ReactionSummaryButton(reaction: reaction, highlighted: selectedReactionKey == reaction.key) { key in ReactionSummaryButton(reaction: reaction, highlighted: selectedReactionKey == reaction.key) { key in
selectedReactionKey = key selectedReactionKey = key
} }
.id(reaction.key)
} }
} }
.padding(.horizontal, 20) .padding(.horizontal, 20)
@@ -61,11 +60,11 @@ struct ReactionsSummaryView: View {
private var sendersList: some View { private var sendersList: some View {
TabView(selection: $selectedReactionKey) { TabView(selection: $selectedReactionKey) {
ForEach(reactions, id: \.self) { reaction in ForEach(reactions) { reaction in
ScrollView { ScrollView {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
ForEach(reaction.senders, id: \.self) { sender in ForEach(reaction.senders) { sender in
ReactionSummarySenderView(sender: sender, member: members[sender.senderID], imageProvider: imageProvider) ReactionSummarySenderView(sender: sender, member: members[sender.id], imageProvider: imageProvider)
.padding(.horizontal, 16) .padding(.horizontal, 16)
} }
} }
@@ -114,14 +113,14 @@ private struct ReactionSummarySenderView: View {
let imageProvider: ImageProviderProtocol? let imageProvider: ImageProviderProtocol?
var displayName: String { var displayName: String {
member?.displayName ?? sender.senderID member?.displayName ?? sender.id
} }
var body: some View { var body: some View {
HStack(spacing: 8) { HStack(spacing: 8) {
LoadableAvatarImage(url: member?.avatarURL, LoadableAvatarImage(url: member?.avatarURL,
name: displayName, name: displayName,
contentID: sender.senderID, contentID: sender.id,
avatarSize: .user(on: .timeline), avatarSize: .user(on: .timeline),
imageProvider: imageProvider) imageProvider: imageProvider)
@@ -134,7 +133,7 @@ private struct ReactionSummarySenderView: View {
.font(.compound.bodyXS) .font(.compound.bodyXS)
.foregroundColor(.compound.textSecondary) .foregroundColor(.compound.textSecondary)
} }
Text(sender.senderID) Text(sender.id)
.font(.compound.bodySM) .font(.compound.bodySM)
.foregroundColor(.compound.textSecondary) .foregroundColor(.compound.textSecondary)
} }
@@ -51,7 +51,7 @@ struct TimelineReactionsView: View {
var body: some View { var body: some View {
layout { layout {
ForEach(reactions, id: \.self) { reaction in ForEach(reactions) { reaction in
TimelineReactionButton(reaction: reaction) { key in TimelineReactionButton(reaction: reaction) { key in
feedbackGenerator.impactOccurred() feedbackGenerator.impactOccurred()
context.send(viewAction: .toggleReaction(key: key, itemID: itemID)) context.send(viewAction: .toggleReaction(key: key, itemID: itemID))
@@ -38,7 +38,7 @@ enum RoomTimelineItemFixtures {
sender: .init(id: "", displayName: "Helena"), sender: .init(id: "", displayName: "Helena"),
content: .init(body: "Let’s get lunch soon! New salad place opened up 🥗. When are y’all free? 🤗"), content: .init(body: "Let’s get lunch soon! New salad place opened up 🥗. When are y’all free? 🤗"),
properties: RoomTimelineItemProperties(reactions: [ properties: RoomTimelineItemProperties(reactions: [
AggregatedReaction(accountOwnerID: "me", key: "🙌", senders: [ReactionSender(senderID: "me", timestamp: Date())]) AggregatedReaction(accountOwnerID: "me", key: "🙌", senders: [ReactionSender(id: "me", timestamp: Date())])
])), ])),
TextRoomTimelineItem(id: .random, TextRoomTimelineItem(id: .random,
timestamp: "10:11 AM", timestamp: "10:11 AM",
@@ -49,13 +49,13 @@ enum RoomTimelineItemFixtures {
sender: .init(id: "", displayName: "Helena"), sender: .init(id: "", displayName: "Helena"),
content: .init(body: "I can be around on Wednesday. How about some 🌮 instead? Like https://www.tortilla.co.uk/"), content: .init(body: "I can be around on Wednesday. How about some 🌮 instead? Like https://www.tortilla.co.uk/"),
properties: RoomTimelineItemProperties(reactions: [ properties: RoomTimelineItemProperties(reactions: [
AggregatedReaction(accountOwnerID: "me", key: "🙏", senders: [ReactionSender(senderID: "helena", timestamp: Date())]), AggregatedReaction(accountOwnerID: "me", key: "🙏", senders: [ReactionSender(id: "helena", timestamp: Date())]),
AggregatedReaction(accountOwnerID: "me", AggregatedReaction(accountOwnerID: "me",
key: "🙌", key: "🙌",
senders: [ senders: [
ReactionSender(senderID: "me", timestamp: Date()), ReactionSender(id: "me", timestamp: Date()),
ReactionSender(senderID: "helena", timestamp: Date()), ReactionSender(id: "helena", timestamp: Date()),
ReactionSender(senderID: "jacob", timestamp: Date()) ReactionSender(id: "jacob", timestamp: Date())
]) ])
])), ])),
SeparatorRoomTimelineItem(id: .init(timelineID: "Today"), text: "Today"), SeparatorRoomTimelineItem(id: .init(timelineID: "Today"), text: "Today"),
@@ -17,12 +17,16 @@
import Foundation import Foundation
/// Represents all reactions of the same type for a single event. /// Represents all reactions of the same type for a single event.
struct AggregatedReaction: Hashable { struct AggregatedReaction: Hashable, Identifiable {
/// Length at which we ellipsize a reaction key for display /// Length at which we ellipsize a reaction key for display
/// Reactions can be free text, so we need to limit the length /// Reactions can be free text, so we need to limit the length
/// displayed on screen. /// displayed on screen.
private static let maxDisplayChars = 16 private static let maxDisplayChars = 16
var id: String {
key
}
/// The id of the account owner /// The id of the account owner
let accountOwnerID: String let accountOwnerID: String
/// The reaction that was sent. /// The reaction that was sent.
@@ -32,9 +36,9 @@ struct AggregatedReaction: Hashable {
} }
/// Details of who sent the reaction /// Details of who sent the reaction
struct ReactionSender: Hashable { struct ReactionSender: Hashable, Identifiable {
/// The id of the user who sent the reaction /// The id of the user who sent the reaction
let senderID: String let id: String
/// The time that the reaction was received on the original homeserver /// The time that the reaction was received on the original homeserver
let timestamp: Date let timestamp: Date
} }
@@ -47,7 +51,7 @@ extension AggregatedReaction {
/// Whether to highlight the reaction, indicating that the current user sent this reaction. /// Whether to highlight the reaction, indicating that the current user sent this reaction.
var isHighlighted: Bool { var isHighlighted: Bool {
senders.contains(where: { $0.senderID == accountOwnerID }) senders.contains(where: { $0.id == accountOwnerID })
} }
/// The key to be displayed on screen. See `maxDisplayChars`. /// The key to be displayed on screen. See `maxDisplayChars`.
@@ -418,7 +418,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
reactions.map { reaction in reactions.map { reaction in
let senders = reaction.senders let senders = reaction.senders
.map { senderData in .map { senderData in
ReactionSender(senderID: senderData.senderId, timestamp: Date(timeIntervalSince1970: TimeInterval(senderData.timestamp / 1000))) ReactionSender(id: senderData.senderId, timestamp: Date(timeIntervalSince1970: TimeInterval(senderData.timestamp / 1000)))
} }
.sorted { a, b in .sorted { a, b in
// Sort reactions within an aggregation by timestamp descending. // Sort reactions within an aggregation by timestamp descending.
@@ -619,7 +619,7 @@ class RoomScreenViewModelTests: XCTestCase {
private extension TextRoomTimelineItem { private extension TextRoomTimelineItem {
init(text: String, sender: String, addReactions: Bool = false, addReadReceipts: [ReadReceipt] = []) { init(text: String, sender: String, addReactions: Bool = false, addReadReceipts: [ReadReceipt] = []) {
let reactions = addReactions ? [AggregatedReaction(accountOwnerID: "bob", key: "🦄", senders: [ReactionSender(senderID: sender, timestamp: Date())])] : [] let reactions = addReactions ? [AggregatedReaction(accountOwnerID: "bob", key: "🦄", senders: [ReactionSender(id: sender, timestamp: Date())])] : []
self.init(id: .random, self.init(id: .random,
timestamp: "10:47 am", timestamp: "10:47 am",
isOutgoing: sender == "bob", isOutgoing: sender == "bob",