diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 35807ebda..0ea949f83 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -7044,7 +7044,7 @@ repositoryURL = "https://github.com/matrix-org/matrix-rust-components-swift"; requirement = { kind = exactVersion; - version = 1.1.44; + version = 1.1.45; }; }; 821C67C9A7F8CC3FD41B28B4 /* XCRemoteSwiftPackageReference "emojibase-bindings" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index cbe6cb781..728811899 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -130,8 +130,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-rust-components-swift", "state" : { - "revision" : "c8d0dd3b7756584276ffb95ffc261aada654e0fa", - "version" : "1.1.44" + "revision" : "55425c708ba6377ef319f85f6f70c7caf2187368", + "version" : "1.1.45" } }, { diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index 13772c4a1..ea3f41e31 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -256,8 +256,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { case (.roomDetails, .dismissRoom, .initial): dismissRoom(animated: animated) - case (.roomDetails, .presentRoomDetailsEditScreen(let accountOwner), .roomDetailsEditScreen): - presentRoomDetailsEditScreen(accountOwner: accountOwner.value) + case (.roomDetails, .presentRoomDetailsEditScreen, .roomDetailsEditScreen): + presentRoomDetailsEditScreen() case (.roomDetailsEditScreen, .dismissRoomDetailsEditScreen, .roomDetails): break @@ -520,8 +520,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { return } - let params = RoomDetailsScreenCoordinatorParameters(accountUserID: userSession.userID, - roomProxy: roomProxy, + let params = RoomDetailsScreenCoordinatorParameters(roomProxy: roomProxy, + clientProxy: userSession.clientProxy, mediaProvider: userSession.mediaProvider, analyticsService: analytics, userIndicatorController: userIndicatorController, @@ -537,8 +537,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { dismissRoom(animated: animated) case .presentRoomMembersList: stateMachine.tryEvent(.presentRoomMembersList) - case .presentRoomDetailsEditScreen(let accountOwner): - stateMachine.tryEvent(.presentRoomDetailsEditScreen(accountOwner: .init(value: accountOwner))) + case .presentRoomDetailsEditScreen: + stateMachine.tryEvent(.presentRoomDetailsEditScreen) case .presentNotificationSettingsScreen: stateMachine.tryEvent(.presentNotificationSettingsScreen) case .presentInviteUsersScreen: @@ -600,17 +600,16 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { } } - private func presentRoomDetailsEditScreen(accountOwner: RoomMemberProxyProtocol) { + private func presentRoomDetailsEditScreen() { guard let roomProxy else { fatalError() } let stackCoordinator = NavigationStackCoordinator() - let roomDetailsEditParameters = RoomDetailsEditScreenCoordinatorParameters(accountOwner: accountOwner, + let roomDetailsEditParameters = RoomDetailsEditScreenCoordinatorParameters(roomProxy: roomProxy, mediaProvider: userSession.mediaProvider, navigationStackCoordinator: stackCoordinator, - roomProxy: roomProxy, userIndicatorController: userIndicatorController, orientationManager: orientationManager) let roomDetailsEditCoordinator = RoomDetailsEditScreenCoordinator(parameters: roomDetailsEditParameters) @@ -639,6 +638,7 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { let parameters = ReportContentScreenCoordinatorParameters(eventID: eventID, senderID: senderID, roomProxy: roomProxy, + clientProxy: userSession.clientProxy, userIndicatorController: userIndicatorController) let coordinator = ReportContentScreenCoordinator(parameters: parameters) @@ -928,8 +928,9 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { fatalError() } - let params = RoomMemberDetailsScreenCoordinatorParameters(roomProxy: roomProxy, - userID: userID, + let params = RoomMemberDetailsScreenCoordinatorParameters(userID: userID, + roomProxy: roomProxy, + clientProxy: userSession.clientProxy, mediaProvider: userSession.mediaProvider, userIndicatorController: userIndicatorController) let coordinator = RoomMemberDetailsScreenCoordinator(parameters: params) @@ -1205,7 +1206,7 @@ private extension RoomFlowCoordinator { case presentRoomDetails(roomID: String) case dismissRoomDetails - case presentRoomDetailsEditScreen(accountOwner: HashableRoomMemberWrapper) + case presentRoomDetailsEditScreen case dismissRoomDetailsEditScreen case presentNotificationSettingsScreen diff --git a/ElementX/Sources/Mocks/ClientProxyMock.swift b/ElementX/Sources/Mocks/ClientProxyMock.swift index c0365e787..124430953 100644 --- a/ElementX/Sources/Mocks/ClientProxyMock.swift +++ b/ElementX/Sources/Mocks/ClientProxyMock.swift @@ -18,7 +18,7 @@ import Combine import Foundation struct ClientProxyMockConfiguration { - let userID: String + var userID: String = RoomMemberProxyMock.mockMe.userID var deviceID: String? var roomSummaryProvider: RoomSummaryProviderProtocol? = RoomSummaryProviderMock(.init()) } @@ -62,8 +62,8 @@ extension ClientProxyMock { searchUsersSearchTermLimitReturnValue = .success(.init(results: [], limited: false)) profileForReturnValue = .success(.init(userID: "@a:b.com", displayName: "Some user")) sessionVerificationControllerProxyReturnValue = .failure(.failedRetrievingSessionVerificationController) - ignoreUserReturnValue = .failure(.failedIgnoringUser) - unignoreUserReturnValue = .failure(.failedUnignoringUser) + ignoreUserReturnValue = .success(()) + unignoreUserReturnValue = .success(()) loadMediaContentForSourceThrowableError = ClientProxyError.failedLoadingMedia loadMediaThumbnailForSourceWidthHeightThrowableError = ClientProxyError.failedLoadingMedia diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 743491f55..6cf780204 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -2253,11 +2253,6 @@ class RoomMemberProxyMock: RoomMemberProxyProtocol { set(value) { underlyingMembership = value } } var underlyingMembership: MembershipState! - var isAccountOwner: Bool { - get { return underlyingIsAccountOwner } - set(value) { underlyingIsAccountOwner = value } - } - var underlyingIsAccountOwner: Bool! var isIgnored: Bool { get { return underlyingIsIgnored } set(value) { underlyingIsIgnored = value } @@ -2273,77 +2268,7 @@ class RoomMemberProxyMock: RoomMemberProxyProtocol { set(value) { underlyingRole = value } } var underlyingRole: RoomMemberRole! - var canInviteUsers: Bool { - get { return underlyingCanInviteUsers } - set(value) { underlyingCanInviteUsers = value } - } - var underlyingCanInviteUsers: Bool! - var canKickUsers: Bool { - get { return underlyingCanKickUsers } - set(value) { underlyingCanKickUsers = value } - } - var underlyingCanKickUsers: Bool! - var canBanUsers: Bool { - get { return underlyingCanBanUsers } - set(value) { underlyingCanBanUsers = value } - } - var underlyingCanBanUsers: Bool! - //MARK: - ignoreUser - - var ignoreUserCallsCount = 0 - var ignoreUserCalled: Bool { - return ignoreUserCallsCount > 0 - } - var ignoreUserReturnValue: Result! - var ignoreUserClosure: (() async -> Result)? - - func ignoreUser() async -> Result { - ignoreUserCallsCount += 1 - if let ignoreUserClosure = ignoreUserClosure { - return await ignoreUserClosure() - } else { - return ignoreUserReturnValue - } - } - //MARK: - unignoreUser - - var unignoreUserCallsCount = 0 - var unignoreUserCalled: Bool { - return unignoreUserCallsCount > 0 - } - var unignoreUserReturnValue: Result! - var unignoreUserClosure: (() async -> Result)? - - func unignoreUser() async -> Result { - unignoreUserCallsCount += 1 - if let unignoreUserClosure = unignoreUserClosure { - return await unignoreUserClosure() - } else { - return unignoreUserReturnValue - } - } - //MARK: - canSendStateEvent - - var canSendStateEventTypeCallsCount = 0 - var canSendStateEventTypeCalled: Bool { - return canSendStateEventTypeCallsCount > 0 - } - var canSendStateEventTypeReceivedType: StateEventType? - var canSendStateEventTypeReceivedInvocations: [StateEventType] = [] - var canSendStateEventTypeReturnValue: Bool! - var canSendStateEventTypeClosure: ((StateEventType) -> Bool)? - - func canSendStateEvent(type: StateEventType) -> Bool { - canSendStateEventTypeCallsCount += 1 - canSendStateEventTypeReceivedType = type - canSendStateEventTypeReceivedInvocations.append(type) - if let canSendStateEventTypeClosure = canSendStateEventTypeClosure { - return canSendStateEventTypeClosure(type) - } else { - return canSendStateEventTypeReturnValue - } - } } class RoomNotificationSettingsProxyMock: RoomNotificationSettingsProxyProtocol { var mode: RoomNotificationModeProxy { @@ -2517,27 +2442,6 @@ class RoomProxyMock: RoomProxyProtocol { return reportContentReasonReturnValue } } - //MARK: - ignoreUser - - var ignoreUserCallsCount = 0 - var ignoreUserCalled: Bool { - return ignoreUserCallsCount > 0 - } - var ignoreUserReceivedUserID: String? - var ignoreUserReceivedInvocations: [String] = [] - var ignoreUserReturnValue: Result! - var ignoreUserClosure: ((String) async -> Result)? - - func ignoreUser(_ userID: String) async -> Result { - ignoreUserCallsCount += 1 - ignoreUserReceivedUserID = userID - ignoreUserReceivedInvocations.append(userID) - if let ignoreUserClosure = ignoreUserClosure { - return await ignoreUserClosure(userID) - } else { - return ignoreUserReturnValue - } - } //MARK: - leaveRoom var leaveRoomCallsCount = 0 @@ -2846,6 +2750,48 @@ class RoomProxyMock: RoomProxyProtocol { return applyPowerLevelChangesReturnValue } } + //MARK: - canUser + + var canUserUserIDSendStateEventCallsCount = 0 + var canUserUserIDSendStateEventCalled: Bool { + return canUserUserIDSendStateEventCallsCount > 0 + } + var canUserUserIDSendStateEventReceivedArguments: (userID: String, event: StateEventType)? + var canUserUserIDSendStateEventReceivedInvocations: [(userID: String, event: StateEventType)] = [] + var canUserUserIDSendStateEventReturnValue: Result! + var canUserUserIDSendStateEventClosure: ((String, StateEventType) async -> Result)? + + func canUser(userID: String, sendStateEvent event: StateEventType) async -> Result { + canUserUserIDSendStateEventCallsCount += 1 + canUserUserIDSendStateEventReceivedArguments = (userID: userID, event: event) + canUserUserIDSendStateEventReceivedInvocations.append((userID: userID, event: event)) + if let canUserUserIDSendStateEventClosure = canUserUserIDSendStateEventClosure { + return await canUserUserIDSendStateEventClosure(userID, event) + } else { + return canUserUserIDSendStateEventReturnValue + } + } + //MARK: - canUserInvite + + var canUserInviteUserIDCallsCount = 0 + var canUserInviteUserIDCalled: Bool { + return canUserInviteUserIDCallsCount > 0 + } + var canUserInviteUserIDReceivedUserID: String? + var canUserInviteUserIDReceivedInvocations: [String] = [] + var canUserInviteUserIDReturnValue: Result! + var canUserInviteUserIDClosure: ((String) async -> Result)? + + func canUserInvite(userID: String) async -> Result { + canUserInviteUserIDCallsCount += 1 + canUserInviteUserIDReceivedUserID = userID + canUserInviteUserIDReceivedInvocations.append(userID) + if let canUserInviteUserIDClosure = canUserInviteUserIDClosure { + return await canUserInviteUserIDClosure(userID) + } else { + return canUserInviteUserIDReturnValue + } + } //MARK: - canUserRedactOther var canUserRedactOtherUserIDCallsCount = 0 @@ -2888,6 +2834,48 @@ class RoomProxyMock: RoomProxyProtocol { return canUserRedactOwnUserIDReturnValue } } + //MARK: - canUserKick + + var canUserKickUserIDCallsCount = 0 + var canUserKickUserIDCalled: Bool { + return canUserKickUserIDCallsCount > 0 + } + var canUserKickUserIDReceivedUserID: String? + var canUserKickUserIDReceivedInvocations: [String] = [] + var canUserKickUserIDReturnValue: Result! + var canUserKickUserIDClosure: ((String) async -> Result)? + + func canUserKick(userID: String) async -> Result { + canUserKickUserIDCallsCount += 1 + canUserKickUserIDReceivedUserID = userID + canUserKickUserIDReceivedInvocations.append(userID) + if let canUserKickUserIDClosure = canUserKickUserIDClosure { + return await canUserKickUserIDClosure(userID) + } else { + return canUserKickUserIDReturnValue + } + } + //MARK: - canUserBan + + var canUserBanUserIDCallsCount = 0 + var canUserBanUserIDCalled: Bool { + return canUserBanUserIDCallsCount > 0 + } + var canUserBanUserIDReceivedUserID: String? + var canUserBanUserIDReceivedInvocations: [String] = [] + var canUserBanUserIDReturnValue: Result! + var canUserBanUserIDClosure: ((String) async -> Result)? + + func canUserBan(userID: String) async -> Result { + canUserBanUserIDCallsCount += 1 + canUserBanUserIDReceivedUserID = userID + canUserBanUserIDReceivedInvocations.append(userID) + if let canUserBanUserIDClosure = canUserBanUserIDClosure { + return await canUserBanUserIDClosure(userID) + } else { + return canUserBanUserIDReturnValue + } + } //MARK: - canUserTriggerRoomNotification var canUserTriggerRoomNotificationUserIDCallsCount = 0 diff --git a/ElementX/Sources/Mocks/RoomMemberProxyMock.swift b/ElementX/Sources/Mocks/RoomMemberProxyMock.swift index ba7d6b3c6..f7826d8be 100644 --- a/ElementX/Sources/Mocks/RoomMemberProxyMock.swift +++ b/ElementX/Sources/Mocks/RoomMemberProxyMock.swift @@ -21,15 +21,12 @@ struct RoomMemberProxyMockConfiguration { var userID: String var displayName: String? var avatarURL: URL? + var membership: MembershipState - var isAccountOwner = false var isIgnored = false + var powerLevel = 0 var role = RoomMemberRole.user - var canInviteUsers = false - var canKickUsers = false - var canBanUsers = false - var canSendStateEvent: (StateEventType) -> Bool = { _ in true } } extension RoomMemberProxyMock { @@ -38,15 +35,12 @@ extension RoomMemberProxyMock { userID = configuration.userID displayName = configuration.displayName avatarURL = configuration.avatarURL + membership = configuration.membership - isAccountOwner = configuration.isAccountOwner isIgnored = configuration.isIgnored + powerLevel = configuration.powerLevel role = configuration.role - canInviteUsers = configuration.canInviteUsers - canKickUsers = configuration.canKickUsers - canBanUsers = configuration.canBanUsers - canSendStateEventTypeClosure = configuration.canSendStateEvent } // Mocks @@ -54,9 +48,7 @@ extension RoomMemberProxyMock { RoomMemberProxyMock(with: .init(userID: "@me:matrix.org", displayName: "Me", avatarURL: URL.picturesDirectory, - membership: .join, - isAccountOwner: true, - canInviteUsers: true)) + membership: .join)) } static var mockMeAdmin: RoomMemberProxyMock { @@ -64,12 +56,8 @@ extension RoomMemberProxyMock { displayName: "Me admin", avatarURL: URL.picturesDirectory, membership: .join, - isAccountOwner: true, powerLevel: 100, - role: .administrator, - canInviteUsers: true, - canKickUsers: true, - canBanUsers: true)) + role: .administrator)) } static var mockAlice: RoomMemberProxyMock { @@ -117,15 +105,6 @@ extension RoomMemberProxyMock { isIgnored: true)) } - static func mockOwner(allowedStateEvents: [StateEventType], canInviteUsers: Bool = true) -> RoomMemberProxyMock { - RoomMemberProxyMock(with: .init(userID: "@foo:some.org", - displayName: "User owner", - membership: .join, - isAccountOwner: true, - canInviteUsers: canInviteUsers, - canSendStateEvent: { allowedStateEvents.contains($0) })) - } - static var mockAdmin: RoomMemberProxyMock { RoomMemberProxyMock(with: .init(userID: "@admin:matrix.org", displayName: "Arthur", diff --git a/ElementX/Sources/Mocks/RoomProxyMock.swift b/ElementX/Sources/Mocks/RoomProxyMock.swift index dc6863233..e97a1ba7e 100644 --- a/ElementX/Sources/Mocks/RoomProxyMock.swift +++ b/ElementX/Sources/Mocks/RoomProxyMock.swift @@ -43,8 +43,9 @@ struct RoomProxyMockConfiguration { var members: [RoomMemberProxyMock] = .allMembers var memberForID: RoomMemberProxyMock = .mockMe - var ownUserID = "@alice:somewhere.org" - + var ownUserID = RoomMemberProxyMock.mockMe.userID + + var canUserInvite = true var canUserTriggerRoomNotification = false var canUserJoinCall = true } @@ -89,7 +90,17 @@ extension RoomProxyMock { currentPowerLevelChangesReturnValue = .success(.init()) applyPowerLevelChangesReturnValue = .success(()) + canUserUserIDSendStateEventClosure = { [weak self] userID, _ in + .success(self?.membersPublisher.value.first { $0.userID == userID }?.role ?? .user != .user) + } + canUserInviteUserIDReturnValue = .success(configuration.canUserInvite) canUserRedactOtherUserIDReturnValue = .success(false) + canUserKickUserIDClosure = { [weak self] userID in + .success(self?.membersPublisher.value.first { $0.userID == userID }?.role ?? .user != .user) + } + canUserBanUserIDClosure = { [weak self] userID in + .success(self?.membersPublisher.value.first { $0.userID == userID }?.role ?? .user != .user) + } canUserTriggerRoomNotificationUserIDReturnValue = .success(configuration.canUserTriggerRoomNotification) canUserJoinCallUserIDReturnValue = .success(configuration.canUserJoinCall) diff --git a/ElementX/Sources/Screens/ComposerToolbar/CompletionSuggestionService.swift b/ElementX/Sources/Screens/ComposerToolbar/CompletionSuggestionService.swift index bcda41b13..79b45f302 100644 --- a/ElementX/Sources/Screens/ComposerToolbar/CompletionSuggestionService.swift +++ b/ElementX/Sources/Screens/ComposerToolbar/CompletionSuggestionService.swift @@ -28,7 +28,7 @@ final class CompletionSuggestionService: CompletionSuggestionServiceProtocol { self.roomProxy = roomProxy suggestionsPublisher = suggestionTriggerSubject .combineLatest(roomProxy.membersPublisher) - .map { [weak self] suggestionPattern, members -> [SuggestionItem] in + .map { [weak self, ownUserID = roomProxy.ownUserID] suggestionPattern, members -> [SuggestionItem] in guard let self, let suggestionPattern else { return [] @@ -38,7 +38,7 @@ final class CompletionSuggestionService: CompletionSuggestionServiceProtocol { case .user: var membersSuggestion = members .compactMap { member -> SuggestionItem? in - guard !member.isAccountOwner, + guard member.userID != ownUserID, member.membership == .join, Self.isIncluded(searchText: suggestionPattern.text, userID: member.userID, displayName: member.displayName) else { return nil diff --git a/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenCoordinator.swift b/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenCoordinator.swift index d4aefd225..1edc27a07 100644 --- a/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenCoordinator.swift +++ b/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenCoordinator.swift @@ -21,6 +21,7 @@ struct ReportContentScreenCoordinatorParameters { let eventID: String let senderID: String let roomProxy: RoomProxyProtocol + let clientProxy: ClientProxyProtocol let userIndicatorController: UserIndicatorControllerProtocol } @@ -42,7 +43,10 @@ final class ReportContentScreenCoordinator: CoordinatorProtocol { init(parameters: ReportContentScreenCoordinatorParameters) { self.parameters = parameters - viewModel = ReportContentScreenViewModel(eventID: parameters.eventID, senderID: parameters.senderID, roomProxy: parameters.roomProxy) + viewModel = ReportContentScreenViewModel(eventID: parameters.eventID, + senderID: parameters.senderID, + roomProxy: parameters.roomProxy, + clientProxy: parameters.clientProxy) } // MARK: - Public @@ -55,9 +59,9 @@ final class ReportContentScreenCoordinator: CoordinatorProtocol { switch action { case .submitStarted: startLoading() - case let .submitFailed(error): + case let .submitFailed(message): stopLoading() - showError(description: error.localizedDescription) + showError(description: message) case .submitFinished: stopLoading() actionsSubject.send(.finish) diff --git a/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenModels.swift b/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenModels.swift index 784747963..39676dadd 100644 --- a/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenModels.swift +++ b/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenModels.swift @@ -20,7 +20,7 @@ enum ReportContentScreenViewModelAction: Equatable { case cancel case submitStarted case submitFinished - case submitFailed(error: RoomProxyError) + case submitFailed(message: String) } struct ReportContentScreenViewState: BindableState { diff --git a/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenViewModel.swift b/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenViewModel.swift index 7c09857a8..4f90921ff 100644 --- a/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenViewModel.swift +++ b/ElementX/Sources/Screens/ReportContentScreen/ReportContentScreenViewModel.swift @@ -23,16 +23,18 @@ class ReportContentScreenViewModel: ReportContentScreenViewModelType, ReportCont private let eventID: String private let senderID: String private let roomProxy: RoomProxyProtocol + private let clientProxy: ClientProxyProtocol private let actionsSubject: PassthroughSubject = .init() var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() } - init(eventID: String, senderID: String, roomProxy: RoomProxyProtocol) { + init(eventID: String, senderID: String, roomProxy: RoomProxyProtocol, clientProxy: ClientProxyProtocol) { self.eventID = eventID self.senderID = senderID self.roomProxy = roomProxy + self.clientProxy = clientProxy super.init(initialViewState: ReportContentScreenViewState(bindings: ReportContentScreenViewStateBindings(reasonText: "", ignoreUser: false))) } @@ -55,14 +57,14 @@ class ReportContentScreenViewModel: ReportContentScreenViewModelType, ReportCont if case let .failure(error) = await roomProxy.reportContent(eventID, reason: state.bindings.reasonText) { MXLog.error("Submit Report Content failed: \(error)") - actionsSubject.send(.submitFailed(error: error)) + actionsSubject.send(.submitFailed(message: error.localizedDescription)) return } // Ignore the sender if the user wants to. - if state.bindings.ignoreUser, case let .failure(error) = await roomProxy.ignoreUser(senderID) { + if state.bindings.ignoreUser, case let .failure(error) = await clientProxy.ignoreUser(senderID) { MXLog.error("Ignore user failed: \(error)") - actionsSubject.send(.submitFailed(error: error)) + actionsSubject.send(.submitFailed(message: error.localizedDescription)) return } diff --git a/ElementX/Sources/Screens/ReportContentScreen/View/ReportContentScreen.swift b/ElementX/Sources/Screens/ReportContentScreen/View/ReportContentScreen.swift index b15908b75..aafffa145 100644 --- a/ElementX/Sources/Screens/ReportContentScreen/View/ReportContentScreen.swift +++ b/ElementX/Sources/Screens/ReportContentScreen/View/ReportContentScreen.swift @@ -77,7 +77,8 @@ struct ReportContentScreen: View { struct ReportContentScreen_Previews: PreviewProvider, TestablePreview { static let viewModel = ReportContentScreenViewModel(eventID: "", senderID: "", - roomProxy: RoomProxyMock(with: .init())) + roomProxy: RoomProxyMock(with: .init()), + clientProxy: ClientProxyMock(.init())) static var previews: some View { NavigationStack { diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenCoordinator.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenCoordinator.swift index 8987b861f..11d9a74e3 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenCoordinator.swift @@ -18,10 +18,9 @@ import Combine import SwiftUI struct RoomDetailsEditScreenCoordinatorParameters { - let accountOwner: RoomMemberProxyProtocol + let roomProxy: RoomProxyProtocol let mediaProvider: MediaProviderProtocol weak var navigationStackCoordinator: NavigationStackCoordinator? - let roomProxy: RoomProxyProtocol let userIndicatorController: UserIndicatorControllerProtocol let orientationManager: OrientationManagerProtocol } @@ -43,9 +42,8 @@ final class RoomDetailsEditScreenCoordinator: CoordinatorProtocol { init(parameters: RoomDetailsEditScreenCoordinatorParameters) { self.parameters = parameters - viewModel = RoomDetailsEditScreenViewModel(accountOwner: parameters.accountOwner, + viewModel = RoomDetailsEditScreenViewModel(roomProxy: parameters.roomProxy, mediaProvider: parameters.mediaProvider, - roomProxy: parameters.roomProxy, userIndicatorController: parameters.userIndicatorController) } diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenModels.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenModels.swift index 560cd0aea..29f136566 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenModels.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenModels.swift @@ -34,9 +34,9 @@ struct RoomDetailsEditScreenViewState: BindableState { let initialAvatarURL: URL? let initialName: String let initialTopic: String - let canEditAvatar: Bool - let canEditName: Bool - let canEditTopic: Bool + var canEditAvatar = false + var canEditName = false + var canEditTopic = false var avatarURL: URL? var localMedia: MediaInfo? diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift index b3ecf1023..6b9d6c118 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift @@ -29,9 +29,8 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe actionsSubject.eraseToAnyPublisher() } - init(accountOwner: RoomMemberProxyProtocol, + init(roomProxy: RoomProxyProtocol, mediaProvider: MediaProviderProtocol, - roomProxy: RoomProxyProtocol, userIndicatorController: UserIndicatorControllerProtocol) { self.roomProxy = roomProxy self.userIndicatorController = userIndicatorController @@ -44,11 +43,15 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe initialAvatarURL: roomAvatar, initialName: roomName ?? "", initialTopic: roomTopic ?? "", - canEditAvatar: accountOwner.canSendStateEvent(type: .roomAvatar), - canEditName: accountOwner.canSendStateEvent(type: .roomName), - canEditTopic: accountOwner.canSendStateEvent(type: .roomTopic), avatarURL: roomAvatar, bindings: .init(name: roomName ?? "", topic: roomTopic ?? "")), imageProvider: mediaProvider) + + Task { + // Can't use async let because the mocks aren't thread safe when calling the same method 🤦‍♂️ + state.canEditAvatar = await roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomAvatar) == .success(true) + state.canEditName = await roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomName) == .success(true) + state.canEditTopic = await roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomTopic) == .success(true) + } } // MARK: - Public diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift index 470f4a0e9..8a06e7cc8 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift @@ -156,16 +156,18 @@ struct RoomDetailsEditScreen: View { // MARK: - Previews struct RoomDetailsEditScreen_Previews: PreviewProvider, TestablePreview { - static let viewModel = RoomDetailsEditScreenViewModel(accountOwner: RoomMemberProxyMock.mockAlice, + static let viewModel = RoomDetailsEditScreenViewModel(roomProxy: RoomProxyMock(with: .init(id: "test_id", + name: "Room", + members: [.mockMeAdmin])), mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: .init(id: "test_id", name: "Room")), userIndicatorController: UserIndicatorControllerMock.default) static let readOnlyViewModel = { - let accountOwner = RoomMemberProxyMock.mockOwner(allowedStateEvents: []) - return RoomDetailsEditScreenViewModel(accountOwner: accountOwner, + let accountOwner = RoomMemberProxyMock.mockMe + return RoomDetailsEditScreenViewModel(roomProxy: RoomProxyMock(with: .init(id: "test_id", + name: "Room", + members: [.mockMe])), mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: .init(id: "test_id", name: "Room")), userIndicatorController: UserIndicatorControllerMock.default) }() @@ -174,10 +176,12 @@ struct RoomDetailsEditScreen_Previews: PreviewProvider, TestablePreview { RoomDetailsEditScreen(context: viewModel.context) } .previewDisplayName("Normal") + .snapshot(delay: 0.1) NavigationStack { RoomDetailsEditScreen(context: readOnlyViewModel.context) } .previewDisplayName("Read only") + .snapshot(delay: 0.1) } } diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift index 0c054009a..b26fb2b66 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift @@ -18,8 +18,8 @@ import Combine import SwiftUI struct RoomDetailsScreenCoordinatorParameters { - let accountUserID: String let roomProxy: RoomProxyProtocol + let clientProxy: ClientProxyProtocol let mediaProvider: MediaProviderProtocol let analyticsService: AnalyticsService let userIndicatorController: UserIndicatorControllerProtocol @@ -30,7 +30,7 @@ struct RoomDetailsScreenCoordinatorParameters { enum RoomDetailsScreenCoordinatorAction { case leftRoom case presentRoomMembersList - case presentRoomDetailsEditScreen(accountOwner: RoomMemberProxyProtocol) + case presentRoomDetailsEditScreen case presentNotificationSettingsScreen case presentInviteUsersScreen case presentPollsHistory @@ -47,8 +47,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol { } init(parameters: RoomDetailsScreenCoordinatorParameters) { - viewModel = RoomDetailsScreenViewModel(accountUserID: parameters.accountUserID, - roomProxy: parameters.roomProxy, + viewModel = RoomDetailsScreenViewModel(roomProxy: parameters.roomProxy, + clientProxy: parameters.clientProxy, mediaProvider: parameters.mediaProvider, analyticsService: parameters.analyticsService, userIndicatorController: parameters.userIndicatorController, @@ -70,8 +70,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol { actionsSubject.send(.presentInviteUsersScreen) case .leftRoom: actionsSubject.send(.leftRoom) - case .requestEditDetailsPresentation(let accountOwner): - actionsSubject.send(.presentRoomDetailsEditScreen(accountOwner: accountOwner)) + case .requestEditDetailsPresentation: + actionsSubject.send(.presentRoomDetailsEditScreen) case .requestNotificationSettingsPresentation: actionsSubject.send(.presentNotificationSettingsScreen) case .requestPollsHistoryPresentation: diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenModels.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenModels.swift index db767995b..17f904662 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenModels.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenModels.swift @@ -26,7 +26,7 @@ enum RoomDetailsScreenViewModelAction { case requestMemberDetailsPresentation case requestInvitePeoplePresentation case leftRoom - case requestEditDetailsPresentation(RoomMemberProxyProtocol) + case requestEditDetailsPresentation case requestPollsHistoryPresentation } diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift index b9c725241..812b391a1 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift @@ -20,18 +20,14 @@ import SwiftUI typealias RoomDetailsScreenViewModelType = StateStoreViewModel class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScreenViewModelProtocol { - private let accountUserID: String private let roomProxy: RoomProxyProtocol + private let clientProxy: ClientProxyProtocol private let analyticsService: AnalyticsService private let mediaProvider: MediaProviderProtocol private let userIndicatorController: UserIndicatorControllerProtocol private let notificationSettingsProxy: NotificationSettingsProxyProtocol private let attributedStringBuilder: AttributedStringBuilderProtocol - private var accountOwner: RoomMemberProxyProtocol? { - didSet { updatePowerLevelPermissions() } - } - private var dmRecipient: RoomMemberProxyProtocol? private var actionsSubject: PassthroughSubject = .init() @@ -40,15 +36,15 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr actionsSubject.eraseToAnyPublisher() } - init(accountUserID: String, - roomProxy: RoomProxyProtocol, + init(roomProxy: RoomProxyProtocol, + clientProxy: ClientProxyProtocol, mediaProvider: MediaProviderProtocol, analyticsService: AnalyticsService, userIndicatorController: UserIndicatorControllerProtocol, notificationSettingsProxy: NotificationSettingsProxyProtocol, attributedStringBuilder: AttributedStringBuilderProtocol) { - self.accountUserID = accountUserID self.roomProxy = roomProxy + self.clientProxy = clientProxy self.mediaProvider = mediaProvider self.analyticsService = analyticsService self.userIndicatorController = userIndicatorController @@ -69,9 +65,10 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr imageProvider: mediaProvider) updateRoomInfo() + Task { await updatePowerLevelPermissions() } setupRoomSubscription() - fetchMembers() + Task { await fetchMembersIfNeeded() } setupNotificationSettingsSubscription() fetchNotificationSettings() @@ -103,11 +100,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr case .processTapUnignore: state.bindings.ignoreUserRoomAlertItem = .init(action: .unignore) case .processTapEdit, .processTapAddTopic: - guard let accountOwner else { - MXLog.error("Missing account owner when presenting the room's edit details screen") - return - } - actionsSubject.send(.requestEditDetailsPresentation(accountOwner)) + actionsSubject.send(.requestEditDetailsPresentation) case .ignoreConfirmed: Task { await ignore() } case .unignoreConfirmed: @@ -154,13 +147,6 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr } } - private func fetchMembers() { - Task { - await fetchMembersIfNeeded() - await fetchAccountOwner() - } - } - private func fetchMembersIfNeeded() async { // We need to fetch members just in 1-to-1 chat to get the member object for the other person guard roomProxy.isEncryptedOneToOneRoom else { @@ -169,9 +155,9 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr roomProxy.membersPublisher .receive(on: DispatchQueue.main) - .sink { [weak self] members in + .sink { [weak self, ownUserID = roomProxy.ownUserID] members in guard let self else { return } - let dmRecipient = members.first(where: { !$0.isAccountOwner }) + let dmRecipient = members.first(where: { $0.userID != ownUserID }) self.dmRecipient = dmRecipient self.state.dmRecipient = dmRecipient.map(RoomMemberDetails.init(withProxy:)) } @@ -180,20 +166,13 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr await roomProxy.updateMembers() } - private func fetchAccountOwner() async { - switch await roomProxy.getMember(userID: accountUserID) { - case .success(let member): - accountOwner = member - case .failure(let error): - MXLog.error("Failed (error: \(error) to get account owner member with id: \(accountUserID), in room: \(roomProxy.id)") - } - } - - private func updatePowerLevelPermissions() { - state.canInviteUsers = accountOwner?.canInviteUsers ?? false - state.canEditRoomName = accountOwner?.canSendStateEvent(type: .roomName) ?? false - state.canEditRoomTopic = accountOwner?.canSendStateEvent(type: .roomTopic) ?? false - state.canEditRoomAvatar = accountOwner?.canSendStateEvent(type: .roomAvatar) ?? false + private func updatePowerLevelPermissions() async { + async let canInviteUsers = roomProxy.canUserInvite(userID: roomProxy.ownUserID) == .success(true) + // Can't use async let because the mocks aren't thread safe when calling the same method 🤦‍♂️ + state.canEditRoomName = await roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomName) == .success(true) + state.canEditRoomTopic = await roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomTopic) == .success(true) + state.canEditRoomAvatar = await roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomAvatar) == .success(true) + state.canInviteUsers = await canInviteUsers } private func setupNotificationSettingsSubscription() { @@ -280,8 +259,14 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr } private func ignore() async { + guard let dmUserID = dmRecipient?.userID else { + MXLog.error("Attempting to ignore a nil DM Recipient") + state.bindings.alertInfo = .init(id: .unknown) + return + } + state.isProcessingIgnoreRequest = true - let result = await dmRecipient?.ignoreUser() + let result = await clientProxy.ignoreUser(dmUserID) state.isProcessingIgnoreRequest = false switch result { case .success: @@ -289,14 +274,20 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr var dmRecipient = state.dmRecipient dmRecipient?.isIgnored = true state.dmRecipient = dmRecipient - case .failure, .none: + case .failure: state.bindings.alertInfo = .init(id: .unknown) } } private func unignore() async { + guard let dmUserID = dmRecipient?.userID else { + MXLog.error("Attempting to unignore a nil DM Recipient") + state.bindings.alertInfo = .init(id: .unknown) + return + } + state.isProcessingIgnoreRequest = true - let result = await dmRecipient?.unignoreUser() + let result = await clientProxy.unignoreUser(dmUserID) state.isProcessingIgnoreRequest = false switch result { case .success: @@ -304,7 +295,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr var dmRecipient = state.dmRecipient dmRecipient?.isIgnored = false state.dmRecipient = dmRecipient - case .failure, .none: + case .failure: state.bindings.alertInfo = .init(id: .unknown) } } diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift index 873e28c04..2e8fb33a6 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift @@ -279,7 +279,7 @@ struct RoomDetailsScreen: View { struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { static let genericRoomViewModel = { let members: [RoomMemberProxyMock] = [ - .mockMe, + .mockMeAdmin, .mockAlice, .mockBob, .mockCharlie @@ -304,8 +304,8 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { let notificationSettingsProxy = NotificationSettingsProxyMock(with: notificationSettingsProxyMockConfiguration) let appSettings = AppSettings() - return RoomDetailsScreenViewModel(accountUserID: RoomMemberProxyMock.mockMe.userID, - roomProxy: roomProxy, + return RoomDetailsScreenViewModel(roomProxy: roomProxy, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -330,8 +330,8 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init()) let appSettings = AppSettings() - return RoomDetailsScreenViewModel(accountUserID: RoomMemberProxyMock.mockMe.userID, - roomProxy: roomProxy, + return RoomDetailsScreenViewModel(roomProxy: roomProxy, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -342,7 +342,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { static let simpleRoomViewModel = { let members: [RoomMemberProxyMock] = [ - .mockMe, + .mockMeAdmin, .mockAlice, .mockBob, .mockCharlie @@ -355,8 +355,8 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview { let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init()) let appSettings = AppSettings() - return RoomDetailsScreenViewModel(accountUserID: RoomMemberProxyMock.mockMe.userID, - roomProxy: roomProxy, + return RoomDetailsScreenViewModel(roomProxy: roomProxy, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, diff --git a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenCoordinator.swift b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenCoordinator.swift index d71a4f8b2..9018c2da8 100644 --- a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenCoordinator.swift @@ -18,8 +18,9 @@ import Combine import SwiftUI struct RoomMemberDetailsScreenCoordinatorParameters { - let roomProxy: RoomProxyProtocol let userID: String + let roomProxy: RoomProxyProtocol + let clientProxy: ClientProxyProtocol let mediaProvider: MediaProviderProtocol let userIndicatorController: UserIndicatorControllerProtocol } @@ -39,8 +40,9 @@ final class RoomMemberDetailsScreenCoordinator: CoordinatorProtocol { } init(parameters: RoomMemberDetailsScreenCoordinatorParameters) { - viewModel = RoomMemberDetailsScreenViewModel(roomProxy: parameters.roomProxy, - userID: parameters.userID, + viewModel = RoomMemberDetailsScreenViewModel(userID: parameters.userID, + roomProxy: parameters.roomProxy, + clientProxy: parameters.clientProxy, mediaProvider: parameters.mediaProvider, userIndicatorController: parameters.userIndicatorController) } diff --git a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenModels.swift b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenModels.swift index 7ac63fd71..f5255ef71 100644 --- a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenModels.swift +++ b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenModels.swift @@ -23,6 +23,7 @@ enum RoomMemberDetailsScreenViewModelAction { struct RoomMemberDetailsScreenViewState: BindableState { let userID: String var memberDetails: RoomMemberDetails? + var isOwnMemberDetails = false var isProcessingIgnoreRequest = false var bindings: RoomMemberDetailsScreenViewStateBindings diff --git a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift index 2fddd250f..e2776ae83 100644 --- a/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomMemberDetailsScreen/RoomMemberDetailsScreenViewModel.swift @@ -21,6 +21,7 @@ typealias RoomMemberDetailsScreenViewModelType = StateStoreViewModel RoomMembersListScreenViewModel { let mockAdmin = RoomMemberProxyMock.mockAdmin - if isAdmin { - mockAdmin.underlyingCanBanUsers = true - mockAdmin.underlyingIsAccountOwner = true - } + let ownUserID = isAdmin ? mockAdmin.userID : RoomMemberProxyMock.mockMe.userID var members: [RoomMemberProxyMock] = [ .mockAlice, @@ -160,7 +157,10 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview { } return RoomMembersListScreenViewModel(initialMode: initialMode, - roomProxy: RoomProxyMock(with: .init(name: "Some room", members: members)), + roomProxy: RoomProxyMock(with: .init(name: "Some room", + members: members, + ownUserID: ownUserID, + canUserInvite: false)), mediaProvider: MockMediaProvider(), userIndicatorController: ServiceLocator.shared.userIndicatorController, appSettings: ServiceLocator.shared.settings) diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index 01a9adf0e..0b86a5676 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -589,7 +589,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol guard let members = await roomProxy.members(), members.count == 2, - let otherPerson = members.first(where: { !$0.isAccountOwner && $0.membership == .leave }) + let otherPerson = members.first(where: { $0.userID != roomProxy.ownUserID && $0.membership == .leave }) else { userIndicatorController.alertInfo = .init(id: .init(), title: L10n.commonError) return diff --git a/ElementX/Sources/Services/Room/RoomMember/RoomMemberDetails.swift b/ElementX/Sources/Services/Room/RoomMember/RoomMemberDetails.swift index 8aab289d3..bb97c63ef 100644 --- a/ElementX/Sources/Services/Room/RoomMember/RoomMemberDetails.swift +++ b/ElementX/Sources/Services/Room/RoomMember/RoomMemberDetails.swift @@ -22,7 +22,7 @@ struct RoomMemberDetails: Identifiable, Hashable { let name: String? let avatarURL: URL? let permalink: URL? - let isAccountOwner: Bool + var isIgnored: Bool var isBanned: Bool @@ -36,7 +36,7 @@ extension RoomMemberDetails { name = proxy.displayName avatarURL = proxy.avatarURL permalink = proxy.permalink - isAccountOwner = proxy.isAccountOwner + isIgnored = proxy.isIgnored isBanned = proxy.membership == .ban role = .init(proxy.role) @@ -47,7 +47,7 @@ extension RoomMemberDetails { name = nil avatarURL = nil permalink = nil - isAccountOwner = false + isIgnored = false isBanned = false role = .user diff --git a/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxy.swift b/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxy.swift index f4b1dcc27..cd18073fd 100644 --- a/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxy.swift +++ b/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxy.swift @@ -19,69 +19,25 @@ import MatrixRustSDK final class RoomMemberProxy: RoomMemberProxyProtocol { private let backgroundTaskService: BackgroundTaskServiceProtocol - private let member: RoomMemberProtocol - + private let member: RoomMember + private let backgroundAccountDataTaskName = "SendAccountDataEvent" private var sendAccountDataEventBackgroundTask: BackgroundTaskProtocol? - + private let userInitiatedDispatchQueue = DispatchQueue(label: "io.element.elementx.roommemberproxy.userinitiated", qos: .userInitiated) - - init(member: RoomMemberProtocol, backgroundTaskService: BackgroundTaskServiceProtocol) { + + init(member: RoomMember, backgroundTaskService: BackgroundTaskServiceProtocol) { self.backgroundTaskService = backgroundTaskService self.member = member } - - lazy var userID = member.userId() - - lazy var displayName = member.displayName() - - lazy var avatarURL = member.avatarUrl().flatMap(URL.init(string:)) - - lazy var membership = member.membership() - - lazy var isAccountOwner = member.isAccountUser() - - lazy var isIgnored = member.isIgnored() - lazy var powerLevel = Int(member.powerLevel()) - lazy var role = member.suggestedRoleForPowerLevel() - lazy var canInviteUsers = member.canInvite() - lazy var canKickUsers = member.canKick() - lazy var canBanUsers = member.canBan() + var userID: String { member.userId } + var displayName: String? { member.displayName } + var avatarURL: URL? { member.avatarUrl.flatMap(URL.init(string:)) } - func canSendStateEvent(type: StateEventType) -> Bool { - member.canSendState(stateEvent: type) - } - - func ignoreUser() async -> Result { - sendAccountDataEventBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundAccountDataTaskName, isReusable: true) - defer { - sendAccountDataEventBackgroundTask?.stop() - } - - return await Task.dispatch(on: userInitiatedDispatchQueue) { - do { - try self.member.ignore() - return .success(()) - } catch { - return .failure(.ignoreUserFailed) - } - } - } - - func unignoreUser() async -> Result { - sendAccountDataEventBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundAccountDataTaskName, isReusable: true) - defer { - sendAccountDataEventBackgroundTask?.stop() - } - - return await Task.dispatch(on: userInitiatedDispatchQueue) { - do { - try self.member.unignore() - return .success(()) - } catch { - return .failure(.unignoreUserFailed) - } - } - } + var membership: MembershipState { member.membership } + var isIgnored: Bool { member.isIgnored } + + var powerLevel: Int { Int(member.powerLevel) } + var role: RoomMemberRole { member.suggestedRoleForPowerLevel } } diff --git a/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift index e31fcd595..97dda711b 100644 --- a/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomMember/RoomMemberProxyProtocol.swift @@ -27,19 +27,12 @@ protocol RoomMemberProxyProtocol: AnyObject { var userID: String { get } var displayName: String? { get } var avatarURL: URL? { get } + var membership: MembershipState { get } - var isAccountOwner: Bool { get } var isIgnored: Bool { get } var powerLevel: Int { get } var role: RoomMemberRole { get } - var canInviteUsers: Bool { get } - var canKickUsers: Bool { get } - var canBanUsers: Bool { get } - - func ignoreUser() async -> Result - func unignoreUser() async -> Result - func canSendStateEvent(type: StateEventType) -> Bool } extension RoomMemberProxyProtocol { diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 4a57429d2..cf5ef4fee 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -233,20 +233,6 @@ class RoomProxy: RoomProxyProtocol { return .failure(.failedRetrievingMember) } } - - func ignoreUser(_ userID: String) async -> Result { - sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) - defer { - sendMessageBackgroundTask?.stop() - } - - do { - try await room.ignoreUser(userId: userID) - return .success(()) - } catch { - return .failure(.failedIgnoringUser) - } - } func leaveRoom() async -> Result { sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true) @@ -409,6 +395,24 @@ class RoomProxy: RoomProxyProtocol { } } + func canUser(userID: String, sendStateEvent event: StateEventType) async -> Result { + do { + return try await .success(room.canUserSendState(userId: userID, stateEvent: event)) + } catch { + MXLog.error("Failed checking if the user can send \(event) with error: \(error)") + return .failure(.failedCheckingPermission) + } + } + + func canUserInvite(userID: String) async -> Result { + do { + return try await .success(room.canUserInvite(userId: userID)) + } catch { + MXLog.error("Failed checking if the user can invite with error: \(error)") + return .failure(.failedCheckingPermission) + } + } + func canUserRedactOther(userID: String) async -> Result { do { return try await .success(room.canUserRedactOther(userId: userID)) @@ -427,6 +431,24 @@ class RoomProxy: RoomProxyProtocol { } } + func canUserKick(userID: String) async -> Result { + do { + return try await .success(room.canUserKick(userId: userID)) + } catch { + MXLog.error("Failed checking if the user can kick with error: \(error)") + return .failure(.failedCheckingPermission) + } + } + + func canUserBan(userID: String) async -> Result { + do { + return try await .success(room.canUserBan(userId: userID)) + } catch { + MXLog.error("Failed checking if the user can ban with error: \(error)") + return .failure(.failedCheckingPermission) + } + } + func canUserTriggerRoomNotification(userID: String) async -> Result { do { return try await .success(room.canUserTriggerRoomNotification(userId: userID)) diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 32063aeb0..e193459bd 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -82,8 +82,6 @@ protocol RoomProxyProtocol { func redact(_ eventID: String) async -> Result func reportContent(_ eventID: String, reason: String?) async -> Result - - func ignoreUser(_ userID: String) async -> Result func leaveRoom() async -> Result @@ -120,8 +118,12 @@ protocol RoomProxyProtocol { func currentPowerLevelChanges() async -> Result func applyPowerLevelChanges(_ changes: RoomPowerLevelChanges) async -> Result + func canUser(userID: String, sendStateEvent event: StateEventType) async -> Result + func canUserInvite(userID: String) async -> Result func canUserRedactOther(userID: String) async -> Result func canUserRedactOwn(userID: String) async -> Result + func canUserKick(userID: String) async -> Result + func canUserBan(userID: String) async -> Result func canUserTriggerRoomNotification(userID: String) async -> Result // MARK: - Moderation diff --git a/ElementX/Sources/UITests/UITestsAppCoordinator.swift b/ElementX/Sources/UITests/UITestsAppCoordinator.swift index a8daea857..c991d3e8b 100644 --- a/ElementX/Sources/UITests/UITestsAppCoordinator.swift +++ b/ElementX/Sources/UITests/UITestsAppCoordinator.swift @@ -586,9 +586,10 @@ class MockScreen: Identifiable { name: "Room", isEncrypted: true, members: members, - memberForID: .mockOwner(allowedStateEvents: [], canInviteUsers: false))) - let coordinator = RoomDetailsScreenCoordinator(parameters: .init(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxy, + memberForID: .mockMe, + canUserInvite: false)) + let coordinator = RoomDetailsScreenCoordinator(parameters: .init(roomProxy: roomProxy, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -607,9 +608,10 @@ class MockScreen: Identifiable { isEncrypted: true, canonicalAlias: "#mock:room.org", members: members, - memberForID: .mockOwner(allowedStateEvents: [], canInviteUsers: false))) - let coordinator = RoomDetailsScreenCoordinator(parameters: .init(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxy, + memberForID: .mockMe, + canUserInvite: false)) + let coordinator = RoomDetailsScreenCoordinator(parameters: .init(roomProxy: roomProxy, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -620,8 +622,7 @@ class MockScreen: Identifiable { return navigationStackCoordinator case .roomDetailsScreenWithEmptyTopic: let navigationStackCoordinator = NavigationStackCoordinator() - let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: [.roomTopic], canInviteUsers: false) - let members: [RoomMemberProxyMock] = [owner, .mockBob, .mockCharlie] + let members: [RoomMemberProxyMock] = [.mockMeAdmin, .mockBob, .mockCharlie] let roomProxy = RoomProxyMock(with: .init(id: "MockRoomIdentifier", name: "Room", topic: nil, @@ -630,9 +631,10 @@ class MockScreen: Identifiable { isEncrypted: true, canonicalAlias: "#mock:room.org", members: members, - memberForID: owner)) - let coordinator = RoomDetailsScreenCoordinator(parameters: .init(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxy, + memberForID: .mockMeAdmin, + canUserInvite: false)) + let coordinator = RoomDetailsScreenCoordinator(parameters: .init(roomProxy: roomProxy, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -643,15 +645,16 @@ class MockScreen: Identifiable { return navigationStackCoordinator case .roomDetailsScreenWithInvite: let navigationStackCoordinator = NavigationStackCoordinator() - let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: [], canInviteUsers: true) + let owner: RoomMemberProxyMock = .mockMe let members: [RoomMemberProxyMock] = [owner, .mockBob, .mockCharlie] let roomProxy = RoomProxyMock(with: .init(id: "MockRoomIdentifier", name: "Room", isEncrypted: true, members: members, - memberForID: owner)) - let coordinator = RoomDetailsScreenCoordinator(parameters: .init(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxy, + memberForID: owner, + canUserInvite: true)) + let coordinator = RoomDetailsScreenCoordinator(parameters: .init(roomProxy: roomProxy, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -669,9 +672,10 @@ class MockScreen: Identifiable { isDirect: true, isEncrypted: true, members: members, - memberForID: .mockOwner(allowedStateEvents: [], canInviteUsers: false))) - let coordinator = RoomDetailsScreenCoordinator(parameters: .init(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxy, + memberForID: .mockMe, + canUserInvite: false)) + let coordinator = RoomDetailsScreenCoordinator(parameters: .init(roomProxy: roomProxy, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -681,16 +685,16 @@ class MockScreen: Identifiable { navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator case .roomEditDetails, .roomEditDetailsReadOnly: - let allowedStateEvents: [StateEventType] = id == .roomEditDetails ? [.roomAvatar, .roomName, .roomTopic] : [] + let members: [RoomMemberProxyMock] = id == .roomEditDetails ? [.mockMeAdmin] : [.mockMe] let navigationStackCoordinator = NavigationStackCoordinator() let roomProxy = RoomProxyMock(with: .init(id: "MockRoomIdentifier", name: "Room", topic: "What a cool topic!", - avatarURL: .picturesDirectory)) - let coordinator = RoomDetailsEditScreenCoordinator(parameters: .init(accountOwner: RoomMemberProxyMock.mockOwner(allowedStateEvents: allowedStateEvents), + avatarURL: .picturesDirectory, + members: members)) + let coordinator = RoomDetailsEditScreenCoordinator(parameters: .init(roomProxy: roomProxy, mediaProvider: MockMediaProvider(), navigationStackCoordinator: navigationStackCoordinator, - roomProxy: roomProxy, userIndicatorController: ServiceLocator.shared.userIndicatorController, orientationManager: OrientationManagerMock())) navigationStackCoordinator.setRootCoordinator(coordinator) @@ -734,6 +738,7 @@ class MockScreen: Identifiable { let coordinator = ReportContentScreenCoordinator(parameters: .init(eventID: "test", senderID: RoomMemberProxyMock.mockAlice.userID, roomProxy: RoomProxyMock(with: .init(name: "test")), + clientProxy: ClientProxyMock(.init()), userIndicatorController: UserIndicatorControllerMock())) navigationStackCoordinator.setRootCoordinator(coordinator) return navigationStackCoordinator @@ -774,8 +779,9 @@ class MockScreen: Identifiable { roomProxy.getMemberUserIDReturnValue = .success(member) let navigationStackCoordinator = NavigationStackCoordinator() - let coordinator = RoomMemberDetailsScreenCoordinator(parameters: .init(roomProxy: roomProxy, - userID: member.userID, + let coordinator = RoomMemberDetailsScreenCoordinator(parameters: .init(userID: member.userID, + roomProxy: roomProxy, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), userIndicatorController: ServiceLocator.shared.userIndicatorController)) navigationStackCoordinator.setRootCoordinator(coordinator) @@ -786,8 +792,9 @@ class MockScreen: Identifiable { roomProxy.getMemberUserIDReturnValue = .success(member) let navigationStackCoordinator = NavigationStackCoordinator() - let coordinator = RoomMemberDetailsScreenCoordinator(parameters: .init(roomProxy: roomProxy, - userID: member.userID, + let coordinator = RoomMemberDetailsScreenCoordinator(parameters: .init(userID: member.userID, + roomProxy: roomProxy, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), userIndicatorController: ServiceLocator.shared.userIndicatorController)) navigationStackCoordinator.setRootCoordinator(coordinator) @@ -798,8 +805,9 @@ class MockScreen: Identifiable { roomProxy.getMemberUserIDReturnValue = .success(member) let navigationStackCoordinator = NavigationStackCoordinator() - let coordinator = RoomMemberDetailsScreenCoordinator(parameters: .init(roomProxy: roomProxy, - userID: member.userID, + let coordinator = RoomMemberDetailsScreenCoordinator(parameters: .init(userID: member.userID, + roomProxy: roomProxy, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), userIndicatorController: ServiceLocator.shared.userIndicatorController)) navigationStackCoordinator.setRootCoordinator(coordinator) diff --git a/UnitTests/Sources/CompletionSuggestionServiceTests.swift b/UnitTests/Sources/CompletionSuggestionServiceTests.swift index 528ced2e9..28573ec2b 100644 --- a/UnitTests/Sources/CompletionSuggestionServiceTests.swift +++ b/UnitTests/Sources/CompletionSuggestionServiceTests.swift @@ -27,7 +27,7 @@ final class CompletionSuggestionServiceTests: XCTestCase { cancellables.removeAll() } - func testUserSuggestons() async throws { + func testUserSuggestions() async throws { let alice: RoomMemberProxyMock = .mockAlice let members: [RoomMemberProxyMock] = [alice, .mockBob, .mockCharlie, .mockMe] let roomProxyMock = RoomProxyMock(with: .init(name: "test", members: members)) @@ -64,7 +64,7 @@ final class CompletionSuggestionServiceTests: XCTestCase { try await deferred.fulfill() } - func testUserSuggestonsIncludingAllUsers() async throws { + func testUserSuggestionsIncludingAllUsers() async throws { let alice: RoomMemberProxyMock = .mockAlice let members: [RoomMemberProxyMock] = [alice, .mockBob, .mockCharlie, .mockMe] let roomProxyMock = RoomProxyMock(with: .init(name: "test", members: members, canUserTriggerRoomNotification: true)) diff --git a/UnitTests/Sources/ReportContentViewModelTests.swift b/UnitTests/Sources/ReportContentViewModelTests.swift index 07be0305e..ab77315a1 100644 --- a/UnitTests/Sources/ReportContentViewModelTests.swift +++ b/UnitTests/Sources/ReportContentViewModelTests.swift @@ -27,9 +27,11 @@ class ReportContentScreenViewModelTests: XCTestCase { // Given the report content view for some content. let roomProxy = RoomProxyMock(with: .init(name: "test")) roomProxy.reportContentReasonReturnValue = .success(()) + let clientProxy = ClientProxyMock(.init()) let viewModel = ReportContentScreenViewModel(eventID: eventID, senderID: senderID, - roomProxy: roomProxy) + roomProxy: roomProxy, + clientProxy: clientProxy) // When reporting the content without ignoring the user. viewModel.state.bindings.reasonText = reportReason @@ -46,18 +48,19 @@ class ReportContentScreenViewModelTests: XCTestCase { XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.") XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.eventID, eventID, "The event ID should match the content being reported.") XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.reason, reportReason, "The reason should match the user input.") - XCTAssertEqual(roomProxy.ignoreUserCallsCount, 0, "A call to ignore a user should not have been made.") - XCTAssertNil(roomProxy.ignoreUserReceivedUserID, "The sender shouldn't have been ignored.") + XCTAssertEqual(clientProxy.ignoreUserCallsCount, 0, "A call to ignore a user should not have been made.") + XCTAssertNil(clientProxy.ignoreUserReceivedUserID, "The sender shouldn't have been ignored.") } func testReportIgnoringSender() async throws { // Given the report content view for some content. let roomProxy = RoomProxyMock(with: .init(name: "test")) roomProxy.reportContentReasonReturnValue = .success(()) - roomProxy.ignoreUserReturnValue = .success(()) + let clientProxy = ClientProxyMock(.init()) let viewModel = ReportContentScreenViewModel(eventID: eventID, senderID: senderID, - roomProxy: roomProxy) + roomProxy: roomProxy, + clientProxy: clientProxy) // When reporting the content and also ignoring the user. viewModel.state.bindings.reasonText = reportReason @@ -75,7 +78,7 @@ class ReportContentScreenViewModelTests: XCTestCase { XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.") XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.eventID, eventID, "The event ID should match the content being reported.") XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.reason, reportReason, "The reason should match the user input.") - XCTAssertEqual(roomProxy.ignoreUserCallsCount, 1, "A call should have been made to ignore the sender.") - XCTAssertEqual(roomProxy.ignoreUserReceivedUserID, senderID, "The ignored user ID should match the sender.") + XCTAssertEqual(clientProxy.ignoreUserCallsCount, 1, "A call should have been made to ignore the sender.") + XCTAssertEqual(clientProxy.ignoreUserReceivedUserID, senderID, "The ignored user ID should match the sender.") } } diff --git a/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift b/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift index 574f39324..b29d37008 100644 --- a/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsEditScreenViewModelTests.swift @@ -30,61 +30,57 @@ class RoomDetailsEditScreenViewModelTests: XCTestCase { } func testCannotSaveOnLanding() { - setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), - roomProxyConfiguration: .init(name: "Some room")) + setupViewModel(roomProxyConfiguration: .init(name: "Some room", members: [.mockMeAdmin])) XCTAssertFalse(context.viewState.canSave) } - func testCanEdit() { - setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), - roomProxyConfiguration: .init(name: "Some room")) + func testCanEdit() async throws { + setupViewModel(roomProxyConfiguration: .init(name: "Some room", members: [.mockMeAdmin])) + + let deferred = deferFulfillment(context.$viewState) { $0.canEditName } + try await deferred.fulfill() + XCTAssertTrue(context.viewState.canEditAvatar) XCTAssertTrue(context.viewState.canEditName) XCTAssertTrue(context.viewState.canEditTopic) } func testCannotEdit() { - setupViewModel(accountOwner: .mockOwner(allowedStateEvents: []), - roomProxyConfiguration: .init(name: "Some room")) + setupViewModel(roomProxyConfiguration: .init(name: "Some room", members: [.mockMe])) XCTAssertFalse(context.viewState.canEditAvatar) XCTAssertFalse(context.viewState.canEditName) XCTAssertFalse(context.viewState.canEditTopic) } func testNameDidChange() { - setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), - roomProxyConfiguration: .init(name: "Some room")) + setupViewModel(roomProxyConfiguration: .init(name: "Some room", members: [.mockMeAdmin])) context.name = "name" XCTAssertTrue(context.viewState.nameDidChange) XCTAssertTrue(context.viewState.canSave) } func testTopicDidChange() { - setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), - roomProxyConfiguration: .init(name: "Some room")) + setupViewModel(roomProxyConfiguration: .init(name: "Some room", members: [.mockMeAdmin])) context.topic = "topic" XCTAssertTrue(context.viewState.topicDidChange) XCTAssertTrue(context.viewState.canSave) } func testAvatarDidChange() { - setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), - roomProxyConfiguration: .init(name: "Some room", avatarURL: .picturesDirectory)) + setupViewModel(roomProxyConfiguration: .init(name: "Some room", avatarURL: .picturesDirectory, members: [.mockMeAdmin])) context.send(viewAction: .removeImage) XCTAssertTrue(context.viewState.avatarDidChange) XCTAssertTrue(context.viewState.canSave) } func testEmptyNameCannotBeSaved() { - setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), - roomProxyConfiguration: .init(name: "Some room")) + setupViewModel(roomProxyConfiguration: .init(name: "Some room", members: [.mockMeAdmin])) context.name = "" XCTAssertFalse(context.viewState.canSave) } func testSaveShowsSheet() { - setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), - roomProxyConfiguration: .init(name: "Some room")) + setupViewModel(roomProxyConfiguration: .init(name: "Some room", members: [.mockMeAdmin])) context.name = "name" XCTAssertFalse(context.showMediaSheet) context.send(viewAction: .presentMediaSource) @@ -92,8 +88,7 @@ class RoomDetailsEditScreenViewModelTests: XCTestCase { } func testSaveTriggersViewModelAction() async throws { - setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), - roomProxyConfiguration: .init(name: "Some room")) + setupViewModel(roomProxyConfiguration: .init(name: "Some room", members: [.mockMeAdmin])) let deferred = deferFulfillment(viewModel.actions) { action in action == .saveFinished @@ -107,16 +102,14 @@ class RoomDetailsEditScreenViewModelTests: XCTestCase { } func testErrorShownOnFailedFetchOfMedia() async throws { - setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), - roomProxyConfiguration: .init(name: "Some room")) + setupViewModel(roomProxyConfiguration: .init(name: "Some room", members: [.mockMeAdmin])) viewModel.didSelectMediaUrl(url: .picturesDirectory) try? await Task.sleep(for: .milliseconds(100)) XCTAssertNotNil(userIndicatorController.alertInfo) } func testDeleteAvatar() { - setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), - roomProxyConfiguration: .init(name: "Some room", avatarURL: .picturesDirectory)) + setupViewModel(roomProxyConfiguration: .init(name: "Some room", avatarURL: .picturesDirectory, members: [.mockMeAdmin])) XCTAssertNotNil(context.viewState.avatarURL) context.send(viewAction: .removeImage) XCTAssertNil(context.viewState.avatarURL) @@ -124,11 +117,10 @@ class RoomDetailsEditScreenViewModelTests: XCTestCase { // MARK: - Private - private func setupViewModel(accountOwner: RoomMemberProxyMock, roomProxyConfiguration: RoomProxyMockConfiguration) { + private func setupViewModel(roomProxyConfiguration: RoomProxyMockConfiguration) { userIndicatorController = UserIndicatorControllerMock.default - viewModel = .init(accountOwner: accountOwner, + viewModel = .init(roomProxy: RoomProxyMock(with: roomProxyConfiguration), mediaProvider: MockMediaProvider(), - roomProxy: RoomProxyMock(with: roomProxyConfiguration), userIndicatorController: userIndicatorController) } } diff --git a/UnitTests/Sources/RoomDetailsViewModelTests.swift b/UnitTests/Sources/RoomDetailsViewModelTests.swift index f8a94f71b..085d1d98d 100644 --- a/UnitTests/Sources/RoomDetailsViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsViewModelTests.swift @@ -33,8 +33,8 @@ class RoomDetailsScreenViewModelTests: XCTestCase { cancellables.removeAll() roomProxyMock = RoomProxyMock(with: .init(name: "Test")) notificationSettingsProxyMock = NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -48,8 +48,8 @@ class RoomDetailsScreenViewModelTests: XCTestCase { func testLeaveRoomTappedWhenPublic() async throws { let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(name: "Test", isPublic: true, members: mockedMembers)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -70,8 +70,8 @@ class RoomDetailsScreenViewModelTests: XCTestCase { func testLeaveRoomTappedWhenRoomNotPublic() async throws { let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(name: "Test", isPublic: false, members: mockedMembers)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -93,8 +93,8 @@ class RoomDetailsScreenViewModelTests: XCTestCase { func testLeaveRoomTappedWithLessThanTwoMembers() async { let mockedMembers: [RoomMemberProxyMock] = [.mockAlice] roomProxyMock = RoomProxyMock(with: .init(name: "Test", isPublic: false, members: mockedMembers)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -146,8 +146,8 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let recipient = RoomMemberProxyMock.mockDan let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -166,15 +166,11 @@ class RoomDetailsScreenViewModelTests: XCTestCase { func testIgnoreSuccess() async throws { let recipient = RoomMemberProxyMock.mockDan - recipient.ignoreUserClosure = { - try? await Task.sleep(for: .milliseconds(100)) - return .success(()) - } let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -203,14 +199,12 @@ class RoomDetailsScreenViewModelTests: XCTestCase { func testIgnoreFailure() async throws { let recipient = RoomMemberProxyMock.mockDan - recipient.ignoreUserClosure = { - try? await Task.sleep(for: .milliseconds(100)) - return .failure(.ignoreUserFailed) - } let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] + let clientProxy = ClientProxyMock(.init()) + clientProxy.ignoreUserReturnValue = .failure(.failedIgnoringUser) roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: clientProxy, mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -240,14 +234,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { func testUnignoreSuccess() async throws { let recipient = RoomMemberProxyMock.mockIgnored - recipient.unignoreUserClosure = { - try? await Task.sleep(for: .milliseconds(100)) - return .success(()) - } let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -276,14 +266,12 @@ class RoomDetailsScreenViewModelTests: XCTestCase { func testUnignoreFailure() async throws { let recipient = RoomMemberProxyMock.mockIgnored - recipient.unignoreUserClosure = { - try? await Task.sleep(for: .milliseconds(100)) - return .failure(.unignoreUserFailed) - } let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] + let clientProxy = ClientProxyMock(.init()) + clientProxy.unignoreUserReturnValue = .failure(.failedUnignoringUser) roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: clientProxy, mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -312,13 +300,13 @@ class RoomDetailsScreenViewModelTests: XCTestCase { } func testCannotInvitePeople() async { - let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice] + let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(name: "Test", isPublic: true, members: mockedMembers, - memberForID: .mockOwner(allowedStateEvents: [], canInviteUsers: false))) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + canUserInvite: false)) + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -334,8 +322,8 @@ class RoomDetailsScreenViewModelTests: XCTestCase { func testInvitePeople() async { let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(name: "Test", isPublic: true, members: mockedMembers)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -365,11 +353,13 @@ class RoomDetailsScreenViewModelTests: XCTestCase { } func testCanEditAvatar() async { - let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: [.roomAvatar]) - let mockedMembers: [RoomMemberProxyMock] = [owner, .mockBob, .mockAlice] - roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: false, isPublic: false, members: mockedMembers, memberForID: owner)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice] + roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: false, isPublic: false, members: mockedMembers)) + roomProxyMock.canUserUserIDSendStateEventClosure = { _, event in + .success(event == .roomAvatar) + } + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -386,11 +376,13 @@ class RoomDetailsScreenViewModelTests: XCTestCase { } func testCanEditName() async { - let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: [.roomName]) - let mockedMembers: [RoomMemberProxyMock] = [owner, .mockBob, .mockAlice] - roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: false, isPublic: false, members: mockedMembers, memberForID: owner)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice] + roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: false, isPublic: false, members: mockedMembers)) + roomProxyMock.canUserUserIDSendStateEventClosure = { _, event in + .success(event == .roomName) + } + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -407,11 +399,13 @@ class RoomDetailsScreenViewModelTests: XCTestCase { } func testCanEditTopic() async { - let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: [.roomTopic]) - let mockedMembers: [RoomMemberProxyMock] = [owner, .mockBob, .mockAlice] - roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: false, isPublic: false, members: mockedMembers, memberForID: owner)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice] + roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: false, isPublic: false, members: mockedMembers)) + roomProxyMock.canUserUserIDSendStateEventClosure = { _, event in + .success(event == .roomTopic) + } + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -428,11 +422,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { } func testCannotEditRoom() async { - let owner: RoomMemberProxyMock = .mockOwner(allowedStateEvents: []) - let mockedMembers: [RoomMemberProxyMock] = [owner, .mockBob, .mockAlice] - roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: false, isPublic: false, members: mockedMembers, memberForID: owner)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + let mockedMembers: [RoomMemberProxyMock] = [.mockMe, .mockBob, .mockAlice] + roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: false, isPublic: false, members: mockedMembers)) + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -449,10 +442,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase { } func testCannotEditDirectRoom() async { - let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), .mockBob, .mockAlice] + let mockedMembers: [RoomMemberProxyMock] = [.mockMeAdmin, .mockBob, .mockAlice] roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: true, isPublic: false, members: mockedMembers)) - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, @@ -469,8 +462,8 @@ class RoomDetailsScreenViewModelTests: XCTestCase { func testNotificationLoadingSettingsFailure() async throws { notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneThrowableError = NotificationSettingsError.Generic(msg: "error") - viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com", - roomProxy: roomProxyMock, + viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), analyticsService: ServiceLocator.shared.analytics, userIndicatorController: ServiceLocator.shared.userIndicatorController, diff --git a/UnitTests/Sources/RoomMemberDetailsViewModelTests.swift b/UnitTests/Sources/RoomMemberDetailsViewModelTests.swift index 5d9a4155f..5fe40d5a1 100644 --- a/UnitTests/Sources/RoomMemberDetailsViewModelTests.swift +++ b/UnitTests/Sources/RoomMemberDetailsViewModelTests.swift @@ -35,8 +35,9 @@ class RoomMemberDetailsViewModelTests: XCTestCase { func testInitialState() async throws { roomMemberProxyMock = RoomMemberProxyMock.mockAlice - viewModel = RoomMemberDetailsScreenViewModel(roomProxy: roomProxyMock, - userID: roomMemberProxyMock.userID, + viewModel = RoomMemberDetailsScreenViewModel(userID: roomMemberProxyMock.userID, + roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), userIndicatorController: ServiceLocator.shared.userIndicatorController) @@ -50,12 +51,9 @@ class RoomMemberDetailsViewModelTests: XCTestCase { func testIgnoreSuccess() async throws { roomMemberProxyMock = RoomMemberProxyMock.mockAlice - roomMemberProxyMock.ignoreUserClosure = { - try? await Task.sleep(for: .milliseconds(100)) - return .success(()) - } - viewModel = RoomMemberDetailsScreenViewModel(roomProxy: roomProxyMock, - userID: roomMemberProxyMock.userID, + viewModel = RoomMemberDetailsScreenViewModel(userID: roomMemberProxyMock.userID, + roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), userIndicatorController: ServiceLocator.shared.userIndicatorController) @@ -88,12 +86,11 @@ class RoomMemberDetailsViewModelTests: XCTestCase { func testIgnoreFailure() async throws { roomMemberProxyMock = RoomMemberProxyMock.mockAlice - roomMemberProxyMock.ignoreUserClosure = { - try? await Task.sleep(for: .milliseconds(100)) - return .failure(.ignoreUserFailed) - } - viewModel = RoomMemberDetailsScreenViewModel(roomProxy: roomProxyMock, - userID: roomMemberProxyMock.userID, + let clientProxy = ClientProxyMock(.init()) + clientProxy.ignoreUserReturnValue = .failure(.failedIgnoringUser) + viewModel = RoomMemberDetailsScreenViewModel(userID: roomMemberProxyMock.userID, + roomProxy: roomProxyMock, + clientProxy: clientProxy, mediaProvider: MockMediaProvider(), userIndicatorController: ServiceLocator.shared.userIndicatorController) @@ -126,13 +123,10 @@ class RoomMemberDetailsViewModelTests: XCTestCase { func testUnignoreSuccess() async throws { roomMemberProxyMock = RoomMemberProxyMock.mockIgnored - roomMemberProxyMock.unignoreUserClosure = { - try? await Task.sleep(for: .milliseconds(100)) - return .success(()) - } - viewModel = RoomMemberDetailsScreenViewModel(roomProxy: roomProxyMock, - userID: roomMemberProxyMock.userID, + viewModel = RoomMemberDetailsScreenViewModel(userID: roomMemberProxyMock.userID, + roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), userIndicatorController: ServiceLocator.shared.userIndicatorController) @@ -163,12 +157,11 @@ class RoomMemberDetailsViewModelTests: XCTestCase { func testUnignoreFailure() async throws { roomMemberProxyMock = RoomMemberProxyMock.mockIgnored - roomMemberProxyMock.unignoreUserClosure = { - try? await Task.sleep(for: .milliseconds(100)) - return .failure(.unignoreUserFailed) - } - viewModel = RoomMemberDetailsScreenViewModel(roomProxy: roomProxyMock, - userID: roomMemberProxyMock.userID, + let clientProxy = ClientProxyMock(.init()) + clientProxy.unignoreUserReturnValue = .failure(.failedUnignoringUser) + viewModel = RoomMemberDetailsScreenViewModel(userID: roomMemberProxyMock.userID, + roomProxy: roomProxyMock, + clientProxy: clientProxy, mediaProvider: MockMediaProvider(), userIndicatorController: ServiceLocator.shared.userIndicatorController) @@ -201,8 +194,9 @@ class RoomMemberDetailsViewModelTests: XCTestCase { func testInitialStateAccountOwner() async throws { roomMemberProxyMock = RoomMemberProxyMock.mockMe - viewModel = RoomMemberDetailsScreenViewModel(roomProxy: roomProxyMock, - userID: roomMemberProxyMock.userID, + viewModel = RoomMemberDetailsScreenViewModel(userID: roomMemberProxyMock.userID, + roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), userIndicatorController: ServiceLocator.shared.userIndicatorController) @@ -216,8 +210,9 @@ class RoomMemberDetailsViewModelTests: XCTestCase { func testInitialStateIgnoredUser() async throws { roomMemberProxyMock = RoomMemberProxyMock.mockIgnored - viewModel = RoomMemberDetailsScreenViewModel(roomProxy: roomProxyMock, - userID: roomMemberProxyMock.userID, + viewModel = RoomMemberDetailsScreenViewModel(userID: roomMemberProxyMock.userID, + roomProxy: roomProxyMock, + clientProxy: ClientProxyMock(.init()), mediaProvider: MockMediaProvider(), userIndicatorController: ServiceLocator.shared.userIndicatorController) diff --git a/project.yml b/project.yml index aeb47e765..d811961df 100644 --- a/project.yml +++ b/project.yml @@ -48,7 +48,7 @@ packages: # Element/Matrix dependencies MatrixRustSDK: url: https://github.com/matrix-org/matrix-rust-components-swift - exactVersion: 1.1.44 + exactVersion: 1.1.45 # path: ../matrix-rust-sdk Compound: url: https://github.com/element-hq/compound-ios