mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Bump the SDK and adopt the new MsgLike timeline item types (#4000)
* Bump the RustSDK to v25.04.09 * Adopt new MsgLike based timeline item structure. * Move the `replyDetails` and `isThreaded` to the `RoomTimelineItemProperties` * Restructure the TimelineItemFactory * Fix line length warning * Rename `msgLikeContent` to `messageLikeContent` wherever possible. * Move the `EventTimelineItem` mocks to the SDK mocks folder.
This commit is contained in:
@@ -17,7 +17,6 @@ enum RoomTimelineItemFixtures {
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "", displayName: "Jacob"),
|
||||
content: .init(body: "That looks so good!"),
|
||||
properties: RoomTimelineItemProperties(isEdited: true)),
|
||||
@@ -27,7 +26,6 @@ enum RoomTimelineItemFixtures {
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "", displayName: "Helena"),
|
||||
content: .init(body: "Let’s get lunch soon! New salad place opened up 🥗. When are y’all free? 🤗"),
|
||||
properties: RoomTimelineItemProperties(reactions: [
|
||||
@@ -39,7 +37,6 @@ enum RoomTimelineItemFixtures {
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "", displayName: "Helena"),
|
||||
content: .init(body: "I can be around on Wednesday. How about some 🌮 instead? Like https://www.tortilla.co.uk/"),
|
||||
properties: RoomTimelineItemProperties(reactions: [
|
||||
@@ -59,7 +56,6 @@ enum RoomTimelineItemFixtures {
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "", displayName: "Helena"),
|
||||
content: .init(body: "Wow, cool. Ok, lets go the usual place tomorrow?! Is that too soon? Here’s the menu, let me know what you want it’s on me!"),
|
||||
properties: RoomTimelineItemProperties(orderedReadReceipts: [ReadReceipt(userID: "alice", formattedTimestamp: nil)])),
|
||||
@@ -69,7 +65,6 @@ enum RoomTimelineItemFixtures {
|
||||
isOutgoing: true,
|
||||
isEditable: true,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "", displayName: "Bob"),
|
||||
content: .init(body: "And John's speech was amazing!")),
|
||||
TextRoomTimelineItem(id: .event(uniqueID: .init("RoomTimelineItemFixtures.default.5"),
|
||||
@@ -78,7 +73,6 @@ enum RoomTimelineItemFixtures {
|
||||
isOutgoing: true,
|
||||
isEditable: true,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "", displayName: "Bob"),
|
||||
content: .init(body: "New home office set up!"),
|
||||
properties: RoomTimelineItemProperties(reactions: AggregatedReaction.mockReactions,
|
||||
@@ -92,7 +86,6 @@ enum RoomTimelineItemFixtures {
|
||||
isOutgoing: false,
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "", displayName: "Helena"),
|
||||
content: .init(body: "",
|
||||
formattedBody: AttributedStringBuilder(mentionBuilder: MentionBuilder())
|
||||
@@ -276,7 +269,6 @@ private extension TextRoomTimelineItem {
|
||||
isOutgoing: senderDisplayName == "Alice",
|
||||
isEditable: false,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: "", displayName: senderDisplayName),
|
||||
content: .init(body: text))
|
||||
}
|
||||
@@ -295,7 +287,6 @@ private extension AudioRoomTimelineItem {
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: isOutgoing,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
|
||||
content: .init(filename: "audio.mp3",
|
||||
caption: caption,
|
||||
@@ -314,7 +305,6 @@ private extension FileRoomTimelineItem {
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: isOutgoing,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
|
||||
content: .init(filename: "file.pdf",
|
||||
caption: caption,
|
||||
@@ -332,7 +322,6 @@ private extension ImageRoomTimelineItem {
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: isOutgoing,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
|
||||
content: .init(filename: "image.jpg",
|
||||
caption: caption,
|
||||
@@ -349,7 +338,6 @@ private extension VideoRoomTimelineItem {
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: isOutgoing,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
|
||||
content: .init(filename: "video.mp4",
|
||||
caption: caption,
|
||||
@@ -366,7 +354,6 @@ private extension VoiceMessageRoomTimelineItem {
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: isOutgoing,
|
||||
canBeRepliedTo: true,
|
||||
isThreaded: false,
|
||||
sender: .init(id: isOutgoing ? "@alice:matrix.org" : "@bob:matrix.org"),
|
||||
content: .init(filename: "message.ogg",
|
||||
duration: 10,
|
||||
|
||||
@@ -493,7 +493,7 @@ class TimelineController: TimelineControllerProtocol {
|
||||
return
|
||||
}
|
||||
|
||||
switch timelineItem.replyDetails {
|
||||
switch timelineItem.properties.replyDetails {
|
||||
case .notLoaded:
|
||||
activeTimeline.fetchDetails(for: eventID)
|
||||
case .error:
|
||||
|
||||
+4
@@ -9,6 +9,10 @@ import Foundation
|
||||
|
||||
/// Properties of a matrix event that are common between all timeline items.
|
||||
struct RoomTimelineItemProperties: Hashable {
|
||||
// Information on the item this one replying to
|
||||
var replyDetails: TimelineItemReplyDetails?
|
||||
// Whether it's part of a thread or not
|
||||
var isThreaded = false
|
||||
/// Whether the item has been edited.
|
||||
var isEdited = false
|
||||
/// The aggregated reactions that have been sent for this item.
|
||||
|
||||
@@ -123,8 +123,6 @@ class EventTimelineItemProxy {
|
||||
avatarURL: nil)
|
||||
}
|
||||
}()
|
||||
|
||||
lazy var reactions = item.reactions
|
||||
|
||||
lazy var timestamp = Date(timeIntervalSince1970: TimeInterval(item.timestamp / 1000))
|
||||
|
||||
|
||||
-2
@@ -20,9 +20,7 @@ enum EventBasedMessageTimelineItemContentType: Hashable {
|
||||
}
|
||||
|
||||
protocol EventBasedMessageTimelineItemProtocol: EventBasedTimelineItemProtocol {
|
||||
var replyDetails: TimelineItemReplyDetails? { get }
|
||||
var contentType: EventBasedMessageTimelineItemContentType { get }
|
||||
var isThreaded: Bool { get }
|
||||
}
|
||||
|
||||
extension EventBasedMessageTimelineItemProtocol {
|
||||
|
||||
-3
@@ -13,14 +13,11 @@ struct AudioRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
let canBeRepliedTo: Bool
|
||||
let isThreaded: Bool
|
||||
var shouldBoost = false
|
||||
|
||||
let sender: TimelineItemSender
|
||||
|
||||
let content: AudioRoomTimelineItemContent
|
||||
|
||||
var replyDetails: TimelineItemReplyDetails?
|
||||
|
||||
var properties = RoomTimelineItemProperties()
|
||||
|
||||
|
||||
-3
@@ -13,14 +13,11 @@ struct EmoteRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
let canBeRepliedTo: Bool
|
||||
let isThreaded: Bool
|
||||
|
||||
let sender: TimelineItemSender
|
||||
|
||||
let content: EmoteRoomTimelineItemContent
|
||||
|
||||
var replyDetails: TimelineItemReplyDetails?
|
||||
|
||||
var properties = RoomTimelineItemProperties()
|
||||
|
||||
var body: String {
|
||||
|
||||
-3
@@ -14,15 +14,12 @@ struct FileRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
let canBeRepliedTo: Bool
|
||||
let isThreaded: Bool
|
||||
var shouldBoost = false
|
||||
|
||||
let sender: TimelineItemSender
|
||||
|
||||
let content: FileRoomTimelineItemContent
|
||||
|
||||
var replyDetails: TimelineItemReplyDetails?
|
||||
|
||||
var properties = RoomTimelineItemProperties()
|
||||
|
||||
var body: String {
|
||||
|
||||
-3
@@ -14,15 +14,12 @@ struct ImageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
let canBeRepliedTo: Bool
|
||||
let isThreaded: Bool
|
||||
var shouldBoost = false
|
||||
|
||||
let sender: TimelineItemSender
|
||||
|
||||
let content: ImageRoomTimelineItemContent
|
||||
|
||||
var replyDetails: TimelineItemReplyDetails?
|
||||
|
||||
var properties = RoomTimelineItemProperties()
|
||||
|
||||
var body: String {
|
||||
|
||||
-4
@@ -9,12 +9,10 @@ import Foundation
|
||||
|
||||
struct LocationRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
|
||||
let timestamp: Date
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
let canBeRepliedTo: Bool
|
||||
let isThreaded: Bool
|
||||
|
||||
let sender: TimelineItemSender
|
||||
|
||||
@@ -24,8 +22,6 @@ struct LocationRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatabl
|
||||
content.body
|
||||
}
|
||||
|
||||
var replyDetails: TimelineItemReplyDetails?
|
||||
|
||||
var properties = RoomTimelineItemProperties()
|
||||
|
||||
var contentType: EventBasedMessageTimelineItemContentType {
|
||||
|
||||
-3
@@ -13,14 +13,11 @@ struct NoticeRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
let canBeRepliedTo: Bool
|
||||
let isThreaded: Bool
|
||||
|
||||
let sender: TimelineItemSender
|
||||
|
||||
let content: NoticeRoomTimelineItemContent
|
||||
|
||||
var replyDetails: TimelineItemReplyDetails?
|
||||
|
||||
var properties = RoomTimelineItemProperties()
|
||||
|
||||
var body: String {
|
||||
|
||||
-4
@@ -9,20 +9,16 @@ import UIKit
|
||||
|
||||
struct TextRoomTimelineItem: TextBasedRoomTimelineItem, Equatable {
|
||||
let id: TimelineItemIdentifier
|
||||
|
||||
let timestamp: Date
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
let canBeRepliedTo: Bool
|
||||
let isThreaded: Bool
|
||||
var shouldBoost = false
|
||||
|
||||
let sender: TimelineItemSender
|
||||
|
||||
let content: TextRoomTimelineItemContent
|
||||
|
||||
var replyDetails: TimelineItemReplyDetails?
|
||||
|
||||
var properties = RoomTimelineItemProperties()
|
||||
|
||||
var body: String {
|
||||
|
||||
-3
@@ -14,15 +14,12 @@ struct VideoRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
let canBeRepliedTo: Bool
|
||||
let isThreaded: Bool
|
||||
var shouldBoost = false
|
||||
|
||||
let sender: TimelineItemSender
|
||||
|
||||
let content: VideoRoomTimelineItemContent
|
||||
|
||||
var replyDetails: TimelineItemReplyDetails?
|
||||
|
||||
var properties = RoomTimelineItemProperties()
|
||||
|
||||
var body: String {
|
||||
|
||||
+1
-3
@@ -13,12 +13,10 @@ struct VoiceMessageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equa
|
||||
let isOutgoing: Bool
|
||||
let isEditable: Bool
|
||||
let canBeRepliedTo: Bool
|
||||
let isThreaded: Bool
|
||||
|
||||
let sender: TimelineItemSender
|
||||
|
||||
let content: AudioRoomTimelineItemContent
|
||||
|
||||
var replyDetails: TimelineItemReplyDetails?
|
||||
|
||||
var properties = RoomTimelineItemProperties()
|
||||
|
||||
|
||||
@@ -28,18 +28,23 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
let isOutgoing = eventItemProxy.isOwn
|
||||
|
||||
switch eventItemProxy.content {
|
||||
case .unableToDecrypt(let encryptedMessage):
|
||||
return buildEncryptedTimelineItem(eventItemProxy, encryptedMessage, isOutgoing)
|
||||
case .redactedMessage:
|
||||
return buildRedactedTimelineItem(eventItemProxy, isOutgoing)
|
||||
case .sticker(let body, let imageInfo, let mediaSource):
|
||||
return buildStickerTimelineItem(eventItemProxy, body, imageInfo, mediaSource, isOutgoing)
|
||||
case .msgLike(let messageLikeContent):
|
||||
switch messageLikeContent.kind {
|
||||
case .message(let messageContent):
|
||||
return buildMessageTimelineItem(eventItemProxy, messageLikeContent, messageContent, isOutgoing)
|
||||
case .sticker(let body, let imageInfo, let mediaSource):
|
||||
return buildStickerTimelineItem(eventItemProxy, messageLikeContent, body, imageInfo, mediaSource, isOutgoing)
|
||||
case .poll(question: let question, kind: let kind, maxSelections: let maxSelections, answers: let answers, votes: let votes, endTime: let endTime, let edited):
|
||||
return buildPollTimelineItem(eventItemProxy, messageLikeContent, question, kind, maxSelections, answers, votes, endTime, isOutgoing, edited)
|
||||
case .redacted:
|
||||
return buildRedactedTimelineItem(eventItemProxy, messageLikeContent, isOutgoing)
|
||||
case .unableToDecrypt(let encryptedMessage):
|
||||
return buildEncryptedTimelineItem(eventItemProxy, messageLikeContent, encryptedMessage, isOutgoing)
|
||||
}
|
||||
case .failedToParseMessageLike(let eventType, let error):
|
||||
return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing)
|
||||
case .failedToParseState(let eventType, _, let error):
|
||||
return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing)
|
||||
case .message(let messageContent):
|
||||
return buildMessageTimelineItem(eventItemProxy, messageContent, isOutgoing)
|
||||
case .state(_, let content):
|
||||
if isDM, content == .roomCreate {
|
||||
return nil
|
||||
@@ -57,8 +62,6 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
avatarURLString: avatarUrl,
|
||||
previousAvatarURLString: prevAvatarUrl,
|
||||
isOutgoing: isOutgoing)
|
||||
case .poll(question: let question, kind: let kind, maxSelections: let maxSelections, answers: let answers, votes: let votes, endTime: let endTime, let edited):
|
||||
return buildPollTimelineItem(question, kind, maxSelections, answers, votes, endTime, eventItemProxy, isOutgoing, edited)
|
||||
case .callInvite:
|
||||
return buildCallInviteTimelineItem(for: eventItemProxy)
|
||||
case .callNotify:
|
||||
@@ -66,52 +69,234 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Message Events
|
||||
// MARK: - MsgLike Events
|
||||
|
||||
private func buildMessageTimelineItem(_ eventItemProxy: EventTimelineItemProxy, _ messageContent: MessageContent, _ isOutgoing: Bool) -> RoomTimelineItemProtocol? {
|
||||
private func buildMessageTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ messageContent: MessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol? {
|
||||
switch messageContent.msgType {
|
||||
case .text(content: let textMessageContent):
|
||||
return buildTextTimelineItem(for: eventItemProxy, messageContent, textMessageContent, isOutgoing)
|
||||
return buildTextTimelineItem(for: eventItemProxy, messageLikeContent, messageContent, textMessageContent, isOutgoing)
|
||||
case .image(content: let imageMessageContent):
|
||||
return buildImageTimelineItem(for: eventItemProxy, messageContent, imageMessageContent, isOutgoing)
|
||||
return buildImageTimelineItem(for: eventItemProxy, messageLikeContent, messageContent, imageMessageContent, isOutgoing)
|
||||
case .video(let videoMessageContent):
|
||||
return buildVideoTimelineItem(for: eventItemProxy, messageContent, videoMessageContent, isOutgoing)
|
||||
return buildVideoTimelineItem(for: eventItemProxy, messageLikeContent, messageContent, videoMessageContent, isOutgoing)
|
||||
case .file(let fileMessageContent):
|
||||
return buildFileTimelineItem(for: eventItemProxy, messageContent, fileMessageContent, isOutgoing)
|
||||
return buildFileTimelineItem(for: eventItemProxy, messageLikeContent, messageContent, fileMessageContent, isOutgoing)
|
||||
case .notice(content: let noticeMessageContent):
|
||||
return buildNoticeTimelineItem(for: eventItemProxy, messageContent, noticeMessageContent, isOutgoing)
|
||||
return buildNoticeTimelineItem(for: eventItemProxy, messageLikeContent, messageContent, noticeMessageContent, isOutgoing)
|
||||
case .emote(content: let emoteMessageContent):
|
||||
return buildEmoteTimelineItem(for: eventItemProxy, messageContent, emoteMessageContent, isOutgoing)
|
||||
return buildEmoteTimelineItem(for: eventItemProxy, messageLikeContent, messageContent, emoteMessageContent, isOutgoing)
|
||||
case .audio(let audioMessageContent):
|
||||
if audioMessageContent.voice != nil {
|
||||
return buildVoiceTimelineItem(for: eventItemProxy, messageContent, audioMessageContent, isOutgoing)
|
||||
return buildVoiceTimelineItem(for: eventItemProxy, messageLikeContent, messageContent, audioMessageContent, isOutgoing)
|
||||
} else {
|
||||
return buildAudioTimelineItem(for: eventItemProxy, messageContent, audioMessageContent, isOutgoing)
|
||||
return buildAudioTimelineItem(for: eventItemProxy, messageLikeContent, messageContent, audioMessageContent, isOutgoing)
|
||||
}
|
||||
case .location(let locationMessageContent):
|
||||
return buildLocationTimelineItem(for: eventItemProxy, messageContent, locationMessageContent, isOutgoing)
|
||||
return buildLocationTimelineItem(for: eventItemProxy, messageLikeContent, messageContent, locationMessageContent, isOutgoing)
|
||||
case .other:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func buildUnsupportedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ eventType: String,
|
||||
_ error: String,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
UnsupportedRoomTimelineItem(id: eventItemProxy.id,
|
||||
body: L10n.commonUnsupportedEvent,
|
||||
eventType: eventType,
|
||||
error: error,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender,
|
||||
properties: RoomTimelineItemProperties())
|
||||
private func buildTextTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ messageContent: MessageContent,
|
||||
_ textMessageContent: TextMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
TextRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
shouldBoost: eventItemProxy.shouldBoost,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildTextTimelineItemContent(textMessageContent),
|
||||
properties: .init(replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil,
|
||||
isEdited: messageContent.isEdited,
|
||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ messageContent: MessageContent,
|
||||
_ imageMessageContent: ImageMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
ImageRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
shouldBoost: eventItemProxy.shouldBoost,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildImageTimelineItemContent(imageMessageContent),
|
||||
properties: .init(replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil,
|
||||
isEdited: messageContent.isEdited,
|
||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ messageContent: MessageContent,
|
||||
_ videoMessageContent: VideoMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
VideoRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
shouldBoost: eventItemProxy.shouldBoost,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildVideoTimelineItemContent(videoMessageContent),
|
||||
properties: .init(replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil,
|
||||
isEdited: messageContent.isEdited,
|
||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ messageContent: MessageContent,
|
||||
_ audioMessageContent: AudioMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
AudioRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
shouldBoost: eventItemProxy.shouldBoost,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildAudioTimelineItemContent(audioMessageContent),
|
||||
properties: .init(replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil,
|
||||
isEdited: messageContent.isEdited,
|
||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildVoiceTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ messageContent: MessageContent,
|
||||
_ audioMessageContent: AudioMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
VoiceMessageRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildAudioTimelineItemContent(audioMessageContent),
|
||||
properties: .init(replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil,
|
||||
isEdited: messageContent.isEdited,
|
||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ messageContent: MessageContent,
|
||||
_ fileMessageContent: FileMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
FileRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
shouldBoost: eventItemProxy.shouldBoost,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildFileTimelineItemContent(fileMessageContent),
|
||||
properties: .init(replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil,
|
||||
isEdited: messageContent.isEdited,
|
||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildNoticeTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ messageContent: MessageContent,
|
||||
_ noticeMessageContent: NoticeMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
NoticeRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildNoticeTimelineItemContent(noticeMessageContent),
|
||||
properties: .init(replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil,
|
||||
isEdited: messageContent.isEdited,
|
||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildEmoteTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ messageContent: MessageContent,
|
||||
_ emoteMessageContent: EmoteMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
EmoteRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildEmoteTimelineItemContent(senderDisplayName: eventItemProxy.sender.displayName, senderID: eventItemProxy.sender.id, messageContent: emoteMessageContent),
|
||||
properties: .init(replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil,
|
||||
isEdited: messageContent.isEdited,
|
||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildLocationTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ messageContent: MessageContent,
|
||||
_ locationMessageContent: LocationContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
LocationRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildLocationTimelineItemContent(locationMessageContent),
|
||||
properties: .init(replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil,
|
||||
isEdited: messageContent.isEdited,
|
||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildStickerTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ body: String,
|
||||
_ info: MatrixRustSDK.ImageInfo,
|
||||
_ mediaSource: MediaSource,
|
||||
@@ -127,13 +312,89 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
sender: eventItemProxy.sender,
|
||||
imageInfo: imageInfo,
|
||||
blurhash: info.blurhash,
|
||||
properties: RoomTimelineItemProperties(reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: authenticity(eventItemProxy.shieldState)))
|
||||
properties: .init(replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil,
|
||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildPollTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ question: String,
|
||||
_ pollKind: PollKind,
|
||||
_ maxSelections: UInt64,
|
||||
_ answers: [PollAnswer],
|
||||
_ votes: [String: [String]],
|
||||
_ endTime: UInt64?,
|
||||
_ isOutgoing: Bool,
|
||||
_ edited: Bool) -> RoomTimelineItemProtocol {
|
||||
let allVotes = votes.reduce(0) { count, pair in
|
||||
count + pair.value.count
|
||||
}
|
||||
|
||||
let maxOptionVotes = votes.map(\.value.count).max()
|
||||
|
||||
let options = answers.map { answer in
|
||||
let optionVotesCount = votes[answer.id]?.count
|
||||
|
||||
return Poll.Option(id: answer.id,
|
||||
text: answer.text,
|
||||
votes: optionVotesCount ?? 0,
|
||||
allVotes: allVotes,
|
||||
isSelected: votes[answer.id]?.contains(userID) ?? false,
|
||||
isWinning: optionVotesCount.map { $0 == maxOptionVotes } ?? false)
|
||||
}
|
||||
|
||||
let pollKind: Poll.Kind = switch pollKind {
|
||||
case .disclosed:
|
||||
.disclosed
|
||||
case .undisclosed:
|
||||
.undisclosed
|
||||
}
|
||||
|
||||
let poll = Poll(question: question,
|
||||
kind: pollKind,
|
||||
maxSelections: Int(maxSelections),
|
||||
options: options,
|
||||
votes: votes,
|
||||
endDate: endTime.map { Date(timeIntervalSince1970: TimeInterval($0 / 1000)) },
|
||||
createdByAccountOwner: eventItemProxy.sender.id == userID)
|
||||
|
||||
return PollRoomTimelineItem(id: eventItemProxy.id,
|
||||
poll: poll,
|
||||
body: poll.question,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender,
|
||||
properties: .init(replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil,
|
||||
isEdited: edited,
|
||||
reactions: buildAggregatedReactions(messageLikeContent.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: buildOrderedReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: buildEncryptionAuthenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildRedactedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
RedactedRoomTimelineItem(id: eventItemProxy.id,
|
||||
body: L10n.commonMessageRemoved,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender,
|
||||
properties: .init(replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil))
|
||||
}
|
||||
|
||||
private func buildEncryptedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageLikeContent: MsgLikeContent,
|
||||
_ encryptedMessage: EncryptedMessage,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
var encryptionType = EncryptedRoomTimelineItem.EncryptionType.unknown
|
||||
@@ -180,313 +441,8 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender,
|
||||
properties: RoomTimelineItemProperties())
|
||||
}
|
||||
|
||||
private func buildRedactedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
RedactedRoomTimelineItem(id: eventItemProxy.id,
|
||||
body: L10n.commonMessageRemoved,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender,
|
||||
properties: RoomTimelineItemProperties())
|
||||
}
|
||||
|
||||
private func buildTextTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageContent: MessageContent,
|
||||
_ textMessageContent: TextMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
TextRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
isThreaded: messageContent.threadRoot != nil,
|
||||
shouldBoost: eventItemProxy.shouldBoost,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildTextTimelineItemContent(textMessageContent),
|
||||
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
|
||||
properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: authenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildImageTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageContent: MessageContent,
|
||||
_ imageMessageContent: ImageMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
ImageRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
isThreaded: messageContent.threadRoot != nil,
|
||||
shouldBoost: eventItemProxy.shouldBoost,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildImageTimelineItemContent(imageMessageContent),
|
||||
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
|
||||
properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: authenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildVideoTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageContent: MessageContent,
|
||||
_ videoMessageContent: VideoMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
VideoRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
isThreaded: messageContent.threadRoot != nil,
|
||||
shouldBoost: eventItemProxy.shouldBoost,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildVideoTimelineItemContent(videoMessageContent),
|
||||
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
|
||||
properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: authenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildAudioTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageContent: MessageContent,
|
||||
_ audioMessageContent: AudioMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
AudioRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
isThreaded: messageContent.threadRoot != nil,
|
||||
shouldBoost: eventItemProxy.shouldBoost,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildAudioTimelineItemContent(audioMessageContent),
|
||||
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
|
||||
properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: authenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildVoiceTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageContent: MessageContent,
|
||||
_ audioMessageContent: AudioMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
VoiceMessageRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
isThreaded: messageContent.threadRoot != nil,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildAudioTimelineItemContent(audioMessageContent),
|
||||
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
|
||||
properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: authenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildFileTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageContent: MessageContent,
|
||||
_ fileMessageContent: FileMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
FileRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
isThreaded: messageContent.threadRoot != nil,
|
||||
shouldBoost: eventItemProxy.shouldBoost,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildFileTimelineItemContent(fileMessageContent),
|
||||
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
|
||||
properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: authenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildNoticeTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageContent: MessageContent,
|
||||
_ noticeMessageContent: NoticeMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
NoticeRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
isThreaded: messageContent.threadRoot != nil,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildNoticeTimelineItemContent(noticeMessageContent),
|
||||
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
|
||||
properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: authenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildEmoteTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageContent: MessageContent,
|
||||
_ emoteMessageContent: EmoteMessageContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
EmoteRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
isThreaded: messageContent.threadRoot != nil,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildEmoteTimelineItemContent(senderDisplayName: eventItemProxy.sender.displayName, senderID: eventItemProxy.sender.id, messageContent: emoteMessageContent),
|
||||
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
|
||||
properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: authenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildLocationTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
_ messageContent: MessageContent,
|
||||
_ locationMessageContent: LocationContent,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
LocationRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
isThreaded: messageContent.threadRoot != nil,
|
||||
sender: eventItemProxy.sender,
|
||||
content: buildLocationTimelineItemContent(locationMessageContent),
|
||||
replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageContent.inReplyTo),
|
||||
properties: RoomTimelineItemProperties(isEdited: messageContent.isEdited,
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: authenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildPollTimelineItem(_ question: String,
|
||||
_ pollKind: PollKind,
|
||||
_ maxSelections: UInt64,
|
||||
_ answers: [PollAnswer],
|
||||
_ votes: [String: [String]],
|
||||
_ endTime: UInt64?,
|
||||
_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ isOutgoing: Bool,
|
||||
_ edited: Bool) -> RoomTimelineItemProtocol {
|
||||
let allVotes = votes.reduce(0) { count, pair in
|
||||
count + pair.value.count
|
||||
}
|
||||
|
||||
let maxOptionVotes = votes.map(\.value.count).max()
|
||||
|
||||
let options = answers.map { answer in
|
||||
let optionVotesCount = votes[answer.id]?.count
|
||||
|
||||
return Poll.Option(id: answer.id,
|
||||
text: answer.text,
|
||||
votes: optionVotesCount ?? 0,
|
||||
allVotes: allVotes,
|
||||
isSelected: votes[answer.id]?.contains(userID) ?? false,
|
||||
isWinning: optionVotesCount.map { $0 == maxOptionVotes } ?? false)
|
||||
}
|
||||
|
||||
let poll = Poll(question: question,
|
||||
kind: .init(pollKind: pollKind),
|
||||
maxSelections: Int(maxSelections),
|
||||
options: options,
|
||||
votes: votes,
|
||||
endDate: endTime.map { Date(timeIntervalSince1970: TimeInterval($0 / 1000)) },
|
||||
createdByAccountOwner: eventItemProxy.sender.id == userID)
|
||||
|
||||
return PollRoomTimelineItem(id: eventItemProxy.id,
|
||||
poll: poll,
|
||||
body: poll.question,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender,
|
||||
properties: RoomTimelineItemProperties(isEdited: edited,
|
||||
reactions: aggregateReactions(eventItemProxy.reactions),
|
||||
deliveryStatus: eventItemProxy.deliveryStatus,
|
||||
orderedReadReceipts: orderReadReceipts(eventItemProxy.readReceipts),
|
||||
encryptionAuthenticity: authenticity(eventItemProxy.shieldState)))
|
||||
}
|
||||
|
||||
private func buildCallInviteTimelineItem(for eventItemProxy: EventTimelineItemProxy) -> RoomTimelineItemProtocol {
|
||||
CallInviteRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender)
|
||||
}
|
||||
|
||||
private func buildCallNotificationTimelineItem(for eventItemProxy: EventTimelineItemProxy) -> RoomTimelineItemProtocol {
|
||||
CallNotificationRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender)
|
||||
}
|
||||
|
||||
private func aggregateReactions(_ reactions: [Reaction]) -> [AggregatedReaction] {
|
||||
reactions.map { reaction in
|
||||
let senders = reaction.senders
|
||||
.map { senderData in
|
||||
ReactionSender(id: senderData.senderId, timestamp: Date(timeIntervalSince1970: TimeInterval(senderData.timestamp / 1000)))
|
||||
}
|
||||
.sorted { a, b in
|
||||
// Sort reactions within an aggregation by timestamp descending.
|
||||
// This puts the most recent at the top, useful in cases like the
|
||||
// reaction summary view.
|
||||
a.timestamp > b.timestamp
|
||||
}
|
||||
return AggregatedReaction(accountOwnerID: userID, key: reaction.key, senders: senders)
|
||||
}
|
||||
.sorted { a, b in
|
||||
// Sort aggregated reactions by count and then timestamp ascending, using
|
||||
// the most recent reaction in the aggregation(hence index 0).
|
||||
// This appends new aggregations on the end of the reaction layout
|
||||
// and the deterministic sort avoids reactions jumping around if the reactions timeline
|
||||
// view reloads.
|
||||
if a.count == b.count {
|
||||
return a.senders[0].timestamp < b.senders[0].timestamp
|
||||
}
|
||||
return a.count > b.count
|
||||
}
|
||||
}
|
||||
|
||||
private func orderReadReceipts(_ receipts: [String: Receipt]) -> [ReadReceipt] {
|
||||
receipts
|
||||
.sorted { firstElement, secondElement in
|
||||
// If there is no timestamp we order them as last
|
||||
let firstTimestamp = firstElement.value.dateTimestamp ?? Date(timeIntervalSince1970: 0)
|
||||
let secondTimestamp = secondElement.value.dateTimestamp ?? Date(timeIntervalSince1970: 0)
|
||||
return firstTimestamp > secondTimestamp
|
||||
}
|
||||
.map { key, receipt in
|
||||
ReadReceipt(userID: key, formattedTimestamp: receipt.dateTimestamp?.formattedMinimal())
|
||||
}
|
||||
}
|
||||
|
||||
private func authenticity(_ shieldState: ShieldState?) -> EncryptionAuthenticity? {
|
||||
shieldState.flatMap(EncryptionAuthenticity.init)
|
||||
properties: .init(replyDetails: buildReplyToDetailsFromDetailsIfAvailable(details: messageLikeContent.inReplyTo),
|
||||
isThreaded: messageLikeContent.threadRoot != nil))
|
||||
}
|
||||
|
||||
// MARK: - Message events content
|
||||
@@ -626,6 +582,86 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
return finalString
|
||||
}
|
||||
|
||||
// MARK: - EventBasedTimelineItem Properties
|
||||
|
||||
private func buildAggregatedReactions(_ reactions: [Reaction]) -> [AggregatedReaction] {
|
||||
reactions.map { reaction in
|
||||
let senders = reaction.senders
|
||||
.map { senderData in
|
||||
ReactionSender(id: senderData.senderId, timestamp: Date(timeIntervalSince1970: TimeInterval(senderData.timestamp / 1000)))
|
||||
}
|
||||
.sorted { a, b in
|
||||
// Sort reactions within an aggregation by timestamp descending.
|
||||
// This puts the most recent at the top, useful in cases like the
|
||||
// reaction summary view.
|
||||
a.timestamp > b.timestamp
|
||||
}
|
||||
return AggregatedReaction(accountOwnerID: userID, key: reaction.key, senders: senders)
|
||||
}
|
||||
.sorted { a, b in
|
||||
// Sort aggregated reactions by count and then timestamp ascending, using
|
||||
// the most recent reaction in the aggregation(hence index 0).
|
||||
// This appends new aggregations on the end of the reaction layout
|
||||
// and the deterministic sort avoids reactions jumping around if the reactions timeline
|
||||
// view reloads.
|
||||
if a.count == b.count {
|
||||
return a.senders[0].timestamp < b.senders[0].timestamp
|
||||
}
|
||||
return a.count > b.count
|
||||
}
|
||||
}
|
||||
|
||||
private func buildOrderedReadReceipts(_ receipts: [String: Receipt]) -> [ReadReceipt] {
|
||||
receipts
|
||||
.sorted { firstElement, secondElement in
|
||||
// If there is no timestamp we order them as last
|
||||
let firstTimestamp = firstElement.value.dateTimestamp ?? Date(timeIntervalSince1970: 0)
|
||||
let secondTimestamp = secondElement.value.dateTimestamp ?? Date(timeIntervalSince1970: 0)
|
||||
return firstTimestamp > secondTimestamp
|
||||
}
|
||||
.map { key, receipt in
|
||||
ReadReceipt(userID: key, formattedTimestamp: receipt.dateTimestamp?.formattedMinimal())
|
||||
}
|
||||
}
|
||||
|
||||
private func buildEncryptionAuthenticity(_ shieldState: ShieldState?) -> EncryptionAuthenticity? {
|
||||
shieldState.flatMap(EncryptionAuthenticity.init)
|
||||
}
|
||||
|
||||
// MARK: - Other Events
|
||||
|
||||
private func buildUnsupportedTimelineItem(_ eventItemProxy: EventTimelineItemProxy,
|
||||
_ eventType: String,
|
||||
_ error: String,
|
||||
_ isOutgoing: Bool) -> RoomTimelineItemProtocol {
|
||||
UnsupportedRoomTimelineItem(id: eventItemProxy.id,
|
||||
body: L10n.commonUnsupportedEvent,
|
||||
eventType: eventType,
|
||||
error: error,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isOutgoing: isOutgoing,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender,
|
||||
properties: .init())
|
||||
}
|
||||
|
||||
private func buildCallInviteTimelineItem(for eventItemProxy: EventTimelineItemProxy) -> RoomTimelineItemProtocol {
|
||||
CallInviteRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender)
|
||||
}
|
||||
|
||||
private func buildCallNotificationTimelineItem(for eventItemProxy: EventTimelineItemProxy) -> RoomTimelineItemProtocol {
|
||||
CallNotificationRoomTimelineItem(id: eventItemProxy.id,
|
||||
timestamp: eventItemProxy.timestamp,
|
||||
isEditable: eventItemProxy.isEditable,
|
||||
canBeRepliedTo: eventItemProxy.canBeRepliedTo,
|
||||
sender: eventItemProxy.sender)
|
||||
}
|
||||
|
||||
// MARK: - State Events
|
||||
|
||||
private func buildStateTimelineItem(for eventItemProxy: EventTimelineItemProxy,
|
||||
@@ -696,14 +732,19 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
let replyContent: TimelineEventContent
|
||||
|
||||
switch timelineItem {
|
||||
case .message(let messageContent):
|
||||
return .init(details: timelineItemReplyDetails(sender: sender, eventID: details.eventId(), messageType: messageContent.msgType), isThreaded: isThreaded)
|
||||
case .poll(let question, _, _, _, _, _, _):
|
||||
replyContent = .poll(question: question)
|
||||
case .sticker(let body, _, _):
|
||||
replyContent = .message(.text(.init(body: body)))
|
||||
case .redactedMessage:
|
||||
replyContent = .redacted
|
||||
case .msgLike(let messageLikeContent):
|
||||
switch messageLikeContent.kind {
|
||||
case .message(let messageContent):
|
||||
return .init(details: timelineItemReplyDetails(sender: sender, eventID: details.eventId(), messageType: messageContent.msgType), isThreaded: isThreaded)
|
||||
case .poll(let question, _, _, _, _, _, _):
|
||||
replyContent = .poll(question: question)
|
||||
case .sticker(let body, _, _):
|
||||
replyContent = .message(.text(.init(body: body)))
|
||||
case .redacted:
|
||||
replyContent = .redacted
|
||||
default:
|
||||
replyContent = .message(.text(.init(body: L10n.commonUnsupportedEvent)))
|
||||
}
|
||||
default:
|
||||
replyContent = .message(.text(.init(body: L10n.commonUnsupportedEvent)))
|
||||
}
|
||||
@@ -756,26 +797,15 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
extension Poll.Kind {
|
||||
init(pollKind: MatrixRustSDK.PollKind) {
|
||||
switch pollKind {
|
||||
case .disclosed:
|
||||
self = .disclosed
|
||||
case .undisclosed:
|
||||
self = .undisclosed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension RepliedToEventDetails {
|
||||
var isThreaded: Bool {
|
||||
switch self {
|
||||
case .ready(let content, _, _):
|
||||
guard case let .message(content) = content else {
|
||||
guard case let .msgLike(messageLikeContent) = content else {
|
||||
return false
|
||||
}
|
||||
|
||||
return content.threadRoot != nil
|
||||
return messageLikeContent.threadRoot != nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -77,7 +77,8 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
|
||||
func messageEventContent(for timelineItemID: TimelineItemIdentifier) async -> RoomMessageEventContentWithoutRelation? {
|
||||
guard let content = await timelineProvider.itemProxies.firstEventTimelineItemUsingStableID(timelineItemID)?.content,
|
||||
case let .message(messageContent) = content else {
|
||||
case let .msgLike(messageLikeContent) = content,
|
||||
case let .message(messageContent) = messageLikeContent.kind else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user