diff --git a/ElementX/Resources/Localizations/en.lproj/InfoPlist.strings b/ElementX/Resources/Localizations/en.lproj/InfoPlist.strings index 47f84ab950..8c69574587 100644 --- a/ElementX/Resources/Localizations/en.lproj/InfoPlist.strings +++ b/ElementX/Resources/Localizations/en.lproj/InfoPlist.strings @@ -2,4 +2,4 @@ "NSFaceIDUsageDescription" = "Face ID is used to access your app."; "NSLocationWhenInUseUsageDescription" = "Grant location access so that Element X can share your location."; "NSMicrophoneUsageDescription" = "To record and send messages with audio, Element X needs to access the microphone."; -"NSPhotoLibraryUsageDescription" = "Allows saving photos and videos to your library."; +"NSPhotoLibraryUsageDescription" = "This lets you save images and videos to your photo library."; diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index adbfd58391..02c787ecc8 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -274,6 +274,7 @@ "dialog_permission_microphone_description_ios" = "Grant access so you can record and send messages with audio."; "dialog_permission_microphone_title_ios" = "%1$@ needs permission to access your microphone."; "dialog_permission_notification" = "In order to let the application display notifications, please grant the permission in the system settings."; +"dialog_permission_photo_library_title_ios" = "%1$@ does not have access to your photo library."; "dialog_title_confirmation" = "Confirmation"; "dialog_title_warning" = "Warning"; "dialog_unsaved_changes_description_ios" = "Your changes won’t be saved"; @@ -448,6 +449,10 @@ "screen_security_and_privacy_room_access_invite_only_option_description" = "People can only join if they are invited"; "screen_security_and_privacy_room_access_invite_only_option_title" = "Invite only"; "screen_security_and_privacy_room_access_section_title" = "Room access"; +"screen_security_and_privacy_room_history_anyone_option_title" = "Anyone"; +"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_title" = "Security & privacy"; "screen_timeline_item_menu_send_failure_changed_identity" = "Message not sent because %1$@’s verified identity has changed."; "screen_timeline_item_menu_send_failure_unsigned_device" = "Message not sent because %1$@ has not verified all devices."; diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index 046a5b8963..714f3bf415 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -606,6 +606,10 @@ internal enum L10n { } /// In order to let the application display notifications, please grant the permission in the system settings. internal static var dialogPermissionNotification: String { return L10n.tr("Localizable", "dialog_permission_notification") } + /// %1$@ does not have access to your photo library. + internal static func dialogPermissionPhotoLibraryTitleIos(_ p1: Any) -> String { + return L10n.tr("Localizable", "dialog_permission_photo_library_title_ios", String(describing: p1)) + } /// Confirmation internal static var dialogTitleConfirmation: String { return L10n.tr("Localizable", "dialog_title_confirmation") } /// Error @@ -2182,6 +2186,14 @@ internal enum L10n { internal static var screenSecurityAndPrivacyRoomAccessInviteOnlyOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_invite_only_option_title") } /// Room access internal static var screenSecurityAndPrivacyRoomAccessSectionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_access_section_title") } + /// Anyone + internal static var screenSecurityAndPrivacyRoomHistoryAnyoneOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_history_anyone_option_title") } + /// 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 + 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 + internal static var screenSecurityAndPrivacyRoomHistorySinceSelectingOptionTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_room_history_since_selecting_option_title") } /// Security & privacy internal static var screenSecurityAndPrivacyTitle: String { return L10n.tr("Localizable", "screen_security_and_privacy_title") } /// Change account provider diff --git a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift index f4e206858a..db29acdede 100644 --- a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift @@ -625,52 +625,6 @@ open class ClientSDKMock: MatrixRustSDK.Client { } } - //MARK: - createRoomAlias - - open var createRoomAliasRoomAliasRoomIdThrowableError: Error? - var createRoomAliasRoomAliasRoomIdUnderlyingCallsCount = 0 - open var createRoomAliasRoomAliasRoomIdCallsCount: Int { - get { - if Thread.isMainThread { - return createRoomAliasRoomAliasRoomIdUnderlyingCallsCount - } else { - var returnValue: Int? = nil - DispatchQueue.main.sync { - returnValue = createRoomAliasRoomAliasRoomIdUnderlyingCallsCount - } - - return returnValue! - } - } - set { - if Thread.isMainThread { - createRoomAliasRoomAliasRoomIdUnderlyingCallsCount = newValue - } else { - DispatchQueue.main.sync { - createRoomAliasRoomAliasRoomIdUnderlyingCallsCount = newValue - } - } - } - } - open var createRoomAliasRoomAliasRoomIdCalled: Bool { - return createRoomAliasRoomAliasRoomIdCallsCount > 0 - } - open var createRoomAliasRoomAliasRoomIdReceivedArguments: (roomAlias: String, roomId: String)? - open var createRoomAliasRoomAliasRoomIdReceivedInvocations: [(roomAlias: String, roomId: String)] = [] - open var createRoomAliasRoomAliasRoomIdClosure: ((String, String) async throws -> Void)? - - open override func createRoomAlias(roomAlias: String, roomId: String) async throws { - if let error = createRoomAliasRoomAliasRoomIdThrowableError { - throw error - } - createRoomAliasRoomAliasRoomIdCallsCount += 1 - createRoomAliasRoomAliasRoomIdReceivedArguments = (roomAlias: roomAlias, roomId: roomId) - DispatchQueue.main.async { - self.createRoomAliasRoomAliasRoomIdReceivedInvocations.append((roomAlias: roomAlias, roomId: roomId)) - } - try await createRoomAliasRoomAliasRoomIdClosure?(roomAlias, roomId) - } - //MARK: - customLoginWithJwt open var customLoginWithJwtJwtInitialDeviceNameDeviceIdThrowableError: Error? @@ -11326,6 +11280,46 @@ open class RoomSDKMock: MatrixRustSDK.Room { try await editEventIdNewContentClosure?(eventId, newContent) } + //MARK: - enableEncryption + + open var enableEncryptionThrowableError: Error? + var enableEncryptionUnderlyingCallsCount = 0 + open var enableEncryptionCallsCount: Int { + get { + if Thread.isMainThread { + return enableEncryptionUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = enableEncryptionUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + enableEncryptionUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + enableEncryptionUnderlyingCallsCount = newValue + } + } + } + } + open var enableEncryptionCalled: Bool { + return enableEncryptionCallsCount > 0 + } + open var enableEncryptionClosure: (() async throws -> Void)? + + open override func enableEncryption() async throws { + if let error = enableEncryptionThrowableError { + throw error + } + enableEncryptionCallsCount += 1 + try await enableEncryptionClosure?() + } + //MARK: - enableSendQueue var enableSendQueueEnableUnderlyingCallsCount = 0 @@ -11437,6 +11431,75 @@ open class RoomSDKMock: MatrixRustSDK.Room { } } + //MARK: - getRoomVisibility + + open var getRoomVisibilityThrowableError: Error? + var getRoomVisibilityUnderlyingCallsCount = 0 + open var getRoomVisibilityCallsCount: Int { + get { + if Thread.isMainThread { + return getRoomVisibilityUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = getRoomVisibilityUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + getRoomVisibilityUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + getRoomVisibilityUnderlyingCallsCount = newValue + } + } + } + } + open var getRoomVisibilityCalled: Bool { + return getRoomVisibilityCallsCount > 0 + } + + var getRoomVisibilityUnderlyingReturnValue: RoomVisibility! + open var getRoomVisibilityReturnValue: RoomVisibility! { + get { + if Thread.isMainThread { + return getRoomVisibilityUnderlyingReturnValue + } else { + var returnValue: RoomVisibility? = nil + DispatchQueue.main.sync { + returnValue = getRoomVisibilityUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + getRoomVisibilityUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + getRoomVisibilityUnderlyingReturnValue = newValue + } + } + } + } + open var getRoomVisibilityClosure: (() async throws -> RoomVisibility)? + + open override func getRoomVisibility() async throws -> RoomVisibility { + if let error = getRoomVisibilityThrowableError { + throw error + } + getRoomVisibilityCallsCount += 1 + if let getRoomVisibilityClosure = getRoomVisibilityClosure { + return try await getRoomVisibilityClosure() + } else { + return getRoomVisibilityReturnValue + } + } + //MARK: - hasActiveRoomCall var hasActiveRoomCallUnderlyingCallsCount = 0 @@ -13653,6 +13716,75 @@ open class RoomSDKMock: MatrixRustSDK.Room { } } + //MARK: - roomEventsDebugString + + open var roomEventsDebugStringThrowableError: Error? + var roomEventsDebugStringUnderlyingCallsCount = 0 + open var roomEventsDebugStringCallsCount: Int { + get { + if Thread.isMainThread { + return roomEventsDebugStringUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = roomEventsDebugStringUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + roomEventsDebugStringUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + roomEventsDebugStringUnderlyingCallsCount = newValue + } + } + } + } + open var roomEventsDebugStringCalled: Bool { + return roomEventsDebugStringCallsCount > 0 + } + + var roomEventsDebugStringUnderlyingReturnValue: [String]! + open var roomEventsDebugStringReturnValue: [String]! { + get { + if Thread.isMainThread { + return roomEventsDebugStringUnderlyingReturnValue + } else { + var returnValue: [String]? = nil + DispatchQueue.main.sync { + returnValue = roomEventsDebugStringUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + roomEventsDebugStringUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + roomEventsDebugStringUnderlyingReturnValue = newValue + } + } + } + } + open var roomEventsDebugStringClosure: (() async throws -> [String])? + + open override func roomEventsDebugString() async throws -> [String] { + if let error = roomEventsDebugStringThrowableError { + throw error + } + roomEventsDebugStringCallsCount += 1 + if let roomEventsDebugStringClosure = roomEventsDebugStringClosure { + return try await roomEventsDebugStringClosure() + } else { + return roomEventsDebugStringReturnValue + } + } + //MARK: - roomInfo open var roomInfoThrowableError: Error? @@ -14794,6 +14926,144 @@ open class RoomSDKMock: MatrixRustSDK.Room { try await unbanUserUserIdReasonClosure?(userId, reason) } + //MARK: - updateCanonicalAlias + + open var updateCanonicalAliasNewAliasThrowableError: Error? + var updateCanonicalAliasNewAliasUnderlyingCallsCount = 0 + open var updateCanonicalAliasNewAliasCallsCount: Int { + get { + if Thread.isMainThread { + return updateCanonicalAliasNewAliasUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = updateCanonicalAliasNewAliasUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateCanonicalAliasNewAliasUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + updateCanonicalAliasNewAliasUnderlyingCallsCount = newValue + } + } + } + } + open var updateCanonicalAliasNewAliasCalled: Bool { + return updateCanonicalAliasNewAliasCallsCount > 0 + } + open var updateCanonicalAliasNewAliasReceivedNewAlias: String? + open var updateCanonicalAliasNewAliasReceivedInvocations: [String?] = [] + open var updateCanonicalAliasNewAliasClosure: ((String?) async throws -> Void)? + + open override func updateCanonicalAlias(newAlias: String?) async throws { + if let error = updateCanonicalAliasNewAliasThrowableError { + throw error + } + updateCanonicalAliasNewAliasCallsCount += 1 + updateCanonicalAliasNewAliasReceivedNewAlias = newAlias + DispatchQueue.main.async { + self.updateCanonicalAliasNewAliasReceivedInvocations.append(newAlias) + } + try await updateCanonicalAliasNewAliasClosure?(newAlias) + } + + //MARK: - updateHistoryVisibility + + open var updateHistoryVisibilityVisibilityThrowableError: Error? + var updateHistoryVisibilityVisibilityUnderlyingCallsCount = 0 + open var updateHistoryVisibilityVisibilityCallsCount: Int { + get { + if Thread.isMainThread { + return updateHistoryVisibilityVisibilityUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = updateHistoryVisibilityVisibilityUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateHistoryVisibilityVisibilityUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + updateHistoryVisibilityVisibilityUnderlyingCallsCount = newValue + } + } + } + } + open var updateHistoryVisibilityVisibilityCalled: Bool { + return updateHistoryVisibilityVisibilityCallsCount > 0 + } + open var updateHistoryVisibilityVisibilityReceivedVisibility: RoomHistoryVisibility? + open var updateHistoryVisibilityVisibilityReceivedInvocations: [RoomHistoryVisibility] = [] + open var updateHistoryVisibilityVisibilityClosure: ((RoomHistoryVisibility) async throws -> Void)? + + open override func updateHistoryVisibility(visibility: RoomHistoryVisibility) async throws { + if let error = updateHistoryVisibilityVisibilityThrowableError { + throw error + } + updateHistoryVisibilityVisibilityCallsCount += 1 + updateHistoryVisibilityVisibilityReceivedVisibility = visibility + DispatchQueue.main.async { + self.updateHistoryVisibilityVisibilityReceivedInvocations.append(visibility) + } + try await updateHistoryVisibilityVisibilityClosure?(visibility) + } + + //MARK: - updateJoinRules + + open var updateJoinRulesNewRuleThrowableError: Error? + var updateJoinRulesNewRuleUnderlyingCallsCount = 0 + open var updateJoinRulesNewRuleCallsCount: Int { + get { + if Thread.isMainThread { + return updateJoinRulesNewRuleUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = updateJoinRulesNewRuleUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateJoinRulesNewRuleUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + updateJoinRulesNewRuleUnderlyingCallsCount = newValue + } + } + } + } + open var updateJoinRulesNewRuleCalled: Bool { + return updateJoinRulesNewRuleCallsCount > 0 + } + open var updateJoinRulesNewRuleReceivedNewRule: JoinRule? + open var updateJoinRulesNewRuleReceivedInvocations: [JoinRule] = [] + open var updateJoinRulesNewRuleClosure: ((JoinRule) async throws -> Void)? + + open override func updateJoinRules(newRule: JoinRule) async throws { + if let error = updateJoinRulesNewRuleThrowableError { + throw error + } + updateJoinRulesNewRuleCallsCount += 1 + updateJoinRulesNewRuleReceivedNewRule = newRule + DispatchQueue.main.async { + self.updateJoinRulesNewRuleReceivedInvocations.append(newRule) + } + try await updateJoinRulesNewRuleClosure?(newRule) + } + //MARK: - updatePowerLevelsForUsers open var updatePowerLevelsForUsersUpdatesThrowableError: Error? @@ -14840,6 +15110,52 @@ open class RoomSDKMock: MatrixRustSDK.Room { try await updatePowerLevelsForUsersUpdatesClosure?(updates) } + //MARK: - updateRoomVisibility + + open var updateRoomVisibilityVisibilityThrowableError: Error? + var updateRoomVisibilityVisibilityUnderlyingCallsCount = 0 + open var updateRoomVisibilityVisibilityCallsCount: Int { + get { + if Thread.isMainThread { + return updateRoomVisibilityVisibilityUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = updateRoomVisibilityVisibilityUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + updateRoomVisibilityVisibilityUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + updateRoomVisibilityVisibilityUnderlyingCallsCount = newValue + } + } + } + } + open var updateRoomVisibilityVisibilityCalled: Bool { + return updateRoomVisibilityVisibilityCallsCount > 0 + } + open var updateRoomVisibilityVisibilityReceivedVisibility: RoomVisibility? + open var updateRoomVisibilityVisibilityReceivedInvocations: [RoomVisibility] = [] + open var updateRoomVisibilityVisibilityClosure: ((RoomVisibility) async throws -> Void)? + + open override func updateRoomVisibility(visibility: RoomVisibility) async throws { + if let error = updateRoomVisibilityVisibilityThrowableError { + throw error + } + updateRoomVisibilityVisibilityCallsCount += 1 + updateRoomVisibilityVisibilityReceivedVisibility = visibility + DispatchQueue.main.async { + self.updateRoomVisibilityVisibilityReceivedInvocations.append(visibility) + } + try await updateRoomVisibilityVisibilityClosure?(visibility) + } + //MARK: - uploadAvatar open var uploadAvatarMimeTypeDataMediaInfoThrowableError: Error? diff --git a/ElementX/Sources/Mocks/InvitedRoomProxyMock.swift b/ElementX/Sources/Mocks/InvitedRoomProxyMock.swift index 79b48100c8..76a65cf3de 100644 --- a/ElementX/Sources/Mocks/InvitedRoomProxyMock.swift +++ b/ElementX/Sources/Mocks/InvitedRoomProxyMock.swift @@ -60,7 +60,8 @@ extension RoomInfo { numUnreadNotifications: 0, numUnreadMentions: 0, pinnedEventIds: [], - joinRule: .invite) + joinRule: .invite, + historyVisibility: nil) } } diff --git a/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift b/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift index 30edf80776..a48a64b737 100644 --- a/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift +++ b/ElementX/Sources/Mocks/JoinedRoomProxyMock.swift @@ -170,6 +170,7 @@ extension RoomInfo { numUnreadNotifications: 0, numUnreadMentions: 0, pinnedEventIds: Array(configuration.pinnedEventIDs), - joinRule: configuration.joinRule) + joinRule: configuration.joinRule, + historyVisibility: nil) } } diff --git a/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift b/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift index 82d84d31ab..e9084330ed 100644 --- a/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift +++ b/ElementX/Sources/Mocks/KnockedRoomProxyMock.swift @@ -58,6 +58,7 @@ extension RoomInfo { numUnreadNotifications: 0, numUnreadMentions: 0, pinnedEventIds: [], - joinRule: .knock) + joinRule: .knock, + historyVisibility: nil) } } diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenModels.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenModels.swift index c017871018..ebff3ef40d 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenModels.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenModels.swift @@ -21,8 +21,11 @@ struct SecurityAndPrivacyScreenViewState: BindableState { } init(accessType: SecurityAndPrivacyRoomAccessType, - isEncryptionEnabled: Bool) { - let settings = SecurityAndPrivacySettings(accessType: accessType, isEncryptionEnabled: isEncryptionEnabled) + isEncryptionEnabled: Bool, + historyVisibility: SecurityAndPrivacyHistoryVisibility) { + let settings = SecurityAndPrivacySettings(accessType: accessType, + isEncryptionEnabled: isEncryptionEnabled, + historyVisibility: historyVisibility) currentSettings = settings bindings = SecurityAndPrivacyScreenViewStateBindings(desiredSettings: settings) } @@ -36,6 +39,7 @@ struct SecurityAndPrivacyScreenViewStateBindings { struct SecurityAndPrivacySettings: Equatable { var accessType: SecurityAndPrivacyRoomAccessType var isEncryptionEnabled: Bool + var historyVisibility: SecurityAndPrivacyHistoryVisibility } enum SecurityAndPrivacyRoomAccessType { @@ -52,3 +56,18 @@ enum SecurityAndPrivacyScreenViewAction { case save case tryUpdatingEncryption(Bool) } + +enum SecurityAndPrivacyHistoryVisibility { + case sinceSelection + case sinceInvite + case anyone + + var isAllowedInPublicRoom: Bool { + switch self { + case .anyone, .sinceSelection: + true + default: + false + } + } +} diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift index effae2043a..1b2c3d1bf9 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/SecurityAndPrivacyScreenViewModel.swift @@ -6,6 +6,7 @@ // import Combine +import MatrixRustSDK import SwiftUI typealias SecurityAndPrivacyScreenViewModelType = StateStoreViewModel @@ -17,11 +18,14 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType, var actionsPublisher: AnyPublisher { actionsSubject.eraseToAnyPublisher() } - + init(roomProxy: JoinedRoomProxyProtocol) { self.roomProxy = roomProxy super.init(initialViewState: SecurityAndPrivacyScreenViewState(accessType: roomProxy.infoPublisher.value.roomAccessType, - isEncryptionEnabled: roomProxy.isEncrypted)) + isEncryptionEnabled: roomProxy.isEncrypted, + historyVisibility: roomProxy.infoPublisher.value.historyVisibility.toSecurityAndPrivacyHistoryVisibility)) + + setupSubscriptions() } // MARK: - Public @@ -37,15 +41,44 @@ class SecurityAndPrivacyScreenViewModel: SecurityAndPrivacyScreenViewModelType, state.bindings.alertInfo = .init(id: .enableEncryption, title: L10n.screenSecurityAndPrivacyEnableEncryptionAlertTitle, message: L10n.screenSecurityAndPrivacyEnableEncryptionAlertDescription, - primaryButton: .init(title: L10n.screenSecurityAndPrivacyEnableEncryptionAlertConfirmButtonTitle) { [weak self] in - self?.state.bindings.desiredSettings.isEncryptionEnabled = true - }, + primaryButton: .init(title: L10n.screenSecurityAndPrivacyEnableEncryptionAlertConfirmButtonTitle) { [weak self] in self?.state.bindings.desiredSettings.isEncryptionEnabled = true }, secondaryButton: .init(title: L10n.actionCancel, role: .cancel, action: nil)) } else { state.bindings.desiredSettings.isEncryptionEnabled = false } } } + + // MARK: - Private + + private func setupSubscriptions() { + context.$viewState + .map(\.bindings.desiredSettings.accessType) + .removeDuplicates() + // Use this otherwise the value won't update properly in the view + .receive(on: DispatchQueue.main) + .sink { [weak self] desiredAcessType in + guard let self else { return } + if (desiredAcessType == .anyone && !state.bindings.desiredSettings.historyVisibility.isAllowedInPublicRoom) || + (desiredAcessType != .anyone && state.bindings.desiredSettings.historyVisibility == .anyone) { + self.state.bindings.desiredSettings.historyVisibility = .sinceSelection + } + } + .store(in: &cancellables) + + context.$viewState + .map(\.bindings.desiredSettings.isEncryptionEnabled) + .removeDuplicates() + // Use this otherwise the value won't update properly in the view + .receive(on: DispatchQueue.main) + .sink { [weak self] desiredIsEncryptionEnabled in + guard let self else { return } + if desiredIsEncryptionEnabled, state.bindings.desiredSettings.historyVisibility == .anyone { + self.state.bindings.desiredSettings.historyVisibility = .sinceSelection + } + } + .store(in: &cancellables) + } } private extension RoomInfoProxy { @@ -60,3 +93,16 @@ private extension RoomInfoProxy { } } } + +private extension RoomHistoryVisibility? { + var toSecurityAndPrivacyHistoryVisibility: SecurityAndPrivacyHistoryVisibility { + switch self { + case .joined, .invited: + return .sinceInvite + case .shared, .custom, .none: + return .sinceSelection + case .worldReadable: + return .anyone + } + } +} diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift index 47379e3c60..e51681adff 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift @@ -15,6 +15,7 @@ struct SecurityAndPrivacyScreen: View { Form { roomAccessSection encryptionSection + historySection } .compoundList() .navigationBarTitleDisplayMode(.inline) @@ -62,6 +63,23 @@ struct SecurityAndPrivacyScreen: View { } } + private var historySection: some View { + Section { + ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomHistorySinceSelectingOptionTitle), + kind: .selection(isSelected: context.desiredSettings.historyVisibility == .sinceSelection) { context.desiredSettings.historyVisibility = .sinceSelection }) + if context.desiredSettings.accessType == .anyone, !context.desiredSettings.isEncryptionEnabled { + ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomHistoryAnyoneOptionTitle), + kind: .selection(isSelected: context.desiredSettings.historyVisibility == .anyone) { context.desiredSettings.historyVisibility = .anyone }) + } else { + ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomHistorySinceInviteOptionTitle), + kind: .selection(isSelected: context.desiredSettings.historyVisibility == .sinceInvite) { context.desiredSettings.historyVisibility = .sinceInvite }) + } + } header: { + Text(L10n.screenSecurityAndPrivacyRoomHistorySectionHeader) + .compoundListSectionHeader() + } + } + @ToolbarContentBuilder var toolbar: some ToolbarContent { ToolbarItem(placement: .confirmationAction) { @@ -79,9 +97,17 @@ struct SecurityAndPrivacyScreen: View { struct SecurityAndPrivacyScreen_Previews: PreviewProvider { static let inviteOnlyViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(joinRule: .invite))) + static let publicViewModel = SecurityAndPrivacyScreenViewModel(roomProxy: JoinedRoomProxyMock(.init(isEncrypted: false, joinRule: .public))) + static var previews: some View { NavigationStack { SecurityAndPrivacyScreen(context: inviteOnlyViewModel.context) } + .previewDisplayName("Private invite only room") + + NavigationStack { + SecurityAndPrivacyScreen(context: publicViewModel.context) + } + .previewDisplayName("Public room") } } diff --git a/ElementX/Sources/Services/Room/RoomInfoProxy.swift b/ElementX/Sources/Services/Room/RoomInfoProxy.swift index 5e9da29e3a..01692ce9ee 100644 --- a/ElementX/Sources/Services/Room/RoomInfoProxy.swift +++ b/ElementX/Sources/Services/Room/RoomInfoProxy.swift @@ -66,6 +66,7 @@ struct RoomInfoProxy: BaseRoomInfoProxyProtocol { var unreadMentionsCount: UInt { UInt(roomInfo.numUnreadMentions) } var pinnedEventIDs: Set { Set(roomInfo.pinnedEventIds) } var joinRule: JoinRule? { roomInfo.joinRule } + var historyVisibility: RoomHistoryVisibility? { roomInfo.historyVisibility } } struct RoomPreviewInfoProxy: BaseRoomInfoProxyProtocol {