Render state items in the timeline and as the last message on the home screen. (#473)

* State events in the timeline WIP
* Implement membership change strings.
* Add other state event types and use in RoomSummaryProvider.

Co-authored-by: Stefan Ceriu <stefanc@matrix.org>
This commit is contained in:
Doug
2023-01-20 13:37:39 +00:00
committed by GitHub
parent 5b0a42ad0f
commit f05fa630c1
32 changed files with 536 additions and 75 deletions
@@ -0,0 +1,84 @@
//
// Copyright 2023 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
struct RoomEventStringBuilder {
private let roomStateStringBuilder: RoomStateStringBuilder
/// The Matrix ID of the current user.
private let userID: String
init(userID: String, roomStateStringBuilder: RoomStateStringBuilder) {
self.userID = userID
self.roomStateStringBuilder = roomStateStringBuilder
}
// swiftlint:disable:next cyclomatic_complexity
func buildAttributedString(for eventItemProxy: EventTimelineItemProxy) -> AttributedString? {
let sender = eventItemProxy.sender
let isOutgoing = eventItemProxy.isOwn
switch eventItemProxy.content.kind() {
case .unableToDecrypt:
return prefix(ElementL10n.encryptionInformationDecryptionError, with: sender)
case .redactedMessage:
return prefix(ElementL10n.eventRedacted, with: sender)
case .sticker:
return prefix(ElementL10n.sendASticker, with: sender)
case .failedToParseMessageLike, .failedToParseState:
return prefix(ElementL10n.roomTimelineItemUnsupported, with: sender)
case .message:
guard let messageContent = eventItemProxy.content.asMessage() else { fatalError("Invalid message timeline item: \(eventItemProxy)") }
let message: String
switch messageContent.msgtype() {
// Message types that don't need a prefix.
case .emote(content: let content):
let senderDisplayName = sender.displayName ?? sender.id
return AttributedString("* \(senderDisplayName) \(content.body)")
// Message types that should be prefixed with the sender's name.
case .image:
message = ElementL10n.sentAnImage
case .video:
message = ElementL10n.sentAVideo
case .file:
message = ElementL10n.sentAFile
default:
message = messageContent.body()
}
return prefix(message, with: sender)
case .state(let stateKey, let state):
return roomStateStringBuilder
.buildString(for: state, stateKey: stateKey, sender: sender, isOutgoing: isOutgoing)
.map(AttributedString.init)
case .roomMembership(userId: let userID, change: let change):
return roomStateStringBuilder
.buildString(for: change, member: userID, sender: sender, isOutgoing: isOutgoing)
.map(AttributedString.init)
}
}
func prefix(_ eventSummary: String, with sender: TimelineItemSender) -> AttributedString {
if let senderDisplayName = sender.displayName,
let attributedSenderDisplayName = try? AttributedString(markdown: "**\(senderDisplayName)**") {
// Don't include the message body in the markdown otherwise it makes tappable links.
return attributedSenderDisplayName + ": " + AttributedString(eventSummary)
} else {
return AttributedString(eventSummary)
}
}
}
@@ -21,6 +21,7 @@ import MatrixRustSDK
class RoomSummaryProvider: RoomSummaryProviderProtocol {
private let slidingSyncViewProxy: SlidingSyncViewProxy
private let serialDispatchQueue: DispatchQueue
private let eventStringBuilder: RoomEventStringBuilder
private var cancellables = Set<AnyCancellable>()
@@ -34,9 +35,10 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
}
}
init(slidingSyncViewProxy: SlidingSyncViewProxy) {
init(slidingSyncViewProxy: SlidingSyncViewProxy, eventStringBuilder: RoomEventStringBuilder) {
self.slidingSyncViewProxy = slidingSyncViewProxy
serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomsummaryprovider")
self.eventStringBuilder = eventStringBuilder
rooms = slidingSyncViewProxy.currentRoomsList().map { roomListEntry in
buildSummaryForRoomListEntry(roomListEntry)
@@ -151,16 +153,8 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
DispatchQueue.global(qos: .default).sync {
if let latestRoomMessage = room.latestRoomMessage() {
let lastMessage = EventTimelineItemProxy(item: latestRoomMessage)
lastMessageTimestamp = lastMessage.timestamp
if let senderDisplayName = lastMessage.sender.displayName,
let attributedSenderDisplayName = try? AttributedString(markdown: "**\(senderDisplayName)**") {
// Don't include the message body in the markdown otherwise it makes tappable links.
attributedLastMessage = attributedSenderDisplayName + ": " + AttributedString(lastMessage.body ?? "")
} else if let body = lastMessage.body {
attributedLastMessage = AttributedString(body)
}
attributedLastMessage = eventStringBuilder.buildAttributedString(for: lastMessage)
}
}