mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
implemented the ask to join restricted rule join UI, and added tests and previews for it.
This commit is contained in:
+40
-5
@@ -74,6 +74,14 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
|
||||
isSpaceSettingsEnabled && selectableSpacesCount > 0
|
||||
}
|
||||
|
||||
var isAskToJoinWithSpaceMembersOptionAvailable: Bool {
|
||||
currentSettings.accessType.isAskToJoinWithSpaceUsers || isAskToJoinWithSpaceMembersOptionSelectable
|
||||
}
|
||||
|
||||
var isAskToJoinWithSpaceMembersOptionSelectable: Bool {
|
||||
isSpaceMembersOptionSelectable && isKnockingEnabled
|
||||
}
|
||||
|
||||
var spaceMembersDescription: String {
|
||||
if isSpaceMembersOptionSelectable {
|
||||
switch spaceSelection {
|
||||
@@ -89,10 +97,27 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
|
||||
}
|
||||
}
|
||||
|
||||
var askToJoinWithSpaceMembersDescription: String {
|
||||
if isAskToJoinWithSpaceMembersOptionSelectable {
|
||||
switch spaceSelection {
|
||||
case .singleJoined(let joinedParentSpace):
|
||||
L10n.screenSecurityAndPrivacyAskToJoinSingleSpaceMembersOptionDescription(joinedParentSpace.name)
|
||||
case .singleUnknown(let id):
|
||||
L10n.screenSecurityAndPrivacyAskToJoinSingleSpaceMembersOptionDescription(id)
|
||||
case .multiple, .empty:
|
||||
L10n.screenSecurityAndPrivacyAskToJoinMultipleSpacesMembersOptionDescription
|
||||
}
|
||||
} else {
|
||||
L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionUnavailableDescription
|
||||
}
|
||||
}
|
||||
|
||||
var accessSectionFooter: AttributedString? {
|
||||
if bindings.desiredSettings.accessType.isSpaceUsers,
|
||||
isSpaceMembersOptionSelectable,
|
||||
case .multiple = spaceSelection {
|
||||
if (bindings.desiredSettings.accessType.isSpaceUsers &&
|
||||
isSpaceMembersOptionSelectable) ||
|
||||
(bindings.desiredSettings.accessType.isAskToJoinWithSpaceUsers &&
|
||||
isAskToJoinWithSpaceMembersOptionSelectable),
|
||||
case .multiple = spaceSelection {
|
||||
Self.accessSectionFooterAttributedString
|
||||
} else {
|
||||
nil
|
||||
@@ -116,8 +141,8 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
|
||||
} else if selectableSpacesCount > 1 {
|
||||
.multiple
|
||||
} else if let joinedParent = joinedParentSpaces.first {
|
||||
if case let .spaceUsers(ids) = currentSettings.accessType {
|
||||
if ids.isEmpty {
|
||||
if currentSettings.accessType.isSpaceUsers || currentSettings.accessType.isAskToJoinWithSpaceUsers {
|
||||
if currentSettings.accessType.spaceIDs.isEmpty {
|
||||
// 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
|
||||
.multiple
|
||||
@@ -185,6 +210,15 @@ enum SecurityAndPrivacyRoomAccessType: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
var isAskToJoinWithSpaceUsers: Bool {
|
||||
switch self {
|
||||
case .askToJoinWithSpaceUsers:
|
||||
true
|
||||
default:
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
var isAddressRequired: Bool {
|
||||
switch self {
|
||||
case .inviteOnly, .spaceUsers:
|
||||
@@ -215,6 +249,7 @@ enum SecurityAndPrivacyScreenViewAction {
|
||||
case tryUpdatingEncryption(Bool)
|
||||
case editAddress
|
||||
case selectedSpaceMembersAccess
|
||||
case selectedAskToJoinWithSpaceMembersAccess
|
||||
case manageSpaces
|
||||
}
|
||||
|
||||
|
||||
+39
-14
@@ -81,7 +81,9 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
||||
case .selectedSpaceMembersAccess:
|
||||
handleSelectedSpaceMembersAccess()
|
||||
case .manageSpaces:
|
||||
displayManageAuthorizedSpacesScreen()
|
||||
displayManageAuthorizedSpacesScreen(isAskToJoin: state.bindings.desiredSettings.accessType.isAskToJoinWithSpaceUsers)
|
||||
case .selectedAskToJoinWithSpaceMembersAccess:
|
||||
handleSelectedAskToJoinWithSpaceMembersAccess()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,11 +250,29 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
||||
case .empty:
|
||||
break // Very edge case. We do nothing in this case.
|
||||
case .multiple:
|
||||
displayManageAuthorizedSpacesScreen()
|
||||
displayManageAuthorizedSpacesScreen(isAskToJoin: false)
|
||||
}
|
||||
}
|
||||
|
||||
private func displayManageAuthorizedSpacesScreen() {
|
||||
private func handleSelectedAskToJoinWithSpaceMembersAccess() {
|
||||
guard !state.bindings.desiredSettings.accessType.isAskToJoinWithSpaceUsers else {
|
||||
// If the user is tapping the ask to join with space members access again we do nothing
|
||||
return
|
||||
}
|
||||
|
||||
switch state.spaceSelection {
|
||||
case .singleJoined(let joinedParent):
|
||||
state.bindings.desiredSettings.accessType = .askToJoinWithSpaceUsers(spaceIDs: [joinedParent.id])
|
||||
case .singleUnknown(let id):
|
||||
state.bindings.desiredSettings.accessType = .askToJoinWithSpaceUsers(spaceIDs: [id])
|
||||
case .empty:
|
||||
break // Very edge case. We do nothing in this case.
|
||||
case .multiple:
|
||||
displayManageAuthorizedSpacesScreen(isAskToJoin: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func displayManageAuthorizedSpacesScreen(isAskToJoin: Bool) {
|
||||
let joinedParentSpaces = state.joinedParentSpaces
|
||||
let unknownSpaceIDs = state.currentSettings.accessType.spaceIDs.filter { id in
|
||||
!joinedParentSpaces.contains { $0.id == id }
|
||||
@@ -263,7 +283,8 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
|
||||
initialSelectedIDs: selectedIDs)
|
||||
authorizedSpacesSelection.selectedIDs
|
||||
.sink { [weak self] desiredSelectedIDs in
|
||||
self?.state.bindings.desiredSettings.accessType = .spaceUsers(spaceIDs: desiredSelectedIDs.sorted())
|
||||
let sortedIDs = desiredSelectedIDs.sorted()
|
||||
self?.state.bindings.desiredSettings.accessType = isAskToJoin ? .askToJoinWithSpaceUsers(spaceIDs: sortedIDs) : .spaceUsers(spaceIDs: sortedIDs)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
@@ -334,20 +355,24 @@ private extension Optional where Wrapped == JoinRule {
|
||||
return .anyone
|
||||
case .invite:
|
||||
return .inviteOnly
|
||||
case .knock, .knockRestricted:
|
||||
// TODO: Handle knock restricted with rules
|
||||
case .knock:
|
||||
return .askToJoin
|
||||
case .knockRestricted(let rules):
|
||||
return .askToJoinWithSpaceUsers(spaceIDs: Self.spaceIDs(from: rules))
|
||||
case .restricted(let rules):
|
||||
let spaceIDs = rules.compactMap { rule in
|
||||
if case let .roomMembership(id) = rule {
|
||||
id
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
}
|
||||
return .spaceUsers(spaceIDs: spaceIDs)
|
||||
return .spaceUsers(spaceIDs: Self.spaceIDs(from: rules))
|
||||
default:
|
||||
return .inviteOnly
|
||||
}
|
||||
}
|
||||
|
||||
private static func spaceIDs(from rules: [AllowRule]) -> [String] {
|
||||
rules.compactMap { rule in
|
||||
if case let .roomMembership(id) = rule {
|
||||
id
|
||||
} else {
|
||||
nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+79
-7
@@ -50,13 +50,6 @@ struct SecurityAndPrivacyScreen: View {
|
||||
description: L10n.screenSecurityAndPrivacyRoomAccessAnyoneOptionDescription,
|
||||
icon: \.public),
|
||||
kind: .selection(isSelected: context.desiredSettings.accessType == .anyone) { context.desiredSettings.accessType = .anyone })
|
||||
if context.viewState.isKnockingEnabled || context.viewState.currentSettings.accessType == .askToJoin {
|
||||
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyAskToJoinOptionTitle,
|
||||
description: L10n.screenSecurityAndPrivacyAskToJoinOptionDescription,
|
||||
icon: \.userAdd),
|
||||
kind: .selection(isSelected: context.desiredSettings.accessType == .askToJoin) { context.desiredSettings.accessType = .askToJoin })
|
||||
.disabled(!context.viewState.isKnockingEnabled)
|
||||
}
|
||||
|
||||
if context.viewState.isSpaceMembersOptionAvailable {
|
||||
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionTitle,
|
||||
@@ -68,6 +61,17 @@ struct SecurityAndPrivacyScreen: View {
|
||||
.disabled(!context.viewState.isSpaceMembersOptionSelectable)
|
||||
}
|
||||
|
||||
if context.viewState.currentSettings.accessType == .askToJoin {
|
||||
askToJoinOption
|
||||
}
|
||||
|
||||
// Ask to join and askToJoin with space members can coexist if the initial setting is ask to join but the space members option is available otherwise it's either one or the other.
|
||||
if context.viewState.isAskToJoinWithSpaceMembersOptionAvailable {
|
||||
askToJoinWithSpaceMembersOption
|
||||
} else if context.viewState.isKnockingEnabled, context.viewState.currentSettings.accessType != .askToJoin {
|
||||
askToJoinOption
|
||||
}
|
||||
|
||||
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyRoomAccessInviteOnlyOptionTitle,
|
||||
description: L10n.screenSecurityAndPrivacyRoomAccessInviteOnlyOptionDescription,
|
||||
icon: \.lock),
|
||||
@@ -87,6 +91,22 @@ struct SecurityAndPrivacyScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var askToJoinOption: some View {
|
||||
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyAskToJoinOptionTitle,
|
||||
description: L10n.screenSecurityAndPrivacyAskToJoinOptionDescription,
|
||||
icon: \.userAdd),
|
||||
kind: .selection(isSelected: context.desiredSettings.accessType == .askToJoin) { context.desiredSettings.accessType = .askToJoin })
|
||||
.disabled(!context.viewState.isKnockingEnabled)
|
||||
}
|
||||
|
||||
private var askToJoinWithSpaceMembersOption: some View {
|
||||
ListRow(label: .default(title: L10n.screenSecurityAndPrivacyAskToJoinOptionTitle,
|
||||
description: context.viewState.askToJoinWithSpaceMembersDescription,
|
||||
icon: \.userAdd),
|
||||
kind: .selection(isSelected: context.desiredSettings.accessType.isAskToJoinWithSpaceUsers) { context.send(viewAction: .selectedAskToJoinWithSpaceMembersAccess) })
|
||||
.disabled(!context.viewState.isAskToJoinWithSpaceMembersOptionSelectable)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var encryptionSection: some View {
|
||||
let binding = Binding<Bool>(get: {
|
||||
@@ -286,6 +306,42 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
|
||||
appSettings: appSettings)
|
||||
}()
|
||||
|
||||
static let singleAskToJoinSpaceMembersViewModel = {
|
||||
AppSettings.resetAllSettings()
|
||||
let appSettings = AppSettings()
|
||||
appSettings.spaceSettingsEnabled = true
|
||||
appSettings.knockingEnabled = true
|
||||
let space = [SpaceRoomProxyProtocol].mockSingleRoom[0]
|
||||
|
||||
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
|
||||
canonicalAlias: "#room:matrix.org",
|
||||
members: .allMembersAsCreator,
|
||||
joinRule: .knockRestricted(rules: [.roomMembership(roomId: space.id)]),
|
||||
isVisibleInPublicDirectory: true)),
|
||||
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org",
|
||||
spaceServiceConfiguration: .init(joinedParentSpaces: [space]))),
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
appSettings: appSettings)
|
||||
}()
|
||||
|
||||
static let multipleAskToJoinSpacesMembersViewModel = {
|
||||
AppSettings.resetAllSettings()
|
||||
let appSettings = AppSettings()
|
||||
appSettings.spaceSettingsEnabled = true
|
||||
appSettings.knockingEnabled = true
|
||||
let spaces = [SpaceRoomProxyProtocol].mockJoinedSpaces
|
||||
|
||||
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
|
||||
canonicalAlias: "#room:matrix.org",
|
||||
members: .allMembersAsCreator,
|
||||
joinRule: .knockRestricted(rules: spaces.map { .roomMembership(roomId: $0.id) }),
|
||||
isVisibleInPublicDirectory: true)),
|
||||
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org",
|
||||
spaceServiceConfiguration: .init(joinedParentSpaces: spaces))),
|
||||
userIndicatorController: UserIndicatorControllerMock(),
|
||||
appSettings: appSettings)
|
||||
}()
|
||||
|
||||
static let publicSpaceViewModel = {
|
||||
AppSettings.resetAllSettings()
|
||||
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isSpace: true,
|
||||
@@ -342,6 +398,22 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
|
||||
})
|
||||
.previewDisplayName("Ask to join room")
|
||||
|
||||
NavigationStack {
|
||||
SecurityAndPrivacyScreen(context: singleAskToJoinSpaceMembersViewModel.context)
|
||||
}
|
||||
.snapshotPreferences(expect: singleAskToJoinSpaceMembersViewModel.context.$viewState.map { state in
|
||||
state.currentSettings.isVisibileInRoomDirectory == true
|
||||
})
|
||||
.previewDisplayName("Ask to join with single space members room")
|
||||
|
||||
NavigationStack {
|
||||
SecurityAndPrivacyScreen(context: multipleAskToJoinSpacesMembersViewModel.context)
|
||||
}
|
||||
.snapshotPreferences(expect: multipleAskToJoinSpacesMembersViewModel.context.$viewState.map { state in
|
||||
state.currentSettings.isVisibileInRoomDirectory == true
|
||||
})
|
||||
.previewDisplayName("Ask to join with multiple spaces members room")
|
||||
|
||||
NavigationStack {
|
||||
SecurityAndPrivacyScreen(context: publicSpaceViewModel.context)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user