mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Fixes #1270 - Parse the latest room message instead of directly using the body. Add an app wide attributed string caching layer.
This commit is contained in:
committed by
Stefan Ceriu
parent
56544d6604
commit
0511bef33a
@@ -16,6 +16,7 @@
|
||||
|
||||
import DTCoreText
|
||||
import Foundation
|
||||
import LRUCache
|
||||
|
||||
struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
private let temporaryBlockquoteMarkingColor = UIColor.magenta
|
||||
@@ -23,6 +24,8 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
private let linkColor = UIColor.blue
|
||||
private let permalinkBaseURL: URL
|
||||
|
||||
private static var cache = LRUCache<String, AttributedString>(countLimit: 1000)
|
||||
|
||||
init(permalinkBaseURL: URL) {
|
||||
self.permalinkBaseURL = permalinkBaseURL
|
||||
}
|
||||
@@ -31,12 +34,18 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
guard let string else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let cached = Self.cache.value(forKey: string) {
|
||||
return cached
|
||||
}
|
||||
|
||||
let mutableAttributedString = NSMutableAttributedString(string: string)
|
||||
addLinks(mutableAttributedString)
|
||||
removeLinkColors(mutableAttributedString)
|
||||
|
||||
return try? AttributedString(mutableAttributedString, including: \.elementX)
|
||||
let result = try? AttributedString(mutableAttributedString, including: \.elementX)
|
||||
Self.cache.setValue(result, forKey: string)
|
||||
return result
|
||||
}
|
||||
|
||||
// Do not use the default HTML renderer of NSAttributedString because this method
|
||||
@@ -52,6 +61,10 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let cached = Self.cache.value(forKey: htmlString) {
|
||||
return cached
|
||||
}
|
||||
|
||||
let defaultFont = UIFont.preferredFont(forTextStyle: .body)
|
||||
|
||||
let parsingOptions: [String: Any] = [
|
||||
@@ -83,7 +96,9 @@ struct AttributedStringBuilder: AttributedStringBuilderProtocol {
|
||||
replaceMarkedCodeBlocks(mutableAttributedString)
|
||||
removeDTCoreTextArtifacts(mutableAttributedString)
|
||||
|
||||
return try? AttributedString(mutableAttributedString, including: \.elementX)
|
||||
let result = try? AttributedString(mutableAttributedString, including: \.elementX)
|
||||
Self.cache.setValue(result, forKey: htmlString)
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
|
||||
struct RoomEventStringBuilder {
|
||||
private let stateEventStringBuilder: RoomStateEventStringBuilder
|
||||
@@ -37,15 +38,28 @@ struct RoomEventStringBuilder {
|
||||
case .failedToParseMessageLike, .failedToParseState:
|
||||
return prefix(L10n.commonUnsupportedEvent, with: sender)
|
||||
case .message:
|
||||
guard let messageContent = eventItemProxy.content.asMessage() else { fatalError("Invalid message timeline item: \(eventItemProxy)") }
|
||||
guard let messageContent = eventItemProxy.content.asMessage() else {
|
||||
fatalError("Invalid message timeline item: \(eventItemProxy)")
|
||||
}
|
||||
|
||||
guard let messageType = messageContent.msgtype() else {
|
||||
return prefix(messageContent.body(), with: sender)
|
||||
}
|
||||
|
||||
let message: String
|
||||
switch messageContent.msgtype() {
|
||||
switch messageType {
|
||||
// Message types that don't need a prefix.
|
||||
case .emote(content: let content):
|
||||
let senderDisplayName = sender.displayName ?? sender.id
|
||||
return AttributedString(L10n.commonEmote(senderDisplayName, content.body))
|
||||
|
||||
if let attributedMessage = attributedMessageFrom(formattedBody: content.formatted) {
|
||||
return AttributedString(L10n.commonEmote(senderDisplayName, String(attributedMessage.characters)))
|
||||
} else {
|
||||
return AttributedString(L10n.commonEmote(senderDisplayName, content.body))
|
||||
}
|
||||
// Message types that should be prefixed with the sender's name.
|
||||
case .audio:
|
||||
message = L10n.commonAudio
|
||||
case .image:
|
||||
message = L10n.commonImage
|
||||
case .video:
|
||||
@@ -54,8 +68,19 @@ struct RoomEventStringBuilder {
|
||||
message = L10n.commonFile
|
||||
case .location:
|
||||
message = L10n.commonSharedLocation
|
||||
default:
|
||||
message = messageContent.body()
|
||||
case .notice(content: let content):
|
||||
if let attributedMessage = attributedMessageFrom(formattedBody: content.formatted) {
|
||||
message = String(attributedMessage.characters)
|
||||
} else {
|
||||
message = content.body
|
||||
}
|
||||
case .text(content: let content):
|
||||
|
||||
if let attributedMessage = attributedMessageFrom(formattedBody: content.formatted) {
|
||||
message = String(attributedMessage.characters)
|
||||
} else {
|
||||
message = content.body
|
||||
}
|
||||
}
|
||||
return prefix(message, with: sender)
|
||||
case .state(let stateKey, let state):
|
||||
@@ -81,7 +106,7 @@ struct RoomEventStringBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
func prefix(_ eventSummary: String, with sender: TimelineItemSender) -> AttributedString {
|
||||
private func prefix(_ eventSummary: String, with sender: TimelineItemSender) -> AttributedString {
|
||||
let attributedEventSummary = AttributedString(eventSummary.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
if let senderDisplayName = sender.displayName,
|
||||
let attributedSenderDisplayName = try? AttributedString(markdown: "**\(senderDisplayName)**") {
|
||||
@@ -91,4 +116,8 @@ struct RoomEventStringBuilder {
|
||||
return attributedEventSummary
|
||||
}
|
||||
}
|
||||
|
||||
private func attributedMessageFrom(formattedBody: FormattedBody?) -> AttributedString? {
|
||||
formattedBody.flatMap { AttributedStringBuilder(permalinkBaseURL: .homeDirectory).fromHTML($0.body) }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user