From 0511bef33a477f035260a7ae77f52644a4cfff67 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 8 Aug 2023 14:55:09 +0300 Subject: [PATCH] Fixes #1270 - Parse the latest room message instead of directly using the body. Add an app wide attributed string caching layer. --- ElementX.xcodeproj/project.pbxproj | 45 +++++++++++++------ .../xcshareddata/swiftpm/Package.resolved | 9 ++++ .../HTMLParsing/AttributedStringBuilder.swift | 19 +++++++- .../RoomSummary/RoomEventStringBuilder.swift | 41 ++++++++++++++--- ElementX/SupportingFiles/target.yml | 1 + project.yml | 3 ++ 6 files changed, 96 insertions(+), 22 deletions(-) diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index cde37d631..9240d77d6 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -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" */; diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 241291ba3..3f1788fb2 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -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", diff --git a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift index cd88f064e..df4fd6fe1 100644 --- a/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift +++ b/ElementX/Sources/Other/HTMLParsing/AttributedStringBuilder.swift @@ -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(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 diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift index e55f2378e..8be2a7965 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomEventStringBuilder.swift @@ -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) } + } } diff --git a/ElementX/SupportingFiles/target.yml b/ElementX/SupportingFiles/target.yml index f1a45a503..c4a25dd33 100644 --- a/ElementX/SupportingFiles/target.yml +++ b/ElementX/SupportingFiles/target.yml @@ -155,6 +155,7 @@ targets: - package: KeychainAccess - package: Kingfisher - package: KZFileWatchers + - package: LRUCache - package: Mapbox - package: PostHog - package: SwiftState diff --git a/project.yml b/project.yml index 6533b2f9d..c673882e3 100644 --- a/project.yml +++ b/project.yml @@ -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