mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 20:09:04 +00:00
Make timeline reactions and reaction senders identifiable
This commit is contained in:
committed by
Stefan Ceriu
parent
9bb177bff5
commit
2a33ee1f63
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user