Always show security and privacy, and just hide the knocking jojn rule option through the feature flag

This commit is contained in:
Mauro Romito
2025-11-10 18:56:20 +01:00
committed by Mauro
parent c9743e2657
commit afbbbeb419
9 changed files with 173 additions and 35 deletions

View File

@@ -1346,7 +1346,8 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol {
private func presentSecurityAndPrivacyScreen() {
let coordinator = SecurityAndPrivacyScreenCoordinator(parameters: .init(roomProxy: roomProxy,
clientProxy: userSession.clientProxy,
userIndicatorController: flowParameters.userIndicatorController))
userIndicatorController: flowParameters.userIndicatorController,
appSetting: flowParameters.appSettings))
coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }

View File

@@ -248,7 +248,8 @@ final class SpaceSettingsFlowCoordinator: FlowCoordinatorProtocol {
private func presentSecurityAndPrivacyScreen() {
let coordinator = SecurityAndPrivacyScreenCoordinator(parameters: .init(roomProxy: roomProxy,
clientProxy: flowParameters.userSession.clientProxy,
userIndicatorController: flowParameters.userIndicatorController))
userIndicatorController: flowParameters.userIndicatorController,
appSetting: flowParameters.appSettings))
coordinator.actionsPublisher.sink { [weak self] action in
guard let self else { return }

View File

@@ -57,6 +57,7 @@ struct RoomDetailsScreenViewState: BindableState {
var canEditRoomTopic = false
var canEditRoomAvatar = false
var canEditRolesOrPermissions = false
var canEditSecurityAndPrivacy = false
var canKickUsers = false
var canBanUsers = false
var notificationSettingsState: RoomDetailsNotificationSettingsState = .loading
@@ -74,7 +75,7 @@ struct RoomDetailsScreenViewState: BindableState {
}
var canSeeSecurityAndPrivacy: Bool {
knockingEnabled && dmRecipientInfo == nil && canEditRolesOrPermissions
dmRecipientInfo == nil && canEditSecurityAndPrivacy
}
var canEditBaseInfo: Bool {

View File

@@ -292,6 +292,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
state.canBanUsers = powerLevels.canOwnUserBan()
state.canJoinCall = powerLevels.canOwnUserJoinCall()
state.canEditRolesOrPermissions = powerLevels.canOwnUserEditRolesAndPermissions()
state.canEditSecurityAndPrivacy = powerLevels.canOwnUserEditSecurityAndPrivacy()
}
}

View File

@@ -13,6 +13,7 @@ struct SecurityAndPrivacyScreenCoordinatorParameters {
let roomProxy: JoinedRoomProxyProtocol
let clientProxy: ClientProxyProtocol
let userIndicatorController: UserIndicatorControllerProtocol
let appSetting: AppSettings
}
enum SecurityAndPrivacyScreenCoordinatorAction {
@@ -32,7 +33,8 @@ final class SecurityAndPrivacyScreenCoordinator: CoordinatorProtocol {
init(parameters: SecurityAndPrivacyScreenCoordinatorParameters) {
viewModel = SecurityAndPrivacyScreenViewModel(roomProxy: parameters.roomProxy,
clientProxy: parameters.clientProxy,
userIndicatorController: parameters.userIndicatorController)
userIndicatorController: parameters.userIndicatorController,
appSettings: parameters.appSetting)
}
func start() {

View File

@@ -17,6 +17,13 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
var currentSettings: SecurityAndPrivacySettings
var bindings: SecurityAndPrivacyScreenViewStateBindings
var canonicalAlias: String?
var isKnockingEnabled: Bool
var isSpace: Bool
var canEditAddress = false
var canEditJoinRule = false
var canEnableEncryption = false
var canEditHistoryVisibility = false
private var hasChanges: Bool {
currentSettings != bindings.desiredSettings
@@ -42,8 +49,13 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
init(serverName: String,
accessType: SecurityAndPrivacyRoomAccessType,
isEncryptionEnabled: Bool,
historyVisibility: SecurityAndPrivacyHistoryVisibility) {
historyVisibility: SecurityAndPrivacyHistoryVisibility,
isSpace: Bool,
isKnockingEnabled: Bool) {
self.serverName = serverName
self.isKnockingEnabled = isKnockingEnabled
self.isSpace = isSpace
let settings = SecurityAndPrivacySettings(accessType: accessType,
isEncryptionEnabled: isEncryptionEnabled,
historyVisibility: historyVisibility)

View File

@@ -16,6 +16,7 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
private let roomProxy: JoinedRoomProxyProtocol
private let clientProxy: ClientProxyProtocol
private let userIndicatorController: UserIndicatorControllerProtocol
private let appSettings: AppSettings
private let actionsSubject: PassthroughSubject<SecurityAndPrivacyScreenViewModelAction, Never> = .init()
var actionsPublisher: AnyPublisher<SecurityAndPrivacyScreenViewModelAction, Never> {
@@ -24,14 +25,23 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
init(roomProxy: JoinedRoomProxyProtocol,
clientProxy: ClientProxyProtocol,
userIndicatorController: UserIndicatorControllerProtocol) {
userIndicatorController: UserIndicatorControllerProtocol,
appSettings: AppSettings) {
self.roomProxy = roomProxy
self.clientProxy = clientProxy
self.userIndicatorController = userIndicatorController
self.appSettings = appSettings
super.init(initialViewState: SecurityAndPrivacyScreenViewState(serverName: clientProxy.userIDServerName ?? "",
accessType: roomProxy.infoPublisher.value.joinRule.toSecurityAndPrivacyRoomAccessType,
isEncryptionEnabled: roomProxy.infoPublisher.value.isEncrypted,
historyVisibility: roomProxy.infoPublisher.value.historyVisibility.toSecurityAndPrivacyHistoryVisibility))
historyVisibility: roomProxy.infoPublisher.value.historyVisibility.toSecurityAndPrivacyHistoryVisibility,
isSpace: roomProxy.infoPublisher.value.isSpace,
isKnockingEnabled: appSettings.knockingEnabled))
if let powerLevels = roomProxy.infoPublisher.value.powerLevels {
setupPermissions(powerLevels: powerLevels)
}
setupRoomDirectoryVisibility()
setupSubscriptions()
@@ -82,7 +92,8 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
let userIDServerName = clientProxy.userIDServerName
roomProxy.infoPublisher
let infoPublisher = roomProxy.infoPublisher
infoPublisher
.compactMap { roomInfo in
guard let userIDServerName else {
return nil
@@ -96,6 +107,33 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
.receive(on: DispatchQueue.main)
.weakAssign(to: \.state.canonicalAlias, on: self)
.store(in: &cancellables)
infoPublisher
.compactMap(\.powerLevels)
.removeDuplicates { $0.userPowerLevels == $1.userPowerLevels && $0.values == $1.values }
.receive(on: DispatchQueue.main)
.sink { [weak self] powerLevels in
self?.setupPermissions(powerLevels: powerLevels)
}
.store(in: &cancellables)
infoPublisher
.map(\.isSpace)
.removeDuplicates()
.receive(on: DispatchQueue.main)
.weakAssign(to: \.state.isSpace, on: self)
.store(in: &cancellables)
appSettings.$knockingEnabled
.weakAssign(to: \.state.isKnockingEnabled, on: self)
.store(in: &cancellables)
}
private func setupPermissions(powerLevels: RoomPowerLevelsProxyProtocol) {
state.canEditAddress = powerLevels.canOwnUser(sendStateEvent: .roomAliases)
state.canEditJoinRule = powerLevels.canOwnUser(sendStateEvent: .roomJoinRules)
state.canEditHistoryVisibility = powerLevels.canOwnUser(sendStateEvent: .roomHistoryVisibility)
state.canEnableEncryption = powerLevels.canOwnUser(sendStateEvent: .roomEncryption)
}
private func setupRoomDirectoryVisibility() {

View File

@@ -14,8 +14,11 @@ struct SecurityAndPrivacyScreen: View {
var body: some View {
Form {
roomAccessSection
if context.desiredSettings.accessType != .inviteOnly {
if context.viewState.canEditJoinRule {
roomAccessSection
}
if context.desiredSettings.accessType != .inviteOnly, context.viewState.canEditAddress {
if let canonicalAlias = context.viewState.canonicalAlias {
visibilitySection
addressSection(canonicalAlias: canonicalAlias)
@@ -25,8 +28,15 @@ struct SecurityAndPrivacyScreen: View {
addAddressSection
}
}
encryptionSection
historySection
if !context.viewState.isSpace {
if context.viewState.canEnableEncryption {
encryptionSection
}
if context.viewState.canEditHistoryVisibility {
historySection
}
}
}
.compoundList()
.navigationBarTitleDisplayMode(.inline)
@@ -40,9 +50,12 @@ struct SecurityAndPrivacyScreen: View {
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomAccessInviteOnlyOptionTitle,
description: L10n.screenSecurityAndPrivacyRoomAccessInviteOnlyOptionDescription),
kind: .selection(isSelected: context.desiredSettings.accessType == .inviteOnly) { context.desiredSettings.accessType = .inviteOnly })
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyAskToJoinOptionTitle,
description: L10n.screenSecurityAndPrivacyAskToJoinOptionDescription),
kind: .selection(isSelected: context.desiredSettings.accessType == .askToJoin) { context.desiredSettings.accessType = .askToJoin })
if context.viewState.isKnockingEnabled || context.viewState.currentSettings.accessType == .askToJoin {
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyAskToJoinOptionTitle,
description: L10n.screenSecurityAndPrivacyAskToJoinOptionDescription),
kind: .selection(isSelected: context.desiredSettings.accessType == .askToJoin) { context.desiredSettings.accessType = .askToJoin })
.disabled(!context.viewState.isKnockingEnabled)
}
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomAccessAnyoneOptionTitle,
description: L10n.screenSecurityAndPrivacyRoomAccessAnyoneOptionDescription),
kind: .selection(isSelected: context.desiredSettings.accessType == .anyone) { context.desiredSettings.accessType = .anyone })
@@ -187,28 +200,76 @@ struct SecurityAndPrivacyScreen: View {
// MARK: - Previews
struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
static let inviteOnlyViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(joinRule: .invite)),
clientProxy: ClientProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock())
static let inviteOnlyViewModel = {
AppSettings.resetAllSettings()
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(members: .allMembersAsCreator,
joinRule: .invite)),
clientProxy: ClientProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock(),
appSettings: AppSettings())
}()
static let publicViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
canonicalAlias: "#room:matrix.org",
joinRule: .public,
isVisibleInPublicDirectory: true)),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")),
userIndicatorController: UserIndicatorControllerMock())
static let publicViewModel = {
AppSettings.resetAllSettings()
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
canonicalAlias: "#room:matrix.org",
members: .allMembersAsCreator,
joinRule: .public,
isVisibleInPublicDirectory: true)),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")),
userIndicatorController: UserIndicatorControllerMock(),
appSettings: AppSettings())
}()
static let publicNoAddressViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
joinRule: .public)),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")),
userIndicatorController: UserIndicatorControllerMock())
static let publicNoAddressViewModel = {
AppSettings.resetAllSettings()
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
members: .allMembersAsCreator,
joinRule: .public)),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")),
userIndicatorController: UserIndicatorControllerMock(),
appSettings: AppSettings())
}()
static let restrictedViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
canonicalAlias: "#room:matrix.org",
joinRule: .restricted(rules: []),
isVisibleInPublicDirectory: true)),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")),
userIndicatorController: UserIndicatorControllerMock())
static let restrictedViewModel = {
AppSettings.resetAllSettings()
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
canonicalAlias: "#room:matrix.org",
members: .allMembersAsCreator,
joinRule: .restricted(rules: []),
isVisibleInPublicDirectory: true)),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")),
userIndicatorController: UserIndicatorControllerMock(),
appSettings: AppSettings())
}()
static let askToJoinViewModel = {
AppSettings.resetAllSettings()
let appSettings = AppSettings()
appSettings.knockingEnabled = true
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
canonicalAlias: "#room:matrix.org",
members: .allMembersAsCreator,
joinRule: .knock,
isVisibleInPublicDirectory: true)),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")),
userIndicatorController: UserIndicatorControllerMock(),
appSettings: appSettings)
}()
static let publicSpaceViewModel = {
AppSettings.resetAllSettings()
return SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isSpace: true,
isEncrypted: false,
canonicalAlias: "#space:matrix.org",
members: .allMembersAsCreator,
joinRule: .public,
isVisibleInPublicDirectory: true)),
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")),
userIndicatorController: UserIndicatorControllerMock(),
appSettings: AppSettings())
}()
static var previews: some View {
NavigationStack {
@@ -236,5 +297,21 @@ struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
state.currentSettings.isVisibileInRoomDirectory == true
})
.previewDisplayName("Restricted room")
NavigationStack {
SecurityAndPrivacyScreen(context: askToJoinViewModel.context)
}
.snapshotPreferences(expect: restrictedViewModel.context.$viewState.map { state in
state.currentSettings.isVisibileInRoomDirectory == true
})
.previewDisplayName("Ask to join room")
NavigationStack {
SecurityAndPrivacyScreen(context: publicSpaceViewModel.context)
}
.snapshotPreferences(expect: restrictedViewModel.context.$viewState.map { state in
state.currentSettings.isVisibileInRoomDirectory == true
})
.previewDisplayName("Public space")
}
}

View File

@@ -40,8 +40,13 @@ protocol RoomPowerLevelsProxyProtocol {
// MARK: - Helpers
extension RoomPowerLevelsProxyProtocol {
/// Can own user edit either the room name, avatar or topic
/// Can own user edit either the room name, avatar or topic.
func canOwnUserEditBaseInfo() -> Bool {
canOwnUser(sendStateEvent: .roomAvatar) || canOwnUser(sendStateEvent: .roomName) || canOwnUser(sendStateEvent: .roomTopic)
}
/// Can own user edit any of the security and privacy settings.
func canOwnUserEditSecurityAndPrivacy() -> Bool {
canOwnUser(sendStateEvent: .roomEncryption) || canOwnUser(sendStateEvent: .roomAliases) || canOwnUser(sendStateEvent: .roomJoinRules) || canOwnUser(sendStateEvent: .roomHistoryVisibility)
}
}