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:
Stefan Ceriu 2023-08-08 14:55:09 +03:00 committed by Stefan Ceriu
parent 56544d6604
commit 0511bef33a
6 changed files with 96 additions and 22 deletions

View File

@ -153,7 +153,8 @@
35E975CFDA60E05362A7CF79 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 1222DB76B917EB8A55365BA5 /* target.yml */; };
368C8758FCD079E6AAA18C2C /* NoticeRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */; };
36AC963F2F04069B7FF1AA0C /* UIConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E6D88E8AFFBF2C1D589C0FA /* UIConstants.swift */; };
36AD4DD4C798E22584ED3200 /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = A05AF81DDD14AD58CB0E1B9B /* Version */; };
36AD4DD4C798E22584ED3200 /* URLRouting in Frameworks */ = {isa = PBXBuildFile; productRef = E9BAB8A793FE3B54CDD47102 /* URLRouting */; };
36CD6E11B37396E14F032CB6 /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = A05AF81DDD14AD58CB0E1B9B /* Version */; };
37D789F24199B32E3FD1AA7B /* FileRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 216F0DDC98F2A2C162D09C28 /* FileRoomTimelineItemContent.swift */; };
383055C6ABE5BE058CEE1DDB /* WelcomeScreenScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FE5EF0AFFE360C66420AAE /* WelcomeScreenScreenCoordinator.swift */; };
38546A6010A2CF240EC9AF73 /* BindableState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EA1D2CBAEA5D0BD00B90D1B /* BindableState.swift */; };
@ -174,7 +175,7 @@
3F2148F11164C7C5609984EB /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 2B788C81F6369D164ADEB917 /* GZIP */; };
3F70E237CE4C3FAB02FC227F /* NotificationConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C830A64609CBD152F06E0457 /* NotificationConstants.swift */; };
401BB28CD6B7DD6B4E7863E7 /* ServerConfirmationScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9342F5D6729627B6393AF853 /* ServerConfirmationScreenModels.swift */; };
407DCE030E0F9B7C9861D38A /* Mapbox in Frameworks */ = {isa = PBXBuildFile; productRef = C1BF15833233CD3BDB7E2B1D /* Mapbox */; };
407DCE030E0F9B7C9861D38A /* LRUCache in Frameworks */ = {isa = PBXBuildFile; productRef = 1081D3630AAD3ACEDDEC3A98 /* LRUCache */; };
40B79D20A873620F7F128A2C /* UserPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35FA991289149D31F4286747 /* UserPreference.swift */; };
414F50CFCFEEE2611127DCFB /* RestorationToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3558A15CFB934F9229301527 /* RestorationToken.swift */; };
4166A7DD2A4E2EFF0EB9369B /* FormRowLabelStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1897720266C036471AD9D1B /* FormRowLabelStyle.swift */; };
@ -321,7 +322,7 @@
74604ACFDBE7F54260E7B617 /* ApplicationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */; };
748F482FEF4E04D61C39AAD7 /* EmojiPickerScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */; };
7501442D52A65F73DF79FFD4 /* PaginationIndicatorRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B987FC3FDBAA0E1C5AA235C /* PaginationIndicatorRoomTimelineItem.swift */; };
754602A7B2AAD443C4228ED4 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 7731767AE437BA3BD2CC14A8 /* Sentry */; };
754602A7B2AAD443C4228ED4 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 997C7385E1A07E061D7E2100 /* GZIP */; };
755727E0B756430DFFEC4732 /* SessionVerificationViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF05DA24F71B455E8EFEBC3B /* SessionVerificationViewModelTests.swift */; };
764AFCC225B044CF5F9B41E5 /* PaginationIndicatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42EEA67A6796BDC2761619C5 /* PaginationIndicatorRoomTimelineView.swift */; };
76BA28216FBAF83B2D86A027 /* InvitesScreenCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA2A71915C1F075E403F559C /* InvitesScreenCell.swift */; };
@ -407,7 +408,7 @@
8DDC6F28C797D8685F2F8E32 /* AnalyticsConsentState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57B6B383F1FD04CC0E7B60C6 /* AnalyticsConsentState.swift */; };
8E650379587C31D7912ED67B /* UNNotification+Creator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0AEA686E425F86F6BA0404 /* UNNotification+Creator.swift */; };
8EF63DDDC1B54F122070B04D /* ReadMarkerRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6311F21F911E23BE4DF51B4 /* ReadMarkerRoomTimelineView.swift */; };
8F2FAA98457750D9D664136F /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 4278261E147DB2DE5CFB7FC5 /* PostHog */; };
8F2FAA98457750D9D664136F /* Mapbox in Frameworks */ = {isa = PBXBuildFile; productRef = C1BF15833233CD3BDB7E2B1D /* Mapbox */; };
90733645AE76FB33DAD28C2B /* URLSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE40D4A5DD857AC16EED945A /* URLSession.swift */; };
9095B9E40DB5CF8BA26CE0D8 /* ReactionsSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 153726EDCE1ACBB3D466A916 /* ReactionsSummaryView.swift */; };
90DF83A6A347F7EE7EDE89EE /* AttributedStringBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF25E364AE85090A70AE4644 /* AttributedStringBuilderTests.swift */; };
@ -518,7 +519,7 @@
B037C365CF8A58A0D149A2DB /* AuthenticationIconImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97755C01C3971474EFAD5367 /* AuthenticationIconImage.swift */; };
B064D42BA087649ACAE462E8 /* SoftLogoutUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55F30E764BED111C81739844 /* SoftLogoutUITests.swift */; };
B09DC6E3D0EE87C4D4ABFAB3 /* EncryptedHistoryRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0140615D2232612C813FD6C /* EncryptedHistoryRoomTimelineItem.swift */; };
B0CB16349B96262AA65A04AF /* URLRouting in Frameworks */ = {isa = PBXBuildFile; productRef = E9BAB8A793FE3B54CDD47102 /* URLRouting */; };
B0CB16349B96262AA65A04AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 7731767AE437BA3BD2CC14A8 /* Sentry */; };
B1069F361E604D5436AE9FFD /* StaticLocationScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */; };
B14BC354E56616B6B7D9A3D7 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27A1AD6389A4659AF0CEAE62 /* NotificationServiceExtension.swift */; };
B22D857D1E8FCA6DD74A58E3 /* UserSessionScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F899D02CF26EA7675EEBE74C /* UserSessionScreenTests.swift */; };
@ -699,7 +700,7 @@
EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */; };
EA6613B29BA671F39CE1B1D2 /* ConfirmationDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B383DCD3DCB19E00FD478A5F /* ConfirmationDialog.swift */; };
EA974337FA7D040E7C74FE6E /* RoomDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EFE1922F39398ABFB36DF3F /* RoomDetailsViewModelTests.swift */; };
EAC6FE2CD4F50A43068ADCD8 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 997C7385E1A07E061D7E2100 /* GZIP */; };
EAC6FE2CD4F50A43068ADCD8 /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 9573B94B1C86C6DF751AF3FD /* SwiftState */; };
EAF2B3E6C6AEC4AD3A8BD454 /* RoomMemberDetailsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A87D0471D438A233C2CF4A /* RoomMemberDetailsScreenViewModel.swift */; };
EB88DBD77221E2CFE463018C /* NSE.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0D8F620C8B314840D8602E3F /* NSE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
EBE13FAB4E29738AC41BD3E5 /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; };
@ -756,7 +757,7 @@
FB9A1DD83EF641A75ABBCE69 /* WaitlistScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C796FC1DFDBCDD5573D0360F /* WaitlistScreenViewModelTests.swift */; };
FBCCF1EA25A071324FCD8544 /* TimelineItemDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7023EB4F3B7C7D1FBA68638B /* TimelineItemDebugView.swift */; };
FBF09B6C900415800DDF2A21 /* EmojiProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C113E0CB7E15E9765B1817A /* EmojiProvider.swift */; };
FC10228E73323BDC09526F97 /* SwiftState in Frameworks */ = {isa = PBXBuildFile; productRef = 9573B94B1C86C6DF751AF3FD /* SwiftState */; };
FC10228E73323BDC09526F97 /* PostHog in Frameworks */ = {isa = PBXBuildFile; productRef = 4278261E147DB2DE5CFB7FC5 /* PostHog */; };
FCD3F2B82CAB29A07887A127 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 2B43F2AF7456567FE37270A7 /* KeychainAccess */; };
FCDA202B246F75BA28E10C5F /* MapTilerAuthorization.swift in Sources */ = {isa = PBXBuildFile; fileRef = E062C1750EFC8627DE4CAB8E /* MapTilerAuthorization.swift */; };
FD762761C5D0C30E6255C3D8 /* ServerConfirmationScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ABA4CF2F5B4F68D02E412004 /* ServerConfirmationScreenViewModelProtocol.swift */; };
@ -1591,13 +1592,14 @@
3C549A0BF39F8A854D45D9FD /* KeychainAccess in Frameworks */,
41DFDD212D1BE57CA50D783B /* Kingfisher in Frameworks */,
6298AB0906DDD3525CD78C6B /* KZFileWatchers in Frameworks */,
407DCE030E0F9B7C9861D38A /* Mapbox in Frameworks */,
8F2FAA98457750D9D664136F /* PostHog in Frameworks */,
FC10228E73323BDC09526F97 /* SwiftState in Frameworks */,
EAC6FE2CD4F50A43068ADCD8 /* GZIP in Frameworks */,
754602A7B2AAD443C4228ED4 /* Sentry in Frameworks */,
B0CB16349B96262AA65A04AF /* URLRouting in Frameworks */,
36AD4DD4C798E22584ED3200 /* Version in Frameworks */,
407DCE030E0F9B7C9861D38A /* LRUCache in Frameworks */,
8F2FAA98457750D9D664136F /* Mapbox in Frameworks */,
FC10228E73323BDC09526F97 /* PostHog in Frameworks */,
EAC6FE2CD4F50A43068ADCD8 /* SwiftState in Frameworks */,
754602A7B2AAD443C4228ED4 /* GZIP in Frameworks */,
B0CB16349B96262AA65A04AF /* Sentry in Frameworks */,
36AD4DD4C798E22584ED3200 /* URLRouting in Frameworks */,
36CD6E11B37396E14F032CB6 /* Version in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -3810,6 +3812,7 @@
020597E28A4BC8E1BE8EDF6E /* KeychainAccess */,
0DD568A494247444A4B56031 /* Kingfisher */,
81DB3AB6CE996AB3954F4F03 /* KZFileWatchers */,
1081D3630AAD3ACEDDEC3A98 /* LRUCache */,
C1BF15833233CD3BDB7E2B1D /* Mapbox */,
4278261E147DB2DE5CFB7FC5 /* PostHog */,
9573B94B1C86C6DF751AF3FD /* SwiftState */,
@ -3935,6 +3938,7 @@
395DE6AE429B7ACC7C7FE31D /* XCRemoteSwiftPackageReference "KZFileWatchers" */,
61916C63E3F5BD900F08DA0C /* XCRemoteSwiftPackageReference "KeychainAccess" */,
D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */,
CCD235515AFCEE6D2005B705 /* XCRemoteSwiftPackageReference "LRUCache" */,
0CBF57301AA172C21F76CE86 /* XCRemoteSwiftPackageReference "maplibre-gl-native-distribution" */,
80B898A3AD2AC63F3ABFC218 /* XCRemoteSwiftPackageReference "matrix-rust-components-swift" */,
96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */,
@ -5477,6 +5481,14 @@
version = 1.6.26;
};
};
CCD235515AFCEE6D2005B705 /* XCRemoteSwiftPackageReference "LRUCache" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/nicklockwood/LRUCache";
requirement = {
kind = upToNextMinorVersion;
minimumVersion = 1.0.4;
};
};
D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/onevcat/Kingfisher";
@ -5543,6 +5555,11 @@
package = D283517192CAC3E2E6920765 /* XCRemoteSwiftPackageReference "Kingfisher" */;
productName = Kingfisher;
};
1081D3630AAD3ACEDDEC3A98 /* LRUCache */ = {
isa = XCSwiftPackageProductDependency;
package = CCD235515AFCEE6D2005B705 /* XCRemoteSwiftPackageReference "LRUCache" */;
productName = LRUCache;
};
19CD5B074D7DD44AF4C58BB6 /* SwiftState */ = {
isa = XCSwiftPackageProductDependency;
package = 6582B5AF3F104B0F7E031E7D /* XCRemoteSwiftPackageReference "SwiftState" */;

View File

@ -88,6 +88,15 @@
"revision" : "d27a9557427d261adccdf4b566acc9d9c0fec6f4"
}
},
{
"identity" : "lrucache",
"kind" : "remoteSourceControl",
"location" : "https://github.com/nicklockwood/LRUCache",
"state" : {
"revision" : "6d2b5246c9c98dcd498552bb22f08d55b12a8371",
"version" : "1.0.4"
}
},
{
"identity" : "maplibre-gl-native-distribution",
"kind" : "remoteSourceControl",

View File

@ -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

View File

@ -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) }
}
}

View File

@ -155,6 +155,7 @@ targets:
- package: KeychainAccess
- package: Kingfisher
- package: KZFileWatchers
- package: LRUCache
- package: Mapbox
- package: PostHog
- package: SwiftState

View File

@ -79,6 +79,9 @@ packages:
KZFileWatchers:
url: https://github.com/krzysztofzablocki/KZFileWatchers
branch: master
LRUCache:
url: https://github.com/nicklockwood/LRUCache
minorVersion: 1.0.4
SwiftUIIntrospect:
url: https://github.com/siteline/SwiftUI-Introspect
minorVersion: 0.9.0