Alert the user about configuration mismatch in notification settings (#1543)

This commit is contained in:
Nicolas Mauri
2023-08-23 16:01:10 +02:00
committed by GitHub
parent 276adb6084
commit e0f885c590
19 changed files with 272 additions and 19 deletions
+10
View File
@@ -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 {
@@ -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
}
@@ -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 {
@@ -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