From 8f3f83167bff109068d8be2ca04f4c8505476138 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 18 Jan 2023 16:29:44 +0200 Subject: [PATCH] Timeline item sender profiles (#463) * Adopt timeline item sender profiles for displaying user avatars and display names * Prevent images from automatically being evicted from the in-memory cache when entering background * Get rid of all the `urlString: String`s and start using proper URLs * Add back manual display name loading as the sender profiles aren't working properly yet * Implement a sender profile. * Bump the RustSDK to v1.0.32-alpha Co-authored-by: Doug --- ElementX.xcodeproj/project.pbxproj | 28 +-- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../Screens/HomeScreen/HomeScreenModels.swift | 2 +- .../HomeScreen/HomeScreenViewModel.swift | 12 +- .../RoomDetails/RoomDetailsModels.swift | 4 +- .../RoomDetails/RoomDetailsViewModel.swift | 3 +- .../RoomMemberDetailsViewModel.swift | 7 +- .../RoomScreen/RoomScreenViewModel.swift | 10 +- .../RoomScreen/View/RoomHeaderView.swift | 2 +- .../Style/TimelineItemBubbledStylerView.swift | 2 +- .../Style/TimelineItemPlainStylerView.swift | 2 +- .../View/Timeline/EmoteRoomTimelineView.swift | 13 +- .../Timeline/EncryptedRoomTimelineView.swift | 2 +- .../View/Timeline/FileRoomTimelineView.swift | 6 +- .../View/Timeline/ImageRoomTimelineView.swift | 6 +- .../Timeline/NoticeRoomTimelineView.swift | 2 +- .../Timeline/RedactedRoomTimelineView.swift | 2 +- .../Timeline/StickerRoomTimelineView.swift | 12 +- .../View/Timeline/TextRoomTimelineView.swift | 2 +- .../UnsupportedRoomTimelineView.swift | 2 +- .../View/Timeline/VideoRoomTimelineView.swift | 6 +- .../View/TimelineSenderAvatarView.swift | 8 +- .../Screens/Settings/SettingsViewModel.swift | 10 +- .../AuthenticationServiceProxy.swift | 2 +- .../Sources/Services/Client/ClientProxy.swift | 26 +-- .../Services/Client/ClientProxyProtocol.swift | 4 +- .../Services/Client/MockClientProxy.swift | 10 +- .../Services/Media/MediaProvider.swift | 58 +++--- .../Media/MediaProviderProtocol.swift | 16 +- .../Sources/Services/Media/MediaProxy.swift | 4 +- .../Services/Media/MediaProxyProtocol.swift | 2 +- .../Services/Media/MediaSourceProxy.swift | 9 +- .../Services/Media/MockMediaProvider.swift | 14 +- .../Manager/NotificationManager.swift | 2 +- .../Proxy/NotificationItemProxy.swift | 4 +- .../Sources/Services/Room/MockRoomProxy.swift | 6 +- .../Services/Room/RoomMemberProxy.swift | 4 +- .../Services/Room/RoomMessageFactory.swift | 34 ---- .../Room/RoomMessageFactoryProtocol.swift | 22 --- .../Sources/Services/Room/RoomProxy.swift | 26 ++- .../Services/Room/RoomProxyProtocol.swift | 7 +- .../RoomSummary/MockRoomSummaryProvider.swift | 6 +- .../Room/RoomSummary/RoomSummaryDetails.swift | 2 +- .../RoomSummary/RoomSummaryProvider.swift | 35 ++-- .../Fixtures/RoomTimelineItemFixtures.swift | 24 +-- .../RoomTimelineController.swift | 43 ++--- .../Services/Timeline/TimelineItemProxy.swift | 7 +- .../TimelineItemSender.swift} | 17 +- .../EventBasedTimelineItemProtocol.swift | 4 +- .../Messages/EmoteRoomTimelineItem.swift | 4 +- .../Items/Messages/FileRoomTimelineItem.swift | 4 +- .../Messages/ImageRoomTimelineItem.swift | 4 +- .../Messages/NoticeRoomTimelineItem.swift | 4 +- .../Items/Messages/TextRoomTimelineItem.swift | 4 +- .../Messages/VideoRoomTimelineItem.swift | 4 +- .../Other/EncryptedRoomTimelineItem.swift | 4 +- .../Other/RedactedRoomTimelineItem.swift | 4 +- .../Items/Other/StickerRoomTimelineItem.swift | 6 +- .../Other/UnsupportedRoomTimelineItem.swift | 4 +- .../RoomTimelineItemFactory.swift | 171 ++++++++---------- .../UserSessionFlowCoordinator.swift | 1 - .../UserSession/UserSessionStore.swift | 27 +-- .../UserSessionStoreProtocol.swift | 4 +- .../UITests/UITestsAppCoordinator.swift | 14 +- NSE/SupportingFiles/target.yml | 1 + UnitTests/Sources/LoggingTests.swift | 14 +- .../MediaProvider/MediaProviderTests.swift | 68 +++---- .../MediaProvider/MockMediaProxy.swift | 4 +- .../NotificationManagerTests.swift | 2 +- project.yml | 2 +- 70 files changed, 390 insertions(+), 495 deletions(-) delete mode 100644 ElementX/Sources/Services/Room/RoomMessageFactory.swift delete mode 100644 ElementX/Sources/Services/Room/RoomMessageFactoryProtocol.swift rename ElementX/Sources/Services/{Room/Messages/RoomMessageProtocol.swift => Timeline/TimelineItemSender.swift} (60%) diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 846dbe98c..ffaf8f0f1 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -66,6 +66,7 @@ 1AE4AEA0FA8DEF52671832E0 /* RoomTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ED1D792EB82506A19A72C8DE /* RoomTimelineItemProtocol.swift */; }; 1B2DADC008EE211AF1DA5292 /* NotificationManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30ED584467DB380E3CEFB1DB /* NotificationManagerTests.swift */; }; 1B4B3E847BF944DB2C1C217F /* BackgroundTaskServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAE73D571D4F9C36DD45255A /* BackgroundTaskServiceProtocol.swift */; }; + 1B88BB631F7FC45A213BB554 /* TimelineItemSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55AEEF8142DF1B59DB40FB93 /* TimelineItemSender.swift */; }; 1CF18DE71D5D23C61BD88852 /* DebugScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9238D3A3A00F45E841FE4EFF /* DebugScreen.swift */; }; 1D69E31913DF66426985909B /* EmojiPickerScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11151E78D6BB2B04A8FBD389 /* EmojiPickerScreenViewModelProtocol.swift */; }; 1E2298F15121667E36378F32 /* RoomDetailsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38B7319C1D6508702B98A8F6 /* RoomDetailsScreen.swift */; }; @@ -277,7 +278,6 @@ 8C454500B8073E1201F801A9 /* MXLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A34A814CBD56230BC74FFCF4 /* MXLogger.swift */; }; 8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; }; 8D3E1FADD78E72504DE0E402 /* UserAgentBuilderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB3B237387B8288A5A938F1B /* UserAgentBuilderTests.swift */; }; - 8D9F646387DF656EF91EE4CB /* RoomMessageFactoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96F37AB24AF5A006521D38D1 /* RoomMessageFactoryProtocol.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 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 7731767AE437BA3BD2CC14A8 /* Sentry */; }; @@ -367,6 +367,7 @@ B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */; }; B6F92EBE04D4AABF30B9E73A /* AnalyticsPromptModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA8BA82CF99D843FEF680E91 /* AnalyticsPromptModels.swift */; }; B80C4FABB5529DF12436FFDA /* AppIcon.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 16DC8C5B2991724903F1FA6A /* AppIcon.pdf */; }; + B8C316C6CA24512DFE9A27FD /* TimelineItemSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55AEEF8142DF1B59DB40FB93 /* TimelineItemSender.swift */; }; B94368839BDB69172E28E245 /* MXLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111B698739E3410E2CDB7144 /* MXLog.swift */; }; BA074E9812F96FFA3200ED1D /* TimelineItemProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */; }; BA31448FBD9697F8CB9A83CD /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E2245243369B99216C7D84E /* ImageCache.swift */; }; @@ -405,7 +406,6 @@ CF82143AA4A4F7BD11D22946 /* RoomTimelineViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */; }; D034A195A3494E38BF060485 /* MockSessionVerificationControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A9CCCF53495CF3D7B19FCE /* MockSessionVerificationControllerProxy.swift */; }; D05A193AE63030F2CFCE2E9C /* UITestScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6FE34A0A47D010BBB4D4D4 /* UITestScreenIdentifier.swift */; }; - D0619D2E6B9C511190FBEB95 /* RoomMessageProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607974D08BD2AF83725D817A /* RoomMessageProtocol.swift */; }; D2D70B5DB1A5E4AF0CD88330 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 033DB41C51865A2E83174E87 /* target.yml */; }; D33AC79A50DFC26D2498DD28 /* FileRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */; }; D59F046B15AA8E971053C1A6 /* RoomDetailsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 813B198AE8833FD12E5A9C78 /* RoomDetailsCoordinator.swift */; }; @@ -473,7 +473,6 @@ FBF09B6C900415800DDF2A21 /* EmojiProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C113E0CB7E15E9765B1817A /* EmojiProvider.swift */; }; FCD3F2B82CAB29A07887A127 /* KeychainAccess in Frameworks */ = {isa = PBXBuildFile; productRef = 2B43F2AF7456567FE37270A7 /* KeychainAccess */; }; FE4593FC2A02AAF92E089565 /* ElementAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF1593DD87F974F8509BB619 /* ElementAnimations.swift */; }; - FE79E2BCCF69E8BF4D21E15A /* RoomMessageFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA154570F693D93513E584C1 /* RoomMessageFactory.swift */; }; FE8D76708280968F7A670852 /* MockUserNotificationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9080CDD3881D0D1B2F280A7C /* MockUserNotificationController.swift */; }; FFD3E4FF948E06C7585317FC /* TimelineStyler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 892E29C98C4E8182C9037F84 /* TimelineStyler.swift */; }; /* End PBXBuildFile section */ @@ -715,6 +714,7 @@ 541542F5AC323709D8563458 /* AnalyticsPrompt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPrompt.swift; sourceTree = ""; }; 542D4F49FABA056DEEEB3400 /* RustTracing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RustTracing.swift; sourceTree = ""; }; 5445FCE0CE15E634FDC1A2E2 /* AnalyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsService.swift; sourceTree = ""; }; + 55AEEF8142DF1B59DB40FB93 /* TimelineItemSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemSender.swift; sourceTree = ""; }; 55BC11560C8A2598964FFA4C /* bs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bs; path = bs.lproj/Localizable.strings; sourceTree = ""; }; 55D7187F6B0C0A651AC3DFFA /* in */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = in; path = in.lproj/Localizable.strings; sourceTree = ""; }; 55EA4B03F92F31EAA83B3F7B /* FilePreviewModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePreviewModels.swift; sourceTree = ""; }; @@ -732,7 +732,6 @@ 5F4134FEFE4EB55759017408 /* UserSessionProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionProtocol.swift; sourceTree = ""; }; 5FF214969B25BFCBF87B908B /* bn-BD */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "bn-BD"; path = "bn-BD.lproj/Localizable.stringsdict"; sourceTree = ""; }; 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProxyProtocol.swift; sourceTree = ""; }; - 607974D08BD2AF83725D817A /* RoomMessageProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageProtocol.swift; sourceTree = ""; }; 612EF972F2A1800682D32C5E /* StickerRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerRoomTimelineView.swift; sourceTree = ""; }; 616197D81103330BF2ADD559 /* gl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = gl; path = gl.lproj/Localizable.strings; sourceTree = ""; }; 624244C398804ADC885239AA /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; @@ -825,7 +824,6 @@ 94BCC8A9C73C1F838122C645 /* TimelineItemPlainStylerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemPlainStylerView.swift; sourceTree = ""; }; 96561CC53F7C1E24D4C292E4 /* MockNotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockNotificationManager.swift; sourceTree = ""; }; 96C4762F8D6112E43117DB2F /* CustomStringConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomStringConvertible.swift; sourceTree = ""; }; - 96F37AB24AF5A006521D38D1 /* RoomMessageFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageFactoryProtocol.swift; sourceTree = ""; }; 9772C1D2223108EB3131AEE4 /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = ""; }; 97755C01C3971474EFAD5367 /* AuthenticationIconImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationIconImage.swift; sourceTree = ""; }; 97F893DBB5F88D746C6DCDE5 /* ku */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ku; path = ku.lproj/Localizable.strings; sourceTree = ""; }; @@ -1051,7 +1049,6 @@ F9212AE02CBDD692C56A879F /* TimelineTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewController.swift; sourceTree = ""; }; F9E785D5137510481733A3E8 /* TextRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextRoomTimelineView.swift; sourceTree = ""; }; F9ED8E731E21055F728E5FED /* TimelineStartRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStartRoomTimelineView.swift; sourceTree = ""; }; - FA154570F693D93513E584C1 /* RoomMessageFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageFactory.swift; sourceTree = ""; }; FAB10E673916D2B8D21FD197 /* TemplateModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateModels.swift; sourceTree = ""; }; FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorProtocol.swift; sourceTree = ""; }; FC3D31C2DA6910AA0079678A /* MediaProxyProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProxyProtocol.swift; sourceTree = ""; }; @@ -1398,11 +1395,8 @@ children = ( 3ACBDC1D28EFB7789EB467E0 /* MockRoomProxy.swift */, 9D1790942BE4FE0D8273191B /* RoomMemberProxy.swift */, - FA154570F693D93513E584C1 /* RoomMessageFactory.swift */, - 96F37AB24AF5A006521D38D1 /* RoomMessageFactoryProtocol.swift */, A65F140F9FE5E8D4DAEFF354 /* RoomProxy.swift */, 47111410B6E659A697D472B5 /* RoomProxyProtocol.swift */, - 4658A940E89BC42EE3346A97 /* Messages */, 70DABA39C844CA931B829395 /* RoomSummary */, ); path = Room; @@ -1435,14 +1429,6 @@ path = View; sourceTree = ""; }; - 4658A940E89BC42EE3346A97 /* Messages */ = { - isa = PBXGroup; - children = ( - 607974D08BD2AF83725D817A /* RoomMessageProtocol.swift */, - ); - path = Messages; - sourceTree = ""; - }; 490F49F5627FBEF3BB8665A3 /* SimpleScreenExample */ = { isa = PBXGroup; children = ( @@ -2436,6 +2422,7 @@ 66F2402D738694F98729A441 /* RoomTimelineProvider.swift */, 095AED4CF56DFF3EB7BB84C8 /* RoomTimelineProviderProtocol.swift */, 2D505843AB66822EB91F0DF0 /* TimelineItemProxy.swift */, + 55AEEF8142DF1B59DB40FB93 /* TimelineItemSender.swift */, 3EA31CC7012EA2A5653DAFC9 /* Fixtures */, 2F2FED77226A43559F009463 /* TimelineController */, 6B0910BCE4F1B02F124E1A09 /* TimelineItemContent */, @@ -2882,6 +2869,7 @@ 7354D094A4C59B555F407FA1 /* RustTracing.swift in Sources */, 719E7AAD1F8E68F68F30FECD /* Task.swift in Sources */, BA074E9812F96FFA3200ED1D /* TimelineItemProxy.swift in Sources */, + B8C316C6CA24512DFE9A27FD /* TimelineItemSender.swift in Sources */, 6126CC51654E159804999E6A /* UNMutableNotificationContent.swift in Sources */, 518C93DC6516D3D018DE065F /* UNNotificationRequest.swift in Sources */, 06B55882911B4BF5B14E9851 /* URL.swift in Sources */, @@ -3126,9 +3114,6 @@ DEC6778FB8CFB829D3E012AC /* RoomMemberDetailsViewModel.swift in Sources */, 0DB01C67B68CB26E5B3A21AF /* RoomMemberDetailsViewModelProtocol.swift in Sources */, BEEEB659A0BA510D7BE6345C /* RoomMemberProxy.swift in Sources */, - FE79E2BCCF69E8BF4D21E15A /* RoomMessageFactory.swift in Sources */, - 8D9F646387DF656EF91EE4CB /* RoomMessageFactoryProtocol.swift in Sources */, - D0619D2E6B9C511190FBEB95 /* RoomMessageProtocol.swift in Sources */, 4FC1EFE4968A259CBBACFAFB /* RoomProxy.swift in Sources */, FA9C427FFB11B1AA2DCC5602 /* RoomProxyProtocol.swift in Sources */, C55A44C99F64A479ABA85B46 /* RoomScreen.swift in Sources */, @@ -3206,6 +3191,7 @@ 01CB8ACFA5E143E89C168CA8 /* TimelineItemContextMenu.swift in Sources */, F508683B76EF7B23BB2CBD6D /* TimelineItemPlainStylerView.swift in Sources */, 440123E29E2F9B001A775BBE /* TimelineItemProxy.swift in Sources */, + 1B88BB631F7FC45A213BB554 /* TimelineItemSender.swift in Sources */, 9B582B3EEFEA615D4A6FBF1A /* TimelineReactionsView.swift in Sources */, ABF3FAB234AD3565B214309B /* TimelineSenderAvatarView.swift in Sources */, C4FE0E11A907C8999F92D5A8 /* TimelineStartRoomTimelineItem.swift in Sources */, @@ -3933,7 +3919,7 @@ repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = "1.0.30-alpha"; + version = "1.0.32-alpha"; }; }; 96495DD8554E2F39D3954354 /* XCRemoteSwiftPackageReference "posthog-ios" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e4da89900..34adb22c6 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-rust-components-swift", "state" : { - "revision" : "08db830ef3f0ab24f39a95705179713ea5382f9c", - "version" : "1.0.30-alpha" + "revision" : "a119a8f16bbe3adc34cfdf2e092886d44e01705a", + "version" : "1.0.32-alpha" } }, { diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift index 6026d1695..992024e4f 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift @@ -101,7 +101,7 @@ struct HomeScreenRoom: Identifiable, Equatable { var lastMessage: AttributedString? - var avatarURLString: String? + var avatarURL: URL? var avatar: UIImage? diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 0fdfa9bae..5bf961215 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -68,8 +68,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol .store(in: &cancellables) Task { - if case let .success(userAvatarURLString) = await userSession.clientProxy.loadUserAvatarURLString() { - if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, avatarSize: .user(on: .home)) { + if case let .success(url) = await userSession.clientProxy.loadUserAvatarURL() { + if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURL(url, avatarSize: .user(on: .home)) { state.userAvatar = avatar } } @@ -169,12 +169,12 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol private func loadDataForRoomIdentifier(_ identifier: String) { guard let room = state.rooms.first(where: { $0.roomId == identifier }), room.avatar == nil, - let avatarURLString = room.avatarURLString else { + let avatarURL = room.avatarURL else { return } Task { - if case let .success(image) = await userSession.mediaProvider.loadImageFromURLString(avatarURLString, avatarSize: .room(on: .home)) { + if case let .success(image) = await userSession.mediaProvider.loadImageFromURL(avatarURL, avatarSize: .room(on: .home)) { guard let roomIndex = state.rooms.firstIndex(where: { $0.roomId == identifier }) else { return } @@ -222,7 +222,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol } private func buildRoom(with details: RoomSummaryDetails, invalidated: Bool) -> HomeScreenRoom { - let avatarImage = userSession.mediaProvider.imageFromURLString(details.avatarURLString, avatarSize: .room(on: .home)) + let avatarImage = details.avatarURL.flatMap { userSession.mediaProvider.imageFromURL($0, avatarSize: .room(on: .home)) } var timestamp: String? if let lastMessageTimestamp = details.lastMessageTimestamp { @@ -236,7 +236,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol hasUnreads: details.unreadNotificationCount > 0, timestamp: timestamp, lastMessage: details.lastMessage, - avatarURLString: details.avatarURLString, + avatarURL: details.avatarURL, avatar: avatarImage) } diff --git a/ElementX/Sources/Screens/RoomDetails/RoomDetailsModels.swift b/ElementX/Sources/Screens/RoomDetails/RoomDetailsModels.swift index 04f85f6c5..281b51722 100644 --- a/ElementX/Sources/Screens/RoomDetails/RoomDetailsModels.swift +++ b/ElementX/Sources/Screens/RoomDetails/RoomDetailsModels.swift @@ -61,13 +61,13 @@ enum RoomDetailsViewAction { struct RoomDetailsMember: Identifiable, Equatable { let id: String let name: String? - let avatarUrl: String? + let avatarURL: URL? // cached var avatar: UIImage? init(withProxy proxy: RoomMemberProxy) { id = proxy.userId name = proxy.displayName - avatarUrl = proxy.avatarUrl + avatarURL = proxy.avatarURL } } diff --git a/ElementX/Sources/Screens/RoomDetails/RoomDetailsViewModel.swift b/ElementX/Sources/Screens/RoomDetails/RoomDetailsViewModel.swift index 0b1face92..e30a51559 100644 --- a/ElementX/Sources/Screens/RoomDetails/RoomDetailsViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetails/RoomDetailsViewModel.swift @@ -54,8 +54,7 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc if let avatarURL = roomProxy.avatarURL { Task { - if case let .success(avatar) = await mediaProvider.loadImageFromURLString(avatarURL, - avatarSize: .room(on: .details)) { + if case let .success(avatar) = await mediaProvider.loadImageFromURL(avatarURL, avatarSize: .room(on: .details)) { state.roomAvatar = avatar } } diff --git a/ElementX/Sources/Screens/RoomMembers/RoomMemberDetailsViewModel.swift b/ElementX/Sources/Screens/RoomMembers/RoomMemberDetailsViewModel.swift index d25b59c4c..f20c721db 100644 --- a/ElementX/Sources/Screens/RoomMembers/RoomMemberDetailsViewModel.swift +++ b/ElementX/Sources/Screens/RoomMembers/RoomMemberDetailsViewModel.swift @@ -46,15 +46,14 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta return } if member.avatar != nil { - // already loaded + // Avatar already loaded. return } - guard let avatarUrl = member.avatarUrl else { - // user has no avatar + guard let avatarURL = member.avatarURL else { return } - switch await mediaProvider.loadImageFromURLString(avatarUrl, avatarSize: .user(on: .roomDetails)) { + switch await mediaProvider.loadImageFromURL(avatarURL, avatarSize: .user(on: .roomDetails)) { case .success(let image): if let index = state.members.firstIndex(where: { $0.id == memberId }) { state.members[index].avatar = image diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index bf2f933b4..a209ef862 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -36,7 +36,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol timelineViewFactory: RoomTimelineViewFactoryProtocol, mediaProvider: MediaProviderProtocol, roomName: String?, - roomAvatarUrl: String? = nil) { + roomAvatarUrl: URL? = nil) { self.timelineController = timelineController self.timelineViewFactory = timelineViewFactory self.mediaProvider = mediaProvider @@ -76,11 +76,11 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol state.contextMenuBuilder = buildContexMenuForItemId(_:) buildTimelineViews() - + if let roomAvatarUrl { Task { - if case let .success(avatar) = await mediaProvider.loadImageFromURLString(roomAvatarUrl, - avatarSize: .room(on: .timeline)) { + if case let .success(avatar) = await mediaProvider.loadImageFromURL(roomAvatarUrl, + avatarSize: .room(on: .timeline)) { state.roomAvatar = avatar } } @@ -261,7 +261,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol } case .reply: state.bindings.composerFocused = true - state.composerMode = .reply(id: item.id, displayName: item.senderDisplayName ?? item.senderId) + state.composerMode = .reply(id: item.id, displayName: item.sender.displayName ?? item.sender.id) case .viewSource: let debugDescription = timelineController.debugDescriptionFor(item.id) MXLog.info(debugDescription) diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift index 88a62e8a3..b734d550b 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomHeaderView.swift @@ -71,7 +71,7 @@ struct RoomHeaderView_Previews: PreviewProvider { timelineViewFactory: RoomTimelineViewFactory(), mediaProvider: MockMediaProvider(), roomName: "Some Room name", - roomAvatarUrl: "mock_url") + roomAvatarUrl: URL.picturesDirectory) RoomHeaderView(context: viewModel.context) .previewLayout(.sizeThatFits) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift index cf42b6e87..fd78cef66 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift @@ -60,7 +60,7 @@ struct TimelineItemBubbledStylerView: View { HStack(alignment: .top, spacing: 4) { TimelineSenderAvatarView(timelineItem: timelineItem) .accessibilityHidden(true) - Text(timelineItem.senderDisplayName ?? timelineItem.senderId) + Text(timelineItem.sender.displayName ?? timelineItem.sender.id) .font(.element.footnoteBold) .foregroundColor(.element.primaryContent) .lineLimit(1) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift index 4549e01df..11608c941 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemPlainStylerView.swift @@ -47,7 +47,7 @@ struct TimelineItemPlainStylerView: View { if timelineItem.shouldShowSenderDetails { HStack { TimelineSenderAvatarView(timelineItem: timelineItem) - Text(timelineItem.senderDisplayName ?? timelineItem.senderId) + Text(timelineItem.sender.displayName ?? timelineItem.sender.id) .font(.body) .foregroundColor(.element.primaryContent) .fontWeight(.semibold) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift index a1ddd121b..f75be89e4 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EmoteRoomTimelineView.swift @@ -22,13 +22,10 @@ struct EmoteRoomTimelineView: View { var body: some View { TimelineStyler(timelineItem: timelineItem) { - HStack(alignment: .top) { - Image(systemName: "face.dashed").padding(.top, 1.0) - if let attributedComponents = timelineItem.attributedComponents { - FormattedBodyText(attributedComponents: attributedComponents) - } else { - FormattedBodyText(text: timelineItem.text) - } + if let attributedComponents = timelineItem.attributedComponents { + FormattedBodyText(attributedComponents: attributedComponents) + } else { + FormattedBodyText(text: timelineItem.text) } } } @@ -59,6 +56,6 @@ struct EmoteRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: senderId) + sender: .init(id: senderId)) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedRoomTimelineView.swift index d587092b7..10961786b 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/EncryptedRoomTimelineView.swift @@ -81,6 +81,6 @@ struct EncryptedRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: isOutgoing, isEditable: false, - senderId: senderId) + sender: .init(id: senderId)) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FileRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FileRoomTimelineView.swift index 25c1ee6c7..e91373831 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FileRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FileRoomTimelineView.swift @@ -47,7 +47,7 @@ struct FileRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "Bob", + sender: .init(id: "Bob"), source: nil, thumbnailSource: nil)) @@ -57,7 +57,7 @@ struct FileRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "Bob", + sender: .init(id: "Bob"), source: nil, thumbnailSource: nil)) @@ -67,7 +67,7 @@ struct FileRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "Bob", + sender: .init(id: "Bob"), source: nil, thumbnailSource: nil)) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift index 51a6dea83..1e26e8f6d 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/ImageRoomTimelineView.swift @@ -62,7 +62,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "Bob", + sender: .init(id: "Bob"), source: nil, image: UIImage(systemName: "photo"))) @@ -72,7 +72,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "Bob", + sender: .init(id: "Bob"), source: nil, image: nil)) @@ -82,7 +82,7 @@ struct ImageRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "Bob", + sender: .init(id: "Bob"), source: nil, image: nil, aspectRatio: 0.7, diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift index 1818a528e..09fa1aae7 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/NoticeRoomTimelineView.swift @@ -59,6 +59,6 @@ struct NoticeRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: senderId) + sender: .init(id: senderId)) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/RedactedRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/RedactedRoomTimelineView.swift index 91d292628..daf16497a 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/RedactedRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/RedactedRoomTimelineView.swift @@ -46,6 +46,6 @@ struct RedactedRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: senderId) + sender: .init(id: senderId)) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/StickerRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/StickerRoomTimelineView.swift index ade3f1ad3..4a5cddcee 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/StickerRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/StickerRoomTimelineView.swift @@ -63,8 +63,8 @@ struct StickerRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "Bob", - urlString: nil, + sender: .init(id: "Bob"), + imageURL: nil, image: UIImage(systemName: "photo"))) StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: UUID().uuidString, @@ -73,8 +73,8 @@ struct StickerRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "Bob", - urlString: nil, + sender: .init(id: "Bob"), + imageURL: nil, image: nil)) StickerRoomTimelineView(timelineItem: StickerRoomTimelineItem(id: UUID().uuidString, @@ -83,8 +83,8 @@ struct StickerRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "Bob", - urlString: nil, + sender: .init(id: "Bob"), + imageURL: nil, image: nil, aspectRatio: 0.7, blurhash: "L%KUc%kqS$RP?Ks,WEf8OlrqaekW")) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift index c6d587641..a8bf63e8b 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/TextRoomTimelineView.swift @@ -68,6 +68,6 @@ struct TextRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: isOutgoing, isEditable: isOutgoing, - senderId: senderId) + sender: .init(id: senderId)) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/UnsupportedRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/UnsupportedRoomTimelineView.swift index 564b01023..f127c823d 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/UnsupportedRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/UnsupportedRoomTimelineView.swift @@ -69,6 +69,6 @@ struct UnsupportedRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: isOutgoing, isEditable: false, - senderId: senderId) + sender: .init(id: senderId)) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/VideoRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/VideoRoomTimelineView.swift index 41e71b236..9d0e6905f 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/VideoRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/VideoRoomTimelineView.swift @@ -72,7 +72,7 @@ struct VideoRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "Bob", + sender: .init(id: "Bob"), duration: 21, source: nil, thumbnailSource: nil, @@ -84,7 +84,7 @@ struct VideoRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "Bob", + sender: .init(id: "Bob"), duration: 22, source: nil, thumbnailSource: nil, @@ -96,7 +96,7 @@ struct VideoRoomTimelineView_Previews: PreviewProvider { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "Bob", + sender: .init(id: "Bob"), duration: 23, source: nil, thumbnailSource: nil, diff --git a/ElementX/Sources/Screens/RoomScreen/View/TimelineSenderAvatarView.swift b/ElementX/Sources/Screens/RoomScreen/View/TimelineSenderAvatarView.swift index e605a7944..2dd752d45 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/TimelineSenderAvatarView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/TimelineSenderAvatarView.swift @@ -24,14 +24,14 @@ struct TimelineSenderAvatarView: View { var body: some View { ZStack(alignment: .center) { - if let avatar = timelineItem.senderAvatar { + if let avatar = timelineItem.sender.avatar { Image(uiImage: avatar) .resizable() .scaledToFill() .overlay(Circle().stroke(Color.element.accent)) } else { - PlaceholderAvatarImage(text: timelineItem.senderDisplayName ?? timelineItem.senderId, - contentId: timelineItem.senderId) + PlaceholderAvatarImage(text: timelineItem.sender.displayName ?? timelineItem.sender.id, + contentId: timelineItem.sender.id) } } .clipShape(Circle()) @@ -41,6 +41,6 @@ struct TimelineSenderAvatarView: View { .stroke(Color.element.background, lineWidth: 3) ) - .animation(.elementDefault, value: timelineItem.senderAvatar) + .animation(.elementDefault, value: timelineItem.sender.avatar) } } diff --git a/ElementX/Sources/Screens/Settings/SettingsViewModel.swift b/ElementX/Sources/Screens/Settings/SettingsViewModel.swift index 221cd2af6..eff90dfea 100644 --- a/ElementX/Sources/Screens/Settings/SettingsViewModel.swift +++ b/ElementX/Sources/Screens/Settings/SettingsViewModel.swift @@ -27,22 +27,22 @@ class SettingsViewModel: SettingsViewModelType, SettingsViewModelProtocol { self.userSession = userSession let bindings = SettingsViewStateBindings() super.init(initialViewState: .init(bindings: bindings, deviceID: userSession.deviceId, userID: userSession.userID)) - + Task { - if case let .success(userAvatarURLString) = await userSession.clientProxy.loadUserAvatarURLString() { - if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURLString(userAvatarURLString, avatarSize: .user(on: .settings)) { + if case let .success(userAvatarURL) = await userSession.clientProxy.loadUserAvatarURL() { + if case let .success(avatar) = await userSession.mediaProvider.loadImageFromURL(userAvatarURL, avatarSize: .user(on: .settings)) { state.userAvatar = avatar } } } - + Task { if case let .success(userDisplayName) = await self.userSession.clientProxy.loadUserDisplayName() { state.userDisplayName = userDisplayName } } } - + override func process(viewAction: SettingsViewAction) async { switch viewAction { case .close: diff --git a/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift b/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift index 5e8079f2f..6e49a6853 100644 --- a/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift +++ b/ElementX/Sources/Services/Authentication/AuthenticationServiceProxy.swift @@ -26,7 +26,7 @@ class AuthenticationServiceProxy: AuthenticationServiceProxyProtocol { init(userSessionStore: UserSessionStoreProtocol) { self.userSessionStore = userSessionStore - authenticationService = AuthenticationService(basePath: userSessionStore.baseDirectory.path) + authenticationService = AuthenticationService(basePath: userSessionStore.baseDirectory.path, passphrase: nil) } // MARK: - Public diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index a3be6dfd7..acc3bc4b1 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -158,12 +158,18 @@ class ClientProxy: ClientProxyProtocol { } } } - - func loadUserAvatarURLString() async -> Result { + + func loadUserAvatarURL() async -> Result { await Task.dispatch(on: clientQueue) { do { - let avatarURL = try self.client.avatarUrl() - return .success(avatarURL) + let urlString = try self.client.avatarUrl() + + guard let url = URL(string: urlString) else { + MXLog.error("Invalid avatar URL string: \(String(describing: urlString))") + return .failure(.failedRetrievingAvatarURL) + } + + return .success(url) } catch { return .failure(.failedRetrievingAvatarURL) } @@ -211,7 +217,7 @@ class ClientProxy: ClientProxyProtocol { deviceDisplayName: String, profileTag: String?, lang: String, - url: String?, + url: URL?, format: PushFormat?, defaultPayload: [AnyHashable: Any]?) async throws { // let defaultPayloadString = jsonString(from: defaultPayload) @@ -312,11 +318,9 @@ class ClientProxy: ClientProxyProtocol { let allRoomsViewProxy = SlidingSyncViewProxy(slidingSync: slidingSync, slidingSyncView: allRoomsView) - visibleRoomsSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: visibleRoomsViewProxy, - roomMessageFactory: RoomMessageFactory()) + visibleRoomsSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: visibleRoomsViewProxy) - allRoomsSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: allRoomsViewProxy, - roomMessageFactory: RoomMessageFactory()) + allRoomsSummaryProvider = RoomSummaryProvider(slidingSyncViewProxy: allRoomsViewProxy) visibleRoomsViewProxy.visibleRangeUpdatePublisher.sink { [weak self] in self?.restartSync() @@ -390,8 +394,8 @@ class ClientProxy: ClientProxyProtocol { } extension ClientProxy: MediaProxyProtocol { - func mediaSourceForURLString(_ urlString: String) -> MediaSourceProxy { - mediaProxy.mediaSourceForURLString(urlString) + func mediaSourceForURL(_ url: URL) -> MediaSourceProxy { + mediaProxy.mediaSourceForURL(url) } func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data { diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index ede21e330..ec37f90c5 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -83,7 +83,7 @@ protocol ClientProxyProtocol: AnyObject, MediaProxyProtocol { func loadUserDisplayName() async -> Result - func loadUserAvatarURLString() async -> Result + func loadUserAvatarURL() async -> Result func accountDataEvent(type: String) async -> Result @@ -101,7 +101,7 @@ protocol ClientProxyProtocol: AnyObject, MediaProxyProtocol { deviceDisplayName: String, profileTag: String?, lang: String, - url: String?, + url: URL?, format: PushFormat?, defaultPayload: [AnyHashable: Any]?) async throws } diff --git a/ElementX/Sources/Services/Client/MockClientProxy.swift b/ElementX/Sources/Services/Client/MockClientProxy.swift index c47ab0f05..cdac27380 100644 --- a/ElementX/Sources/Services/Client/MockClientProxy.swift +++ b/ElementX/Sources/Services/Client/MockClientProxy.swift @@ -57,7 +57,7 @@ class MockClientProxy: ClientProxyProtocol { .success("User display name") } - func loadUserAvatarURLString() async -> Result { + func loadUserAvatarURL() async -> Result { .failure(.failedRetrievingAvatarURL) } @@ -69,8 +69,8 @@ class MockClientProxy: ClientProxyProtocol { .failure(.failedSettingAccountData) } - func mediaSourceForURLString(_ urlString: String) -> MediaSourceProxy { - .init(urlString: urlString) + func mediaSourceForURL(_ url: URL) -> MediaSourceProxy { + .init(url: url) } func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data { @@ -103,7 +103,7 @@ class MockClientProxy: ClientProxyProtocol { var setPusherDeviceDisplayName: String? var setPusherProfileTag: String? var setPusherLang: String? - var setPusherUrl: String? + var setPusherUrl: URL? var setPusherFormat: PushFormat? var setPusherDefaultPayload: [AnyHashable: Any]? // swiftlint:disable:next function_parameter_count @@ -114,7 +114,7 @@ class MockClientProxy: ClientProxyProtocol { deviceDisplayName: String, profileTag: String?, lang: String, - url: String?, + url: URL?, format: PushFormat?, defaultPayload: [AnyHashable: Any]?) async throws { if let setPusherErrorToThrow { throw setPusherErrorToThrow } diff --git a/ElementX/Sources/Services/Media/MediaProvider.swift b/ElementX/Sources/Services/Media/MediaProvider.swift index f5a0cd148..663a1e342 100644 --- a/ElementX/Sources/Services/Media/MediaProvider.swift +++ b/ElementX/Sources/Services/Media/MediaProvider.swift @@ -34,36 +34,37 @@ struct MediaProvider: MediaProviderProtocol { } func imageFromSource(_ source: MediaSourceProxy?, avatarSize: AvatarSize?) -> UIImage? { - guard let source else { + guard let url = source?.url else { return nil } - let cacheKey = cacheKeyForURLString(source.url, avatarSize: avatarSize) + let cacheKey = cacheKeyForURL(url, avatarSize: avatarSize) return imageCache.retrieveImageInMemoryCache(forKey: cacheKey, options: nil) } - func imageFromURLString(_ urlString: String?, avatarSize: AvatarSize?) -> UIImage? { - guard let urlString else { + func imageFromURL(_ url: URL?, avatarSize: AvatarSize?) -> UIImage? { + guard let url else { return nil } - return imageFromSource(.init(urlString: urlString), avatarSize: avatarSize) + return imageFromSource(.init(url: url), avatarSize: avatarSize) } - func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result { - await loadImageFromSource(.init(urlString: urlString), avatarSize: avatarSize) + func loadImageFromURL(_ url: URL, avatarSize: AvatarSize?) async -> Result { + await loadImageFromSource(.init(url: url), avatarSize: avatarSize) } func loadImageFromSource(_ source: MediaSourceProxy, avatarSize: AvatarSize?) async -> Result { if let image = imageFromSource(source, avatarSize: avatarSize) { return .success(image) } - + + #warning("Media loading should check for existing in flight operations and de-dupe requests.") let loadImageBgTask = await backgroundTaskService?.startBackgroundTask(withName: "LoadImage: \(source.url.hashValue)") defer { loadImageBgTask?.stop() } - let cacheKey = cacheKeyForURLString(source.url, avatarSize: avatarSize) + let cacheKey = cacheKeyForURL(source.url, avatarSize: avatarSize) if case let .success(cacheResult) = await imageCache.retrieveImage(forKey: cacheKey), let image = cacheResult.image { @@ -96,7 +97,7 @@ struct MediaProvider: MediaProviderProtocol { guard let source else { return nil } - let cacheKey = fileCacheKeyForURLString(source.url) + let cacheKey = fileCacheKeyForURL(source.url) return fileCache.file(forKey: cacheKey, fileExtension: fileExtension) } @@ -110,11 +111,11 @@ struct MediaProvider: MediaProviderProtocol { loadFileBgTask?.stop() } - let cacheKey = fileCacheKeyForURLString(source.url) - + let cacheKey = fileCacheKeyForURL(source.url) + do { let data = try await mediaProxy.loadMediaContentForSource(source) - + let url = try fileCache.store(data, with: fileExtension, forKey: cacheKey) return .success(url) } catch { @@ -122,34 +123,33 @@ struct MediaProvider: MediaProviderProtocol { return .failure(.failedRetrievingImage) } } - - func fileFromURLString(_ urlString: String?, fileExtension: String) -> URL? { - guard let urlString else { + + func fileFromURL(_ url: URL?, fileExtension: String) -> URL? { + guard let url else { return nil } - - return fileFromSource(MediaSourceProxy(urlString: urlString), - fileExtension: fileExtension) + + return fileFromSource(MediaSourceProxy(url: url), fileExtension: fileExtension) } - - func loadFileFromURLString(_ urlString: String, fileExtension: String) async -> Result { - await loadFileFromSource(MediaSourceProxy(urlString: urlString), - fileExtension: fileExtension) + + func loadFileFromURL(_ url: URL, fileExtension: String) async -> Result { + await loadFileFromSource(MediaSourceProxy(url: url), fileExtension: fileExtension) } // MARK: - Private - private func cacheKeyForURLString(_ urlString: String, avatarSize: AvatarSize?) -> String { + private func cacheKeyForURL(_ url: URL, avatarSize: AvatarSize?) -> String { if let avatarSize { - return "\(urlString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" + return "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" } else { - return urlString + return url.absoluteString } } - private func fileCacheKeyForURLString(_ urlString: String) -> String { - guard let component = urlString.split(separator: "/").last else { - return urlString + private func fileCacheKeyForURL(_ url: URL) -> String { + let component = url.lastPathComponent + guard !component.isEmpty else { + return url.absoluteString } return String(component) } diff --git a/ElementX/Sources/Services/Media/MediaProviderProtocol.swift b/ElementX/Sources/Services/Media/MediaProviderProtocol.swift index 1acd3db71..9e3899a1d 100644 --- a/ElementX/Sources/Services/Media/MediaProviderProtocol.swift +++ b/ElementX/Sources/Services/Media/MediaProviderProtocol.swift @@ -28,17 +28,17 @@ protocol MediaProviderProtocol { @discardableResult func loadImageFromSource(_ source: MediaSourceProxy, avatarSize: AvatarSize?) async -> Result - func imageFromURLString(_ urlString: String?, avatarSize: AvatarSize?) -> UIImage? + func imageFromURL(_ url: URL?, avatarSize: AvatarSize?) -> UIImage? - @discardableResult func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result + @discardableResult func loadImageFromURL(_ url: URL, avatarSize: AvatarSize?) async -> Result func fileFromSource(_ source: MediaSourceProxy?, fileExtension: String) -> URL? @discardableResult func loadFileFromSource(_ source: MediaSourceProxy, fileExtension: String) async -> Result - func fileFromURLString(_ urlString: String?, fileExtension: String) -> URL? + func fileFromURL(_ url: URL?, fileExtension: String) -> URL? - @discardableResult func loadFileFromURLString(_ urlString: String, fileExtension: String) async -> Result + @discardableResult func loadFileFromURL(_ url: URL, fileExtension: String) async -> Result } extension MediaProviderProtocol { @@ -50,11 +50,11 @@ extension MediaProviderProtocol { await loadImageFromSource(source, avatarSize: nil) } - func imageFromURLString(_ urlString: String?) -> UIImage? { - imageFromURLString(urlString, avatarSize: nil) + func imageFromURL(_ url: URL?) -> UIImage? { + imageFromURL(url, avatarSize: nil) } - @discardableResult func loadImageFromURLString(_ urlString: String) async -> Result { - await loadImageFromURLString(urlString, avatarSize: nil) + @discardableResult func loadImageFromURL(_ url: URL) async -> Result { + await loadImageFromURL(url, avatarSize: nil) } } diff --git a/ElementX/Sources/Services/Media/MediaProxy.swift b/ElementX/Sources/Services/Media/MediaProxy.swift index a5e6caf49..6fd70c2b7 100644 --- a/ElementX/Sources/Services/Media/MediaProxy.swift +++ b/ElementX/Sources/Services/Media/MediaProxy.swift @@ -29,8 +29,8 @@ class MediaProxy: MediaProxyProtocol { self.clientQueue = clientQueue } - func mediaSourceForURLString(_ urlString: String) -> MediaSourceProxy { - .init(urlString: urlString) + func mediaSourceForURL(_ url: URL) -> MediaSourceProxy { + .init(url: url) } func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data { diff --git a/ElementX/Sources/Services/Media/MediaProxyProtocol.swift b/ElementX/Sources/Services/Media/MediaProxyProtocol.swift index 69e22ce05..4acbb7815 100644 --- a/ElementX/Sources/Services/Media/MediaProxyProtocol.swift +++ b/ElementX/Sources/Services/Media/MediaProxyProtocol.swift @@ -17,7 +17,7 @@ import Foundation protocol MediaProxyProtocol { - func mediaSourceForURLString(_ urlString: String) -> MediaSourceProxy + func mediaSourceForURL(_ url: URL) -> MediaSourceProxy func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data diff --git a/ElementX/Sources/Services/Media/MediaSourceProxy.swift b/ElementX/Sources/Services/Media/MediaSourceProxy.swift index 771b46c1e..ba9ca38c0 100644 --- a/ElementX/Sources/Services/Media/MediaSourceProxy.swift +++ b/ElementX/Sources/Services/Media/MediaSourceProxy.swift @@ -24,12 +24,13 @@ struct MediaSourceProxy: Hashable { underlyingSource = source } - init(urlString: String) { - underlyingSource = mediaSourceFromUrl(url: urlString) + init(url: URL) { + underlyingSource = mediaSourceFromUrl(url: url.absoluteString) } - var url: String { - underlyingSource.url() + var url: URL! { + // Expecting these to always be valid URLs + URL(string: underlyingSource.url()) } } diff --git a/ElementX/Sources/Services/Media/MockMediaProvider.swift b/ElementX/Sources/Services/Media/MockMediaProvider.swift index e6eaf2614..61f3d3830 100644 --- a/ElementX/Sources/Services/Media/MockMediaProvider.swift +++ b/ElementX/Sources/Services/Media/MockMediaProvider.swift @@ -26,11 +26,11 @@ struct MockMediaProvider: MediaProviderProtocol { .failure(.failedRetrievingImage) } - func imageFromURLString(_ urlString: String?, avatarSize: AvatarSize?) -> UIImage? { - guard urlString != nil else { + func imageFromURL(_ url: URL?, avatarSize: AvatarSize?) -> UIImage? { + guard url != nil else { return nil } - + if let avatarSize { switch avatarSize { case .room: @@ -41,8 +41,8 @@ struct MockMediaProvider: MediaProviderProtocol { } return UIImage(systemName: "photo") } - - func loadImageFromURLString(_ urlString: String, avatarSize: AvatarSize?) async -> Result { + + func loadImageFromURL(_ url: URL, avatarSize: AvatarSize?) async -> Result { guard let image = UIImage(systemName: "photo") else { fatalError() } @@ -58,11 +58,11 @@ struct MockMediaProvider: MediaProviderProtocol { .failure(.failedRetrievingFile) } - func fileFromURLString(_ urlString: String?, fileExtension: String) -> URL? { + func fileFromURL(_ url: URL?, fileExtension: String) -> URL? { nil } - @discardableResult func loadFileFromURLString(_ urlString: String, fileExtension: String) async -> Result { + @discardableResult func loadFileFromURL(_ url: URL, fileExtension: String) async -> Result { .failure(.failedRetrievingFile) } } diff --git a/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift b/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift index 6940f1ca9..cb0495c46 100644 --- a/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift +++ b/ElementX/Sources/Services/Notification/Manager/NotificationManager.swift @@ -91,7 +91,7 @@ class NotificationManager: NSObject, NotificationManagerProtocol { deviceDisplayName: UIDevice.current.name, profileTag: pusherProfileTag(), lang: Bundle.preferredLanguages.first ?? "en", - url: ServiceLocator.shared.settings.pushGatewayBaseURL.absoluteString, + url: ServiceLocator.shared.settings.pushGatewayBaseURL, format: .eventIdOnly, defaultPayload: [ "aps": [ diff --git a/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxy.swift b/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxy.swift index 740655139..03f76f01b 100644 --- a/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxy.swift +++ b/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxy.swift @@ -40,7 +40,7 @@ struct NotificationItemProxy { // notificationItem.isNoisy // } // -// var avatarUrl: String? { +// var avatarURL: URL? { // notificationItem.avatarUrl // } // @@ -63,7 +63,7 @@ struct NotificationItemProxy { true } - var avatarUrl: String? { + var avatarURL: URL? { nil } diff --git a/ElementX/Sources/Services/Room/MockRoomProxy.swift b/ElementX/Sources/Services/Room/MockRoomProxy.swift index 4a0291687..8209717e6 100644 --- a/ElementX/Sources/Services/Room/MockRoomProxy.swift +++ b/ElementX/Sources/Services/Room/MockRoomProxy.swift @@ -23,7 +23,7 @@ struct MockRoomProxy: RoomProxyProtocol { let name: String? = nil let displayName: String? var topic: String? - var avatarURL: String? + var avatarURL: URL? var isDirect = Bool.random() var isSpace = Bool.random() var isPublic = Bool.random() @@ -37,11 +37,11 @@ struct MockRoomProxy: RoomProxyProtocol { .failure(.failedRetrievingMemberDisplayName) } - func avatarURLStringForUserId(_ userId: String) -> String? { + func avatarURLForUserId(_ userId: String) -> URL? { nil } - func loadAvatarURLForUserId(_ userId: String) async -> Result { + func loadAvatarURLForUserId(_ userId: String) async -> Result { .failure(.failedRetrievingMemberAvatarURL) } diff --git a/ElementX/Sources/Services/Room/RoomMemberProxy.swift b/ElementX/Sources/Services/Room/RoomMemberProxy.swift index 6840b9b7b..b2cdfac84 100644 --- a/ElementX/Sources/Services/Room/RoomMemberProxy.swift +++ b/ElementX/Sources/Services/Room/RoomMemberProxy.swift @@ -32,8 +32,8 @@ struct RoomMemberProxy { member.displayName } - var avatarUrl: String? { - member.avatarUrl + var avatarURL: URL? { + member.avatarUrl.flatMap(URL.init(string:)) } var membership: MembershipState { diff --git a/ElementX/Sources/Services/Room/RoomMessageFactory.swift b/ElementX/Sources/Services/Room/RoomMessageFactory.swift deleted file mode 100644 index ca9d3c0df..000000000 --- a/ElementX/Sources/Services/Room/RoomMessageFactory.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// Copyright 2022 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import MatrixRustSDK - -struct SomeRoomMessage: RoomMessageProtocol { - let id: String - let body: String - let sender: String - let timestamp: Date -} - -struct RoomMessageFactory: RoomMessageFactoryProtocol { - func buildRoomMessageFrom(_ eventItemProxy: EventTimelineItemProxy) -> RoomMessageProtocol { - SomeRoomMessage(id: eventItemProxy.id, - body: eventItemProxy.body ?? "", - sender: eventItemProxy.sender, - timestamp: eventItemProxy.timestamp) - } -} diff --git a/ElementX/Sources/Services/Room/RoomMessageFactoryProtocol.swift b/ElementX/Sources/Services/Room/RoomMessageFactoryProtocol.swift deleted file mode 100644 index a6421d42c..000000000 --- a/ElementX/Sources/Services/Room/RoomMessageFactoryProtocol.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// Copyright 2022 New Vector Ltd -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -import MatrixRustSDK - -protocol RoomMessageFactoryProtocol { - func buildRoomMessageFrom(_ eventItemProxy: EventTimelineItemProxy) -> RoomMessageProtocol -} diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index f2dcb2beb..827a5eab7 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -29,7 +29,7 @@ class RoomProxy: RoomProxyProtocol { private var sendMessageBackgroundTask: BackgroundTaskProtocol? - private var memberAvatars = [String: String]() + private var memberAvatars = [String: URL]() private var memberDisplayNames = [String: String]() private(set) var displayName: String? @@ -82,8 +82,8 @@ class RoomProxy: RoomProxyProtocol { room.isTombstoned() } - var avatarURL: String? { - room.avatarUrl() + var avatarURL: URL? { + room.avatarUrl().flatMap(URL.init(string:)) } var encryptionBadgeImage: UIImage? { @@ -95,16 +95,24 @@ class RoomProxy: RoomProxyProtocol { return Asset.Images.encryptionTrusted.image } - func avatarURLStringForUserId(_ userId: String) -> String? { + func avatarURLForUserId(_ userId: String) -> URL? { memberAvatars[userId] } - func loadAvatarURLForUserId(_ userId: String) async -> Result { + func loadAvatarURLForUserId(_ userId: String) async -> Result { do { - let avatarURL = try await Task.dispatch(on: serialDispatchQueue) { + guard let urlString = try await Task.dispatch(on: serialDispatchQueue, { try self.room.memberAvatarUrl(userId: userId) + }) else { + return .success(nil) } - update(avatarURL: avatarURL, forUserId: userId) + + guard let avatarURL = URL(string: urlString) else { + MXLog.error("Invalid avatar URL string: \(String(describing: urlString))") + return .failure(.failedRetrievingMemberAvatarURL) + } + + update(url: avatarURL, forUserId: userId) return .success(avatarURL) } catch { return .failure(.failedRetrievingMemberAvatarURL) @@ -238,8 +246,8 @@ class RoomProxy: RoomProxyProtocol { // MARK: - Private - private func update(avatarURL: String?, forUserId userId: String) { - memberAvatars[userId] = avatarURL + private func update(url: URL?, forUserId userId: String) { + memberAvatars[userId] = url } private func update(displayName: String?, forUserId userId: String) { diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index a398aa252..b9308459f 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -15,6 +15,7 @@ // import Combine +import Foundation import MatrixRustSDK enum RoomProxyError: Error { @@ -44,11 +45,11 @@ protocol RoomProxyProtocol { var topic: String? { get } - var avatarURL: String? { get } + var avatarURL: URL? { get } - func avatarURLStringForUserId(_ userId: String) -> String? + func avatarURLForUserId(_ userId: String) -> URL? - func loadAvatarURLForUserId(_ userId: String) async -> Result + func loadAvatarURLForUserId(_ userId: String) async -> Result func displayNameForUserId(_ userId: String) -> String? diff --git a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift index d97d8e198..d0f94d062 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift @@ -53,21 +53,21 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol { static let rooms: [RoomSummary] = [ .filled(details: RoomSummaryDetails(id: "1", name: "First room", isDirect: true, - avatarURLString: nil, + avatarURL: nil, lastMessage: AttributedString("Prosciutto beef ribs pancetta filet mignon kevin hamburger, chuck ham venison picanha. Beef ribs chislic turkey biltong tenderloin."), lastMessageTimestamp: .distantPast, unreadNotificationCount: 4)), .filled(details: RoomSummaryDetails(id: "2", name: "Second room", isDirect: true, - avatarURLString: "mockImageURLString", + avatarURL: URL.picturesDirectory, lastMessage: nil, lastMessageTimestamp: nil, unreadNotificationCount: 1)), .filled(details: RoomSummaryDetails(id: "3", name: "Third room", isDirect: true, - avatarURLString: nil, + avatarURL: nil, lastMessage: try? AttributedString(markdown: "**@mock:client.com**: T-bone beef ribs bacon"), lastMessageTimestamp: .distantPast, unreadNotificationCount: 0)), diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryDetails.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryDetails.swift index 49d03cc53..6d39c87b2 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryDetails.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryDetails.swift @@ -20,7 +20,7 @@ struct RoomSummaryDetails { let id: String let name: String let isDirect: Bool - let avatarURLString: String? + let avatarURL: URL? let lastMessage: AttributedString? let lastMessageTimestamp: Date? let unreadNotificationCount: UInt diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift index 33e350589..f38b6cc38 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift @@ -20,7 +20,6 @@ import MatrixRustSDK class RoomSummaryProvider: RoomSummaryProviderProtocol { private let slidingSyncViewProxy: SlidingSyncViewProxy - private let roomMessageFactory: RoomMessageFactoryProtocol private let serialDispatchQueue: DispatchQueue private var cancellables = Set() @@ -35,9 +34,8 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { } } - init(slidingSyncViewProxy: SlidingSyncViewProxy, roomMessageFactory: RoomMessageFactoryProtocol) { + init(slidingSyncViewProxy: SlidingSyncViewProxy) { self.slidingSyncViewProxy = slidingSyncViewProxy - self.roomMessageFactory = roomMessageFactory serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomsummaryprovider") rooms = slidingSyncViewProxy.currentRoomsList().map { roomListEntry in @@ -147,22 +145,31 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { var attributedLastMessage: AttributedString? var lastMessageTimestamp: Date? - if let latestRoomMessage = room.latestRoomMessage() { - let lastMessage = roomMessageFactory.buildRoomMessageFrom(EventTimelineItemProxy(item: latestRoomMessage)) - - #warning("Intentionally remove the sender mxid from the room list for now") - // if let lastMessageSender = try? AttributedString(markdown: "**\(lastMessage.sender)**") { - // // Don't include the message body in the markdown otherwise it makes tappable links. - // attributedLastMessage = lastMessageSender + ": " + AttributedString(lastMessage.body) - // } - attributedLastMessage = AttributedString(lastMessage.body) - lastMessageTimestamp = lastMessage.timestamp + + // Dispatch onto another queue otherwise the rust method latestRoomMessage crashes. + // This will be fixed when we get async uniffi support. + DispatchQueue.global(qos: .default).sync { + if let latestRoomMessage = room.latestRoomMessage() { + let lastMessage = EventTimelineItemProxy(item: latestRoomMessage) + + lastMessageTimestamp = lastMessage.timestamp + + if let senderDisplayName = lastMessage.sender.displayName, + let attributedSenderDisplayName = try? AttributedString(markdown: "**\(senderDisplayName)**") { + // Don't include the message body in the markdown otherwise it makes tappable links. + attributedLastMessage = attributedSenderDisplayName + ": " + AttributedString(lastMessage.body ?? "") + } else if let body = lastMessage.body { + attributedLastMessage = AttributedString(body) + } + } } + let avatarURL = room.fullRoom()?.avatarUrl().flatMap(URL.init(string:)) + let details = RoomSummaryDetails(id: room.roomId(), name: room.name() ?? room.roomId(), isDirect: room.isDm() ?? false, - avatarURLString: room.fullRoom()?.avatarUrl(), + avatarURL: avatarURL, lastMessage: attributedLastMessage, lastMessageTimestamp: lastMessageTimestamp, unreadNotificationCount: UInt(room.unreadNotifications().notificationCount())) diff --git a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift index 4f60ae832..803f62f82 100644 --- a/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift +++ b/ElementX/Sources/Services/Timeline/Fixtures/RoomTimelineItemFixtures.swift @@ -26,8 +26,7 @@ enum RoomTimelineItemFixtures { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "", - senderDisplayName: "Jacob", + sender: .init(id: "", displayName: "Jacob"), properties: RoomTimelineItemProperties(isEdited: true)), TextRoomTimelineItem(id: UUID().uuidString, text: "Let’s get lunch soon! New salad place opened up 🥗. When are y’all free? 🤗", @@ -35,8 +34,7 @@ enum RoomTimelineItemFixtures { groupState: .beginning, isOutgoing: false, isEditable: false, - senderId: "", - senderDisplayName: "Helena", + sender: .init(id: "", displayName: "Helena"), properties: RoomTimelineItemProperties(reactions: [ AggregatedReaction(key: "🙌", count: 1, isHighlighted: true) ])), @@ -46,8 +44,7 @@ enum RoomTimelineItemFixtures { groupState: .end, isOutgoing: false, isEditable: false, - senderId: "", - senderDisplayName: "Helena", + sender: .init(id: "", displayName: "Helena"), properties: RoomTimelineItemProperties(reactions: [ AggregatedReaction(key: "🙏", count: 1, isHighlighted: false), AggregatedReaction(key: "🙌", count: 2, isHighlighted: true) @@ -59,24 +56,21 @@ enum RoomTimelineItemFixtures { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "", - senderDisplayName: "Helena"), + sender: .init(id: "", displayName: "Helena")), TextRoomTimelineItem(id: UUID().uuidString, text: "And John's speech was amazing!", timestamp: "5 PM", groupState: .beginning, isOutgoing: true, isEditable: true, - senderId: "", - senderDisplayName: "Bob"), + sender: .init(id: "", displayName: "Bob")), TextRoomTimelineItem(id: UUID().uuidString, text: "New home office set up!", timestamp: "5 PM", groupState: .end, isOutgoing: true, isEditable: true, - senderId: "", - senderDisplayName: "Bob", + sender: .init(id: "", displayName: "Bob"), properties: RoomTimelineItemProperties(reactions: [ AggregatedReaction(key: "🙏", count: 1, isHighlighted: false), AggregatedReaction(key: "😁", count: 3, isHighlighted: false) @@ -92,8 +86,7 @@ enum RoomTimelineItemFixtures { groupState: .single, isOutgoing: false, isEditable: false, - senderId: "", - senderDisplayName: "Helena") + sender: .init(id: "", displayName: "Helena")) ] /// A small chunk of events, containing 2 text items. @@ -214,7 +207,6 @@ private extension TextRoomTimelineItem { groupState: groupState, isOutgoing: senderDisplayName == "Alice", isEditable: false, - senderId: "", - senderDisplayName: senderDisplayName) + sender: .init(id: "", displayName: senderDisplayName)) } } diff --git a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift index 6434d874c..889e9cfb3 100644 --- a/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift +++ b/ElementX/Sources/Services/Timeline/TimelineController/RoomTimelineController.swift @@ -95,7 +95,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { case let item as VideoRoomTimelineItem: await loadThumbnailForVideoTimelineItem(item) case let item as StickerRoomTimelineItem: - await loadThumbnailForStickerTimelineItem(item) + await loadImageForStickerTimelineItem(item) default: break } @@ -372,16 +372,16 @@ class RoomTimelineController: RoomTimelineControllerProtocol { } } - private func loadThumbnailForStickerTimelineItem(_ timelineItem: StickerRoomTimelineItem) async { + private func loadImageForStickerTimelineItem(_ timelineItem: StickerRoomTimelineItem) async { if timelineItem.image != nil { return } - guard let urlString = timelineItem.urlString else { + guard let url = timelineItem.imageURL else { return } - switch await mediaProvider.loadImageFromURLString(urlString) { + switch await mediaProvider.loadImageFromURL(url) { case .success(let image): guard let index = timelineItems.firstIndex(where: { $0.id == timelineItem.id }), var item = timelineItems[index] as? StickerRoomTimelineItem else { @@ -498,41 +498,34 @@ class RoomTimelineController: RoomTimelineControllerProtocol { } private func loadUserAvatarForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) async { - if timelineItem.shouldShowSenderDetails == false || timelineItem.senderAvatar != nil { + guard timelineItem.shouldShowSenderDetails, + let avatarURL = timelineItem.sender.avatarURL, + timelineItem.sender.avatar == nil else { return } - switch await roomProxy.loadAvatarURLForUserId(timelineItem.senderId) { - case .success(let avatarURLString): - guard let avatarURLString else { + switch await mediaProvider.loadImageFromURL(avatarURL, avatarSize: .user(on: .timeline)) { + case .success(let avatar): + guard let index = timelineItems.firstIndex(where: { $0.id == timelineItem.id }), + var item = timelineItems[index] as? EventBasedTimelineItemProtocol else { return } - switch await mediaProvider.loadImageFromURLString(avatarURLString, avatarSize: .user(on: .timeline)) { - case .success(let avatar): - guard let index = timelineItems.firstIndex(where: { $0.id == timelineItem.id }), - var item = timelineItems[index] as? EventBasedTimelineItemProtocol else { - return - } - - item.senderAvatar = avatar - timelineItems[index] = item - callbacks.send(.updatedTimelineItem(timelineItem.id)) - case .failure: - break - } - + item.sender.avatar = avatar + timelineItems[index] = item + callbacks.send(.updatedTimelineItem(timelineItem.id)) case .failure: break } } + #warning("This is here because sender profiles aren't working properly. Remove it entirely later") private func loadUserDisplayNameForTimelineItem(_ timelineItem: EventBasedTimelineItemProtocol) async { - if timelineItem.shouldShowSenderDetails == false || timelineItem.senderDisplayName != nil { + if timelineItem.shouldShowSenderDetails == false || timelineItem.sender.displayName != nil { return } - switch await roomProxy.loadDisplayNameForUserId(timelineItem.senderId) { + switch await roomProxy.loadDisplayNameForUserId(timelineItem.sender.id) { case .success(let displayName): guard let displayName, let index = timelineItems.firstIndex(where: { $0.id == timelineItem.id }), @@ -540,7 +533,7 @@ class RoomTimelineController: RoomTimelineControllerProtocol { return } - item.senderDisplayName = displayName + item.sender.displayName = displayName timelineItems[index] = item callbacks.send(.updatedTimelineItem(timelineItem.id)) case .failure: diff --git a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift index da9f6d86c..6d45d64c7 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItemProxy.swift @@ -95,8 +95,11 @@ struct EventTimelineItemProxy: CustomDebugStringConvertible { item.isEditable() } - var sender: String { - item.sender() + var sender: TimelineItemSender { + let profile = item.senderProfile() + return .init(id: item.sender(), + displayName: profile.displayName, + avatarURL: profile.avatarUrl.flatMap(URL.init(string:))) } var reactions: [Reaction] { diff --git a/ElementX/Sources/Services/Room/Messages/RoomMessageProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItemSender.swift similarity index 60% rename from ElementX/Sources/Services/Room/Messages/RoomMessageProtocol.swift rename to ElementX/Sources/Services/Timeline/TimelineItemSender.swift index 362b96f42..5f2610b46 100644 --- a/ElementX/Sources/Services/Room/Messages/RoomMessageProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItemSender.swift @@ -1,5 +1,5 @@ // -// Copyright 2022 New Vector Ltd +// Copyright 2023 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -14,11 +14,14 @@ // limitations under the License. // -import Foundation +import UIKit -protocol RoomMessageProtocol { - var id: String { get } - var body: String { get } - var sender: String { get } - var timestamp: Date { get } +#warning("This could be replaced by RoomMemberProxy if Rust includes a RoomMember.") +struct TimelineItemSender: Identifiable, Hashable { + let id: String + + // Lazy loaded properties, displayName and avatarURL will be come lets. + var displayName: String? + var avatarURL: URL? + var avatar: UIImage? } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift index d19b5235c..fd048c5f8 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/EventBasedTimelineItemProtocol.swift @@ -41,9 +41,7 @@ protocol EventBasedTimelineItemProtocol: RoomTimelineItemProtocol { var isOutgoing: Bool { get } var isEditable: Bool { get } - var senderId: String { get } - var senderDisplayName: String? { get set } - var senderAvatar: UIImage? { get set } + var sender: TimelineItemSender { get set } var properties: RoomTimelineItemProperties { get } } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/EmoteRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/EmoteRoomTimelineItem.swift index abdfd446e..7398eeb82 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/EmoteRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/EmoteRoomTimelineItem.swift @@ -25,9 +25,7 @@ struct EmoteRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hash let isOutgoing: Bool let isEditable: Bool - let senderId: String - var senderDisplayName: String? - var senderAvatar: UIImage? + var sender: TimelineItemSender var properties = RoomTimelineItemProperties() } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/FileRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/FileRoomTimelineItem.swift index f44be6373..b3fd1915d 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/FileRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/FileRoomTimelineItem.swift @@ -24,9 +24,7 @@ struct FileRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hasha let isOutgoing: Bool let isEditable: Bool - let senderId: String - var senderDisplayName: String? - var senderAvatar: UIImage? + var sender: TimelineItemSender let source: MediaSourceProxy? let thumbnailSource: MediaSourceProxy? diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItem.swift index 3ffc106b7..80453a1a3 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/ImageRoomTimelineItem.swift @@ -24,9 +24,7 @@ struct ImageRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hash let isOutgoing: Bool let isEditable: Bool - let senderId: String - var senderDisplayName: String? - var senderAvatar: UIImage? + var sender: TimelineItemSender let source: MediaSourceProxy? var image: UIImage? diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/NoticeRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/NoticeRoomTimelineItem.swift index 8db5586fc..9b33bb70c 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/NoticeRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/NoticeRoomTimelineItem.swift @@ -25,9 +25,7 @@ struct NoticeRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Has let isOutgoing: Bool let isEditable: Bool - let senderId: String - var senderDisplayName: String? - var senderAvatar: UIImage? + var sender: TimelineItemSender var properties = RoomTimelineItemProperties() } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/TextRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/TextRoomTimelineItem.swift index eb2363bc4..c377ffb06 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/TextRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/TextRoomTimelineItem.swift @@ -25,9 +25,7 @@ struct TextRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hasha let isOutgoing: Bool let isEditable: Bool - let senderId: String - var senderDisplayName: String? - var senderAvatar: UIImage? + var sender: TimelineItemSender var properties = RoomTimelineItemProperties() } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItem.swift index 86d86eacf..f7e8d62d2 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItem.swift @@ -24,9 +24,7 @@ struct VideoRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Hash let isOutgoing: Bool let isEditable: Bool - let senderId: String - var senderDisplayName: String? - var senderAvatar: UIImage? + var sender: TimelineItemSender let duration: UInt64 let source: MediaSourceProxy? diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/EncryptedRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/EncryptedRoomTimelineItem.swift index 478409f91..dab71ed4f 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/EncryptedRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/EncryptedRoomTimelineItem.swift @@ -31,9 +31,7 @@ struct EncryptedRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, let isOutgoing: Bool let isEditable: Bool - let senderId: String - var senderDisplayName: String? - var senderAvatar: UIImage? + var sender: TimelineItemSender var properties = RoomTimelineItemProperties() } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/RedactedRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/RedactedRoomTimelineItem.swift index 3e1a296b7..31e4d059c 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/RedactedRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/RedactedRoomTimelineItem.swift @@ -25,9 +25,7 @@ struct RedactedRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, H let isOutgoing: Bool let isEditable: Bool - let senderId: String - var senderDisplayName: String? - var senderAvatar: UIImage? + var sender: TimelineItemSender var properties = RoomTimelineItemProperties() } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/StickerRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/StickerRoomTimelineItem.swift index 301c36a00..1f82adc33 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/StickerRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/StickerRoomTimelineItem.swift @@ -24,11 +24,9 @@ struct StickerRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable, Ha let isOutgoing: Bool let isEditable: Bool - let senderId: String - var senderDisplayName: String? - var senderAvatar: UIImage? + var sender: TimelineItemSender - let urlString: String? + let imageURL: URL? var image: UIImage? var width: CGFloat? diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/UnsupportedRoomTimelineItem.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/UnsupportedRoomTimelineItem.swift index 4f37d287c..0cd71e19f 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/UnsupportedRoomTimelineItem.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Other/UnsupportedRoomTimelineItem.swift @@ -28,9 +28,7 @@ struct UnsupportedRoomTimelineItem: EventBasedTimelineItemProtocol, Identifiable let isOutgoing: Bool let isEditable: Bool - let senderId: String - var senderDisplayName: String? - var senderAvatar: UIImage? + var sender: TimelineItemSender var properties = RoomTimelineItemProperties() } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift index fecb4f0ac..be579ab75 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/RoomTimelineItemFactory.swift @@ -19,7 +19,6 @@ import UIKit struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { private let mediaProvider: MediaProviderProtocol - private let roomProxy: RoomProxyProtocol private let attributedStringBuilder: AttributedStringBuilderProtocol /// The Matrix ID of the current user. @@ -27,58 +26,63 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { init(userID: String, mediaProvider: MediaProviderProtocol, - roomProxy: RoomProxyProtocol, attributedStringBuilder: AttributedStringBuilderProtocol) { self.userID = userID self.mediaProvider = mediaProvider - self.roomProxy = roomProxy self.attributedStringBuilder = attributedStringBuilder } // swiftlint:disable:next cyclomatic_complexity func buildTimelineItemFor(eventItemProxy: EventTimelineItemProxy, groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol { - let displayName = roomProxy.displayNameForUserId(eventItemProxy.sender) - let avatarURL = roomProxy.avatarURLStringForUserId(eventItemProxy.sender) - let avatarImage = mediaProvider.imageFromURLString(avatarURL, avatarSize: .user(on: .timeline)) + var sender = eventItemProxy.sender + if let senderAvatarURL = eventItemProxy.sender.avatarURL, + let image = mediaProvider.imageFromURL(senderAvatarURL, avatarSize: .user(on: .timeline)) { + sender.avatar = image + } + let isOutgoing = eventItemProxy.isOwn switch eventItemProxy.content.kind() { case .unableToDecrypt(let encryptedMessage): - return buildEncryptedTimelineItem(eventItemProxy, encryptedMessage, isOutgoing, groupState, displayName, avatarImage) + return buildEncryptedTimelineItem(eventItemProxy, encryptedMessage, sender, isOutgoing, groupState) case .redactedMessage: - return buildRedactedTimelineItem(eventItemProxy, isOutgoing, groupState, displayName, avatarImage) + return buildRedactedTimelineItem(eventItemProxy, sender, isOutgoing, groupState) case .sticker(let body, let imageInfo, let url): - return buildStickerTimelineItem(eventItemProxy, body, imageInfo, url, isOutgoing, groupState, displayName, avatarImage) + return buildStickerTimelineItem(eventItemProxy, body, imageInfo, url, sender, isOutgoing, groupState) case .failedToParseMessageLike(let eventType, let error): - return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing, groupState, displayName, avatarImage) + return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, sender, isOutgoing, groupState) case .failedToParseState(let eventType, _, let error): - return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, isOutgoing, groupState, displayName, avatarImage) + return buildUnsupportedTimelineItem(eventItemProxy, eventType, error, sender, isOutgoing, groupState) case .message: guard let messageContent = eventItemProxy.content.asMessage() else { fatalError("Invalid message timeline item: \(eventItemProxy)") } switch messageContent.msgtype() { case .text(content: let content): let message = MessageTimelineItem(item: eventItemProxy.item, content: content) - return buildTextTimelineItemFromMessage(eventItemProxy, message, isOutgoing, groupState, displayName, avatarImage) + return buildTextTimelineItemFromMessage(eventItemProxy, message, sender, isOutgoing, groupState) case .image(content: let content): let message = MessageTimelineItem(item: eventItemProxy.item, content: content) - return buildImageTimelineItemFromMessage(eventItemProxy, message, isOutgoing, groupState, displayName, avatarImage) + return buildImageTimelineItemFromMessage(eventItemProxy, message, sender, isOutgoing, groupState) case .video(let content): let message = MessageTimelineItem(item: eventItemProxy.item, content: content) - return buildVideoTimelineItemFromMessage(eventItemProxy, message, isOutgoing, groupState, displayName, avatarImage) + return buildVideoTimelineItemFromMessage(eventItemProxy, message, sender, isOutgoing, groupState) case .file(let content): let message = MessageTimelineItem(item: eventItemProxy.item, content: content) - return buildFileTimelineItemFromMessage(eventItemProxy, message, isOutgoing, groupState, displayName, avatarImage) + return buildFileTimelineItemFromMessage(eventItemProxy, message, sender, isOutgoing, groupState) case .notice(content: let content): let message = MessageTimelineItem(item: eventItemProxy.item, content: content) - return buildNoticeTimelineItemFromMessage(eventItemProxy, message, isOutgoing, groupState, displayName, avatarImage) + return buildNoticeTimelineItemFromMessage(eventItemProxy, message, sender, isOutgoing, groupState) case .emote(content: let content): let message = MessageTimelineItem(item: eventItemProxy.item, content: content) - return buildEmoteTimelineItemFromMessage(eventItemProxy, message, isOutgoing, groupState, displayName, avatarImage) + return buildEmoteTimelineItemFromMessage(eventItemProxy, message, sender, isOutgoing, groupState) case .none: - return buildFallbackTimelineItem(eventItemProxy, isOutgoing, groupState, displayName, avatarImage) + return buildFallbackTimelineItem(eventItemProxy, sender, isOutgoing, groupState) } + case .state: + return buildFallbackTimelineItem(eventItemProxy, sender, isOutgoing, groupState) + case .roomMembership: + return buildFallbackTimelineItem(eventItemProxy, sender, isOutgoing, groupState) } } @@ -88,10 +92,9 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { private func buildUnsupportedTimelineItem(_ eventItemProxy: EventTimelineItemProxy, _ eventType: String, _ error: String, + _ sender: TimelineItemSender, _ isOutgoing: Bool, - _ groupState: TimelineItemGroupState, - _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { + _ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol { UnsupportedRoomTimelineItem(id: eventItemProxy.id, text: ElementL10n.roomTimelineItemUnsupported, eventType: eventType, @@ -100,9 +103,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { groupState: groupState, isOutgoing: isOutgoing, isEditable: eventItemProxy.isEditable, - senderId: eventItemProxy.sender, - senderDisplayName: displayName, - senderAvatar: avatarImage, + sender: sender, properties: RoomTimelineItemProperties()) } @@ -110,17 +111,21 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { private func buildStickerTimelineItem(_ eventItemProxy: EventTimelineItemProxy, _ body: String, _ imageInfo: ImageInfo, - _ urlString: String, + _ imageURLString: String, + _ sender: TimelineItemSender, _ isOutgoing: Bool, - _ groupState: TimelineItemGroupState, - _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { + _ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol { var aspectRatio: CGFloat? let width = imageInfo.width.map(CGFloat.init) let height = imageInfo.height.map(CGFloat.init) if let width, let height { aspectRatio = width / height } + + var image: UIImage? + if let url = URL(string: imageURLString) { + image = mediaProvider.imageFromURL(url) + } return StickerRoomTimelineItem(id: eventItemProxy.id, text: body, @@ -128,11 +133,9 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { groupState: groupState, isOutgoing: isOutgoing, isEditable: eventItemProxy.isEditable, - senderId: eventItemProxy.sender, - senderDisplayName: displayName, - senderAvatar: avatarImage, - urlString: urlString, - image: mediaProvider.imageFromURLString(urlString), + sender: sender, + imageURL: URL(string: imageURLString), + image: image, width: width, height: height, aspectRatio: aspectRatio, @@ -140,14 +143,12 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { properties: RoomTimelineItemProperties(reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus)) } - - // swiftlint:disable:next function_parameter_count + private func buildEncryptedTimelineItem(_ eventItemProxy: EventTimelineItemProxy, _ encryptedMessage: EncryptedMessage, + _ sender: TimelineItemSender, _ isOutgoing: Bool, - _ groupState: TimelineItemGroupState, - _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { + _ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol { var encryptionType = EncryptedRoomTimelineItem.EncryptionType.unknown switch encryptedMessage { case .megolmV1AesSha2(let sessionId): @@ -165,34 +166,28 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { groupState: groupState, isOutgoing: isOutgoing, isEditable: eventItemProxy.isEditable, - senderId: eventItemProxy.sender, - senderDisplayName: displayName, - senderAvatar: avatarImage, + sender: sender, properties: RoomTimelineItemProperties()) } private func buildRedactedTimelineItem(_ eventItemProxy: EventTimelineItemProxy, + _ sender: TimelineItemSender, _ isOutgoing: Bool, - _ groupState: TimelineItemGroupState, - _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { + _ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol { RedactedRoomTimelineItem(id: eventItemProxy.id, text: ElementL10n.eventRedacted, timestamp: eventItemProxy.timestamp.formatted(date: .omitted, time: .shortened), groupState: groupState, isOutgoing: isOutgoing, isEditable: eventItemProxy.isEditable, - senderId: eventItemProxy.sender, - senderDisplayName: displayName, - senderAvatar: avatarImage, + sender: sender, properties: RoomTimelineItemProperties()) } private func buildFallbackTimelineItem(_ eventItemProxy: EventTimelineItemProxy, + _ sender: TimelineItemSender, _ isOutgoing: Bool, - _ groupState: TimelineItemGroupState, - _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { + _ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol { let attributedText = attributedStringBuilder.fromPlain(eventItemProxy.body) let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText) @@ -203,20 +198,16 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { groupState: groupState, isOutgoing: isOutgoing, isEditable: eventItemProxy.isEditable, - senderId: eventItemProxy.sender, - senderDisplayName: displayName, - senderAvatar: avatarImage, + sender: sender, properties: RoomTimelineItemProperties(isEdited: eventItemProxy.content.asMessage()?.isEdited() ?? false, reactions: aggregateReactions(eventItemProxy.reactions))) } - // swiftlint:disable:next function_parameter_count private func buildTextTimelineItemFromMessage(_ eventItemProxy: EventTimelineItemProxy, _ message: MessageTimelineItem, + _ sender: TimelineItemSender, _ isOutgoing: Bool, - _ groupState: TimelineItemGroupState, - _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { + _ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol { let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body)) let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText) @@ -227,21 +218,17 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { groupState: groupState, isOutgoing: isOutgoing, isEditable: eventItemProxy.isEditable, - senderId: message.sender, - senderDisplayName: displayName, - senderAvatar: avatarImage, + sender: sender, properties: RoomTimelineItemProperties(isEdited: message.isEdited, reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus)) } - // swiftlint:disable:next function_parameter_count private func buildImageTimelineItemFromMessage(_ eventItemProxy: EventTimelineItemProxy, _ message: MessageTimelineItem, + _ sender: TimelineItemSender, _ isOutgoing: Bool, - _ groupState: TimelineItemGroupState, - _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { + _ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol { var aspectRatio: CGFloat? if let width = message.width, let height = message.height { @@ -254,9 +241,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { groupState: groupState, isOutgoing: isOutgoing, isEditable: eventItemProxy.isEditable, - senderId: message.sender, - senderDisplayName: displayName, - senderAvatar: avatarImage, + sender: sender, source: message.source, image: mediaProvider.imageFromSource(message.source), width: message.width, @@ -268,13 +253,11 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { deliveryStatus: eventItemProxy.deliveryStatus)) } - // swiftlint:disable:next function_parameter_count private func buildVideoTimelineItemFromMessage(_ eventItemProxy: EventTimelineItemProxy, _ message: MessageTimelineItem, + _ sender: TimelineItemSender, _ isOutgoing: Bool, - _ groupState: TimelineItemGroupState, - _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { + _ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol { var aspectRatio: CGFloat? if let width = message.width, let height = message.height { @@ -287,9 +270,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { groupState: groupState, isOutgoing: isOutgoing, isEditable: eventItemProxy.isEditable, - senderId: message.sender, - senderDisplayName: displayName, - senderAvatar: avatarImage, + sender: sender, duration: message.duration, source: message.source, thumbnailSource: message.thumbnailSource, @@ -303,22 +284,18 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { deliveryStatus: eventItemProxy.deliveryStatus)) } - // swiftlint:disable:next function_parameter_count private func buildFileTimelineItemFromMessage(_ eventItemProxy: EventTimelineItemProxy, _ message: MessageTimelineItem, + _ sender: TimelineItemSender, _ isOutgoing: Bool, - _ groupState: TimelineItemGroupState, - _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { + _ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol { FileRoomTimelineItem(id: message.id, text: message.body, timestamp: message.timestamp.formatted(date: .omitted, time: .shortened), groupState: groupState, isOutgoing: isOutgoing, isEditable: eventItemProxy.isEditable, - senderId: message.sender, - senderDisplayName: displayName, - senderAvatar: avatarImage, + sender: sender, source: message.source, thumbnailSource: message.thumbnailSource, properties: RoomTimelineItemProperties(isEdited: message.isEdited, @@ -326,13 +303,11 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { deliveryStatus: eventItemProxy.deliveryStatus)) } - // swiftlint:disable:next function_parameter_count private func buildNoticeTimelineItemFromMessage(_ eventItemProxy: EventTimelineItemProxy, _ message: MessageTimelineItem, + _ sender: TimelineItemSender, _ isOutgoing: Bool, - _ groupState: TimelineItemGroupState, - _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { + _ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol { let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body)) let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText) @@ -343,22 +318,26 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { groupState: groupState, isOutgoing: isOutgoing, isEditable: eventItemProxy.isEditable, - senderId: message.sender, - senderDisplayName: displayName, - senderAvatar: avatarImage, + sender: sender, properties: RoomTimelineItemProperties(isEdited: message.isEdited, reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus)) } - // swiftlint:disable:next function_parameter_count private func buildEmoteTimelineItemFromMessage(_ eventItemProxy: EventTimelineItemProxy, _ message: MessageTimelineItem, + _ sender: TimelineItemSender, _ isOutgoing: Bool, - _ groupState: TimelineItemGroupState, - _ displayName: String?, - _ avatarImage: UIImage?) -> RoomTimelineItemProtocol { - let attributedText = (message.htmlBody != nil ? attributedStringBuilder.fromHTML(message.htmlBody) : attributedStringBuilder.fromPlain(message.body)) + _ groupState: TimelineItemGroupState) -> RoomTimelineItemProtocol { + let name = sender.displayName ?? sender.id + + var attributedText: AttributedString? + if let htmlBody = message.htmlBody { + attributedText = attributedStringBuilder.fromHTML("* \(name) \(htmlBody)") + } else { + attributedText = attributedStringBuilder.fromPlain("* \(name) \(message.body)") + } + let attributedComponents = attributedStringBuilder.blockquoteCoalescedComponentsFrom(attributedText) return EmoteRoomTimelineItem(id: message.id, @@ -368,9 +347,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { groupState: groupState, isOutgoing: isOutgoing, isEditable: eventItemProxy.isEditable, - senderId: message.sender, - senderDisplayName: displayName, - senderAvatar: avatarImage, + sender: sender, properties: RoomTimelineItemProperties(isEdited: message.isEdited, reactions: aggregateReactions(eventItemProxy.reactions), deliveryStatus: eventItemProxy.deliveryStatus)) @@ -378,7 +355,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol { private func aggregateReactions(_ reactions: [Reaction]) -> [AggregatedReaction] { reactions.map { reaction in - let isHighlighted = false // reaction.details.contains(where: { $0.sender == userID }) + let isHighlighted = false // reaction.details.contains(where: { $0.sender.id == userID }) return AggregatedReaction(key: reaction.key, count: Int(reaction.count), isHighlighted: isHighlighted) } } diff --git a/ElementX/Sources/Services/UserSession/UserSessionFlowCoordinator.swift b/ElementX/Sources/Services/UserSession/UserSessionFlowCoordinator.swift index d4b1d211f..c88f55dd5 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionFlowCoordinator.swift @@ -153,7 +153,6 @@ class UserSessionFlowCoordinator: CoordinatorProtocol { let timelineItemFactory = RoomTimelineItemFactory(userID: userId, mediaProvider: userSession.mediaProvider, - roomProxy: roomProxy, attributedStringBuilder: AttributedStringBuilder()) let timelineController = roomTimelineControllerFactory.buildRoomTimelineController(userId: userId, diff --git a/ElementX/Sources/Services/UserSession/UserSessionStore.swift b/ElementX/Sources/Services/UserSession/UserSessionStore.swift index 37a327449..ccc01e725 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStore.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStore.swift @@ -42,7 +42,7 @@ class UserSessionStore: UserSessionStoreProtocol { keychainController.removeAllRestorationTokens() } - func restoreUserSession() async -> Result { + func restoreUserSession() async -> Result { let availableCredentials = keychainController.restorationTokens() guard let credentials = availableCredentials.first else { @@ -51,11 +51,7 @@ class UserSessionStore: UserSessionStoreProtocol { switch await restorePreviousLogin(credentials) { case .success(let clientProxy): - return .success(UserSession(clientProxy: clientProxy, - mediaProvider: MediaProvider(mediaProxy: clientProxy, - imageCache: .onlyInMemory, - fileCache: FileCache.default, - backgroundTaskService: backgroundTaskService))) + return .success(buildUserSessionWithClient(clientProxy)) case .failure(let error): MXLog.error("Failed restoring login with error: \(error)") @@ -67,14 +63,10 @@ class UserSessionStore: UserSessionStoreProtocol { } } - func userSession(for client: Client) async -> Result { + func userSession(for client: Client) async -> Result { switch await setupProxyForClient(client) { case .success(let clientProxy): - return .success(UserSession(clientProxy: clientProxy, - mediaProvider: MediaProvider(mediaProxy: clientProxy, - imageCache: .onlyInMemory, - fileCache: FileCache.default, - backgroundTaskService: backgroundTaskService))) + return .success(buildUserSessionWithClient(clientProxy)) case .failure(let error): MXLog.error("Failed creating user session with error: \(error)") return .failure(error) @@ -99,6 +91,17 @@ class UserSessionStore: UserSessionStoreProtocol { // MARK: - Private + private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol) -> UserSessionProtocol { + let imageCache = ImageCache.onlyInMemory + imageCache.memoryStorage.config.keepWhenEnteringBackground = true + + return UserSession(clientProxy: clientProxy, + mediaProvider: MediaProvider(mediaProxy: clientProxy, + imageCache: imageCache, + fileCache: FileCache.default, + backgroundTaskService: backgroundTaskService)) + } + private func restorePreviousLogin(_ credentials: KeychainCredentials) async -> Result { let builder = ClientBuilder() .basePath(path: baseDirectory.path) diff --git a/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift b/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift index 8ea1ae2ea..714335c50 100644 --- a/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift +++ b/ElementX/Sources/Services/UserSession/UserSessionStoreProtocol.swift @@ -35,10 +35,10 @@ protocol UserSessionStoreProtocol { var baseDirectory: URL { get } /// Restores an existing user session. - func restoreUserSession() async -> Result + func restoreUserSession() async -> Result /// Creates a user session for a new client from the SDK. - func userSession(for client: Client) async -> Result + func userSession(for client: Client) async -> Result /// Refresh the restore token of the client for a given session. func refreshRestorationToken(for userSession: UserSessionProtocol) -> Result diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index 9f59ad4f4..e8180828f 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -156,7 +156,7 @@ class MockScreen: Identifiable { case .roomEncryptedWithAvatar: let navigationStackCoordinator = NavigationStackCoordinator() let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator, - roomProxy: MockRoomProxy(displayName: "Some room name", avatarURL: "mock_url"), + roomProxy: MockRoomProxy(displayName: "Some room name", avatarURL: URL.picturesDirectory), timelineController: MockRoomTimelineController(), mediaProvider: MockMediaProvider(), emojiProvider: EmojiProvider()) @@ -168,7 +168,7 @@ class MockScreen: Identifiable { let timelineController = MockRoomTimelineController() timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator, - roomProxy: MockRoomProxy(displayName: "New room", avatarURL: "mock_url"), + roomProxy: MockRoomProxy(displayName: "New room", avatarURL: URL.picturesDirectory), timelineController: timelineController, mediaProvider: MockMediaProvider(), emojiProvider: EmojiProvider()) @@ -183,7 +183,7 @@ class MockScreen: Identifiable { timelineController.backPaginationResponses = [RoomTimelineItemFixtures.singleMessageChunk] timelineController.incomingItems = [RoomTimelineItemFixtures.incomingMessage] let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator, - roomProxy: MockRoomProxy(displayName: "Small timeline", avatarURL: "mock_url"), + roomProxy: MockRoomProxy(displayName: "Small timeline", avatarURL: URL.picturesDirectory), timelineController: timelineController, mediaProvider: MockMediaProvider(), emojiProvider: EmojiProvider()) @@ -198,7 +198,7 @@ class MockScreen: Identifiable { timelineController.timelineItems = RoomTimelineItemFixtures.smallChunk timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk] let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator, - roomProxy: MockRoomProxy(displayName: "Small timeline, paginating", avatarURL: "mock_url"), + roomProxy: MockRoomProxy(displayName: "Small timeline, paginating", avatarURL: URL.picturesDirectory), timelineController: timelineController, mediaProvider: MockMediaProvider(), emojiProvider: EmojiProvider()) @@ -213,7 +213,7 @@ class MockScreen: Identifiable { timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk] let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator, - roomProxy: MockRoomProxy(displayName: "Large timeline", avatarURL: "mock_url"), + roomProxy: MockRoomProxy(displayName: "Large timeline", avatarURL: URL.picturesDirectory), timelineController: timelineController, mediaProvider: MockMediaProvider(), emojiProvider: EmojiProvider()) @@ -229,7 +229,7 @@ class MockScreen: Identifiable { timelineController.backPaginationResponses = [RoomTimelineItemFixtures.largeChunk] timelineController.incomingItems = [RoomTimelineItemFixtures.incomingMessage] let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator, - roomProxy: MockRoomProxy(displayName: "Large timeline", avatarURL: "mock_url"), + roomProxy: MockRoomProxy(displayName: "Large timeline", avatarURL: URL.picturesDirectory), timelineController: timelineController, mediaProvider: MockMediaProvider(), emojiProvider: EmojiProvider()) @@ -244,7 +244,7 @@ class MockScreen: Identifiable { timelineController.timelineItems = RoomTimelineItemFixtures.largeChunk timelineController.incomingItems = [RoomTimelineItemFixtures.incomingMessage] let parameters = RoomScreenCoordinatorParameters(navigationStackCoordinator: navigationStackCoordinator, - roomProxy: MockRoomProxy(displayName: "Large timeline", avatarURL: "mock_url"), + roomProxy: MockRoomProxy(displayName: "Large timeline", avatarURL: URL.picturesDirectory), timelineController: timelineController, mediaProvider: MockMediaProvider(), emojiProvider: EmojiProvider()) diff --git a/NSE/SupportingFiles/target.yml b/NSE/SupportingFiles/target.yml index 5a81f4749..99eb07ced 100644 --- a/NSE/SupportingFiles/target.yml +++ b/NSE/SupportingFiles/target.yml @@ -66,6 +66,7 @@ targets: - path: ../Sources - path: ../SupportingFiles - path: ../../ElementX/Sources/Services/Timeline/TimelineItemProxy.swift + - path: ../../ElementX/Sources/Services/Timeline/TimelineItemSender.swift - path: ../../ElementX/Sources/Services/Keychain/KeychainControllerProtocol.swift - path: ../../ElementX/Sources/Services/Keychain/KeychainController.swift - path: ../../ElementX/Sources/Services/UserSession/RestorationToken.swift diff --git a/UnitTests/Sources/LoggingTests.swift b/UnitTests/Sources/LoggingTests.swift index ed5e990bd..a5afb9efb 100644 --- a/UnitTests/Sources/LoggingTests.swift +++ b/UnitTests/Sources/LoggingTests.swift @@ -181,7 +181,7 @@ class LoggingTests: XCTestCase { let roomSummary = RoomSummaryDetails(id: "myroomid", name: roomName, isDirect: true, - avatarURLString: nil, + avatarURL: nil, lastMessage: AttributedString(lastMessage), lastMessageTimestamp: .now, unreadNotificationCount: 0) @@ -213,26 +213,26 @@ class LoggingTests: XCTestCase { let textMessage = TextRoomTimelineItem(id: "mytextmessage", text: "TextString", attributedComponents: [.init(attributedString: AttributedString(textAttributedString), isBlockquote: false)], - timestamp: "", groupState: .single, isOutgoing: false, isEditable: false, senderId: "sender") + timestamp: "", groupState: .single, isOutgoing: false, isEditable: false, sender: .init(id: "sender")) let noticeAttributedString = "NoticeAttributed" let noticeMessage = NoticeRoomTimelineItem(id: "mynoticemessage", text: "NoticeString", attributedComponents: [.init(attributedString: AttributedString(noticeAttributedString), isBlockquote: false)], - timestamp: "", groupState: .single, isOutgoing: false, isEditable: false, senderId: "sender") + timestamp: "", groupState: .single, isOutgoing: false, isEditable: false, sender: .init(id: "sender")) let emoteAttributedString = "EmoteAttributed" let emoteMessage = EmoteRoomTimelineItem(id: "myemotemessage", text: "EmoteString", attributedComponents: [.init(attributedString: AttributedString(emoteAttributedString), isBlockquote: false)], - timestamp: "", groupState: .single, isOutgoing: false, isEditable: false, senderId: "sender") + timestamp: "", groupState: .single, isOutgoing: false, isEditable: false, sender: .init(id: "sender")) let imageMessage = ImageRoomTimelineItem(id: "myimagemessage", text: "ImageString", timestamp: "", groupState: .single, isOutgoing: false, isEditable: false, - senderId: "sender", source: nil) + sender: .init(id: "sender"), source: nil) let videoMessage = VideoRoomTimelineItem(id: "myvideomessage", text: "VideoString", timestamp: "", groupState: .single, isOutgoing: false, isEditable: false, - senderId: "sender", duration: 0, source: nil, thumbnailSource: nil) + sender: .init(id: "sender"), duration: 0, source: nil, thumbnailSource: nil) let fileMessage = FileRoomTimelineItem(id: "myfilemessage", text: "FileString", timestamp: "", groupState: .single, isOutgoing: false, isEditable: false, - senderId: "sender", source: nil, thumbnailSource: nil) + sender: .init(id: "sender"), source: nil, thumbnailSource: nil) // When logging that value XCTAssert(MXLogger.logFiles.isEmpty) diff --git a/UnitTests/Sources/MediaProvider/MediaProviderTests.swift b/UnitTests/Sources/MediaProvider/MediaProviderTests.swift index 65bea2111..246b3d222 100644 --- a/UnitTests/Sources/MediaProvider/MediaProviderTests.swift +++ b/UnitTests/Sources/MediaProvider/MediaProviderTests.swift @@ -34,7 +34,7 @@ final class MediaProviderTests: XCTestCase { fileCache: fileCache, backgroundTaskService: backgroundTaskService) } - + func test_whenImageFromSourceWithSourceNil_nilReturned() throws { let image = mediaProvider.imageFromSource(nil, avatarSize: .room(on: .timeline)) XCTAssertNil(image) @@ -42,64 +42,64 @@ final class MediaProviderTests: XCTestCase { func test_whenImageFromSourceWithSourceNotNilAndImageCacheContainsImage_ImageIsReturned() throws { let avatarSize = AvatarSize.room(on: .timeline) - let urlString = "test" - let key = "\(urlString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" + let url = URL.picturesDirectory + let key = "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" let imageForKey = UIImage() imageCache.retrievedImagesInMemory[key] = imageForKey - let image = mediaProvider.imageFromSource(MediaSourceProxy(urlString: urlString), avatarSize: avatarSize) + let image = mediaProvider.imageFromSource(MediaSourceProxy(url: url), avatarSize: avatarSize) XCTAssertEqual(image, imageForKey) } func test_whenImageFromSourceWithSourceNotNilAndImageNotCached_nilReturned() throws { - let image = mediaProvider.imageFromSource(MediaSourceProxy(urlString: "test"), avatarSize: .room(on: .timeline)) + let image = mediaProvider.imageFromSource(MediaSourceProxy(url: URL.picturesDirectory), avatarSize: .room(on: .timeline)) XCTAssertNil(image) } func test_whenImageFromURLStringWithURLStringNil_nilReturned() throws { - let image = mediaProvider.imageFromURLString(nil, avatarSize: .room(on: .timeline)) + let image = mediaProvider.imageFromURL(nil, avatarSize: .room(on: .timeline)) XCTAssertNil(image) } - + func test_whenImageFromURLStringWithURLStringNotNilAndImageCacheContainsImage_imageIsReturned() throws { let avatarSize = AvatarSize.room(on: .timeline) - let urlString = "test" - let key = "\(urlString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" + let url = URL.picturesDirectory + let key = "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" let imageForKey = UIImage() imageCache.retrievedImagesInMemory[key] = imageForKey - let image = mediaProvider.imageFromURLString("test", avatarSize: avatarSize) + let image = mediaProvider.imageFromURL(url, avatarSize: avatarSize) XCTAssertEqual(image, imageForKey) } func test_whenImageFromURLStringWithURLStringNotNilAndImageNotCached_nilReturned() throws { - let image = mediaProvider.imageFromURLString("test", avatarSize: .room(on: .timeline)) + let image = mediaProvider.imageFromURL(URL.picturesDirectory, avatarSize: .room(on: .timeline)) XCTAssertNil(image) } func test_whenLoadImageFromSourceAndImageCacheContainsImage_successIsReturned() async throws { let avatarSize = AvatarSize.room(on: .timeline) - let urlString = "test" - let key = "\(urlString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" + let url = URL.picturesDirectory + let key = "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" let imageForKey = UIImage() imageCache.retrievedImagesInMemory[key] = imageForKey - let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(urlString: urlString), avatarSize: avatarSize) + let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: url), avatarSize: avatarSize) XCTAssertEqual(Result.success(imageForKey), result) } func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageSucceeds_successIsReturned() async throws { let avatarSize = AvatarSize.room(on: .timeline) - let urlString = "test" - let key = "\(urlString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" + let url = URL.picturesDirectory + let key = "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" let imageForKey = UIImage() imageCache.retrievedImages[key] = imageForKey - let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(urlString: urlString), avatarSize: avatarSize) + let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: url), avatarSize: avatarSize) XCTAssertEqual(Result.success(imageForKey), result) } - + func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFails_imageThumbnailIsLoaded() async throws { let avatarSize = AvatarSize.room(on: .timeline) let expectedImage = try loadTestImage() mediaProxy.mediaThumbnailData = expectedImage.pngData() - let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(urlString: "test"), avatarSize: avatarSize) + let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: URL.picturesDirectory), avatarSize: avatarSize) switch result { case .success(let image): XCTAssertEqual(image.pngData(), expectedImage.pngData()) @@ -110,11 +110,11 @@ final class MediaProviderTests: XCTestCase { func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFails_imageIsStored() async throws { let avatarSize = AvatarSize.room(on: .timeline) - let urlString = "test" - let key = "\(urlString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" + let url = URL.picturesDirectory + let key = "\(url.absoluteString){\(avatarSize.scaledValue),\(avatarSize.scaledValue)}" let expectedImage = try loadTestImage() mediaProxy.mediaThumbnailData = expectedImage.pngData() - _ = await mediaProvider.loadImageFromSource(MediaSourceProxy(urlString: urlString), avatarSize: avatarSize) + _ = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: url), avatarSize: avatarSize) let storedImage = try XCTUnwrap(imageCache.storedImages[key]) XCTAssertEqual(expectedImage.pngData(), storedImage.pngData()) } @@ -122,7 +122,7 @@ final class MediaProviderTests: XCTestCase { func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndNoAvatarSize_imageContentIsLoaded() async throws { let expectedImage = try loadTestImage() mediaProxy.mediaContentData = expectedImage.pngData() - let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(urlString: "test"), avatarSize: nil) + let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: URL.picturesDirectory), avatarSize: nil) switch result { case .success(let image): XCTAssertEqual(image.pngData(), expectedImage.pngData()) @@ -132,7 +132,7 @@ final class MediaProviderTests: XCTestCase { } func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndLoadImageThumbnailFails_errorIsThrown() async throws { - let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(urlString: "test"), avatarSize: AvatarSize.room(on: .timeline)) + let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: URL.picturesDirectory), avatarSize: AvatarSize.room(on: .timeline)) switch result { case .success: XCTFail("Should fail") @@ -142,7 +142,7 @@ final class MediaProviderTests: XCTestCase { } func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndNoAvatarSizeAndLoadImageContentFails_errorIsThrown() async throws { - let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(urlString: "test"), avatarSize: nil) + let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: URL.picturesDirectory), avatarSize: nil) switch result { case .success: XCTFail("Should fail") @@ -153,7 +153,7 @@ final class MediaProviderTests: XCTestCase { func test_whenLoadImageFromSourceAndImageNotCachedAndRetrieveImageFailsAndImageThumbnailIsLoadedWithCorruptedData_errorIsThrown() async throws { mediaProxy.mediaThumbnailData = Data() - let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(urlString: "test"), avatarSize: AvatarSize.room(on: .timeline)) + let result = await mediaProvider.loadImageFromSource(MediaSourceProxy(url: URL.picturesDirectory), avatarSize: AvatarSize.room(on: .timeline)) switch result { case .success: XCTFail("Should fail") @@ -170,7 +170,7 @@ final class MediaProviderTests: XCTestCase { func test_whenFileFromSourceWithSource_correctValuesAreReturned() throws { let expectedURL = URL(filePath: "/some/file/path") fileCache.fileURLToReturn = expectedURL - let url = mediaProvider.fileFromSource(MediaSourceProxy(urlString: "test/test1"), fileExtension: "png") + let url = mediaProvider.fileFromSource(MediaSourceProxy(url: URL(staticString: "test/test1")), fileExtension: "png") XCTAssertEqual(fileCache.fileKey, "test1") XCTAssertEqual(fileCache.fileExtension, "png") XCTAssertEqual(url?.absoluteString, expectedURL.absoluteString) @@ -180,7 +180,7 @@ final class MediaProviderTests: XCTestCase { let expectedURL = URL(filePath: "/some/file/path") let expectedResult: Result = .success(expectedURL) fileCache.fileURLToReturn = expectedURL - let result = await mediaProvider.loadFileFromSource(MediaSourceProxy(urlString: "test/test1"), fileExtension: "png") + let result = await mediaProvider.loadFileFromSource(MediaSourceProxy(url: URL(staticString: "test/test1")), fileExtension: "png") XCTAssertEqual(result, expectedResult) } @@ -189,7 +189,7 @@ final class MediaProviderTests: XCTestCase { let expectedResult: Result = .success(expectedURL) mediaProxy.mediaContentData = try loadTestImage().pngData() fileCache.storeURLToReturn = expectedURL - let result = await mediaProvider.loadFileFromSource(MediaSourceProxy(urlString: "test/test1"), fileExtension: "png") + let result = await mediaProvider.loadFileFromSource(MediaSourceProxy(url: URL(staticString: "test/test1")), fileExtension: "png") XCTAssertEqual(result, expectedResult) XCTAssertEqual(mediaProxy.mediaContentData, fileCache.storedData) XCTAssertEqual("test1", fileCache.storedFileKey) @@ -199,27 +199,27 @@ final class MediaProviderTests: XCTestCase { func test_whenLoadFileFromSourceAndNoFileFromSourceExistsAndLoadContentSourceFails_failureIsReturned() async throws { let expectedResult: Result = .failure(.failedRetrievingImage) mediaProxy.mediaContentData = nil - let result = await mediaProvider.loadFileFromSource(MediaSourceProxy(urlString: "test/test1"), fileExtension: "png") + let result = await mediaProvider.loadFileFromSource(MediaSourceProxy(url: URL(staticString: "test/test1")), fileExtension: "png") XCTAssertEqual(result, expectedResult) } func test_whenLoadFileFromSourceAndNoFileFromSourceExistsAndStoreDataFails_failureIsReturned() async throws { let expectedResult: Result = .failure(.failedRetrievingImage) mediaProxy.mediaContentData = try loadTestImage().pngData() - let result = await mediaProvider.loadFileFromSource(MediaSourceProxy(urlString: "test/test1"), fileExtension: "png") + let result = await mediaProvider.loadFileFromSource(MediaSourceProxy(url: URL(staticString: "test/test1")), fileExtension: "png") XCTAssertEqual(result, expectedResult) } func test_whenFileFromURLStringAndURLIsNil_nilIsReturned() async throws { mediaProxy.mediaContentData = try loadTestImage().pngData() - let url = mediaProvider.fileFromURLString(nil, fileExtension: "png") + let url = mediaProvider.fileFromURL(nil, fileExtension: "png") XCTAssertNil(url) } func test_whenFileFromURLString_correctURLIsReturned() throws { let expectedURL = URL(filePath: "/some/file/path") fileCache.fileURLToReturn = expectedURL - let url = mediaProvider.fileFromURLString("test/test1", fileExtension: "png") + let url = mediaProvider.fileFromURL(URL(staticString: "test/test1"), fileExtension: "png") XCTAssertEqual(url?.absoluteString, expectedURL.absoluteString) } @@ -227,7 +227,7 @@ final class MediaProviderTests: XCTestCase { let expectedURL = URL(filePath: "/some/file/path") let expectedResult: Result = .success(expectedURL) fileCache.fileURLToReturn = expectedURL - let result = await mediaProvider.loadFileFromURLString("test/test1", fileExtension: "png") + let result = await mediaProvider.loadFileFromURL(URL(staticString: "test/test1"), fileExtension: "png") XCTAssertEqual(result, expectedResult) } diff --git a/UnitTests/Sources/MediaProvider/MockMediaProxy.swift b/UnitTests/Sources/MediaProvider/MockMediaProxy.swift index 89ab1bb40..a096d16c8 100644 --- a/UnitTests/Sources/MediaProvider/MockMediaProxy.swift +++ b/UnitTests/Sources/MediaProvider/MockMediaProxy.swift @@ -24,8 +24,8 @@ class MockMediaProxy: MediaProxyProtocol { var mediaContentData: Data? var mediaThumbnailData: Data? - func mediaSourceForURLString(_ urlString: String) -> MediaSourceProxy { - MediaSourceProxy(urlString: "test") + func mediaSourceForURL(_ url: URL) -> MediaSourceProxy { + MediaSourceProxy(url: URL.picturesDirectory) } func loadMediaContentForSource(_ source: ElementX.MediaSourceProxy) async throws -> Data { diff --git a/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift b/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift index ebd1dbfb2..82f6f84ff 100644 --- a/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift +++ b/UnitTests/Sources/NotificationManager/NotificationManagerTests.swift @@ -64,7 +64,7 @@ final class NotificationManagerTests: XCTestCase { XCTAssertEqual(clientProxy.setPusherDeviceDisplayName, UIDevice.current.name) XCTAssertNotNil(clientProxy.setPusherProfileTag) XCTAssertEqual(clientProxy.setPusherLang, Bundle.preferredLanguages.first) - XCTAssertEqual(clientProxy.setPusherUrl, settings?.pushGatewayBaseURL.absoluteString) + XCTAssertEqual(clientProxy.setPusherUrl, settings?.pushGatewayBaseURL) XCTAssertEqual(clientProxy.setPusherFormat, .eventIdOnly) let defaultPayload: [AnyHashable: Any] = [ "aps": [ diff --git a/project.yml b/project.yml index 3662a5d3a..38a3f8e2b 100644 --- a/project.yml +++ b/project.yml @@ -40,7 +40,7 @@ include: packages: MatrixRustSDK: url: https://github.com/matrix-org/matrix-rust-components-swift - exactVersion: 1.0.30-alpha + exactVersion: 1.0.32-alpha # path: ../matrix-rust-sdk DesignKit: path: DesignKit