mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Notification settings screen (#1414)
This commit is contained in:
@@ -260,7 +260,16 @@
|
|||||||
"screen_media_upload_preview_error_failed_sending" = "Failed uploading media, please try again.";
|
"screen_media_upload_preview_error_failed_sending" = "Failed uploading media, please try again.";
|
||||||
"screen_migration_message" = "This is a one time process, thanks for waiting.";
|
"screen_migration_message" = "This is a one time process, thanks for waiting.";
|
||||||
"screen_migration_title" = "Setting up your account.";
|
"screen_migration_title" = "Setting up your account.";
|
||||||
|
"screen_notification_settings_additional_settings_section_title" = "Additional settings";
|
||||||
|
"screen_notification_settings_calls_label" = "Audio and video calls";
|
||||||
|
"screen_notification_settings_direct_chats" = "Direct chats";
|
||||||
"screen_notification_settings_enable_notifications" = "Enable notifications on this device";
|
"screen_notification_settings_enable_notifications" = "Enable notifications on this device";
|
||||||
|
"screen_notification_settings_group_chats" = "Group chats";
|
||||||
|
"screen_notification_settings_mentions_section_title" = "Mentions";
|
||||||
|
"screen_notification_settings_mode_all" = "All";
|
||||||
|
"screen_notification_settings_mode_mentions" = "Mentions";
|
||||||
|
"screen_notification_settings_notification_section_title" = "Notify me for";
|
||||||
|
"screen_notification_settings_room_mention_label" = "Notify me on @room";
|
||||||
"screen_notification_settings_system_notifications_action_required" = "To receive notifications, please change your %1$@.";
|
"screen_notification_settings_system_notifications_action_required" = "To receive notifications, please change your %1$@.";
|
||||||
"screen_notification_settings_system_notifications_action_required_content_link" = "system settings";
|
"screen_notification_settings_system_notifications_action_required_content_link" = "system settings";
|
||||||
"screen_notification_settings_system_notifications_turned_off" = "System notifications turned off";
|
"screen_notification_settings_system_notifications_turned_off" = "System notifications turned off";
|
||||||
|
|||||||
@@ -299,14 +299,21 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
// MARK: Settings
|
// MARK: Settings
|
||||||
|
|
||||||
private func presentSettingsScreen(animated: Bool) {
|
private func presentSettingsScreen(animated: Bool) {
|
||||||
|
Task {
|
||||||
|
await asyncPresentSettingsScreen(animated: animated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func asyncPresentSettingsScreen(animated: Bool) async {
|
||||||
let settingsNavigationStackCoordinator = NavigationStackCoordinator()
|
let settingsNavigationStackCoordinator = NavigationStackCoordinator()
|
||||||
|
|
||||||
let userIndicatorController = UserIndicatorController(rootCoordinator: settingsNavigationStackCoordinator)
|
let userIndicatorController = UserIndicatorController(rootCoordinator: settingsNavigationStackCoordinator)
|
||||||
|
|
||||||
let parameters = SettingsScreenCoordinatorParameters(navigationStackCoordinator: settingsNavigationStackCoordinator,
|
let parameters = await SettingsScreenCoordinatorParameters(navigationStackCoordinator: settingsNavigationStackCoordinator,
|
||||||
userIndicatorController: userIndicatorController,
|
userIndicatorController: userIndicatorController,
|
||||||
userSession: userSession,
|
userSession: userSession,
|
||||||
bugReportService: bugReportService)
|
bugReportService: bugReportService,
|
||||||
|
notificationSettings: userSession.clientProxy.notificationSettings())
|
||||||
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: parameters)
|
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: parameters)
|
||||||
settingsScreenCoordinator.callback = { [weak self] action in
|
settingsScreenCoordinator.callback = { [weak self] action in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|||||||
@@ -672,8 +672,26 @@ public enum L10n {
|
|||||||
public static var screenMigrationMessage: String { return L10n.tr("Localizable", "screen_migration_message") }
|
public static var screenMigrationMessage: String { return L10n.tr("Localizable", "screen_migration_message") }
|
||||||
/// Setting up your account.
|
/// Setting up your account.
|
||||||
public static var screenMigrationTitle: String { return L10n.tr("Localizable", "screen_migration_title") }
|
public static var screenMigrationTitle: String { return L10n.tr("Localizable", "screen_migration_title") }
|
||||||
|
/// Additional settings
|
||||||
|
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") }
|
||||||
|
/// Direct chats
|
||||||
|
public static var screenNotificationSettingsDirectChats: String { return L10n.tr("Localizable", "screen_notification_settings_direct_chats") }
|
||||||
/// Enable notifications on this device
|
/// Enable notifications on this device
|
||||||
public static var screenNotificationSettingsEnableNotifications: String { return L10n.tr("Localizable", "screen_notification_settings_enable_notifications") }
|
public static var screenNotificationSettingsEnableNotifications: String { return L10n.tr("Localizable", "screen_notification_settings_enable_notifications") }
|
||||||
|
/// Group chats
|
||||||
|
public static var screenNotificationSettingsGroupChats: String { return L10n.tr("Localizable", "screen_notification_settings_group_chats") }
|
||||||
|
/// Mentions
|
||||||
|
public static var screenNotificationSettingsMentionsSectionTitle: String { return L10n.tr("Localizable", "screen_notification_settings_mentions_section_title") }
|
||||||
|
/// All
|
||||||
|
public static var screenNotificationSettingsModeAll: String { return L10n.tr("Localizable", "screen_notification_settings_mode_all") }
|
||||||
|
/// Mentions
|
||||||
|
public static var screenNotificationSettingsModeMentions: String { return L10n.tr("Localizable", "screen_notification_settings_mode_mentions") }
|
||||||
|
/// Notify me for
|
||||||
|
public static var screenNotificationSettingsNotificationSectionTitle: String { return L10n.tr("Localizable", "screen_notification_settings_notification_section_title") }
|
||||||
|
/// Notify me on @room
|
||||||
|
public static var screenNotificationSettingsRoomMentionLabel: String { return L10n.tr("Localizable", "screen_notification_settings_room_mention_label") }
|
||||||
/// To receive notifications, please change your %1$@.
|
/// To receive notifications, please change your %1$@.
|
||||||
public static func screenNotificationSettingsSystemNotificationsActionRequired(_ p1: Any) -> String {
|
public static func screenNotificationSettingsSystemNotificationsActionRequired(_ p1: Any) -> String {
|
||||||
return L10n.tr("Localizable", "screen_notification_settings_system_notifications_action_required", String(describing: p1))
|
return L10n.tr("Localizable", "screen_notification_settings_system_notifications_action_required", String(describing: p1))
|
||||||
|
|||||||
@@ -51,5 +51,19 @@ extension NotificationSettingsProxyMock {
|
|||||||
self.callbacks.send(.settingsDidChange)
|
self.callbacks.send(.settingsDidChange)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setRoomMentionEnabledEnabledClosure = { [weak self] enabled in
|
||||||
|
guard let self else { return }
|
||||||
|
self.isRoomMentionEnabledReturnValue = enabled
|
||||||
|
Task {
|
||||||
|
self.callbacks.send(.settingsDidChange)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setCallEnabledEnabledClosure = { [weak self] enabled in
|
||||||
|
guard let self else { return }
|
||||||
|
self.isCallEnabledReturnValue = enabled
|
||||||
|
Task {
|
||||||
|
self.callbacks.send(.settingsDidChange)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-2
@@ -19,6 +19,7 @@ import SwiftUI
|
|||||||
|
|
||||||
struct NotificationSettingsScreenCoordinatorParameters {
|
struct NotificationSettingsScreenCoordinatorParameters {
|
||||||
let userNotificationCenter: UserNotificationCenterProtocol
|
let userNotificationCenter: UserNotificationCenterProtocol
|
||||||
|
let notificationSettings: NotificationSettingsProxyProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NotificationSettingsScreenCoordinatorAction { }
|
enum NotificationSettingsScreenCoordinatorAction { }
|
||||||
@@ -37,10 +38,13 @@ final class NotificationSettingsScreenCoordinator: CoordinatorProtocol {
|
|||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
|
|
||||||
viewModel = NotificationSettingsScreenViewModel(appSettings: ServiceLocator.shared.settings,
|
viewModel = NotificationSettingsScreenViewModel(appSettings: ServiceLocator.shared.settings,
|
||||||
userNotificationCenter: parameters.userNotificationCenter)
|
userNotificationCenter: parameters.userNotificationCenter,
|
||||||
|
notificationSettingsProxy: parameters.notificationSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start() { }
|
func start() {
|
||||||
|
viewModel.fetchInitialContent()
|
||||||
|
}
|
||||||
|
|
||||||
func toPresentable() -> AnyView {
|
func toPresentable() -> AnyView {
|
||||||
AnyView(NotificationSettingsScreen(context: viewModel.context))
|
AnyView(NotificationSettingsScreen(context: viewModel.context))
|
||||||
|
|||||||
+37
@@ -23,14 +23,31 @@ struct NotificationSettingsScreenViewState: BindableState {
|
|||||||
var bindings: NotificationSettingsScreenViewStateBindings
|
var bindings: NotificationSettingsScreenViewStateBindings
|
||||||
var strings = NotificationSettingsScreenStrings()
|
var strings = NotificationSettingsScreenStrings()
|
||||||
var isUserPermissionGranted: Bool?
|
var isUserPermissionGranted: Bool?
|
||||||
|
var allowedNotificationModes: [RoomNotificationModeProxy] = [.allMessages, .mentionsAndKeywordsOnly]
|
||||||
|
|
||||||
var showSystemNotificationsAlert: Bool {
|
var showSystemNotificationsAlert: Bool {
|
||||||
bindings.enableNotifications && isUserPermissionGranted == false
|
bindings.enableNotifications && isUserPermissionGranted == false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var settings: NotificationSettingsScreenSettings?
|
||||||
|
var applyingChange = false
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NotificationSettingsScreenViewStateBindings {
|
struct NotificationSettingsScreenViewStateBindings {
|
||||||
var enableNotifications = false
|
var enableNotifications = false
|
||||||
|
var roomMentionsEnabled = false
|
||||||
|
var callsEnabled = false
|
||||||
|
var alertInfo: AlertInfo<NotificationSettingsScreenErrorType>?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NotificationSettingsScreenSettings {
|
||||||
|
let groupChatsMode: RoomNotificationModeProxy
|
||||||
|
let directChatsMode: RoomNotificationModeProxy
|
||||||
|
let roomMentionsEnabled: Bool?
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NotificationSettingsScreenStrings {
|
struct NotificationSettingsScreenStrings {
|
||||||
@@ -45,9 +62,29 @@ struct NotificationSettingsScreenStrings {
|
|||||||
|
|
||||||
return text
|
return text
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
func string(for mode: RoomNotificationModeProxy) -> String {
|
||||||
|
switch mode {
|
||||||
|
case .allMessages:
|
||||||
|
return L10n.screenNotificationSettingsModeAll
|
||||||
|
case .mentionsAndKeywordsOnly:
|
||||||
|
return L10n.screenNotificationSettingsModeMentions
|
||||||
|
case .mute:
|
||||||
|
return L10n.commonMute
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum NotificationSettingsScreenViewAction {
|
enum NotificationSettingsScreenViewAction {
|
||||||
case linkClicked(url: URL)
|
case linkClicked(url: URL)
|
||||||
case changedEnableNotifications
|
case changedEnableNotifications
|
||||||
|
case groupChatsTapped
|
||||||
|
case directChatsTapped
|
||||||
|
case roomMentionChanged
|
||||||
|
case callsChanged
|
||||||
|
}
|
||||||
|
|
||||||
|
enum NotificationSettingsScreenErrorType: Hashable {
|
||||||
|
/// A specific error message shown in an alert.
|
||||||
|
case alert
|
||||||
}
|
}
|
||||||
|
|||||||
+125
-14
@@ -23,14 +23,18 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
|||||||
private var actionsSubject: PassthroughSubject<NotificationSettingsScreenViewModelAction, Never> = .init()
|
private var actionsSubject: PassthroughSubject<NotificationSettingsScreenViewModelAction, Never> = .init()
|
||||||
private let appSettings: AppSettings
|
private let appSettings: AppSettings
|
||||||
private let userNotificationCenter: UserNotificationCenterProtocol
|
private let userNotificationCenter: UserNotificationCenterProtocol
|
||||||
|
private let notificationSettingsProxy: NotificationSettingsProxyProtocol
|
||||||
|
@CancellableTask private var fetchSettingsTask: Task<Void, Error>?
|
||||||
|
|
||||||
var actions: AnyPublisher<NotificationSettingsScreenViewModelAction, Never> {
|
var actions: AnyPublisher<NotificationSettingsScreenViewModelAction, Never> {
|
||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(appSettings: AppSettings, userNotificationCenter: UserNotificationCenterProtocol) {
|
init(appSettings: AppSettings, userNotificationCenter: UserNotificationCenterProtocol, notificationSettingsProxy: NotificationSettingsProxyProtocol) {
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
self.userNotificationCenter = userNotificationCenter
|
self.userNotificationCenter = userNotificationCenter
|
||||||
|
self.notificationSettingsProxy = notificationSettingsProxy
|
||||||
|
|
||||||
let bindings = NotificationSettingsScreenViewStateBindings(enableNotifications: appSettings.enableNotifications)
|
let bindings = NotificationSettingsScreenViewStateBindings(enableNotifications: appSettings.enableNotifications)
|
||||||
super.init(initialViewState: NotificationSettingsScreenViewState(bindings: bindings))
|
super.init(initialViewState: NotificationSettingsScreenViewState(bindings: bindings))
|
||||||
|
|
||||||
@@ -39,6 +43,50 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
|||||||
.weakAssign(to: \.state.bindings.enableNotifications, on: self)
|
.weakAssign(to: \.state.bindings.enableNotifications, on: self)
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
setupDidBecomeActiveSubscription()
|
||||||
|
setupNotificationSettingsSubscription()
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchInitialContent() {
|
||||||
|
fetchSettings()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Public
|
||||||
|
|
||||||
|
override func process(viewAction: NotificationSettingsScreenViewAction) {
|
||||||
|
switch viewAction {
|
||||||
|
case .linkClicked(let url):
|
||||||
|
MXLog.warning("Link clicked: \(url)")
|
||||||
|
case .changedEnableNotifications:
|
||||||
|
toggleNotifications()
|
||||||
|
case .groupChatsTapped:
|
||||||
|
break
|
||||||
|
case .directChatsTapped:
|
||||||
|
break
|
||||||
|
case .roomMentionChanged:
|
||||||
|
guard let settings = state.settings, settings.roomMentionsEnabled != state.bindings.roomMentionsEnabled else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Task { await enableRoomMention(state.bindings.roomMentionsEnabled) }
|
||||||
|
case .callsChanged:
|
||||||
|
guard let settings = state.settings, settings.callsEnabled != state.bindings.callsEnabled else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Task { await enableCalls(state.bindings.callsEnabled) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
func readSystemAuthorizationStatus() async {
|
||||||
|
state.isUserPermissionGranted = await userNotificationCenter.authorizationStatus() == .authorized
|
||||||
|
}
|
||||||
|
|
||||||
|
func toggleNotifications() {
|
||||||
|
appSettings.enableNotifications.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupDidBecomeActiveSubscription() {
|
||||||
// Refresh authorization status uppon UIApplication.didBecomeActiveNotification notification
|
// Refresh authorization status uppon UIApplication.didBecomeActiveNotification notification
|
||||||
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
|
NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
|
||||||
.sink { [weak self] _ in
|
.sink { [weak self] _ in
|
||||||
@@ -53,24 +101,87 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Public
|
private func setupNotificationSettingsSubscription() {
|
||||||
|
notificationSettingsProxy.callbacks
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] callback in
|
||||||
|
guard let self else { return }
|
||||||
|
|
||||||
override func process(viewAction: NotificationSettingsScreenViewAction) {
|
switch callback {
|
||||||
switch viewAction {
|
case .settingsDidChange:
|
||||||
case .linkClicked(let url):
|
self.fetchSettings()
|
||||||
MXLog.warning("Link clicked: \(url)")
|
}
|
||||||
case .changedEnableNotifications:
|
}
|
||||||
toggleNotifications()
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func fetchSettings() {
|
||||||
|
fetchSettingsTask = Task {
|
||||||
|
// Group chats
|
||||||
|
// A group chat is a chat having more than 2 active members
|
||||||
|
let groupChatActiveMembers: UInt64 = 3
|
||||||
|
var groupChatsMode = await notificationSettingsProxy.getDefaultNotificationRoomMode(isEncrypted: false, activeMembersCount: groupChatActiveMembers)
|
||||||
|
let encryptedGroupChatsMode = await notificationSettingsProxy.getDefaultNotificationRoomMode(isEncrypted: true, activeMembersCount: groupChatActiveMembers)
|
||||||
|
|
||||||
|
// Direct chats
|
||||||
|
// A direct chat is a chat having exactly 2 active members
|
||||||
|
let directChatActiveMembers: UInt64 = 2
|
||||||
|
var directChatsMode = await notificationSettingsProxy.getDefaultNotificationRoomMode(isEncrypted: false, activeMembersCount: directChatActiveMembers)
|
||||||
|
let encryptedDirectChatsMode = await notificationSettingsProxy.getDefaultNotificationRoomMode(isEncrypted: true, activeMembersCount: directChatActiveMembers)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
if directChatsMode != encryptedDirectChatsMode {
|
||||||
|
directChatsMode = .allMessages
|
||||||
|
inconsistencyDetected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following calls may fail if the associated push rule doesn't exist
|
||||||
|
let roomMentionsEnabled = try? await notificationSettingsProxy.isRoomMentionEnabled()
|
||||||
|
let callEnabled = try? await notificationSettingsProxy.isCallEnabled()
|
||||||
|
|
||||||
|
guard !Task.isCancelled else { return }
|
||||||
|
|
||||||
|
let notificationSettings = NotificationSettingsScreenSettings(groupChatsMode: groupChatsMode,
|
||||||
|
directChatsMode: directChatsMode,
|
||||||
|
roomMentionsEnabled: roomMentionsEnabled,
|
||||||
|
callsEnabled: callEnabled,
|
||||||
|
inconsistentSettings: inconsistencyDetected)
|
||||||
|
|
||||||
|
state.settings = notificationSettings
|
||||||
|
state.bindings.roomMentionsEnabled = notificationSettings.roomMentionsEnabled ?? false
|
||||||
|
state.bindings.callsEnabled = notificationSettings.callsEnabled ?? false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
private func enableRoomMention(_ enable: Bool) async {
|
||||||
|
guard let notificationSettings = state.settings else { return }
|
||||||
func readSystemAuthorizationStatus() async {
|
do {
|
||||||
state.isUserPermissionGranted = await userNotificationCenter.authorizationStatus() == .authorized
|
state.applyingChange = true
|
||||||
|
MXLog.info("[NotificationSettingsScreenViewMode] setRoomMentionEnabled(\(enable))")
|
||||||
|
try await notificationSettingsProxy.setRoomMentionEnabled(enabled: enable)
|
||||||
|
} catch {
|
||||||
|
state.bindings.alertInfo = AlertInfo(id: .alert)
|
||||||
|
state.bindings.roomMentionsEnabled = notificationSettings.roomMentionsEnabled ?? false
|
||||||
|
}
|
||||||
|
state.applyingChange = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func toggleNotifications() {
|
func enableCalls(_ enable: Bool) async {
|
||||||
appSettings.enableNotifications.toggle()
|
guard let notificationSettings = state.settings else { return }
|
||||||
|
do {
|
||||||
|
state.applyingChange = true
|
||||||
|
MXLog.info("[NotificationSettingsScreenViewMode] setCallEnabled(\(enable))")
|
||||||
|
try await notificationSettingsProxy.setCallEnabled(enabled: enable)
|
||||||
|
} catch {
|
||||||
|
state.bindings.alertInfo = AlertInfo(id: .alert)
|
||||||
|
state.bindings.callsEnabled = notificationSettings.callsEnabled ?? false
|
||||||
|
}
|
||||||
|
state.applyingChange = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -20,4 +20,6 @@ import Combine
|
|||||||
protocol NotificationSettingsScreenViewModelProtocol {
|
protocol NotificationSettingsScreenViewModelProtocol {
|
||||||
var actions: AnyPublisher<NotificationSettingsScreenViewModelAction, Never> { get }
|
var actions: AnyPublisher<NotificationSettingsScreenViewModelAction, Never> { get }
|
||||||
var context: NotificationSettingsScreenViewModelType.Context { get }
|
var context: NotificationSettingsScreenViewModelType.Context { get }
|
||||||
|
|
||||||
|
func fetchInitialContent()
|
||||||
}
|
}
|
||||||
|
|||||||
+107
-1
@@ -25,9 +25,19 @@ struct NotificationSettingsScreen: View {
|
|||||||
userPermissionSection
|
userPermissionSection
|
||||||
}
|
}
|
||||||
enableNotificationSection
|
enableNotificationSection
|
||||||
|
if context.enableNotifications {
|
||||||
|
roomsNotificationSection
|
||||||
|
if context.viewState.settings?.roomMentionsEnabled != nil {
|
||||||
|
mentionsSection
|
||||||
|
}
|
||||||
|
if context.viewState.settings?.callsEnabled != nil {
|
||||||
|
callsSection
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.compoundForm()
|
.compoundForm()
|
||||||
.navigationTitle(L10n.screenNotificationSettingsTitle)
|
.navigationTitle(L10n.screenNotificationSettingsTitle)
|
||||||
|
.alert(item: $context.alertInfo)
|
||||||
.track(screen: .settingsNotifications)
|
.track(screen: .settingsNotifications)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +79,87 @@ struct NotificationSettingsScreen: View {
|
|||||||
}
|
}
|
||||||
.compoundFormSection()
|
.compoundFormSection()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var roomsNotificationSection: some View {
|
||||||
|
Section {
|
||||||
|
// Group chats
|
||||||
|
Button {
|
||||||
|
context.send(viewAction: .groupChatsTapped)
|
||||||
|
} label: {
|
||||||
|
LabeledContent {
|
||||||
|
if let settings = context.viewState.settings {
|
||||||
|
Text(context.viewState.strings.string(for: settings.groupChatsMode))
|
||||||
|
} else {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text(L10n.screenNotificationSettingsGroupChats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.notifications)
|
||||||
|
.buttonStyle(.compoundForm(accessory: context.viewState.settings.flatMap { _ in .navigationLink }))
|
||||||
|
.disabled(context.viewState.settings == nil)
|
||||||
|
|
||||||
|
// Direct chats
|
||||||
|
Button {
|
||||||
|
context.send(viewAction: .directChatsTapped)
|
||||||
|
} label: {
|
||||||
|
LabeledContent {
|
||||||
|
if let settings = context.viewState.settings {
|
||||||
|
Text(context.viewState.strings.string(for: settings.directChatsMode))
|
||||||
|
} else {
|
||||||
|
ProgressView()
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Text(L10n.screenNotificationSettingsDirectChats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.notifications)
|
||||||
|
.buttonStyle(.compoundForm(accessory: context.viewState.settings.flatMap { _ in .navigationLink }))
|
||||||
|
.disabled(context.viewState.settings == nil)
|
||||||
|
|
||||||
|
} header: {
|
||||||
|
Text(L10n.screenNotificationSettingsNotificationSectionTitle)
|
||||||
|
.compoundFormSectionHeader()
|
||||||
|
}
|
||||||
|
.compoundFormSection()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mentionsSection: some View {
|
||||||
|
Section {
|
||||||
|
Toggle(isOn: $context.roomMentionsEnabled) {
|
||||||
|
Text(L10n.screenNotificationSettingsRoomMentionLabel)
|
||||||
|
}
|
||||||
|
.toggleStyle(.compoundForm)
|
||||||
|
.onChange(of: context.roomMentionsEnabled) { _ in
|
||||||
|
context.send(viewAction: .roomMentionChanged)
|
||||||
|
}
|
||||||
|
.disabled(context.viewState.settings?.roomMentionsEnabled == nil)
|
||||||
|
.allowsHitTesting(!context.viewState.applyingChange)
|
||||||
|
} header: {
|
||||||
|
Text(L10n.screenNotificationSettingsMentionsSectionTitle)
|
||||||
|
.compoundFormSectionHeader()
|
||||||
|
}
|
||||||
|
.compoundFormSection()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var callsSection: some View {
|
||||||
|
Section {
|
||||||
|
Toggle(isOn: $context.callsEnabled) {
|
||||||
|
Text(L10n.screenNotificationSettingsCallsLabel)
|
||||||
|
}
|
||||||
|
.toggleStyle(.compoundForm)
|
||||||
|
.onChange(of: context.callsEnabled) { _ in
|
||||||
|
context.send(viewAction: .callsChanged)
|
||||||
|
}
|
||||||
|
.disabled(context.viewState.settings?.callsEnabled == nil)
|
||||||
|
.allowsHitTesting(!context.viewState.applyingChange)
|
||||||
|
} header: {
|
||||||
|
Text(L10n.screenNotificationSettingsAdditionalSettingsSectionTitle)
|
||||||
|
.compoundFormSectionHeader()
|
||||||
|
}
|
||||||
|
.compoundFormSection()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Previews
|
// MARK: - Previews
|
||||||
@@ -78,7 +169,22 @@ struct NotificationSettingsScreen_Previews: PreviewProvider {
|
|||||||
let appSettings = AppSettings()
|
let appSettings = AppSettings()
|
||||||
let notificationCenter = UserNotificationCenterMock()
|
let notificationCenter = UserNotificationCenterMock()
|
||||||
notificationCenter.authorizationStatusReturnValue = .notDetermined
|
notificationCenter.authorizationStatusReturnValue = .notDetermined
|
||||||
var viewModel = NotificationSettingsScreenViewModel(appSettings: appSettings, userNotificationCenter: notificationCenter)
|
let notificationSettingsProxy = NotificationSettingsProxyMock(with: .init())
|
||||||
|
notificationSettingsProxy.getDefaultNotificationRoomModeIsEncryptedActiveMembersCountClosure = { isEncrypted, activeMembersCount in
|
||||||
|
switch (isEncrypted, activeMembersCount) {
|
||||||
|
case (_, 2):
|
||||||
|
return .allMessages
|
||||||
|
default:
|
||||||
|
return .mentionsAndKeywordsOnly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
notificationSettingsProxy.isRoomMentionEnabledReturnValue = true
|
||||||
|
notificationSettingsProxy.isCallEnabledReturnValue = false
|
||||||
|
|
||||||
|
var viewModel = NotificationSettingsScreenViewModel(appSettings: appSettings,
|
||||||
|
userNotificationCenter: notificationCenter,
|
||||||
|
notificationSettingsProxy: notificationSettingsProxy)
|
||||||
|
viewModel.fetchInitialContent()
|
||||||
return viewModel
|
return viewModel
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ struct SettingsScreenCoordinatorParameters {
|
|||||||
weak var userIndicatorController: UserIndicatorControllerProtocol?
|
weak var userIndicatorController: UserIndicatorControllerProtocol?
|
||||||
let userSession: UserSessionProtocol
|
let userSession: UserSessionProtocol
|
||||||
let bugReportService: BugReportServiceProtocol
|
let bugReportService: BugReportServiceProtocol
|
||||||
|
let notificationSettings: NotificationSettingsProxyProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SettingsScreenCoordinatorAction {
|
enum SettingsScreenCoordinatorAction {
|
||||||
@@ -140,9 +141,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func presentNotificationSettings() {
|
private func presentNotificationSettings() {
|
||||||
let notificationParameters = NotificationSettingsScreenCoordinatorParameters(
|
let notificationParameters = NotificationSettingsScreenCoordinatorParameters(userNotificationCenter: UNUserNotificationCenter.current(),
|
||||||
userNotificationCenter: UNUserNotificationCenter.current()
|
notificationSettings: parameters.notificationSettings)
|
||||||
)
|
|
||||||
let coordinator = NotificationSettingsScreenCoordinator(parameters: notificationParameters)
|
let coordinator = NotificationSettingsScreenCoordinator(parameters: notificationParameters)
|
||||||
parameters.navigationStackCoordinator?.push(coordinator)
|
parameters.navigationStackCoordinator?.push(coordinator)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,8 @@ class MockScreen: Identifiable {
|
|||||||
userIndicatorController: nil,
|
userIndicatorController: nil,
|
||||||
userSession: MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"),
|
userSession: MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"),
|
||||||
mediaProvider: MockMediaProvider()),
|
mediaProvider: MockMediaProvider()),
|
||||||
bugReportService: BugReportServiceMock()))
|
bugReportService: BugReportServiceMock(),
|
||||||
|
notificationSettings: NotificationSettingsProxyMock(with: .init())))
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
case .bugReport:
|
case .bugReport:
|
||||||
@@ -195,7 +196,8 @@ class MockScreen: Identifiable {
|
|||||||
case .notificationSettingsScreen:
|
case .notificationSettingsScreen:
|
||||||
let userNotificationCenter = UserNotificationCenterMock()
|
let userNotificationCenter = UserNotificationCenterMock()
|
||||||
userNotificationCenter.authorizationStatusReturnValue = .denied
|
userNotificationCenter.authorizationStatusReturnValue = .denied
|
||||||
let parameters = NotificationSettingsScreenCoordinatorParameters(userNotificationCenter: userNotificationCenter)
|
let parameters = NotificationSettingsScreenCoordinatorParameters(userNotificationCenter: userNotificationCenter,
|
||||||
|
notificationSettings: NotificationSettingsProxyMock(with: .init()))
|
||||||
return NotificationSettingsScreenCoordinator(parameters: parameters)
|
return NotificationSettingsScreenCoordinator(parameters: parameters)
|
||||||
case .onboarding:
|
case .onboarding:
|
||||||
return OnboardingCoordinator()
|
return OnboardingCoordinator()
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ final class NotificationManagerTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func test_whenRegisteredAndPusherTagIsSetInSettings_tagNotGenerated() async throws {
|
func test_whenRegisteredAndPusherTagIsSetInSettings_tagNotGenerated() async throws {
|
||||||
|
notificationCenter.requestAuthorizationOptionsReturnValue = true
|
||||||
appSettings.pusherProfileTag = "12345"
|
appSettings.pusherProfileTag = "12345"
|
||||||
_ = await notificationManager.register(with: Data())
|
_ = await notificationManager.register(with: Data())
|
||||||
XCTAssertEqual(appSettings.pusherProfileTag, "12345")
|
XCTAssertEqual(appSettings.pusherProfileTag, "12345")
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import MatrixRustSDK
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
@testable import ElementX
|
@testable import ElementX
|
||||||
@@ -24,6 +25,7 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
|
|||||||
private var context: NotificationSettingsScreenViewModelType.Context!
|
private var context: NotificationSettingsScreenViewModelType.Context!
|
||||||
private var appSettings: AppSettings!
|
private var appSettings: AppSettings!
|
||||||
private var userNotificationCenter: UserNotificationCenterMock!
|
private var userNotificationCenter: UserNotificationCenterMock!
|
||||||
|
private var notificationSettingsProxy: NotificationSettingsProxyMock!
|
||||||
|
|
||||||
@MainActor override func setUpWithError() throws {
|
@MainActor override func setUpWithError() throws {
|
||||||
AppSettings.reset()
|
AppSettings.reset()
|
||||||
@@ -31,8 +33,14 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
|
|||||||
userNotificationCenter = UserNotificationCenterMock()
|
userNotificationCenter = UserNotificationCenterMock()
|
||||||
userNotificationCenter.authorizationStatusReturnValue = .authorized
|
userNotificationCenter.authorizationStatusReturnValue = .authorized
|
||||||
appSettings = AppSettings()
|
appSettings = AppSettings()
|
||||||
|
notificationSettingsProxy = NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration())
|
||||||
|
notificationSettingsProxy.getDefaultNotificationRoomModeIsEncryptedActiveMembersCountReturnValue = .allMessages
|
||||||
|
notificationSettingsProxy.isRoomMentionEnabledReturnValue = true
|
||||||
|
notificationSettingsProxy.isCallEnabledReturnValue = true
|
||||||
|
|
||||||
viewModel = NotificationSettingsScreenViewModel(appSettings: appSettings,
|
viewModel = NotificationSettingsScreenViewModel(appSettings: appSettings,
|
||||||
userNotificationCenter: userNotificationCenter)
|
userNotificationCenter: userNotificationCenter,
|
||||||
|
notificationSettingsProxy: notificationSettingsProxy)
|
||||||
context = viewModel.context
|
context = viewModel.context
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,9 +50,183 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertTrue(appSettings.enableNotifications)
|
XCTAssertTrue(appSettings.enableNotifications)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testOptOut() {
|
func testDisableNotifications() {
|
||||||
appSettings.enableNotifications = true
|
appSettings.enableNotifications = true
|
||||||
context.send(viewAction: .changedEnableNotifications)
|
context.send(viewAction: .changedEnableNotifications)
|
||||||
XCTAssertFalse(appSettings.enableNotifications)
|
XCTAssertFalse(appSettings.enableNotifications)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testFetchSettings() async throws {
|
||||||
|
notificationSettingsProxy.getDefaultNotificationRoomModeIsEncryptedActiveMembersCountClosure = { isEncrypted, activeMembersCount in
|
||||||
|
switch (isEncrypted, activeMembersCount) {
|
||||||
|
case (_, 2):
|
||||||
|
return .allMessages
|
||||||
|
case (_, _):
|
||||||
|
return .mentionsAndKeywordsOnly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let deferred = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||||
|
.first(where: { $0 != nil }))
|
||||||
|
notificationSettingsProxy.callbacks.send(.settingsDidChange)
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
XCTAssertEqual(notificationSettingsProxy.getDefaultNotificationRoomModeIsEncryptedActiveMembersCountCallsCount, 4)
|
||||||
|
XCTAssert(notificationSettingsProxy.isRoomMentionEnabledCalled)
|
||||||
|
XCTAssert(notificationSettingsProxy.isCallEnabledCalled)
|
||||||
|
|
||||||
|
XCTAssertEqual(context.viewState.settings?.groupChatsMode, .mentionsAndKeywordsOnly)
|
||||||
|
XCTAssertEqual(context.viewState.settings?.directChatsMode, .allMessages)
|
||||||
|
XCTAssertEqual(context.viewState.settings?.inconsistentSettings, false)
|
||||||
|
XCTAssertNil(context.viewState.bindings.alertInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInconsistentGroupChatsSettings() async throws {
|
||||||
|
notificationSettingsProxy.getDefaultNotificationRoomModeIsEncryptedActiveMembersCountClosure = { isEncrypted, activeMembersCount in
|
||||||
|
switch (isEncrypted, activeMembersCount) {
|
||||||
|
case (true, 3):
|
||||||
|
return .allMessages
|
||||||
|
case (false, 3):
|
||||||
|
return .mentionsAndKeywordsOnly
|
||||||
|
default:
|
||||||
|
return .allMessages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let deferred = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||||
|
.first(where: { $0 != nil }))
|
||||||
|
notificationSettingsProxy.callbacks.send(.settingsDidChange)
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
XCTAssertEqual(context.viewState.settings?.groupChatsMode, .allMessages)
|
||||||
|
XCTAssertEqual(context.viewState.settings?.inconsistentSettings, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInconsistentDirectChatsSettings() async throws {
|
||||||
|
notificationSettingsProxy.getDefaultNotificationRoomModeIsEncryptedActiveMembersCountClosure = { isEncrypted, activeMembersCount in
|
||||||
|
switch (isEncrypted, activeMembersCount) {
|
||||||
|
case (true, 2):
|
||||||
|
return .allMessages
|
||||||
|
case (false, 2):
|
||||||
|
return .mentionsAndKeywordsOnly
|
||||||
|
default:
|
||||||
|
return .allMessages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let deferred = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||||
|
.first(where: { $0 != nil }))
|
||||||
|
notificationSettingsProxy.callbacks.send(.settingsDidChange)
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
XCTAssertEqual(context.viewState.settings?.directChatsMode, .allMessages)
|
||||||
|
XCTAssertEqual(context.viewState.settings?.inconsistentSettings, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testToggleRoomMentionOff() async throws {
|
||||||
|
notificationSettingsProxy.isRoomMentionEnabledReturnValue = true
|
||||||
|
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||||
|
.first(where: { $0 != nil }))
|
||||||
|
notificationSettingsProxy.callbacks.send(.settingsDidChange)
|
||||||
|
try await deferredInitialFetch.fulfill()
|
||||||
|
|
||||||
|
context.roomMentionsEnabled = false
|
||||||
|
let deferred = deferFulfillment(notificationSettingsProxy.callbacks
|
||||||
|
.first(where: { $0 == .settingsDidChange }))
|
||||||
|
context.send(viewAction: .roomMentionChanged)
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
XCTAssert(notificationSettingsProxy.setRoomMentionEnabledEnabledCalled)
|
||||||
|
XCTAssertEqual(notificationSettingsProxy.setRoomMentionEnabledEnabledReceivedEnabled, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testToggleRoomMentionOn() async throws {
|
||||||
|
notificationSettingsProxy.isRoomMentionEnabledReturnValue = false
|
||||||
|
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||||
|
.first(where: { $0 != nil }))
|
||||||
|
viewModel.fetchInitialContent()
|
||||||
|
try await deferredInitialFetch.fulfill()
|
||||||
|
|
||||||
|
context.roomMentionsEnabled = true
|
||||||
|
let deferred = deferFulfillment(notificationSettingsProxy.callbacks
|
||||||
|
.first(where: { $0 == .settingsDidChange }))
|
||||||
|
context.send(viewAction: .roomMentionChanged)
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
XCTAssert(notificationSettingsProxy.setRoomMentionEnabledEnabledCalled)
|
||||||
|
XCTAssertEqual(notificationSettingsProxy.setRoomMentionEnabledEnabledReceivedEnabled, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testToggleRoomMentionFailure() async throws {
|
||||||
|
notificationSettingsProxy.setRoomMentionEnabledEnabledThrowableError = NotificationSettingsError.Generic(message: "error")
|
||||||
|
notificationSettingsProxy.isRoomMentionEnabledReturnValue = false
|
||||||
|
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||||
|
.first(where: { $0 != nil }))
|
||||||
|
viewModel.fetchInitialContent()
|
||||||
|
try await deferredInitialFetch.fulfill()
|
||||||
|
|
||||||
|
context.roomMentionsEnabled = true
|
||||||
|
let deferred = deferFulfillment(context.$viewState.map(\.applyingChange)
|
||||||
|
.removeDuplicates()
|
||||||
|
.collect(3)
|
||||||
|
.first())
|
||||||
|
context.send(viewAction: .roomMentionChanged)
|
||||||
|
let states = try await deferred.fulfill()
|
||||||
|
|
||||||
|
XCTAssertEqual(states, [false, true, false])
|
||||||
|
XCTAssertNotNil(context.alertInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testToggleCallsOff() async throws {
|
||||||
|
notificationSettingsProxy.isCallEnabledReturnValue = true
|
||||||
|
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||||
|
.first(where: { $0 != nil }))
|
||||||
|
viewModel.fetchInitialContent()
|
||||||
|
try await deferredInitialFetch.fulfill()
|
||||||
|
|
||||||
|
context.callsEnabled = false
|
||||||
|
let deferred = deferFulfillment(notificationSettingsProxy.callbacks
|
||||||
|
.first(where: { $0 == .settingsDidChange }))
|
||||||
|
context.send(viewAction: .callsChanged)
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
XCTAssert(notificationSettingsProxy.setCallEnabledEnabledCalled)
|
||||||
|
XCTAssertEqual(notificationSettingsProxy.setCallEnabledEnabledReceivedEnabled, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testToggleCallsOn() async throws {
|
||||||
|
notificationSettingsProxy.isCallEnabledReturnValue = false
|
||||||
|
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||||
|
.first(where: { $0 != nil }))
|
||||||
|
viewModel.fetchInitialContent()
|
||||||
|
try await deferredInitialFetch.fulfill()
|
||||||
|
|
||||||
|
context.callsEnabled = true
|
||||||
|
let deferred = deferFulfillment(notificationSettingsProxy.callbacks
|
||||||
|
.first(where: { $0 == .settingsDidChange }))
|
||||||
|
context.send(viewAction: .callsChanged)
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
XCTAssert(notificationSettingsProxy.setCallEnabledEnabledCalled)
|
||||||
|
XCTAssertEqual(notificationSettingsProxy.setCallEnabledEnabledReceivedEnabled, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testToggleCallsFailure() async throws {
|
||||||
|
notificationSettingsProxy.setCallEnabledEnabledThrowableError = NotificationSettingsError.Generic(message: "error")
|
||||||
|
notificationSettingsProxy.isCallEnabledReturnValue = false
|
||||||
|
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||||
|
.first(where: { $0 != nil }))
|
||||||
|
viewModel.fetchInitialContent()
|
||||||
|
try await deferredInitialFetch.fulfill()
|
||||||
|
|
||||||
|
context.callsEnabled = true
|
||||||
|
let deferred = deferFulfillment(context.$viewState.map(\.applyingChange)
|
||||||
|
.removeDuplicates()
|
||||||
|
.collect(3)
|
||||||
|
.first())
|
||||||
|
context.send(viewAction: .callsChanged)
|
||||||
|
let states = try await deferred.fulfill()
|
||||||
|
|
||||||
|
XCTAssertEqual(states, [false, true, false])
|
||||||
|
XCTAssertNotNil(context.alertInfo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user