mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 11:58:32 +00:00
Alert the user about configuration mismatch in notification settings (#1543)
This commit is contained in:
@@ -686,6 +686,14 @@ public enum L10n {
|
||||
public static var screenNotificationSettingsAdditionalSettingsSectionTitle: String { return L10n.tr("Localizable", "screen_notification_settings_additional_settings_section_title") }
|
||||
/// Audio and video calls
|
||||
public static var screenNotificationSettingsCallsLabel: String { return L10n.tr("Localizable", "screen_notification_settings_calls_label") }
|
||||
/// Configuration mismatch
|
||||
public static var screenNotificationSettingsConfigurationMismatch: String { return L10n.tr("Localizable", "screen_notification_settings_configuration_mismatch") }
|
||||
/// We’ve simplified Notifications Settings to make options easier to find.
|
||||
///
|
||||
/// Some custom settings you’ve chosen in the past are not shown here, but they’re still active.
|
||||
///
|
||||
/// If you proceed, some of your settings may change.
|
||||
public static var screenNotificationSettingsConfigurationMismatchDescription: String { return L10n.tr("Localizable", "screen_notification_settings_configuration_mismatch_description") }
|
||||
/// Direct chats
|
||||
public static var screenNotificationSettingsDirectChats: String { return L10n.tr("Localizable", "screen_notification_settings_direct_chats") }
|
||||
/// Custom setting per chat
|
||||
@@ -702,6 +710,8 @@ public enum L10n {
|
||||
public static var screenNotificationSettingsEditScreenGroupSectionHeader: String { return L10n.tr("Localizable", "screen_notification_settings_edit_screen_group_section_header") }
|
||||
/// Enable notifications on this device
|
||||
public static var screenNotificationSettingsEnableNotifications: String { return L10n.tr("Localizable", "screen_notification_settings_enable_notifications") }
|
||||
/// The configuration has not been corrected, please try again.
|
||||
public static var screenNotificationSettingsFailedFixingConfiguration: String { return L10n.tr("Localizable", "screen_notification_settings_failed_fixing_configuration") }
|
||||
/// Group chats
|
||||
public static var screenNotificationSettingsGroupChats: String { return L10n.tr("Localizable", "screen_notification_settings_group_chats") }
|
||||
/// Mentions
|
||||
|
||||
@@ -38,6 +38,8 @@ extension NotificationSettingsProxyMock {
|
||||
getDefaultRoomNotificationModeIsEncryptedIsOneToOneReturnValue = configuration.defaultRoomMode
|
||||
getUserDefinedRoomNotificationModeRoomIdReturnValue = configuration.roomMode.isDefault ? nil : configuration.roomMode.mode
|
||||
getRoomsWithUserDefinedRulesReturnValue = []
|
||||
isRoomMentionEnabledReturnValue = true
|
||||
isCallEnabledReturnValue = true
|
||||
|
||||
setNotificationModeRoomIdModeClosure = { [weak self] _, mode in
|
||||
guard let self else { return }
|
||||
|
||||
@@ -36,6 +36,7 @@ struct A11yIdentifiers {
|
||||
static let invitesScreen = InvitesScreen()
|
||||
static let welcomeScreen = WelcomeScreen()
|
||||
static let migrationScreen = MigrationScreen()
|
||||
static let notificationSettingsScreen = NotificationSettingsScreen()
|
||||
static let notificationSettingsEditScreen = NotificationSettingsEditScreen()
|
||||
|
||||
struct AnalyticsPromptScreen {
|
||||
@@ -163,6 +164,10 @@ struct A11yIdentifiers {
|
||||
let message = "migration_screen-message"
|
||||
}
|
||||
|
||||
struct NotificationSettingsScreen {
|
||||
let fixMismatchConfiguration = "notification_settings_screen-fix_mismatch_configuration"
|
||||
}
|
||||
|
||||
struct NotificationSettingsEditScreen {
|
||||
let roomNamePrefix = "notification_settings_edit_screen-room_name"
|
||||
func roomName(_ name: String) -> String {
|
||||
|
||||
+9
-1
@@ -28,6 +28,7 @@ struct NotificationSettingsScreenViewState: BindableState {
|
||||
let isModallyPresented: Bool
|
||||
var isUserPermissionGranted: Bool?
|
||||
var allowedNotificationModes: [RoomNotificationModeProxy] = [.allMessages, .mentionsAndKeywordsOnly]
|
||||
var fixingConfigurationMismatch = false
|
||||
|
||||
var showSystemNotificationsAlert: Bool {
|
||||
bindings.enableNotifications && isUserPermissionGranted == false
|
||||
@@ -51,7 +52,12 @@ struct NotificationSettingsScreenSettings {
|
||||
let callsEnabled: Bool?
|
||||
// Old clients were having specific settings for encrypted and unencrypted rooms,
|
||||
// so it's possible for `group chats` and `direct chats` settings to be inconsistent (e.g. encrypted `direct chats` can have a different mode that unencrypted `direct chats`)
|
||||
let inconsistentSettings: Bool
|
||||
let inconsistentSettings: [NotificationSettingsScreenInvalidSetting]
|
||||
}
|
||||
|
||||
struct NotificationSettingsScreenInvalidSetting: Equatable {
|
||||
let chatType: NotificationSettingsChatType
|
||||
let isEncrypted: Bool
|
||||
}
|
||||
|
||||
struct NotificationSettingsScreenStrings {
|
||||
@@ -87,9 +93,11 @@ enum NotificationSettingsScreenViewAction {
|
||||
case roomMentionChanged
|
||||
case callsChanged
|
||||
case close
|
||||
case fixConfigurationMismatchTapped
|
||||
}
|
||||
|
||||
enum NotificationSettingsScreenErrorType: Hashable {
|
||||
/// A specific error message shown in an alert.
|
||||
case alert
|
||||
case fixMismatchConfigurationFailed
|
||||
}
|
||||
|
||||
+30
-4
@@ -77,6 +77,8 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
||||
Task { await enableCalls(state.bindings.callsEnabled) }
|
||||
case .close:
|
||||
actionsSubject.send(.close)
|
||||
case .fixConfigurationMismatchTapped:
|
||||
Task { await fixConfigurationMismatch() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +123,7 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
||||
|
||||
private func fetchSettings() {
|
||||
fetchSettingsTask = Task {
|
||||
var inconsistentSettings: [NotificationSettingsScreenInvalidSetting] = []
|
||||
// Group chats
|
||||
var groupChatsMode = await notificationSettingsProxy.getDefaultRoomNotificationMode(isEncrypted: false, isOneToOne: false)
|
||||
let encryptedGroupChatsMode = await notificationSettingsProxy.getDefaultRoomNotificationMode(isEncrypted: true, isOneToOne: false)
|
||||
@@ -131,14 +134,15 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
||||
|
||||
// Old clients were having specific settings for encrypted and unencrypted rooms,
|
||||
// so it's possible for `group chats` and `direct chats` settings to be inconsistent (e.g. encrypted `direct chats` can have a different mode that unencrypted `direct chats`)
|
||||
var inconsistencyDetected = false
|
||||
if groupChatsMode != encryptedGroupChatsMode {
|
||||
groupChatsMode = .allMessages
|
||||
inconsistencyDetected = true
|
||||
// a default setting for a chat can only be `.allMessages` or `.mentionsAndKeywordsOnly`.
|
||||
inconsistentSettings.append(.init(chatType: .groupChat, isEncrypted: encryptedGroupChatsMode != .allMessages))
|
||||
}
|
||||
if directChatsMode != encryptedDirectChatsMode {
|
||||
directChatsMode = .allMessages
|
||||
inconsistencyDetected = true
|
||||
// a default setting for a chat can only be `.allMessages` or `.mentionsAndKeywordsOnly`.
|
||||
inconsistentSettings.append(.init(chatType: .oneToOneChat, isEncrypted: encryptedDirectChatsMode != .allMessages))
|
||||
}
|
||||
|
||||
// The following calls may fail if the associated push rule doesn't exist
|
||||
@@ -151,7 +155,7 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
||||
directChatsMode: directChatsMode,
|
||||
roomMentionsEnabled: roomMentionsEnabled,
|
||||
callsEnabled: callEnabled,
|
||||
inconsistentSettings: inconsistencyDetected)
|
||||
inconsistentSettings: inconsistentSettings)
|
||||
|
||||
state.settings = notificationSettings
|
||||
state.bindings.roomMentionsEnabled = notificationSettings.roomMentionsEnabled ?? false
|
||||
@@ -159,6 +163,28 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
||||
}
|
||||
}
|
||||
|
||||
private func fixConfigurationMismatch() async {
|
||||
guard let settings = state.settings, !settings.inconsistentSettings.isEmpty, !state.fixingConfigurationMismatch else { return }
|
||||
state.fixingConfigurationMismatch = true
|
||||
|
||||
var failures: [NotificationSettingsScreenInvalidSetting] = []
|
||||
for inconsistentSetting in settings.inconsistentSettings {
|
||||
do {
|
||||
try await notificationSettingsProxy.setDefaultRoomNotificationMode(isEncrypted: inconsistentSetting.isEncrypted, isOneToOne: inconsistentSetting.chatType == .oneToOneChat, mode: .allMessages)
|
||||
} catch {
|
||||
failures.append(inconsistentSetting)
|
||||
}
|
||||
}
|
||||
|
||||
if !failures.isEmpty {
|
||||
state.bindings.alertInfo = AlertInfo(id: .fixMismatchConfigurationFailed,
|
||||
title: L10n.commonError,
|
||||
message: L10n.screenNotificationSettingsFailedFixingConfiguration,
|
||||
primaryButton: .init(title: L10n.actionOk, action: nil))
|
||||
}
|
||||
state.fixingConfigurationMismatch = false
|
||||
}
|
||||
|
||||
private func enableRoomMention(_ enable: Bool) async {
|
||||
guard let notificationSettings = state.settings else { return }
|
||||
do {
|
||||
|
||||
+72
-10
@@ -22,17 +22,21 @@ struct NotificationSettingsScreen: View {
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
if context.viewState.showSystemNotificationsAlert {
|
||||
userPermissionSection
|
||||
}
|
||||
enableNotificationSection
|
||||
if context.enableNotifications {
|
||||
roomsNotificationSection
|
||||
if context.viewState.settings?.roomMentionsEnabled != nil {
|
||||
mentionsSection
|
||||
if context.viewState.settings?.inconsistentSettings.isEmpty == false {
|
||||
configurationMismatchSection
|
||||
} else {
|
||||
if context.viewState.showSystemNotificationsAlert {
|
||||
userPermissionSection
|
||||
}
|
||||
if context.viewState.settings?.callsEnabled != nil {
|
||||
callsSection
|
||||
enableNotificationSection
|
||||
if context.enableNotifications {
|
||||
roomsNotificationSection
|
||||
if context.viewState.settings?.roomMentionsEnabled != nil {
|
||||
mentionsSection
|
||||
}
|
||||
if context.viewState.settings?.callsEnabled != nil {
|
||||
callsSection
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,6 +156,33 @@ struct NotificationSettingsScreen: View {
|
||||
.compoundListSectionHeader()
|
||||
}
|
||||
}
|
||||
|
||||
private var configurationMismatchSection: some View {
|
||||
Section {
|
||||
ListRow(kind: .custom {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Image(systemName: "gearshape")
|
||||
.font(.compound.headingSMSemibold)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(L10n.screenNotificationSettingsConfigurationMismatch)
|
||||
.font(.compound.headingSMSemibold)
|
||||
Text(L10n.screenNotificationSettingsConfigurationMismatchDescription)
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
}
|
||||
Button {
|
||||
context.send(viewAction: .fixConfigurationMismatchTapped)
|
||||
} label: {
|
||||
Text(L10n.actionContinue)
|
||||
}
|
||||
.buttonStyle(.elementCapsuleProminent)
|
||||
.disabled(context.viewState.fixingConfigurationMismatch)
|
||||
.accessibilityIdentifier(A11yIdentifiers.notificationSettingsScreen.fixMismatchConfiguration)
|
||||
}
|
||||
.padding(.horizontal, ListRowPadding.horizontal)
|
||||
.padding(.vertical, ListRowPadding.vertical)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
@@ -183,8 +214,39 @@ struct NotificationSettingsScreen_Previews: PreviewProvider {
|
||||
viewModel.fetchInitialContent()
|
||||
return viewModel
|
||||
}()
|
||||
|
||||
static let viewModelConfigurationMismatch: NotificationSettingsScreenViewModel = {
|
||||
let appSettings = AppSettings()
|
||||
let notificationCenter = UserNotificationCenterMock()
|
||||
notificationCenter.authorizationStatusReturnValue = .notDetermined
|
||||
let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init())
|
||||
notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneClosure = { isEncrypted, isOneToOne in
|
||||
switch (isEncrypted, isOneToOne) {
|
||||
case (true, true):
|
||||
return .allMessages
|
||||
case (false, true):
|
||||
return .mute
|
||||
default:
|
||||
return .mentionsAndKeywordsOnly
|
||||
}
|
||||
}
|
||||
notificationSettingsProxy.isRoomMentionEnabledReturnValue = true
|
||||
notificationSettingsProxy.isCallEnabledReturnValue = false
|
||||
|
||||
let userSession = MockUserSession(clientProxy: MockClientProxy(userID: "John Doe"), mediaProvider: MockMediaProvider())
|
||||
|
||||
var viewModel = NotificationSettingsScreenViewModel(userSession: userSession,
|
||||
appSettings: appSettings,
|
||||
userNotificationCenter: notificationCenter,
|
||||
notificationSettingsProxy: notificationSettingsProxy,
|
||||
isModallyPresented: true)
|
||||
viewModel.fetchInitialContent()
|
||||
return viewModel
|
||||
}()
|
||||
|
||||
static var previews: some View {
|
||||
NotificationSettingsScreen(context: viewModel.context)
|
||||
NotificationSettingsScreen(context: viewModelConfigurationMismatch.context)
|
||||
.previewDisplayName("Configuration mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +204,25 @@ class MockScreen: Identifiable {
|
||||
notificationSettings: NotificationSettingsProxyMock(with: .init()),
|
||||
isModallyPresented: false)
|
||||
return NotificationSettingsScreenCoordinator(parameters: parameters)
|
||||
case .notificationSettingsScreenMismatchConfiguration:
|
||||
let userNotificationCenter = UserNotificationCenterMock()
|
||||
userNotificationCenter.authorizationStatusReturnValue = .denied
|
||||
let session = MockUserSession(clientProxy: MockClientProxy(userID: "@mock:matrix.org"),
|
||||
mediaProvider: MockMediaProvider())
|
||||
let notificationSettings = NotificationSettingsProxyMock(with: .init())
|
||||
notificationSettings.getDefaultRoomNotificationModeIsEncryptedIsOneToOneClosure = { isEncrypted, isOneToOne in
|
||||
switch (isEncrypted, isOneToOne) {
|
||||
case (true, _):
|
||||
return .allMessages
|
||||
case (false, _):
|
||||
return .mentionsAndKeywordsOnly
|
||||
}
|
||||
}
|
||||
let parameters = NotificationSettingsScreenCoordinatorParameters(userSession: session,
|
||||
userNotificationCenter: userNotificationCenter,
|
||||
notificationSettings: notificationSettings,
|
||||
isModallyPresented: false)
|
||||
return NotificationSettingsScreenCoordinator(parameters: parameters)
|
||||
case .onboarding:
|
||||
return OnboardingCoordinator()
|
||||
case .roomPlainNoAvatar:
|
||||
|
||||
@@ -34,6 +34,7 @@ enum UITestsScreenIdentifier: String {
|
||||
case bugReport
|
||||
case bugReportWithScreenshot
|
||||
case notificationSettingsScreen
|
||||
case notificationSettingsScreenMismatchConfiguration
|
||||
case onboarding
|
||||
case roomPlainNoAvatar
|
||||
case roomEncryptedWithAvatar
|
||||
|
||||
Reference in New Issue
Block a user