Added the banned room proxy (#3744)

* added the banned room proxy

and a way to have a consistent loading + a retry alert

* trailing closure

* indent a comment

* push package.resolved

* updated test case
This commit is contained in:
Mauro
2025-02-06 10:15:36 +01:00
committed by GitHub
parent 3839025caa
commit f86a5a2bb9
13 changed files with 228 additions and 89 deletions
@@ -0,0 +1,64 @@
//
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Combine
import Foundation
import MatrixRustSDK
@MainActor
struct BannedRoomProxyMockConfiguration {
var id = UUID().uuidString
var name: String?
var avatarURL: URL?
var members: [RoomMemberProxyMock] = .allMembers
}
extension BannedRoomProxyMock {
@MainActor
convenience init(_ configuration: KnockedRoomProxyMockConfiguration) {
self.init()
id = configuration.id
info = RoomInfoProxy(roomInfo: .init(configuration))
}
}
extension RoomInfo {
@MainActor init(_ configuration: BannedRoomProxyMockConfiguration) {
self.init(id: configuration.id,
creator: nil,
displayName: configuration.name,
rawName: nil,
topic: nil,
avatarUrl: configuration.avatarURL?.absoluteString,
isDirect: false,
isPublic: false,
isSpace: false,
isTombstoned: false,
isFavourite: false,
canonicalAlias: nil,
alternativeAliases: [],
membership: .knocked,
inviter: nil,
heroes: [],
activeMembersCount: UInt64(configuration.members.filter { $0.membership == .join || $0.membership == .invite }.count),
invitedMembersCount: UInt64(configuration.members.filter { $0.membership == .invite }.count),
joinedMembersCount: UInt64(configuration.members.filter { $0.membership == .join }.count),
userPowerLevels: [:],
highlightCount: 0,
notificationCount: 0,
cachedUserDefinedNotificationMode: nil,
hasRoomCall: false,
activeRoomCallParticipants: [],
isMarkedUnread: false,
numUnreadMessages: 0,
numUnreadNotifications: 0,
numUnreadMentions: 0,
pinnedEventIds: [],
joinRule: .knock,
historyVisibility: .shared)
}
}
@@ -2047,6 +2047,88 @@ class AuthenticationClientBuilderMock: AuthenticationClientBuilderProtocol, @unc
}
}
}
class BannedRoomProxyMock: BannedRoomProxyProtocol, @unchecked Sendable {
var info: BaseRoomInfoProxyProtocol {
get { return underlyingInfo }
set(value) { underlyingInfo = value }
}
var underlyingInfo: BaseRoomInfoProxyProtocol!
var id: String {
get { return underlyingId }
set(value) { underlyingId = value }
}
var underlyingId: String!
var ownUserID: String {
get { return underlyingOwnUserID }
set(value) { underlyingOwnUserID = value }
}
var underlyingOwnUserID: String!
//MARK: - forgetRoom
var forgetRoomUnderlyingCallsCount = 0
var forgetRoomCallsCount: Int {
get {
if Thread.isMainThread {
return forgetRoomUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = forgetRoomUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
forgetRoomUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
forgetRoomUnderlyingCallsCount = newValue
}
}
}
}
var forgetRoomCalled: Bool {
return forgetRoomCallsCount > 0
}
var forgetRoomUnderlyingReturnValue: Result<Void, RoomProxyError>!
var forgetRoomReturnValue: Result<Void, RoomProxyError>! {
get {
if Thread.isMainThread {
return forgetRoomUnderlyingReturnValue
} else {
var returnValue: Result<Void, RoomProxyError>? = nil
DispatchQueue.main.sync {
returnValue = forgetRoomUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
forgetRoomUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
forgetRoomUnderlyingReturnValue = newValue
}
}
}
}
var forgetRoomClosure: (() async -> Result<Void, RoomProxyError>)?
func forgetRoom() async -> Result<Void, RoomProxyError> {
forgetRoomCallsCount += 1
if let forgetRoomClosure = forgetRoomClosure {
return await forgetRoomClosure()
} else {
return forgetRoomReturnValue
}
}
}
class BugReportServiceMock: BugReportServiceProtocol, @unchecked Sendable {
var crashedLastRun: Bool {
get { return underlyingCrashedLastRun }
@@ -13393,70 +13475,6 @@ class RoomPreviewProxyMock: RoomPreviewProxyProtocol, @unchecked Sendable {
var underlyingOwnMembershipDetails: RoomMembershipDetailsProxyProtocol?
var ownMembershipDetailsClosure: (() async -> RoomMembershipDetailsProxyProtocol?)?
//MARK: - forgetRoom
var forgetRoomUnderlyingCallsCount = 0
var forgetRoomCallsCount: Int {
get {
if Thread.isMainThread {
return forgetRoomUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = forgetRoomUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
forgetRoomUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
forgetRoomUnderlyingCallsCount = newValue
}
}
}
}
var forgetRoomCalled: Bool {
return forgetRoomCallsCount > 0
}
var forgetRoomUnderlyingReturnValue: Result<Void, RoomProxyError>!
var forgetRoomReturnValue: Result<Void, RoomProxyError>! {
get {
if Thread.isMainThread {
return forgetRoomUnderlyingReturnValue
} else {
var returnValue: Result<Void, RoomProxyError>? = nil
DispatchQueue.main.sync {
returnValue = forgetRoomUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
forgetRoomUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
forgetRoomUnderlyingReturnValue = newValue
}
}
}
}
var forgetRoomClosure: (() async -> Result<Void, RoomProxyError>)?
func forgetRoom() async -> Result<Void, RoomProxyError> {
forgetRoomCallsCount += 1
if let forgetRoomClosure = forgetRoomClosure {
return await forgetRoomClosure()
} else {
return forgetRoomReturnValue
}
}
}
class RoomProxyMock: RoomProxyProtocol, @unchecked Sendable {
var id: String {
@@ -91,6 +91,7 @@ struct JoinRoomScreenViewStateBindings {
enum JoinRoomScreenAlertType {
case declineInvite
case cancelKnock
case loadingError
}
enum JoinRoomScreenViewAction {
@@ -19,6 +19,7 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
private var roomPreview: RoomPreviewProxyProtocol?
private var room: RoomProxyType?
private var isLoadingPreview = true
private var membershipStateChangeCancellable: AnyCancellable?
private let actionsSubject: PassthroughSubject<JoinRoomScreenViewModelAction, Never> = .init()
@@ -81,12 +82,19 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
switch await clientProxy.roomPreviewForIdentifier(roomID, via: via) {
case .success(let roomPreview):
isLoadingPreview = false
self.roomPreview = roomPreview
await updateRoomDetails()
case .failure(.roomPreviewIsPrivate):
break // Handled by the mode, we don't need an error indicator.
// Handled by the mode, we don't need an error indicator.
isLoadingPreview = false
case .failure:
userIndicatorController.submitIndicator(UserIndicator(title: L10n.errorUnknown))
hideLoadingIndicator()
state.bindings.alertInfo = .init(id: .loadingError,
title: L10n.commonError,
message: L10n.screenJoinRoomLoadingAlertMessage,
primaryButton: .init(title: L10n.actionTryAgain) { [weak self] in Task { await self?.loadRoomDetails() }},
secondaryButton: .init(title: L10n.actionCancel, role: .cancel) { [weak self] in self?.actionsSubject.send(.dismiss) })
}
hideLoadingIndicator()
@@ -131,6 +139,8 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
Task { await self?.loadRoomDetails() }
}
}
case .banned(let bannedRoomProxy):
roomInfo = bannedRoomProxy.info
default:
break
}
@@ -155,6 +165,11 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
}
private func updateMode() async {
if isLoadingPreview {
state.mode = .loading
return
}
if roomPreview == nil, room == nil {
state.mode = .unknown
return
@@ -336,12 +351,12 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo
userIndicatorController.submitIndicator(UserIndicator(id: roomID, type: .modal, title: L10n.commonLoading, persistent: true))
guard case .banned = room, let roomPreview else {
guard case let .banned(roomProxy) = room else {
userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown))
return
}
let result = await roomPreview.forgetRoom()
let result = await roomProxy.forgetRoom()
if case .failure = result {
userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown))
@@ -330,7 +330,7 @@ struct JoinRoomScreen_Previews: PreviewProvider, TestablePreview {
switch mode {
case .unknown:
clientProxy.roomPreviewForIdentifierViaReturnValue = .failure(.sdkError(ClientProxyMockError.generic))
clientProxy.roomPreviewForIdentifierViaReturnValue = .failure(.roomPreviewIsPrivate)
clientProxy.roomForIdentifierReturnValue = nil
case .joinable:
clientProxy.roomPreviewForIdentifierViaReturnValue = .success(RoomPreviewProxyMock.joinable)
@@ -364,7 +364,7 @@ struct JoinRoomScreen_Previews: PreviewProvider, TestablePreview {
case .banned:
clientProxy.roomPreviewForIdentifierViaReturnValue = .success(RoomPreviewProxyMock.banned)
clientProxy.roomForIdentifierClosure = { _ in
.banned
.banned(BannedRoomProxyMock(.init(avatarURL: .mockMXCAvatar)))
}
case .forbidden:
clientProxy.roomPreviewForIdentifierViaReturnValue = .success(RoomPreviewProxyMock.restricted)
@@ -943,7 +943,9 @@ class ClientProxy: ClientProxyProtocol {
case .left:
return .left
case .banned:
return .banned
return try await .banned(BannedRoomProxy(roomListItem: roomListItem,
roomPreview: roomListItem.previewRoom(via: []),
ownUserID: userID))
}
} catch {
MXLog.error("Failed retrieving room: \(roomID), with error: \(error)")
@@ -0,0 +1,38 @@
//
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Foundation
import MatrixRustSDK
class BannedRoomProxy: BannedRoomProxyProtocol {
private let roomListItem: RoomListItemProtocol
private let roomPreview: RoomPreviewProtocol
let info: BaseRoomInfoProxyProtocol
let ownUserID: String
// A room identifier is constant and lazy stops it from being fetched
// multiple times over FFI
lazy var id = info.id
init(roomListItem: RoomListItemProtocol,
roomPreview: RoomPreviewProtocol,
ownUserID: String) throws {
self.roomListItem = roomListItem
self.roomPreview = roomPreview
self.ownUserID = ownUserID
info = try RoomPreviewInfoProxy(roomPreviewInfo: roomPreview.info())
}
func forgetRoom() async -> Result<Void, RoomProxyError> {
do {
return try await .success(roomPreview.forget())
} catch {
MXLog.error("Failed forgetting the room with error: \(error)")
return .failure(.sdkError(error))
}
}
}
@@ -32,14 +32,4 @@ final class RoomPreviewProxy: RoomPreviewProxyProtocol {
senderRoomMember: senderRoomMember)
}
}
func forgetRoom() async -> Result<Void, RoomProxyError> {
do {
try await roomPreview.forget()
return .success(())
} catch {
MXLog.error("Failed forgetting the room with error: \(error)")
return .failure(.sdkError(error))
}
}
}
@@ -11,7 +11,4 @@ import Foundation
protocol RoomPreviewProxyProtocol {
var info: RoomPreviewInfoProxy { get }
var ownMembershipDetails: RoomMembershipDetailsProxyProtocol? { get async }
/// Use this function to forget the room, should only be used if the current user membership is `banned`
func forgetRoom() async -> Result<Void, RoomProxyError>
}
@@ -22,7 +22,7 @@ enum RoomProxyType {
case joined(JoinedRoomProxyProtocol)
case invited(InvitedRoomProxyProtocol)
case knocked(KnockedRoomProxyProtocol)
case banned
case banned(BannedRoomProxyProtocol)
case left
}
@@ -45,6 +45,12 @@ protocol KnockedRoomProxyProtocol: RoomProxyProtocol {
func cancelKnock() async -> Result<Void, RoomProxyError>
}
// sourcery: AutoMockable
protocol BannedRoomProxyProtocol: RoomProxyProtocol {
var info: BaseRoomInfoProxyProtocol { get }
func forgetRoom() async -> Result<Void, RoomProxyError>
}
enum JoinedRoomProxyAction: Equatable {
case roomInfoUpdate
}