mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 19:44:05 +00:00
single space access implementation
also added the copies for the single and multiple spaces selection
This commit is contained in:
@@ -648,11 +648,15 @@
|
|||||||
"screen_security_and_privacy_encryption_section_footer" = "Once enabled, encryption cannot be disabled.";
|
"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_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_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_description" = "Only invited people can join.";
|
||||||
"screen_security_and_privacy_room_access_invite_only_option_title" = "Invite only";
|
"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_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_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_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_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.";
|
"screen_security_and_privacy_room_directory_visibility_toggle_description" = "Allow to be found by searching the public directory.";
|
||||||
|
|||||||
@@ -648,11 +648,15 @@
|
|||||||
"screen_security_and_privacy_encryption_section_footer" = "Once enabled, encryption cannot be disabled.";
|
"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_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_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_description" = "Only invited people can join.";
|
||||||
"screen_security_and_privacy_room_access_invite_only_option_title" = "Invite only";
|
"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_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_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_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_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.";
|
"screen_security_and_privacy_room_directory_visibility_toggle_description" = "Allow to be found by searching the public directory.";
|
||||||
|
|||||||
@@ -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") }
|
internal static var screenSecurityAndPrivacyRoomAccessAnyoneOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_anyone_option_description") }
|
||||||
/// Anyone
|
/// Anyone
|
||||||
internal static var screenSecurityAndPrivacyRoomAccessAnyoneOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_anyone_option_title") }
|
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.
|
/// Only invited people can join.
|
||||||
internal static var screenSecurityAndPrivacyRoomAccessInviteOnlyOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_invite_only_option_description") }
|
internal static var screenSecurityAndPrivacyRoomAccessInviteOnlyOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_invite_only_option_description") }
|
||||||
/// Invite only
|
/// Invite only
|
||||||
internal static var screenSecurityAndPrivacyRoomAccessInviteOnlyOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_invite_only_option_title") }
|
internal static var screenSecurityAndPrivacyRoomAccessInviteOnlyOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_invite_only_option_title") }
|
||||||
/// Access
|
/// Access
|
||||||
internal static var screenSecurityAndPrivacyRoomAccessSectionHeader: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_section_header") }
|
internal static var screenSecurityAndPrivacyRoomAccessSectionHeader: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_section_header") }
|
||||||
/// Spaces are not currently supported
|
/// Anyone in authorized spaces can join.
|
||||||
internal static var screenSecurityAndPrivacyRoomAccessSpaceMembersOptionDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_space_members_option_description") }
|
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
|
/// Space members
|
||||||
internal static var screenSecurityAndPrivacyRoomAccessSpaceMembersOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_space_members_option_title") }
|
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") }
|
||||||
/// You’ll need an address in order to make it visible in the public directory.
|
/// You’ll 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") }
|
internal static var screenSecurityAndPrivacyRoomAddressSectionFooter: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_address_section_footer") }
|
||||||
/// Address
|
/// Address
|
||||||
|
|||||||
@@ -16616,6 +16616,76 @@ class SpaceServiceProxyMock: SpaceServiceProxyProtocol, @unchecked Sendable {
|
|||||||
return leaveSpaceSpaceIDReturnValue
|
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 {
|
class StaticRoomSummaryProviderMock: StaticRoomSummaryProviderProtocol, @unchecked Sendable {
|
||||||
var statePublisher: CurrentValuePublisher<RoomSummaryProviderState, Never> {
|
var statePublisher: CurrentValuePublisher<RoomSummaryProviderState, Never> {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import MatrixRustSDK
|
|||||||
extension SpaceServiceProxyMock {
|
extension SpaceServiceProxyMock {
|
||||||
struct Configuration {
|
struct Configuration {
|
||||||
var joinedSpaces: [SpaceRoomProxyProtocol] = []
|
var joinedSpaces: [SpaceRoomProxyProtocol] = []
|
||||||
|
var joinedParentSpaces: [SpaceRoomProxyProtocol] = []
|
||||||
var spaceRoomLists: [String: SpaceRoomListProxyMock] = [:]
|
var spaceRoomLists: [String: SpaceRoomListProxyMock] = [:]
|
||||||
var leaveSpaceRooms: [LeaveSpaceRoom] = []
|
var leaveSpaceRooms: [LeaveSpaceRoom] = []
|
||||||
}
|
}
|
||||||
@@ -21,6 +22,7 @@ extension SpaceServiceProxyMock {
|
|||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
joinedSpacesPublisher = .init(configuration.joinedSpaces)
|
joinedSpacesPublisher = .init(configuration.joinedSpaces)
|
||||||
|
joinedParentsRoomIDReturnValue = .success(configuration.joinedParentSpaces)
|
||||||
spaceRoomListSpaceIDClosure = { spaceID in
|
spaceRoomListSpaceIDClosure = { spaceID in
|
||||||
if let spaceRoomList = configuration.spaceRoomLists[spaceID] {
|
if let spaceRoomList = configuration.spaceRoomLists[spaceID] {
|
||||||
.success(spaceRoomList)
|
.success(spaceRoomList)
|
||||||
|
|||||||
+116
-3
@@ -7,23 +7,45 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import MatrixRustSDK
|
||||||
|
|
||||||
enum SecurityAndPrivacyScreenViewModelAction {
|
enum SecurityAndPrivacyScreenViewModelAction {
|
||||||
case displayEditAddressScreen
|
case displayEditAddressScreen
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SecurityAndPrivacyScreenViewState: BindableState {
|
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
|
let serverName: String
|
||||||
var currentSettings: SecurityAndPrivacySettings
|
var currentSettings: SecurityAndPrivacySettings
|
||||||
var bindings: SecurityAndPrivacyScreenViewStateBindings
|
var bindings: SecurityAndPrivacyScreenViewStateBindings
|
||||||
var canonicalAlias: String?
|
var canonicalAlias: String?
|
||||||
var isKnockingEnabled: Bool
|
var isKnockingEnabled: Bool
|
||||||
|
var isSpaceSettingsEnabled: Bool
|
||||||
var isSpace: Bool
|
var isSpace: Bool
|
||||||
|
|
||||||
var canEditAddress = false
|
var canEditAddress = false
|
||||||
var canEditJoinRule = false
|
var canEditJoinRule = false
|
||||||
var canEnableEncryption = false
|
var canEnableEncryption = false
|
||||||
var canEditHistoryVisibility = 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 {
|
private var hasChanges: Bool {
|
||||||
currentSettings != bindings.desiredSettings
|
currentSettings != bindings.desiredSettings
|
||||||
@@ -45,16 +67,77 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
|
|||||||
}
|
}
|
||||||
return options
|
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,
|
init(serverName: String,
|
||||||
accessType: SecurityAndPrivacyRoomAccessType,
|
accessType: SecurityAndPrivacyRoomAccessType,
|
||||||
isEncryptionEnabled: Bool,
|
isEncryptionEnabled: Bool,
|
||||||
historyVisibility: SecurityAndPrivacyHistoryVisibility,
|
historyVisibility: SecurityAndPrivacyHistoryVisibility,
|
||||||
isSpace: Bool,
|
isSpace: Bool,
|
||||||
isKnockingEnabled: Bool) {
|
isKnockingEnabled: Bool,
|
||||||
|
isSpaceSettingsEnabled: Bool) {
|
||||||
self.serverName = serverName
|
self.serverName = serverName
|
||||||
self.isKnockingEnabled = isKnockingEnabled
|
self.isKnockingEnabled = isKnockingEnabled
|
||||||
self.isSpace = isSpace
|
self.isSpace = isSpace
|
||||||
|
self.isSpaceSettingsEnabled = isSpaceSettingsEnabled
|
||||||
|
|
||||||
let settings = SecurityAndPrivacySettings(accessType: accessType,
|
let settings = SecurityAndPrivacySettings(accessType: accessType,
|
||||||
isEncryptionEnabled: isEncryptionEnabled,
|
isEncryptionEnabled: isEncryptionEnabled,
|
||||||
@@ -76,11 +159,39 @@ struct SecurityAndPrivacySettings: Equatable {
|
|||||||
var isVisibileInRoomDirectory: Bool?
|
var isVisibileInRoomDirectory: Bool?
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SecurityAndPrivacyRoomAccessType {
|
enum SecurityAndPrivacyRoomAccessType: Equatable {
|
||||||
case inviteOnly
|
case inviteOnly
|
||||||
case askToJoin
|
case askToJoin
|
||||||
|
case askToJoinWithSpaceUsers(spaceIDs: [String])
|
||||||
case anyone
|
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 {
|
enum SecurityAndPrivacyAlertType {
|
||||||
@@ -91,6 +202,8 @@ enum SecurityAndPrivacyScreenViewAction {
|
|||||||
case save
|
case save
|
||||||
case tryUpdatingEncryption(Bool)
|
case tryUpdatingEncryption(Bool)
|
||||||
case editAddress
|
case editAddress
|
||||||
|
case selectedSpaceMembersAccess
|
||||||
|
case manageSpaces
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SecurityAndPrivacyHistoryVisibility {
|
enum SecurityAndPrivacyHistoryVisibility {
|
||||||
|
|||||||
+60
-20
@@ -37,7 +37,8 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
|||||||
isEncryptionEnabled: roomProxy.infoPublisher.value.isEncrypted,
|
isEncryptionEnabled: roomProxy.infoPublisher.value.isEncrypted,
|
||||||
historyVisibility: roomProxy.infoPublisher.value.historyVisibility.toSecurityAndPrivacyHistoryVisibility,
|
historyVisibility: roomProxy.infoPublisher.value.historyVisibility.toSecurityAndPrivacyHistoryVisibility,
|
||||||
isSpace: roomProxy.infoPublisher.value.isSpace,
|
isSpace: roomProxy.infoPublisher.value.isSpace,
|
||||||
isKnockingEnabled: appSettings.knockingEnabled))
|
isKnockingEnabled: appSettings.knockingEnabled,
|
||||||
|
isSpaceSettingsEnabled: appSettings.spaceSettingsEnabled))
|
||||||
|
|
||||||
if let powerLevels = roomProxy.infoPublisher.value.powerLevels {
|
if let powerLevels = roomProxy.infoPublisher.value.powerLevels {
|
||||||
setupPermissions(powerLevels: powerLevels)
|
setupPermissions(powerLevels: powerLevels)
|
||||||
@@ -45,6 +46,14 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
|||||||
|
|
||||||
setupRoomDirectoryVisibility()
|
setupRoomDirectoryVisibility()
|
||||||
setupSubscriptions()
|
setupSubscriptions()
|
||||||
|
Task {
|
||||||
|
switch await clientProxy.spaceService.joinedParents(roomID: roomProxy.id) {
|
||||||
|
case .success(let joinedParentSpaces):
|
||||||
|
state.joinedParentSpaces = joinedParentSpaces
|
||||||
|
case .failure:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
@@ -69,6 +78,11 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
|||||||
}
|
}
|
||||||
case .editAddress:
|
case .editAddress:
|
||||||
actionsSubject.send(.displayEditAddressScreen)
|
actionsSubject.send(.displayEditAddressScreen)
|
||||||
|
case .selectedSpaceMembersAccess:
|
||||||
|
handleSelectedSpaceMembersAccess()
|
||||||
|
case .manageSpaces:
|
||||||
|
// TODO: Implement multiple space selection
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +141,10 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
|||||||
appSettings.$knockingEnabled
|
appSettings.$knockingEnabled
|
||||||
.weakAssign(to: \.state.isKnockingEnabled, on: self)
|
.weakAssign(to: \.state.isKnockingEnabled, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
appSettings.$spaceSettingsEnabled
|
||||||
|
.weakAssign(to: \.state.isSpaceSettingsEnabled, on: self)
|
||||||
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupPermissions(powerLevels: RoomPowerLevelsProxyProtocol) {
|
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 static let loadingIndicatorIdentifier = "\(EditRoomAddressScreenViewModel.self)-Loading"
|
||||||
|
|
||||||
private func showLoadingIndicator() {
|
private func showLoadingIndicator() {
|
||||||
@@ -225,25 +255,10 @@ private extension SecurityAndPrivacyRoomAccessType {
|
|||||||
.knock
|
.knock
|
||||||
case .anyone:
|
case .anyone:
|
||||||
.public
|
.public
|
||||||
case .spaceUsers:
|
case .spaceUsers(let spaceIDs):
|
||||||
fatalError("The user shouldn't be able to select this rule")
|
.restricted(rules: spaceIDs.map { .roomMembership(roomId: $0) })
|
||||||
}
|
case .askToJoinWithSpaceUsers(let spaceIDs):
|
||||||
}
|
.knockRestricted(rules: spaceIDs.map { .roomMembership(roomId: $0) })
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+54
-14
@@ -18,7 +18,7 @@ struct SecurityAndPrivacyScreen: View {
|
|||||||
roomAccessSection
|
roomAccessSection
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.desiredSettings.accessType != .inviteOnly, context.viewState.canEditAddress {
|
if context.desiredSettings.accessType.isAddressRequired, context.viewState.canEditAddress {
|
||||||
visibilitySection
|
visibilitySection
|
||||||
if let canonicalAlias = context.viewState.canonicalAlias {
|
if let canonicalAlias = context.viewState.canonicalAlias {
|
||||||
addressSection(canonicalAlias: canonicalAlias)
|
addressSection(canonicalAlias: canonicalAlias)
|
||||||
@@ -57,13 +57,14 @@ struct SecurityAndPrivacyScreen: View {
|
|||||||
.disabled(!context.viewState.isKnockingEnabled)
|
.disabled(!context.viewState.isKnockingEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
if context.viewState.currentSettings.accessType == .spaceUsers {
|
if context.viewState.isSpaceMembersOptionAvailable {
|
||||||
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionTitle,
|
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionTitle,
|
||||||
description: L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionDescription,
|
description: context.viewState.spaceMembersDescription,
|
||||||
icon: \.space),
|
icon: \.space),
|
||||||
kind: .selection(isSelected: context.desiredSettings.accessType == .spaceUsers) { })
|
kind: .selection(isSelected: context.desiredSettings.accessType.isSpaceUsers) {
|
||||||
// This is not supported so it will always be disabled and has no handler
|
context.send(viewAction: .selectedSpaceMembersAccess)
|
||||||
.disabled(true)
|
})
|
||||||
|
.disabled(!context.viewState.isSpaceMembersOptionSelectable)
|
||||||
}
|
}
|
||||||
|
|
||||||
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyRoomAccessInviteOnlyOptionTitle,
|
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyRoomAccessInviteOnlyOptionTitle,
|
||||||
@@ -73,6 +74,15 @@ struct SecurityAndPrivacyScreen: View {
|
|||||||
} header: {
|
} header: {
|
||||||
Text(L10n.screenSecurityAndPrivacyRoomAccessSectionHeader)
|
Text(L10n.screenSecurityAndPrivacyRoomAccessSectionHeader)
|
||||||
.compoundListSectionHeader()
|
.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())
|
appSettings: AppSettings())
|
||||||
}()
|
}()
|
||||||
|
|
||||||
static let restrictedViewModel = {
|
static let singleSpaceMembersViewModel = {
|
||||||
AppSettings.resetAllSettings()
|
AppSettings.resetAllSettings()
|
||||||
|
let appSettings = AppSettings()
|
||||||
|
appSettings.spaceSettingsEnabled = true
|
||||||
|
let space = [SpaceRoomProxyProtocol].mockSingleRoom[0]
|
||||||
|
|
||||||
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
|
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
|
||||||
canonicalAlias: "#room:matrix.org",
|
canonicalAlias: "#room:matrix.org",
|
||||||
members: .allMembersAsCreator,
|
members: .allMembersAsCreator,
|
||||||
joinRule: .restricted(rules: []),
|
joinRule: .restricted(rules: [.roomMembership(roomId: space.id)]),
|
||||||
isVisibleInPublicDirectory: true)),
|
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(),
|
userIndicatorController: UserIndicatorControllerMock(),
|
||||||
appSettings: AppSettings())
|
appSettings: AppSettings())
|
||||||
}()
|
}()
|
||||||
@@ -278,17 +310,25 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
.previewDisplayName("Public room without address")
|
.previewDisplayName("Public room without address")
|
||||||
|
|
||||||
NavigationStack {
|
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
|
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 {
|
NavigationStack {
|
||||||
SecurityAndPrivacyScreen(context: askToJoinViewModel.context)
|
SecurityAndPrivacyScreen(context: askToJoinViewModel.context)
|
||||||
}
|
}
|
||||||
.snapshotPreferences(expect: restrictedViewModel.context.$viewState.map { state in
|
.snapshotPreferences(expect: askToJoinViewModel.context.$viewState.map { state in
|
||||||
state.currentSettings.isVisibileInRoomDirectory == true
|
state.currentSettings.isVisibileInRoomDirectory == true
|
||||||
})
|
})
|
||||||
.previewDisplayName("Ask to join room")
|
.previewDisplayName("Ask to join room")
|
||||||
@@ -296,7 +336,7 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
NavigationStack {
|
NavigationStack {
|
||||||
SecurityAndPrivacyScreen(context: publicSpaceViewModel.context)
|
SecurityAndPrivacyScreen(context: publicSpaceViewModel.context)
|
||||||
}
|
}
|
||||||
.snapshotPreferences(expect: restrictedViewModel.context.$viewState.map { state in
|
.snapshotPreferences(expect: publicSpaceViewModel.context.$viewState.map { state in
|
||||||
state.currentSettings.isVisibileInRoomDirectory == true
|
state.currentSettings.isVisibileInRoomDirectory == true
|
||||||
})
|
})
|
||||||
.previewDisplayName("Public space")
|
.previewDisplayName("Public space")
|
||||||
|
|||||||
@@ -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
|
// MARK: - Private
|
||||||
|
|
||||||
private func handleUpdates(_ updates: [SpaceListUpdate]) {
|
private func handleUpdates(_ updates: [SpaceListUpdate]) {
|
||||||
|
|||||||
@@ -19,4 +19,6 @@ protocol SpaceServiceProxyProtocol {
|
|||||||
|
|
||||||
func spaceRoomList(spaceID: String) async -> Result<SpaceRoomListProxyProtocol, SpaceServiceProxyError>
|
func spaceRoomList(spaceID: String) async -> Result<SpaceRoomListProxyProtocol, SpaceServiceProxyError>
|
||||||
func leaveSpace(spaceID: String) async -> Result<LeaveSpaceHandleProxy, 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>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user