Update history visible settings (#4861)

* Fixes #4852 - Update history visible settings

* Address PR comments and move attributed strings to the view state
This commit is contained in:
Stefan Ceriu
2025-12-17 15:04:02 +02:00
committed by GitHub
parent aa28ef3f19
commit 8164c88437
34 changed files with 150 additions and 112 deletions
@@ -702,9 +702,11 @@
"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_title" = "Visible in public directory";
"screen_security_and_privacy_room_history_anyone_option_title" = "Anyone (history is public)";
"screen_security_and_privacy_room_history_section_footer" = "Changes won't affect past messages, only new ones. %@";
"screen_security_and_privacy_room_history_section_header" = "Who can read history";
"screen_security_and_privacy_room_history_since_invite_option_title" = "Members only since they were invited";
"screen_security_and_privacy_room_history_since_selecting_option_title" = "Members only since selecting this option";
"screen_security_and_privacy_room_history_since_invite_option_title" = "Members since invited";
"screen_security_and_privacy_room_history_since_selecting_option_title" = "Members (full history)";
"screen_security_and_privacy_room_publishing_section_footer" = "Room addresses are ways to find and access rooms. This also ensures you can easily share your room with others.\nYou can choose to publish your room in your homeserver public room directory.";
"screen_security_and_privacy_room_publishing_section_header" = "Room publishing";
"screen_security_and_privacy_room_visibility_section_footer" = "Addresses are a way to find and access rooms and spaces. This also ensures you can easily share them with others.";
@@ -1411,7 +1413,6 @@
"screen_security_and_privacy_room_access_anyone_option_title" = "Anyone";
"screen_security_and_privacy_room_access_footer_manage_spaces_action" = "Manage spaces";
"screen_security_and_privacy_room_address_section_footer" = "You’ll need an address in order to make it visible in the public directory.";
"screen_security_and_privacy_room_history_anyone_option_title" = "Anyone";
"screen_space_settings_leave_space" = "Leave space";
"screen_space_settings_roles_and_permissions" = "Roles & permissions";
"screen_space_settings_security_and_privacy" = "Security & privacy";
@@ -702,9 +702,11 @@
"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_title" = "Visible in public directory";
"screen_security_and_privacy_room_history_anyone_option_title" = "Anyone (history is public)";
"screen_security_and_privacy_room_history_section_footer" = "Changes won't affect past messages, only new ones. %@";
"screen_security_and_privacy_room_history_section_header" = "Who can read history";
"screen_security_and_privacy_room_history_since_invite_option_title" = "Members only since they were invited";
"screen_security_and_privacy_room_history_since_selecting_option_title" = "Members only since selecting this option";
"screen_security_and_privacy_room_history_since_invite_option_title" = "Members since invited";
"screen_security_and_privacy_room_history_since_selecting_option_title" = "Members (full history)";
"screen_security_and_privacy_room_publishing_section_footer" = "Room addresses are ways to find and access rooms. This also ensures you can easily share your room with others.\nYou can choose to publish your room in your homeserver public room directory.";
"screen_security_and_privacy_room_publishing_section_header" = "Room publishing";
"screen_security_and_privacy_room_visibility_section_footer" = "Addresses are a way to find and access rooms and spaces. This also ensures you can easily share them with others.";
@@ -1411,7 +1413,6 @@
"screen_security_and_privacy_room_access_anyone_option_title" = "Anyone";
"screen_security_and_privacy_room_access_footer_manage_spaces_action" = "Manage spaces";
"screen_security_and_privacy_room_address_section_footer" = "You’ll need an address in order to make it visible in the public directory.";
"screen_security_and_privacy_room_history_anyone_option_title" = "Anyone";
"screen_space_settings_leave_space" = "Leave space";
"screen_space_settings_roles_and_permissions" = "Roles & permissions";
"screen_space_settings_security_and_privacy" = "Security & privacy";
@@ -125,6 +125,7 @@ final class AppSettings {
deviceVerificationURL: URL,
chatBackupDetailsURL: URL,
identityPinningViolationDetailsURL: URL,
historySharingDetailsURL: URL,
elementWebHosts: [String],
accountProvisioningHost: String,
bugReportApplicationID: String,
@@ -144,6 +145,7 @@ final class AppSettings {
self.deviceVerificationURL = deviceVerificationURL
self.chatBackupDetailsURL = chatBackupDetailsURL
self.identityPinningViolationDetailsURL = identityPinningViolationDetailsURL
self.historySharingDetailsURL = historySharingDetailsURL
self.elementWebHosts = elementWebHosts
self.accountProvisioningHost = accountProvisioningHost
self.bugReportApplicationID = bugReportApplicationID
@@ -202,6 +204,9 @@ final class AppSettings {
private(set) var chatBackupDetailsURL: URL = "https://element.io/help#encryption5"
/// A URL where users can go read more about identity pinning violations
private(set) var identityPinningViolationDetailsURL: URL = "https://element.io/help#encryption18"
/// A URL describing how history sharing works
private(set) var historySharingDetailsURL: URL = "https://element.io/en/help#e2ee-history-sharing"
/// Any domains that Element web may be hosted on - used for handling links.
private(set) var elementWebHosts = ["app.element.io", "staging.element.io", "develop.element.io"]
/// The domain that account provisioning links will be hosted on - used for handling the links.
+7 -3
View File
@@ -2906,13 +2906,17 @@ internal enum L10n {
internal static var screenSecurityAndPrivacyRoomDirectoryVisibilityToggleDescription: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_directory_visibility_toggle_description") }
/// Visible in public directory
internal static var screenSecurityAndPrivacyRoomDirectoryVisibilityToggleTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_directory_visibility_toggle_title") }
/// Anyone
/// Anyone (history is public)
internal static var screenSecurityAndPrivacyRoomHistoryAnyoneOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_history_anyone_option_title") }
/// Changes won't affect past messages, only new ones. %@
internal static func screenSecurityAndPrivacyRoomHistorySectionFooter(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_security_and_privacy_room_history_section_footer", String(describing: p1))
}
/// Who can read history
internal static var screenSecurityAndPrivacyRoomHistorySectionHeader: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_history_section_header") }
/// Members only since they were invited
/// Members since invited
internal static var screenSecurityAndPrivacyRoomHistorySinceInviteOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_history_since_invite_option_title") }
/// Members only since selecting this option
/// Members (full history)
internal static var screenSecurityAndPrivacyRoomHistorySinceSelectingOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_history_since_selecting_option_title") }
/// Room addresses are ways to find and access rooms. This also ensures you can easily share your room with others.
/// You can choose to publish your room in your homeserver public room directory.
@@ -15,20 +15,12 @@ enum SecurityAndPrivacyScreenViewModelAction {
}
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
let strings: SecurityAndPrivacyScreenStrings
var canonicalAlias: String?
var isKnockingEnabled: Bool
var isSpaceSettingsEnabled: Bool
@@ -38,6 +30,7 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
var canEditJoinRule = false
var canEnableEncryption = false
var canEditHistoryVisibility = false
/// The union of joined parent spaces and the joined spaces in the current access type
var selectableJoinedSpaces: [SpaceRoomProxyProtocol] = []
@@ -58,13 +51,13 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
}
var availableVisibilityOptions: [SecurityAndPrivacyHistoryVisibility] {
var options = [SecurityAndPrivacyHistoryVisibility.sinceSelection]
var options = [SecurityAndPrivacyHistoryVisibility.shared]
if !bindings.desiredSettings.isEncryptionEnabled, bindings.desiredSettings.accessType == .anyone {
options.append(.anyone)
options.append(.worldReadable)
} else {
options.append(.sinceInvite)
options.append(.invited)
}
return options
return options.sorted()
}
var isSpaceMembersOptionAvailable: Bool {
@@ -113,16 +106,14 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
}
}
var accessSectionFooter: AttributedString? {
if (bindings.desiredSettings.accessType.isSpaceMembers &&
isSpaceMembersOptionSelectable) ||
(bindings.desiredSettings.accessType.isAskToJoinWithSpaceMembers &&
isAskToJoinWithSpaceMembersOptionSelectable),
var shouldShowAccessSectionFooter: Bool {
if (bindings.desiredSettings.accessType.isSpaceMembers && isSpaceMembersOptionSelectable) ||
(bindings.desiredSettings.accessType.isAskToJoinWithSpaceMembers && isAskToJoinWithSpaceMembersOptionSelectable),
case .multiple = spaceSelection {
Self.accessSectionFooterAttributedString
} else {
nil
return true
}
return false
}
enum SpaceSelection {
@@ -169,7 +160,8 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
historyVisibility: SecurityAndPrivacyHistoryVisibility,
isSpace: Bool,
isKnockingEnabled: Bool,
isSpaceSettingsEnabled: Bool) {
isSpaceSettingsEnabled: Bool,
historySharingDetailsURL: URL) {
self.serverName = serverName
self.isKnockingEnabled = isKnockingEnabled
self.isSpace = isSpace
@@ -180,6 +172,7 @@ struct SecurityAndPrivacyScreenViewState: BindableState {
historyVisibility: historyVisibility)
currentSettings = settings
bindings = SecurityAndPrivacyScreenViewStateBindings(desiredSettings: settings)
strings = SecurityAndPrivacyScreenStrings(historySharingDetailsURL: historySharingDetailsURL)
}
}
@@ -254,17 +247,44 @@ enum SecurityAndPrivacyScreenViewAction {
case manageSpaces
}
enum SecurityAndPrivacyHistoryVisibility {
case sinceSelection
case sinceInvite
case anyone
enum SecurityAndPrivacyHistoryVisibility: Int, Comparable {
case invited
case shared
case worldReadable
var fallbackOption: Self {
switch self {
case .sinceInvite, .sinceSelection:
return .sinceSelection
case .anyone:
return .sinceInvite
case .invited, .shared:
return .shared
case .worldReadable:
return .invited
}
}
static func < (lhs: SecurityAndPrivacyHistoryVisibility, rhs: SecurityAndPrivacyHistoryVisibility) -> Bool {
lhs.rawValue < rhs.rawValue
}
}
struct SecurityAndPrivacyScreenStrings {
let accessSectionFooterString: AttributedString
let historySectionFooterString: AttributedString
init(historySharingDetailsURL: URL) {
let linkPlaceholder = "{link}"
var accessFooterString = AttributedString(L10n.screenSecurityAndPrivacyRoomAccessFooter(linkPlaceholder))
var accessLinkString = AttributedString(L10n.screenSecurityAndPrivacyRoomAccessFooterManageSpacesAction)
accessLinkString.link = .init(stringLiteral: "action://manageSpace") // The link address doesn't matter
accessLinkString.bold()
accessFooterString.replace(linkPlaceholder, with: accessLinkString)
accessSectionFooterString = accessFooterString
var historyFooterString = AttributedString(L10n.screenSecurityAndPrivacyRoomHistorySectionFooter(linkPlaceholder))
var historyLinkString = AttributedString(L10n.actionLearnMore)
historyLinkString.link = historySharingDetailsURL
historyLinkString.bold()
historyFooterString.replace(linkPlaceholder, with: historyLinkString)
historySectionFooterString = historyFooterString
}
}
@@ -38,7 +38,8 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType,
historyVisibility: roomProxy.infoPublisher.value.historyVisibility.toSecurityAndPrivacyHistoryVisibility,
isSpace: roomProxy.infoPublisher.value.isSpace,
isKnockingEnabled: appSettings.knockingEnabled,
isSpaceSettingsEnabled: appSettings.spaceSettingsEnabled))
isSpaceSettingsEnabled: appSettings.spaceSettingsEnabled,
historySharingDetailsURL: appSettings.historySharingDetailsURL))
if let powerLevels = roomProxy.infoPublisher.value.powerLevels {
setupPermissions(powerLevels: powerLevels)
@@ -345,11 +346,11 @@ private extension RoomHistoryVisibility {
var toSecurityAndPrivacyHistoryVisibility: SecurityAndPrivacyHistoryVisibility {
switch self {
case .joined, .invited:
return .sinceInvite
return .invited
case .shared, .custom:
return .sinceSelection
return .shared
case .worldReadable:
return .anyone
return .worldReadable
}
}
}
@@ -357,11 +358,11 @@ private extension RoomHistoryVisibility {
private extension SecurityAndPrivacyHistoryVisibility {
var toRoomHistoryVisibility: RoomHistoryVisibility {
switch self {
case .sinceSelection:
case .shared:
return .shared
case .sinceInvite:
case .invited:
return .invited
case .anyone:
case .worldReadable:
return .worldReadable
}
}
@@ -80,8 +80,8 @@ struct SecurityAndPrivacyScreen: View {
Text(L10n.screenSecurityAndPrivacyRoomAccessSectionHeader)
.compoundListSectionHeader()
} footer: {
if let footer = context.viewState.accessSectionFooter {
Text(footer)
if context.viewState.shouldShowAccessSectionFooter {
Text(context.viewState.strings.accessSectionFooterString)
.compoundListSectionFooter()
.environment(\.openURL, OpenURLAction { _ in
context.send(viewAction: .manageSpaces)
@@ -133,20 +133,23 @@ struct SecurityAndPrivacyScreen: 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:
case .invited:
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomHistorySinceInviteOptionTitle),
kind: .selection(isSelected: context.desiredSettings.historyVisibility == .sinceInvite) { context.desiredSettings.historyVisibility = .sinceInvite })
kind: .selection(isSelected: context.desiredSettings.historyVisibility == .invited) { context.desiredSettings.historyVisibility = .invited })
case .shared:
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomHistorySinceSelectingOptionTitle),
kind: .selection(isSelected: context.desiredSettings.historyVisibility == .shared) { context.desiredSettings.historyVisibility = .shared })
case .worldReadable:
ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomHistoryAnyoneOptionTitle),
kind: .selection(isSelected: context.desiredSettings.historyVisibility == .worldReadable) { context.desiredSettings.historyVisibility = .worldReadable })
}
}
} header: {
Text(L10n.screenSecurityAndPrivacyRoomHistorySectionHeader)
.compoundListSectionHeader()
} footer: {
Text(context.viewState.strings.historySectionFooterString)
.compoundListSectionFooter()
}
}
@@ -140,6 +140,7 @@ class MockScreen: Identifiable {
deviceVerificationURL: appSettings.deviceVerificationURL,
chatBackupDetailsURL: appSettings.chatBackupDetailsURL,
identityPinningViolationDetailsURL: appSettings.identityPinningViolationDetailsURL,
historySharingDetailsURL: appSettings.historySharingDetailsURL,
elementWebHosts: appSettings.elementWebHosts,
accountProvisioningHost: appSettings.accountProvisioningHost,
bugReportApplicationID: appSettings.bugReportApplicationID,