single space access implementation

also added the copies for the single and multiple spaces selection
This commit is contained in:
Mauro Romito
2025-11-25 17:55:23 +01:00
committed by Mauro
parent d92ef7895f
commit 2292f4bd4f
10 changed files with 337 additions and 41 deletions

View File

@@ -648,11 +648,15 @@
"screen_security_and_privacy_encryption_section_footer" = "Once enabled, encryption cannot be disabled.";
"screen_security_and_privacy_encryption_toggle_title" = "Enable end-to-end encryption";
"screen_security_and_privacy_room_access_anyone_option_description" = "Anyone can join.";
"screen_security_and_privacy_room_access_footer" = "Choose which spaces members can join this room without an invitation. %1$@";
"screen_security_and_privacy_room_access_footer_manage_spaces_action" = "Manage spaces";
"screen_security_and_privacy_room_access_invite_only_option_description" = "Only invited people can join.";
"screen_security_and_privacy_room_access_invite_only_option_title" = "Invite only";
"screen_security_and_privacy_room_access_section_header" = "Access";
"screen_security_and_privacy_room_access_space_members_option_description" = "Spaces are not currently supported";
"screen_security_and_privacy_room_access_space_members_option_multiple_parents_description" = "Anyone in authorized spaces can join.";
"screen_security_and_privacy_room_access_space_members_option_single_parent_description" = "Anyone in %1$@ can join.";
"screen_security_and_privacy_room_access_space_members_option_title" = "Space members";
"screen_security_and_privacy_room_access_space_members_option_unavailable_description" = "Spaces are not currently supported";
"screen_security_and_privacy_room_address_section_header" = "Address";
"screen_security_and_privacy_room_directory_visibility_section_footer" = "Allow for this room to be found by searching %1$@ public room directory";
"screen_security_and_privacy_room_directory_visibility_toggle_description" = "Allow to be found by searching the public directory.";

View File

@@ -648,11 +648,15 @@
"screen_security_and_privacy_encryption_section_footer" = "Once enabled, encryption cannot be disabled.";
"screen_security_and_privacy_encryption_toggle_title" = "Enable end-to-end encryption";
"screen_security_and_privacy_room_access_anyone_option_description" = "Anyone can join.";
"screen_security_and_privacy_room_access_footer" = "Choose which spaces members can join this room without an invitation. %1$@";
"screen_security_and_privacy_room_access_footer_manage_spaces_action" = "Manage spaces";
"screen_security_and_privacy_room_access_invite_only_option_description" = "Only invited people can join.";
"screen_security_and_privacy_room_access_invite_only_option_title" = "Invite only";
"screen_security_and_privacy_room_access_section_header" = "Access";
"screen_security_and_privacy_room_access_space_members_option_description" = "Spaces are not currently supported";
"screen_security_and_privacy_room_access_space_members_option_multiple_parents_description" = "Anyone in authorized spaces can join.";
"screen_security_and_privacy_room_access_space_members_option_single_parent_description" = "Anyone in %1$@ can join.";
"screen_security_and_privacy_room_access_space_members_option_title" = "Space members";
"screen_security_and_privacy_room_access_space_members_option_unavailable_description" = "Spaces are not currently supported";
"screen_security_and_privacy_room_address_section_header" = "Address";
"screen_security_and_privacy_room_directory_visibility_section_footer" = "Allow for this room to be found by searching %1$@ public room directory";
"screen_security_and_privacy_room_directory_visibility_toggle_description" = "Allow to be found by searching the public directory.";

View File

@@ -2768,16 +2768,28 @@ internal enum L10n {
internal static var screenSecurityAndPrivacyRoomAccessAnyoneOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_anyone_option_description") }
/// Anyone
internal static var screenSecurityAndPrivacyRoomAccessAnyoneOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_anyone_option_title") }
/// Choose which spaces members can join this room without an invitation. %1$@
internal static func screenSecurityAndPrivacyRoomAccessFooter(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_security_and_privacy_room_access_footer", String(describing: p1))
}
/// Manage spaces
internal static var screenSecurityAndPrivacyRoomAccessFooterManageSpacesAction: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_footer_manage_spaces_action") }
/// Only invited people can join.
internal static var screenSecurityAndPrivacyRoomAccessInviteOnlyOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_invite_only_option_description") }
/// Invite only
internal static var screenSecurityAndPrivacyRoomAccessInviteOnlyOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_invite_only_option_title") }
/// Access
internal static var screenSecurityAndPrivacyRoomAccessSectionHeader: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_section_header") }
/// Spaces are not currently supported
internal static var screenSecurityAndPrivacyRoomAccessSpaceMembersOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_space_members_option_description") }
/// Anyone in authorized spaces can join.
internal static var screenSecurityAndPrivacyRoomAccessSpaceMembersOptionMultipleParentsDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_space_members_option_multiple_parents_description") }
/// Anyone in %1$@ can join.
internal static func screenSecurityAndPrivacyRoomAccessSpaceMembersOptionSingleParentDescription(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_security_and_privacy_room_access_space_members_option_single_parent_description", String(describing: p1))
}
/// Space members
internal static var screenSecurityAndPrivacyRoomAccessSpaceMembersOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_space_members_option_title") }
/// Spaces are not currently supported
internal static var screenSecurityAndPrivacyRoomAccessSpaceMembersOptionUnavailableDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_space_members_option_unavailable_description") }
/// Youll need an address in order to make it visible in the public directory.
internal static var screenSecurityAndPrivacyRoomAddressSectionFooter: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_address_section_footer") }
/// Address

View File

@@ -16616,6 +16616,76 @@ class SpaceServiceProxyMock: SpaceServiceProxyProtocol, @unchecked Sendable {
return leaveSpaceSpaceIDReturnValue
}
}
//MARK: - joinedParents
var joinedParentsRoomIDUnderlyingCallsCount = 0
var joinedParentsRoomIDCallsCount: Int {
get {
if Thread.isMainThread {
return joinedParentsRoomIDUnderlyingCallsCount
} else {
var returnValue: Int? = nil
DispatchQueue.main.sync {
returnValue = joinedParentsRoomIDUnderlyingCallsCount
}
return returnValue!
}
}
set {
if Thread.isMainThread {
joinedParentsRoomIDUnderlyingCallsCount = newValue
} else {
DispatchQueue.main.sync {
joinedParentsRoomIDUnderlyingCallsCount = newValue
}
}
}
}
var joinedParentsRoomIDCalled: Bool {
return joinedParentsRoomIDCallsCount > 0
}
var joinedParentsRoomIDReceivedRoomID: String?
var joinedParentsRoomIDReceivedInvocations: [String] = []
var joinedParentsRoomIDUnderlyingReturnValue: Result<[SpaceRoomProxyProtocol], SpaceServiceProxyError>!
var joinedParentsRoomIDReturnValue: Result<[SpaceRoomProxyProtocol], SpaceServiceProxyError>! {
get {
if Thread.isMainThread {
return joinedParentsRoomIDUnderlyingReturnValue
} else {
var returnValue: Result<[SpaceRoomProxyProtocol], SpaceServiceProxyError>? = nil
DispatchQueue.main.sync {
returnValue = joinedParentsRoomIDUnderlyingReturnValue
}
return returnValue!
}
}
set {
if Thread.isMainThread {
joinedParentsRoomIDUnderlyingReturnValue = newValue
} else {
DispatchQueue.main.sync {
joinedParentsRoomIDUnderlyingReturnValue = newValue
}
}
}
}
var joinedParentsRoomIDClosure: ((String) async -> Result<[SpaceRoomProxyProtocol], SpaceServiceProxyError>)?
func joinedParents(roomID: String) async -> Result<[SpaceRoomProxyProtocol], SpaceServiceProxyError> {
joinedParentsRoomIDCallsCount += 1
joinedParentsRoomIDReceivedRoomID = roomID
DispatchQueue.main.async {
self.joinedParentsRoomIDReceivedInvocations.append(roomID)
}
if let joinedParentsRoomIDClosure = joinedParentsRoomIDClosure {
return await joinedParentsRoomIDClosure(roomID)
} else {
return joinedParentsRoomIDReturnValue
}
}
}
class StaticRoomSummaryProviderMock: StaticRoomSummaryProviderProtocol, @unchecked Sendable {
var statePublisher: CurrentValuePublisher<RoomSummaryProviderState, Never> {

View File

@@ -13,6 +13,7 @@ import MatrixRustSDK
extension SpaceServiceProxyMock {
struct Configuration {
var joinedSpaces: [SpaceRoomProxyProtocol] = []
var joinedParentSpaces: [SpaceRoomProxyProtocol] = []
var spaceRoomLists: [String: SpaceRoomListProxyMock] = [:]
var leaveSpaceRooms: [LeaveSpaceRoom] = []
}
@@ -21,6 +22,7 @@ extension SpaceServiceProxyMock {
self.init()
joinedSpacesPublisher = .init(configuration.joinedSpaces)
joinedParentsRoomIDReturnValue = .success(configuration.joinedParentSpaces)
spaceRoomListSpaceIDClosure = { spaceID in
if let spaceRoomList = configuration.spaceRoomLists[spaceID] {
.success(spaceRoomList)

View File

@@ -7,23 +7,45 @@
//
import Foundation
import MatrixRustSDK
enum SecurityAndPrivacyScreenViewModelAction {
case displayEditAddressScreen
}
struct SecurityAndPrivacyScreenViewState: BindableState {
private static let accessSectionFooterAttributedString = {
let linkPlaceholder = "{link}"
var footer = AttributedString(L10n.screenSecurityAndPrivacyRoomAccessFooter(linkPlaceholder))
var linkString = AttributedString(L10n.screenSecurityAndPrivacyRoomAccessFooterManageSpacesAction)
// Doesn't really matter
linkString.link = .init(stringLiteral: "action://manageSpace")
linkString.bold()
footer.replace(linkPlaceholder, with: linkString)
return footer
}()
let serverName: String
var currentSettings: SecurityAndPrivacySettings
var bindings: SecurityAndPrivacyScreenViewStateBindings
var canonicalAlias: String?
var isKnockingEnabled: Bool
var isSpaceSettingsEnabled: Bool
var isSpace: Bool
var canEditAddress = false
var canEditJoinRule = false
var canEnableEncryption = false
var canEditHistoryVisibility = false
var joinedParentSpaces: [SpaceRoomProxyProtocol] = []
var selectableSpacesCount: Int {
Set(joinedParentSpaces.map(\.id) + currentSettings.accessType.spaceIDs).count
}
private var desiredJoinedParentSpaces: [SpaceRoomProxyProtocol] {
joinedParentSpaces.filter { bindings.desiredSettings.accessType.spaceIDs.contains($0.id) }
}
private var hasChanges: Bool {
currentSettings != bindings.desiredSettings
@@ -45,16 +67,77 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
}
return options
}
var isSpaceMembersOptionAvailable: Bool {
currentSettings.accessType.isSpaceUsers || isSpaceMembersOptionSelectable
}
var isSpaceMembersOptionSelectable: Bool {
isSpaceSettingsEnabled && selectableSpacesCount > 0
}
var spaceMembersDescription: String {
if isSpaceMembersOptionSelectable {
switch spaceSelection {
case .singleJoined(let joinedParentSpace):
L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionSingleParentDescription(joinedParentSpace.name)
case .singleUnknown(let id):
L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionSingleParentDescription(id)
case .multiple:
L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionMultipleParentsDescription
}
} else {
L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionUnavailableDescription
}
}
var accessSectionFooter: AttributedString? {
if bindings.desiredSettings.accessType.isSpaceUsers, isSpaceMembersOptionSelectable, selectableSpacesCount > 1 {
Self.accessSectionFooterAttributedString
} else {
nil
}
}
enum SpaceSelection {
/// There is only one available parent space for selection and is joined by the user
case singleJoined(SpaceRoomProxyProtocol)
/// There is only one available space for selection and is unknown to the user
case singleUnknown(id: String)
/// Multiple spaces are available for selection
case multiple
}
var spaceSelection: SpaceSelection {
if selectableSpacesCount > 1 {
.multiple
} else if let desiredJoinedParent = desiredJoinedParentSpaces.first {
// The parent space is joined by the user and is also currently selected
.singleJoined(desiredJoinedParent)
} else if let joinedParent = joinedParentSpaces.first {
// The parent space is joined by the user but is not currently selected
.singleJoined(joinedParent)
} else if let unknownSpaceID = bindings.desiredSettings.accessType.spaceIDs.first {
// The space is not joined by the user but is currently selected
.singleUnknown(id: unknownSpaceID)
} else {
// Not reachable because it would mean the selectable spaces are more than 1
// but are neither selected and/or joined parents.
fatalError("Not reachable")
}
}
init(serverName: String,
accessType: SecurityAndPrivacyRoomAccessType,
isEncryptionEnabled: Bool,
historyVisibility: SecurityAndPrivacyHistoryVisibility,
isSpace: Bool,
isKnockingEnabled: Bool) {
isKnockingEnabled: Bool,
isSpaceSettingsEnabled: Bool) {
self.serverName = serverName
self.isKnockingEnabled = isKnockingEnabled
self.isSpace = isSpace
self.isSpaceSettingsEnabled = isSpaceSettingsEnabled
let settings = SecurityAndPrivacySettings(accessType: accessType,
isEncryptionEnabled: isEncryptionEnabled,
@@ -76,11 +159,39 @@ struct SecurityAndPrivacySettings: Equatable {
var isVisibileInRoomDirectory: Bool?
}
enum SecurityAndPrivacyRoomAccessType {
enum SecurityAndPrivacyRoomAccessType: Equatable {
case inviteOnly
case askToJoin
case askToJoinWithSpaceUsers(spaceIDs: [String])
case anyone
case spaceUsers
case spaceUsers(spaceIDs: [String])
var isSpaceUsers: Bool {
switch self {
case .spaceUsers:
true
default:
false
}
}
var isAddressRequired: Bool {
switch self {
case .inviteOnly, .spaceUsers:
false
case .anyone, .askToJoin, .askToJoinWithSpaceUsers:
true
}
}
var spaceIDs: [String] {
switch self {
case .spaceUsers(let spaceIDs), .askToJoinWithSpaceUsers(let spaceIDs):
return spaceIDs
case .inviteOnly, .askToJoin, .anyone:
return []
}
}
}
enum SecurityAndPrivacyAlertType {
@@ -91,6 +202,8 @@ enum SecurityAndPrivacyScreenViewAction {
case save
case tryUpdatingEncryption(Bool)
case editAddress
case selectedSpaceMembersAccess
case manageSpaces
}
enum SecurityAndPrivacyHistoryVisibility {

View File

@@ -37,7 +37,8 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
isEncryptionEnabled: roomProxy.infoPublisher.value.isEncrypted,
historyVisibility: roomProxy.infoPublisher.value.historyVisibility.toSecurityAndPrivacyHistoryVisibility,
isSpace: roomProxy.infoPublisher.value.isSpace,
isKnockingEnabled: appSettings.knockingEnabled))
isKnockingEnabled: appSettings.knockingEnabled,
isSpaceSettingsEnabled: appSettings.spaceSettingsEnabled))
if let powerLevels = roomProxy.infoPublisher.value.powerLevels {
setupPermissions(powerLevels: powerLevels)
@@ -45,6 +46,14 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
setupRoomDirectoryVisibility()
setupSubscriptions()
Task {
switch await clientProxy.spaceService.joinedParents(roomID: roomProxy.id) {
case .success(let joinedParentSpaces):
state.joinedParentSpaces = joinedParentSpaces
case .failure:
break
}
}
}
// MARK: - Public
@@ -69,6 +78,11 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
}
case .editAddress:
actionsSubject.send(.displayEditAddressScreen)
case .selectedSpaceMembersAccess:
handleSelectedSpaceMembersAccess()
case .manageSpaces:
// TODO: Implement multiple space selection
break
}
}
@@ -127,6 +141,10 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
appSettings.$knockingEnabled
.weakAssign(to: \.state.isKnockingEnabled, on: self)
.store(in: &cancellables)
appSettings.$spaceSettingsEnabled
.weakAssign(to: \.state.isSpaceSettingsEnabled, on: self)
.store(in: &cancellables)
}
private func setupPermissions(powerLevels: RoomPowerLevelsProxyProtocol) {
@@ -202,6 +220,18 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
}
}
private func handleSelectedSpaceMembersAccess() {
switch context.viewState.spaceSelection {
case .singleJoined(let joinedParent):
context.desiredSettings.accessType = .spaceUsers(spaceIDs: [joinedParent.id])
case .singleUnknown(let id):
context.desiredSettings.accessType = .spaceUsers(spaceIDs: [id])
case .multiple:
// TODO: Implement multiple space selection
break
}
}
private static let loadingIndicatorIdentifier = "\(EditRoomAddressScreenViewModel.self)-Loading"
private func showLoadingIndicator() {
@@ -225,25 +255,10 @@ private extension SecurityAndPrivacyRoomAccessType {
.knock
case .anyone:
.public
case .spaceUsers:
fatalError("The user shouldn't be able to select this rule")
}
}
}
private extension Optional where Wrapped == JoinRule {
var toSecurityAndPrivacyRoomAccessType: SecurityAndPrivacyRoomAccessType {
switch self {
case .none, .public:
return .anyone
case .invite:
return .inviteOnly
case .knock, .knockRestricted:
return .askToJoin
case .restricted:
return .spaceUsers
default:
return .inviteOnly
case .spaceUsers(let spaceIDs):
.restricted(rules: spaceIDs.map { .roomMembership(roomId: $0) })
case .askToJoinWithSpaceUsers(let spaceIDs):
.knockRestricted(rules: spaceIDs.map { .roomMembership(roomId: $0) })
}
}
}
@@ -273,3 +288,28 @@ private extension SecurityAndPrivacyHistoryVisibility {
}
}
}
private extension Optional where Wrapped == JoinRule {
var toSecurityAndPrivacyRoomAccessType: SecurityAndPrivacyRoomAccessType {
switch self {
case .none, .public:
return .anyone
case .invite:
return .inviteOnly
case .knock, .knockRestricted:
// TODO: Handle knock restricted with rules
return .askToJoin
case .restricted(let rules):
let spaceIDs = rules.compactMap { rule in
if case let .roomMembership(id) = rule {
id
} else {
nil
}
}
return .spaceUsers(spaceIDs: spaceIDs)
default:
return .inviteOnly
}
}
}

View File

@@ -18,7 +18,7 @@ struct SecurityAndPrivacyScreen: View {
roomAccessSection
}
if context.desiredSettings.accessType != .inviteOnly, context.viewState.canEditAddress {
if context.desiredSettings.accessType.isAddressRequired, context.viewState.canEditAddress {
visibilitySection
if let canonicalAlias = context.viewState.canonicalAlias {
addressSection(canonicalAlias: canonicalAlias)
@@ -57,13 +57,14 @@ struct SecurityAndPrivacyScreen: View {
.disabled(!context.viewState.isKnockingEnabled)
}
if context.viewState.currentSettings.accessType == .spaceUsers {
if context.viewState.isSpaceMembersOptionAvailable {
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionTitle,
description: L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionDescription,
description: context.viewState.spaceMembersDescription,
icon: \.space),
kind: .selection(isSelected: context.desiredSettings.accessType == .spaceUsers) { })
// This is not supported so it will always be disabled and has no handler
.disabled(true)
kind: .selection(isSelected: context.desiredSettings.accessType.isSpaceUsers) {
context.send(viewAction: .selectedSpaceMembersAccess)
})
.disabled(!context.viewState.isSpaceMembersOptionSelectable)
}
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyRoomAccessInviteOnlyOptionTitle,
@@ -73,6 +74,15 @@ struct SecurityAndPrivacyScreen: View {
} header: {
Text(L10n.screenSecurityAndPrivacyRoomAccessSectionHeader)
.compoundListSectionHeader()
} footer: {
if let footer = context.viewState.accessSectionFooter {
Text(footer)
.compoundListSectionFooter()
.environment(\.openURL, OpenURLAction { _ in
context.send(viewAction: .manageSpaces)
return .handled
})
}
}
}
@@ -218,14 +228,36 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
appSettings: AppSettings())
}()
static let restrictedViewModel = {
static let singleSpaceMembersViewModel = {
AppSettings.resetAllSettings()
let appSettings = AppSettings()
appSettings.spaceSettingsEnabled = true
let space = [SpaceRoomProxyProtocol].mockSingleRoom[0]
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
canonicalAlias: "#room:matrix.org",
members: .allMembersAsCreator,
joinRule: .restricted(rules: []),
joinRule: .restricted(rules: [.roomMembership(roomId: space.id)]),
isVisibleInPublicDirectory: true)),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org",
spaceServiceConfiguration: .init(joinedParentSpaces: [space]))),
userIndicatorController: UserIndicatorControllerMock(),
appSettings: AppSettings())
}()
static let multipleSpacesMembersViewModel = {
AppSettings.resetAllSettings()
let appSettings = AppSettings()
appSettings.spaceSettingsEnabled = true
let spaces = [SpaceRoomProxyProtocol].mockJoinedSpaces
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
canonicalAlias: "#room:matrix.org",
members: .allMembersAsCreator,
joinRule: .restricted(rules: spaces.map { .roomMembership(roomId: $0.id) }),
isVisibleInPublicDirectory: true)),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org",
spaceServiceConfiguration: .init(joinedParentSpaces: spaces))),
userIndicatorController: UserIndicatorControllerMock(),
appSettings: AppSettings())
}()
@@ -278,17 +310,25 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
.previewDisplayName("Public room without address")
NavigationStack {
SecurityAndPrivacyScreen(context: restrictedViewModel.context)
SecurityAndPrivacyScreen(context: singleSpaceMembersViewModel.context)
}
.snapshotPreferences(expect: restrictedViewModel.context.$viewState.map { state in
.snapshotPreferences(expect: singleSpaceMembersViewModel.context.$viewState.map { state in
state.currentSettings.isVisibileInRoomDirectory == true
})
.previewDisplayName("Restricted room")
.previewDisplayName("Space members")
NavigationStack {
SecurityAndPrivacyScreen(context: multipleSpacesMembersViewModel.context)
}
.snapshotPreferences(expect: multipleSpacesMembersViewModel.context.$viewState.map { state in
state.currentSettings.isVisibileInRoomDirectory == true
})
.previewDisplayName("Multiple Spaces members")
NavigationStack {
SecurityAndPrivacyScreen(context: askToJoinViewModel.context)
}
.snapshotPreferences(expect: restrictedViewModel.context.$viewState.map { state in
.snapshotPreferences(expect: askToJoinViewModel.context.$viewState.map { state in
state.currentSettings.isVisibileInRoomDirectory == true
})
.previewDisplayName("Ask to join room")
@@ -296,7 +336,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
NavigationStack {
SecurityAndPrivacyScreen(context: publicSpaceViewModel.context)
}
.snapshotPreferences(expect: restrictedViewModel.context.$viewState.map { state in
.snapshotPreferences(expect: publicSpaceViewModel.context.$viewState.map { state in
state.currentSettings.isVisibileInRoomDirectory == true
})
.previewDisplayName("Public space")

View File

@@ -49,6 +49,15 @@ class SpaceServiceProxy: SpaceServiceProxyProtocol {
}
}
func joinedParents(roomID: String) async -> Result<[SpaceRoomProxyProtocol], SpaceServiceProxyError> {
do {
return try await .success(spaceService.joinedParentsOfChild(childId: roomID).map(SpaceRoomProxy.init))
} catch {
MXLog.error("Failed to get joined parents for \(roomID): \(error)")
return .failure(.sdkError(error))
}
}
// MARK: - Private
private func handleUpdates(_ updates: [SpaceListUpdate]) {

View File

@@ -19,4 +19,6 @@ protocol SpaceServiceProxyProtocol {
func spaceRoomList(spaceID: String) async -> Result<SpaceRoomListProxyProtocol, SpaceServiceProxyError>
func leaveSpace(spaceID: String) async -> Result<LeaveSpaceHandleProxy, SpaceServiceProxyError>
/// Returns all the parent spaces of a room that user has joined.
func joinedParents(roomID: String) async -> Result<[SpaceRoomProxyProtocol], SpaceServiceProxyError>
}