mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Security and privacy part 2 (#3637)
* handling the history visibility flag * better logic to handle visibility * better handling of the visibility options state * added some copies, and the public room directory visibility state * completed the UI added also the preview tests * improved the handling of the directory visibility * added the space users case and improved handling of the access -> vsibility reaction. Also added a simple error handling for the public directory toggle * added the edit room address view but is missing its full implementation * implement the UI for the edit room address screen * implemented error checking when editing the address * updated preview tests and improved code * typo fix * Fix various issues after rebasing. * Fix build errors and broken snapshot tests * Adopt latest room privacy and canonical alias setting APIs * Add support for creating and editing the room's alias. * Add support for saving room privacy setting changes. * Fix room alias screen snapshot tests following recent changes. --------- Co-authored-by: Stefan Ceriu <stefanc@matrix.org>
This commit is contained in:
+153
-7
@@ -14,7 +14,18 @@ struct SecurityAndPrivacyScreen: View {
|
||||
var body: some View {
|
||||
Form {
|
||||
roomAccessSection
|
||||
if context.desiredSettings.accessType != .inviteOnly {
|
||||
if let canonicalAlias = context.viewState.canonicalAlias {
|
||||
visibilitySection
|
||||
addressSection(canonicalAlias: canonicalAlias)
|
||||
roomDirectoryVisibilitySection
|
||||
} else {
|
||||
publishingSection
|
||||
addAddressSection
|
||||
}
|
||||
}
|
||||
encryptionSection
|
||||
historySection
|
||||
}
|
||||
.compoundList()
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
@@ -34,6 +45,14 @@ struct SecurityAndPrivacyScreen: View {
|
||||
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomAccessAnyoneOptionTitle,
|
||||
description: L10n.screenSecurityAndPrivacyRoomAccessAnyoneOptionDescription),
|
||||
kind: .selection(isSelected: context.desiredSettings.accessType == .anyone) { context.desiredSettings.accessType = .anyone })
|
||||
|
||||
if context.viewState.currentSettings.accessType == .spaceUsers {
|
||||
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionTitle,
|
||||
description: L10n.screenSecurityAndPrivacyRoomAccessSpaceMembersOptionDescription),
|
||||
kind: .selection(isSelected: context.desiredSettings.accessType == .spaceUsers) { })
|
||||
// This is not supported so it will always be disabled and has no handler
|
||||
.disabled(true)
|
||||
}
|
||||
} header: {
|
||||
Text(L10n.screenSecurityAndPrivacyRoomAccessSectionHeader)
|
||||
.compoundListSectionHeader()
|
||||
@@ -42,7 +61,7 @@ struct SecurityAndPrivacyScreen: View {
|
||||
|
||||
@ViewBuilder
|
||||
private var encryptionSection: some View {
|
||||
let encryptionBinding = Binding<Bool>(get: {
|
||||
let binding = Binding<Bool>(get: {
|
||||
context.desiredSettings.isEncryptionEnabled
|
||||
}, set: { newValue in
|
||||
context.send(viewAction: .tryUpdatingEncryption(newValue))
|
||||
@@ -50,7 +69,7 @@ struct SecurityAndPrivacyScreen: View {
|
||||
|
||||
Section {
|
||||
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyEncryptionToggleTitle),
|
||||
kind: .toggle(encryptionBinding))
|
||||
kind: .toggle(binding))
|
||||
// We don't allow editing the encryption state if the current setting on the server is `enabled`
|
||||
.disabled(context.viewState.currentSettings.isEncryptionEnabled)
|
||||
} header: {
|
||||
@@ -62,26 +81,153 @@ struct SecurityAndPrivacyScreen: View {
|
||||
}
|
||||
}
|
||||
|
||||
private var historySection: some View {
|
||||
Section {
|
||||
ForEach(context.viewState.availableVisibilityOptions, id: \.self) { option in
|
||||
switch option {
|
||||
case .sinceSelection:
|
||||
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomHistorySinceSelectingOptionTitle),
|
||||
kind: .selection(isSelected: context.desiredSettings.historyVisibility == .sinceSelection) { context.desiredSettings.historyVisibility = .sinceSelection })
|
||||
case .anyone:
|
||||
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomHistoryAnyoneOptionTitle),
|
||||
kind: .selection(isSelected: context.desiredSettings.historyVisibility == .anyone) { context.desiredSettings.historyVisibility = .anyone })
|
||||
case .sinceInvite:
|
||||
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomHistorySinceInviteOptionTitle),
|
||||
kind: .selection(isSelected: context.desiredSettings.historyVisibility == .sinceInvite) { context.desiredSettings.historyVisibility = .sinceInvite })
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text(L10n.screenSecurityAndPrivacyRoomHistorySectionHeader)
|
||||
.compoundListSectionHeader()
|
||||
}
|
||||
}
|
||||
|
||||
private var visibilitySection: some View {
|
||||
Section {
|
||||
EmptyView()
|
||||
} header: {
|
||||
Text(L10n.screenSecurityAndPrivacyRoomVisibilitySectionHeader)
|
||||
.compoundListSectionHeader()
|
||||
} footer: {
|
||||
Text(L10n.screenSecurityAndPrivacyRoomVisibilitySectionFooter(context.viewState.serverName))
|
||||
.compoundListSectionFooter()
|
||||
}
|
||||
}
|
||||
|
||||
private func addressSection(canonicalAlias: String) -> some View {
|
||||
Section {
|
||||
ListRow(label: .plain(title: canonicalAlias), kind: .navigationLink { context.send(viewAction: .editAddress) })
|
||||
} header: {
|
||||
Text(L10n.screenSecurityAndPrivacyRoomAddressSectionHeader)
|
||||
.compoundListSectionHeader()
|
||||
} footer: {
|
||||
Text(L10n.screenSecurityAndPrivacyRoomAddressSectionFooter)
|
||||
.compoundListSectionFooter()
|
||||
}
|
||||
}
|
||||
|
||||
private var publishingSection: some View {
|
||||
Section {
|
||||
EmptyView()
|
||||
} header: {
|
||||
Text(L10n.screenSecurityAndPrivacyRoomPublishingSectionHeader)
|
||||
.compoundListSectionHeader()
|
||||
} footer: {
|
||||
Text(L10n.screenSecurityAndPrivacyRoomPublishingSectionFooter)
|
||||
.compoundListSectionFooter()
|
||||
}
|
||||
}
|
||||
|
||||
private var addAddressSection: some View {
|
||||
Section {
|
||||
ListRow(kind: .custom {
|
||||
Button(L10n.screenSecurityAndPrivacyAddRoomAddressAction) { context.send(viewAction: .editAddress) }
|
||||
.foregroundColor(.compound.textActionAccent)
|
||||
.padding(ListRowPadding.insets)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var roomDirectoryVisibilitySection: some View {
|
||||
let binding = Binding<Bool>(get: {
|
||||
context.desiredSettings.isVisibileInRoomDirectory ?? false
|
||||
}, set: { newValue in
|
||||
context.desiredSettings.isVisibileInRoomDirectory = newValue
|
||||
})
|
||||
|
||||
Section {
|
||||
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomDirectoryVisibilityToggleTitle),
|
||||
details: .isWaiting(context.desiredSettings.isVisibileInRoomDirectory == nil),
|
||||
kind: context.desiredSettings.isVisibileInRoomDirectory == nil ? .label : .toggle(binding))
|
||||
} footer: {
|
||||
Text(L10n.screenSecurityAndPrivacyRoomDirectoryVisibilitySectionFooter(context.viewState.serverName))
|
||||
.compoundListSectionFooter()
|
||||
}
|
||||
}
|
||||
|
||||
@ToolbarContentBuilder
|
||||
var toolbar: some ToolbarContent {
|
||||
private var toolbar: some ToolbarContent {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(L10n.actionSave) {
|
||||
context.send(viewAction: .save)
|
||||
}
|
||||
.disabled(!context.viewState.hasChanges)
|
||||
.disabled(context.viewState.isSaveDisabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
// TODO: Add back TestablePreview, this is WIP so running preview tests for it is not necessary
|
||||
struct SecurityAndPrivacyScreen_Previews: PreviewProvider {
|
||||
static let inviteOnlyViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(joinRule: .invite)))
|
||||
struct SecurityAndPrivacyScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let inviteOnlyViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(joinRule: .invite)),
|
||||
clientProxy: ClientProxyMock(.init()),
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
|
||||
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 publicNoAddressViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false,
|
||||
joinRule: .public)),
|
||||
clientProxy: ClientProxyMock(.init(userIDServerName: "matrix.org")),
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
|
||||
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 var previews: some View {
|
||||
NavigationStack {
|
||||
SecurityAndPrivacyScreen(context: inviteOnlyViewModel.context)
|
||||
}
|
||||
.previewDisplayName("Private invite only room")
|
||||
|
||||
NavigationStack {
|
||||
SecurityAndPrivacyScreen(context: publicViewModel.context)
|
||||
}
|
||||
.snapshotPreferences(expect: publicViewModel.context.$viewState.map { state in
|
||||
state.currentSettings.isVisibileInRoomDirectory == true
|
||||
})
|
||||
.previewDisplayName("Public room")
|
||||
|
||||
NavigationStack {
|
||||
SecurityAndPrivacyScreen(context: publicNoAddressViewModel.context)
|
||||
}
|
||||
.previewDisplayName("Public room without address")
|
||||
|
||||
NavigationStack {
|
||||
SecurityAndPrivacyScreen(context: restrictedViewModel.context)
|
||||
}
|
||||
.snapshotPreferences(expect: restrictedViewModel.context.$viewState.map { state in
|
||||
state.currentSettings.isVisibileInRoomDirectory == true
|
||||
})
|
||||
.previewDisplayName("Restricted room")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user