diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 422c641c0..7a253dbb9 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -355,7 +355,6 @@ 8B1D5CE017EEC734CF5FE130 /* Encodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 260004737C573A56FA01E86E /* Encodable.swift */; }; 8B41D0357B91CD3B6F6A3BCA /* EmoteRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE378083653EF0C9B5E9D580 /* EmoteRoomTimelineItemContent.swift */; }; 8B76191B9DDD1AC90A6E3A35 /* MediaFileHandleProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC1D382565A4E9CAC2F14EA /* MediaFileHandleProxy.swift */; }; - 8B7771E319436E542412A22C /* SlidingSyncListProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 074DA547928E85183066DB4A /* SlidingSyncListProxy.swift */; }; 8BC8EF6705A78946C1F22891 /* SoftLogoutScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A7D4DDEEE5D2CA0C8D63CD /* SoftLogoutScreen.swift */; }; 8C454500B8073E1201F801A9 /* MXLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = A34A814CBD56230BC74FFCF4 /* MXLogger.swift */; }; 8CC12086CBF91A7E10CDC205 /* HomeScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D653265D006E708E4E51AD64 /* HomeScreenCoordinator.swift */; }; @@ -442,7 +441,6 @@ A6F713461DB62AC06293E7B7 /* FilePreviewScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820637A0F9C2F562FF40CBC8 /* FilePreviewScreenModels.swift */; }; A74438ED16F8683A4B793E6A /* AnalyticsSettingsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BCE3FAF40932AC7C7639AC4 /* AnalyticsSettingsScreenViewModel.swift */; }; A7D48E44D485B143AADDB77D /* Strings+Untranslated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A18F6CE4D694D21E4EA9B25 /* Strings+Untranslated.swift */; }; - A7F55D1C2A3B639A009D8E93 /* ConfirmationDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7F55D1B2A3B639A009D8E93 /* ConfirmationDialog.swift */; }; A7FD7B992E6EE6E5A8429197 /* RoomSummaryDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = 142808B69851451AC32A2CEA /* RoomSummaryDetails.swift */; }; A816F7087C495D85048AC50E /* RoomMemberDetailsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B6E30BB748F3F480F077969 /* RoomMemberDetailsScreenModels.swift */; }; A851635B3255C6DC07034A12 /* RoomScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8108C8F0ACF6A7EB72D0117 /* RoomScreenCoordinator.swift */; }; @@ -621,6 +619,7 @@ E9F148072F9513EC2272AA21 /* SessionVerificationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A96A67AD0E32C48941EFBB3 /* SessionVerificationScreenCoordinator.swift */; }; EA01A06EEDFEF4AE7652E5F3 /* NSRegularExpresion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95BAC0F6C9644336E9567EE6 /* NSRegularExpresion.swift */; }; EA65360A0EC026DD83AC0CF5 /* AuthenticationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */; }; + EA6613B29BA671F39CE1B1D2 /* ConfirmationDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = B383DCD3DCB19E00FD478A5F /* ConfirmationDialog.swift */; }; EA974337FA7D040E7C74FE6E /* RoomDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EFE1922F39398ABFB36DF3F /* RoomDetailsViewModelTests.swift */; }; EAC6FE2CD4F50A43068ADCD8 /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 997C7385E1A07E061D7E2100 /* GZIP */; }; EAF2B3E6C6AEC4AD3A8BD454 /* RoomMemberDetailsScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A87D0471D438A233C2CF4A /* RoomMemberDetailsScreenViewModel.swift */; }; @@ -747,7 +746,6 @@ 05F598B1B346DAF223651C91 /* LoginScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginScreenCoordinator.swift; sourceTree = ""; }; 0685156EB62D7E243F097CFC /* ServerSelectionScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerSelectionScreenViewModelProtocol.swift; sourceTree = ""; }; 06B098A612DCB5A7358EECD5 /* DeveloperOptionsScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperOptionsScreenModels.swift; sourceTree = ""; }; - 074DA547928E85183066DB4A /* SlidingSyncListProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SlidingSyncListProxy.swift; sourceTree = ""; }; 077D7C3BE199B6E5DDEC07EC /* AppCoordinatorStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinatorStateMachine.swift; sourceTree = ""; }; 07E65E613F057697A1A0BC03 /* NotificationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationViewController.swift; sourceTree = ""; }; 086B997409328F091EBA43CE /* RoomScreenUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomScreenUITests.swift; sourceTree = ""; }; @@ -1124,7 +1122,6 @@ A6B891A6DA826E2461DBB40F /* PHGPostHogConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHGPostHogConfiguration.swift; sourceTree = ""; }; A73A07BAEDD74C48795A996A /* AsyncSequence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncSequence.swift; sourceTree = ""; }; A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTimelineItem.swift; sourceTree = ""; }; - A7F55D1B2A3B639A009D8E93 /* ConfirmationDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationDialog.swift; sourceTree = ""; }; A861DA5932B128FE1DCB5CE2 /* InviteUsersScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenCoordinator.swift; sourceTree = ""; }; A8903A9F615BBD0E6D7CD133 /* ApplicationProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationProtocol.swift; sourceTree = ""; }; A9FAFE1C2149E6AC8156ED2B /* Collection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Collection.swift; sourceTree = ""; }; @@ -1160,6 +1157,7 @@ B2B5EDCD05D50BA9B815C66C /* ImageRoomTimelineItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineItemContent.swift; sourceTree = ""; }; B2E7C987AE5DC9087BB19F7D /* MediaUploadPreviewScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaUploadPreviewScreenModels.swift; sourceTree = ""; }; B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartChatScreenCoordinator.swift; sourceTree = ""; }; + B383DCD3DCB19E00FD478A5F /* ConfirmationDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfirmationDialog.swift; sourceTree = ""; }; B43456E73F8A2D52B69B9FB9 /* TemplateScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenViewModel.swift; sourceTree = ""; }; B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadableAvatarImage.swift; sourceTree = ""; }; B5B243E7818E5E9F6A4EDC7A /* NoticeRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoticeRoomTimelineView.swift; sourceTree = ""; }; @@ -1744,6 +1742,13 @@ path = Views; sourceTree = ""; }; + 3348D14DBDB54E72FC67E2F3 /* MessageForwardingScreen */ = { + isa = PBXGroup; + children = ( + ); + path = MessageForwardingScreen; + sourceTree = ""; + }; 337015ADFBA3AB96660DB3A6 /* Generated */ = { isa = PBXGroup; children = ( @@ -1908,6 +1913,7 @@ 52BD6ED18E2EB61E28C340AD /* AttributedString.swift */, B6E89E530A8E92EC44301CA1 /* Bundle.swift */, A9FAFE1C2149E6AC8156ED2B /* Collection.swift */, + B383DCD3DCB19E00FD478A5F /* ConfirmationDialog.swift */, 2141693488CE5446BB391964 /* Date.swift */, BFDCAC6CAAD65A2C24EA9C4B /* Dictionary.swift */, 260004737C573A56FA01E86E /* Encodable.swift */, @@ -1930,7 +1936,6 @@ AE40D4A5DD857AC16EED945A /* URLSession.swift */, 897DF5E9A70CE05A632FC8AF /* UTType.swift */, E992D7B8BE54B2AB454613AF /* XCUIElement.swift */, - A7F55D1B2A3B639A009D8E93 /* ConfirmationDialog.swift */, ); path = Extensions; sourceTree = ""; @@ -2458,7 +2463,6 @@ 18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */, 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */, 3F40F48279322E504153AB0D /* MockClientProxy.swift */, - 074DA547928E85183066DB4A /* SlidingSyncListProxy.swift */, ); path = Client; sourceTree = ""; @@ -3150,6 +3154,7 @@ 948DD12A5533BE1BC260E437 /* LocationSharing */, 87E2774157D9C4894BCFF3F8 /* MediaPickerScreen */, 23605DD08620BE6558242469 /* MediaUploadPreviewScreen */, + 3348D14DBDB54E72FC67E2F3 /* MessageForwardingScreen */, 3F38EAC92E2281990E65DAF2 /* OnboardingScreen */, A448A3A8F764174C60CD0CA1 /* Other */, 5970F275D6014548DCED6106 /* ReportContentScreen */, @@ -3909,6 +3914,7 @@ 9FAF6DA7E8E85C9699757764 /* CollapsibleRoomTimelineView.swift in Sources */, 0DC815CA24E1BD7F408F37D3 /* CollapsibleTimelineItem.swift in Sources */, 663E198678778F7426A9B27D /* Collection.swift in Sources */, + EA6613B29BA671F39CE1B1D2 /* ConfirmationDialog.swift in Sources */, AC7AA215D60FBC307F984028 /* Consumable.swift in Sources */, C3522917C0C367C403429EEC /* CoordinatorProtocol.swift in Sources */, 564BF06B3E93D6DD55F903B2 /* CreateRoomCoordinator.swift in Sources */, @@ -4191,7 +4197,6 @@ A009BDFB0A6816D4C392ADCB /* SettingsScreenViewModelProtocol.swift in Sources */, 8922219C5C934C4155E8CA50 /* SharedUserDefaultsKeys.swift in Sources */, 274CE3C986841D15FD530BF5 /* ShimmerModifier.swift in Sources */, - 8B7771E319436E542412A22C /* SlidingSyncListProxy.swift in Sources */, 8BC8EF6705A78946C1F22891 /* SoftLogoutScreen.swift in Sources */, A3A7A05E8F9B7EB0E1A09A2A /* SoftLogoutScreenCoordinator.swift in Sources */, F86102DC2C68BBBB0521BAAE /* SoftLogoutScreenModels.swift in Sources */, @@ -4209,7 +4214,6 @@ B1069F361E604D5436AE9FFD /* StaticLocationScreen.swift in Sources */, 1AB3D8563AB12635250A6A6E /* StaticLocationScreenCoordinator.swift in Sources */, DFD5AA8688A34C72D48AF3B1 /* StaticLocationScreenViewModel.swift in Sources */, - A7F55D1C2A3B639A009D8E93 /* ConfirmationDialog.swift in Sources */, 5C164551F7D26E24F09083D3 /* StaticLocationScreenViewModelProtocol.swift in Sources */, C58E305C380D3ADDF7912180 /* StickerRoomTimelineItem.swift in Sources */, F32B271F60531BE92C6E62A1 /* StickerRoomTimelineView.swift in Sources */, @@ -4944,7 +4948,7 @@ repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = "1.0.75-alpha"; + version = "1.0.76-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 39805bc50..e3262ad8f 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -11,7 +11,7 @@ { "identity" : "compound-ios", "kind" : "remoteSourceControl", - "location" : "https://github.com/vector-im/compound-ios.git", + "location" : "https://github.com/vector-im/compound-ios", "state" : { "revision" : "d59c317362beba940baa43d6aacdd357e208048d" } @@ -111,8 +111,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-rust-components-swift", "state" : { - "revision" : "c6e794f305a34453ed081be131b2eea719154bc5", - "version" : "1.0.75-alpha" + "revision" : "06e154e94138e93fd4609471e67fb56f2daef472", + "version" : "1.0.76-alpha" } }, { diff --git a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift index ac7242dd7..4aa683ec5 100644 --- a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift @@ -404,23 +404,6 @@ class SDKClientMock: SDKClientProtocol { restoreSessionSessionReceivedInvocations.append(`session`) try restoreSessionSessionClosure?(`session`) } - //MARK: - `rooms` - - public var roomsCallsCount = 0 - public var roomsCalled: Bool { - return roomsCallsCount > 0 - } - public var roomsReturnValue: [Room]! - public var roomsClosure: (() -> [Room])? - - public func `rooms`() -> [Room] { - roomsCallsCount += 1 - if let roomsClosure = roomsClosure { - return roomsClosure() - } else { - return roomsReturnValue - } - } //MARK: - `roomList` public var roomListThrowableError: Error? @@ -442,6 +425,23 @@ class SDKClientMock: SDKClientProtocol { return roomListReturnValue } } + //MARK: - `rooms` + + public var roomsCallsCount = 0 + public var roomsCalled: Bool { + return roomsCallsCount > 0 + } + public var roomsReturnValue: [Room]! + public var roomsClosure: (() -> [Room])? + + public func `rooms`() -> [Room] { + roomsCallsCount += 1 + if let roomsClosure = roomsClosure { + return roomsClosure() + } else { + return roomsReturnValue + } + } //MARK: - `searchUsers` public var searchUsersSearchTermLimitThrowableError: Error? diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index 65b65b7fa..f1ed371a9 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -21,9 +21,8 @@ typealias HomeScreenViewModelType = StateStoreViewModel HomeScreenRoom { - let identifier = invalidated ? "invalidated-" + details.id : details.id - - return HomeScreenRoom(id: identifier, - roomId: details.id, - name: details.name, - hasUnreads: details.unreadNotificationCount > 0, - timestamp: details.lastMessageFormattedTimestamp, - lastMessage: .init(attributedString: details.lastMessage, isLoading: isLoading), - avatarURL: details.avatarURL) + private func buildRoom(with details: RoomSummaryDetails) -> HomeScreenRoom { + HomeScreenRoom(id: details.id, + roomId: details.id, + name: details.name, + hasUnreads: details.unreadNotificationCount > 0, + timestamp: details.lastMessageFormattedTimestamp, + lastMessage: .init(attributedString: details.lastMessage, isLoading: false), + avatarURL: details.avatarURL) } - private func updateVisibleRange(_ range: Range, timelineLimit: UInt) { + private func updateVisibleRange(_ range: Range) { guard !range.isEmpty else { return } - guard let visibleRoomsSummaryProvider else { + guard let roomSummaryProvider else { MXLog.error("Visible rooms summary provider unavailable") return } - visibleRoomsSummaryProvider.updateVisibleRange(range, timelineLimit: timelineLimit) + roomSummaryProvider.updateVisibleRange(range) } private static let leaveRoomLoadingID = "LeaveRoomLoading" diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift index 13a46b302..ab6aad990 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift @@ -177,7 +177,7 @@ struct HomeScreenRoomCell_Previews: PreviewProvider { switch summary { case .empty: return nil - case .filled(let details), .invalidated(let details): + case .filled(let details): return HomeScreenRoom(id: UUID().uuidString, roomId: details.id, name: details.name, diff --git a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift index d45cfc576..f98d56043 100644 --- a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift +++ b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift @@ -52,17 +52,17 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel userSession.clientProxy } - private var invitesSummaryProvider: RoomSummaryProviderProtocol? { - clientProxy.invitesSummaryProvider + private var inviteSummaryProvider: RoomSummaryProviderProtocol? { + clientProxy.inviteSummaryProvider } private func setupSubscriptions() { - guard let invitesSummaryProvider else { + guard let inviteSummaryProvider else { MXLog.error("Room summary provider unavailable") return } - invitesSummaryProvider.roomListPublisher + inviteSummaryProvider.roomListPublisher .receive(on: DispatchQueue.main) .sink { [weak self] roomSummaries in guard let self else { return } diff --git a/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreen.swift b/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreen.swift index 48d748ad9..bd561eaab 100644 --- a/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreen.swift +++ b/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreen.swift @@ -78,8 +78,8 @@ private extension InvitesScreenViewModel { static let someInvite: InvitesScreenViewModel = { let clientProxy = MockClientProxy(userID: "@userid:example.com") - clientProxy.invitesSummaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites)) - clientProxy.visibleRoomsSummaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites)) + clientProxy.inviteSummaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites)) + clientProxy.roomSummaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites)) let userSession = MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()) let regularViewModel = InvitesScreenViewModel(userSession: userSession) diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 4fa5f6ea0..5dcd8f558 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -31,7 +31,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol private let timelineController: RoomTimelineControllerProtocol private unowned let userIndicatorController: UserIndicatorControllerProtocol private var loadingTask: Task? - + init(timelineController: RoomTimelineControllerProtocol, mediaProvider: MediaProviderProtocol, roomProxy: RoomProxyProtocol, diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index fc4289b44..e8c1aff32 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -19,7 +19,7 @@ import Foundation import MatrixRustSDK import UIKit -private class WeakClientProxyWrapper: ClientDelegate, SlidingSyncObserver { +private class WeakClientProxyWrapper: ClientDelegate { private weak var clientProxy: ClientProxy? init(clientProxy: ClientProxy) { @@ -37,13 +37,6 @@ private class WeakClientProxyWrapper: ClientDelegate, SlidingSyncObserver { MXLog.info("The session has updated tokens.") clientProxy?.updateRestorationToken() } - - // MARK: - SlidingSyncDelegate - - func didReceiveSyncUpdate(summary: UpdateSummary) { - MXLog.info("Received sliding sync update") - clientProxy?.didReceiveSlidingSyncUpdate(summary: summary) - } } class ClientProxy: ClientProxyProtocol { @@ -52,22 +45,13 @@ class ClientProxy: ClientProxyProtocol { private var sessionVerificationControllerProxy: SessionVerificationControllerProxy? private let mediaLoader: MediaLoaderProtocol private let clientQueue: DispatchQueue + + private var roomListService: RoomList? + private var roomListSyncTaskHandle: TaskHandle? + private var roomListStateUpdateTaskHandle: TaskHandle? - private var slidingSyncObserverToken: TaskHandle? - private var slidingSync: SlidingSyncProtocol? - private var slidingSyncTasks = [TaskHandle?]() - - var visibleRoomsListBuilder: SlidingSyncListBuilder? - var visibleRoomsListProxy: SlidingSyncListProxy? - var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol? - - var allRoomsListBuilder: SlidingSyncListBuilder? - var allRoomsListProxy: SlidingSyncListProxy? - var allRoomsSummaryProvider: RoomSummaryProviderProtocol? - - var invitesListBuilder: SlidingSyncListBuilder? - var invitesListProxy: SlidingSyncListProxy? - var invitesSummaryProvider: RoomSummaryProviderProtocol? + var roomSummaryProvider: RoomSummaryProviderProtocol? + var inviteSummaryProvider: RoomSummaryProviderProtocol? private let roomListRecencyOrderingAllowedEventTypes = ["m.room.message", "m.room.encrypted", "m.sticker"] @@ -87,8 +71,7 @@ class ClientProxy: ClientProxyProtocol { // These need to be inlined instead of using stopSync() // as we can't call async methods safely from deinit client.setDelegate(delegate: nil) - slidingSyncObserverToken?.cancel() - slidingSync?.setObserver(observer: nil) + roomListSyncTaskHandle?.cancel() } let callbacks = PassthroughSubject() @@ -103,7 +86,7 @@ class ClientProxy: ClientProxyProtocol { let delegate = WeakClientProxyWrapper(clientProxy: self) client.setDelegate(delegate: delegate) - configureSlidingSync() + await configureRoomListService() loadUserAvatarURLFromCache() } @@ -140,7 +123,7 @@ class ClientProxy: ClientProxyProtocol { } var isSyncing: Bool { - slidingSyncObserverToken != nil + roomListSyncTaskHandle != nil } func startSync() { @@ -149,18 +132,13 @@ class ClientProxy: ClientProxyProtocol { return } - slidingSyncObserverToken = slidingSync?.sync() + roomListSyncTaskHandle = roomListService?.sync() } func stopSync() { MXLog.info("Stopping sync") - slidingSyncObserverToken = nil - - do { - try slidingSync?.stopSync() - } catch { - MXLog.error("Failed stopping sync with error: \(error). Ignore this error if ran just before the app being suspended") - } + roomListSyncTaskHandle?.cancel() + roomListSyncTaskHandle = nil } func directRoomForUserID(_ userID: String) async -> Result { @@ -229,7 +207,7 @@ class ClientProxy: ClientProxyProtocol { private func waitForRoomSummary(with result: Result, name: String?) async -> Result { guard case .success(let roomId) = result else { return result } let runner = ExpiringTaskRunner { [weak self] in - guard let roomLists = self?.allRoomsSummaryProvider?.roomListPublisher.values else { + guard let roomLists = self?.roomSummaryProvider?.roomListPublisher.values else { return } // for every list of summaries, we check if we have a room summary with matching ID and name (if present) @@ -249,42 +227,42 @@ class ClientProxy: ClientProxyProtocol { func roomForIdentifier(_ identifier: String) async -> RoomProxyProtocol? { // Try fetching the room from the cold cache (if available) first - var (slidingSyncRoom, room) = await Task.dispatch(on: clientQueue) { + var (roomListItem, room) = await Task.dispatch(on: clientQueue) { self.roomTupleForIdentifier(identifier) } - if let slidingSyncRoom, let room { - return await RoomProxy(slidingSyncRoom: slidingSyncRoom, + if let roomListItem, let room { + return await RoomProxy(roomListItem: roomListItem, room: room, backgroundTaskService: backgroundTaskService) } // Else wait for the visible rooms list to go into fully loaded - guard let visibleRoomsSummaryProvider else { - MXLog.error("Visible rooms summary provider not setup yet") + guard let roomSummaryProvider else { + MXLog.error("Rooms summary provider not setup yet") return nil } - if visibleRoomsSummaryProvider.statePublisher.value != .fullyLoaded { - _ = await visibleRoomsSummaryProvider.statePublisher.values.first(where: { $0 == .fullyLoaded }) + if roomSummaryProvider.statePublisher.value != .fullyLoaded { + _ = await roomSummaryProvider.statePublisher.values.first(where: { $0 == .fullyLoaded }) } - (slidingSyncRoom, room) = await Task.dispatch(on: clientQueue) { + (roomListItem, room) = await Task.dispatch(on: clientQueue) { self.roomTupleForIdentifier(identifier) } - - guard let slidingSyncRoom else { - MXLog.error("Invalid slidingSyncRoom for identifier \(identifier)") + + guard let roomListItem else { + MXLog.error("Invalid roomListItem for identifier \(identifier)") return nil } guard let room else { - MXLog.error("Invalid slidingSyncRoom fullRoom for identifier \(identifier)") + MXLog.error("Invalid roomListItem fullRoom for identifier \(identifier)") return nil } - - return await RoomProxy(slidingSyncRoom: slidingSyncRoom, + + return await RoomProxy(roomListItem: roomListItem, room: room, backgroundTaskService: backgroundTaskService) } @@ -378,6 +356,11 @@ class ClientProxy: ClientProxyProtocol { } // MARK: Private + + private func restartSync() { + stopSync() + startSync() + } private func loadUserAvatarURLFromCache() { loadCachedAvatarURLTask = Task { @@ -394,210 +377,59 @@ class ClientProxy: ClientProxyProtocol { } } - private func configureSlidingSync() { - guard slidingSync == nil else { + private func configureRoomListService() async { + guard roomListService == nil else { fatalError("This shouldn't be called more than once") } do { - let slidingSyncBuilder = try client.slidingSync(id: "ElementX") + let roomListService = try client.roomList() + roomListStateUpdateTaskHandle = roomListService.state(listener: RoomListStateListenerProxy { [weak self] state in + guard let self else { return } + MXLog.info("Received room list update: \(state)") + + if state == .allRooms || state == .carryOn { + self.callbacks.send(.receivedSyncUpdate) + } + + #warning("Not great, not terrible (ツ)") + if state == .terminated { + self.restartSync() + } + + if state == .allRooms { + Task { + // Subscribe to invites later as the underlying SlidingSync list is only added when entering AllRooms + await self.inviteSummaryProvider?.subscribeIfNecessary(entriesFunction: roomListService.invites(listener:), + // This state function is wrong but it's not used + entriesLoadingStateFunction: roomListService.entriesLoadingState(listener:)) + } + } + }) - // List observers need to be setup before calling build() on the SlidingSyncBuilder otherwise - // cold cache state and count updates will be lost - buildAndConfigureVisibleRoomsSlidingSyncList() - buildAndConfigureAllRoomsSlidingSyncList() - buildAndConfigureInvitesSlidingSyncList() - - guard let visibleRoomsListBuilder else { - MXLog.error("Visible rooms sliding sync view unavailable") - return - } - - // Add the visibleRoomsSlidingSyncList here so that it can take advantage of the SS builder cold cache - // We will still register the allRoomsSlidingSyncList later, and than will have no cache - var slidingSyncBuilderWithExtension = slidingSyncBuilder - .addList(listBuilder: visibleRoomsListBuilder) - .withCommonExtensions() - - if !ServiceLocator.shared.settings.readReceiptsEnabled { - slidingSyncBuilderWithExtension = slidingSyncBuilderWithExtension.withoutReceiptExtension() - } - - let slidingSync = try slidingSyncBuilderWithExtension.build() - - // Don't forget to update the view proxies after building the slidingSync - visibleRoomsListProxy?.setSlidingSync(slidingSync: slidingSync) - allRoomsListProxy?.setSlidingSync(slidingSync: slidingSync) - invitesListProxy?.setSlidingSync(slidingSync: slidingSync) - - // Build the room summary providers later so the sliding sync view proxies are up to date and the - // currentRoomList is populated with the data from the cold cache - buildRoomSummaryProviders() - - slidingSync.setObserver(observer: WeakClientProxyWrapper(clientProxy: self)) - - self.slidingSync = slidingSync - } catch { - MXLog.error("Failed building sliding sync with error: \(error)") - } - } - - private func buildAndConfigureVisibleRoomsSlidingSyncList() { - guard visibleRoomsListBuilder == nil else { - fatalError("This shouldn't be called more than once") - } - - let listName = "CurrentlyVisibleRooms" - - let visibleRoomsListProxy = SlidingSyncListProxy(name: listName) - - let visibleRoomsListBuilder = SlidingSyncListBuilder(name: listName) - .timelineLimit(limit: UInt32(SlidingSyncConstants.initialTimelineLimit)) // Starts off with zero to quickly load rooms, then goes to 1 while scrolling to quickly load last messages and 20 when the scrolling stops to load room history - .requiredState(requiredState: slidingSyncRequiredState) - .filters(filters: slidingSyncFilters) - .syncModeSelective(selectiveModeBuilder: .init().addRange(start: 0, endInclusive: 20)) - .bumpEventTypes(bumpEventTypes: roomListRecencyOrderingAllowedEventTypes) - .onceBuilt(callback: visibleRoomsListProxy) - - self.visibleRoomsListBuilder = visibleRoomsListBuilder - self.visibleRoomsListProxy = visibleRoomsListProxy - - // The allRoomsSlidingSyncList will be registered as soon as the visibleRoomsSlidingSyncList receives its first update - visibleRoomsListProxyStateObservationToken = visibleRoomsListProxy.statePublisher.sink { [weak self] state in - guard state == .fullyLoaded else { - return - } - - MXLog.info("Visible rooms view received first update, configuring views post initial sync") - self?.configureViewsPostInitialSync() - self?.visibleRoomsListProxyStateObservationToken = nil - } - } - - private func buildAndConfigureAllRoomsSlidingSyncList() { - guard allRoomsListBuilder == nil else { - fatalError("This shouldn't be called more than once") - } - - let listName = "AllRooms" - - let allRoomsListProxy = SlidingSyncListProxy(name: listName) - - let allRoomsListBuilder = SlidingSyncListBuilder(name: listName) - .noTimelineLimit() - .requiredState(requiredState: slidingSyncRequiredState) - .filters(filters: slidingSyncFilters) - .syncModeGrowing(batchSize: 100, maximumNumberOfRoomsToFetch: nil) - .bumpEventTypes(bumpEventTypes: roomListRecencyOrderingAllowedEventTypes) - .onceBuilt(callback: allRoomsListProxy) - - self.allRoomsListBuilder = allRoomsListBuilder - self.allRoomsListProxy = allRoomsListProxy - } - - private func buildAndConfigureInvitesSlidingSyncList() { - guard invitesListBuilder == nil else { - fatalError("This shouldn't be called more than once") - } - - let listName = "Invites" - - let invitesListProxy = SlidingSyncListProxy(name: "Invites") - - let invitesListBuilder = SlidingSyncListBuilder(name: listName) - .noTimelineLimit() - .requiredState(requiredState: slidingSyncInvitesRequiredState) - .filters(filters: slidingSyncInviteFilters) - .syncModeGrowing(batchSize: 100, maximumNumberOfRoomsToFetch: nil) - .onceBuilt(callback: invitesListProxy) - - self.invitesListBuilder = invitesListBuilder - self.invitesListProxy = invitesListProxy - } - - private func buildRoomSummaryProviders() { - guard visibleRoomsSummaryProvider == nil, allRoomsSummaryProvider == nil, invitesSummaryProvider == nil else { - fatalError("This shouldn't be called more than once") - } - - guard let visibleRoomsListProxy, let allRoomsListProxy, let invitesListProxy else { - MXLog.error("Sliding sync view proxies unavailable") - return - } - - visibleRoomsSummaryProvider = RoomSummaryProvider(slidingSyncListProxy: visibleRoomsListProxy, - eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)), - name: visibleRoomsListProxy.name) - - allRoomsSummaryProvider = RoomSummaryProvider(slidingSyncListProxy: allRoomsListProxy, + roomSummaryProvider = RoomSummaryProvider(roomListService: roomListService, eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)), - name: allRoomsListProxy.name) - - invitesSummaryProvider = RoomSummaryProvider(slidingSyncListProxy: invitesListProxy, - eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)), - name: invitesListProxy.name) - } - - private lazy var slidingSyncRequiredState = [RequiredState(key: "m.room.avatar", value: ""), - RequiredState(key: "m.room.encryption", value: ""), - RequiredState(key: "m.room.power_levels", value: "")] - - private lazy var slidingSyncInvitesRequiredState = [RequiredState(key: "m.room.avatar", value: ""), - RequiredState(key: "m.room.encryption", value: ""), - RequiredState(key: "m.room.member", value: "$ME"), - RequiredState(key: "m.room.canonical_alias", value: "")] - - private lazy var slidingSyncFilters = SlidingSyncRequestListFilters(isDm: nil, - spaces: [], - isEncrypted: nil, - isInvite: false, - isTombstoned: false, - roomTypes: [], - notRoomTypes: ["m.space"], - roomNameLike: nil, - tags: [], - notTags: []) - - private lazy var slidingSyncInviteFilters = SlidingSyncRequestListFilters(isDm: nil, - spaces: [], - isEncrypted: nil, - isInvite: true, - isTombstoned: false, - roomTypes: [], - notRoomTypes: ["m.space"], - roomNameLike: nil, - tags: [], - notTags: []) - - private func configureViewsPostInitialSync() { - if let visibleRoomsListProxy { - MXLog.info("Setting visible rooms view timeline limit to \(SlidingSyncConstants.lastMessageTimelineLimit)") - visibleRoomsListProxy.updateVisibleRange(nil, timelineLimit: SlidingSyncConstants.lastMessageTimelineLimit) - } else { - MXLog.error("Visible rooms sliding sync view unavailable") - } - - if let allRoomsListBuilder { - MXLog.info("Registering all rooms view") - slidingSyncTasks.append(slidingSync?.addList(listBuilder: allRoomsListBuilder)) - } else { - MXLog.error("All rooms sliding sync view unavailable") - } - - if let invitesListBuilder { - MXLog.info("Registering invites view") - slidingSyncTasks.append(slidingSync?.addList(listBuilder: invitesListBuilder)) - } else { - MXLog.error("Invites sliding sync view unavailable") + name: "AllRooms") + + await roomSummaryProvider?.subscribeIfNecessary(entriesFunction: roomListService.entries(listener:), + entriesLoadingStateFunction: roomListService.entriesLoadingState(listener:)) + + inviteSummaryProvider = RoomSummaryProvider(roomListService: roomListService, + eventStringBuilder: RoomEventStringBuilder(stateEventStringBuilder: RoomStateEventStringBuilder(userID: userID)), + name: "Invites") + + self.roomListService = roomListService + } catch { + MXLog.error("Failed building room list service with error: \(error)") } } - private func roomTupleForIdentifier(_ identifier: String) -> (SlidingSyncRoom?, Room?) { + private func roomTupleForIdentifier(_ identifier: String) -> (RoomListItem?, Room?) { do { - let slidingSyncRoom = try slidingSync?.getRoom(roomId: identifier) - let fullRoom = slidingSyncRoom?.fullRoom() - - return (slidingSyncRoom, fullRoom) + let roomListItem = try roomListService?.room(roomId: identifier) + let fullRoom = roomListItem?.fullRoom() + + return (roomListItem, fullRoom) } catch { MXLog.error("Failed retrieving room with identifier: \(identifier)") return (nil, nil) @@ -611,10 +443,6 @@ class ClientProxy: ClientProxyProtocol { fileprivate func didReceiveAuthError(isSoftLogout: Bool) { callbacks.send(.receivedAuthError(isSoftLogout: isSoftLogout)) } - - fileprivate func didReceiveSlidingSyncUpdate(summary: UpdateSummary) { - callbacks.send(.receivedSyncUpdate) - } } extension ClientProxy: MediaLoaderProtocol { @@ -630,3 +458,15 @@ extension ClientProxy: MediaLoaderProtocol { try await mediaLoader.loadMediaFileForSource(source, body: body) } } + +private class RoomListStateListenerProxy: RoomListStateListener { + private let onUpdateClosure: (RoomListState) -> Void + + init(_ onUpdateClosure: @escaping (RoomListState) -> Void) { + self.onUpdateClosure = onUpdateClosure + } + + func onUpdate(state: RoomListState) { + onUpdateClosure(state) + } +} diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index 613d5a300..3599cd4d6 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -47,9 +47,7 @@ enum ClientProxyError: Error { } enum SlidingSyncConstants { - static let initialTimelineLimit: UInt = 0 - static let lastMessageTimelineLimit: UInt = 1 - static let timelinePrecachingTimelineLimit: UInt = 20 + static let defaultTimelineLimit: UInt = 20 } /// This struct represents the configuration that we are using to register the application through Pusher to Sygnal @@ -77,11 +75,9 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { var restorationToken: RestorationToken? { get } - var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol? { get } + var roomSummaryProvider: RoomSummaryProviderProtocol? { get } - var allRoomsSummaryProvider: RoomSummaryProviderProtocol? { get } - - var invitesSummaryProvider: RoomSummaryProviderProtocol? { get } + var inviteSummaryProvider: RoomSummaryProviderProtocol? { get } var isSyncing: Bool { get } diff --git a/ElementX/Sources/Services/Client/MockClientProxy.swift b/ElementX/Sources/Services/Client/MockClientProxy.swift index c7436e1dd..9c21ddab6 100644 --- a/ElementX/Sources/Services/Client/MockClientProxy.swift +++ b/ElementX/Sources/Services/Client/MockClientProxy.swift @@ -26,11 +26,9 @@ class MockClientProxy: ClientProxyProtocol { let homeserver = "" let restorationToken: RestorationToken? = nil - var visibleRoomsSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider() + var roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider() - var allRoomsSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider() - - var invitesSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider() + var inviteSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider() var avatarURLPublisher: AnyPublisher { Empty().eraseToAnyPublisher() } @@ -38,7 +36,7 @@ class MockClientProxy: ClientProxyProtocol { internal init(userID: String, roomSummaryProvider: RoomSummaryProviderProtocol? = MockRoomSummaryProvider()) { self.userID = userID - visibleRoomsSummaryProvider = roomSummaryProvider + self.roomSummaryProvider = roomSummaryProvider } func loadUserAvatarURL() async { } @@ -72,14 +70,14 @@ class MockClientProxy: ClientProxyProtocol { return roomForIdentifierMocks[identifier] } - guard let room = visibleRoomsSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else { + guard let room = roomSummaryProvider?.roomListPublisher.value.first(where: { $0.id == identifier }) else { return nil } switch room { case .empty: return RoomProxyMock(with: .init(displayName: "Empty room")) - case .filled(let details), .invalidated(let details): + case .filled(let details): return RoomProxyMock(with: .init(displayName: details.name)) } } diff --git a/ElementX/Sources/Services/Client/SlidingSyncListProxy.swift b/ElementX/Sources/Services/Client/SlidingSyncListProxy.swift deleted file mode 100644 index 80ac9bd6e..000000000 --- a/ElementX/Sources/Services/Client/SlidingSyncListProxy.swift +++ /dev/null @@ -1,141 +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 Combine -import Foundation -import MatrixRustSDK - -private class SlidingSyncListObserver: SlidingSyncListRoomListObserver, SlidingSyncListStateObserver, SlidingSyncListRoomsCountObserver { - /// Publishes room list diffs as they come in through sliding sync - let roomListDiffPublisher = PassthroughSubject() - - /// Publishes the current state of sliding sync, such as whether its catching up or live. - let stateUpdatePublisher = CurrentValueSubject(.notLoaded) - - /// Publishes the number of available rooms - let countUpdatePublisher = CurrentValueSubject(0) - - private let name: String - - init(name: String) { - self.name = name - } - - // MARK: - SlidingSyncListRoomListObserver - - func didReceiveUpdate(diff: SlidingSyncListRoomsListDiff) { - MXLog.verbose("\(name): Received room diff") - roomListDiffPublisher.send(diff) - } - - // MARK: - SlidingSyncListStateObserver - - func didReceiveUpdate(newState: SlidingSyncState) { - MXLog.info("\(name): Updated state: \(newState)") - stateUpdatePublisher.send(newState) - } - - // MARK: - SlidingSyncListRoomsCountObserver - - func didReceiveUpdate(count: UInt32) { - MXLog.info("\(name): Updated room count: \(count)") - countUpdatePublisher.send(UInt(count)) - } -} - -class SlidingSyncListProxy: SlidingSyncListOnceBuilt { - let name: String - private var slidingSync: SlidingSyncProtocol? - private var slidingSyncList: SlidingSyncListProtocol? - - private var listUpdateObserverToken: TaskHandle? - private var stateUpdateObserverToken: TaskHandle? - private var countUpdateObserverToken: TaskHandle? - - private var cancellables = Set() - - let diffPublisher = PassthroughSubject() - let statePublisher = PassthroughSubject() - let countPublisher = PassthroughSubject() - - deinit { - listUpdateObserverToken?.cancel() - stateUpdateObserverToken?.cancel() - countUpdateObserverToken?.cancel() - } - - init(name: String) { - self.name = name - } - - func setSlidingSync(slidingSync: SlidingSyncProtocol) { - self.slidingSync = slidingSync - } - - func currentRoomsList() -> [RoomListEntry] { - guard let slidingSyncList else { - return [] - } - - return slidingSyncList.currentRoomList() - } - - func roomForIdentifier(_ identifier: String) throws -> SlidingSyncRoomProtocol? { - guard let slidingSync else { - return nil - } - - return try slidingSync.getRoom(roomId: identifier) - } - - func updateVisibleRange(_ range: Range?, timelineLimit: UInt?) { - if let range { - MXLog.info("Setting '\(name)' list range to \(range)") - slidingSyncList?.setSyncMode(builder: .init().addRange(start: UInt32(range.lowerBound), endInclusive: UInt32(range.upperBound))) - } - - if let timelineLimit { - MXLog.info("Setting '\(name)' list timeline limit to \(timelineLimit)") - slidingSyncList?.setTimelineLimit(value: UInt32(timelineLimit)) - } - } - - // MARK: - SlidingSyncListOnceBuilt - - func updateList(list: MatrixRustSDK.SlidingSyncList) -> MatrixRustSDK.SlidingSyncList { - slidingSyncList = list - - let slidingSyncListObserver = SlidingSyncListObserver(name: name) - - slidingSyncListObserver.stateUpdatePublisher - .subscribe(statePublisher) - .store(in: &cancellables) - - slidingSyncListObserver.countUpdatePublisher - .subscribe(countPublisher) - .store(in: &cancellables) - - slidingSyncListObserver.roomListDiffPublisher - .subscribe(diffPublisher) - .store(in: &cancellables) - - listUpdateObserverToken = list.observeRoomList(observer: slidingSyncListObserver) - stateUpdateObserverToken = list.observeState(observer: slidingSyncListObserver) - countUpdateObserverToken = list.observeRoomsCount(observer: slidingSyncListObserver) - - return list - } -} diff --git a/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift b/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift index 5dcef5450..ff453ab6d 100644 --- a/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift +++ b/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift @@ -190,7 +190,7 @@ struct MediaUploadingPreprocessor { mimetype: thumbnailResult.mimeType, size: thumbnailSize) - let videoInfo = VideoInfo(duration: UInt64(result.duration), + let videoInfo = VideoInfo(duration: result.duration, height: UInt64(result.height), width: UInt64(result.width), mimetype: result.mimeType, @@ -223,7 +223,7 @@ struct MediaUploadingPreprocessor { return .failure(.failedProcessingAudio) } - let audioInfo = AudioInfo(duration: UInt64(durationInSeconds * 1000), size: fileSize, mimetype: mimeType) + let audioInfo = AudioInfo(duration: durationInSeconds * 1000, size: fileSize, mimetype: mimeType) return .success(.audio(audioURL: url, audioInfo: audioInfo)) } diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 295df35b3..1858966d7 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -21,7 +21,7 @@ import UIKit import MatrixRustSDK class RoomProxy: RoomProxyProtocol { - private let slidingSyncRoom: SlidingSyncRoomProtocol + private let roomListItem: RoomListItemProtocol private let room: RoomProtocol private let backgroundTaskService: BackgroundTaskServiceProtocol private let backgroundTaskName = "SendRoomEvent" @@ -47,16 +47,16 @@ class RoomProxy: RoomProxyProtocol { } deinit { - Task { @MainActor [roomTimelineObservationToken, slidingSyncRoom] in + Task { @MainActor [roomTimelineObservationToken, roomListItem] in roomTimelineObservationToken?.cancel() - slidingSyncRoom.unsubscribeFromRoom() + roomListItem.unsubscribe() } } - init(slidingSyncRoom: SlidingSyncRoomProtocol, + init(roomListItem: RoomListItemProtocol, room: RoomProtocol, backgroundTaskService: BackgroundTaskServiceProtocol) { - self.slidingSyncRoom = slidingSyncRoom + self.roomListItem = roomListItem self.room = room self.backgroundTaskService = backgroundTaskService } @@ -64,7 +64,7 @@ class RoomProxy: RoomProxyProtocol { lazy var id: String = room.id() var name: String? { - slidingSyncRoom.name() + roomListItem.name() } var topic: String? { @@ -104,7 +104,7 @@ class RoomProxy: RoomProxyProtocol { } var hasUnreadNotifications: Bool { - slidingSyncRoom.hasUnreadNotifications() + roomListItem.hasUnreadNotifications() } var avatarURL: URL? { @@ -166,33 +166,30 @@ class RoomProxy: RoomProxyProtocol { guard timelineListener == nil else { return .failure(.roomListenerAlreadyRegistered) } - + let settings = RoomSubscription(requiredState: [RequiredState(key: "m.room.name", value: ""), RequiredState(key: "m.room.topic", value: ""), RequiredState(key: "m.room.avatar", value: ""), RequiredState(key: "m.room.canonical_alias", value: ""), RequiredState(key: "m.room.join_rules", value: "")], - timelineLimit: UInt32(SlidingSyncConstants.timelinePrecachingTimelineLimit)) - slidingSyncRoom.subscribeToRoom(settings: settings) - + timelineLimit: UInt32(SlidingSyncConstants.defaultTimelineLimit)) + roomListItem.subscribe(settings: settings) + let timelineListener = RoomTimelineListener { [weak self] timelineDiff in self?.updatesSubject.send(timelineDiff) } - + self.timelineListener = timelineListener - if let result = try? slidingSyncRoom.addTimelineListener(listener: timelineListener) { - roomTimelineObservationToken = result.taskHandle - - Task { - await fetchMembers() - await updateMembers() - } - return .success(result.items) - } else { - self.timelineListener = nil - return .failure(.failedAddingTimelineListener) + let result = room.addTimelineListener(listener: timelineListener) + roomTimelineObservationToken = result.itemsStream + + Task { + await fetchMembers() + await updateMembers() } + + return .success(result.items) } func paginateBackwards(requestSize: UInt, untilNumberOfItems: UInt) async -> Result { @@ -591,7 +588,7 @@ private class RoomTimelineListener: TimelineListener { self.onUpdateClosure = onUpdateClosure } - func onUpdate(update: TimelineDiff) { - onUpdateClosure(update) + func onUpdate(diff: TimelineDiff) { + onUpdateClosure(diff) } } diff --git a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift index 4bf3e24b1..519e98aac 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/MockRoomSummaryProvider.swift @@ -16,6 +16,7 @@ import Combine import Foundation +import MatrixRustSDK enum MockRoomSummaryProviderState { case loading @@ -25,7 +26,6 @@ enum MockRoomSummaryProviderState { class MockRoomSummaryProvider: RoomSummaryProviderProtocol { let roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> let statePublisher: CurrentValuePublisher - let countPublisher: CurrentValuePublisher convenience init() { self.init(state: .loading) @@ -36,15 +36,16 @@ class MockRoomSummaryProvider: RoomSummaryProviderProtocol { case .loading: roomListPublisher = .init([]) statePublisher = .init(.notLoaded) - countPublisher = .init(0) case .loaded(let rooms): roomListPublisher = .init(rooms) statePublisher = .init(.fullyLoaded) - countPublisher = .init(UInt(rooms.count)) } } - func updateVisibleRange(_ range: Range, timelineLimit: UInt) { } + func subscribeIfNecessary(entriesFunction: (RoomListEntriesListener) async throws -> RoomListEntriesResult, + entriesLoadingStateFunction: (SlidingSyncListStateObserver) async throws -> RoomListEntriesLoadingStateResult) { } + + func updateVisibleRange(_ range: Range) { } } extension Array where Element == RoomSummary { diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift index 49286e454..98d36f74c 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProvider.swift @@ -19,17 +19,22 @@ import Foundation import MatrixRustSDK class RoomSummaryProvider: RoomSummaryProviderProtocol { - private let slidingSyncListProxy: SlidingSyncListProxy - private let serialDispatchQueue: DispatchQueue + private let roomListService: RoomListProtocol private let eventStringBuilder: RoomEventStringBuilder private let name: String + private let serialDispatchQueue: DispatchQueue + private var cancellables = Set() + private var listUpdatesTaskHandle: TaskHandle? + private var stateUpdatesTaskHandle: TaskHandle? private let roomListSubject = CurrentValueSubject<[RoomSummary], Never>([]) private let stateSubject = CurrentValueSubject(.notLoaded) private let countSubject = CurrentValueSubject(0) + private let diffPublisher = PassthroughSubject() + var roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> { roomListSubject.asCurrentValuePublisher() } @@ -37,10 +42,6 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { var statePublisher: CurrentValuePublisher { stateSubject.asCurrentValuePublisher() } - - var countPublisher: CurrentValuePublisher { - countSubject.asCurrentValuePublisher() - } private var rooms: [RoomSummary] = [] { didSet { @@ -48,47 +49,75 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { } } - init(slidingSyncListProxy: SlidingSyncListProxy, eventStringBuilder: RoomEventStringBuilder, name: String) { - self.slidingSyncListProxy = slidingSyncListProxy + init(roomListService: RoomListProtocol, + eventStringBuilder: RoomEventStringBuilder, + name: String) { + self.roomListService = roomListService serialDispatchQueue = DispatchQueue(label: "io.element.elementx.roomsummaryprovider", qos: .utility) self.eventStringBuilder = eventStringBuilder self.name = name - rooms = slidingSyncListProxy.currentRoomsList().map { roomListEntry in - buildSummaryForRoomListEntry(roomListEntry) - } - - roomListSubject.send(rooms) // didSet not called from initialisers - - slidingSyncListProxy.statePublisher - .map(RoomSummaryProviderState.init) - .subscribe(stateSubject) - .store(in: &cancellables) - - slidingSyncListProxy.countPublisher - .subscribe(countSubject) - .store(in: &cancellables) - - slidingSyncListProxy.diffPublisher + diffPublisher .collect(.byTime(serialDispatchQueue, 0.025)) .sink { [weak self] in self?.updateRoomsWithDiffs($0) } .store(in: &cancellables) } - func updateVisibleRange(_ range: Range, timelineLimit: UInt) { - slidingSyncListProxy.updateVisibleRange(range, timelineLimit: timelineLimit) + func subscribeIfNecessary(entriesFunction: (RoomListEntriesListener) async throws -> RoomListEntriesResult, + entriesLoadingStateFunction: (SlidingSyncListStateObserver) async throws -> RoomListEntriesLoadingStateResult) async { + guard listUpdatesTaskHandle == nil, stateUpdatesTaskHandle == nil else { + return + } + + do { + let listUpdatesSubscriptionResult = try await entriesFunction(RoomListEntriesListenerProxy { [weak self] update in + guard let self else { return } + MXLog.verbose("\(name): Received list update") + diffPublisher.send(update) + }) + + listUpdatesTaskHandle = listUpdatesSubscriptionResult.entriesStream + + rooms = listUpdatesSubscriptionResult.entries.map { roomListEntry in + buildSummaryForRoomListEntry(roomListEntry) + } + + let stateUpdatesSubscriptionResult = try await entriesLoadingStateFunction(RoomListStateObserver { [weak self] state in + guard let self else { return } + MXLog.info("\(name): Received state update: \(state)") + stateSubject.send(RoomSummaryProviderState(slidingSyncState: state)) + }) + + stateSubject.send(RoomSummaryProviderState(slidingSyncState: stateUpdatesSubscriptionResult.entriesLoadingState)) + + stateUpdatesTaskHandle = stateUpdatesSubscriptionResult.entriesLoadingStateStream + + } catch { + MXLog.error("Failed setting up room list entry listener with error: \(error)") + } + } + + func updateVisibleRange(_ range: Range) { + Task { + do { + MXLog.info("\(name): Setting visible range to \(range)") + try await roomListService.applyInput(input: .viewport(ranges: [.init(start: UInt32(range.lowerBound), endInclusive: UInt32(range.upperBound))])) + } catch { + MXLog.error("Failed updating visible range with error: \(error)") + } + } } // MARK: - Private - fileprivate func updateRoomsWithDiffs(_ diffs: [SlidingSyncListRoomsListDiff]) { + fileprivate func updateRoomsWithDiffs(_ diffs: [RoomListEntriesUpdate]) { let span = MXLog.createSpan("\(name).process_room_list_diffs") span.enter() defer { span.exit() } - MXLog.info("\(name): Received \(diffs.count) diffs, current room list \(rooms.compactMap { $0.id ?? "Empty" })") + MXLog.verbose("\(name): Received \(diffs.count) diffs, current room list \(rooms.compactMap { $0.id ?? "Empty" })") rooms = diffs .reduce(rooms) { currentItems, diff in @@ -107,11 +136,11 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { detectDuplicatesInRoomList(rooms) - MXLog.info("\(name): Finished applying \(diffs.count) diffs, new room list \(rooms.compactMap { $0.id ?? "Empty" })") + MXLog.verbose("\(name): Finished applying \(diffs.count) diffs, new room list \(rooms.compactMap { $0.id ?? "Empty" })") } - private func buildRoomSummaryForIdentifier(_ identifier: String, invalidated: Bool) -> RoomSummary { - guard let room = try? slidingSyncListProxy.roomForIdentifier(identifier) else { + private func buildRoomSummaryForIdentifier(_ identifier: String) -> RoomSummary { + guard let roomListItem = try? roomListService.room(roomId: identifier) else { MXLog.error("\(name): Failed finding room with id: \(identifier)") return .empty } @@ -119,30 +148,24 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { var attributedLastMessage: AttributedString? var lastMessageFormattedTimestamp: String? - // Dispatch onto another queue otherwise the rust method latestRoomMessage crashes. - // This will be fixed when we get async uniffi support. - DispatchQueue.global(qos: .userInitiated).sync { - if let latestRoomMessage = room.latestRoomMessage() { - let lastMessage = EventTimelineItemProxy(item: latestRoomMessage) - lastMessageFormattedTimestamp = lastMessage.timestamp.formattedMinimal() - attributedLastMessage = eventStringBuilder.buildAttributedString(for: lastMessage) - } + if let latestRoomMessage = roomListItem.latestEvent() { + let lastMessage = EventTimelineItemProxy(item: latestRoomMessage) + lastMessageFormattedTimestamp = lastMessage.timestamp.formattedMinimal() + attributedLastMessage = eventStringBuilder.buildAttributedString(for: lastMessage) } - let fullRoom = room.fullRoom() - let avatarURL = fullRoom?.avatarUrl().flatMap(URL.init(string:)) - let canonicalAlias = fullRoom?.canonicalAlias() - - let details = RoomSummaryDetails(id: room.roomId(), - name: room.name() ?? room.roomId(), - isDirect: fullRoom?.isDirect() ?? room.isDm() ?? false, - avatarURL: avatarURL, + let room = roomListItem.fullRoom() + + let details = RoomSummaryDetails(id: roomListItem.id(), + name: roomListItem.name() ?? room.id(), + isDirect: room.isDirect(), + avatarURL: room.avatarUrl().flatMap(URL.init(string:)), lastMessage: attributedLastMessage, lastMessageFormattedTimestamp: lastMessageFormattedTimestamp, - unreadNotificationCount: UInt(room.unreadNotifications().notificationCount()), - canonicalAlias: canonicalAlias) - - return invalidated ? .invalidated(details: details) : .filled(details: details) + unreadNotificationCount: UInt(roomListItem.unreadNotifications().notificationCount()), + canonicalAlias: room.canonicalAlias()) + + return .filled(details: details) } private func buildSummaryForRoomListEntry(_ entry: RoomListEntry) -> RoomSummary { @@ -150,48 +173,48 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { case .empty: return .empty case .filled(let roomId): - return buildRoomSummaryForIdentifier(roomId, invalidated: false) + return buildRoomSummaryForIdentifier(roomId) case .invalidated(let roomId): - return buildRoomSummaryForIdentifier(roomId, invalidated: true) + return buildRoomSummaryForIdentifier(roomId) } } // swiftlint:disable:next cyclomatic_complexity function_body_length - private func buildDiff(from diff: SlidingSyncListRoomsListDiff, on rooms: [RoomSummary]) -> CollectionDifference? { + private func buildDiff(from diff: RoomListEntriesUpdate, on rooms: [RoomSummary]) -> CollectionDifference? { var changes = [CollectionDifference.Change]() switch diff { case .pushFront(let value): - MXLog.info("\(name): Push Front \(value.debugIdentifier)") + MXLog.verbose("\(name): Push Front \(value.debugIdentifier)") let summary = buildSummaryForRoomListEntry(value) changes.append(.insert(offset: 0, element: summary, associatedWith: nil)) case .pushBack(let value): - MXLog.info("\(name): Push Back \(value.debugIdentifier)") + MXLog.verbose("\(name): Push Back \(value.debugIdentifier)") let summary = buildSummaryForRoomListEntry(value) changes.append(.insert(offset: rooms.count, element: summary, associatedWith: nil)) case .append(values: let values): let debugIdentifiers = values.map(\.debugIdentifier) - MXLog.info("\(name): Append \(debugIdentifiers)") + MXLog.verbose("\(name): Append \(debugIdentifiers)") for (index, value) in values.enumerated() { let summary = buildSummaryForRoomListEntry(value) changes.append(.insert(offset: rooms.count + index, element: summary, associatedWith: nil)) } case .set(let index, let value): - MXLog.info("\(name): Update \(value.debugIdentifier) at \(index)") + MXLog.verbose("\(name): Update \(value.debugIdentifier) at \(index)") let summary = buildSummaryForRoomListEntry(value) changes.append(.remove(offset: Int(index), element: summary, associatedWith: nil)) changes.append(.insert(offset: Int(index), element: summary, associatedWith: nil)) case .insert(let index, let value): - MXLog.info("\(name): Insert at \(value.debugIdentifier) at \(index)") + MXLog.verbose("\(name): Insert at \(value.debugIdentifier) at \(index)") let summary = buildSummaryForRoomListEntry(value) changes.append(.insert(offset: Int(index), element: summary, associatedWith: nil)) case .remove(let index): let summary = rooms[Int(index)] - MXLog.info("\(name): Remove \(summary.id ?? "") from \(index)") + MXLog.verbose("\(name): Remove \(summary.id ?? "") from \(index)") changes.append(.remove(offset: Int(index), element: summary, associatedWith: nil)) case .reset(let values): let debugIdentifiers = values.map(\.debugIdentifier) - MXLog.info("\(name): Replace all items with \(debugIdentifiers)") + MXLog.verbose("\(name): Replace all items with \(debugIdentifiers)") for (index, summary) in rooms.enumerated() { changes.append(.remove(offset: index, element: summary, associatedWith: nil)) } @@ -200,16 +223,16 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { changes.append(.insert(offset: index, element: buildSummaryForRoomListEntry(value), associatedWith: nil)) } case .clear: - MXLog.info("\(name): Clear all items") + MXLog.verbose("\(name): Clear all items") for (index, value) in rooms.enumerated() { changes.append(.remove(offset: index, element: value, associatedWith: nil)) } case .popFront: - MXLog.info("\(name): Pop Front") + MXLog.verbose("\(name): Pop Front") let summary = rooms[0] changes.append(.remove(offset: 0, element: summary, associatedWith: nil)) case .popBack: - MXLog.info("\(name): Pop Back") + MXLog.verbose("\(name): Pop Back") guard let value = rooms.last else { fatalError() } @@ -241,7 +264,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol { } extension RoomSummaryProviderState { - init(slidingSyncState: SlidingSyncState) { + init(slidingSyncState: SlidingSyncListLoadingState) { switch slidingSyncState { case .notLoaded: self = .notLoaded @@ -276,3 +299,27 @@ extension MatrixRustSDK.RoomListEntry { } } } + +private class RoomListEntriesListenerProxy: RoomListEntriesListener { + private let onUpdateClosure: (RoomListEntriesUpdate) -> Void + + init(_ onUpdateClosure: @escaping (RoomListEntriesUpdate) -> Void) { + self.onUpdateClosure = onUpdateClosure + } + + func onUpdate(roomEntriesUpdate: RoomListEntriesUpdate) { + onUpdateClosure(roomEntriesUpdate) + } +} + +private class RoomListStateObserver: SlidingSyncListStateObserver { + private let onUpdateClosure: (SlidingSyncListLoadingState) -> Void + + init(_ onUpdateClosure: @escaping (SlidingSyncListLoadingState) -> Void) { + self.onUpdateClosure = onUpdateClosure + } + + func didReceiveUpdate(newState: SlidingSyncListLoadingState) { + onUpdateClosure(newState) + } +} diff --git a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift index 50bd0aa28..0fcab5802 100644 --- a/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomSummary/RoomSummaryProviderProtocol.swift @@ -28,13 +28,12 @@ enum RoomSummaryProviderState { enum RoomSummary: CustomStringConvertible { case empty case filled(details: RoomSummaryDetails) - case invalidated(details: RoomSummaryDetails) var id: String? { switch self { case .empty: return nil - case .invalidated(let details), .filled(let details): + case .filled(let details): return details.id } } @@ -43,7 +42,7 @@ enum RoomSummary: CustomStringConvertible { switch self { case .empty: return nil - case .invalidated(let details), .filled(let details): + case .filled(let details): return details.name } } @@ -52,8 +51,6 @@ enum RoomSummary: CustomStringConvertible { switch self { case .empty: return "\(String(describing: Self.self)): Empty" - case .invalidated(let details): - return "\(String(describing: Self.self)): Invalidated(\(details.id))" case .filled(let details): return "\(String(describing: Self.self)): Filled(\(details.id))" } @@ -67,8 +64,10 @@ protocol RoomSummaryProviderProtocol { /// Publishes the current state the summary provider is finding itself in var statePublisher: CurrentValuePublisher { get } - /// Publishes the total number of rooms - var countPublisher: CurrentValuePublisher { get } - - func updateVisibleRange(_ range: Range, timelineLimit: UInt) + /// A separate subscription method is needed instead of running this in the constructor because the invites list is added later on the Rust side. + /// Wanted to be able to build the InvitesSummaryProvider directly instead of having to inform the HomeScreenViewModel about it later + func subscribeIfNecessary(entriesFunction: (RoomListEntriesListener) async throws -> RoomListEntriesResult, + entriesLoadingStateFunction: (SlidingSyncListStateObserver) async throws -> RoomListEntriesLoadingStateResult) async + + func updateVisibleRange(_ range: Range) } diff --git a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift index f4b28a0d4..15d726377 100644 --- a/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift +++ b/ElementX/Sources/Services/Timeline/RoomTimelineProvider.swift @@ -49,9 +49,9 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol { switch roomProxy.registerTimelineListenerIfNeeded() { case let .success(items): itemProxies = items.map(TimelineItemProxy.init) - MXLog.info("Added timeline listener, current items (\(items.count)) : \(items.map(\.debugIdentifier))") + MXLog.verbose("Added timeline listener, current items (\(items.count)) : \(items.map(\.debugIdentifier))") case .failure(.roomListenerAlreadyRegistered): - MXLog.info("Listener already registered for the room: \(roomProxy.id)") + MXLog.verbose("Listener already registered for the room: \(roomProxy.id)") case .failure: let roomID = roomProxy.id MXLog.error("Failed adding timeline listener on room with identifier: \(roomID)") @@ -67,7 +67,7 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol { span.exit() } - MXLog.info("Received timeline diff") + MXLog.verbose("Received timeline diff") itemProxies = diffs .reduce(itemProxies) { currentItems, diff in @@ -84,7 +84,7 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol { return updatedItems } - MXLog.info("Finished applying diffs, current items (\(itemProxies.count)) : \(itemProxies.map(\.debugIdentifier))") + MXLog.verbose("Finished applying diffs, current items (\(itemProxies.count)) : \(itemProxies.map(\.debugIdentifier))") } // swiftlint:disable:next cyclomatic_complexity function_body_length @@ -95,45 +95,45 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol { case .pushFront: guard let item = diff.pushFront() else { fatalError() } - MXLog.info("Push Front: \(item.debugIdentifier)") + MXLog.verbose("Push Front: \(item.debugIdentifier)") let itemProxy = TimelineItemProxy(item: item) changes.append(.insert(offset: 0, element: itemProxy, associatedWith: nil)) case .pushBack: guard let item = diff.pushBack() else { fatalError() } - MXLog.info("Push Back \(item.debugIdentifier)") + MXLog.verbose("Push Back \(item.debugIdentifier)") let itemProxy = TimelineItemProxy(item: item) changes.append(.insert(offset: Int(itemProxies.count), element: itemProxy, associatedWith: nil)) case .insert: guard let update = diff.insert() else { fatalError() } - MXLog.info("Insert \(update.item.debugIdentifier) at \(update.index)") + MXLog.verbose("Insert \(update.item.debugIdentifier) at \(update.index)") let itemProxy = TimelineItemProxy(item: update.item) changes.append(.insert(offset: Int(update.index), element: itemProxy, associatedWith: nil)) case .append: guard let items = diff.append() else { fatalError() } - MXLog.info("Append \(items.map(\.debugIdentifier))") + MXLog.verbose("Append \(items.map(\.debugIdentifier))") for (index, item) in items.enumerated() { changes.append(.insert(offset: Int(itemProxies.count) + index, element: TimelineItemProxy(item: item), associatedWith: nil)) } case .set: guard let update = diff.set() else { fatalError() } - MXLog.info("Set \(update.item.debugIdentifier) at index \(update.index)") + MXLog.verbose("Set \(update.item.debugIdentifier) at index \(update.index)") let itemProxy = TimelineItemProxy(item: update.item) changes.append(.remove(offset: Int(update.index), element: itemProxy, associatedWith: nil)) changes.append(.insert(offset: Int(update.index), element: itemProxy, associatedWith: nil)) case .popFront: guard let itemProxy = itemProxies.first else { fatalError() } - MXLog.info("Pop Front \(itemProxy.debugIdentifier)") + MXLog.verbose("Pop Front \(itemProxy.debugIdentifier)") changes.append(.remove(offset: 0, element: itemProxy, associatedWith: nil)) case .popBack: guard let itemProxy = itemProxies.last else { fatalError() } - MXLog.info("Pop Back \(itemProxy.debugIdentifier)") + MXLog.verbose("Pop Back \(itemProxy.debugIdentifier)") changes.append(.remove(offset: itemProxies.count - 1, element: itemProxy, associatedWith: nil)) case .remove: @@ -141,18 +141,18 @@ class RoomTimelineProvider: RoomTimelineProviderProtocol { let itemProxy = itemProxies[Int(index)] - MXLog.info("Remove \(itemProxy.debugIdentifier) at: \(index)") + MXLog.verbose("Remove \(itemProxy.debugIdentifier) at: \(index)") changes.append(.remove(offset: Int(index), element: itemProxy, associatedWith: nil)) case .clear: - MXLog.info("Clear all items") + MXLog.verbose("Clear all items") for (index, itemProxy) in itemProxies.enumerated() { changes.append(.remove(offset: index, element: itemProxy, associatedWith: nil)) } case .reset: guard let items = diff.reset() else { fatalError() } - MXLog.info("Replace all items with \(items.map(\.debugIdentifier))") + MXLog.verbose("Replace all items with \(items.map(\.debugIdentifier))") for (index, itemProxy) in itemProxies.enumerated() { changes.append(.remove(offset: index, element: itemProxy, associatedWith: nil)) } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/AudioRoomTimelineItemContent.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/AudioRoomTimelineItemContent.swift index af43801a9..0245e26f7 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/AudioRoomTimelineItemContent.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/AudioRoomTimelineItemContent.swift @@ -19,7 +19,7 @@ import UniformTypeIdentifiers struct AudioRoomTimelineItemContent: Hashable { let body: String - let duration: UInt64 + let duration: TimeInterval let source: MediaSourceProxy? let contentType: UTType? } diff --git a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItemContent.swift b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItemContent.swift index cdd53dab8..eb44127ab 100644 --- a/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItemContent.swift +++ b/ElementX/Sources/Services/Timeline/TimelineItems/Items/Messages/VideoRoomTimelineItemContent.swift @@ -19,7 +19,7 @@ import UniformTypeIdentifiers struct VideoRoomTimelineItemContent: Hashable { let body: String - let duration: UInt64 + let duration: TimeInterval let source: MediaSourceProxy? let thumbnailSource: MediaSourceProxy? var width: CGFloat? diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index 2c7c6a3db..80507cf47 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -472,7 +472,7 @@ class MockScreen: Identifiable { clientProxy.roomForIdentifierMocks["someAwesomeRoomId1"] = .init(with: .init(displayName: "First room", inviter: .mockCharlie)) clientProxy.roomForIdentifierMocks["someAwesomeRoomId2"] = .init(with: .init(displayName: "Second room", inviter: .mockCharlie)) let summaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites)) - clientProxy.invitesSummaryProvider = summaryProvider + clientProxy.inviteSummaryProvider = summaryProvider let coordinator = InvitesScreenCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()))) navigationStackCoordinator.setRootCoordinator(coordinator) @@ -484,7 +484,7 @@ class MockScreen: Identifiable { clientProxy.roomForIdentifierMocks["someAwesomeRoomId1"] = .init(with: .init(displayName: "First room", inviter: .mockCharlie)) clientProxy.roomForIdentifierMocks["someAwesomeRoomId2"] = .init(with: .init(displayName: "Second room", inviter: .mockCharlie)) let summaryProvider = MockRoomSummaryProvider(state: .loaded(.mockInvites)) - clientProxy.invitesSummaryProvider = summaryProvider + clientProxy.inviteSummaryProvider = summaryProvider let coordinator = InvitesScreenCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider()))) navigationStackCoordinator.setRootCoordinator(coordinator) diff --git a/UnitTests/Sources/InvitesScreenViewModelTests.swift b/UnitTests/Sources/InvitesScreenViewModelTests.swift index daf00afd1..76b28f935 100644 --- a/UnitTests/Sources/InvitesScreenViewModelTests.swift +++ b/UnitTests/Sources/InvitesScreenViewModelTests.swift @@ -78,8 +78,8 @@ class InvitesScreenViewModelTests: XCTestCase { private func setupViewModel(roomSummaries: [RoomSummary]? = nil) { if let roomSummaries { let summaryProvider = MockRoomSummaryProvider(state: .loaded(roomSummaries)) - clientProxy.invitesSummaryProvider = summaryProvider - clientProxy.visibleRoomsSummaryProvider = summaryProvider + clientProxy.inviteSummaryProvider = summaryProvider + clientProxy.roomSummaryProvider = summaryProvider } viewModel = InvitesScreenViewModel(userSession: userSession) diff --git a/UnitTests/Sources/LoggingTests.swift b/UnitTests/Sources/LoggingTests.swift index 83a67c901..e37dfd325 100644 --- a/UnitTests/Sources/LoggingTests.swift +++ b/UnitTests/Sources/LoggingTests.swift @@ -347,7 +347,7 @@ class LoggingTests: XCTestCase { let pointer = Unmanaged.passRetained(NSURL(fileURLWithPath: "/tmp/file")).toOpaque() let imageMessage = ImageMessageContent(body: "ImageString", source: MediaSource(unsafeFromRawPointer: pointer), info: nil) let videoMessage = VideoMessageContent(body: "VideoString", source: MediaSource(unsafeFromRawPointer: pointer), info: nil) - let fileMessage = FileMessageContent(body: "FileString", source: MediaSource(unsafeFromRawPointer: pointer), info: nil) + let fileMessage = FileMessageContent(body: "FileString", filename: "FileName", source: MediaSource(unsafeFromRawPointer: pointer), info: nil) // When logging that value XCTAssert(MXLogger.logFiles.isEmpty) diff --git a/UnitTests/Sources/MediaUploadingPreprocessorTests.swift b/UnitTests/Sources/MediaUploadingPreprocessorTests.swift index 4bb9c2c97..be355ace9 100644 --- a/UnitTests/Sources/MediaUploadingPreprocessorTests.swift +++ b/UnitTests/Sources/MediaUploadingPreprocessorTests.swift @@ -37,7 +37,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { XCTAssertEqual(audioURL.lastPathComponent, "test_audio.mp3") XCTAssertEqual(audioInfo.mimetype, "audio/mpeg") - XCTAssertEqual(audioInfo.duration, 27252) + XCTAssertEqual(floor(audioInfo.duration ?? 0), 27252) XCTAssertEqual(audioInfo.size, 764_176) } @@ -72,7 +72,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { XCTAssertEqual(videoInfo.size, 1_431_959) XCTAssertEqual(videoInfo.width, 1280) XCTAssertEqual(videoInfo.height, 720) - XCTAssertEqual(videoInfo.duration, 30483) + XCTAssertEqual(floor(videoInfo.duration ?? 0), 30483) XCTAssertNotNil(videoInfo.thumbnailInfo) XCTAssertEqual(videoInfo.thumbnailInfo?.mimetype, "image/jpeg") diff --git a/project.yml b/project.yml index 78f8e3fd9..781e735e7 100644 --- a/project.yml +++ b/project.yml @@ -44,7 +44,7 @@ include: packages: MatrixRustSDK: url: https://github.com/matrix-org/matrix-rust-components-swift - exactVersion: 1.0.75-alpha + exactVersion: 1.0.76-alpha # path: ../matrix-rust-sdk DesignKit: path: DesignKit