Files
letro-ios/ElementX/Sources/Services/Room/RoomSummary/RoomMessageEventStringBuilder.swift
Stefan Ceriu 0104ad06aa Refactor the RoomMessageEventStringBuilder to take a Style instead of a Destination
This make its usage clearer and makes different styles reusable
2026-03-31 18:21:14 +03:00

120 lines
4.7 KiB
Swift

//
// Copyright 2025 Element Creations Ltd.
// Copyright 2023-2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Foundation
import MatrixRustSDK
struct RoomMessageEventStringBuilder {
enum Style {
/// Plain: no prefix, no special text treatment
/// Shown in push notifications and thread lists
case plain
/// Strings show on the room list as the last message
/// The sender will be prefixed in bold
case senderPrefixed
/// Events pinned to the banner on the top of the timeline
/// The message type will be prefixed in bold
case typeBolded
}
let attributedStringBuilder: AttributedStringBuilderProtocol
let style: Style
func buildAttributedString(for messageType: MessageType, senderDisplayName: String, isOutgoing: Bool) -> AttributedString {
let message: AttributedString
switch messageType {
case .emote(content: let content):
if let attributedMessage = attributedMessageFrom(formattedBody: content.formatted) {
return AttributedString(L10n.commonEmote(senderDisplayName, String(attributedMessage.characters)))
} else {
return AttributedString(L10n.commonEmote(senderDisplayName, content.body))
}
case .audio(content: let content):
let isVoiceMessage = content.voice != nil
var content = AttributedString(isVoiceMessage ? L10n.commonVoiceMessage : L10n.commonAudio)
if style == .typeBolded {
content.bold()
}
message = content
case .image(let content):
message = buildMessage(for: style, caption: content.caption, type: L10n.commonImage)
case .video(let content):
message = buildMessage(for: style, caption: content.caption, type: L10n.commonVideo)
case .file(let content):
message = buildMessage(for: style, caption: content.caption, type: L10n.commonFile)
case .location:
var content = AttributedString(L10n.commonSharedLocation)
if style == .typeBolded {
content.bold()
}
message = content
case .notice(content: let content):
if let attributedMessage = attributedMessageFrom(formattedBody: content.formatted) {
message = attributedMessage
} else {
message = AttributedString(content.body)
}
case .text(content: let content):
if let attributedMessage = attributedMessageFrom(formattedBody: content.formatted) {
message = attributedMessage
} else {
message = AttributedString(content.body)
}
case .gallery(let content):
message = AttributedString(content.body)
case .other(_, let body):
message = AttributedString(body)
}
if style == .senderPrefixed {
return prefix(message, with: isOutgoing ? L10n.commonYou : senderDisplayName)
} else {
return message
}
}
func buildAttributedStringForLiveLocation(senderDisplayName: String, isOutgoing: Bool) -> AttributedString {
var message = AttributedString(L10n.commonSharedLiveLocation)
if style == .typeBolded {
message.bold()
}
if style == .senderPrefixed {
return prefix(message, with: isOutgoing ? L10n.commonYou : senderDisplayName)
} else {
return message
}
}
private func buildMessage(for style: Style, caption: String?, type: String) -> AttributedString {
guard let caption else {
return AttributedString(type)
}
if style == .typeBolded {
return prefix(AttributedString(caption), with: type)
} else {
return AttributedString("\(type) - \(caption)")
}
}
private func prefix(_ eventSummary: AttributedString, with textToBold: String) -> AttributedString {
let attributedEventSummary = AttributedString(eventSummary.string.trimmingCharacters(in: .whitespacesAndNewlines))
var attributedPrefix = AttributedString(textToBold + ":")
attributedPrefix.bold()
// Don't include the message body in the markdown otherwise it makes tappable links.
return attributedPrefix + " " + attributedEventSummary
}
private func attributedMessageFrom(formattedBody: FormattedBody?) -> AttributedString? {
formattedBody.flatMap { attributedStringBuilder.fromHTML($0.body) }
}
}