mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 19:11:45 +00:00
handle the case where a non parent joined space is already available in the existing restricted join rule
This commit is contained in:
@@ -9570,7 +9570,7 @@
|
|||||||
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
repositoryURL = "https://github.com/element-hq/matrix-rust-components-swift";
|
||||||
requirement = {
|
requirement = {
|
||||||
kind = exactVersion;
|
kind = exactVersion;
|
||||||
version = 25.12.10;
|
version = 25.12.11;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
701C7BEF8F70F7A83E852DCC /* XCRemoteSwiftPackageReference "GZIP" */ = {
|
||||||
|
|||||||
@@ -158,8 +158,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
"location" : "https://github.com/element-hq/matrix-rust-components-swift",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "ae9a08f7faebb5cd2af7fe29860378009066a27e",
|
"revision" : "8efbdc808f699f6df830f552a2b302e91d45431a",
|
||||||
"version" : "25.12.10"
|
"version" : "25.12.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16568,6 +16568,76 @@ class SpaceServiceProxyMock: SpaceServiceProxyProtocol, @unchecked Sendable {
|
|||||||
return spaceRoomListSpaceIDReturnValue
|
return spaceRoomListSpaceIDReturnValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//MARK: - spaceForIdentifier
|
||||||
|
|
||||||
|
var spaceForIdentifierSpaceIDUnderlyingCallsCount = 0
|
||||||
|
var spaceForIdentifierSpaceIDCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return spaceForIdentifierSpaceIDUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = spaceForIdentifierSpaceIDUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
spaceForIdentifierSpaceIDUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
spaceForIdentifierSpaceIDUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var spaceForIdentifierSpaceIDCalled: Bool {
|
||||||
|
return spaceForIdentifierSpaceIDCallsCount > 0
|
||||||
|
}
|
||||||
|
var spaceForIdentifierSpaceIDReceivedSpaceID: String?
|
||||||
|
var spaceForIdentifierSpaceIDReceivedInvocations: [String] = []
|
||||||
|
|
||||||
|
var spaceForIdentifierSpaceIDUnderlyingReturnValue: Result<SpaceRoomProxyProtocol?, SpaceServiceProxyError>!
|
||||||
|
var spaceForIdentifierSpaceIDReturnValue: Result<SpaceRoomProxyProtocol?, SpaceServiceProxyError>! {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return spaceForIdentifierSpaceIDUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: Result<SpaceRoomProxyProtocol?, SpaceServiceProxyError>? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = spaceForIdentifierSpaceIDUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
spaceForIdentifierSpaceIDUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
spaceForIdentifierSpaceIDUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var spaceForIdentifierSpaceIDClosure: ((String) async -> Result<SpaceRoomProxyProtocol?, SpaceServiceProxyError>)?
|
||||||
|
|
||||||
|
func spaceForIdentifier(spaceID: String) async -> Result<SpaceRoomProxyProtocol?, SpaceServiceProxyError> {
|
||||||
|
spaceForIdentifierSpaceIDCallsCount += 1
|
||||||
|
spaceForIdentifierSpaceIDReceivedSpaceID = spaceID
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.spaceForIdentifierSpaceIDReceivedInvocations.append(spaceID)
|
||||||
|
}
|
||||||
|
if let spaceForIdentifierSpaceIDClosure = spaceForIdentifierSpaceIDClosure {
|
||||||
|
return await spaceForIdentifierSpaceIDClosure(spaceID)
|
||||||
|
} else {
|
||||||
|
return spaceForIdentifierSpaceIDReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
//MARK: - leaveSpace
|
//MARK: - leaveSpace
|
||||||
|
|
||||||
var leaveSpaceSpaceIDUnderlyingCallsCount = 0
|
var leaveSpaceSpaceIDUnderlyingCallsCount = 0
|
||||||
|
|||||||
@@ -23265,6 +23265,81 @@ open class SpaceServiceSDKMock: MatrixRustSDK.SpaceService, @unchecked Sendable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: - getSpaceRoom
|
||||||
|
|
||||||
|
open var getSpaceRoomRoomIdThrowableError: Error?
|
||||||
|
var getSpaceRoomRoomIdUnderlyingCallsCount = 0
|
||||||
|
open var getSpaceRoomRoomIdCallsCount: Int {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return getSpaceRoomRoomIdUnderlyingCallsCount
|
||||||
|
} else {
|
||||||
|
var returnValue: Int? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = getSpaceRoomRoomIdUnderlyingCallsCount
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
getSpaceRoomRoomIdUnderlyingCallsCount = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
getSpaceRoomRoomIdUnderlyingCallsCount = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var getSpaceRoomRoomIdCalled: Bool {
|
||||||
|
return getSpaceRoomRoomIdCallsCount > 0
|
||||||
|
}
|
||||||
|
open var getSpaceRoomRoomIdReceivedRoomId: String?
|
||||||
|
open var getSpaceRoomRoomIdReceivedInvocations: [String] = []
|
||||||
|
|
||||||
|
var getSpaceRoomRoomIdUnderlyingReturnValue: SpaceRoom?
|
||||||
|
open var getSpaceRoomRoomIdReturnValue: SpaceRoom? {
|
||||||
|
get {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
return getSpaceRoomRoomIdUnderlyingReturnValue
|
||||||
|
} else {
|
||||||
|
var returnValue: SpaceRoom?? = nil
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
returnValue = getSpaceRoomRoomIdUnderlyingReturnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set {
|
||||||
|
if Thread.isMainThread {
|
||||||
|
getSpaceRoomRoomIdUnderlyingReturnValue = newValue
|
||||||
|
} else {
|
||||||
|
DispatchQueue.main.sync {
|
||||||
|
getSpaceRoomRoomIdUnderlyingReturnValue = newValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
open var getSpaceRoomRoomIdClosure: ((String) async throws -> SpaceRoom?)?
|
||||||
|
|
||||||
|
open override func getSpaceRoom(roomId: String) async throws -> SpaceRoom? {
|
||||||
|
if let error = getSpaceRoomRoomIdThrowableError {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
getSpaceRoomRoomIdCallsCount += 1
|
||||||
|
getSpaceRoomRoomIdReceivedRoomId = roomId
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.getSpaceRoomRoomIdReceivedInvocations.append(roomId)
|
||||||
|
}
|
||||||
|
if let getSpaceRoomRoomIdClosure = getSpaceRoomRoomIdClosure {
|
||||||
|
return try await getSpaceRoomRoomIdClosure(roomId)
|
||||||
|
} else {
|
||||||
|
return getSpaceRoomRoomIdReturnValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: - joinedParentsOfChild
|
//MARK: - joinedParentsOfChild
|
||||||
|
|
||||||
open var joinedParentsOfChildChildIdThrowableError: Error?
|
open var joinedParentsOfChildChildIdThrowableError: Error?
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ extension SpaceServiceProxyMock {
|
|||||||
.success(LeaveSpaceHandleProxy(spaceID: spaceID,
|
.success(LeaveSpaceHandleProxy(spaceID: spaceID,
|
||||||
leaveHandle: LeaveSpaceHandleSDKMock(.init(rooms: configuration.leaveSpaceRooms))))
|
leaveHandle: LeaveSpaceHandleSDKMock(.init(rooms: configuration.leaveSpaceRooms))))
|
||||||
}
|
}
|
||||||
|
spaceForIdentifierSpaceIDClosure = { spaceID in
|
||||||
|
.success(configuration.joinedSpaces.first { $0.id == spaceID })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ enum ManageAuthorizedSpacesScreenViewAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct AuthorizedSpacesSelection {
|
struct AuthorizedSpacesSelection {
|
||||||
let joinedParentSpaces: [SpaceRoomProxyProtocol]
|
let joinedSpaces: [SpaceRoomProxyProtocol]
|
||||||
let unknownSpacesIDs: [String]
|
let unknownSpacesIDs: [String]
|
||||||
let initialSelectedIDs: Set<String>
|
let initialSelectedIDs: Set<String>
|
||||||
let selectedIDs: PassthroughSubject<Set<String>, Never> = .init()
|
let selectedIDs: PassthroughSubject<Set<String>, Never> = .init()
|
||||||
|
|||||||
+5
-5
@@ -15,8 +15,8 @@ struct ManageAuthorizedSpacesScreen: View {
|
|||||||
Form {
|
Form {
|
||||||
header
|
header
|
||||||
|
|
||||||
if !context.viewState.authorizedSpacesSelection.joinedParentSpaces.isEmpty {
|
if !context.viewState.authorizedSpacesSelection.joinedSpaces.isEmpty {
|
||||||
joinedParentsSection
|
joinedSpacesSection
|
||||||
}
|
}
|
||||||
|
|
||||||
if !context.viewState.authorizedSpacesSelection.unknownSpacesIDs.isEmpty {
|
if !context.viewState.authorizedSpacesSelection.unknownSpacesIDs.isEmpty {
|
||||||
@@ -46,9 +46,9 @@ struct ManageAuthorizedSpacesScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var joinedParentsSection: some View {
|
private var joinedSpacesSection: some View {
|
||||||
Section {
|
Section {
|
||||||
ForEach(context.viewState.authorizedSpacesSelection.joinedParentSpaces, id: \.id) { space in
|
ForEach(context.viewState.authorizedSpacesSelection.joinedSpaces, id: \.id) { space in
|
||||||
ListRow(label: .avatar(title: space.name,
|
ListRow(label: .avatar(title: space.name,
|
||||||
description: space.canonicalAlias,
|
description: space.canonicalAlias,
|
||||||
icon: avatar(space: space)),
|
icon: avatar(space: space)),
|
||||||
@@ -102,7 +102,7 @@ struct ManageAuthorizedSpacesScreen: View {
|
|||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
|
|
||||||
struct ManageAuthorizedSpacesScreen_Previews: PreviewProvider, TestablePreview {
|
struct ManageAuthorizedSpacesScreen_Previews: PreviewProvider, TestablePreview {
|
||||||
static let viewModel = ManageAuthorizedSpacesScreenViewModel(authorizedSpacesSelection: .init(joinedParentSpaces: .mockJoinedSpaces2,
|
static let viewModel = ManageAuthorizedSpacesScreenViewModel(authorizedSpacesSelection: .init(joinedSpaces: .mockJoinedSpaces2,
|
||||||
unknownSpacesIDs: ["!unknown-space-id-1",
|
unknownSpacesIDs: ["!unknown-space-id-1",
|
||||||
"!unknown-space-id-2",
|
"!unknown-space-id-2",
|
||||||
"!unknown-space-id-3"],
|
"!unknown-space-id-3"],
|
||||||
|
|||||||
+10
-9
@@ -38,11 +38,12 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
|
|||||||
var canEditJoinRule = false
|
var canEditJoinRule = false
|
||||||
var canEnableEncryption = false
|
var canEnableEncryption = false
|
||||||
var canEditHistoryVisibility = false
|
var canEditHistoryVisibility = false
|
||||||
var joinedParentSpaces: [SpaceRoomProxyProtocol] = []
|
/// The union of joined parent spaces and the joined spaces in the current access type
|
||||||
|
var selectableJoinedSpaces: [SpaceRoomProxyProtocol] = []
|
||||||
|
|
||||||
/// The count of the intersection between the set of joined parent spaces and the set of spaces in the current access type
|
/// The count of the intersection between the set of joined parent spaces and the set of spaces in the current access type
|
||||||
var selectableSpacesCount: Int {
|
var selectableSpacesCount: Int {
|
||||||
Set(joinedParentSpaces.map(\.id) + currentSettings.accessType.spaceIDs).count
|
Set(selectableJoinedSpaces.map(\.id) + currentSettings.accessType.spaceIDs).count
|
||||||
}
|
}
|
||||||
|
|
||||||
private var hasChanges: Bool {
|
private var hasChanges: Bool {
|
||||||
@@ -85,8 +86,8 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
|
|||||||
var spaceMembersDescription: String {
|
var spaceMembersDescription: String {
|
||||||
if isSpaceMembersOptionSelectable {
|
if isSpaceMembersOptionSelectable {
|
||||||
switch spaceSelection {
|
switch spaceSelection {
|
||||||
case .singleJoined(let joinedParentSpace):
|
case .singleJoined(let joinedSpace):
|
||||||
L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionSingleParentDescription(joinedParentSpace.name)
|
L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionSingleParentDescription(joinedSpace.name)
|
||||||
case .singleUnknown(let id):
|
case .singleUnknown(let id):
|
||||||
L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionSingleParentDescription(id)
|
L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionSingleParentDescription(id)
|
||||||
case .multiple, .empty:
|
case .multiple, .empty:
|
||||||
@@ -100,8 +101,8 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
|
|||||||
var askToJoinWithSpaceMembersDescription: String {
|
var askToJoinWithSpaceMembersDescription: String {
|
||||||
if isAskToJoinWithSpaceMembersOptionSelectable {
|
if isAskToJoinWithSpaceMembersOptionSelectable {
|
||||||
switch spaceSelection {
|
switch spaceSelection {
|
||||||
case .singleJoined(let joinedParentSpace):
|
case .singleJoined(let joinedSpace):
|
||||||
L10n.screenSecurityAndPrivacyAskToJoinSingleSpaceMembersOptionDescription(joinedParentSpace.name)
|
L10n.screenSecurityAndPrivacyAskToJoinSingleSpaceMembersOptionDescription(joinedSpace.name)
|
||||||
case .singleUnknown(let id):
|
case .singleUnknown(let id):
|
||||||
L10n.screenSecurityAndPrivacyAskToJoinSingleSpaceMembersOptionDescription(id)
|
L10n.screenSecurityAndPrivacyAskToJoinSingleSpaceMembersOptionDescription(id)
|
||||||
case .multiple, .empty:
|
case .multiple, .empty:
|
||||||
@@ -140,17 +141,17 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
|
|||||||
.empty
|
.empty
|
||||||
} else if selectableSpacesCount > 1 {
|
} else if selectableSpacesCount > 1 {
|
||||||
.multiple
|
.multiple
|
||||||
} else if let joinedParent = joinedParentSpaces.first {
|
} else if let joinedSpace = selectableJoinedSpaces.first {
|
||||||
if currentSettings.accessType.isSpaceMembers || currentSettings.accessType.isAskToJoinWithSpaceMembers {
|
if currentSettings.accessType.isSpaceMembers || currentSettings.accessType.isAskToJoinWithSpaceMembers {
|
||||||
if currentSettings.accessType.spaceIDs.isEmpty {
|
if currentSettings.accessType.spaceIDs.isEmpty {
|
||||||
// Edge case where the access type is already space members, but it does not contain any id
|
// Edge case where the access type is already space members, but it does not contain any id
|
||||||
// So if the user wants to add their own parent they need to do it from the selection menu
|
// So if the user wants to add their own parent they need to do it from the selection menu
|
||||||
.multiple
|
.multiple
|
||||||
} else {
|
} else {
|
||||||
.singleJoined(joinedParent)
|
.singleJoined(joinedSpace)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
.singleJoined(joinedParent)
|
.singleJoined(joinedSpace)
|
||||||
}
|
}
|
||||||
} else if let unknownSpaceID = currentSettings.accessType.spaceIDs.first {
|
} else if let unknownSpaceID = currentSettings.accessType.spaceIDs.first {
|
||||||
// The space is not joined by the user but is currently selected
|
// The space is not joined by the user but is currently selected
|
||||||
|
|||||||
+32
-13
@@ -47,12 +47,7 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
|||||||
setupRoomDirectoryVisibility()
|
setupRoomDirectoryVisibility()
|
||||||
setupSubscriptions()
|
setupSubscriptions()
|
||||||
Task {
|
Task {
|
||||||
switch await clientProxy.spaceService.joinedParents(childID: roomProxy.id) {
|
await setupSelectableJoinedSpaces()
|
||||||
case .success(let joinedParentSpaces):
|
|
||||||
state.joinedParentSpaces = joinedParentSpaces
|
|
||||||
case .failure:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,6 +228,8 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
|||||||
|
|
||||||
if shouldDismiss, !hasFailures {
|
if shouldDismiss, !hasFailures {
|
||||||
actionsSubject.send(.dismiss)
|
actionsSubject.send(.dismiss)
|
||||||
|
} else if !shouldDismiss {
|
||||||
|
await setupSelectableJoinedSpaces()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,8 +240,8 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch state.spaceSelection {
|
switch state.spaceSelection {
|
||||||
case .singleJoined(let joinedParent):
|
case .singleJoined(let joinedSpace):
|
||||||
state.bindings.desiredSettings.accessType = .spaceMembers(spaceIDs: [joinedParent.id])
|
state.bindings.desiredSettings.accessType = .spaceMembers(spaceIDs: [joinedSpace.id])
|
||||||
case .singleUnknown(let id):
|
case .singleUnknown(let id):
|
||||||
state.bindings.desiredSettings.accessType = .spaceMembers(spaceIDs: [id])
|
state.bindings.desiredSettings.accessType = .spaceMembers(spaceIDs: [id])
|
||||||
case .empty:
|
case .empty:
|
||||||
@@ -261,8 +258,8 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch state.spaceSelection {
|
switch state.spaceSelection {
|
||||||
case .singleJoined(let joinedParent):
|
case .singleJoined(let joinedSpace):
|
||||||
state.bindings.desiredSettings.accessType = .askToJoinWithSpaceMembers(spaceIDs: [joinedParent.id])
|
state.bindings.desiredSettings.accessType = .askToJoinWithSpaceMembers(spaceIDs: [joinedSpace.id])
|
||||||
case .singleUnknown(let id):
|
case .singleUnknown(let id):
|
||||||
state.bindings.desiredSettings.accessType = .askToJoinWithSpaceMembers(spaceIDs: [id])
|
state.bindings.desiredSettings.accessType = .askToJoinWithSpaceMembers(spaceIDs: [id])
|
||||||
case .empty:
|
case .empty:
|
||||||
@@ -273,12 +270,12 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func displayManageAuthorizedSpacesScreen(isAskToJoin: Bool) {
|
private func displayManageAuthorizedSpacesScreen(isAskToJoin: Bool) {
|
||||||
let joinedParentSpaces = state.joinedParentSpaces
|
let joinedSpaces = state.selectableJoinedSpaces
|
||||||
let unknownSpaceIDs = state.currentSettings.accessType.spaceIDs.filter { id in
|
let unknownSpaceIDs = state.currentSettings.accessType.spaceIDs.filter { id in
|
||||||
!joinedParentSpaces.contains { $0.id == id }
|
!joinedSpaces.contains { $0.id == id }
|
||||||
}
|
}
|
||||||
let selectedIDs = Set(state.bindings.desiredSettings.accessType.spaceIDs)
|
let selectedIDs = Set(state.bindings.desiredSettings.accessType.spaceIDs)
|
||||||
let authorizedSpacesSelection = AuthorizedSpacesSelection(joinedParentSpaces: joinedParentSpaces,
|
let authorizedSpacesSelection = AuthorizedSpacesSelection(joinedSpaces: joinedSpaces,
|
||||||
unknownSpacesIDs: unknownSpaceIDs,
|
unknownSpacesIDs: unknownSpaceIDs,
|
||||||
initialSelectedIDs: selectedIDs)
|
initialSelectedIDs: selectedIDs)
|
||||||
authorizedSpacesSelection.selectedIDs
|
authorizedSpacesSelection.selectedIDs
|
||||||
@@ -291,6 +288,28 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
|||||||
actionsSubject.send(.displayManageAuthorizedSpacesScreen(authorizedSpacesSelection))
|
actionsSubject.send(.displayManageAuthorizedSpacesScreen(authorizedSpacesSelection))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func setupSelectableJoinedSpaces() async {
|
||||||
|
var joinedParentSpaces: [SpaceRoomProxyProtocol] = []
|
||||||
|
switch await clientProxy.spaceService.joinedParents(childID: roomProxy.id) {
|
||||||
|
case .success(let value):
|
||||||
|
joinedParentSpaces = value
|
||||||
|
case .failure:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var nonParentJoinedSpaces: [SpaceRoomProxyProtocol] = []
|
||||||
|
for spaceID in state.currentSettings.accessType.spaceIDs where !joinedParentSpaces.contains(where: { $0.id == spaceID }) {
|
||||||
|
if case let .success(.some(space)) = await clientProxy.spaceService.spaceForIdentifier(spaceID: spaceID) {
|
||||||
|
nonParentJoinedSpaces.append(space)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default we only want to allow selection among joined parents but
|
||||||
|
// if there is a non parent joined space already set in the access type
|
||||||
|
// we also include it in the known spaces selection list.
|
||||||
|
state.selectableJoinedSpaces = joinedParentSpaces + nonParentJoinedSpaces
|
||||||
|
}
|
||||||
|
|
||||||
private static let loadingIndicatorIdentifier = "\(EditRoomAddressScreenViewModel.self)-Loading"
|
private static let loadingIndicatorIdentifier = "\(EditRoomAddressScreenViewModel.self)-Loading"
|
||||||
|
|
||||||
private func showLoadingIndicator() {
|
private func showLoadingIndicator() {
|
||||||
|
|||||||
@@ -40,6 +40,15 @@ class SpaceServiceProxy: SpaceServiceProxyProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func spaceForIdentifier(spaceID: String) async -> Result<SpaceRoomProxyProtocol?, SpaceServiceProxyError> {
|
||||||
|
do {
|
||||||
|
return try await .success(spaceService.getSpaceRoom(roomId: spaceID).map(SpaceRoomProxy.init))
|
||||||
|
} catch {
|
||||||
|
MXLog.error("Failed getting space room for \(spaceID): \(error)")
|
||||||
|
return .failure(.sdkError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func leaveSpace(spaceID: String) async -> Result<LeaveSpaceHandleProxy, SpaceServiceProxyError> {
|
func leaveSpace(spaceID: String) async -> Result<LeaveSpaceHandleProxy, SpaceServiceProxyError> {
|
||||||
do {
|
do {
|
||||||
return try await .success(.init(spaceID: spaceID, leaveHandle: spaceService.leaveSpace(spaceId: spaceID)))
|
return try await .success(.init(spaceID: spaceID, leaveHandle: spaceService.leaveSpace(spaceId: spaceID)))
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ protocol SpaceServiceProxyProtocol {
|
|||||||
var joinedSpacesPublisher: CurrentValuePublisher<[SpaceRoomProxyProtocol], Never> { get }
|
var joinedSpacesPublisher: CurrentValuePublisher<[SpaceRoomProxyProtocol], Never> { get }
|
||||||
|
|
||||||
func spaceRoomList(spaceID: String) async -> Result<SpaceRoomListProxyProtocol, SpaceServiceProxyError>
|
func spaceRoomList(spaceID: String) async -> Result<SpaceRoomListProxyProtocol, SpaceServiceProxyError>
|
||||||
|
/// Returns a joined space given its identifier
|
||||||
|
func spaceForIdentifier(spaceID: String) async -> Result<SpaceRoomProxyProtocol?, SpaceServiceProxyError>
|
||||||
func leaveSpace(spaceID: String) async -> Result<LeaveSpaceHandleProxy, SpaceServiceProxyError>
|
func leaveSpace(spaceID: String) async -> Result<LeaveSpaceHandleProxy, SpaceServiceProxyError>
|
||||||
/// Returns all the parent spaces of a child that user has joined.
|
/// Returns all the parent spaces of a child that user has joined.
|
||||||
func joinedParents(childID: String) async -> Result<[SpaceRoomProxyProtocol], SpaceServiceProxyError>
|
func joinedParents(childID: String) async -> Result<[SpaceRoomProxyProtocol], SpaceServiceProxyError>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import XCTest
|
|||||||
@MainActor
|
@MainActor
|
||||||
class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
||||||
var viewModel: SecurityAndPrivacyScreenViewModelProtocol!
|
var viewModel: SecurityAndPrivacyScreenViewModelProtocol!
|
||||||
|
var spaceServiceProxy: SpaceServiceProxyMock!
|
||||||
var roomProxy: JoinedRoomProxyMock!
|
var roomProxy: JoinedRoomProxyMock!
|
||||||
|
|
||||||
var context: SecurityAndPrivacyScreenViewModelType.Context {
|
var context: SecurityAndPrivacyScreenViewModelType.Context {
|
||||||
@@ -32,7 +33,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
|||||||
let space = singleRoom[0]
|
let space = singleRoom[0]
|
||||||
setupViewModel(joinedParentSpaces: singleRoom, joinRule: .public)
|
setupViewModel(joinedParentSpaces: singleRoom, joinRule: .public)
|
||||||
|
|
||||||
let deferred = deferFulfillment(context.$viewState) { $0.joinedParentSpaces.count == 1 }
|
let deferred = deferFulfillment(context.$viewState) { $0.selectableJoinedSpaces.count == 1 }
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
XCTAssertEqual(context.viewState.currentSettings.accessType, .anyone)
|
XCTAssertEqual(context.viewState.currentSettings.accessType, .anyone)
|
||||||
@@ -63,7 +64,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
|||||||
let space = singleRoom[0]
|
let space = singleRoom[0]
|
||||||
setupViewModel(joinedParentSpaces: singleRoom, joinRule: .public)
|
setupViewModel(joinedParentSpaces: singleRoom, joinRule: .public)
|
||||||
|
|
||||||
let deferred = deferFulfillment(context.$viewState) { $0.joinedParentSpaces.count == 1 }
|
let deferred = deferFulfillment(context.$viewState) { $0.selectableJoinedSpaces.count == 1 }
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
XCTAssertEqual(context.viewState.currentSettings.accessType, .anyone)
|
XCTAssertEqual(context.viewState.currentSettings.accessType, .anyone)
|
||||||
@@ -94,7 +95,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
|||||||
let space = singleRoom[0]
|
let space = singleRoom[0]
|
||||||
setupViewModel(joinedParentSpaces: [], joinRule: .restricted(rules: [.roomMembership(roomId: space.id)]))
|
setupViewModel(joinedParentSpaces: [], joinRule: .restricted(rules: [.roomMembership(roomId: space.id)]))
|
||||||
|
|
||||||
let deferred = deferFulfillment(context.$viewState) { $0.joinedParentSpaces.count == 0 }
|
let deferred = deferFulfillment(context.$viewState) { $0.selectableJoinedSpaces.count == 0 }
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
XCTAssertEqual(context.viewState.currentSettings.accessType, .spaceMembers(spaceIDs: [space.id]))
|
XCTAssertEqual(context.viewState.currentSettings.accessType, .spaceMembers(spaceIDs: [space.id]))
|
||||||
@@ -124,7 +125,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
|||||||
let spaces = [SpaceRoomProxyProtocol].mockJoinedSpaces2
|
let spaces = [SpaceRoomProxyProtocol].mockJoinedSpaces2
|
||||||
setupViewModel(joinedParentSpaces: spaces, joinRule: .public)
|
setupViewModel(joinedParentSpaces: spaces, joinRule: .public)
|
||||||
|
|
||||||
let deferred = deferFulfillment(context.$viewState) { $0.joinedParentSpaces.count == 3 }
|
let deferred = deferFulfillment(context.$viewState) { $0.selectableJoinedSpaces.count == 3 }
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
XCTAssertEqual(context.viewState.currentSettings.accessType, .anyone)
|
XCTAssertEqual(context.viewState.currentSettings.accessType, .anyone)
|
||||||
@@ -140,7 +141,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
|||||||
switch action {
|
switch action {
|
||||||
case .displayManageAuthorizedSpacesScreen(let authorizedSpacesSelection):
|
case .displayManageAuthorizedSpacesScreen(let authorizedSpacesSelection):
|
||||||
defer { selectedIDs = authorizedSpacesSelection.selectedIDs }
|
defer { selectedIDs = authorizedSpacesSelection.selectedIDs }
|
||||||
return authorizedSpacesSelection.joinedParentSpaces.map(\.id) == spaces.map(\.id) &&
|
return authorizedSpacesSelection.joinedSpaces.map(\.id) == spaces.map(\.id) &&
|
||||||
authorizedSpacesSelection.unknownSpacesIDs.isEmpty &&
|
authorizedSpacesSelection.unknownSpacesIDs.isEmpty &&
|
||||||
authorizedSpacesSelection.initialSelectedIDs.isEmpty
|
authorizedSpacesSelection.initialSelectedIDs.isEmpty
|
||||||
default:
|
default:
|
||||||
@@ -168,7 +169,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
|||||||
let spaces = [SpaceRoomProxyProtocol].mockJoinedSpaces2
|
let spaces = [SpaceRoomProxyProtocol].mockJoinedSpaces2
|
||||||
setupViewModel(joinedParentSpaces: spaces, joinRule: .public)
|
setupViewModel(joinedParentSpaces: spaces, joinRule: .public)
|
||||||
|
|
||||||
let deferred = deferFulfillment(context.$viewState) { $0.joinedParentSpaces.count == 3 }
|
let deferred = deferFulfillment(context.$viewState) { $0.selectableJoinedSpaces.count == 3 }
|
||||||
try await deferred.fulfill()
|
try await deferred.fulfill()
|
||||||
|
|
||||||
XCTAssertEqual(context.viewState.currentSettings.accessType, .anyone)
|
XCTAssertEqual(context.viewState.currentSettings.accessType, .anyone)
|
||||||
@@ -184,7 +185,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
|||||||
switch action {
|
switch action {
|
||||||
case .displayManageAuthorizedSpacesScreen(let authorizedSpacesSelection):
|
case .displayManageAuthorizedSpacesScreen(let authorizedSpacesSelection):
|
||||||
defer { selectedIDs = authorizedSpacesSelection.selectedIDs }
|
defer { selectedIDs = authorizedSpacesSelection.selectedIDs }
|
||||||
return authorizedSpacesSelection.joinedParentSpaces.map(\.id) == spaces.map(\.id) &&
|
return authorizedSpacesSelection.joinedSpaces.map(\.id) == spaces.map(\.id) &&
|
||||||
authorizedSpacesSelection.unknownSpacesIDs.isEmpty &&
|
authorizedSpacesSelection.unknownSpacesIDs.isEmpty &&
|
||||||
authorizedSpacesSelection.initialSelectedIDs.isEmpty
|
authorizedSpacesSelection.initialSelectedIDs.isEmpty
|
||||||
default:
|
default:
|
||||||
@@ -230,7 +231,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
|||||||
case .displayManageAuthorizedSpacesScreen(let authorizedSpacesSelection):
|
case .displayManageAuthorizedSpacesScreen(let authorizedSpacesSelection):
|
||||||
// We need the
|
// We need the
|
||||||
defer { selectedIDs = authorizedSpacesSelection.selectedIDs }
|
defer { selectedIDs = authorizedSpacesSelection.selectedIDs }
|
||||||
return authorizedSpacesSelection.joinedParentSpaces.map(\.id) == spaces.map(\.id) &&
|
return authorizedSpacesSelection.joinedSpaces.map(\.id) == spaces.map(\.id) &&
|
||||||
authorizedSpacesSelection.unknownSpacesIDs == ["unknownSpaceID"] &&
|
authorizedSpacesSelection.unknownSpacesIDs == ["unknownSpaceID"] &&
|
||||||
authorizedSpacesSelection.initialSelectedIDs == ["unknownSpaceID"]
|
authorizedSpacesSelection.initialSelectedIDs == ["unknownSpaceID"]
|
||||||
default:
|
default:
|
||||||
@@ -254,6 +255,48 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
|||||||
await fulfillment(of: [expectation])
|
await fulfillment(of: [expectation])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testMultipleSpacesMembersSelectionWithAnExistingNonParentButJoinedSpace() async throws {
|
||||||
|
let joinedParentSpaces = [SpaceRoomProxyProtocol].mockJoinedSpaces2
|
||||||
|
let singleRoom = [SpaceRoomProxyProtocol].mockSingleRoom
|
||||||
|
let space = singleRoom[0]
|
||||||
|
let allSpaces = joinedParentSpaces + singleRoom
|
||||||
|
setupViewModel(joinedParentSpaces: joinedParentSpaces,
|
||||||
|
joinedSpaces: allSpaces,
|
||||||
|
joinRule: .restricted(rules: [.roomMembership(roomId: space.id),
|
||||||
|
.roomMembership(roomId: "unknownSpaceID")]))
|
||||||
|
|
||||||
|
let deferred = deferFulfillment(context.$viewState) { $0.selectableSpacesCount == 5 }
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
XCTAssertTrue(context.viewState.currentSettings.accessType.isSpaceMembers)
|
||||||
|
XCTAssertTrue(context.viewState.isSaveDisabled)
|
||||||
|
XCTAssertTrue(context.viewState.isSpaceMembersOptionSelectable)
|
||||||
|
guard case .multiple = context.viewState.spaceSelection else {
|
||||||
|
XCTFail("Expected spaceSelection to be .multiple")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedIDs: PassthroughSubject<Set<String>, Never>!
|
||||||
|
let deferredAction = deferFulfillment(viewModel.actionsPublisher) { action in
|
||||||
|
switch action {
|
||||||
|
case .displayManageAuthorizedSpacesScreen(let authorizedSpacesSelection):
|
||||||
|
// We need the
|
||||||
|
defer { selectedIDs = authorizedSpacesSelection.selectedIDs }
|
||||||
|
return authorizedSpacesSelection.joinedSpaces.map(\.id) == allSpaces.map(\.id) &&
|
||||||
|
authorizedSpacesSelection.unknownSpacesIDs == ["unknownSpaceID"] &&
|
||||||
|
authorizedSpacesSelection.initialSelectedIDs == [space.id, "unknownSpaceID"]
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.send(viewAction: .manageSpaces)
|
||||||
|
try await deferredAction.fulfill()
|
||||||
|
selectedIDs.send([allSpaces[0].id, "unknownSpaceID"])
|
||||||
|
XCTAssertEqual(context.desiredSettings.accessType, .spaceMembers(spaceIDs: [allSpaces[0].id, "unknownSpaceID"]))
|
||||||
|
XCTAssertNotNil(context.viewState.accessSectionFooter)
|
||||||
|
XCTAssertFalse(context.viewState.isSaveDisabled)
|
||||||
|
}
|
||||||
|
|
||||||
func testEmptySpaceMembersSelectionEdgeCase() async throws {
|
func testEmptySpaceMembersSelectionEdgeCase() async throws {
|
||||||
// Edge case where there is no available joined parents and the room has a restricted join rule.
|
// Edge case where there is no available joined parents and the room has a restricted join rule.
|
||||||
// With no space ids in it
|
// With no space ids in it
|
||||||
@@ -295,7 +338,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
|||||||
let deferredAction = deferFulfillment(viewModel.actionsPublisher) { action in
|
let deferredAction = deferFulfillment(viewModel.actionsPublisher) { action in
|
||||||
switch action {
|
switch action {
|
||||||
case .displayManageAuthorizedSpacesScreen(let authorizedSpacesSelection):
|
case .displayManageAuthorizedSpacesScreen(let authorizedSpacesSelection):
|
||||||
return authorizedSpacesSelection.joinedParentSpaces.map(\.id) == singleRoom.map(\.id) &&
|
return authorizedSpacesSelection.joinedSpaces.map(\.id) == singleRoom.map(\.id) &&
|
||||||
authorizedSpacesSelection.unknownSpacesIDs.isEmpty &&
|
authorizedSpacesSelection.unknownSpacesIDs.isEmpty &&
|
||||||
authorizedSpacesSelection.initialSelectedIDs.isEmpty
|
authorizedSpacesSelection.initialSelectedIDs.isEmpty
|
||||||
default:
|
default:
|
||||||
@@ -382,6 +425,7 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
|||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
private func setupViewModel(joinedParentSpaces: [SpaceRoomProxyProtocol],
|
private func setupViewModel(joinedParentSpaces: [SpaceRoomProxyProtocol],
|
||||||
|
joinedSpaces: [SpaceRoomProxyProtocol] = [],
|
||||||
joinRule: JoinRule) {
|
joinRule: JoinRule) {
|
||||||
let appSettings = AppSettings()
|
let appSettings = AppSettings()
|
||||||
appSettings.spaceSettingsEnabled = true
|
appSettings.spaceSettingsEnabled = true
|
||||||
@@ -396,7 +440,8 @@ class SecurityAndPrivacyScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
viewModel = SecurityAndPrivacyScreenViewModel(roomProxy: roomProxy,
|
viewModel = SecurityAndPrivacyScreenViewModel(roomProxy: roomProxy,
|
||||||
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org",
|
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org",
|
||||||
spaceServiceConfiguration: .init(joinedParentSpaces: joinedParentSpaces))),
|
spaceServiceConfiguration: .init(joinedSpaces: joinedSpaces,
|
||||||
|
joinedParentSpaces: joinedParentSpaces))),
|
||||||
userIndicatorController: UserIndicatorControllerMock(),
|
userIndicatorController: UserIndicatorControllerMock(),
|
||||||
appSettings: appSettings)
|
appSettings: appSettings)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -71,7 +71,7 @@ packages:
|
|||||||
# Element/Matrix dependencies
|
# Element/Matrix dependencies
|
||||||
MatrixRustSDK:
|
MatrixRustSDK:
|
||||||
url: https://github.com/element-hq/matrix-rust-components-swift
|
url: https://github.com/element-hq/matrix-rust-components-swift
|
||||||
exactVersion: 25.12.10
|
exactVersion: 25.12.11
|
||||||
# path: ../matrix-rust-sdk
|
# path: ../matrix-rust-sdk
|
||||||
Compound:
|
Compound:
|
||||||
path: compound-ios
|
path: compound-ios
|
||||||
|
|||||||
Reference in New Issue
Block a user