mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
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:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user