Files
letro-ios/ElementX/Sources/Services/Room/RoomSummary/RoomMessageEventStringBuilder.swift
2025-06-10 15:09:20 +03:00

106 lines
4.2 KiB
Swift

//
// Copyright 2023, 2024 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 Destination {
/// Strings show on the room list as the last message
/// The sender will be prefixed in bold
case roomList
/// Events pinned to the banner on the top of the timeline
/// The message type will be prefixed in bold
case pinnedEvent
/// Shown in push notifications
/// No prefix
case notification
}
let attributedStringBuilder: AttributedStringBuilderProtocol
let destination: Destination
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 destination == .pinnedEvent {
content.bold()
}
message = content
case .image(let content):
message = buildMessage(for: destination, caption: content.caption, type: L10n.commonImage)
case .video(let content):
message = buildMessage(for: destination, caption: content.caption, type: L10n.commonVideo)
case .file(let content):
message = buildMessage(for: destination, caption: content.caption, type: L10n.commonFile)
case .location:
var content = AttributedString(L10n.commonSharedLocation)
if destination == .pinnedEvent {
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 destination == .roomList {
return prefix(message, with: isOutgoing ? L10n.commonYou : senderDisplayName)
} else {
return message
}
}
private func buildMessage(for destination: Destination, caption: String?, type: String) -> AttributedString {
guard let caption else {
return AttributedString(type)
}
if destination == .pinnedEvent {
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) }
}
}