diff --git a/ElementX/Sources/Mocks/ClientProxyMock.swift b/ElementX/Sources/Mocks/ClientProxyMock.swift index a61295d32..13e54811f 100644 --- a/ElementX/Sources/Mocks/ClientProxyMock.swift +++ b/ElementX/Sources/Mocks/ClientProxyMock.swift @@ -24,6 +24,10 @@ struct ClientProxyMockConfiguration { var roomDirectorySearchProxy: RoomDirectorySearchProxyProtocol? } +enum ClientProxyMockError: Error { + case generic +} + extension ClientProxyMock { convenience init(_ configuration: ClientProxyMockConfiguration) { self.init() @@ -53,25 +57,25 @@ extension ClientProxyMock { isOnlyDeviceLeftReturnValue = .success(false) accountURLActionReturnValue = "https://matrix.org/account" - directRoomForUserIDReturnValue = .failure(.failedRetrievingDirectRoom) - createDirectRoomWithExpectedRoomNameReturnValue = .failure(.failedCreatingRoom) - createRoomNameTopicIsRoomPrivateUserIDsAvatarURLReturnValue = .failure(.failedCreatingRoom) - uploadMediaReturnValue = .failure(.failedUploadingMedia(.unknown)) - loadUserDisplayNameReturnValue = .failure(.failedRetrievingUserDisplayName) - setUserDisplayNameReturnValue = .failure(.failedSettingUserDisplayName) - loadUserAvatarURLReturnValue = .failure(.failedRetrievingUserAvatarURL) - setUserAvatarMediaReturnValue = .failure(.failedSettingUserAvatar) - removeUserAvatarReturnValue = .failure(.failedSettingUserAvatar) + directRoomForUserIDReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) + createDirectRoomWithExpectedRoomNameReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) + createRoomNameTopicIsRoomPrivateUserIDsAvatarURLReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) + uploadMediaReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) + loadUserDisplayNameReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) + setUserDisplayNameReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) + loadUserAvatarURLReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) + setUserAvatarMediaReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) + removeUserAvatarReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) logoutReturnValue = nil searchUsersSearchTermLimitReturnValue = .success(.init(results: [], limited: false)) profileForReturnValue = .success(.init(userID: "@a:b.com", displayName: "Some user")) - sessionVerificationControllerProxyReturnValue = .failure(.failedRetrievingSessionVerificationController) + sessionVerificationControllerProxyReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) ignoreUserReturnValue = .success(()) unignoreUserReturnValue = .success(()) - loadMediaContentForSourceThrowableError = ClientProxyError.failedLoadingMedia - loadMediaThumbnailForSourceWidthHeightThrowableError = ClientProxyError.failedLoadingMedia - loadMediaFileForSourceBodyThrowableError = ClientProxyError.failedLoadingMedia + loadMediaContentForSourceThrowableError = ClientProxyError.sdkError(ClientProxyMockError.generic) + loadMediaThumbnailForSourceWidthHeightThrowableError = ClientProxyError.sdkError(ClientProxyMockError.generic) + loadMediaFileForSourceBodyThrowableError = ClientProxyError.sdkError(ClientProxyMockError.generic) secureBackupController = { let secureBackupController = SecureBackupControllerMock() diff --git a/ElementX/Sources/Mocks/RoomProxyMock.swift b/ElementX/Sources/Mocks/RoomProxyMock.swift index 665117627..6f176d168 100644 --- a/ElementX/Sources/Mocks/RoomProxyMock.swift +++ b/ElementX/Sources/Mocks/RoomProxyMock.swift @@ -49,6 +49,10 @@ struct RoomProxyMockConfiguration { var canUserJoinCall = true } +enum RoomProxyMockError: Error { + case generic +} + extension RoomProxyMock { @MainActor convenience init(with configuration: RoomProxyMockConfiguration) { @@ -83,7 +87,7 @@ extension RoomProxyMock { setTopicClosure = { _ in .success(()) } getMemberUserIDClosure = { [weak self] userID in guard let member = self?.membersPublisher.value.first(where: { $0.userID == userID }) else { - return .failure(.failedRetrievingMember) + return .failure(.sdkError(RoomProxyMockError.generic)) } return .success(member) } @@ -98,7 +102,7 @@ extension RoomProxyMock { resetPowerLevelsReturnValue = .success(.mock) suggestedRoleForClosure = { [weak self] userID in guard case .success(let member) = await self?.getMember(userID: userID) else { - return .failure(.failedCheckingPermission) + return .failure(.sdkError(RoomProxyMockError.generic)) } return .success(member.role) } diff --git a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift index 358d9a4a3..4e6e35d58 100644 --- a/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift +++ b/ElementX/Sources/Screens/CreateRoom/CreateRoomViewModel.swift @@ -110,33 +110,6 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol .store(in: &cancellables) } - private func displayError(_ type: ClientProxyError) { - switch type { - case .failedCreatingRoom: - state.bindings.alertInfo = AlertInfo(id: .failedCreatingRoom, - title: L10n.commonError, - message: L10n.screenStartChatErrorStartingChat) - case .failedSearchingUsers: - state.bindings.alertInfo = AlertInfo(id: .unknown) - case .failedUploadingMedia(let matrixError): - switch matrixError { - case .fileTooLarge: - // waiting for proper copy - state.bindings.alertInfo = AlertInfo(id: .fileTooLarge) - default: - state.bindings.alertInfo = AlertInfo(id: .failedUploadingMedia) - } - case .mediaFileError: - state.bindings.alertInfo = AlertInfo(id: .mediaFileError) - default: - break - } - } - - private var clientProxy: ClientProxyProtocol { - userSession.clientProxy - } - private func createRoom() async { defer { hideLoadingIndicator() @@ -145,27 +118,42 @@ class CreateRoomViewModel: CreateRoomViewModelType, CreateRoomViewModelProtocol let avatarURL: URL? if let media = createRoomParameters.avatarImageMedia { - switch await clientProxy.uploadMedia(media) { + switch await userSession.clientProxy.uploadMedia(media) { case .success(let url): avatarURL = URL(string: url) case .failure(let error): - displayError(error) + switch error { + case .failedUploadingMedia(_, let errorCode): + switch errorCode { + case .fileTooLarge: + state.bindings.alertInfo = AlertInfo(id: .fileTooLarge) + default: + state.bindings.alertInfo = AlertInfo(id: .failedUploadingMedia) + } + case .invalidMedia: + state.bindings.alertInfo = AlertInfo(id: .mediaFileError) + default: + state.bindings.alertInfo = AlertInfo(id: .unknown) + } + return } } else { avatarURL = nil } - - switch await clientProxy.createRoom(name: createRoomParameters.name, - topic: createRoomParameters.topic, - isRoomPrivate: createRoomParameters.isRoomPrivate, - userIDs: state.selectedUsers.map(\.userID), - avatarURL: avatarURL) { + + switch await userSession.clientProxy.createRoom(name: createRoomParameters.name, + topic: createRoomParameters.topic, + isRoomPrivate: createRoomParameters.isRoomPrivate, + userIDs: state.selectedUsers.map(\.userID), + avatarURL: avatarURL) { case .success(let roomId): analytics.trackCreatedRoom(isDM: false) actionsSubject.send(.openRoom(withIdentifier: roomId)) - case .failure(let failure): - displayError(failure) + case .failure: + state.bindings.alertInfo = AlertInfo(id: .failedCreatingRoom, + title: L10n.commonError, + message: L10n.screenStartChatErrorStartingChat) } } diff --git a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift index cf33b2e7a..b5fae7edd 100644 --- a/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift +++ b/ElementX/Sources/Screens/InvitesScreen/InvitesScreenViewModel.swift @@ -119,7 +119,7 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel userIndicatorController.submitIndicator(UserIndicator(id: roomID, type: .modal, title: L10n.commonLoading, persistent: true)) guard let roomProxy = await clientProxy.roomForIdentifier(roomID) else { - displayError(.failedAcceptingInvite) + displayError() return } @@ -127,8 +127,8 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel case .success: actionsSubject.send(.openRoom(withIdentifier: roomID)) analytics.trackJoinedRoom(isDM: roomProxy.isDirect, isSpace: roomProxy.isSpace, activeMemberCount: UInt(roomProxy.activeMembersCount)) - case .failure(let error): - displayError(error) + case .failure: + displayError() } } } @@ -143,20 +143,19 @@ class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModel userIndicatorController.submitIndicator(UserIndicator(id: roomID, type: .modal, title: L10n.commonLoading, persistent: true)) guard let roomProxy = await clientProxy.roomForIdentifier(roomID) else { - displayError(.failedRejectingInvite) + displayError() return } let result = await roomProxy.rejectInvitation() - if case .failure(let error) = result { - displayError(error) + if case .failure = result { + displayError() } } } - private func displayError(_ error: RoomProxyError) { - MXLog.error("Failed to accept/decline invite: \(error)") + private func displayError() { state.bindings.alertInfo = .init(id: true, title: L10n.commonError, message: L10n.errorUnknown) diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift index 6b9d6c118..abbef093c 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/RoomDetailsEditScreenViewModel.swift @@ -48,9 +48,9 @@ class RoomDetailsEditScreenViewModel: RoomDetailsEditScreenViewModelType, RoomDe 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) + state.canEditAvatar = await (try? roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomAvatar).get()) == .some(true) + state.canEditName = await (try? roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomName).get()) == .some(true) + state.canEditTopic = await (try? roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomTopic).get()) == .some(true) } } diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift index 54ecaaaca..874ba050f 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenViewModel.swift @@ -178,15 +178,15 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr } 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.canEditRoomName = await (try? roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomName).get()) == true + state.canEditRoomTopic = await (try? roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomTopic).get()) == true + state.canEditRoomAvatar = await (try? roomProxy.canUser(userID: roomProxy.ownUserID, sendStateEvent: .roomAvatar).get()) == true + if appSettings.roomModerationEnabled { - state.canEditRolesOrPermissions = await roomProxy.suggestedRole(for: roomProxy.ownUserID) == .success(.administrator) + state.canEditRolesOrPermissions = await (try? roomProxy.suggestedRole(for: roomProxy.ownUserID).get()) == .administrator } - state.canInviteUsers = await canInviteUsers + + state.canInviteUsers = await (try? roomProxy.canUserInvite(userID: roomProxy.ownUserID).get()) == true } private func setupNotificationSettingsSubscription() { diff --git a/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift b/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift index ca168c546..8d7bf5458 100644 --- a/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomMemberListScreen/RoomMembersListScreenViewModel.swift @@ -110,12 +110,9 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe bannedMembers: roomMembersDetails.bannedMembers, bindings: state.bindings) - 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) + self.state.canInviteUsers = await (try? roomProxy.canUserInvite(userID: roomProxy.ownUserID).get()) == true + self.state.canKickUsers = await (try? roomProxy.canUserKick(userID: roomProxy.ownUserID).get()) == true + self.state.canBanUsers = await (try? roomProxy.canUserBan(userID: roomProxy.ownUserID).get()) == true hideLoader() } diff --git a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift index ead0e1163..07fe0193a 100644 --- a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift +++ b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenViewModel.swift @@ -56,16 +56,16 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie case .selectUser(let user): showLoadingIndicator() Task { - let currentDirectRoom = await clientProxy.directRoomForUserID(user.userID) + let currentDirectRoom = await userSession.clientProxy.directRoomForUserID(user.userID) switch currentDirectRoom { case .success(.some(let roomId)): self.hideLoadingIndicator() self.actionsSubject.send(.openRoom(withIdentifier: roomId)) - case .success(nil): + case .success: await self.createDirectRoom(with: user) - case .failure(let failure): + case .failure: self.hideLoadingIndicator() - self.displayError(failure) + self.displayError() } } } @@ -73,19 +73,6 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie // MARK: - Private - private func displayError(_ type: ClientProxyError) { - switch type { - case .failedCreatingRoom, .failedRetrievingDirectRoom: - state.bindings.alertInfo = AlertInfo(id: .failedCreatingRoom, - title: L10n.commonError, - message: L10n.screenStartChatErrorStartingChat) - case .failedSearchingUsers: - state.bindings.alertInfo = AlertInfo(id: .unknown) - default: - break - } - } - private func setupBindings() { context.$viewState .map(\.bindings.searchQuery) @@ -101,12 +88,12 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie private var fetchUsersTask: Task? private func fetchUsers() { - guard searchQuery.count >= 3 else { + guard context.searchQuery.count >= 3 else { state.usersSection = .init(type: .suggestions, users: []) return } fetchUsersTask = Task { - let result = await userDiscoveryService.searchProfiles(with: searchQuery) + let result = await userDiscoveryService.searchProfiles(with: context.searchQuery) guard !Task.isCancelled else { return } handleResult(for: .searchResult, result: result) } @@ -117,7 +104,7 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie case .success(let users): state.usersSection = .init(type: sectionType, users: users) case .failure: - break + state.bindings.alertInfo = AlertInfo(id: .unknown) } } @@ -126,23 +113,21 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie hideLoadingIndicator() } showLoadingIndicator() - switch await clientProxy.createDirectRoom(with: user.userID, expectedRoomName: user.displayName) { + switch await userSession.clientProxy.createDirectRoom(with: user.userID, expectedRoomName: user.displayName) { case .success(let roomId): analytics.trackCreatedRoom(isDM: true) actionsSubject.send(.openRoom(withIdentifier: roomId)) - case .failure(let failure): - displayError(failure) + case .failure: + displayError() } } - private var clientProxy: ClientProxyProtocol { - userSession.clientProxy + private func displayError() { + state.bindings.alertInfo = AlertInfo(id: .failedCreatingRoom, + title: L10n.commonError, + message: L10n.screenStartChatErrorStartingChat) } - - private var searchQuery: String { - context.searchQuery - } - + // MARK: Loading indicator private static let loadingIndicatorIdentifier = "\(StartChatScreenViewModel.self)-Loading" diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 40c0e614c..5234a41ce 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -216,7 +216,7 @@ class ClientProxy: ClientProxyProtocol { return .success(result) } catch { MXLog.error("Failed checking isLastDevice with error: \(error)") - return .failure(.failedCheckingIsLastDevice(error)) + return .failure(.sdkError(error)) } } @@ -293,7 +293,8 @@ class ClientProxy: ClientProxyProtocol { let roomID = try self.client.getDmRoom(userId: userID)?.id() return .success(roomID) } catch { - return .failure(.failedRetrievingDirectRoom) + MXLog.error("Failed retrieving direct room for userID: \(userID) with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -313,7 +314,8 @@ class ClientProxy: ClientProxyProtocol { let result = try self.client.createRoom(request: parameters) return .success(result) } catch { - return .failure(.failedCreatingRoom) + MXLog.error("Failed creating direct room for userID: \(userID) with error: \(error)") + return .failure(.sdkError(error)) } } @@ -335,7 +337,8 @@ class ClientProxy: ClientProxyProtocol { let roomID = try self.client.createRoom(request: parameters) return .success(roomID) } catch { - return .failure(.failedCreatingRoom) + MXLog.error("Failed creating room with error: \(error)") + return .failure(.sdkError(error)) } } @@ -351,20 +354,27 @@ class ClientProxy: ClientProxyProtocol { return .success(()) } catch { - return .failure(.failedJoiningRoom) + MXLog.error("Failed joining roomID: \(roomID) with error: \(error)") + return .failure(.sdkError(error)) } } func uploadMedia(_ media: MediaInfo) async -> Result { - guard let mimeType = media.mimeType else { return .failure(ClientProxyError.mediaFileError) } + guard let mimeType = media.mimeType else { + MXLog.error("Failed uploading media, invalid mime type: \(media)") + return .failure(ClientProxyError.invalidMedia) + } + do { let data = try Data(contentsOf: media.url) let matrixUrl = try await client.uploadMedia(mimeType: mimeType, data: data, progressWatcher: nil) return .success(matrixUrl) } catch let error as ClientError { - return .failure(ClientProxyError.failedUploadingMedia(error.code)) + MXLog.error("Failed uploading media with error: \(error)") + return .failure(ClientProxyError.failedUploadingMedia(error, error.code)) } catch { - return .failure(ClientProxyError.mediaFileError) + MXLog.error("Failed uploading media with error: \(error)") + return .failure(ClientProxyError.sdkError(error)) } } @@ -435,7 +445,8 @@ class ClientProxy: ClientProxyProtocol { self.userDisplayNameSubject.send(displayName) return .success(()) } catch { - return .failure(.failedRetrievingUserDisplayName) + MXLog.error("Failed loading user display name with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -449,7 +460,8 @@ class ClientProxy: ClientProxyProtocol { } return .success(()) } catch { - return .failure(.failedSettingUserDisplayName) + MXLog.error("Failed setting user display name with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -462,7 +474,8 @@ class ClientProxy: ClientProxyProtocol { self.userAvatarURLSubject.send(urlString.flatMap(URL.init)) return .success(()) } catch { - return .failure(.failedRetrievingUserAvatarURL) + MXLog.error("Failed loading user avatar URL with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -470,7 +483,8 @@ class ClientProxy: ClientProxyProtocol { func setUserAvatar(media: MediaInfo) async -> Result { await Task.dispatch(on: .global()) { guard case let .image(imageURL, _, _) = media, let mimeType = media.mimeType else { - return .failure(.failedSettingUserAvatar) + MXLog.error("Failed uploading, invalid media: \(media)") + return .failure(.invalidMedia) } do { @@ -481,7 +495,8 @@ class ClientProxy: ClientProxyProtocol { } return .success(()) } catch { - return .failure(.failedSettingUserAvatar) + MXLog.error("Failed setting user avatar with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -495,7 +510,8 @@ class ClientProxy: ClientProxyProtocol { } return .success(()) } catch { - return .failure(.failedSettingUserAvatar) + MXLog.error("Failed removing user avatar with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -506,7 +522,8 @@ class ClientProxy: ClientProxyProtocol { let sessionVerificationController = try self.client.getSessionVerificationController() return .success(SessionVerificationControllerProxy(sessionVerificationController: sessionVerificationController)) } catch { - return .failure(.failedRetrievingSessionVerificationController) + MXLog.error("Failed retrieving session verification controller proxy with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -536,7 +553,8 @@ class ClientProxy: ClientProxyProtocol { do { return try .success(.init(sdkResults: self.client.searchUsers(searchTerm: searchTerm, limit: UInt64(limit)))) } catch { - return .failure(.failedSearchingUsers) + MXLog.error("Failed searching users with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -546,7 +564,8 @@ class ClientProxy: ClientProxyProtocol { do { return try .success(.init(sdkUserProfile: self.client.getProfile(userId: userID))) } catch { - return .failure(.failedGettingUserProfile) + MXLog.error("Failed retrieving profile for userID: \(userID) with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -563,7 +582,7 @@ class ClientProxy: ClientProxyProtocol { return .success(()) } catch { MXLog.error("Failed ignoring user with error: \(error)") - return .failure(.failedIgnoringUser) + return .failure(.sdkError(error)) } } @@ -573,7 +592,7 @@ class ClientProxy: ClientProxyProtocol { return .success(()) } catch { MXLog.error("Failed unignoring user with error: \(error)") - return .failure(.failedUnignoringUser) + return .failure(.sdkError(error)) } } diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index 7b40b9cc3..7a427e371 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -38,22 +38,9 @@ enum ClientProxyLoadingState { } enum ClientProxyError: Error { - case failedCreatingRoom - case failedRetrievingDirectRoom - case failedRetrievingUserDisplayName - case failedRetrievingUserAvatarURL - case failedSettingUserDisplayName - case failedRetrievingSessionVerificationController - case failedLoadingMedia - case mediaFileError - case failedUploadingMedia(MatrixErrorCode) - case failedSearchingUsers - case failedGettingUserProfile - case failedSettingUserAvatar - case failedCheckingIsLastDevice(Error?) - case failedIgnoringUser - case failedUnignoringUser - case failedJoiningRoom + case sdkError(Error) + case invalidMedia + case failedUploadingMedia(Error, MatrixErrorCode) } enum SlidingSyncConstants { diff --git a/ElementX/Sources/Services/Room/RoomProxy.swift b/ElementX/Sources/Services/Room/RoomProxy.swift index 22ec58c3d..107867165 100644 --- a/ElementX/Sources/Services/Room/RoomProxy.swift +++ b/ElementX/Sources/Services/Room/RoomProxy.swift @@ -168,7 +168,8 @@ class RoomProxy: RoomProxyProtocol { try self.room.redact(eventId: eventID, reason: nil) return .success(()) } catch { - return .failure(.failedRedactingEvent) + MXLog.error("Failed redacting eventID: \(eventID) with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -184,7 +185,8 @@ class RoomProxy: RoomProxyProtocol { try self.room.reportContent(eventId: eventID, score: nil, reason: reason) return .success(()) } catch { - return .failure(.failedReportingContent) + MXLog.error("Failed reporting eventID: \(eventID) with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -198,7 +200,7 @@ class RoomProxy: RoomProxyProtocol { membersSubject.value = members.map(RoomMemberProxy.init) } } catch { - MXLog.error("[RoomProxy] Failed to update members using no sync API: \(error)") + MXLog.error("[RoomProxy] Failed updating members using no sync API: \(error)") } do { @@ -208,7 +210,7 @@ class RoomProxy: RoomProxyProtocol { membersSubject.value = members.map(RoomMemberProxy.init) } } catch { - MXLog.error("[RoomProxy] Failed to update members using sync API: \(error)") + MXLog.error("[RoomProxy] Failed updating members using sync API: \(error)") } } @@ -226,7 +228,8 @@ class RoomProxy: RoomProxyProtocol { let member = try await room.member(userId: userID) return .success(RoomMemberProxy(member: member)) } catch { - return .failure(.failedRetrievingMember) + MXLog.error("Failed retrieving member \(userID) with error: \(error)") + return .failure(.sdkError(error)) } } @@ -241,8 +244,8 @@ class RoomProxy: RoomProxyProtocol { try self.room.leave() return .success(()) } catch { - MXLog.error("Failed to leave the room: \(error)") - return .failure(.failedLeavingRoom) + MXLog.error("Failed leaving room with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -252,7 +255,8 @@ class RoomProxy: RoomProxyProtocol { do { return try .success(self.room.leave()) } catch { - return .failure(.failedRejectingInvite) + MXLog.error("Failed rejecting invitiation with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -263,7 +267,8 @@ class RoomProxy: RoomProxyProtocol { try self.room.join() return .success(()) } catch { - return .failure(.failedAcceptingInvite) + MXLog.error("Failed accepting invitation with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -275,7 +280,7 @@ class RoomProxy: RoomProxyProtocol { return try .success(self.room.inviteUserById(userId: userID)) } catch { MXLog.error("Failed inviting user \(userID) with error: \(error)") - return .failure(.failedInvitingUser) + return .failure(.sdkError(error)) } } } @@ -285,7 +290,8 @@ class RoomProxy: RoomProxyProtocol { do { return try .success(self.room.setName(name: name)) } catch { - return .failure(.failedSettingRoomName) + MXLog.error("Failed setting name with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -295,7 +301,8 @@ class RoomProxy: RoomProxyProtocol { do { return try .success(self.room.setTopic(topic: topic)) } catch { - return .failure(.failedSettingRoomTopic) + MXLog.error("Failed setting topic with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -305,7 +312,8 @@ class RoomProxy: RoomProxyProtocol { do { return try .success(self.room.removeAvatar()) } catch { - return .failure(.failedRemovingAvatar) + MXLog.error("Failed removing avatar with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -313,14 +321,16 @@ class RoomProxy: RoomProxyProtocol { func uploadAvatar(media: MediaInfo) async -> Result { await Task.dispatch(on: .global()) { guard case let .image(imageURL, _, _) = media, let mimeType = media.mimeType else { - return .failure(.failedUploadingAvatar) + MXLog.error("Failed uploading avatar, invalid media: \(media)") + return .failure(.invalidMedia) } do { let data = try Data(contentsOf: imageURL) return try .success(self.room.uploadAvatar(mimeType: mimeType, data: data, mediaInfo: nil)) } catch { - return .failure(.failedUploadingAvatar) + MXLog.error("Failed uploading avatar with error: \(error)") + return .failure(.sdkError(error)) } } } @@ -331,7 +341,7 @@ class RoomProxy: RoomProxyProtocol { return .success(()) } catch { MXLog.error("Failed marking room \(id) as read with error: \(error)") - return .failure(.failedMarkingAsRead) + return .failure(.sdkError(error)) } } @@ -343,7 +353,7 @@ class RoomProxy: RoomProxyProtocol { return .success(()) } catch { MXLog.error("Failed sending typing notice with error: \(error)") - return .failure(.failedSendingTypingNotice) + return .failure(.sdkError(error)) } } @@ -357,7 +367,7 @@ class RoomProxy: RoomProxyProtocol { return .success(()) } catch { MXLog.error("Failed marking room \(id) as unread: \(isUnread) with error: \(error)") - return .failure(.failedFlaggingAsUnread) + return .failure(.sdkError(error)) } } @@ -367,7 +377,7 @@ class RoomProxy: RoomProxyProtocol { return .success(()) } catch { MXLog.error("Failed flagging room \(id) as favourite with error: \(error)") - return .failure(.failedFlaggingAsFavourite) + return .failure(.sdkError(error)) } } @@ -378,7 +388,7 @@ class RoomProxy: RoomProxyProtocol { return try await .success(room.getPowerLevels()) } catch { MXLog.error("Failed building the current power level settings: \(error)") - return .failure(.failedCheckingPermission) + return .failure(.sdkError(error)) } } @@ -387,7 +397,7 @@ class RoomProxy: RoomProxyProtocol { return try await .success(room.applyPowerLevelChanges(changes: changes)) } catch { MXLog.error("Failed applying the power level changes: \(error)") - return .failure(.failedSettingPermission) + return .failure(.sdkError(error)) } } @@ -396,7 +406,7 @@ class RoomProxy: RoomProxyProtocol { return try await .success(room.resetPowerLevels()) } catch { MXLog.error("Failed resetting the power levels: \(error)") - return .failure(.failedSettingPermission) + return .failure(.sdkError(error)) } } @@ -405,7 +415,7 @@ class RoomProxy: RoomProxyProtocol { return try await .success(room.suggestedRoleForUser(userId: userID)) } catch { MXLog.error("Failed getting a user's role: \(error)") - return .failure(.failedCheckingPermission) + return .failure(.sdkError(error)) } } @@ -415,7 +425,7 @@ class RoomProxy: RoomProxyProtocol { return try await .success(room.updatePowerLevelsForUsers(updates: updates)) } catch { MXLog.error("Failed updating user power levels changes: \(error)") - return .failure(.failedSettingPermission) + return .failure(.sdkError(error)) } } @@ -424,7 +434,7 @@ class RoomProxy: RoomProxyProtocol { 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) + return .failure(.sdkError(error)) } } @@ -433,7 +443,7 @@ class RoomProxy: RoomProxyProtocol { return try await .success(room.canUserInvite(userId: userID)) } catch { MXLog.error("Failed checking if the user can invite with error: \(error)") - return .failure(.failedCheckingPermission) + return .failure(.sdkError(error)) } } @@ -442,7 +452,7 @@ class RoomProxy: RoomProxyProtocol { return try await .success(room.canUserRedactOther(userId: userID)) } catch { MXLog.error("Failed checking if the user can redact others with error: \(error)") - return .failure(.failedCheckingPermission) + return .failure(.sdkError(error)) } } @@ -451,7 +461,7 @@ class RoomProxy: RoomProxyProtocol { return try await .success(room.canUserRedactOwn(userId: userID)) } catch { MXLog.error("Failed checking if the user can redact self with error: \(error)") - return .failure(.failedCheckingPermission) + return .failure(.sdkError(error)) } } @@ -460,7 +470,7 @@ class RoomProxy: RoomProxyProtocol { return try await .success(room.canUserKick(userId: userID)) } catch { MXLog.error("Failed checking if the user can kick with error: \(error)") - return .failure(.failedCheckingPermission) + return .failure(.sdkError(error)) } } @@ -469,7 +479,7 @@ class RoomProxy: RoomProxyProtocol { return try await .success(room.canUserBan(userId: userID)) } catch { MXLog.error("Failed checking if the user can ban with error: \(error)") - return .failure(.failedCheckingPermission) + return .failure(.sdkError(error)) } } @@ -478,7 +488,7 @@ class RoomProxy: RoomProxyProtocol { return try await .success(room.canUserTriggerRoomNotification(userId: userID)) } catch { MXLog.error("Failed checking if the user can trigger room notification with error: \(error)") - return .failure(.failedCheckingPermission) + return .failure(.sdkError(error)) } } @@ -490,7 +500,7 @@ class RoomProxy: RoomProxyProtocol { return .success(()) } catch { MXLog.error("Failed kicking \(userID) with error: \(error)") - return .failure(.failedModeration) + return .failure(.sdkError(error)) } } @@ -500,7 +510,7 @@ class RoomProxy: RoomProxyProtocol { return .success(()) } catch { MXLog.error("Failed banning \(userID) with error: \(error)") - return .failure(.failedModeration) + return .failure(.sdkError(error)) } } @@ -510,7 +520,7 @@ class RoomProxy: RoomProxyProtocol { return .success(()) } catch { MXLog.error("Failed unbanning \(userID) with error: \(error)") - return .failure(.failedModeration) + return .failure(.sdkError(error)) } } @@ -521,7 +531,7 @@ class RoomProxy: RoomProxyProtocol { return try await .success(room.canUserSendState(userId: userID, stateEvent: .callMember)) } catch { MXLog.error("Failed checking if the user can trigger room notification with error: \(error)") - return .failure(.failedCheckingPermission) + return .failure(.sdkError(error)) } } @@ -536,14 +546,14 @@ class RoomProxy: RoomProxyProtocol { let urlString = try await room.matrixToPermalink() guard let url = URL(string: urlString) else { - MXLog.error("Invalid permalink URL string: \(urlString)") - return .failure(.generic("Invalid permalink URL string: \(urlString)")) + MXLog.error("Failed creating permalink for roomID: \(id), invalid permalink URL string: \(urlString)") + return .failure(.invalidURL) } return .success(url) } catch { MXLog.error("Failed creating permalink for roomID: \(id) with error: \(error)") - return .failure(.generic(error.localizedDescription)) + return .failure(.sdkError(error)) } } @@ -552,14 +562,14 @@ class RoomProxy: RoomProxyProtocol { let urlString = try await room.matrixToEventPermalink(eventId: eventID) guard let url = URL(string: urlString) else { - MXLog.error("Invalid permalink URL string: \(urlString)") - return .failure(.generic("Invalid permalink URL string: \(urlString)")) + MXLog.error("Failed creating permalink for eventID: \(eventID), invalid permalink URL string: \(urlString)") + return .failure(.invalidURL) } return .success(url) } catch { MXLog.error("Failed creating permalink for eventID: \(eventID) with error: \(error)") - return .failure(.generic(error.localizedDescription)) + return .failure(.sdkError(error)) } } diff --git a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift index 9edfd310b..aab830ed2 100644 --- a/ElementX/Sources/Services/Room/RoomProxyProtocol.swift +++ b/ElementX/Sources/Services/Room/RoomProxyProtocol.swift @@ -18,26 +18,11 @@ import Combine import Foundation import MatrixRustSDK -enum RoomProxyError: Error, Equatable { - case generic(String?) - case failedRedactingEvent - case failedReportingContent - case failedRetrievingMember - case failedLeavingRoom - case failedAcceptingInvite - case failedRejectingInvite - case failedInvitingUser - case failedSettingRoomName - case failedSettingRoomTopic - case failedRemovingAvatar - case failedUploadingAvatar - case failedCheckingPermission - case failedSettingPermission - case failedFlaggingAsUnread - case failedMarkingAsRead - case failedSendingTypingNotice - case failedFlaggingAsFavourite - case failedModeration +enum RoomProxyError: Error { + case sdkError(Error) + + case invalidURL + case invalidMedia } enum RoomProxyAction { diff --git a/UnitTests/Sources/HomeScreenViewModelTests.swift b/UnitTests/Sources/HomeScreenViewModelTests.swift index 46dbcc598..91cb72df7 100644 --- a/UnitTests/Sources/HomeScreenViewModelTests.swift +++ b/UnitTests/Sources/HomeScreenViewModelTests.swift @@ -105,7 +105,7 @@ class HomeScreenViewModelTests: XCTestCase { func testLeaveRoomError() async throws { let mockRoomId = "1" let room: RoomProxyMock = .init(with: .init(id: mockRoomId, name: "Some room")) - room.leaveRoomClosure = { .failure(.failedLeavingRoom) } + room.leaveRoomClosure = { .failure(.sdkError(ClientProxyMockError.generic)) } clientProxy.roomForIdentifierClosure = { _ in room } diff --git a/UnitTests/Sources/RoomDetailsViewModelTests.swift b/UnitTests/Sources/RoomDetailsViewModelTests.swift index a7abfbbcc..c5b707659 100644 --- a/UnitTests/Sources/RoomDetailsViewModelTests.swift +++ b/UnitTests/Sources/RoomDetailsViewModelTests.swift @@ -138,7 +138,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { defer { expectation.fulfill() } - return .failure(.failedLeavingRoom) + return .failure(.sdkError(ClientProxyMockError.generic)) } context.send(viewAction: .confirmLeave) await fulfillment(of: [expectation]) @@ -207,7 +207,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let recipient = RoomMemberProxyMock.mockDan let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] let clientProxy = ClientProxyMock(.init()) - clientProxy.ignoreUserReturnValue = .failure(.failedIgnoringUser) + clientProxy.ignoreUserReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, clientProxy: clientProxy, @@ -276,7 +276,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase { let recipient = RoomMemberProxyMock.mockIgnored let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient] let clientProxy = ClientProxyMock(.init()) - clientProxy.unignoreUserReturnValue = .failure(.failedUnignoringUser) + clientProxy.unignoreUserReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) roomProxyMock = RoomProxyMock(with: .init(name: "Test", isDirect: true, isEncrypted: true, members: mockedMembers)) viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, clientProxy: clientProxy, diff --git a/UnitTests/Sources/RoomMemberDetailsViewModelTests.swift b/UnitTests/Sources/RoomMemberDetailsViewModelTests.swift index 5fe40d5a1..b7055297f 100644 --- a/UnitTests/Sources/RoomMemberDetailsViewModelTests.swift +++ b/UnitTests/Sources/RoomMemberDetailsViewModelTests.swift @@ -87,7 +87,7 @@ class RoomMemberDetailsViewModelTests: XCTestCase { func testIgnoreFailure() async throws { roomMemberProxyMock = RoomMemberProxyMock.mockAlice let clientProxy = ClientProxyMock(.init()) - clientProxy.ignoreUserReturnValue = .failure(.failedIgnoringUser) + clientProxy.ignoreUserReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) viewModel = RoomMemberDetailsScreenViewModel(userID: roomMemberProxyMock.userID, roomProxy: roomProxyMock, clientProxy: clientProxy, @@ -158,7 +158,7 @@ class RoomMemberDetailsViewModelTests: XCTestCase { func testUnignoreFailure() async throws { roomMemberProxyMock = RoomMemberProxyMock.mockIgnored let clientProxy = ClientProxyMock(.init()) - clientProxy.unignoreUserReturnValue = .failure(.failedUnignoringUser) + clientProxy.unignoreUserReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) viewModel = RoomMemberDetailsScreenViewModel(userID: roomMemberProxyMock.userID, roomProxy: roomProxyMock, clientProxy: clientProxy, diff --git a/UnitTests/Sources/UserDiscoveryService/UserDiscoveryServiceTest.swift b/UnitTests/Sources/UserDiscoveryService/UserDiscoveryServiceTest.swift index 8fde1b14c..2bf772442 100644 --- a/UnitTests/Sources/UserDiscoveryService/UserDiscoveryServiceTest.swift +++ b/UnitTests/Sources/UserDiscoveryService/UserDiscoveryServiceTest.swift @@ -71,7 +71,7 @@ class UserDiscoveryServiceTest: XCTestCase { } func testLocalResultShowsOnSearchError() async { - clientProxy.searchUsersSearchTermLimitReturnValue = .failure(.failedSearchingUsers) + clientProxy.searchUsersSearchTermLimitReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) clientProxy.profileForReturnValue = .success(.init(userID: "@some:matrix.org")) let results = await (try? search(query: "@a:b.com").get()) ?? [] @@ -81,7 +81,7 @@ class UserDiscoveryServiceTest: XCTestCase { } func testSearchErrorTriggers() async { - clientProxy.searchUsersSearchTermLimitReturnValue = .failure(.failedSearchingUsers) + clientProxy.searchUsersSearchTermLimitReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) clientProxy.profileForReturnValue = .success(.init(userID: "@some:matrix.org")) switch await search(query: "some query") { @@ -108,7 +108,7 @@ class UserDiscoveryServiceTest: XCTestCase { func testSearchResultsShowWhenGetProfileFails() async { clientProxy.searchUsersSearchTermLimitReturnValue = .success(.init(results: searchResults, limited: true)) - clientProxy.profileForReturnValue = .failure(.failedGettingUserProfile) + clientProxy.profileForReturnValue = .failure(.sdkError(ClientProxyMockError.generic)) let results = await (try? search(query: "@a:b.com").get()) ?? []