diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 7712262fa..0195ff3db 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -90,6 +90,7 @@ 0C932A5158C1D0604DFC5750 /* ComposerToolbarViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA29952595B804DA221A0C1D /* ComposerToolbarViewModelTests.swift */; }; 0D4EB2ABAA5FE8CB10FDBCB8 /* TimelineItemFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AA3AF94A06D319BB37E52DA /* TimelineItemFactoryTests.swift */; }; 0D617A152D099D94271D3BA8 /* SecurityAndPrivacyScreenViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D046ABB22E680F7C5054441B /* SecurityAndPrivacyScreenViewModelProtocol.swift */; }; + 0DAB08C117E0391F3ADB2031 /* SpaceRoomListProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B2B564CA6570E1487A7C7CC /* SpaceRoomListProxy.swift */; }; 0DC815CA24E1BD7F408F37D3 /* CollapsibleTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7C4EA55DA62F9D0F984A2AE /* CollapsibleTimelineItem.swift */; }; 0DFCEAB8C82B151718F71D49 /* FrequentlyUsedEmoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2074C0449B83D5858BD2D7 /* FrequentlyUsedEmoji.swift */; }; 0E08BB72B2258652CF501A8B /* Version in Frameworks */ = {isa = PBXBuildFile; productRef = 2B9ACE4FCACB5A8812154424 /* Version */; }; @@ -208,6 +209,7 @@ 24B7CD41342C143117ADA768 /* Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2B1CC9AA154F4D5435BF60A /* Comparable.swift */; }; 24BDDD09A90B8BFE3793F3AA /* ClientProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6033779EB37259F27F938937 /* ClientProxyProtocol.swift */; }; 24C32D7EF94ECF9081638DF6 /* RemotePreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69A05E85E4872C3221C5C287 /* RemotePreference.swift */; }; + 24D4E0BBC2ECD2AAD84E7159 /* SpaceRoomProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 355C8C46DA9C0B45F1B7FC4F /* SpaceRoomProxy.swift */; }; 250D2E8309C8CC48796D41D1 /* SpaceScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = C11397904D19CFF0E3689F0E /* SpaceScreenModels.swift */; }; 25618589E0DE0F1E95FC7B5C /* EmojiProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 099F2D36C141D845A445B1E6 /* EmojiProviderTests.swift */; }; 256D76972BA3254F7CB7F88B /* LocationAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAD8234D0E9C9B12BF9F240B /* LocationAnnotation.swift */; }; @@ -820,6 +822,7 @@ 9462C62798F47E39DCC182D2 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = CA89A2DD51B6BBE1DA55E263 /* Application.swift */; }; 94871CA883F44022086FE750 /* AuthenticationStartLogo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98784280D98C852727BE0111 /* AuthenticationStartLogo.swift */; }; 94A65DD8A353DF112EBEF67A /* SessionVerificationControllerProxyProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D56469A9EE0CFA2B7BA9760 /* SessionVerificationControllerProxyProtocol.swift */; }; + 94C2B531B96493B68B976E5F /* SpaceServiceProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226A56746AD99ABDA97189E1 /* SpaceServiceProxy.swift */; }; 94D0F36A87E596A93C0C178A /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; }; 94E15D018D70563FA4AB4E5A /* ComposerToolbarModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4D3A7375AB22721C436EB056 /* ComposerToolbarModels.swift */; }; 94EB81606839C175CA6C2ADB /* RoomScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D97A4E73EA97CA08D2BB9806 /* RoomScreenTests.swift */; }; @@ -1655,6 +1658,7 @@ 21BA866267F84BF4350B0CB7 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-BR"; path = "pt-BR.lproj/Localizable.stringsdict"; sourceTree = ""; }; 21DD8599815136EFF5B73F38 /* UserFlowTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserFlowTests.swift; sourceTree = ""; }; 2214C32EA81FA9168D923D4C /* CreateRoomScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomScreenTests.swift; sourceTree = ""; }; + 226A56746AD99ABDA97189E1 /* SpaceServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceServiceProxy.swift; sourceTree = ""; }; 22730A30C50AC2E3D5BA8642 /* InviteUsersScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteUsersScreenViewModelProtocol.swift; sourceTree = ""; }; 227AC5D71A4CE43512062243 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; 22DB19219E6CC4D002E15D48 /* GlobalSearchScreenCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalSearchScreenCell.swift; sourceTree = ""; }; @@ -1759,6 +1763,7 @@ 34ED3AB7E0287552A5648AB3 /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/InfoPlist.strings; sourceTree = ""; }; 353024006CB726E9F9187B3A /* AnalyticsConsentState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsConsentState.swift; sourceTree = ""; }; 3558A15CFB934F9229301527 /* RestorationToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestorationToken.swift; sourceTree = ""; }; + 355C8C46DA9C0B45F1B7FC4F /* SpaceRoomProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceRoomProxy.swift; sourceTree = ""; }; 35A057BA9BE0F079784CD061 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 35AFCF4C05DEED04E3DB1A16 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; 36DA824791172B9821EACBED /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; @@ -1873,6 +1878,7 @@ 4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenViewModelProtocol.swift; sourceTree = ""; }; 4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = ""; }; 4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerMock.swift; sourceTree = ""; }; + 4B2B564CA6570E1487A7C7CC /* SpaceRoomListProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpaceRoomListProxy.swift; sourceTree = ""; }; 4B2D4EEBE8C098BBADD10939 /* SecureBackupKeyBackupScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupKeyBackupScreenCoordinator.swift; sourceTree = ""; }; 4B41FABA2B0AEF4389986495 /* LoginMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginMode.swift; sourceTree = ""; }; 4BD371B60E07A5324B9507EF /* AnalyticsSettingsScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenCoordinator.swift; sourceTree = ""; }; @@ -5443,8 +5449,11 @@ AAAB1791344F28CDC62E764D /* Spaces */ = { isa = PBXGroup; children = ( + 4B2B564CA6570E1487A7C7CC /* SpaceRoomListProxy.swift */, EACD4855BDAD0799FD86B7B5 /* SpaceRoomListProxyProtocol.swift */, + 355C8C46DA9C0B45F1B7FC4F /* SpaceRoomProxy.swift */, A01F2E9FD8FF95AB89A345E6 /* SpaceRoomProxyProtocol.swift */, + 226A56746AD99ABDA97189E1 /* SpaceServiceProxy.swift */, C57DB49B8426AA721BF85D83 /* SpaceServiceProxyProtocol.swift */, ); path = Spaces; @@ -8153,8 +8162,10 @@ F396470968764E2C3EDA92DA /* SpaceListScreenViewModel.swift in Sources */, 368EC173453FB805C677BFEF /* SpaceListScreenViewModelProtocol.swift in Sources */, 9B84F55288AB98783C11CC49 /* SpaceRoomCell.swift in Sources */, + 0DAB08C117E0391F3ADB2031 /* SpaceRoomListProxy.swift in Sources */, 9FBF0078657CA4F2A9747E98 /* SpaceRoomListProxyMock.swift in Sources */, 2EAA1B35D9CA24F090F48792 /* SpaceRoomListProxyProtocol.swift in Sources */, + 24D4E0BBC2ECD2AAD84E7159 /* SpaceRoomProxy.swift in Sources */, C73CF79A578133D3AB7FB83D /* SpaceRoomProxyMock.swift in Sources */, BE8075CA131C5EA3665C9E0D /* SpaceRoomProxyProtocol.swift in Sources */, 6CAADDC6318E41C7D7AA9526 /* SpaceScreen.swift in Sources */, @@ -8162,6 +8173,7 @@ 250D2E8309C8CC48796D41D1 /* SpaceScreenModels.swift in Sources */, 5A58C7C1837D3E5F2C3E8C8C /* SpaceScreenViewModel.swift in Sources */, 639A0A27383EC655B0E81E95 /* SpaceScreenViewModelProtocol.swift in Sources */, + 94C2B531B96493B68B976E5F /* SpaceServiceProxy.swift in Sources */, A2091F4B1332D9BF273B09D5 /* SpaceServiceProxyMock.swift in Sources */, DB5200B87C4CE9DF0024AC4E /* SpaceServiceProxyProtocol.swift in Sources */, DF004A5B2EABBD0574D06A04 /* SplashScreenCoordinator.swift in Sources */, @@ -9258,7 +9270,7 @@ repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = 25.08.26; + version = 25.09.01; }; }; 701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 473d36c6f..5abc9d881 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -158,8 +158,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/element-hq/matrix-rust-components-swift", "state" : { - "revision" : "c87560bd157c83d63c3414f6c1fe1dc3a3b7d515", - "version" : "25.8.26" + "revision" : "37730751af3a42569677b04d3458629a4b8dc347", + "version" : "25.9.1" } }, { diff --git a/ElementX/Sources/FlowCoordinators/SpaceExplorerFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SpaceExplorerFlowCoordinator.swift index 72c655dae..14ab705f9 100644 --- a/ElementX/Sources/FlowCoordinators/SpaceExplorerFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SpaceExplorerFlowCoordinator.swift @@ -91,7 +91,7 @@ class SpaceExplorerFlowCoordinator: FlowCoordinatorProtocol { stateMachine.addRouteMapping { event, fromState, userInfo in guard event == .selectSpace, case .spaceList = fromState else { return nil } guard let spaceRoomListProxy = userInfo as? SpaceRoomListProxyProtocol else { fatalError("A space proxy must be provided.") } - return .spaceList(selectedSpaceID: spaceRoomListProxy.spaceRoom.id) + return .spaceList(selectedSpaceID: spaceRoomListProxy.spaceRoomProxy.id) } handler: { [weak self] context in guard let self, let spaceRoomListProxy = context.userInfo as? SpaceRoomListProxyProtocol else { return } startSpaceFlow(spaceRoomListProxy: spaceRoomListProxy) @@ -156,6 +156,6 @@ class SpaceExplorerFlowCoordinator: FlowCoordinatorProtocol { } coordinator.start() - selectedSpaceSubject.send(spaceRoomListProxy.spaceRoom.id) + selectedSpaceSubject.send(spaceRoomListProxy.spaceRoomProxy.id) } } diff --git a/ElementX/Sources/FlowCoordinators/SpaceFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SpaceFlowCoordinator.swift index 5f3c60e4e..7fa43f288 100644 --- a/ElementX/Sources/FlowCoordinators/SpaceFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SpaceFlowCoordinator.swift @@ -104,7 +104,7 @@ class SpaceFlowCoordinator: FlowCoordinatorProtocol { stateMachine.addRouteMapping { event, fromState, userInfo in guard event == .startChildFlow, case .space = fromState else { return nil } guard let spaceRoomListProxy = userInfo as? SpaceRoomListProxyProtocol else { fatalError("A space proxy must be provided.") } - return .presentingChild(childSpaceID: spaceRoomListProxy.spaceRoom.id, previousState: fromState) + return .presentingChild(childSpaceID: spaceRoomListProxy.spaceRoomProxy.id, previousState: fromState) } handler: { [weak self] context in guard let self, let spaceRoomListProxy = context.userInfo as? SpaceRoomListProxyProtocol else { return } startChildFlow(for: spaceRoomListProxy) diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 767d7cfad..ec760a56b 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -15840,21 +15840,21 @@ class SessionVerificationControllerProxyMock: SessionVerificationControllerProxy } } class SpaceRoomListProxyMock: SpaceRoomListProxyProtocol, @unchecked Sendable { - var spaceRoom: SpaceRoomProxyProtocol { - get { return underlyingSpaceRoom } - set(value) { underlyingSpaceRoom = value } + var spaceRoomProxy: SpaceRoomProxyProtocol { + get { return underlyingSpaceRoomProxy } + set(value) { underlyingSpaceRoomProxy = value } } - var underlyingSpaceRoom: SpaceRoomProxyProtocol! + var underlyingSpaceRoomProxy: SpaceRoomProxyProtocol! var spaceRoomsPublisher: CurrentValuePublisher<[SpaceRoomProxyProtocol], Never> { get { return underlyingSpaceRoomsPublisher } set(value) { underlyingSpaceRoomsPublisher = value } } var underlyingSpaceRoomsPublisher: CurrentValuePublisher<[SpaceRoomProxyProtocol], Never>! - var paginationStatePublisher: CurrentValuePublisher { + var paginationStatePublisher: CurrentValuePublisher { get { return underlyingPaginationStatePublisher } set(value) { underlyingPaginationStatePublisher = value } } - var underlyingPaginationStatePublisher: CurrentValuePublisher! + var underlyingPaginationStatePublisher: CurrentValuePublisher! //MARK: - paginate @@ -15964,7 +15964,7 @@ class SpaceServiceProxyMock: SpaceServiceProxyProtocol, @unchecked Sendable { var spaceRoomListForCalled: Bool { return spaceRoomListForCallsCount > 0 } - var spaceRoomListForReceivedSpaceRoom: SpaceRoomProxyProtocol? + var spaceRoomListForReceivedSpaceRoomProxy: SpaceRoomProxyProtocol? var spaceRoomListForReceivedInvocations: [SpaceRoomProxyProtocol] = [] var spaceRoomListForUnderlyingReturnValue: Result! @@ -15993,14 +15993,14 @@ class SpaceServiceProxyMock: SpaceServiceProxyProtocol, @unchecked Sendable { } var spaceRoomListForClosure: ((SpaceRoomProxyProtocol) async -> Result)? - func spaceRoomList(for spaceRoom: SpaceRoomProxyProtocol) async -> Result { + func spaceRoomList(for spaceRoomProxy: SpaceRoomProxyProtocol) async -> Result { spaceRoomListForCallsCount += 1 - spaceRoomListForReceivedSpaceRoom = spaceRoom + spaceRoomListForReceivedSpaceRoomProxy = spaceRoomProxy DispatchQueue.main.async { - self.spaceRoomListForReceivedInvocations.append(spaceRoom) + self.spaceRoomListForReceivedInvocations.append(spaceRoomProxy) } if let spaceRoomListForClosure = spaceRoomListForClosure { - return await spaceRoomListForClosure(spaceRoom) + return await spaceRoomListForClosure(spaceRoomProxy) } else { return spaceRoomListForReturnValue } diff --git a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift index 7917e0eda..f4ca9d1c9 100644 --- a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift @@ -4572,6 +4572,71 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable { } } + //MARK: - spaceService + + var spaceServiceUnderlyingCallsCount = 0 + open var spaceServiceCallsCount: Int { + get { + if Thread.isMainThread { + return spaceServiceUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = spaceServiceUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + spaceServiceUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + spaceServiceUnderlyingCallsCount = newValue + } + } + } + } + open var spaceServiceCalled: Bool { + return spaceServiceCallsCount > 0 + } + + var spaceServiceUnderlyingReturnValue: SpaceService! + open var spaceServiceReturnValue: SpaceService! { + get { + if Thread.isMainThread { + return spaceServiceUnderlyingReturnValue + } else { + var returnValue: SpaceService? = nil + DispatchQueue.main.sync { + returnValue = spaceServiceUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + spaceServiceUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + spaceServiceUnderlyingReturnValue = newValue + } + } + } + } + open var spaceServiceClosure: (() -> SpaceService)? + + open override func spaceService() -> SpaceService { + spaceServiceCallsCount += 1 + if let spaceServiceClosure = spaceServiceClosure { + return spaceServiceClosure() + } else { + return spaceServiceReturnValue + } + } + //MARK: - startSsoLogin open var startSsoLoginRedirectUrlIdpIdThrowableError: Error? @@ -21111,6 +21176,551 @@ open class SessionVerificationEmojiSDKMock: MatrixRustSDK.SessionVerificationEmo } } } +open class SpaceRoomListSDKMock: MatrixRustSDK.SpaceRoomList, @unchecked Sendable { + init() { + super.init(noPointer: .init()) + } + + public required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + fatalError("init(unsafeFromRawPointer:) has not been implemented") + } + + fileprivate var pointer: UnsafeMutableRawPointer! + + //MARK: - paginate + + open var paginateThrowableError: Error? + var paginateUnderlyingCallsCount = 0 + open var paginateCallsCount: Int { + get { + if Thread.isMainThread { + return paginateUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = paginateUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + paginateUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + paginateUnderlyingCallsCount = newValue + } + } + } + } + open var paginateCalled: Bool { + return paginateCallsCount > 0 + } + open var paginateClosure: (() async throws -> Void)? + + open override func paginate() async throws { + if let error = paginateThrowableError { + throw error + } + paginateCallsCount += 1 + try await paginateClosure?() + } + + //MARK: - paginationState + + var paginationStateUnderlyingCallsCount = 0 + open var paginationStateCallsCount: Int { + get { + if Thread.isMainThread { + return paginationStateUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = paginationStateUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + paginationStateUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + paginationStateUnderlyingCallsCount = newValue + } + } + } + } + open var paginationStateCalled: Bool { + return paginationStateCallsCount > 0 + } + + var paginationStateUnderlyingReturnValue: SpaceRoomListPaginationState! + open var paginationStateReturnValue: SpaceRoomListPaginationState! { + get { + if Thread.isMainThread { + return paginationStateUnderlyingReturnValue + } else { + var returnValue: SpaceRoomListPaginationState? = nil + DispatchQueue.main.sync { + returnValue = paginationStateUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + paginationStateUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + paginationStateUnderlyingReturnValue = newValue + } + } + } + } + open var paginationStateClosure: (() -> SpaceRoomListPaginationState)? + + open override func paginationState() -> SpaceRoomListPaginationState { + paginationStateCallsCount += 1 + if let paginationStateClosure = paginationStateClosure { + return paginationStateClosure() + } else { + return paginationStateReturnValue + } + } + + //MARK: - rooms + + var roomsUnderlyingCallsCount = 0 + open var roomsCallsCount: Int { + get { + if Thread.isMainThread { + return roomsUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = roomsUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + roomsUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + roomsUnderlyingCallsCount = newValue + } + } + } + } + open var roomsCalled: Bool { + return roomsCallsCount > 0 + } + + var roomsUnderlyingReturnValue: [SpaceRoom]! + open var roomsReturnValue: [SpaceRoom]! { + get { + if Thread.isMainThread { + return roomsUnderlyingReturnValue + } else { + var returnValue: [SpaceRoom]? = nil + DispatchQueue.main.sync { + returnValue = roomsUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + roomsUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + roomsUnderlyingReturnValue = newValue + } + } + } + } + open var roomsClosure: (() -> [SpaceRoom])? + + open override func rooms() -> [SpaceRoom] { + roomsCallsCount += 1 + if let roomsClosure = roomsClosure { + return roomsClosure() + } else { + return roomsReturnValue + } + } + + //MARK: - subscribeToPaginationStateUpdates + + var subscribeToPaginationStateUpdatesListenerUnderlyingCallsCount = 0 + open var subscribeToPaginationStateUpdatesListenerCallsCount: Int { + get { + if Thread.isMainThread { + return subscribeToPaginationStateUpdatesListenerUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = subscribeToPaginationStateUpdatesListenerUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + subscribeToPaginationStateUpdatesListenerUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + subscribeToPaginationStateUpdatesListenerUnderlyingCallsCount = newValue + } + } + } + } + open var subscribeToPaginationStateUpdatesListenerCalled: Bool { + return subscribeToPaginationStateUpdatesListenerCallsCount > 0 + } + open var subscribeToPaginationStateUpdatesListenerReceivedListener: SpaceRoomListPaginationStateListener? + open var subscribeToPaginationStateUpdatesListenerReceivedInvocations: [SpaceRoomListPaginationStateListener] = [] + + var subscribeToPaginationStateUpdatesListenerUnderlyingReturnValue: TaskHandle! + open var subscribeToPaginationStateUpdatesListenerReturnValue: TaskHandle! { + get { + if Thread.isMainThread { + return subscribeToPaginationStateUpdatesListenerUnderlyingReturnValue + } else { + var returnValue: TaskHandle? = nil + DispatchQueue.main.sync { + returnValue = subscribeToPaginationStateUpdatesListenerUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + subscribeToPaginationStateUpdatesListenerUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + subscribeToPaginationStateUpdatesListenerUnderlyingReturnValue = newValue + } + } + } + } + open var subscribeToPaginationStateUpdatesListenerClosure: ((SpaceRoomListPaginationStateListener) -> TaskHandle)? + + open override func subscribeToPaginationStateUpdates(listener: SpaceRoomListPaginationStateListener) -> TaskHandle { + subscribeToPaginationStateUpdatesListenerCallsCount += 1 + subscribeToPaginationStateUpdatesListenerReceivedListener = listener + DispatchQueue.main.async { + self.subscribeToPaginationStateUpdatesListenerReceivedInvocations.append(listener) + } + if let subscribeToPaginationStateUpdatesListenerClosure = subscribeToPaginationStateUpdatesListenerClosure { + return subscribeToPaginationStateUpdatesListenerClosure(listener) + } else { + return subscribeToPaginationStateUpdatesListenerReturnValue + } + } + + //MARK: - subscribeToRoomUpdate + + var subscribeToRoomUpdateListenerUnderlyingCallsCount = 0 + open var subscribeToRoomUpdateListenerCallsCount: Int { + get { + if Thread.isMainThread { + return subscribeToRoomUpdateListenerUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = subscribeToRoomUpdateListenerUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + subscribeToRoomUpdateListenerUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + subscribeToRoomUpdateListenerUnderlyingCallsCount = newValue + } + } + } + } + open var subscribeToRoomUpdateListenerCalled: Bool { + return subscribeToRoomUpdateListenerCallsCount > 0 + } + open var subscribeToRoomUpdateListenerReceivedListener: SpaceRoomListEntriesListener? + open var subscribeToRoomUpdateListenerReceivedInvocations: [SpaceRoomListEntriesListener] = [] + + var subscribeToRoomUpdateListenerUnderlyingReturnValue: TaskHandle! + open var subscribeToRoomUpdateListenerReturnValue: TaskHandle! { + get { + if Thread.isMainThread { + return subscribeToRoomUpdateListenerUnderlyingReturnValue + } else { + var returnValue: TaskHandle? = nil + DispatchQueue.main.sync { + returnValue = subscribeToRoomUpdateListenerUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + subscribeToRoomUpdateListenerUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + subscribeToRoomUpdateListenerUnderlyingReturnValue = newValue + } + } + } + } + open var subscribeToRoomUpdateListenerClosure: ((SpaceRoomListEntriesListener) -> TaskHandle)? + + open override func subscribeToRoomUpdate(listener: SpaceRoomListEntriesListener) -> TaskHandle { + subscribeToRoomUpdateListenerCallsCount += 1 + subscribeToRoomUpdateListenerReceivedListener = listener + DispatchQueue.main.async { + self.subscribeToRoomUpdateListenerReceivedInvocations.append(listener) + } + if let subscribeToRoomUpdateListenerClosure = subscribeToRoomUpdateListenerClosure { + return subscribeToRoomUpdateListenerClosure(listener) + } else { + return subscribeToRoomUpdateListenerReturnValue + } + } +} +open class SpaceServiceSDKMock: MatrixRustSDK.SpaceService, @unchecked Sendable { + init() { + super.init(noPointer: .init()) + } + + public required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + fatalError("init(unsafeFromRawPointer:) has not been implemented") + } + + fileprivate var pointer: UnsafeMutableRawPointer! + + //MARK: - joinedSpaces + + var joinedSpacesUnderlyingCallsCount = 0 + open var joinedSpacesCallsCount: Int { + get { + if Thread.isMainThread { + return joinedSpacesUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = joinedSpacesUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + joinedSpacesUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + joinedSpacesUnderlyingCallsCount = newValue + } + } + } + } + open var joinedSpacesCalled: Bool { + return joinedSpacesCallsCount > 0 + } + + var joinedSpacesUnderlyingReturnValue: [SpaceRoom]! + open var joinedSpacesReturnValue: [SpaceRoom]! { + get { + if Thread.isMainThread { + return joinedSpacesUnderlyingReturnValue + } else { + var returnValue: [SpaceRoom]? = nil + DispatchQueue.main.sync { + returnValue = joinedSpacesUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + joinedSpacesUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + joinedSpacesUnderlyingReturnValue = newValue + } + } + } + } + open var joinedSpacesClosure: (() async -> [SpaceRoom])? + + open override func joinedSpaces() async -> [SpaceRoom] { + joinedSpacesCallsCount += 1 + if let joinedSpacesClosure = joinedSpacesClosure { + return await joinedSpacesClosure() + } else { + return joinedSpacesReturnValue + } + } + + //MARK: - spaceRoomList + + open var spaceRoomListSpaceIdThrowableError: Error? + var spaceRoomListSpaceIdUnderlyingCallsCount = 0 + open var spaceRoomListSpaceIdCallsCount: Int { + get { + if Thread.isMainThread { + return spaceRoomListSpaceIdUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = spaceRoomListSpaceIdUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + spaceRoomListSpaceIdUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + spaceRoomListSpaceIdUnderlyingCallsCount = newValue + } + } + } + } + open var spaceRoomListSpaceIdCalled: Bool { + return spaceRoomListSpaceIdCallsCount > 0 + } + open var spaceRoomListSpaceIdReceivedSpaceId: String? + open var spaceRoomListSpaceIdReceivedInvocations: [String] = [] + + var spaceRoomListSpaceIdUnderlyingReturnValue: SpaceRoomList! + open var spaceRoomListSpaceIdReturnValue: SpaceRoomList! { + get { + if Thread.isMainThread { + return spaceRoomListSpaceIdUnderlyingReturnValue + } else { + var returnValue: SpaceRoomList? = nil + DispatchQueue.main.sync { + returnValue = spaceRoomListSpaceIdUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + spaceRoomListSpaceIdUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + spaceRoomListSpaceIdUnderlyingReturnValue = newValue + } + } + } + } + open var spaceRoomListSpaceIdClosure: ((String) async throws -> SpaceRoomList)? + + open override func spaceRoomList(spaceId: String) async throws -> SpaceRoomList { + if let error = spaceRoomListSpaceIdThrowableError { + throw error + } + spaceRoomListSpaceIdCallsCount += 1 + spaceRoomListSpaceIdReceivedSpaceId = spaceId + DispatchQueue.main.async { + self.spaceRoomListSpaceIdReceivedInvocations.append(spaceId) + } + if let spaceRoomListSpaceIdClosure = spaceRoomListSpaceIdClosure { + return try await spaceRoomListSpaceIdClosure(spaceId) + } else { + return spaceRoomListSpaceIdReturnValue + } + } + + //MARK: - subscribeToJoinedSpaces + + var subscribeToJoinedSpacesListenerUnderlyingCallsCount = 0 + open var subscribeToJoinedSpacesListenerCallsCount: Int { + get { + if Thread.isMainThread { + return subscribeToJoinedSpacesListenerUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = subscribeToJoinedSpacesListenerUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + subscribeToJoinedSpacesListenerUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + subscribeToJoinedSpacesListenerUnderlyingCallsCount = newValue + } + } + } + } + open var subscribeToJoinedSpacesListenerCalled: Bool { + return subscribeToJoinedSpacesListenerCallsCount > 0 + } + open var subscribeToJoinedSpacesListenerReceivedListener: SpaceServiceJoinedSpacesListener? + open var subscribeToJoinedSpacesListenerReceivedInvocations: [SpaceServiceJoinedSpacesListener] = [] + + var subscribeToJoinedSpacesListenerUnderlyingReturnValue: TaskHandle! + open var subscribeToJoinedSpacesListenerReturnValue: TaskHandle! { + get { + if Thread.isMainThread { + return subscribeToJoinedSpacesListenerUnderlyingReturnValue + } else { + var returnValue: TaskHandle? = nil + DispatchQueue.main.sync { + returnValue = subscribeToJoinedSpacesListenerUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + subscribeToJoinedSpacesListenerUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + subscribeToJoinedSpacesListenerUnderlyingReturnValue = newValue + } + } + } + } + open var subscribeToJoinedSpacesListenerClosure: ((SpaceServiceJoinedSpacesListener) async -> TaskHandle)? + + open override func subscribeToJoinedSpaces(listener: SpaceServiceJoinedSpacesListener) async -> TaskHandle { + subscribeToJoinedSpacesListenerCallsCount += 1 + subscribeToJoinedSpacesListenerReceivedListener = listener + DispatchQueue.main.async { + self.subscribeToJoinedSpacesListenerReceivedInvocations.append(listener) + } + if let subscribeToJoinedSpacesListenerClosure = subscribeToJoinedSpacesListenerClosure { + return await subscribeToJoinedSpacesListenerClosure(listener) + } else { + return subscribeToJoinedSpacesListenerReturnValue + } + } +} open class SpanSDKMock: MatrixRustSDK.Span, @unchecked Sendable { init() { super.init(noPointer: .init()) @@ -21395,6 +22005,42 @@ open class SyncServiceSDKMock: MatrixRustSDK.SyncService, @unchecked Sendable { fileprivate var pointer: UnsafeMutableRawPointer! + //MARK: - expireSessions + + var expireSessionsUnderlyingCallsCount = 0 + open var expireSessionsCallsCount: Int { + get { + if Thread.isMainThread { + return expireSessionsUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = expireSessionsUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + expireSessionsUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + expireSessionsUnderlyingCallsCount = newValue + } + } + } + } + open var expireSessionsCalled: Bool { + return expireSessionsCallsCount > 0 + } + open var expireSessionsClosure: (() async -> Void)? + + open override func expireSessions() async { + expireSessionsCallsCount += 1 + await expireSessionsClosure?() + } + //MARK: - roomListService var roomListServiceUnderlyingCallsCount = 0 diff --git a/ElementX/Sources/Mocks/SpaceRoomListProxyMock.swift b/ElementX/Sources/Mocks/SpaceRoomListProxyMock.swift index c8823ca10..dd502a676 100644 --- a/ElementX/Sources/Mocks/SpaceRoomListProxyMock.swift +++ b/ElementX/Sources/Mocks/SpaceRoomListProxyMock.swift @@ -13,12 +13,12 @@ extension SpaceRoomListProxyMock { class Configuration { var spaceRoomProxy: SpaceRoomProxyProtocol var initialSpaceRooms: [SpaceRoomProxyProtocol] - var paginationStateSubject: CurrentValueSubject + var paginationStateSubject: CurrentValueSubject var paginationResponses: [[SpaceRoomProxyProtocol]] init(spaceRoomProxy: SpaceRoomProxyProtocol, initialSpaceRooms: [SpaceRoomProxyProtocol] = [], - paginationStateSubject: CurrentValueSubject = .init(.idle(endReached: true)), + paginationStateSubject: CurrentValueSubject = .init(.idle(endReached: true)), paginationResponses: [[SpaceRoomProxyProtocol]] = []) { self.spaceRoomProxy = spaceRoomProxy self.initialSpaceRooms = initialSpaceRooms @@ -32,7 +32,7 @@ extension SpaceRoomListProxyMock { let spaceRoomsSubject: CurrentValueSubject<[SpaceRoomProxyProtocol], Never> = .init(configuration.initialSpaceRooms) - spaceRoom = configuration.spaceRoomProxy + spaceRoomProxy = configuration.spaceRoomProxy spaceRoomsPublisher = spaceRoomsSubject.asCurrentValuePublisher() paginationStatePublisher = configuration.paginationStateSubject.asCurrentValuePublisher() diff --git a/ElementX/Sources/Other/SDKListener.swift b/ElementX/Sources/Other/SDKListener.swift index 3e3d82bec..827476bdc 100644 --- a/ElementX/Sources/Other/SDKListener.swift +++ b/ElementX/Sources/Other/SDKListener.swift @@ -78,6 +78,20 @@ extension SDKListener: RoomListLoadingStateListener where T == RoomListLoadingSt func onUpdate(state: RoomListLoadingState) { onUpdateClosure(state) } } +// MARK: Spaces + +extension SDKListener: SpaceServiceJoinedSpacesListener where T == [SpaceListUpdate] { + func onUpdate(rooms: [SpaceListUpdate]) { onUpdateClosure(rooms) } +} + +extension SDKListener: SpaceRoomListEntriesListener where T == [SpaceListUpdate] { + func onUpdate(roomUpdates: [SpaceListUpdate]) { onUpdateClosure(roomUpdates) } +} + +extension SDKListener: SpaceRoomListPaginationStateListener where T == SpaceRoomListPaginationState { + func onUpdate(paginationState: SpaceRoomListPaginationState) { onUpdateClosure(paginationState) } +} + // MARK: Room extension SDKListener: RoomInfoListener where T == RoomInfo { diff --git a/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenCoordinator.swift b/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenCoordinator.swift index 834280c86..711fc4d09 100644 --- a/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenCoordinator.swift @@ -35,7 +35,7 @@ final class SpaceScreenCoordinator: CoordinatorProtocol { init(parameters: SpaceScreenCoordinatorParameters) { self.parameters = parameters - viewModel = SpaceScreenViewModel(spaceRoomList: parameters.spaceRoomListProxy, + viewModel = SpaceScreenViewModel(spaceRoomListProxy: parameters.spaceRoomListProxy, spaceServiceProxy: parameters.spaceServiceProxy, mediaProvider: parameters.mediaProvider, userIndicatorController: parameters.userIndicatorController) diff --git a/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenViewModel.swift b/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenViewModel.swift index c24b76fc6..19dc0d7fb 100644 --- a/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenViewModel.swift +++ b/ElementX/Sources/Screens/Spaces/SpaceScreen/SpaceScreenViewModel.swift @@ -19,30 +19,32 @@ class SpaceScreenViewModel: SpaceScreenViewModelType, SpaceScreenViewModelProtoc actionsSubject.eraseToAnyPublisher() } - init(spaceRoomList: SpaceRoomListProxyProtocol, + init(spaceRoomListProxy: SpaceRoomListProxyProtocol, spaceServiceProxy: SpaceServiceProxyProtocol, mediaProvider: MediaProviderProtocol, userIndicatorController: UserIndicatorControllerProtocol) { self.spaceServiceProxy = spaceServiceProxy self.userIndicatorController = userIndicatorController - super.init(initialViewState: SpaceScreenViewState(space: spaceRoomList.spaceRoom, - rooms: spaceRoomList.spaceRoomsPublisher.value), + super.init(initialViewState: SpaceScreenViewState(space: spaceRoomListProxy.spaceRoomProxy, + rooms: spaceRoomListProxy.spaceRoomsPublisher.value), mediaProvider: mediaProvider) - spaceRoomList.spaceRoomsPublisher + spaceRoomListProxy.spaceRoomsPublisher .receive(on: DispatchQueue.main) .weakAssign(to: \.state.rooms, on: self) .store(in: &cancellables) - spaceRoomList.paginationStatePublisher + // As the server is slow, we just let the screen automatically paginate everything in. We can + // switch this to use the scroll position once Synapse receives some performance improvements. + spaceRoomListProxy.paginationStatePublisher .receive(on: DispatchQueue.main) .sink { [weak self] paginationState in switch paginationState { case .idle(let endReached): self?.state.isPaginating = false guard !endReached else { return } - Task { await spaceRoomList.paginate() } + Task { await spaceRoomListProxy.paginate() } case .loading: self?.state.isPaginating = true } diff --git a/ElementX/Sources/Screens/Spaces/SpaceScreen/View/SpaceScreen.swift b/ElementX/Sources/Screens/Spaces/SpaceScreen/View/SpaceScreen.swift index ec50e8e85..33dd99f21 100644 --- a/ElementX/Sources/Screens/Spaces/SpaceScreen/View/SpaceScreen.swift +++ b/ElementX/Sources/Screens/Spaces/SpaceScreen/View/SpaceScreen.swift @@ -64,7 +64,7 @@ struct SpaceScreen_Previews: PreviewProvider, TestablePreview { let spaceRoomListProxy = SpaceRoomListProxyMock(.init(spaceRoomProxy: spaceRoomProxy, initialSpaceRooms: .mockSpaceList)) - let viewModel = SpaceScreenViewModel(spaceRoomList: spaceRoomListProxy, + let viewModel = SpaceScreenViewModel(spaceRoomListProxy: spaceRoomListProxy, spaceServiceProxy: SpaceServiceProxyMock(.init()), mediaProvider: MediaProviderMock(configuration: .init()), userIndicatorController: UserIndicatorControllerMock()) diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 31d44f9e6..dfb79dd39 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -166,9 +166,7 @@ class ClientProxy: ClientProxyProtocol { secureBackupController = SecureBackupController(encryption: client.encryption()) - // Temporarily using the mock until the SDK is updated. - // spaceService = SpaceServiceProxy(spaceService: client.spaceService()) - spaceService = SpaceServiceProxyMock(.init()) + spaceService = SpaceServiceProxy(spaceService: client.spaceService()) let configuredAppService = try await ClientProxyServices(client: client, actionsSubject: actionsSubject, diff --git a/ElementX/Sources/Services/Spaces/SpaceRoomListProxy.swift b/ElementX/Sources/Services/Spaces/SpaceRoomListProxy.swift new file mode 100644 index 000000000..4eaa9396d --- /dev/null +++ b/ElementX/Sources/Services/Spaces/SpaceRoomListProxy.swift @@ -0,0 +1,82 @@ +// +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. +// + +import Combine +import MatrixRustSDK + +class SpaceRoomListProxy: SpaceRoomListProxyProtocol { + private let spaceRoomList: SpaceRoomListProtocol + let spaceRoomProxy: SpaceRoomProxyProtocol + + private var spaceRoomsHandle: TaskHandle? + private let spaceRoomsSubject = CurrentValueSubject<[SpaceRoomProxyProtocol], Never>([]) + var spaceRoomsPublisher: CurrentValuePublisher<[SpaceRoomProxyProtocol], Never> { + spaceRoomsSubject.asCurrentValuePublisher() + } + + private let paginationStateHandle: TaskHandle + let paginationStatePublisher: CurrentValuePublisher + + init(_ spaceRoomList: SpaceRoomListProtocol, spaceRoomProxy: SpaceRoomProxyProtocol) { + self.spaceRoomList = spaceRoomList + self.spaceRoomProxy = spaceRoomProxy + + let paginationStateSubject = CurrentValueSubject(spaceRoomList.paginationState()) + paginationStatePublisher = paginationStateSubject.asCurrentValuePublisher() + + paginationStateHandle = spaceRoomList.subscribeToPaginationStateUpdates(listener: SDKListener { paginationState in + paginationStateSubject.send(paginationState) + }) + + spaceRoomsHandle = spaceRoomList.subscribeToRoomUpdate(listener: SDKListener { [weak self] updates in + self?.handleUpdates(updates) + }) + } + + func paginate() async { + do { + try await spaceRoomList.paginate() + } catch { + MXLog.error("Pagination failure: \(error)") + } + } + + // MARK: - Private + + private func handleUpdates(_ updates: [SpaceListUpdate]) { + var rooms = spaceRoomsSubject.value + + for update in updates { + switch update { + case .append(let spaceRooms): + rooms.append(contentsOf: spaceRooms.map(SpaceRoomProxy.init)) + case .clear: + rooms.removeAll() + case .pushFront(let spaceRoom): + rooms.insert(SpaceRoomProxy(spaceRoom: spaceRoom), at: 0) + case .pushBack(let spaceRoom): + rooms.append(SpaceRoomProxy(spaceRoom: spaceRoom)) + case .popFront: + rooms.removeFirst() + case .popBack: + rooms.removeLast() + case .insert(let index, let spaceRoom): + rooms.insert(SpaceRoomProxy(spaceRoom: spaceRoom), at: Int(index)) + case .set(let index, let spaceRoom): + rooms[Int(index)] = SpaceRoomProxy(spaceRoom: spaceRoom) + case .remove(let index): + rooms.remove(at: Int(index)) + case .truncate(let length): + rooms.removeSubrange(Int(length).. { get } - var paginationStatePublisher: CurrentValuePublisher { get } + var paginationStatePublisher: CurrentValuePublisher { get } func paginate() async } diff --git a/ElementX/Sources/Services/Spaces/SpaceRoomProxy.swift b/ElementX/Sources/Services/Spaces/SpaceRoomProxy.swift new file mode 100644 index 000000000..357b53773 --- /dev/null +++ b/ElementX/Sources/Services/Spaces/SpaceRoomProxy.swift @@ -0,0 +1,34 @@ +// +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. +// + +import Foundation +import MatrixRustSDK + +class SpaceRoomProxy: SpaceRoomProxyProtocol { + private let spaceRoom: SpaceRoom + + init(spaceRoom: SpaceRoom) { + self.spaceRoom = spaceRoom + } + + lazy var id = spaceRoom.roomId + var name: String? { spaceRoom.name } + var avatarURL: URL? { spaceRoom.avatarUrl.flatMap(URL.init) } + + var isSpace: Bool { spaceRoom.roomType == .space } + var childrenCount: Int { Int(spaceRoom.childrenCount) } + + var joinedMembersCount: Int { Int(spaceRoom.numJoinedMembers) } + var heroes: [UserProfileProxy] { (spaceRoom.heroes ?? []).map(UserProfileProxy.init) } + var topic: String? { spaceRoom.topic } + var canonicalAlias: String? { spaceRoom.canonicalAlias } + + var joinRule: JoinRule? { spaceRoom.joinRule } + var worldReadable: Bool? { spaceRoom.worldReadable } + var guestCanJoin: Bool { spaceRoom.guestCanJoin } + var state: Membership? { spaceRoom.state } +} diff --git a/ElementX/Sources/Services/Spaces/SpaceServiceProxy.swift b/ElementX/Sources/Services/Spaces/SpaceServiceProxy.swift new file mode 100644 index 000000000..8e10992fe --- /dev/null +++ b/ElementX/Sources/Services/Spaces/SpaceServiceProxy.swift @@ -0,0 +1,76 @@ +// +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +// Please see LICENSE files in the repository root for full details. +// + +import Combine +import Foundation +import MatrixRustSDK + +class SpaceServiceProxy: SpaceServiceProxyProtocol { + private let spaceService: SpaceServiceProtocol + + private var joinedSpacesHandle: TaskHandle? + private let spacesSubject = CurrentValueSubject<[SpaceRoomProxyProtocol], Never>([]) + var joinedSpacesPublisher: CurrentValuePublisher<[SpaceRoomProxyProtocol], Never> { + spacesSubject.asCurrentValuePublisher() + } + + init(spaceService: SpaceServiceProtocol) { + self.spaceService = spaceService + + Task { await setupSubscriptions() } + } + + private func setupSubscriptions() async { + joinedSpacesHandle = await spaceService.subscribeToJoinedSpaces(listener: SDKListener { [weak self] updates in + self?.handleUpdates(updates) + }) + } + + func spaceRoomList(for spaceRoomProxy: SpaceRoomProxyProtocol) async -> Result { + do { + return try await .success(SpaceRoomListProxy(spaceService.spaceRoomList(spaceId: spaceRoomProxy.id), spaceRoomProxy: spaceRoomProxy)) + } catch { + MXLog.error("Failed creating space room list for \(spaceRoomProxy.id): \(error)") + return .failure(.sdkError(error)) + } + } + + // MARK: - Private + + private func handleUpdates(_ updates: [SpaceListUpdate]) { + var spaces = spacesSubject.value + + for update in updates { + switch update { + case .append(let spaceRooms): + spaces.append(contentsOf: spaceRooms.map(SpaceRoomProxy.init)) + case .clear: + spaces.removeAll() + case .pushFront(let spaceRoom): + spaces.insert(SpaceRoomProxy(spaceRoom: spaceRoom), at: 0) + case .pushBack(let spaceRoom): + spaces.append(SpaceRoomProxy(spaceRoom: spaceRoom)) + case .popFront: + spaces.removeFirst() + case .popBack: + spaces.removeLast() + case .insert(let index, let spaceRoom): + spaces.insert(SpaceRoomProxy(spaceRoom: spaceRoom), at: Int(index)) + case .set(let index, let spaceRoom): + spaces[Int(index)] = SpaceRoomProxy(spaceRoom: spaceRoom) + case .remove(let index): + spaces.remove(at: Int(index)) + case .truncate(let length): + spaces.removeSubrange(Int(length).. { get } - func spaceRoomList(for spaceRoom: SpaceRoomProxyProtocol) async -> Result + func spaceRoomList(for spaceRoomProxy: SpaceRoomProxyProtocol) async -> Result } diff --git a/UnitTests/Sources/SpaceListScreenViewModelTests.swift b/UnitTests/Sources/SpaceListScreenViewModelTests.swift index b917f76c7..e3e3fb36d 100644 --- a/UnitTests/Sources/SpaceListScreenViewModelTests.swift +++ b/UnitTests/Sources/SpaceListScreenViewModelTests.swift @@ -52,7 +52,7 @@ class SpaceListScreenViewModelTests: XCTestCase { let action = try await deferred.fulfill() switch action { - case .selectSpace(let spaceRoomListProxy) where spaceRoomListProxy.spaceRoom.id == selectedSpace.id: + case .selectSpace(let spaceRoomListProxy) where spaceRoomListProxy.spaceRoomProxy.id == selectedSpace.id: break default: XCTFail("The action should select the space.") diff --git a/UnitTests/Sources/SpaceScreenViewModelTests.swift b/UnitTests/Sources/SpaceScreenViewModelTests.swift index 4b91b148b..8e8e08db9 100644 --- a/UnitTests/Sources/SpaceScreenViewModelTests.swift +++ b/UnitTests/Sources/SpaceScreenViewModelTests.swift @@ -9,12 +9,13 @@ import Combine import XCTest @testable import ElementX +import MatrixRustSDK @MainActor class SpaceScreenViewModelTests: XCTestCase { var spaceRoomListProxy: SpaceRoomListProxyMock! let mockSpaceRooms = [SpaceRoomProxyProtocol].mockSpaceList - var paginationStateSubject: CurrentValueSubject = .init(.idle(endReached: true)) + var paginationStateSubject: CurrentValueSubject = .init(.idle(endReached: true)) var viewModel: SpaceScreenViewModelProtocol! @@ -97,7 +98,7 @@ class SpaceScreenViewModelTests: XCTestCase { let action = try await deferred.fulfill() switch action { - case .selectSpace(let spaceRoomListProxy) where spaceRoomListProxy.spaceRoom.id == selectedSpace.id: + case .selectSpace(let spaceRoomListProxy) where spaceRoomListProxy.spaceRoomProxy.id == selectedSpace.id: break default: XCTFail("The action should select the space.") @@ -114,7 +115,7 @@ class SpaceScreenViewModelTests: XCTestCase { let spaceServiceProxy = SpaceServiceProxyMock(.init()) spaceServiceProxy.spaceRoomListForClosure = { .success(SpaceRoomListProxyMock(.init(spaceRoomProxy: $0))) } - viewModel = SpaceScreenViewModel(spaceRoomList: spaceRoomListProxy, + viewModel = SpaceScreenViewModel(spaceRoomListProxy: spaceRoomListProxy, spaceServiceProxy: spaceServiceProxy, mediaProvider: MediaProviderMock(configuration: .init()), userIndicatorController: UserIndicatorControllerMock()) diff --git a/project.yml b/project.yml index 09f99c9c8..d2f10b34b 100644 --- a/project.yml +++ b/project.yml @@ -68,7 +68,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/element-hq/matrix-rust-components-swift - exactVersion: 25.08.26 + exactVersion: 25.09.01 # path: ../matrix-rust-sdk Compound: url: https://github.com/element-hq/compound-ios