Update the SDK and handle breaking changes. (#2527)

This commit is contained in:
Doug
2024-03-05 14:08:34 +00:00
committed by GitHub
parent 4320e5c9e0
commit 1b5db6c63d
39 changed files with 455 additions and 504 deletions
+1 -1
View File
@@ -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" */ = {
@@ -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"
}
},
{
@@ -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
+3 -3
View File
@@ -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
@@ -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<Void, RoomMemberProxyError>!
var ignoreUserClosure: (() async -> Result<Void, RoomMemberProxyError>)?
func ignoreUser() async -> Result<Void, RoomMemberProxyError> {
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<Void, RoomMemberProxyError>!
var unignoreUserClosure: (() async -> Result<Void, RoomMemberProxyError>)?
func unignoreUser() async -> Result<Void, RoomMemberProxyError> {
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<Void, RoomProxyError>!
var ignoreUserClosure: ((String) async -> Result<Void, RoomProxyError>)?
func ignoreUser(_ userID: String) async -> Result<Void, RoomProxyError> {
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<Bool, RoomProxyError>!
var canUserUserIDSendStateEventClosure: ((String, StateEventType) async -> Result<Bool, RoomProxyError>)?
func canUser(userID: String, sendStateEvent event: StateEventType) async -> Result<Bool, RoomProxyError> {
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<Bool, RoomProxyError>!
var canUserInviteUserIDClosure: ((String) async -> Result<Bool, RoomProxyError>)?
func canUserInvite(userID: String) async -> Result<Bool, RoomProxyError> {
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<Bool, RoomProxyError>!
var canUserKickUserIDClosure: ((String) async -> Result<Bool, RoomProxyError>)?
func canUserKick(userID: String) async -> Result<Bool, RoomProxyError> {
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<Bool, RoomProxyError>!
var canUserBanUserIDClosure: ((String) async -> Result<Bool, RoomProxyError>)?
func canUserBan(userID: String) async -> Result<Bool, RoomProxyError> {
canUserBanUserIDCallsCount += 1
canUserBanUserIDReceivedUserID = userID
canUserBanUserIDReceivedInvocations.append(userID)
if let canUserBanUserIDClosure = canUserBanUserIDClosure {
return await canUserBanUserIDClosure(userID)
} else {
return canUserBanUserIDReturnValue
}
}
//MARK: - canUserTriggerRoomNotification
var canUserTriggerRoomNotificationUserIDCallsCount = 0
@@ -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",
+13 -2
View File
@@ -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)
@@ -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
@@ -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)
@@ -20,7 +20,7 @@ enum ReportContentScreenViewModelAction: Equatable {
case cancel
case submitStarted
case submitFinished
case submitFailed(error: RoomProxyError)
case submitFailed(message: String)
}
struct ReportContentScreenViewState: BindableState {
@@ -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<ReportContentScreenViewModelAction, Never> = .init()
var actions: AnyPublisher<ReportContentScreenViewModelAction, Never> {
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
}
@@ -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 {
@@ -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)
}
@@ -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?
@@ -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
@@ -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)
}
}
@@ -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:
@@ -26,7 +26,7 @@ enum RoomDetailsScreenViewModelAction {
case requestMemberDetailsPresentation
case requestInvitePeoplePresentation
case leftRoom
case requestEditDetailsPresentation(RoomMemberProxyProtocol)
case requestEditDetailsPresentation
case requestPollsHistoryPresentation
}
@@ -20,18 +20,14 @@ import SwiftUI
typealias RoomDetailsScreenViewModelType = StateStoreViewModel<RoomDetailsScreenViewState, RoomDetailsScreenViewAction>
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<RoomDetailsScreenViewModelAction, Never> = .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)
}
}
@@ -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,
@@ -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)
}
@@ -23,6 +23,7 @@ enum RoomMemberDetailsScreenViewModelAction {
struct RoomMemberDetailsScreenViewState: BindableState {
let userID: String
var memberDetails: RoomMemberDetails?
var isOwnMemberDetails = false
var isProcessingIgnoreRequest = false
var bindings: RoomMemberDetailsScreenViewStateBindings
@@ -21,6 +21,7 @@ typealias RoomMemberDetailsScreenViewModelType = StateStoreViewModel<RoomMemberD
class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, RoomMemberDetailsScreenViewModelProtocol {
private let roomProxy: RoomProxyProtocol
private let clientProxy: ClientProxyProtocol
private let mediaProvider: MediaProviderProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
@@ -32,11 +33,13 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
actionsSubject.eraseToAnyPublisher()
}
init(roomProxy: RoomProxyProtocol,
userID: String,
init(userID: String,
roomProxy: RoomProxyProtocol,
clientProxy: ClientProxyProtocol,
mediaProvider: MediaProviderProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
self.roomProxy = roomProxy
self.clientProxy = clientProxy
self.mediaProvider = mediaProvider
self.userIndicatorController = userIndicatorController
@@ -54,6 +57,7 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
case .success(let member):
roomMemberProxy = member
state.memberDetails = RoomMemberDetails(withProxy: member)
state.isOwnMemberDetails = member.userID == roomProxy.ownUserID
case .failure(let error):
state.bindings.alertInfo = .init(id: .unknown)
MXLog.error("[RoomFlowCoordinator] Failed to get member: \(error)")
@@ -100,7 +104,7 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
}
state.isProcessingIgnoreRequest = true
let result = await roomMemberProxy.ignoreUser()
let result = await clientProxy.ignoreUser(roomMemberProxy.userID)
state.isProcessingIgnoreRequest = false
switch result {
case .success:
@@ -118,7 +122,7 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
}
state.isProcessingIgnoreRequest = true
let result = await roomMemberProxy.unignoreUser()
let result = await clientProxy.unignoreUser(roomMemberProxy.userID)
state.isProcessingIgnoreRequest = false
switch result {
case .success:
@@ -36,8 +36,7 @@ struct RoomMemberDetailsScreen: View {
Form {
headerSection
if let memberDetails = context.viewState.memberDetails,
!memberDetails.isAccountOwner {
if context.viewState.memberDetails != nil, !context.viewState.isOwnMemberDetails {
directChatSection
blockUserSection
}
@@ -124,8 +123,9 @@ struct RoomMemberDetailsScreen_Previews: PreviewProvider, TestablePreview {
let roomProxyMock = RoomProxyMock(with: .init(name: ""))
roomProxyMock.getMemberUserIDReturnValue = .success(member)
return RoomMemberDetailsScreenViewModel(roomProxy: roomProxyMock,
userID: member.userID,
return RoomMemberDetailsScreenViewModel(userID: member.userID,
roomProxy: roomProxyMock,
clientProxy: ClientProxyMock(.init()),
mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController)
}()
@@ -135,8 +135,9 @@ struct RoomMemberDetailsScreen_Previews: PreviewProvider, TestablePreview {
let roomProxyMock = RoomProxyMock(with: .init(name: ""))
roomProxyMock.getMemberUserIDReturnValue = .success(member)
return RoomMemberDetailsScreenViewModel(roomProxy: roomProxyMock,
userID: member.userID,
return RoomMemberDetailsScreenViewModel(userID: member.userID,
roomProxy: roomProxyMock,
clientProxy: ClientProxyMock(.init()),
mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController)
}()
@@ -146,8 +147,9 @@ struct RoomMemberDetailsScreen_Previews: PreviewProvider, TestablePreview {
let roomProxyMock = RoomProxyMock(with: .init(name: ""))
roomProxyMock.getMemberUserIDReturnValue = .success(member)
return RoomMemberDetailsScreenViewModel(roomProxy: roomProxyMock,
userID: member.userID,
return RoomMemberDetailsScreenViewModel(userID: member.userID,
roomProxy: roomProxyMock,
clientProxy: ClientProxyMock(.init()),
mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController)
}()
@@ -106,11 +106,14 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
invitedMembers: roomMembersDetails.invitedMembers,
bannedMembers: roomMembersDetails.bannedMembers,
bindings: state.bindings)
if let accountOwner = roomMembersDetails.accountOwner {
self.state.canInviteUsers = accountOwner.canInviteUsers
self.state.canKickUsers = accountOwner.canKickUsers
self.state.canBanUsers = accountOwner.canBanUsers
}
async let canInviteUsers = roomProxy.canUserInvite(userID: roomProxy.ownUserID)
async let canKickUsers = roomProxy.canUserKick(userID: roomProxy.ownUserID)
async let canBanUsers = roomProxy.canUserBan(userID: roomProxy.ownUserID)
self.state.canInviteUsers = await canInviteUsers == .success(true)
self.state.canKickUsers = await canKickUsers == .success(true)
self.state.canBanUsers = await canBanUsers == .success(true)
if state.bindings.mode == .banned, roomMembersDetails.bannedMembers.isEmpty {
state.bindings.mode = .members
}
@@ -125,13 +128,8 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
var invitedMembers: [RoomMemberDetails] = .init()
var joinedMembers: [RoomMemberDetails] = .init()
var bannedMembers: [RoomMemberDetails] = .init()
var accountOwner: RoomMemberProxyProtocol?
for member in members {
if accountOwner == nil, member.isAccountOwner {
accountOwner = member
}
switch member.membership {
case .invite:
invitedMembers.append(.init(withProxy: member))
@@ -146,8 +144,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
return .init(invitedMembers: invitedMembers,
joinedMembers: joinedMembers,
bannedMembers: bannedMembers.sorted { $0.id.localizedStandardCompare($1.id) == .orderedAscending }, // Re-sort ignoring display name.
accountOwner: accountOwner)
bannedMembers: bannedMembers.sorted { $0.id.localizedStandardCompare($1.id) == .orderedAscending }) // Re-sort ignoring display name.
}
.value
}
@@ -257,5 +254,4 @@ private struct RoomMembersDetails {
var invitedMembers: [RoomMemberDetails]
var joinedMembers: [RoomMemberDetails]
var bannedMembers: [RoomMemberDetails]
var accountOwner: RoomMemberProxyProtocol?
}
@@ -142,10 +142,7 @@ struct RoomMembersListScreen_Previews: PreviewProvider, TestablePreview {
initialMode: RoomMembersListScreenMode = .members) -> 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)
@@ -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
@@ -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
@@ -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<Void, RoomMemberProxyError> {
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<Void, RoomMemberProxyError> {
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 }
}
@@ -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<Void, RoomMemberProxyError>
func unignoreUser() async -> Result<Void, RoomMemberProxyError>
func canSendStateEvent(type: StateEventType) -> Bool
}
extension RoomMemberProxyProtocol {
+36 -14
View File
@@ -233,20 +233,6 @@ class RoomProxy: RoomProxyProtocol {
return .failure(.failedRetrievingMember)
}
}
func ignoreUser(_ userID: String) async -> Result<Void, RoomProxyError> {
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<Void, RoomProxyError> {
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
@@ -409,6 +395,24 @@ class RoomProxy: RoomProxyProtocol {
}
}
func canUser(userID: String, sendStateEvent event: StateEventType) async -> Result<Bool, RoomProxyError> {
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<Bool, RoomProxyError> {
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<Bool, RoomProxyError> {
do {
return try await .success(room.canUserRedactOther(userId: userID))
@@ -427,6 +431,24 @@ class RoomProxy: RoomProxyProtocol {
}
}
func canUserKick(userID: String) async -> Result<Bool, RoomProxyError> {
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<Bool, RoomProxyError> {
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<Bool, RoomProxyError> {
do {
return try await .success(room.canUserTriggerRoomNotification(userId: userID))
@@ -82,8 +82,6 @@ protocol RoomProxyProtocol {
func redact(_ eventID: String) async -> Result<Void, RoomProxyError>
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError>
func ignoreUser(_ userID: String) async -> Result<Void, RoomProxyError>
func leaveRoom() async -> Result<Void, RoomProxyError>
@@ -120,8 +118,12 @@ protocol RoomProxyProtocol {
func currentPowerLevelChanges() async -> Result<RoomPowerLevelChanges, RoomProxyError>
func applyPowerLevelChanges(_ changes: RoomPowerLevelChanges) async -> Result<Void, RoomProxyError>
func canUser(userID: String, sendStateEvent event: StateEventType) async -> Result<Bool, RoomProxyError>
func canUserInvite(userID: String) async -> Result<Bool, RoomProxyError>
func canUserRedactOther(userID: String) async -> Result<Bool, RoomProxyError>
func canUserRedactOwn(userID: String) async -> Result<Bool, RoomProxyError>
func canUserKick(userID: String) async -> Result<Bool, RoomProxyError>
func canUserBan(userID: String) async -> Result<Bool, RoomProxyError>
func canUserTriggerRoomNotification(userID: String) async -> Result<Bool, RoomProxyError>
// MARK: - Moderation
@@ -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)
@@ -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))
@@ -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.")
}
}
@@ -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)
}
}
@@ -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,
@@ -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)
+1 -1
View File
@@ -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