mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Alert the user about configuration mismatch in notification settings (#1543)
This commit is contained in:
+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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user