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
@@ -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)