diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt index 5cfc14545e..b14dc53b56 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/NotificationSettingsPresenter.kt @@ -29,7 +29,8 @@ import androidx.compose.runtime.setValue import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.architecture.runCatchingUpdatingState +import io.element.android.libraries.architecture.runUpdatingState +import io.element.android.libraries.architecture.runUpdatingStateNoSuccess import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.RoomNotificationMode @@ -74,7 +75,7 @@ class NotificationSettingsPresenter @Inject constructor( LaunchedEffect(Unit) { fetchSettings(matrixSettings) - observeNotificationSettings(matrixSettings) + observeNotificationSettings(matrixSettings, changeNotificationSettingAction) } // List of PushProvider -> Distributor @@ -172,11 +173,15 @@ class NotificationSettingsPresenter @Inject constructor( } @OptIn(FlowPreview::class) - private fun CoroutineScope.observeNotificationSettings(target: MutableState) { + private fun CoroutineScope.observeNotificationSettings( + target: MutableState, + changeNotificationSettingAction: MutableState>, + ) { notificationSettingsService.notificationSettingsChangeFlow .debounce(0.5.seconds) .onEach { fetchSettings(target) + changeNotificationSettingAction.value = AsyncAction.Uninitialized } .launchIn(this) } @@ -238,21 +243,21 @@ class NotificationSettingsPresenter @Inject constructor( } private fun CoroutineScope.setAtRoomNotificationsEnabled(enabled: Boolean, action: MutableState>) = launch { - suspend { - notificationSettingsService.setRoomMentionEnabled(enabled).getOrThrow() - }.runCatchingUpdatingState(action) + action.runUpdatingStateNoSuccess { + notificationSettingsService.setRoomMentionEnabled(enabled) + } } private fun CoroutineScope.setCallNotificationsEnabled(enabled: Boolean, action: MutableState>) = launch { - suspend { - notificationSettingsService.setCallEnabled(enabled).getOrThrow() - }.runCatchingUpdatingState(action) + action.runUpdatingStateNoSuccess { + notificationSettingsService.setCallEnabled(enabled) + } } private fun CoroutineScope.setInviteForMeNotificationsEnabled(enabled: Boolean, action: MutableState>) = launch { - suspend { - notificationSettingsService.setInviteForMeEnabled(enabled).getOrThrow() - }.runCatchingUpdatingState(action) + action.runUpdatingStateNoSuccess { + notificationSettingsService.setInviteForMeEnabled(enabled) + } } private fun CoroutineScope.setNotificationsEnabled(userPushStore: UserPushStore, enabled: Boolean) = launch { diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt index 04accef4ea..3f1f62aafc 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt @@ -29,7 +29,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.architecture.runCatchingUpdatingState +import io.element.android.libraries.architecture.runUpdatingStateNoSuccess import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService import io.element.android.libraries.matrix.api.room.RoomNotificationMode @@ -73,7 +73,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( val localCoroutineScope = rememberCoroutineScope() LaunchedEffect(Unit) { fetchSettings(mode) - observeNotificationSettings(mode) + observeNotificationSettings(mode, changeNotificationSettingAction) observeRoomSummaries(roomsWithUserDefinedMode) displayMentionsOnlyDisclaimer = !notificationSettingsService.canHomeServerPushEncryptedEventsToDevice().getOrDefault(true) } @@ -102,11 +102,15 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( } @OptIn(FlowPreview::class) - private fun CoroutineScope.observeNotificationSettings(mode: MutableState) { + private fun CoroutineScope.observeNotificationSettings( + mode: MutableState, + changeNotificationSettingAction: MutableState>, + ) { notificationSettingsService.notificationSettingsChangeFlow .debounce(0.5.seconds) .onEach { fetchSettings(mode) + changeNotificationSettingAction.value = AsyncAction.Uninitialized } .launchIn(this) } @@ -139,10 +143,12 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( } private fun CoroutineScope.setDefaultNotificationMode(mode: RoomNotificationMode, action: MutableState>) = launch { - suspend { + action.runUpdatingStateNoSuccess { // On modern clients, we don't have different settings for encrypted and non-encrypted rooms (Legacy clients did). - notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, mode = mode, isOneToOne = isOneToOne).getOrThrow() - notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne).getOrThrow() - }.runCatchingUpdatingState(action) + notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = true, mode = mode, isOneToOne = isOneToOne) + .map { + notificationSettingsService.setDefaultRoomNotificationMode(isEncrypted = false, mode = mode, isOneToOne = isOneToOne) + } + } } } diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt index 9545f1ab68..3f5f4ac8d0 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/AsyncAction.kt @@ -125,6 +125,24 @@ suspend inline fun MutableState>.runUpdatingState( resultBlock = resultBlock, ) +/** + * Run the given block and update the state accordingly, using only Loading and Failure states. + * It's up to the caller to manage the Success state. + */ +@OptIn(ExperimentalContracts::class) +inline fun MutableState>.runUpdatingStateNoSuccess( + resultBlock: () -> Result, +): Result { + contract { + callsInPlace(resultBlock, InvocationKind.EXACTLY_ONCE) + } + value = AsyncAction.Loading + return resultBlock() + .onFailure { failure -> + value = AsyncAction.Failure(failure) + } +} + /** * Calls the specified [Result]-returning function [resultBlock] * encapsulating its progress and return value into an [AsyncAction] while